Repository navigation
v0.2.0 — Verified staged transfers, local confinement and a bilingual EN/FR interface
The GitHub tag and release are cut from this commit. Publication to npm and to
the MCP registry is still pending, so npx -y ftp-deploy-mcp and registry-based
installation do not work yet; install from source. Package and server metadata
are aligned at 0.2.0. All changes below are relative to 0.1.0, the only version
ever released.
Added
- Bilingual command line.
--lang enand--lang fr(also--lang=fr) select
the language of general and subcommand help,setupprompts and choices,
connection-test labels,doctordiagnostics,import-filezillawarnings and
server startup messages. The newFTP_MCP_LANGenvironment variable sets the
same default, and the option takes precedence over it, including over an
invalid environment value. The option is read before the subcommand parser and
may appear before or after the subcommand. Onlyenandfrare accepted; an
invalid or incomplete option exits unsuccessfully beforesetupor
import-filezillawrites anything. OS locale variables such asLANGare
never consulted. See languages. - Localized MCP contracts. Tool titles, descriptions, input field descriptions,
business success text and error messages follow the selected language. The ten
tool names, structured result field names and public error codes are stable and
are not translated, and no tool argument selects a language. setupwritesFTP_MCP_LANGinto the MCP client entries it generates,
includingen, so a configured server keeps its setup language after a
restart. The paste-ready Trae snippet carries the same setting.- Structured error envelope on every tool error. Alongside the
isErrortext,
results now carrystructuredContent.errorwithschema_version,code,
message,retryable, a per-callrequest_idUUID,next_actionand
effects. Upload, download and deployment errors can add a strictpartial
block (completed_files,completed_bytes,failed_files, optional
total_files, andfinal).request_idis not a durable operation
identifier; there is no journal, rollback or recovery command. This release
returnsretryable: falsefor every error and never retries an uncertain
mutation. See the error contract. - Six per-server configuration keys, each with an enforced default and maximum.
Tool arguments cannot override them, and values must be positive safe integers:
operationTimeoutMs(default 120000 ms, accepted range 100–3600000),
maxTransferBytes(default 268435456, maximum 1099511627776),
maxDeployFiles(default 10000, maximum 100000),
maxDeployBytes(default 1073741824, maximum 1099511627776),
maxScanEntries(default 100000, maximum 1000000) and
maxScanDepth(default 64, maximum 256). These defaults apply to servers that
do not set them, so a deployment that succeeded on 0.1.0 can now be refused
withTRANSFER_LIMITorSCAN_LIMITbefore any connection is opened.
BREAKING —operationTimeoutMsalso introduces a deadline 0.1.0 never
had: every tool call now aborts withTIMEOUTafter 120000 ms by default,
and unlike the other limits it fires mid-flight rather than refusing up
front, so a large or slowftp_deploythat ran to completion on 0.1.0 can
stop part-way with files already promoted and no rollback. Raise
operationTimeoutMs(up to 3600000) for slow links. See
verified transfers and
resource limits. - Process-wide admission control. One Node isolate admits at most 64 concurrent
tool calls; the 65th returns the newCAPACITY_LIMITwitheffects:none,
retryable:falseandnext_action:retry. This is a concurrency cap, not a
lifetime quota: a slot is released when its worker and cleanup actually settle,
including after cancellation or timeout, and there is no queue or automatic
retry. Admission happens after schema validation and before preparation. - MCP cancellation and per-call deadlines. A peer
notifications/cancelled
aborts the operation and the SDK suppresses its result, so clients must not
expect a deliveredCANCELLEDenvelope; the separate internal deadline
returnsTIMEOUT. Cancellation and expiry stop later stages and close the
transport, but do not undo effects already accepted by the server. A public
transport decorator corrects the installed SDK's cancellation gap for the
numeric correlation ID0and the empty string""; other IDs, including the
string"0", keep native SDK behaviour. - Serialized mutations. Writing tools take a FIFO lock on the remote endpoint
(protocol, host, port, user) andftp_downloadtakes one on the canonical
local destination, so two calls cannot interleave writes on the same target.
Locks are held until the underlying worker actually settles, not until the
response is sent, and they coordinate only within one Node.js process. - Per-server
localRootboundary forftp_upload,ftp_deploy, and
ftp_download. The root must be absolute (with~expansion supported);
traversal and local symlink/junction escapes are refused. - SFTP host-key pinning through
hostKeySha256, accepting one SHA-256
fingerprint or a non-empty array for controlled key rotation. - Explicit
allowUnknownHostKeyoverride for operators who temporarily accept
an unverified SFTP server identity. - Explicit
allowUnsafeRemoteRootoverride for FTP/FTPS operators who accept
the unresolved symlink risk of a client-side sub-root. - MCP output schemas and successful structured responses for every tool except
the intentionally text-onlyftp_read, plus annotations for all ten tools. - Bilingual documentation set, each page in English and French:
security model,
release process,
error contract,
languages,
verified transfers,
resource limits,
state storage,
workflow model and
scripted MCP conformance.CONTRIBUTING,
SECURITY,LICENSEand this changelog gain French counterparts, joining the
READMEpair that already shipped in 0.1.0; bothREADMElanguages were
rewritten for this release. - A bilingual interactive HTML guide under
site/, generated from
site/project-data.jsonandsite/i18n.jsonand verified in CI by
scripts/build-guide.mjs --check. It lives in the repository only and is
excluded from the npm package. - A reproducible, external read-only agent evaluation fixture and 10-question
MCP evaluation set, now in English (evaluations/read-only.xml) and French
(evaluations/read-only.fr.xml), plus a separate scripted conformance runner
of 43 scenarios executed in both languages against the real MCP SDK client and
in-memory transport (npm run eval:scripted). The runner is repository
tooling, is not an LLM benchmark, and is excluded from the npm package. See
scripted MCP conformance. - Internal, inert libraries
src/state/andsrc/workflow/. They ship inside
the package because it publishessrc, but they expose no MCP tool, read no
configuration, create no state directory and are not imported by the running
server. They deliver no deployment workflow, rollback or recovery in this
release; both state storage and the
workflow model state this explicitly. - Contributor-facing npm scripts:
test:transports,test:contract,
test:bounds,test:state,test:workflow,test:eval-runnerand
eval:scripted. CI additionally runsscripts/check-docs.mjsfor bilingual
documentation andtest/release-gates.js. server.jsonMCP registry metadata, declaring theFTP_MCP_CONFIGand
FTP_MCP_LANGenvironment variables and a stdio transport.- A repository
Dockerfileand.dockerignorebuilding anode:24-alpine
image whose entrypoint is the stdio server; mount a configuration file and
pointFTP_MCP_CONFIGat it. No image is published, and neither file is
included in the npm package.
Changed
- BREAKING — Node.js 18, 19, 20 and 21 are no longer supported.
engines.nodemoves from>=18to>=22. CI qualifies Node 22 and 24 on
Linux, Windows and macOS. - BREAKING — an explicit configuration selector now fails closed. When
--config <path>orFTP_MCP_CONFIGis present, that single path is
authoritative: it must exist, be a regular file and load successfully, and no
other candidate is attempted.--configtakes priority over
FTP_MCP_CONFIG, and an empty explicit value is rejected. Only when neither
is set does discovery still try./ftp-servers.jsonthen
~/.ftp-mcp/servers.json. In 0.1.0 both selectors were merely the first
entries of a fallback list, so a missing or unreadable explicit path silently
fell through to another file. The server still starts; every tool that needs
the configuration now fails withCONFIG_INVALIDinstead of operating on an
unintended configuration, andftp_list_serversreturnsstatus: "invalid"
with the loader diagnostic. - One rejected server entry no longer disables the whole configuration. Entries
are validated individually: valid servers stay usable, and each invalid one is
reported by name with its reason throughftp_list_servers(status,
valid_count,invalid_countand a boundederrorslist). Naming a rejected
entry returns that entry'sCONFIG_INVALID, an unknown name
returnsSERVER_UNKNOWN, and omittingserverwith several valid candidates
and nodefaultServerreturnsSERVER_REQUIRED. In 0.1.0 the first invalid
entry made the entire file unusable and every tool call failed. Envelope
problems — noserversobject, an empty one, an unknowndefaultServer, an
unset${ENV:VAR}— still reject the whole file, as does a file in which
every entry is invalid. - BREAKING —
localRootis now required byftp_upload,ftp_deployand
ftp_download, and relative tool paths now resolve against it instead of
the server process's working directory. A server entry without an absolute
localRootrefuses those three tools withCONFIG_INVALID. In 0.1.0
local_pathandlocal_dirresolved againstprocess.cwd(), so an unchanged
call such asftp_deploy {"local_dir":"dist"}now reads<localRoot>/dist
instead of<cwd>/dist— it can silently select a different directory, or
fail withNOT_FOUNDorPATH_REJECTEDwhen the old target sits outside the
root. Absolute paths keep their meaning and must still resolve inside
localRoot.ftp_list_serversreports each server'slocalRootstatus. - Tool errors are no longer text-only. Every error result now carries the
structured{error}envelope described above, and the nine tools with an
output schema publish a JSON Schema draft-07oneOfof their success object
and that envelope.ftp_readkeeps text-only success and no output schema;
its errors still use the same internally validated envelope. Existing text
responses remain available alongside structured successful responses, and
ftp_deploystill reports bounded samples rather than exhaustive file lists. ftp_upload,ftp_deployandftp_downloadnow verify every file before
promoting it. Uploads hash the local source with SHA256, write to an
unpredictable sibling named.ftp-mcp-<random>.tmp, read that temporary back,
compare byte count and digest, then promote with one rename. On failure the
server never deletes the final destination to make promotion succeed and never
falls back to a direct overwrite; it deletes only its own temporary, and a
cleanup failure adds a bounded warning that a temporary may remain. Downloads
read a bounded remote SHA256 expectation first, then promote by same-directory
hard link withoverwrite:false(the default) or by rename with
overwrite:true. The basename pattern.ftp-mcp-*.tmpis reserved and always
excluded fromftp_deploy, even when an explicitincludematches it. This
adds network traffic and latency, does not make a multi-file deployment
atomic, and does not promise universal atomic replacement: FTP and SFTP rename
behaviour depends on the server and filesystem. SFTP records the temporary's
server-assigned mode, restricts it to 0600 while writing, and before promotion
restores the existing destination's permission bits (0777 mask), or that
recorded creation mode for a new file; a required stat or chmod failure
prevents promotion. FTP/FTPS has no portable permission-preservation mechanism
here, so replacing an existing remote file can reset its permissions to the
server's creation defaults. Locally,ftp_downloadcopies an existing
destination's 0777 bits onto its temporary, but a new local file is now
created with mode 0600 rather than 0.1.0's process-umask default, and
promotion with the defaultoverwrite:falseneeds hard-link support in the
destination directory and fails closed without it. See
verified transfers.- Deployment selection is now bounded and asynchronous.
maxScanEntriesand
maxScanDepthcap visited entries and directory depth; exceeding either
refuses the complete selection withSCAN_LIMIT(effects:none,
retryable:false,next_action:fix_input) before any remote connection, for
dry runs as well as real deploys. See
resource limits. - Deployment selection can now include files it previously skipped. Only the
built-innode_modules,.gitand.ftp-mcpdirectory names prune
traversal. Customexcludepatterns andincludepatterns no longer prune
directories; they keep their existing per-file matching rules. This removes a
former directory-probe false positive in which, for example,
exclude: ["**/__ftp_deploy_probe__"]also hiddocs/wanted.txt. Review a
dry run after upgrading if you rely on exclusion patterns. - BREAKING — plain FTP, and FTPS with
insecureTLS: true, are now refused.
0.1.0 had no insecure-transport gate and connected to both without objection.
0.2.0 raisesTRANSPORT_POLICYbefore any network I/O unless the server entry
setsallowInsecure: true, so every 0.1.0"protocol": "ftp"entry — the
default, namesake protocol of this package — stops connecting until that flag
is added. - BREAKING — SFTP connections without
hostKeySha256are refused before
authentication, withHOST_KEY_REJECTED, unlessallowUnknownHostKey: true
is explicitly configured. 0.1.0 performed no host-key verification and had no
such field, so every existing SFTP server entry stops connecting until one of
the two keys is added. - BREAKING — FTP/FTPS connections whose
rootis not/are refused
unlessallowUnsafeRemoteRoot: trueis explicitly configured;
REMOTE_ROOT_REJECTEDis raised before any network I/O. 0.1.0 accepted any
rootand its own README recommended exactly this pattern
("root": "/var/www/site"), so those entries stop connecting until the flag
is added. The recommended boundary is now a dedicated server-side chrooted
account withroot: "/". ftp_deployreturns an MCP error when any transfer fails and includes a
partial-deployment summary, now also as structuredpartialcounters. Earlier
successful transfers are not rolled back.ftp_listno longer returns every directory entry by default. It returns the
first 50 entries and pagination metadata; callers can select an offset and a
page size from 1 through 200.- Tool results are bounded to 25000 bytes of UTF-8
JSON.stringifycovering
both text and structured data after redaction. Oversized results shrink their
bounded samples and rebuild pagination first, then truncate the largest text
body with a… [output truncated]marker, and fall back toOUTPUT_LIMIT
only when nothing can be shrunk — while preserving the request UUID, effects
and observed partial counters. This cap sits belowftp_read's unchanged
max_byteswindow (default 262144, hard max 1048576): a read that returned
256 KB of content on 0.1.0 now returns roughly 25 KB, marked as truncated, in
a successful result. - Boolean safety flags and SFTP fingerprint formats are validated per server.
setupsurfaces active insecure-transport acknowledgments;doctorsurfaces
active insecure-transport, unknown-host-key and unsafe-remote-root
acknowledgments.- Documentation now distinguishes the working source install from future
npxand MCP registry installation. - The published package now also ships
docs/,evaluations/,server.json,
CONTRIBUTING.md,CONTRIBUTING.fr.md,SECURITY.md,SECURITY.fr.md,
CHANGELOG.fr.mdandLICENSE.fr.md. The package andserver.json
descriptions are now bilingual and describe local confinement and verified
staging. - New runtime dependency
zod-to-json-schema, used to publish the tool input
and output JSON Schemas. - The local FTP test server now uses the
@electerm/ftp-srvdevelopment
dependency.
Removed
assets/banner.svg,assets/banner.png,assets/demo.svgand
assets/diagram.svg. The README banners are nowassets/banner-en.pngand
assets/banner-fr.png, with their generation prompts and provenance recorded
underassets/provenance/.- The
ftp-srvdevelopment dependency, replaced as noted above.
Fixed
- Interactive manual server entry in
setupno longer writes the wizard result
through a missing property. - Case variants of protocol names can no longer bypass insecure-transport
checks on setup and diagnostic paths. - Tool errors retain applicable security warnings, including failures after an
insecure connection may already have exposed credentials.
Security
- Insecure transports are refused by default; see the BREAKING entry under
Changed for the newallowInsecuregate and its upgrade impact. Warnings
are attached to discovery, diagnostics, successes, and failures involving
accepted insecure transports. - SFTP resolves the configured remote root and uses realpath/lstat checks to
refuse symbolic-link components and resolved paths outside that root. A
malicious server can still race state between validation and operation. An
SFTP connection also pins its initial canonical root; a known root change
returnsTARGET_CHANGEDand refuses to rebase an owned temporary. - FTP/FTPS no longer describe lexical path normalization as a trustworthy
anti-symlink jail; server-side account isolation is the real boundary. - Local sources and download destinations are constrained under
localRoot. - Non-interactive setup fails closed: it reports insecure transports but never
grants anallowInsecureexception automatically. - Error confidentiality is enforced at the result boundary. Error objects,
causes, stacks, credentials and raw arguments are never serialized; secrets
are masked in free text; configuration credentials are collected before
validation, including values from rejected entries and resolved${ENV:VAR}
placeholders, so loader diagnostics use the same mask. Malformed configuration
JSON yields a generic syntax diagnostic with no parser excerpt, and rejected
FileZilla blocks are identified by numeric index without echoing untrusted
names or protocol fields. Remote strings such asPage:andSECURITY WARNINGcannot impersonate the trusted notice rendering. - The committed lockfile resolves the MCP SDK's transitive
honodependency to
the patched 4.13.5.