Skip to main content
Glama
alebgl77

ftp-deploy-mcp

by alebgl77

ftp-deploy-mcp

AI 코딩 에이전트를 위한 배포 버튼. Claude Code · Claude Desktop · Cursor · Windsurf · Trae · Antigravity → 나만의 FTP / FTPS / SFTP 서버.

Version française → README.fr.md

CI License: MIT Node >=18 MCP compatible PRs welcome

에이전트가 배포를 실행합니다 — 당신은 그저 요청만 하면 됩니다.


왜 필요한가

  • 모든 웹 프로젝트는 결국 같은 결말에 이릅니다: "이제 서버에 올려야지."

  • AI 에이전트는 훌륭한 코드를 작성하지만, 클래식 호스팅에 안전하게 배포할 방법이 없습니다 — OVH, Ionos, Hostinger, o2switch 등 공유 호스팅의 대부분은 여전히 FTP/SFTP를 사용하며 git push를 지원하지 않습니다.

  • ftp-deploy-mcp는 모든 MCP 클라이언트에게 코드가 작성된 바로 그 대화 속에서 나만의 인프라로 배포할 수 있는 경로를 제공합니다.

  • 일반적인 SSH-exec MCP 서버와 달리, 이 서버는 파일 배포에 특화되어 있습니다: 경로 잠금(jail), 읽기 전용 모드, 드라이런(dry-run), 그리고 모델의 컨텍스트에 절대 들어가지 않는 자격 증명.

Related MCP server: SSH MCP Server

기능

기능

설명

다중 서버

FTP / FTPS / SFTP, 하나의 설정 파일에 원하는 만큼의 서버

원커맨드 배포

재귀 디렉터리 배포, gitignore 스타일 제외 규칙(모든 깊이), 드라이런

경로 잠금

모든 작업이 서버별 root 아래로 제한됨

읽기 전용 모드

건드리면 안 되는 서버에 대한 모든 쓰기 작업 차단

FileZilla 가져오기

기존 sitemanager.xml 사이트를 한 번의 명령으로 변환

자동 설정

5개 이상의 MCP 클라이언트를 자동으로 설정, 타임스탬프 백업 포함

닥터(Doctor)

Node, 설정 파일, 서버, 클라이언트 연결 상태에 대한 읽기 전용 진단

제로 빌드

순수 ESM JavaScript — Node 표준 라이브러리 + 5개의 소형 의존성

안전 기본값

일반 FTP / 검증되지 않은 TLS는 서버별로 명시적으로 허용하지 않는 한 거부됨

전투 테스트 완료

실제 로컬 FTP + SFTP 서버에 대한 209개의 e2e 단언(assertion)

텔레메트리 없음

나만의 서버로의 호출 외에는 어떤 것도 머신 밖으로 나가지 않음

빠른 시작

  1. git clone https://github.com/alebgl77/ftp-deploy-mcp.git && cd ftp-deploy-mcp

  2. install.cmd 실행 (Windows, 더블클릭) 또는 ./install.sh 실행 (macOS / Linux).

  3. IDE를 재시작하고 에이전트에게 말하세요: "prod에 ./dist를 배포해 줘."

작동 방식

flowchart LR
subgraph agents [AI agents]
  A[Claude Code]; B[Cursor]; C[Windsurf]; D[Trae]; E[Antigravity]
end
agents -- MCP stdio --> S[ftp-deploy-mcp<br/>10 tools · path jail · read-only guard]
S -- FTP / FTPS --> F[(your web hosts)]
S -- SFTP --> G[(your servers)]
K[ftp-servers.json<br/>credentials stay local] -.-> S

1. 무엇인가

MCP(Model Context Protocol) 서버로, stdio를 통해 실행되며 코딩 에이전트에게 10개의 도구를 제공합니다. 자격 증명은 로컬 설정 파일에 저장되며 LLM 컨텍스트를 통과하지 않습니다. 모든 원격 작업은 서버별로 선택한 root 아래로 제한됩니다.

Node.js >= 18 필요. 컴파일할 네이티브 의존성 없음.


2. 설치

⚡ 원커맨드 설치 (권장)

git clone https://github.com/alebgl77/ftp-deploy-mcp.git
cd ftp-deploy-mcp

그런 다음 마법사를 실행하세요:

  • Windows: install.cmd 더블클릭.

  • macOS / Linux: ./install.sh 실행 (chmod +x install.sh 먼저 필요한 경우).

  • 또는 수동으로: npm install && npm run setup.

setup 마법사는 모든 것을 처리합니다:

  • 서버 설정을 작성하거나 가져옵니다 (기존 사이트의 FileZilla 가져오기 포함);

  • 각 서버에 대한 연결을 테스트합니다;

  • 감지된 MCP 클라이언트(Claude Code, Claude Desktop, Cursor, Windsurf, Antigravity)의 설정 파일을 자동으로 작성합니다 — 기존 파일을 수정하기 전에 .backup-<날짜> 백업을 만든 후;

  • Trae용 붙여넣기 준비 완료 블록을 출력하고 클립보드에 복사합니다 (Trae는 UI에서 설정하므로).

그런 다음 IDE를 재시작하고 에이전트에게 말하세요, 예: "내 FTP 서버 목록을 보여 줘".

진단

언제든지, 읽기 전용 진단(아무것도 쓰지 않음):

npm run doctor          # or: node src/index.js doctor

Node 버전, 사용 중인 설정 파일, 서버 목록(비밀번호 제외), 그리고 각 클라이언트별로 ftp 항목이 이 설치에 연결되어 있는지 여부를 출력합니다.

setup 옵션 (node src/index.js setup [options]):

옵션

효과

--yes

비대화형 (기존 설정 유지, 또는 --from-filezilla로 가져오기).

--clients <all|none|id,id>

설정할 클라이언트 (기본값: 감지된 모든 클라이언트).

--from-filezilla [path]

FileZilla에서 가져오기 (경로 선택 사항 → 기본 위치).

--config-dest <path>

설정 파일 대상 위치 (기본값 ~/.ftp-mcp/servers.json).

--skip-test

연결 테스트 건너뛰기.

--dry-run

계획된 작업만 출력하고 아무것도 쓰지 않음.

--force

기존의 다른 ftp 항목을 교체.

(b) 전역 설치

npm install -g .

ftp-deploy-mcp 명령이 이제 PATH에 있습니다. node .../src/index.js 대신 이 명령을 사용하세요.

(c) npm에 게시 (npx 사용 시)

이 패키지를 npm에 자신의 이름으로 게시하면, 클라이언트는 사전 설치 없이 실행할 수 있습니다:

{ "command": "npx", "args": ["-y", "your-package-name"] }

3. 서버 설정

ftp-servers.json 파일을 만드세요. 서버는 다음 순서로 파일을 찾습니다 (먼저 찾은 것이 우선):

  1. --config <path> (명령줄 플래그)

  2. FTP_MCP_CONFIG 환경 변수 (JSON 경로)

  3. ./ftp-servers.json (프로세스 작업 디렉터리)

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

전체 스키마

{
  "defaultServer": "prod",          // optional: used when "server" is not given
  "servers": {
    "prod": {
      "protocol": "sftp",           // REQUIRED: "ftp" | "ftps" | "sftp"
      "host": "ssh.example.com",    // REQUIRED
      "port": 22,                    // optional (defaults: ftp/ftps 21, implicit ftps 990, sftp 22)
      "user": "deploy",             // REQUIRED
      "password": "${ENV:PROD_PW}", // optional: password (or an env placeholder)
      "privateKeyPath": "~/.ssh/id_ed25519", // optional (sftp); "~" is expanded
      "passphrase": "…",            // optional: private-key passphrase
      "root": "/var/www/site",      // optional (default "/"): ALL ops are jailed under it
      "readOnly": false,             // optional: blocks upload/deploy/mkdir/rename/delete
      "insecureTLS": false,           // optional (ftps): skip certificate checks — requires "allowInsecure"
      "implicitTLS": false,          // optional (ftps): implicit TLS (port 990, legacy servers)
      "allowInsecure": false         // optional: explicit opt-in REQUIRED for plain "ftp" or "insecureTLS"
    }
  }
}

위 블록의 // 주석은 설명을 위한 것입니다. 실제 파일은 엄격한 JSON이어야 합니다 (주석 불가). ftp-servers.example.json 참조.

환경 변수 치환

모든 문자열 값에는 ${ENV:VARIABLE_NAME}을 포함할 수 있습니다. 시작 시 환경 변수의 값으로 대체됩니다. 변수가 설정되어 있지 않으면 도구는 누락된 변수를 명시하는 명확한 오류를 반환합니다.

"password": "${ENV:OVH_FTP_PASSWORD}"

보안 팁

  • SFTP를 선호하세요. 일반 ftp 및 insecureTLS: true인 ftps는 기본적으로 거부됩니다: 해당 전송 방식에서는 네트워크 공격자가 자격 증명이나 파일을 가로채거나 변조할 수 있습니다. 어쨌든 사용하려면 해당 서버에 명시적으로 "allowInsecure": true를 설정해야 합니다 — 그러면 모든 시작 로그와 도구 결과에 보이는 보안 경고가 표시됩니다.

  • ftp-servers.json을 .gitignore에 추가하세요 (이 저장소에는 이미 되어 있음).

  • 파일 권한을 제한하세요 (Unix에서 chmod 600 ftp-servers.json).

  • 평문 비밀번호 대신 환경 변수(${ENV:…}) 또는 SSH 키를 선호하세요.

  • 에이전트가 절대 쓰면 안 되는 서버에는 readOnly: true를 사용하세요.

  • root를 가능한 한 좁게 설정하세요: 잠금(jail)이 모든 ../ 탈출을 방지합니다.


4. FileZilla에서 가져오기

이미 FileZilla에 사이트가 있나요? 변환하세요:

# Auto-detect the default sitemanager.xml location…
node src/index.js import-filezilla

# …or an explicit file, written to an ftp-servers.json
node src/index.js import-filezilla --file /path/sitemanager.xml --out ./ftp-servers.json

--out 없이 실행하면 JSON이 표준 출력으로 출력됩니다. Base64로 인코딩된 비밀번호는 디코딩됩니다. 저장된 비밀번호가 없는 사이트는 ${ENV:<NAME>_PASSWORD} 자리 표시자를 받습니다 (변수는 직접 설정). 출력 예시:

{
  "defaultServer": "my-site",
  "servers": {
    "my-site": {
      "protocol": "ftp",
      "host": "ftp.example.com",
      "user": "deploy",
      "password": "…",
      "root": "/www/html"
    }
  }
}

주의: 생성된 파일에는 디코딩된 평문 비밀번호가 포함되어 있습니다 — 버전 관리(.gitignore)에서 제외하고 권한을 제한하세요 (chmod 600).

일반 FTP 사이트: 가져온 "protocol": "ftp" 서버(위 예시와 같은)는 sftp/ftps로 전환하거나 명시적으로 "allowInsecure": true를 설정할 때까지 연결 시 거부됩니다 — 가져오기 시 각각에 대해 경고가 출력됩니다. 보안 참조.


5. 수동 클라이언트 설정 (setup을 사용하지 않는 경우)

npm run setup이 이 파일들을 자동으로 작성합니다 (백업 포함). 이 섹션은 모든 것을 직접 연결하고 싶을 때만 유용합니다.

/absolute/path/to/ftp-deploy-mcp/src/index.js를 실제 경로로 바꾸세요 (Windows에서도 슬래시 / 사용 가능). 패키지를 npm에 게시했다면 "command": "node", "args": ["…/src/index.js"]를 "command": "npx", "args": ["-y", "your-package-name"]로 교체하세요.

아래 파일 위치는 작성 시점의 기본 위치입니다. 이 제품들의 UI는 계속 진화하므로 필요 시 해당 문서를 확인하세요.

Claude Code

프로젝트 루트 .mcp.json:

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

또는 한 줄 명령으로:

claude mcp add ftp -- node /absolute/path/to/ftp-deploy-mcp/src/index.js

Claude Desktop

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

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

Cursor

~/.cursor/mcp.json (전역) 또는 .cursor/mcp.json (프로젝트):

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

Windsurf

~/.codeium/windsurf/mcp_config.json:

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

Trae

Trae에는 안정적인 설정 파일이 없습니다 — 모든 것이 UI에서 처리됩니다. AI 채팅 패널 → 설정/기어 → MCP → 추가 → 수동 구성을 선택한 다음 붙여넣으세요 (이것이 setup이 출력하고 클립보드에 복사하는 블록입니다):

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

Antigravity

버전에 따라 파일은 다음 중 하나입니다:

  • ~/.gemini/antigravity/mcp_config.json

  • 변형: ~/.gemini/config/mcp_config.json

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

에이전트의 MCP 패널(MCP 서버 관리) → 서버 추가를 사용할 수도 있으며, 동일한 구조를 사용합니다.


6. 10가지 도구

모든 원격 경로(path, remote_path, …)는 서버 root 기준이며 POSIX 스타일을 사용합니다. server 매개변수는 항상 선택 사항입니다 (아래 해석 규칙 참조).

도구

매개변수

설명

ftp_list_servers

(없음)

구성된 서버 목록 표시 (프로토콜, 호스트, 포트, 루트, 읽기 전용, 인증 종류). 비밀번호는 절대 표시하지 않음.

ftp_test

server?

연결하고, 루트를 나열하고, 성공 여부를 확인.

ftp_list

server?, path?

원격 디렉터리 나열 (디렉터리 우선).

ftp_read

server?, path, max_bytes?

텍스트 파일 읽기 (기본 262144, 최대 1048576 바이트). 바이너리 파일은 거부.

ftp_upload

server?, local_path, remote_path?

파일 하나 업로드, 상위 디렉터리 생성.

ftp_deploy

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

단일 연결로 디렉터리를 재귀적으로 배포, 기본 제외 패턴 적용. dry_run은 읽기 전용(readOnly) 서버에서도 작동.

ftp_download

server?, remote_path, local_path, overwrite?

파일 다운로드; overwrite: true가 아니면 덮어쓰기 거부.

ftp_mkdir

server?, path

디렉터리 생성 (재귀적).

ftp_rename

server?, from_path, to_path

이름 변경 또는 이동.

ftp_delete

server?, path, recursive?

파일 삭제; 디렉터리는 recursive: true 필요. 루트는 절대 삭제 불가.

서버 결정 방식: 명시적 server 매개변수 → defaultServer → 서버가 하나뿐이면 해당 서버 → 그 외에는 사용 가능한 이름 목록을 오류로 반환.

ftp_deploy 기본 제외 패턴: **/node_modules/**, **/.git/**, .env, .env.*, *.log, .DS_Store, Thumbs.db, ftp-servers.json, **/.ftp-mcp/** (사용자 exclude 글로브는 추가됨; include는 일치하는 파일로 제한). 슬래시 없는 패턴은 모든 깊이에서 일치 (gitignore 방식): 중첩된 apps/api/.env도 제외됨.


7. 예시 프롬프트

  • "./dist를 prod 서버에 배포해."

  • "ovh의 /www에 무엇이 있는지 나열해."

  • "prod에서 .htaccess를 가져와서 보여줘."

  • "./build를 /www에 배포하는 시뮬레이션(dry run)을 실행해 어떤 파일이 전송될지 확인하고 싶어."

  • "prod에서 index.old.html을 index.html로 이름을 변경해."


8. 보안

  • 기본적으로 보안 전송 사용: 일반 FTP 및 인증서 검증이 비활성화된(insecureTLS: true) FTPS는 서버 항목이 명시적으로 "allowInsecure": true를 설정하지 않으면 거부됨. 허용된 경우, 시작 시, ftp_list_servers에서, doctor에서, 그리고 해당 서버의 모든 도구 결과에 보안 경고가 추가됨.

  • 루트 감옥(Root jail): 모든 작업은 정규화된 후 서버 root 내에 머무르는지 검증됨. root가 /인 경우에도 이탈 시도(../…)는 거부됨.

  • 읽기 전용: readOnly: true는 모든 쓰기 작업(업로드, 배포, 디렉터리 생성, 이름 변경, 삭제)을 차단; 읽기는 계속 허용됨.

  • LLM에 자격 증명 노출 금지: 비밀번호, 암호문구 및 키는 도구 출력에 절대 반환되지 않음.

  • 원격 측정 없음: 자신의 서버 외부로 나가는 연결 없음.

  • 호출별 연결: 각 도구는 연결을 열고, 작업을 수행하고, 연결을 닫음 — 지속적인 세션 없음.


9. 문제 해결

  • 시간 초과 / 연결 불가 (FTP): 일반적으로 방화벽에 의해 **수동 모드(passive mode)**가 차단됨. 서버의 수동 포트에 연결할 수 있는지 확인.

  • SFTP 키 인증: privateKeyPath 설정 (~는 확장됨), 키가 암호화된 경우 passphrase 설정. 키 권한 확인.

  • "안전하지 않은 연결 거부됨": 서버가 일반 FTP를 사용하거나 인증서 검증이 비활성화된 FTPS를 사용함. sftp(또는 유효한 인증서가 있는 ftps)로 전환하거나 — 차단 위험을 완전히 수용하는 경우에만 해당 서버에 "allowInsecure": true를 설정.

  • 자체 서명 FTPS: insecureTLS: true는 검증되지 않은 인증서를 수락함. 이는 중간자 공격 방지를 비활성화하므로 "allowInsecure": true도 필요하며 모든 호출에 보안 경고를 출력함. 유효한 인증서를 설치하는 것이 좋음.

  • 암시적 FTPS (포트 990): implicitTLS: true (ftps 프로토콜) 설정은 레거시 서버용으로, AUTH TLS 명령 없이 첫 바이트부터 암호화함.

  • "서버가 구성되지 않음": 4개 위치에서 파일을 찾지 못함. ftp-servers.json을 생성하거나 --config <path> / FTP_MCP_CONFIG=<path>를 전달.

  • setup 후 클라이언트에 도구가 표시되지 않음: IDE를 완전히 다시 시작 (프로젝트만 닫지 말고 모든 창 닫기), 그런 다음 npm run doctor로 연결을 확인.

  • 잘못된 구성으로도 서버가 시작됨: 이는 의도된 동작입니다 (MCP 클라이언트는 시작 시 죽는 서버를 싫어함). 정확한 오류는 시작 시 stderr에 출력되고 모든 도구 호출에 반환됨.


개발

npm test          # runs the full smoke test (local FTP + SFTP, no external network)
node src/index.js --version
node src/index.js --help

기여

기여를 환영합니다 — 개발 환경 설정, 프로젝트 원칙 및 PR 체크리스트는 CONTRIBUTING.md를 참조하세요.

보안

취약점을 발견하셨나요? 공개 이슈를 열지 마시고 — 비공개로 신고하는 방법은 SECURITY.md를 참조하세요.

라이선스

MIT — 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
    37 npm
    3
    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.
    41 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    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
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server for managing remote SSH servers, enabling AI agents to execute commands, transfer files, and perform deployment operations securely.
    MIT