Skip to main content
Glama
alebgl77

ftp-deploy-mcp

by alebgl77

ftp-deploy-mcp

The deploy button for AI coding agents.

Give Claude Code, Claude Desktop, Cursor, Windsurf, Trae, Antigravity, or any MCP client a focused way to list, read, upload, download, and deploy files on your own FTP, FTPS, and SFTP servers.

Version française → README.fr.md

CLI and MCP language: English by default; use --lang fr or FTP_MCP_LANG=fr. Language scope and precedence.

CI License: MIT Node >=22 MCP compatible

Purpose-built deployment controls keep credentials out of tool responses, support read-only servers and dry runs, and limit local file access with localRoot. The repository is covered by an extensive end-to-end suite that runs against local FTP and SFTP servers. There is no telemetry.

Availability: install from source today. The npm package has not been published yet, so npx -y ftp-deploy-mcp is unavailable. The project still lists publication in the Official MCP Registry as pending; registry-based installation is not offered here. GitHub source releases are separate from npm and Official MCP Registry publication. Existing Glama and MCP Index pages are discovery listings, not proof of an installable package. This checkout has version 0.2.1. Find published source versions on GitHub Releases; the published GitHub release v0.2.0 remains unchanged.

Documentation

Document

English

Français

Overview and installation

README

README

Changes

Changelog

Modifications

Contributing

Guide

Guide

Security policy

Policy

Politique

Security model

Model

Modèle

Release process

Guide

Guide

Agent evaluation

Instructions

Instructions

Scripted MCP conformance

Guide

Guide

CLI and MCP languages

Guide

Guide

MCP error contract

Contract

Contrat

Verified staged transfers

Guide

Guide

Scan and admission limits

Limits

Limites

State storage primitives (internal library, no MCP tool)

Library

Bibliothèque

Durable workflow model (internal library, no MCP tool)

Model

Modèle

MIT license

Canonical text

Informative translation

Related MCP server: mcp-remote-ssh

Architecture and scenarios

Explore the current and target architecture diagrams and illustrative scenarios in the bilingual interactive HTML guide. The guide and its English / French build instructions are in the repository, outside the npm package. After cloning, open site/index.html locally and use its language selector.

The README banners were generated with ChatGPT Image. Their provenance and exact prompts document their illustrative role; they are not a certification.

First source install

  1. Install a supported Node.js LTS version: 22 or 24 (minimum: 22).

  2. Run git clone https://github.com/alebgl77/ftp-deploy-mcp.git, then cd ftp-deploy-mcp.

  3. Run npm ci --omit=dev --ignore-scripts.

  4. Run npm run setup, then edit the generated server config: set an absolute localRoot, replace credentials, and configure the SFTP host-key pin or the FTP/FTPS safety acknowledgments described below.

  5. Restart the MCP client and try a dry run:

Call ftp_deploy with:
{"server":"prod","local_dir":"dist","remote_dir":"/","dry_run":true}

Windows users can run install.cmd and macOS/Linux users can run ./install.sh instead of steps 3–4. Review every generated server entry before the first connection.

For a tagged version, check out that exact tag before installing. npm ci uses the sole authoritative package-lock.json, with six exact direct pins and the reviewed graph. Keep it with the sources. --ignore-scripts disables automatic dependency lifecycle scripts; setup is a separate, explicit action.

The v0.2.1 source installation asset is named ftp-deploy-mcp-0.2.1-source.tar.gz: 82 files, including that lockfile. Once its publication and checksums are verified, extract it in a fresh directory:

tar --ignore-zeros -xzf ftp-deploy-mcp-0.2.1-source.tar.gz
cd package
npm ci --omit=dev --ignore-scripts
node src/index.js setup

This installation archive excludes repository tests and maintainer scripts; GitHub's automatic source ZIP/tar contains the complete repository. The separate npm .tgz contains 81 files without a lockfile and remains unqualified: an actual npm consumer changed ten production dependencies. It provides no locked-install guarantee. See the release guide for verification and limits.

Protocol and security matrix

Protocol

Transport identity

Remote-root behavior

Recommended use

SFTP

Encrypted. hostKeySha256 is required unless allowUnknownHostKey: true explicitly accepts impersonation risk.

realpath/lstat checks refuse symlink components and keep operations below root. A malicious or changing server can still create a race between validation and use.

Preferred. Pin a fingerprint verified out of band.

FTPS

Encrypted when certificate verification succeeds. insecureTLS: true also requires allowInsecure: true and disables MITM protection.

A client-side sub-root cannot be a reliable anti-symlink jail. root other than / is refused unless allowUnsafeRemoteRoot: true.

Use a dedicated, server-side chrooted account whose visible root is /.

FTP

Plaintext. Refused unless allowInsecure: true accepts interception and credential exposure.

Same limitation as FTPS: the real boundary is the server account/chroot, not lexical client path checks.

Legacy-only, on a trusted network, with a dedicated chrooted account.

All three protocols also enforce localRoot for ftp_upload, ftp_deploy, and ftp_download. This limits which local files the MCP server can access.

What you get

  • Ten focused MCP tools for server discovery, testing, listing, reading, uploading, recursive deploys, downloading, creating directories, renaming, and deleting.

  • Multiple named servers in one local configuration.

  • Gitignore-like deploy exclusions, dry-run, and per-server readOnly mode.

  • FileZilla import, an interactive setup wizard, and a read-only doctor command.

  • Credentials loaded locally from the config, environment variables, or SSH keys and never intentionally returned to the model.

Server configuration

An explicit configuration path is authoritative:

  1. --config <path>

  2. FTP_MCP_CONFIG, when --config is absent

An empty, missing, unreadable, or invalid explicit configuration reports an error without falling back to another file. The MCP tools remain available to report the configuration problem. --config requires a path argument.

Without either selector, the first configuration found wins:

  1. ./ftp-servers.json

  2. ~/.ftp-mcp/servers.json

The teaching example below is JSON with comments. Real configuration files must be strict JSON; start from ftp-servers.example.json.

{
  "defaultServer": "prod",
  "servers": {
    "prod": {
      "protocol": "sftp",
      "host": "ssh.example.com",
      "port": 22,
      "user": "deploy",
      "password": "${ENV:PROD_PASSWORD}",
      "privateKeyPath": "~/.ssh/id_ed25519",
      "passphrase": "${ENV:PROD_KEY_PASSPHRASE}",
      "localRoot": "/home/alice/projects/site",
      "root": "/var/www/site",
      "hostKeySha256": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
      "readOnly": false
    }
  }
}

Replace the all-A fingerprint; it is deliberately non-functional for a real host. Use hostKeySha256 as an array during a controlled key rotation:

"hostKeySha256": [
  "SHA256:old_verified_43_character_base64_value_here",
  "SHA256:new_verified_43_character_base64_value_here"
]

The illustrative labels above show the shape but are not valid pins. Each real entry is exactly SHA256: plus 43 characters of unpadded base64.

Field reference

Field

Applies to

Meaning

protocol

all

Required: ftp, ftps, or sftp.

host / port / user

all

Connection endpoint and account. Default ports are 21, 990 for implicit FTPS, and 22 for SFTP.

password

all

Password or ${ENV:NAME} placeholder.

privateKeyPath / passphrase

SFTP

SSH private key path and optional passphrase. A leading ~ is expanded. These authenticate the user; they do not verify the server.

localRoot

all

Required for upload, deploy, and download. Must resolve to an existing absolute local directory; a leading ~ is supported. Relative and symlink/junction escapes are refused. Relative tool paths resolve inside it, and absolute tool paths must still remain inside it.

root

all

Remote root, default /. Tool paths are resolved below it. SFTP performs server-side realpath/lstat checks. FTP/FTPS need a server-side chroot for a trustworthy boundary.

hostKeySha256

SFTP

Required fingerprint string or non-empty array of pins. Format: SHA256:<43-character unpadded base64>. Cannot be combined with allowUnknownHostKey.

allowUnknownHostKey

SFTP

Emergency compatibility override. true accepts an unverified server identity and visibly warns on results.

readOnly

all

Blocks upload, deploy, mkdir, rename, and delete. A deploy dry run remains available.

operationTimeoutMs

all

Tool deadline including queue and connection time, default 120000 ms. Integer from 100 to 3600000. Native transport timeouts also apply.

maxTransferBytes

all

Maximum actual bytes per file, default 268435456 (256 MiB), maximum 1099511627776 (1 TiB). Positive safe integer.

maxDeployFiles

all

Maximum selected files per deploy, default 10000, maximum 100000. Positive safe integer; does not bound the full directory scan.

maxScanEntries

all

Visited local entries per deploy, including the root, excluded entries and links. Default 100000, maximum 1000000; positive safe integer.

maxScanDepth

all

Inclusive directory depth below the root (depth 0). Default 64, maximum 256; positive safe integer.

maxDeployBytes

all

Maximum cumulative source bytes per deploy, default 1073741824 (1 GiB), maximum 1099511627776 (1 TiB). Failed attempts retain their reservation.

implicitTLS

FTPS

Uses implicit TLS, normally on port 990.

insecureTLS

FTPS

Disables certificate verification. Requires allowInsecure: true.

allowInsecure

FTP/FTPS

Explicitly accepts plaintext FTP or unverified FTPS. It does not make the connection secure.

allowUnsafeRemoteRoot

FTP/FTPS

Allows a root other than / despite the unresolved symlink-escape risk. Use only when the server-side account boundary is understood.

Any string can contain ${ENV:VARIABLE_NAME}. Missing variables produce a named configuration error.

Verify an SFTP fingerprint out of band

Do not trust a fingerprint obtained only through the connection you are about to verify.

  1. Obtain the SHA-256 host-key fingerprint from the hosting provider's authenticated control panel or support channel, or from an administrator through a separately authenticated channel.

  2. If you administer the host, use its trusted console to run a command such as ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256.

  3. Compare the complete SHA256:... value before putting it in hostKeySha256. ssh-keyscan may collect a candidate key, but by itself it does not authenticate that key.

For rotation, verify the new fingerprint out of band, temporarily configure both old and new pins, rotate the server key, confirm connections use the expected key, then remove the old pin. Do not use allowUnknownHostKey as a rotation shortcut.

Tool reference

All remote path arguments are relative to the configured remote root. The server parameter is optional when defaultServer is set or only one server exists.

Tool

Main parameters

Purpose

ftp_list_servers

none

Show server metadata and active safety warnings, never passwords.

ftp_test

server?

Connect and list the visible root.

ftp_list

server?, path?, limit?, offset?

List a remote directory. limit defaults to 50 (1–200); offset defaults to 0.

ftp_read

server?, path, max_bytes?

Read a bounded text file; binary data is refused.

ftp_upload

server?, local_path, remote_path?

Upload one file from localRoot.

ftp_deploy

server?, local_dir, remote_dir?, include?, exclude?, dry_run?

Recursively deploy a directory from localRoot.

ftp_download

server?, remote_path, local_path, overwrite?

Download into localRoot.

ftp_mkdir

server?, path

Create a remote directory recursively.

ftp_rename

server?, from_path, to_path

Rename or move.

ftp_delete

server?, path, recursive?

Delete a file or, with explicit recursion, a directory.

Verified staged promotion

Uploads and deploys write to an unpredictable sibling temporary, verify its actual byte count and SHA256 against the local source, then promote it with one rename. A verification or rename failure never triggers deletion of the final target or a direct-overwrite fallback. Downloads verify an exclusive local temporary, synchronize and close it, then promote the complete file. The default overwrite:false uses a hard link to avoid clobbering a file that appeared meanwhile; filesystems without hard-link support fail closed.

Local and SFTP replacements preserve an existing regular target's permission bits (0777); FTP/FTPS cannot portably preserve permissions, so account creation defaults, umask and ACL policy need to suit the destination. Rename behavior depends on the server and filesystem. This does not promise universal atomic replacement, a site transaction, or rollback. Readback adds network traffic. See transfer guarantees, limits and cleanup.

Execution, cancellation, and contention

Within one Node process, upload, deploy, mkdir, rename, and delete run in FIFO order for the same normalized protocol, hostname, port, and username. Server aliases and roots share that lock. Downloads serialize by canonical local destination, including across servers. Read-only remote operations can overlap.

MCP cancellation and deadlines close active transports and stop subsequent deploy files. Errors identify CANCELLED or TIMEOUT; TARGET_BUSY indicates waiting for another operation. A timed-out call can return before an underlying operation settles, but its lock remains owned until settlement and cleanup. No uncertain mutation is retried automatically. Inspect partial remote/local state before retrying; this is not transactional deployment or rollback.

Internal preparation is single-use and limited to 10 seconds without extending the overall operation deadline. Admission remains held until the actual work and cleanup settle. A prepared context is released once, including when it arrives after cancellation; the handler is not called after preparation is cancelled or expires. Preparation callbacks themselves may act. This adds no public MCP tool or durable deployment workflow. See the resource limits.

Progress notifications are sent only when the caller supplies a progress token, with increasing counters and no paths or credential text. Locks do not coordinate other Node processes, DNS aliases, other accounts, or external clients on the same host.

Response compatibility, pagination, and annotations

ftp_list returns one page instead of an unbounded directory listing. Its successful response includes total, count, offset, limit, has_more, and next_offset pagination fields. For example, a request with {"server":"prod","path":"/assets","limit":2,"offset":2} may include the following intentionally non-exhaustive excerpt. It omits the required top-level server, path, and security_warning fields and the required per-entry size_bytes and modified_at fields:

{
  "structuredContent": {
    "entries": [
      { "name": "app.css", "type": "file" },
      { "name": "app.js", "type": "file" }
    ],
    "total": 6,
    "count": 2,
    "offset": 2,
    "limit": 2,
    "has_more": true,
    "next_offset": 4
  }
}

The existing human-readable content text is retained for compatibility. On success, every tool except ftp_read also advertises an MCP outputSchema and returns matching structuredContent; ftp_read remains a bounded text-only tool on success. Each of the nine structured schemas accepts either its strict success shape or a strict {error} envelope. Tool execution errors return isError: true with localized text and structured code, request UUID, effect observations and safe-retry guidance. Unknown tools remain protocol errors. See the error contract.

The process admits at most 64 tool calls, including workers still cleaning up after cancellation. Excess calls return CAPACITY_LIMIT. Local deployment scans are asynchronous and separately bounded; see resource limits for exact counting, SCAN_LIMIT and corrected custom exclusion behavior.

All tools publish MCP annotations describing read-only, destructive, idempotent, and open-world behavior. These annotations are client hints, not a security boundary; enforce access with server credentials, readOnly, localRoot, and the protocol controls described above. ftp_deploy structured results contain a summary and bounded samples, not exhaustive file lists.

ftp_deploy is not a transaction. If one or more transfers fail, the tool returns an MCP error with a partial-deployment summary; files already promoted before the failure are not rolled back.

Default deploy exclusions include node_modules, .git, environment files, logs, OS metadata, ftp-servers.json, and .ftp-mcp content at any depth. The reserved .ftp-mcp-*.tmp basename is always excluded, even if an explicit include pattern matches it, so deploy cannot select a partial download temporary.

Client setup

npm run setup detects supported clients, creates timestamped backups before changing existing client configuration, and prints a block for UI-only clients. To wire a client manually, replace the path below with the absolute path to this checkout:

{
  "mcpServers": {
    "ftp": {
      "command": "node",
      "args": ["/absolute/path/to/ftp-deploy-mcp/src/index.js"]
    }
  }
}

Common locations include .mcp.json for Claude Code, ~/.cursor/mcp.json for Cursor, and ~/.codeium/windsurf/mcp_config.json for Windsurf. Claude Desktop and other clients accept the same command/arguments structure through their MCP settings.

After the first npm release is verified, this source command can be replaced with npx -y ftp-deploy-mcp. It is intentionally not presented as a working installation method today.

FileZilla import and diagnostics

node src/index.js import-filezilla --file /path/sitemanager.xml --out ./ftp-servers.json
npm run doctor

Imported passwords may be decoded into plaintext. Keep the output outside version control, restrict its permissions, add localRoot, and review every insecure-transport or FTP/FTPS remote-root warning before connecting. doctor is read-only and reports configuration and client wiring without printing passwords.

Migrating from v0.1 to v0.2

GitHub v0.2.0 is published. This source checkout has version 0.2.1. The npm package remains unpublished, and the project still lists Official MCP Registry publication as pending. The steps below apply to the source installation and do not announce an npm release.

  1. Add an absolute localRoot to every server used by upload, deploy, or download.

  2. For every SFTP server, add an out-of-band-verified hostKeySha256. Use allowUnknownHostKey: true only as a temporary, explicit risk acceptance; do not configure both fields.

  3. For FTP/FTPS, prefer a dedicated server-side chroot whose visible root is / and set root to /. A non-root client path now requires allowUnsafeRemoteRoot: true and remains unsafe against server-side symlinks.

  4. Treat a failed ftp_deploy as a partial deployment: inspect its summary and reconcile the remote tree before retrying.

  5. Re-run npm run setup or update the MCP client command to this checkout, then run npm run doctor and a dry run.

Atomic replacement for newly written sensitive configuration is checked by the release tests; a version number alone does not establish that guarantee. See docs/RELEASE.md for the qualification and publication steps.

Security and limitations

  • The strongest FTP/FTPS boundary is the server's own account isolation or chroot. Client-side normalization rejects obvious traversal, but FTP lacks portable REALPATH/LSTAT primitives and cannot prove that a server-side symlink stays inside a configured sub-root.

  • SFTP verifies the host pin and rejects symlink components using realpath/lstat. A server controlled by an attacker can still change filesystem state between checks and operations.

  • readOnly reduces accidental writes through this MCP server; it is not a substitute for read-only credentials enforced by the remote server.

  • FTP, insecureTLS, allowUnknownHostKey, and allowUnsafeRemoteRoot are explicit risk acceptances, not security features.

Read the full security model and private disclosure policy.

Development

npm test
npm run eval:scripted
node src/index.js --version
node src/index.js --help

The test suite uses local FTP and SFTP servers and does not require an external network. Contributions are welcome; see CONTRIBUTING.md. Maintainers should use the release guide. A reproducible, externally hosted read-only agent evaluation is documented in evaluations/README.md.

From a source checkout, npm run eval:scripted runs 43 cases in both languages with a memory adapter and real MCP handlers. See the scripted conformance guide for reports and limits; this command does not evaluate a model.

License

MIT — see LICENSE.

Available Tools

10 tools
ftp_deleteDelete a remote file or directoryA
DestructiveIdempotent

Delete a remote file, or a directory when recursive:true. Never deletes the server root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesRemote path to delete, relative to the server root.
serverNoName of the configured server. Defaults to the configured default, or the sole server.
recursiveNoRequired to delete a directory and its contents.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds valuable behavioral detail beyond annotations: recursive deletion applies to directories, and the server root is protected. The annotations already signal destructive/idempotent behavior, so the description's extra guardrail and recursive qualification are strong contributions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with the core action front-loaded and the safety constraint immediately after. No filler or repeated schema boilerplate.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description gives the core behavior, the recursive condition, and a critical root-safety guard. It does not address errors, permissions, or edge cases like nonexistent paths, but for a simple destructive operation with strong annotations and schema coverage, it is reasonably complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is high (all parameters documented). The description adds no new parameter semantics beyond restating that recursive deletion applies to directories, so the schema carries the explanatory burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Delete'), identifies the target ('remote file' or 'directory'), and includes the recursive condition for directories. This clearly distinguishes it from sibling operations like ftp_read, ftp_upload, and ftp_list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly states when the tool applies (files and directories with recursive=true) and adds a meaningful safety exclusion ('Never deletes the server root'). It does not directly compare with sibling tools, but no sibling covers deletion, so the usage context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_deployDeploy a local directoryA
Destructive

Recursively upload a local directory to the server over a single connection, applying default and custom exclude globs (and optional include globs). Supports dry_run.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoName of the configured server. Defaults to the configured default, or the sole server.
dry_runNoIf true, list what would be uploaded without connecting.
excludeNoExtra glob patterns to exclude, added to the built-in defaults.
includeNoGlob patterns; when given, a file must match at least one to be uploaded.
local_dirYesLocal directory to deploy, absolute or relative to the server's configured localRoot.
remote_dirNoDestination remote directory, relative to the server root. Defaults to the root.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already signal destructiveHint=true and readOnlyHint=false, so the description does not need to prove the operation is a write. It adds useful context about single-connection behavior and dry-run capability, but it does not disclose what the destructive behavior actually affects, such as whether existing remote files are overwritten or whether files absent locally are deleted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences lead with the action and resource, then pack the glob behavior and dry-run support without wasted words. The description is easy to scan and every clause contributes information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With a complete input schema, an output schema, and annotations covering the destructive nature, the description is largely sufficient. The only notable gap is the lack of explicit routing guidance against ftp_upload, but this does not prevent a capable agent from invoking the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the input schema fully documents all six parameters and the description is not required to compensate. The description reinforces exclude/include semantics and dry_run, but adds little meaning beyond what the schema already states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('upload'), a specific resource ('local directory'), and the key behavioral qualifiers: recursive traversal, glob-based exclusion/inclusion, single connection, and dry-run support. This separates it from the sibling ftp_upload tool, which appears to target individual files, even though that alternative is not named.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the intended use clear: deploy a whole local directory recursively rather than a single file. However, it never explicitly says when to prefer this over ftp_upload or other siblings, nor does it state exclusions such as 'for individual files, use ftp_upload'. The usage context is implied by the wording, but not spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_downloadDownload a remote fileA
Destructive

Download a remote file to a local path, auto-creating local parent directories. Refuses to overwrite unless overwrite:true.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoName of the configured server. Defaults to the configured default, or the sole server.
overwriteNoAllow overwriting an existing local file.
local_pathYesLocal destination path, absolute or relative to the server's configured localRoot.
remote_pathYesRemote file path, relative to the server root.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses two meaningful behaviors beyond the annotations: it auto-creates local parent directories assuredly, and it refuses to overwrite existing files unless overwrite:true is set. This materially helps the agent anticipate side effects beyond the raw destructiveHint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise, front-loaded sentences cover the primary action and both edge-case behaviors. No redundancy with the title or the input schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple download tool with a rich input schema and output schema, this is mostly complete. It could mention what happens on remote-file absence or connection failures, but those are minor gaps given the structured context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers all parameter descriptions (100%), giving a baseline of 3. The description adds relevant semantics by clarifying that local parent directories will be created and that overwrite is disabled by default, which supplements the parameter meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('download'), the source ('remote file'), the destination ('local path'), and two behavioral constraints (auto-creating parent directories, refusing to overwrite unless asked). This clearly identifies the tool's job and distinguishes it from siblings like ftp_read or ftp_upload.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'download a remote file to a local path' gives clear operational context for when this tool would be used. However, it does not explicitly contrast it with alternatives such as ftp_read (view without saving) or mention any selection criteria, so the guidance is mostly implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_listList a remote directoryA
Read-onlyIdempotent

List the contents of a remote directory (relative to the server root). Directories are listed first.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNoRemote directory, relative to the server root. Defaults to the root.
limitNoMaximum entries to return (default 50, maximum 200).
offsetNoZero-based entry offset (default 0).
serverNoName of the configured server. Defaults to the configured default, or the sole server.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds two behavioral facts not in the annotations: directories are listed first, and paths are relative to the server root. This adds value beyond structured metadata without contradicting it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler. The primary action and the most important scoping constraint are front-loaded, and the behavioral note about directory ordering is a single clause. Nothing extraneous.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple listing tool, annotations covering safety, schema covering all params, and an output schema present, the description is nearly complete. It could mention recursion behavior or sorting, but those are not essential for correct invocation. The provided details are sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all parameters are described. The description adds context for the 'path' parameter (relative to server root) but does not elaborate on limit/offset or server defaults. Since the schema handles the heavy lifting, this is a baseline 3 with a minor enhancement.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('List') and resource ('remote directory'), and adds a precise scoping detail ('relative to the server root'). This clearly differentiates from siblings like ftp_read (file content) and ftp_list_servers (server configuration) based on the action and target.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage—this is the tool for listing directory contents—but does not explicitly mention when to prefer it over ftp_read or other siblings, nor does it state exclusions. The distinction is inferable from the name and title, but no explicit guidance is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_list_serversList configured serversA
Read-onlyIdempotent

List all configured FTP/FTPS/SFTP servers (name, protocol, host, port, root, read-only, auth kind) and which is default. Never reveals passwords or keys.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already convey readOnlyHint and idempotentHint, but the description adds a meaningful behavioral guarantee: 'Never reveals passwords or keys.' It also specifies the exact set of fields returned (name, protocol, host, port, root, read-only, auth kind, default), which is beyond what annotations provide. No contradiction exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, well-structured sentence front-loads the action and resource, then lists specifics and a security guarantee. No filler or redundant text. Every clause adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the presence of an output schema, the description does not need to elaborate return values. It covers the purpose, lists output fields, and flags a security invariant. For a parameterless configuration-listing tool, nothing critical is absent. The only minor gap is explicit usage guidance, but that is covered under dimension 2.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Since the tool has zero parameters, the description cannot add parameter semantics. Per calibration, baseline for 0 params is 4. The description appropriately confirms no inputs are needed, and the schema is empty, so nothing is missing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'list' and the resource 'configured FTP/FTPS/SFTP servers', enumerates the exact fields returned, and notes the default flag. It is unambiguous and distinguishes itself from sibling ftp_list (which likely lists files) by naming the resource type. The security note about not revealing passwords also clarifies its non-sensitive nature.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage (when you need to know configured servers) but provides no explicit guidance on when to choose it over alternatives like ftp_list or ftp_test. It does not state exclusions or prerequisites, relying on the tool name and purpose. A sentence like 'Use this to discover server names before invoking other ftp_* tools' would improve clarity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_mkdirCreate a remote directoryA
Idempotent

Recursively create a remote directory (relative to the server root).

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesRemote directory to create, relative to the server root.
serverNoName of the configured server. Defaults to the configured default, or the sole server.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnlyHint=false, idempotentHint=true, and destructiveHint=false. The description adds meaningful behavioral context beyond annotations by stating the operation is recursive and relative to the server root. No contradiction with annotations exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no filler, front-loading the core action and the recursive behavior. Every word contributes to the tool's meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with full schema descriptions, annotations for safety and idempotence, and an output schema present, the description covers the essential behavioral nuance (recursion) not already in the structured data. Nothing critical appears missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with both path and server already described in the input schema. The description's mention of 'relative to the server root' repeats the schema's path description rather than adding new meaning, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('create'), a concrete resource ('remote directory'), and a key qualifier ('recursively') plus scope ('relative to the server root'). It clearly distinguishes this directory-creation tool from file-oriented siblings like ftp_upload and from operations like ftp_list or ftp_delete.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance about when to use this tool over alternatives such as ftp_upload or ftp_deploy, nor any mention of prerequisites, exclusions, or conditions. The only implied context is that it creates directories, leaving the agent to infer when this is the correct choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_readRead a remote text fileA
Read-onlyIdempotent

Read a remote text file and return its content. Binary files are refused (use ftp_download instead).

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesRemote file path, relative to the server root.
serverNoName of the configured server. Defaults to the configured default, or the sole server.
max_bytesNoMaximum bytes to read (default 262144, hard max 1048576).

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint false, so the safety profile is covered. The description adds the important behavioral detail that binary files are refused, which is beyond the annotations and helps the agent anticipate failure modes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence plus a parenthetical alternative. The core purpose is front-loaded, and every word earns its place—no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read tool with no output schema, the description covers core behavior and the binary refusal. It doesn't address error cases (e.g., file not found) but that's minor given the tool's simplicity and strong annotation coverage. Slightly more detail about failure behavior would push it to 5, but it's already solid.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All three parameters (path, server, max_bytes) have descriptions in the schema, so the schema carries heavy weight. The tool description adds no new information about parameter meaning, but the high schema coverage makes this acceptable. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('read') and resource ('remote text file') and explicitly distinguishes itself from ftp_download by refusing binary files. An agent can immediately tell this is the text-reading tool versus the binary-download sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the alternative (ftp_download) and the condition that triggers it (binary files are refused). This gives clear direction on when to use this tool versus the sibling, leaving no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_renameRename or move a remote entryA
Destructive

Rename or move a remote file or directory (both paths relative to the server root).

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoName of the configured server. Defaults to the configured default, or the sole server.
to_pathYesNew remote path, relative to the server root.
from_pathYesExisting remote path, relative to the server root.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the description doesn't need to restate that. The description adds the path-relative-to-root context, but does not disclose what happens on failure, whether directories can be moved recursively, or whether overwriting is possible. With annotations covering the destructive nature, a 3 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, efficient sentence that front-loads the action and includes the key scoping detail about paths. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-required-parameter mutation tool with a full output schema and annotations covering destructiveness, the description is nearly complete. It could mention whether moving directories is supported or whether overwrites are allowed, but these are minor gaps given the schema and annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters. The description adds the shared context that paths are relative to the server root, which reinforces the schema but doesn't add new meaning beyond it. Baseline 3 is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Rename or move') and resource ('remote file or directory'), and clarifies that paths are relative to the server root. This clearly distinguishes it from siblings like ftp_delete, ftp_upload, and ftp_download.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for renaming or moving remote entries, but does not explicitly state when to use this tool versus alternatives like ftp_upload or ftp_mkdir. It provides context (both paths relative to server root) but no exclusions or alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_testTest a server connectionA
Read-onlyIdempotent

Connect to a server, list its root directory, and report success.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoName of the configured server. Defaults to the configured default, or the sole server.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds the behavioral detail that it lists the root directory and reports success, which is useful context beyond the annotations. However, it doesn't disclose what 'report success' returns or whether it throws on failure, which would be valuable for a test tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that front-loads the core action and includes the key behavioral detail ('report success'). Every word earns its place, and there is no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple test tool with one optional parameter, full annotation coverage, and an output schema, the description is nearly complete. The only gap is that it doesn't specify what the success report looks like, but the output schema likely covers that, so the description is adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the single parameter 'server' is already documented in the schema. The description doesn't add any additional meaning about the parameter beyond what the schema provides, so the baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Connect to a server, list its root directory, and report success') with a clear verb and resource. It distinguishes itself from siblings like ftp_list by focusing on testing connectivity rather than general listing, though it doesn't explicitly name a sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage as a connectivity test by mentioning 'report success', but it doesn't explicitly state when to use this tool versus alternatives like ftp_list or ftp_list_servers. The context is clear enough for an agent to infer the primary use case, but no exclusions or alternative routing are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

ftp_uploadUpload a local fileA
Destructive

Upload one local file to the server, auto-creating parent directories. Remote path defaults to the file basename at the root.

ParametersJSON Schema
NameRequiredDescriptionDefault
serverNoName of the configured server. Defaults to the configured default, or the sole server.
local_pathYesLocal file path, absolute or relative to the server's configured localRoot.
remote_pathNoDestination remote path, relative to the server root. Defaults to the local basename at the root.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, which signal a mutating operation. The description adds the auto-creation of parent directories and the default remote path behavior, which are valuable non-obvious details. It does not disclose potential overwrites of existing files, but the annotation covers the destructive aspect, so the description adds appropriate context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is one tight sentence with no filler. The key behavioral details are front-loaded: action, auto-creation, and default path. Every word contributes, and it is neither under-specified nor verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present to document return values and annotations covering safety, the description is complete enough. It covers the primary behavior and defaults. It may not cover edge cases like what happens if the file doesn't exist, but given the context, it is adequately comprehensive for an upload tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema documents each parameter clearly. The description adds the default remote path behavior (basename at root) and clarifies the auto-creation of directories, which complements the schema. This is more than a simple restatement, adding value over the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action: uploading a single local file to the server, with the specific behavior of auto-creating parent directories. It distinguishes itself from siblings like ftp_deploy (which likely uploads multiple files or directories) and ftp_download, and the default remote path behavior is clearly explained.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies use for single-file uploads and implicitly contrasts with ftp_deploy for bulk deployments, but it does not explicitly state when to use this versus alternatives. It provides no explicit exclusions, but the context is enough for an agent to infer the intended use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 10 tool updatesv0.2.0
    • Changedftp_delete1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "deleted": {
        +          "type": "boolean"
        +        },
        +        "entry_type": {
        +          "enum": [
        +            "file",
        +            "directory"
        +          ],
        +          "type": "string"
        +        },
        +        "path": {
        +          "type": "string"
        +        },
        +        "recursive": {
        +          "type": "boolean"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "path",
        +        "entry_type",
        +        "recursive",
        +        "deleted",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_deploy2 fields changed
      • changedInput schema / properties / local_dir / description
        Previous value: -"Local directory to deploy (relative paths resolve against the process cwd)."New value: +"Local directory to deploy, absolute or relative to the server's configured localRoot."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "aborted_early": {
        +          "type": "boolean"
        +        },
        +        "complete": {
        +          "type": "boolean"
        +        },
        +        "duration_ms": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "failed_count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "failures": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "message": {
        +                "type": "string"
        +              },
        +              "path": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "path",
        +              "message"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "failures_omitted": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "mode": {
        +          "enum": [
        +            "dry_run",
        +            "deploy"
        +          ],
        +          "type": "string"
        +        },
        +        "planned": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "path": {
        +                "type": "string"
        +              },
        +              "size_bytes": {
        +                "minimum": 0,
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "path",
        +              "size_bytes"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "planned_omitted": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "remote_base": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        },
        +        "total_bytes": {
        +          "minimum": 0,
        +          "type": "number"
        +        },
        +        "total_files": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "uploaded": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "path": {
        +                "type": "string"
        +              },
        +              "size_bytes": {
        +                "minimum": 0,
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "path",
        +              "size_bytes"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "uploaded_bytes": {
        +          "minimum": 0,
        +          "type": "number"
        +        },
        +        "uploaded_count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "uploaded_omitted": {
        +          "minimum": 0,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "mode",
        +        "server",
        +        "remote_base",
        +        "total_files",
        +        "total_bytes",
        +        "uploaded_count",
        +        "uploaded_bytes",
        +        "failed_count",
        +        "aborted_early",
        +        "complete",
        +        "duration_ms",
        +        "security_warning",
        +        "uploaded",
        +        "uploaded_omitted",
        +        "planned",
        +        "planned_omitted",
        +        "failures",
        +        "failures_omitted"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_download2 fields changed
      • changedInput schema / properties / local_path / description
        Previous value: -"Local destination path (relative paths resolve against the process cwd)."New value: +"Local destination path, absolute or relative to the server's configured localRoot."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "local_path": {
        +          "type": "string"
        +        },
        +        "overwritten": {
        +          "type": "boolean"
        +        },
        +        "remote_path": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        },
        +        "size_bytes": {
        +          "minimum": 0,
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "remote_path",
        +        "local_path",
        +        "size_bytes",
        +        "overwritten",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_list3 fields changed
      • addedInput schema / properties / limit
        Added value: +{
        +  "description": "Maximum entries to return (default 50, maximum 200).",
        +  "maximum": 200,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / offset
        Added value: +{
        +  "description": "Zero-based entry offset (default 0).",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "entries": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "modified_at": {
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              },
        +              "name": {
        +                "type": "string"
        +              },
        +              "size_bytes": {
        +                "minimum": 0,
        +                "type": "number"
        +              },
        +              "type": {
        +                "enum": [
        +                  "dir",
        +                  "file",
        +                  "link"
        +                ],
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "name",
        +              "type",
        +              "size_bytes",
        +              "modified_at"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "has_more": {
        +          "type": "boolean"
        +        },
        +        "limit": {
        +          "maximum": 200,
        +          "minimum": 1,
        +          "type": "integer"
        +        },
        +        "next_offset": {
        +          "anyOf": [
        +            {
        +              "minimum": 0,
        +              "type": "integer"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ]
        +        },
        +        "offset": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "path": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        },
        +        "total": {
        +          "minimum": 0,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "path",
        +        "total",
        +        "count",
        +        "offset",
        +        "limit",
        +        "has_more",
        +        "next_offset",
        +        "entries",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_list_servers2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "configured_count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "default_server": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "errors": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "message": {
        +                "type": "string"
        +              },
        +              "server": {
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "required": [
        +              "server",
        +              "message"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "errors_omitted": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "invalid_count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "servers": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "auth": {
        +                "enum": [
        +                  "key",
        +                  "password"
        +                ],
        +                "type": "string"
        +              },
        +              "connection_refused": {
        +                "type": "boolean"
        +              },
        +              "host": {
        +                "type": "string"
        +              },
        +              "is_default": {
        +                "type": "boolean"
        +              },
        +              "local_root_status": {
        +                "type": "string"
        +              },
        +              "name": {
        +                "type": "string"
        +              },
        +              "port": {
        +                "exclusiveMinimum": 0,
        +                "type": "integer"
        +              },
        +              "protocol": {
        +                "enum": [
        +                  "ftp",
        +                  "ftps",
        +                  "sftp"
        +                ],
        +                "type": "string"
        +              },
        +              "read_only": {
        +                "type": "boolean"
        +              },
        +              "root": {
        +                "type": "string"
        +              },
        +              "security_warning": {
        +                "type": [
        +                  "string",
        +                  "null"
        +                ]
        +              }
        +            },
        +            "required": [
        +              "name",
        +              "protocol",
        +              "host",
        +              "port",
        +              "root",
        +              "read_only",
        +              "auth",
        +              "is_default",
        +              "local_root_status",
        +              "connection_refused",
        +              "security_warning"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "servers_omitted": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "status": {
        +          "enum": [
        +            "configured",
        +            "missing",
        +            "invalid"
        +          ],
        +          "type": "string"
        +        },
        +        "valid_count": {
        +          "minimum": 0,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "status",
        +        "configured_count",
        +        "valid_count",
        +        "invalid_count",
        +        "default_server",
        +        "servers",
        +        "servers_omitted",
        +        "errors",
        +        "errors_omitted"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_mkdir1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "created": {
        +          "type": "boolean"
        +        },
        +        "path": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "path",
        +        "created",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Addedftp_read
    • Addedftp_rename
    • Changedftp_test1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "entries_visible": {
        +          "minimum": 0,
        +          "type": "integer"
        +        },
        +        "host": {
        +          "type": "string"
        +        },
        +        "port": {
        +          "exclusiveMinimum": 0,
        +          "type": "integer"
        +        },
        +        "protocol": {
        +          "enum": [
        +            "ftp",
        +            "ftps",
        +            "sftp"
        +          ],
        +          "type": "string"
        +        },
        +        "root": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "protocol",
        +        "host",
        +        "port",
        +        "root",
        +        "entries_visible",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedftp_upload2 fields changed
      • changedInput schema / properties / local_path / description
        Previous value: -"Local file path (relative paths resolve against the process cwd)."New value: +"Local file path, absolute or relative to the server's configured localRoot."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "oneOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "local_path": {
        +          "type": "string"
        +        },
        +        "remote_path": {
        +          "type": "string"
        +        },
        +        "security_warning": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "server": {
        +          "type": "string"
        +        },
        +        "size_bytes": {
        +          "minimum": 0,
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "server",
        +        "local_path",
        +        "remote_path",
        +        "size_bytes",
        +        "security_warning"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "error": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "code": {
        +              "enum": [
        +                "CONFIG_REQUIRED",
        +                "CONFIG_INVALID",
        +                "SERVER_REQUIRED",
        +                "SERVER_UNKNOWN",
        +                "INVALID_ARGUMENT",
        +                "READ_ONLY",
        +                "TRANSPORT_POLICY",
        +                "HOST_KEY_REJECTED",
        +                "REMOTE_ROOT_REJECTED",
        +                "PATH_REJECTED",
        +                "NOT_FOUND",
        +                "ALREADY_EXISTS",
        +                "TRANSFER_LIMIT",
        +                "TRANSFER_VERIFY",
        +                "TARGET_CHANGED",
        +                "CANCELLED",
        +                "TIMEOUT",
        +                "DEPLOY_PARTIAL",
        +                "TRANSPORT_ERROR",
        +                "OUTPUT_LIMIT",
        +                "INTERNAL_ERROR",
        +                "SCAN_LIMIT",
        +                "CAPACITY_LIMIT"
        +              ],
        +              "type": "string"
        +            },
        +            "effects": {
        +              "enum": [
        +                "none",
        +                "possible",
        +                "confirmed"
        +              ],
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            },
        +            "next_action": {
        +              "enum": [
        +                "fix_input",
        +                "fix_config",
        +                "select_server",
        +                "inspect_target",
        +                "retry",
        +                "contact_operator",
        +                "none"
        +              ],
        +              "type": "string"
        +            },
        +            "partial": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "completed_bytes": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "completed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "failed_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                },
        +                "final": {
        +                  "type": "boolean"
        +                },
        +                "total_files": {
        +                  "maximum": 9007199254740991,
        +                  "minimum": 0,
        +                  "type": "integer"
        +                }
        +              },
        +              "required": [
        +                "completed_files",
        +                "completed_bytes",
        +                "failed_files",
        +                "final"
        +              ],
        +              "type": "object"
        +            },
        +            "request_id": {
        +              "format": "uuid",
        +              "type": "string"
        +            },
        +            "retryable": {
        +              "type": "boolean"
        +            },
        +            "schema_version": {
        +              "const": 1,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "schema_version",
        +            "code",
        +            "message",
        +            "retryable",
        +            "request_id",
        +            "next_action",
        +            "effects"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
  2. 8 tool updatesv0.1.0
    • First observedftp_delete
    • First observedftp_deploy
    • First observedftp_download
    • First observedftp_list
    • First observedftp_list_servers
    • First observedftp_mkdir
    • First observedftp_test
    • First observedftp_upload

TDQS

A4.1/5.0

Scored across 10 tools

Disambiguation4/5

Most tools have clear, distinct purposes (list, read, upload, download, mkdir, rename, delete, deploy, list servers, test). The only potential overlap is between ftp_deploy and ftp_upload, but ftp_deploy is for recursive directory uploads while ftp_upload is for a single file, so they are distinguishable with descriptions.

Naming Consistency5/5

All tool names follow a consistent ftp_ prefix with a verb (deploy, list, test, read, upload, download, mkdir, rename, delete). The pattern is uniform and predictable, making it easy for an agent to infer functionality.

Tool Count5/5

With 10 tools, the set is well-scoped for an FTP deployment server. Each tool covers a distinct operation (connection management, file operations, directory operations, deployment), and the count is within the ideal range of 3-15 tools.

Completeness4/5

The core FTP operations are covered: list, read, upload, download, mkdir, rename, delete, and deploy. Missing features like file permissions or detailed metadata (e.g., last modified time) are minor gaps, but the CRUD lifecycle is complete for file and directory operations.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    An enterprise-grade MCP server for FTP and SFTP operations optimized for AI coding assistants, featuring smart synchronization, connection pooling, and unified diff patching.
    28
    17 npm
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server giving AI agents full SSH access with persistent sessions, structured command output, SFTP file transfer, and port forwarding.
    18
    10
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that gives AI agents SSH capabilities to execute commands, transfer files, and inspect remote systems through a preconfigured host list.
    51 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that enables AI assistants to perform development operations on remote servers via SSH, including executing commands, managing files, and browsing directories.
    1
    MIT