Repository avatar
AI Tools
v1.1.4
active

sftp-mcp

io.github.AdversTM/sftp-mcp

SFTP-only file access for AI: browse, edit, transfer and sync files, no shell access needed.

Documentation

sftp-mcp

SFTP for AI assistants. Files only, no shell.

A Model Context Protocol server that lets Claude and other MCP clients browse, edit, transfer and sync files over plain SFTP. It works on chrooted, SFTP-only and shared-hosting accounts where SSH-based servers can't run, and the assistant never gets a shell.

CI npm Node.js License: MIT

Install · Configure · Tools · Security · Contribute


Why sftp-mcp?

Most MCP servers for remote machines are SSH servers that can also move files. They need a shell account and give the assistant command execution. sftp-mcp does the opposite: it only speaks the SFTP protocol.

sftp-mcpTypical SSH MCP server
Works with SFTP-only and chrooted accountsYesUsually needs a shell
Assistant can run arbitrary commandsNoYes
Read-only, no-delete and path limits per serverBuilt inVaries
Many hosts from one processYesVaries
Parallel transfers over one warm connection per hostYesVaries
Directory sync that uploads only changed filesYesVaries

The only remote command it ever sends is a fixed sha256sum for hashing. If the account has no shell, it hashes over SFTP instead.

Pick sftp-mcp when the assistant should work with files: deploying a site, editing configs, pulling logs or syncing a build. Pick an SSH server when it needs to run commands.

Installation

Requires Node.js 24 or newer. Most MCP clients can start the server straight from npm, with nothing to install:

npx -y sftp-mcp

Or install it globally and use the sftp-mcp command:

npm install -g sftp-mcp

Quick start

Add the server to your MCP client. For Claude Desktop, edit claude_desktop_config.json:

{
  "mcpServers": {
    "sftp": {
      "command": "npx",
      "args": ["-y", "sftp-mcp"],
      "env": {
        "SFTP_HOST": "sftp.example.com",
        "SFTP_USER": "deploy",
        "SFTP_PRIVATE_KEY": "/home/me/.ssh/id_ed25519"
      }
    }
  }
}

On Windows, npx may need to go through cmd: "command": "cmd", "args": ["/c", "npx", "-y", "sftp-mcp"].

For Claude Code:

claude mcp add sftp -e SFTP_HOST=sftp.example.com -e SFTP_USER=deploy \
  -e SFTP_PRIVATE_KEY=$HOME/.ssh/id_ed25519 -- npx -y sftp-mcp

Restart the client and ask it something like "list /var/www on the sftp server".

Configuration

All configuration comes from environment variables. Invalid values stop the server at startup with a clear error, so a typo never quietly loosens a policy.

Single server

VariableDefaultDescription
SFTP_HOSTrequiredSSH host
SFTP_PORT22SSH port
SFTP_USERrequiredSSH username
SFTP_PASSWORDPassword authentication
SFTP_PRIVATE_KEYPath to a private key file
SFTP_PASSPHRASEPassphrase for the private key

The server is registered with the id default.

Multiple servers

Describe the servers as a JSON map of id to connection. Pass it inline in SFTP_SERVERS, or as a file path in SFTP_SERVERS_FILE. You can't set both.

{
  "prod": {
    "host": "prod.example.com",
    "username": "deploy",
    "privateKey": "/home/me/.ssh/id_ed25519",
    "allowedWritePrefix": "/var/www",
    "allowDestructive": false,
    "description": "Production web"
  },
  "staging": {
    "host": "staging.example.com",
    "port": 2222,
    "username": "ci",
    "password": "secret"
  }
}
FieldDescription
host, username (alias user)Required
portDefaults to 22
password, privateKey, passphraseCredentials. privateKey is a local file path
readOnly, allowDestructive, readyTimeoutPer-server overrides of the global settings below
allowedReadPrefix, allowedWritePrefixPath restrictions, see Access control
descriptionShown to the assistant in sftp_list_servers

Ids must match [A-Za-z0-9][A-Za-z0-9_.-]*. Unknown fields stop startup. The single-server variables can't be combined with a server map.

Global settings

VariableDefaultDescription
SFTP_READ_ONLYfalseDeny every remote mutation
SFTP_ALLOW_DESTRUCTIVEtruefalse denies delete, rmdir, rename and non-dry-run sync
SFTP_ALLOWED_WRITE_PREFIXAbsolute path prefix for mutations (single-server mode)
SFTP_ALLOWED_READ_PREFIXAbsolute path prefix for reads (single-server mode)
SFTP_DISABLED_TOOLSComma-separated tool names to hide from the client
SFTP_READY_TIMEOUT20000SSH handshake timeout, in ms
SFTP_MAX_READ_BYTES1048576Size limit for sftp_read. Larger files must use sftp_download
SFTP_MAX_CONCURRENCY8Default parallelism of the batch tools
SFTP_TRANSFER_CONCURRENCY64In-flight requests per file transfer
SFTP_CHUNK_SIZE32768Transfer chunk size in bytes (minimum 1024)
SFTP_LOG_FILEAlso append logs to this file, for clients that hide stderr. Its directory must exist

Booleans must be exactly true or false.

Access control

Policies apply to each server separately and are checked before any SFTP request is sent.

PolicyEffect
readOnly: trueBlocks every mutation, including dry-run sync. Overrides everything else.
allowDestructive: falseWrites, uploads and mkdir still work. Delete, rmdir, rename/move and non-dry-run sync are blocked.
allowedWritePrefixMutations only inside this path. Both ends of a rename are checked.
allowedReadPrefixListing, reading, downloads, stat and hashing only inside this path.

Prefixes are normalized and compared as POSIX paths: /srv/app allows /srv/app and /srv/app/x, but not /srv/app-old. A prefix of / allows everything.

[!IMPORTANT] Prefix checks are lexical. A symlink on the server can still point outside the allowed prefix. For hard isolation, use a chroot, account permissions, or SFTP server configuration.

To hide tools from the assistant entirely, set SFTP_DISABLED_TOOLS, for example sftp_delete,sftp_sync_dir. Hidden tools are never registered, so they never appear in the client's tool list.

Tools

Every tool except sftp_list_servers takes a server argument (one of the configured ids).

ToolKindDescription
sftp_list_serversdiscoveryConfigured servers with their address, policy and description
sftp_listreadList a directory (type, size, mtime, name)
sftp_readreadRead a text file, up to SFTP_MAX_READ_BYTES
sftp_statreadFile metadata
sftp_existsreadfalse, or the entry type: d, - or l
sftp_sha256readSHA-256 via sha256sum on the server, falling back to SFTP streaming
sftp_downloadreadDownload a remote file to a local path
sftp_download_manyreadParallel downloads
sftp_writewriteCreate or overwrite a text file from inline content
sftp_uploadwriteUpload a local file (binary-safe, parallel chunks)
sftp_mkdirwriteCreate a directory, optionally recursive
sftp_write_manywriteParallel text writes
sftp_upload_manywriteParallel uploads
sftp_sync_dirwriteUpload missing, changed or newer files. Can also delete remote files missing locally. Supports dryRun
sftp_deletedestructiveDelete a file
sftp_rmdirdestructiveRemove a directory, optionally recursive
sftp_renamedestructiveRename or move
sftp_delete_manydestructiveParallel deletes

Batch tools report each failure individually, so one bad file doesn't abort the batch.


Development

Requires Bun 1.4+ for tooling. The published package runs on plain Node.js 24+.

git clone https://github.com/AdversTM/sftp-mcp.git
cd sftp-mcp
bun install

bun run test        # unit tests (vitest)
bun run typecheck   # strict TypeScript
bun run check       # Biome lint + format check (bun run format to fix)
bun run build       # compile to dist/ for Node.js

Project layout

src/
  index.ts            entry point: config, transport, lifecycle
  config.ts           env / JSON parsing and validation (zod)
  server.ts           MCP registration: tool descriptions + input schemas
  tool-catalog.ts     canonical list of tool names
  tools-read.ts       read-only tool handlers
  tools-write.ts      mutation tool handlers
  tools-batch.ts      parallel batch tools and directory sync
  paths.ts            path normalization and access-policy guards
  sftp-connection.ts  ssh2 provider: warm connection, retry, hashing
  sftp-operations.ts  shared helpers over the provider interface
test/                 vitest suites, using an in-memory SFTP provider
scripts/
  smoke-test.mjs      end-to-end check against a real SFTP server

Adding a tool

  1. Add the name to toolNames in src/tool-catalog.ts.
  2. Add a description and a zod input schema in src/server.ts. The compiler enforces both through Record<ToolName, …>.
  3. Implement the handler in the matching tools-*.ts module. Before touching the provider, run every remote path through deps.guards.guardRead, guardWrite or guardDestructive.
  4. Cover it in test/tools.test.ts.

End-to-end smoke test

Run the server against a disposable SFTP container:

docker run -d --rm --name sftp -p 127.0.0.1:2222:22 atmoz/sftp "smoke:smoke:::upload"
SFTP_HOST=127.0.0.1 SFTP_PORT=2222 SFTP_USER=smoke SFTP_PASSWORD=smoke \
  node scripts/smoke-test.mjs node dist/index.js

Contributing

Contributions are welcome. Please open an issue before starting large changes, and make sure test, typecheck and check pass.

License

MIT © Damian Szuster

NPM
sftp-mcp
Install Command
npm install sftp-mcp
Runtime: npx