Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.6.2] - 2026-07-26

### Fixed

**The PyPI project page had no description at all.** `pyproject.toml` declared no `readme`, so the built wheel carried a zero-length long description and https://pypi.org/project/blc-ssh-mcp/ rendered an empty body — just the name and a one-line summary. `readme = "README.md"` fixes it. Package metadata cannot be edited on PyPI and published files are immutable, so correcting this requires a release; that is the only reason 0.6.2 exists.

**Seven README links would have 404'd once rendered on PyPI.** PyPI resolves relative Markdown links against `pypi.org`, not the repository, so `[LICENSE](LICENSE)`, `[CHANGELOG.md](CHANGELOG.md)`, `[SECURITY.md](SECURITY.md)`, `[CONTRIBUTING.md](CONTRIBUTING.md)`, `[compose.yaml](compose.yaml)` and `config/servers.example.toml` are now absolute. Keep any new README link absolute for the same reason.

### Changed

- **Summary rewritten** from "SSH MCP server for managing infrastructure via Claude Code" to "MCP server giving AI assistants SSH access to run commands, transfer files, and manage server fleets" — the old one named a single MCP client and omitted what the server actually does.
- **`license` migrated to the PEP 639 SPDX form** (`license = "MPL-2.0"` plus `license-files`) from the deprecated `{text = "..."}` table, and the correspondingly deprecated `License :: OSI Approved ::` classifier was dropped. The published `License-File: LICENSE` metadata is unchanged.
- **More project links on PyPI**: Changelog, Documentation, Security Policy and Container Image, alongside the existing Homepage/Repository/Issues.
- Added the `System Administrators` audience classifier and the `sftp`, `devops`, `automation` keywords.

## [0.6.1] - 2026-07-26

### Changed — BREAKING (install command)
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

SSH MCP server that lets AI assistants execute commands on remote servers.

[![License: MPL-2.0](https://img.shields.io/badge/License-MPL--2.0-brightgreen.svg)](LICENSE)
[![License: MPL-2.0](https://img.shields.io/badge/License-MPL--2.0-brightgreen.svg)](https://github.com/blackaxgit/ssh-mcp/blob/main/LICENSE)
[![Claude Code Ready](https://img.shields.io/badge/Claude_Code-Auto_Install_Ready-blueviolet?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48dGV4dCB4PSIwIiB5PSIxMyIgZm9udC1zaXplPSIxNCI+8J+UpTwvdGV4dD48L3N2Zz4=)](#add-to-claude-code)

## What is this
Expand Down Expand Up @@ -65,7 +65,7 @@ services:
- ~/.ssh:/home/sshmcp/.ssh:ro
```

The image uses a non-root `sshmcp` user (uid 1000). Mount your SSH keys and config file read-only. [compose.yaml](compose.yaml) in the repo carries a fuller example — it builds from the local Dockerfile rather than pulling the published image, and includes the HTTP-transport service, `ulimits` and healthcheck guidance omitted above.
The image uses a non-root `sshmcp` user (uid 1000). Mount your SSH keys and config file read-only. [compose.yaml](https://github.com/blackaxgit/ssh-mcp/blob/main/compose.yaml) in the repo carries a fuller example — it builds from the local Dockerfile rather than pulling the published image, and includes the HTTP-transport service, `ulimits` and healthcheck guidance omitted above.

### Create a config file

Expand Down Expand Up @@ -349,7 +349,7 @@ Note two things about the timeouts, because the names invite confusion:

`max_parallel_hosts` bounds the whole process, not a single call: the semaphore is built once at startup, so concurrent `execute_on_group` calls share the same budget rather than each getting their own.

Per-server overrides (`hostname`, `port`, `user`, `identity_file`, `jump_host`, `default_dir`, `timeout`) take precedence over `~/.ssh/config`. See [config/servers.example.toml](config/servers.example.toml) for the annotated reference.
Per-server overrides (`hostname`, `port`, `user`, `identity_file`, `jump_host`, `default_dir`, `timeout`) take precedence over `~/.ssh/config`. See [config/servers.example.toml](https://github.com/blackaxgit/ssh-mcp/blob/main/config/servers.example.toml) for the annotated reference.

Restrict config file permissions to your user:

Expand Down Expand Up @@ -404,7 +404,7 @@ ASCII control characters (null bytes, newlines, `\x01..\x1f`, `\x7f`) are normal

> **Known limitation: command OUTPUT is NOT redacted.** If you run `cat /etc/mysql/my.cnf`, `env | grep PASSWORD`, or `kubectl get secret X -o yaml`, the stdout/stderr returned to the MCP client will contain plaintext secrets. The redaction pipeline only filters the COMMAND string (what you asked to run), not the OUTPUT (what it printed). Avoid running commands that print secrets via ssh-mcp — pass credentials through env vars, Docker/K8s secrets, or dedicated config files instead.

**Local path confinement (new in 0.6.0; versions ≤ 0.5.6 are affected by the flaw it fixes — see [CHANGELOG.md](CHANGELOG.md)).** SFTP `upload_file` and `download_file` no longer accept arbitrary absolute local paths. Every local path is **relative to a configured transfer root** and is resolved one component at a time beneath it, refusing a symbolic link at *any* component:
**Local path confinement (new in 0.6.0; versions ≤ 0.5.6 are affected by the flaw it fixes — see [CHANGELOG.md](https://github.com/blackaxgit/ssh-mcp/blob/main/CHANGELOG.md)).** SFTP `upload_file` and `download_file` no longer accept arbitrary absolute local paths. Every local path is **relative to a configured transfer root** and is resolved one component at a time beneath it, refusing a symbolic link at *any* component:

```toml
[settings]
Expand Down Expand Up @@ -436,7 +436,7 @@ For production log aggregation, set `SSH_MCP_LOG_FORMAT=json` to emit single-lin

When running in Docker, capture stderr with `docker logs` for the audit trail.

For vulnerability reports, see [SECURITY.md](SECURITY.md). Do not open public GitHub issues for security concerns.
For vulnerability reports, see [SECURITY.md](https://github.com/blackaxgit/ssh-mcp/blob/main/SECURITY.md). Do not open public GitHub issues for security concerns.

## Development

Expand All @@ -448,12 +448,12 @@ uv run pytest
uv run ruff check src/ tests/
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on making changes and submitting pull requests.
See [CONTRIBUTING.md](https://github.com/blackaxgit/ssh-mcp/blob/main/CONTRIBUTING.md) for guidelines on making changes and submitting pull requests.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).
See [CHANGELOG.md](https://github.com/blackaxgit/ssh-mcp/blob/main/CHANGELOG.md).

## License

Mozilla Public License 2.0. See [LICENSE](LICENSE).
Mozilla Public License 2.0. See [LICENSE](https://github.com/blackaxgit/ssh-mcp/blob/main/LICENSE).
23 changes: 19 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@
# container image stays ghcr.io/blackaxgit/ssh-mcp.
name = "blc-ssh-mcp"
dynamic = ["version"]
description = "SSH MCP server for managing infrastructure via Claude Code"
description = "MCP server giving AI assistants SSH access to run commands, transfer files, and manage server fleets"
# Without `readme` there is NO long description in the wheel METADATA, and the
# PyPI project page renders an empty body — which is exactly what 0.6.1 shipped.
# The README is written to survive being rendered off-GitHub: every link in it
# is absolute, because PyPI resolves relative links against pypi.org and they
# would 404. Keep it that way when editing README.md.
readme = "README.md"
requires-python = ">=3.11"
license = {text = "MPL-2.0"}
# PEP 639 SPDX expression, not the legacy `{text = "..."}` table (deprecated,
# and setuptools/hatchling both warn on it). `license-files` replaces the
# `License :: OSI Approved ::` classifier, which PEP 639 also deprecates — do
# not add that classifier back alongside this.
license = "MPL-2.0"
license-files = ["LICENSE"]
authors = [
{name = "SSH MCP Contributors"},
]
keywords = ["mcp", "ssh", "claude", "infrastructure"]
keywords = ["mcp", "ssh", "claude", "infrastructure", "sftp", "devops", "automation"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Intended Audience :: System Administrators",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -53,6 +64,10 @@ ssh-mcp = "ssh_mcp.server:main"
Homepage = "https://github.com/blackaxgit/ssh-mcp"
Repository = "https://github.com/blackaxgit/ssh-mcp"
Issues = "https://github.com/blackaxgit/ssh-mcp/issues"
Changelog = "https://github.com/blackaxgit/ssh-mcp/blob/main/CHANGELOG.md"
Documentation = "https://github.com/blackaxgit/ssh-mcp#readme"
"Security Policy" = "https://github.com/blackaxgit/ssh-mcp/blob/main/SECURITY.md"
"Container Image" = "https://github.com/blackaxgit/ssh-mcp/pkgs/container/ssh-mcp"

[project.optional-dependencies]
dev = [
Expand Down
2 changes: 1 addition & 1 deletion src/ssh_mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""SSH MCP Server - Manage infrastructure via MCP clients like Claude Code."""

__version__ = "0.6.1"
__version__ = "0.6.2"
Loading