ftp-ssh-mcp
Provides a MySQL query tool that runs arbitrary SQL over SSH to the remote host's mysql client, enabling database operations on the configured MySQL database.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ftp-ssh-mcpList files in the remote /public_html directory"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ftp-ssh-mcp
An MCP server for working with a single remote host over FTP, FTPS, SFTP, and SSH — the kind of host a typical shared-hosting plan or a plain VPS gives you, not a cloud provider's API. It groups its tools into three capability classes: file transfer (list, upload, download, mkdir, delete), shell execution over SSH, and a MySQL query wrapper that also runs over SSH. File transfer is the only capability enabled by default — shell execution and database access are both opt-in and stay off until you explicitly turn them on. Treat those two as equally dangerous: mysql_query runs arbitrary SQL on a production database and can escape to a shell, so it is not the milder of the pair.
Install
Published to npm and run with npx — there is nothing to clone or build. Requires Node 20 or newer. Works on Windows, macOS and Linux.
1. Add it to your MCP client
On Windows, use the
cmd /cform shown for each client below. Most clients spawncommanddirectly rather than through a shell, andnpxon Windows is a.cmdshim that cannot be executed that way — the server simply fails to start, usually with no useful error. Treat this as the rule on Windows, not an edge case.
{
"mcpServers": {
"remote": { "command": "npx", "args": ["-y", "ftp-ssh-mcp@1"] }
}
}Windows:
{
"mcpServers": {
"remote": { "command": "cmd", "args": ["/c", "npx", "-y", "ftp-ssh-mcp@1"] }
}
}OS | Path |
Windows |
|
macOS |
|
Linux |
|
Same mcpServers shape as Claude Code. Claude Desktop has no project directory, so set MCP_ENV_FILE to an absolute path — see step 2.
Same mcpServers shape as Claude Code.
VS Code uses servers, not mcpServers:
{
"servers": {
"remote": { "command": "npx", "args": ["-y", "ftp-ssh-mcp@1"] }
}
}Codex uses TOML with a snake_case mcp_servers table:
[mcp_servers.remote]
command = "npx"
args = ["-y", "ftp-ssh-mcp@1"]Windows:
[mcp_servers.remote]
command = "cmd"
args = ["/c", "npx", "-y", "ftp-ssh-mcp@1"]Or add it from the shell: codex mcp add remote -- npx -y ftp-ssh-mcp@1
2. Point it at a host
Create a .env file in your project root. The server reads it from the working directory, which is the project root for project-scoped clients:
REMOTE_HOST=ftp.example.com
REMOTE_USER=deploy
REMOTE_PASSWORD=your-password
REMOTE_BASE_DIR=/home/deploy/public_htmlThat is enough for the six file tools over FTPS. Keep this file out of version control — add .env and .env.* to .gitignore. Getting credentials out of a tracked config file is the reason the server reads .env at all.
If your client has no project directory (Claude Desktop), or you want several servers against different accounts, point each at its own file instead:
{
"mcpServers": {
"prod": {
"command": "npx",
"args": ["-y", "ftp-ssh-mcp@1"],
"env": { "MCP_ENV_FILE": "/absolute/path/to/.env.prod" }
}
}
}A fuller setup, adding shell execution and database access — both off unless you opt in:
REMOTE_HOST=example.com
REMOTE_USER=deploy
REMOTE_PASSWORD=your-password
REMOTE_BASE_DIR=/home/deploy/public_html
# Pin the host key. Without this, any host key is accepted — see Security.
SSH_HOST_FINGERPRINT=SHA256:your-fingerprint-here
SSH_PORT=22
SSH_BASE_DIR=/home/deploy
SSH_ALLOW_EXEC=true
SSH_ALLOWED_CMDS=@basic,@node
# Runs the mysql client on the host over SSH. At least as dangerous as
# SSH_ALLOW_EXEC — read the Security section before enabling.
DB_USER=deploy_dbuser
DB_PASSWORD=your-db-password
DB_NAME=deploy_db
# Off by default; required for file_delete.
REMOTE_ALLOW_DELETE=trueSee .env.example for every variable and Configuration below for what each does.
3. Check it before you rely on it
npx -y ftp-ssh-mcp@1 --selftestRun it from the directory holding your .env. It resolves configuration, registers tools and prints a one-line summary — without opening a connection — then exits. Use it to confirm the right capabilities came up before wiring the server into a client:
ftp-ssh-mcp 1.0.1 selftest OK. env=.env, transport=ftp, host=example.com,
capabilities=[files, ssh, mysql], tools=[file_list, file_upload, ...]It exits non-zero with a message naming the missing variable if configuration is incomplete. See Troubleshooting.
Options
Option | Does |
| Resolve the configuration, print the tools it would register, and exit. Opens no connection. |
| Drop the informational startup warnings. |
| Print the version and exit. |
| Print usage and exit. |
--version and --help answer before any configuration is read, so they work on a machine with nothing set up yet. Both print to stdout; everything else the server says goes to stderr, because stdout is the JSON-RPC channel.
--quiet silences the warnings about a missing DB_PASSWORD, an MCP_CAPABILITIES entry that registered no tools, and a relative base directory. It deliberately does not silence the two that say your security posture is weaker than you might assume — an unverified host key, or path confinement switched off. A flag set once in a client's config file is never looked at again, so those stay visible until you fix them, which is the only silence worth having.
Related MCP server: MCP SSH Manager
Capabilities
A capability's tools are only registered when it is both configured (its required variables are set) and, if MCP_CAPABILITIES restricts the set, named in that list.
Capability | Tools | Enabled when |
|
| An FTP or SSH profile is configured. |
|
| An SSH profile is configured with |
|
| An SSH profile is configured, and |
The files tools are transport-neutral: they work over whichever transport FILE_TRANSPORT selects (ftp or sftp), and each call can override the transport for that one call if both profiles are configured. mysql_query is a convenience wrapper, not a database driver — it pipes SQL over SSH to the mysql client already installed on the host (mysql --user=... --database=... --table, with the SQL sent on stdin), then parses the CLI's table output. There is no connection pooling and no parameterised-query support. It exists because most shared hosts will not accept a database connection from outside the host itself; if you need a real client-side database integration, use one instead of mysql_query. Enable it with the same caution as ssh_exec, not less — see Security.
Configuration
Every setting is resolved from environment variables in this order: a profile-specific variable (FTP_* or SSH_*) first, then the shared REMOTE_* fallback, then a built-in default. Two independent profiles exist — FTP and SSH — and you may configure either, both, or neither in isolation (though at least one is required to start).
Variables are normally read from process environment, but the server also loads a .env file from the current working directory before resolving configuration, so credentials can live outside the MCP client's own (often tracked) config file. A real environment variable already set takes precedence over the .env file. Point at a different file with MCP_ENV_FILE — a leading ~ is expanded to your home directory, a relative value is resolved against the working directory, and a set-but-unreadable one fails startup rather than silently falling back to .env and loading different credentials than the ones you named. The .env parser does not support inline comments — KEY=value # note treats everything after = as the value, including # note, so keep comments on their own line.
Secret inheritance
Non-secret settings (HOST, USER, BASE_DIR) fall back to REMOTE_* freely. Secrets (PASSWORD, PRIVATE_KEY, PASSPHRASE) fall back to REMOTE_PASSWORD only when the profile does not set its own *_USER. A profile that names its own user is declaring a distinct identity, and sending that identity a password meant for a different account causes repeated authentication failures — on many shared hosts, enough failures trip brute-force protection and lock the account out at the host level. If FTP_USER (or SSH_USER) is set, its password must be set explicitly too.
Variables
Variable | Default | Notes |
| — | Shared host, used when a profile does not set its own. |
| — | Shared user. |
| — | Shared secret. Only inherited by a profile with no |
| — | Shared base directory that remote paths are confined to. A leading |
| inherits | |
|
| |
| inherits | |
| see secret inheritance | |
| inherits | Base directory the |
|
|
|
|
| Set |
|
| |
| inherits | |
|
| |
| inherits | |
| see secret inheritance | Password auth. Can be combined with a key; the server will try both. |
| see secret inheritance | Local path to a private key. A leading |
| see secret inheritance | Passphrase for |
| inherits | SHA-256 host key fingerprint to pin. Accepts the |
| inherits | Required for |
| — | Remote path to a script (e.g. a Node virtualenv's |
|
| Connection and per-command timeout. |
|
| Combined stdout+stderr byte cap per command; output beyond this is truncated, not buffered. |
|
| Comma-separated allowlist of program names |
| — | Must be set explicitly. Unlike other non-secrets it does not inherit |
| see secret inheritance | See below — not required. |
| — | Default database for |
|
| Which profile serves the |
| all configured capabilities | Comma-separated allowlist restricting which capabilities register tools ( |
|
| When |
|
| Must be |
|
| Must be |
Command presets
SSH_ALLOWED_CMDS entries starting with @ expand to a named group; presets and literal command names mix freely (SSH_ALLOWED_CMDS=@basic,@php,rsync). An unknown preset name fails startup. Presets are named after stacks, not hosting products, and expand as follows:
Preset | Commands |
|
|
|
|
|
|
|
|
A DB_USER with no resolvable DB_PASSWORD is not treated as an error: the remote mysql client can legitimately get credentials from ~/.my.cnf or a trusted local socket instead of a password on the connection. The server prints a warning to stderr at startup rather than refusing to run, and if a query then fails to authenticate, mysql_query's error message names DB_PASSWORD again so the cause is visible from inside the MCP client, not just in a startup log you may not have seen.
Tilde base directories
~ and ~/<dir> are accepted and expanded to the account's own login directory: realpath(".") over SFTP, PWD over FTP, for the files tools. That lookup happens on connect, and only when there is a ~ to expand, so an absolute base directory costs nothing extra and the server still needs no network at startup.
ssh_exec and mysql_query resolve the same setting a different way: the raw ~ is handed to the remote login shell, which expands $HOME itself on every command, so there is no connect-time lookup on that path at all. The two mechanisms agree in practice, since they resolve against the same logged-in account — only when and how the expansion happens differ.
Each profile expands its own value against its own login directory. That is deliberate: on many shared hosts the FTP account is chrooted so that its ~ is /, while SSH sees the real /home/<user> — so a ~/public_html shared through REMOTE_BASE_DIR lands in the right place on both.
A named home, ~other/site, is refused at startup — but only when the files tools are registered; a config that excludes them (e.g. MCP_CAPABILITIES=ssh) starts fine and only fails later, the first time ssh_exec or mysql_query tries to cd there. Only the account that logged in can be located, so a named home would be sent to the server as a literal path. Use an absolute path for that case.
Security
ssh_execonly runs a program named inSSH_ALLOWED_CMDS, checked against the first whitespace-delimited token of the command — not a policy over arguments.Before that check, the command is rejected outright if it contains any of a fourteen-character shell metacharacter class (
; & |$(){}<>\and newline/carriage return), so a call can only ever be a single plain invocation — no chaining, redirection, or substitution.Remote paths passed to the
filestools and toSSH_BASE_DIR/FTP_BASE_DIRare resolved and confined to the profile's configured base directory;..segments are rejected outright. Confinement is per profile, and a per-calltransportoverride is confined to the profile it selects, not to the default one. A profile with no base directory at all is not confined — there is nothing to confine it to — so each profile falls back to the other's base directory when it has none of its own, and the server warns on stderr at startup for any transport that still ends up without one.Deletes (
REMOTE_ALLOW_DELETE) and shell execution (SSH_ALLOW_EXEC) are both off by default and must be turned on explicitly.Pin the SSH host key with
SSH_HOST_FINGERPRINT. Unless it is set, the SSH and SFTP connections accept whatever host key they are offered — there is noknown_hoststo fall back on — so anything on the network path can present its own key and be handed the account's password. When the variable is set, a mismatched key aborts the connection with an error naming both the expected and the received fingerprint; when it is unset, the server says so in a startup warning on stderr. Get the value from the host you trust with:ssh-keyscan -t rsa your-host.example.com | ssh-keygen -lf -and copy the
SHA256:…field (a bare hex digest is also accepted). A fingerprint that cannot be parsed is a startup error rather than a silent fallback to "no verification".mysql_querynever builds a shell string from your SQL — the query is written to themysqlclient's stdin, so it is never interpolated into a command line.DB_PASSWORDnever appears in the host's process list. It is delivered as the first line of the command's stdin, read intoMYSQL_PWDby the remote shell and exported to themysqlprocess — so no argv anywhere carries it, andps auxshows only the harmless command text. The secret exists only in process environments, which are owner-readable. One constraint follows from the framing: a password containing a line break is refused with an explicit error. WhenDB_PASSWORDis unset, no password mechanism runs at all, so host-side credentials such as~/.my.cnfgenuinely take over.mysql_queryis at least as powerful asssh_exec, not a lesser grant. It runs arbitrary SQL against a production database, and themysqlclient's\!command is a shell escape that is honoured on piped input — so\! curl … | shreaches a shell regardless ofSSH_ALLOW_EXECandSSH_ALLOWED_CMDS. The two capabilities are separate switches because they are separate features, not because one is safer; enablemysqlwith the same care asssh.
None of this makes the remote host a sandbox. An allowed command still runs with the full privileges of whichever account is configured, and there is no isolation between what ssh_exec can do and what that account could do logged in directly. The guards constrain the shape of a single call — one program, no shell tricks, paths that stay inside a base directory — they do not constrain what an allowed program itself is capable of once it runs.
Found a security problem? Please report it privately — see SECURITY.md.
Known advisories
npm install ftp-ssh-mcp currently reports two moderate advisories for
@hono/node-server, pulled in transitively by @modelcontextprotocol/sdk. They
concern path traversal in that package's static-file server on Windows. This
server talks JSON-RPC over stdio and never starts an HTTP listener, so that code
path is not reachable here, and the only "fix" npm offers is a breaking downgrade
of the SDK. CI runs npm audit --omit=dev --audit-level=high, which passes today
while still failing the build on anything high or critical. The advisories will
clear when the SDK updates its dependency.
Troubleshooting
Start with npx -y ftp-ssh-mcp@1 --selftest from the directory holding your .env. It reproduces everything the server does at startup except connecting, so it separates a configuration problem from a network or credentials one.
Symptom | Cause |
Server never starts, no error in the client | On Windows, |
| No host or user resolved. Set |
| No |
| The explicitly named env file does not exist or is not readable at that path (resolved against the working directory when relative). This fails startup deliberately — a typo'd path must not silently start the server on whatever |
| The profile sets its own |
A tool you expected is missing | Its capability is not enabled. |
Selftest says |
|
Value from | The parser has no inline-comment support. Put comments on their own line. |
Warning about an unverified host key |
|
Warning that path confinement is disabled | The transport's profile has no base directory and neither does the other one. Set |
Warning that the base directory is relative | The value has no leading |
Uploads land somewhere unexpected | Remote paths are relative to the profile's base directory, and each profile has its own. A per-call |
A |
|
Development
git clone https://github.com/Chris221/ftp-ssh-mcp.git
cd ftp-ssh-mcp
npm install
npm test
npm run selftestnpm test runs the Vitest suite, including capability-registration and transport tests against an in-process FTP server. npm run selftest resolves configuration from the environment (and .env, if present), registers tools, and prints a one-line summary — including the resolved transport and which capabilities registered — without opening a connection or starting the stdio transport.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceMCP server for SSH remote execution, file transfer, and file editing with automatic backup/trash and ~/.ssh/config integration.Last updated1171MIT
- AlicenseAqualityBmaintenanceMCP server for managing remote servers via SSH, enabling command execution, file transfer, rsync, tunnels, health checks, backups, and database operations.Last updated178691MIT
- AlicenseAqualityCmaintenanceA secure remote server management tool based on MCP protocol, supporting SSH connections, command execution, and SFTP file transfers.Last updated20826MIT
- AlicenseBqualityBmaintenanceMCP server for managing SSH tunnels and remote hosts, offering tools for host inventory, file operations, security scanning, and SSH configuration.Last updated81MIT
Related MCP Connectors
Official remote MCP server for Lolipop Rental Server by GMO Pepabo.
MCP server for ScanMalware.com URL scanning, malware detection, and analysis.
Remote MCP server for XDaLa workflow preparation on XGR.Network.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Chris221/ftp-ssh-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server