Skip to content

Releases: developerz-ai/mcp-ssh

v1.2.4

Choose a tag to compare

@sebyx07 sebyx07 released this 27 Jun 13:15

job poll is now newest-first — latest output is the first page

Polling a long-running job used to be forward-paginated (cursor 0 = oldest), so seeing what a job is doing now meant paging to the end or computing cursor = total − limit. Fixed:

  • job(action="poll") with no cursor returns the most recent page. Pass the response's next_cursor to page back through history.
  • Lines stay chronological within a page, so stack traces / build logs read top-to-bottom — only the page window moves newest-first.
  • file read is unchanged (forward, top-to-bottom — a file is read start-to-end).

Internals: new paginate_tail / read_page_tail (byte budget trimmed from the oldest end so the newest line is always kept); saved-tail fallback paginates the same way; tool description, cursor doc, and the overflow/page-back hints updated.

Install: sudo dpkg -i mcp-ssh_1.2.4-1_amd64.deb

v1.2.3

Choose a tag to compare

@sebyx07 sebyx07 released this 27 Jun 12:35

Admin CLI: jobs, job kill, sessions

Local ops surface over the same SQLite state the server uses — for a shell on the host:

  • mcp-ssh jobs [--all] — list running jobs (or all, newest-first, capped at 200).
  • mcp-ssh job kill <id> — SIGTERM then SIGKILL the job's process group.
  • mcp-ssh sessions — summarise durable OAuth tokens (active/expired counts + next expiry). Never prints token material.

Design

  • New src/admin.rs. Commands read/act on SQLite directly and never construct a JobStore — that would start the reaper + startup reconcile and flip the live server's running rows to failed.
  • Jobs now persist their pgid (new nullable column + idempotent migration) so the CLI can signal a job's group even after a server restart. Reuses the reaper's TERM→KILL escalation.
  • config::db_path() resolves just the DB path, so the admin commands work without auth credentials configured.

No changes to the MCP tool surface. Drop-in upgrade.

Install: sudo dpkg -i mcp-ssh_1.2.3-1_amd64.deb

v1.2.2

Choose a tag to compare

@sebyx07 sebyx07 released this 27 Jun 12:15

Fix: clients no longer fail job/file with invalid type ... expected string or map

The action enum was emitted as a JSON-Schema $ref into $defs. Clients (Claude Desktop, codex, n8n) drop $defs, so the $ref resolved to nothing — the model couldn't see action is a string enum and sent a non-string placeholder (null, then true), failing deserialization before dispatch.

  • #[schemars(inline)] on JobAction/FileAction → the enum is now inlined on the property ({"type":"string","enum":[…]}), no $ref/$defs. Documented fix (MCP python-sdk #1373).
  • lenient_action coerces any non-string action — and an omitted key — to the read-only list default instead of dead-ending the call. file stays strict (destructive actions, no safe default).

Supersedes v1.2.1 (null-only tolerance). MCP clients must reconnect/restart to drop the cached old tool schema.

Install: sudo dpkg -i mcp-ssh_1.2.2-1_amd64.deb

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 27 Jun 12:17
chore(release): v1.2.1 — null-tolerant job action deb

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Jun 09:52
feat: SQLite-backed durable state + refresh tokens (v1.2.0)

Durable state moves to a single SQLite database (rusqlite, bundled — still a
single binary) at /var/lib/mcp-ssh/mcp-ssh.db, in a hybrid model: SQLite holds
the low-frequency structured state, while high-frequency live job output keeps
streaming to per-job log files.

OAuth (src/oauth, src/db):
- access + refresh tokens persisted in SQLite, so logins survive a restart
  (the agent self-updates by restarting its own service — previously that
  logged every client out). Access tokens 24h, refresh tokens 1 year, rotated
  on use (RFC 6749 §10.4). grant_type=refresh_token added + advertised; the
  /token response returns a refresh_token. Many tokens coexist → multiple
  clients (desktop, mobile, CLI) stay authenticated independently.

Jobs (src/jobs):
- job metadata + a bounded output tail persisted to SQLite, so job(list) and
  poll survive restarts; live output still streams to log files under
  /var/lib/mcp-ssh/logs/jobs. poll falls back to the saved tail when the live
  log is gone. Reaper runs on startup AND hourly: drops >24h jobs (rows +
  files), trims finished logs to a tail (5000 lines <3h, 500 after), mtime-ages
  orphan files, and marks jobs stuck 'running' across a restart as failed.
- job ids/log filenames now use '-' for the time (job-23-30-07), not ':' —
  colon-free is portable everywhere (Windows/scp/globbing).
- fix: poll() self-deadlocked on a MutexGuard held to the end of an `if let`
  block (temporary-in-scrutinee); bind the lookup to a local first.

config: db_path (MCP_SSH_DB) decoupled from the job-log dir.

Built in parallel by three agents (oauth, jobs, docs) + integration here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.1.5

Choose a tag to compare

@github-actions github-actions released this 27 Jun 08:37
fix: file write/append/move create parent dirs (mkdir -p)

Live QA surfaced it: writing or moving a file under a path whose parent
doesn't exist yet failed with a bare ENOENT the agent then had to diagnose.
`write`, `append`, and `move` now create missing parent directories first, so
writing a new file under a fresh path just works. Adds a directory-redirect
test for `read` while here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.1.4

Choose a tag to compare

@github-actions github-actions released this 27 Jun 08:27
feat: tiered job-log compaction so disk stays bounded over long uptime

The server runs for weeks; finished jobs' logs can't grow forever. The hourly
reaper now compacts each finished job's log to a trailing tail — enough to
debug a failure from a phone, not enough to fill the disk:

- running job  → full log, never touched (still being appended)
- finished <3h → last 5000 lines
- finished ≥3h → last 500 lines
- >24h         → purged (unchanged)

Trimming is idempotent (strips a prior marker before re-measuring, shrink-only)
so hourly passes never erode the tail, and swaps atomically (temp + rename) so a
concurrent poll never reads a half-written log. A marker line records how many
lines were dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.1.3

Choose a tag to compare

@github-actions github-actions released this 27 Jun 08:19
feat: job titles, bounded output everywhere, inline job id + prompt docs

Job titles: `bash` takes an optional `title`; the job id becomes
`<title>-HH:MM:SS` (seconds added) so the agent can tell its own jobs apart
in `job(list)`. Title is normalized to one `[A-Za-z0-9_-]+` path component so
it can't break the `<id>.log` filename or escape the job dir; blank/garbage
falls back to the neutral `job` label. Never derived from `cmd` (secret-safe).

Bounded output: pagination now caps BOTH line count and bytes (64 KB/page,
8 KB/line) in the shared `paginate`, used by `job(poll)`, inline `bash`, and
`file(read)` — a single 10 MB line or a fat page can no longer flood context.
`next_cursor` tracks lines actually returned so byte-capping never desyncs the
cursor. `file(list/grep)` output is byte-capped with a marker. `file(read)`
now saturates its slice and redirects directories to `list`.

Inline job id: a fast command whose output overflows the first page now
returns its job id + cursor so the agent can poll the rest instead of losing
it (previously inline dropped the id).

Docs: docs/prompts/{system-prompt,skill}.md — ready-to-paste prompt + Claude
Code skill for driving the server, referenced from README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.1.2

Choose a tag to compare

@github-actions github-actions released this 27 Jun 07:56
feat: agent self-management — sudo (root) by default

The agent is meant to run its own box: install its own updates, restart
itself, manage services. That needs sudo, which is setuid and blocked by
NoNewPrivileges=true. Flip the shipped unit to NoNewPrivileges=false and
have the installer grant the run user NOPASSWD:ALL in /etc/sudoers.d/mcp-ssh
(validated with visudo). The auth-gated MCP shell is now root-capable by
design; README + docs/deploy.md document the blast radius and the lock-down
path (remove sudoers + set NoNewPrivileges=true) for operators who don't
want it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v1.1.1

Choose a tag to compare

@github-actions github-actions released this 27 Jun 07:43
fix: render job action as flat enum so Claude Desktop can call it

A `///`-documented unit enum makes schemars emit `oneOf` of
`{const, description}`. Claude Desktop mishandles that form and sends an
`action` value serde can't deserialize, so every `job` call fails before
dispatch — visible in the server logs as `tool="bash"` dispatches only,
never `tool="job"`. `bash` (no enum) and `file` (bare `FileAction`, flat
`enum`) were unaffected.

Drop the per-variant doc comments so `JobAction` renders a flat string
`enum` like `FileAction`; the per-action docs already live in the tool and
field descriptions. Regression test locks both action enums to flat `enum`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>