ftp-deploy-mcp 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.fr.md +468 -0
  2. package/CHANGELOG.md +415 -0
  3. package/CONTRIBUTING.fr.md +120 -0
  4. package/CONTRIBUTING.md +111 -0
  5. package/LICENSE +21 -0
  6. package/LICENSE.fr.md +26 -0
  7. package/README.fr.md +518 -0
  8. package/README.md +501 -2
  9. package/SECURITY.fr.md +99 -0
  10. package/SECURITY.md +94 -0
  11. package/docs/AGENT-PERFORMANCE.fr.md +144 -0
  12. package/docs/AGENT-PERFORMANCE.md +133 -0
  13. package/docs/ERROR-CONTRACT.fr.md +141 -0
  14. package/docs/ERROR-CONTRACT.md +137 -0
  15. package/docs/LANGUAGES.fr.md +68 -0
  16. package/docs/LANGUAGES.md +62 -0
  17. package/docs/RELEASE.fr.md +462 -0
  18. package/docs/RELEASE.md +435 -0
  19. package/docs/RESOURCE-BOUNDS.fr.md +144 -0
  20. package/docs/RESOURCE-BOUNDS.md +132 -0
  21. package/docs/SCRIPTED-EVALUATIONS.fr.md +190 -0
  22. package/docs/SCRIPTED-EVALUATIONS.md +181 -0
  23. package/docs/SECURITY-MODEL.fr.md +262 -0
  24. package/docs/SECURITY-MODEL.md +236 -0
  25. package/docs/STATE-STORAGE.fr.md +236 -0
  26. package/docs/STATE-STORAGE.md +231 -0
  27. package/docs/TRANSFERS.fr.md +140 -0
  28. package/docs/TRANSFERS.md +123 -0
  29. package/docs/WORKFLOW-MODEL.fr.md +63 -0
  30. package/docs/WORKFLOW-MODEL.md +62 -0
  31. package/evaluations/README.fr.md +95 -0
  32. package/evaluations/README.md +92 -0
  33. package/evaluations/fixture/README.txt +3 -0
  34. package/evaluations/fixture/catalog/alpha.txt +4 -0
  35. package/evaluations/fixture/catalog/bravo.txt +4 -0
  36. package/evaluations/fixture/catalog/charlie.txt +4 -0
  37. package/evaluations/fixture/catalog/delta.txt +4 -0
  38. package/evaluations/fixture/catalog/echo.txt +4 -0
  39. package/evaluations/fixture/catalog/foxtrot.txt +4 -0
  40. package/evaluations/fixture/reports/checks.txt +4 -0
  41. package/evaluations/fixture/reports/release.txt +3 -0
  42. package/evaluations/read-only.fr.xml +42 -0
  43. package/evaluations/read-only.xml +42 -0
  44. package/ftp-servers.example.json +27 -0
  45. package/install.cmd +29 -0
  46. package/install.sh +18 -0
  47. package/package.json +88 -4
  48. package/server.json +35 -0
  49. package/src/adapters/ftp.js +392 -0
  50. package/src/adapters/sftp.js +647 -0
  51. package/src/admission.js +15 -0
  52. package/src/atomic-write.js +131 -0
  53. package/src/clients.js +212 -0
  54. package/src/config.js +629 -0
  55. package/src/durable/confirmation.js +88 -0
  56. package/src/durable/engine.js +696 -0
  57. package/src/durable/plan.js +238 -0
  58. package/src/durable/schemas.js +206 -0
  59. package/src/durable/state.js +317 -0
  60. package/src/durable/status.js +175 -0
  61. package/src/durable-tools.js +94 -0
  62. package/src/errors.js +88 -0
  63. package/src/filezilla.js +260 -0
  64. package/src/i18n.js +58 -0
  65. package/src/index.js +175 -0
  66. package/src/local-path.js +130 -0
  67. package/src/locales/en.js +152 -0
  68. package/src/locales/errors.en.js +22 -0
  69. package/src/locales/errors.fr.js +22 -0
  70. package/src/locales/fr.js +152 -0
  71. package/src/locales/runtime.en.js +259 -0
  72. package/src/locales/runtime.fr.js +259 -0
  73. package/src/operations.js +276 -0
  74. package/src/redact.js +105 -0
  75. package/src/remote-path.js +101 -0
  76. package/src/scanner.js +103 -0
  77. package/src/setup.js +888 -0
  78. package/src/state/codec.mjs +103 -0
  79. package/src/state/index.mjs +1 -0
  80. package/src/state/io.mjs +173 -0
  81. package/src/state/records.mjs +60 -0
  82. package/src/state/store.mjs +360 -0
  83. package/src/state/writer.mjs +250 -0
  84. package/src/tool-registry.js +464 -0
  85. package/src/tools.js +1226 -0
  86. package/src/transfers.js +425 -0
  87. package/src/workflow/budget.mjs +83 -0
  88. package/src/workflow/events.mjs +199 -0
  89. package/src/workflow/model.mjs +179 -0
package/SECURITY.md ADDED
@@ -0,0 +1,94 @@
1
+ # Security Policy
2
+
3
+ **English** | [Français](./SECURITY.fr.md)
4
+
5
+ ## Supported versions
6
+
7
+ The project has not yet completed its first npm or MCP registry publication.
8
+ Security fixes target the current source branch and the latest tagged 0.x
9
+ release. Older 0.x tags may be asked to upgrade rather than receive a backport.
10
+
11
+ | Version | Support |
12
+ |---|---|
13
+ | Current source / latest 0.x tag | Supported |
14
+ | Older 0.x tags | Best effort |
15
+
16
+ ## Report a vulnerability privately
17
+
18
+ **Do not open a public GitHub issue, discussion, or pull request for a suspected
19
+ vulnerability.**
20
+
21
+ Use GitHub's private vulnerability reporting: open the
22
+ [repository Security page](https://github.com/alebgl77/ftp-deploy-mcp/security),
23
+ choose **Report a vulnerability**, and include:
24
+
25
+ - affected commit or version and protocol;
26
+ - minimal reproduction steps and required server configuration;
27
+ - expected versus observed access or disclosure;
28
+ - impact, including which local or remote files/credentials are exposed;
29
+ - any suggested remediation or disclosure deadline.
30
+
31
+ Avoid including live credentials or private keys. Use disposable test accounts
32
+ and redact logs. If private reporting is unavailable, open a public issue that
33
+ contains no vulnerability details and asks the maintainer to establish a
34
+ private channel.
35
+
36
+ The target is to acknowledge a complete report within 72 hours, then confirm
37
+ scope, coordinate a fix and tests, and agree on a disclosure date. This is a
38
+ response target, not a bug-bounty or resolution-time promise.
39
+
40
+ ## In scope
41
+
42
+ - Access outside `localRoot` through traversal, symlink, junction, or download
43
+ destination handling.
44
+ - SFTP access outside `root`, host-key verification bypass, or unsafe handling
45
+ of a verified host-key rotation.
46
+ - FTP/FTPS connection to a non-root client jail without the explicit
47
+ `allowUnsafeRemoteRoot` acknowledgment.
48
+ - Plain FTP or unverified FTPS without the explicit `allowInsecure`
49
+ acknowledgment.
50
+ - Credential or key material exposed in tool results, diagnostics, logs, or
51
+ errors.
52
+ - `readOnly` bypass, destructive-operation guard bypass, or incorrect success
53
+ reporting after a partial deployment.
54
+ - Corruption or unsafe replacement of server or MCP client configuration.
55
+ - A release archive accepted with changed shipped source, a changed dependency
56
+ manifest or source lockfile, or an unapproved dependency modifier or install hook.
57
+
58
+ Issues that depend on a malicious FTP server using symlinks outside a
59
+ client-configured sub-root are important but are **not claimed to be prevented**:
60
+ FTP/FTPS require a server-side account/chroot boundary. Likewise, SFTP
61
+ realpath/lstat checks reduce symlink escapes but cannot remove every race on a
62
+ malicious server. These limits are documented in the
63
+ [security model](./docs/SECURITY-MODEL.md).
64
+
65
+ ## Dependency and release integrity
66
+
67
+ The six direct production dependencies use exact versions.
68
+ `package-lock.json` is the sole source graph authority, shipped in the 90-file
69
+ source installation archive. Use `npm ci --omit=dev --ignore-scripts` after
70
+ extraction and retain this file. Changes require review of the complete graph,
71
+ its URLs and integrity values, then the checks in the
72
+ [release guide](./docs/RELEASE.md).
73
+
74
+ The 89-file npm package contains no lockfile and remains unqualified. A real
75
+ installation changed ten production dependencies. Workflows reject installed
76
+ graph drift before npm or MCP publication; a source proof cannot authorize npm.
77
+ A future matching npm graph is evidence only for the tested environment and date,
78
+ with no guarantee about future installations.
79
+
80
+ A passing dependency audit is evidence for that graph on that date, not a
81
+ guarantee against future advisories or a certification of production use.
82
+ Report archive-validation bypasses privately using the same process above.
83
+ GitHub releases do not establish npm or Official MCP Registry availability.
84
+
85
+ ## Research guidelines
86
+
87
+ - Test only systems and accounts you own or are authorized to assess.
88
+ - Do not access other users' data, degrade service, persist access, or use
89
+ social engineering.
90
+ - Stop once the issue is demonstrated and share details privately.
91
+ - Give the project a reasonable opportunity to remediate before disclosure.
92
+
93
+ Good-faith research following these guidelines is welcome, but this document
94
+ does not grant authorization against third-party FTP/SFTP services.
@@ -0,0 +1,144 @@
1
+ # Performances et intégration des agents
2
+
3
+ [English](./AGENT-PERFORMANCE.md)
4
+
5
+ Les optimisations réduisent les calculs locaux répétés dans le serveur MCP.
6
+ Elles s'appliquent à tout client utilisant ses outils stdio, quel que soit le
7
+ fournisseur du modèle. L'architecture, les noms des outils, les arguments, les
8
+ schémas de réponse, la vérification des transferts, l'annulation et les limites
9
+ d'admission sont conservés.
10
+
11
+ - L'adaptateur FTP/SFTP sélectionné est chargé au premier usage. Le coût de
12
+ l'import est reporté à la première connexion ; les suivants utilisent le
13
+ cache de modules de Node.
14
+ - Chaque redactor garde en cache les secrets littéraux triés et les motifs
15
+ compilés des secrets courts, puis les reconstruit à l'ajout d'un nouveau
16
+ secret distinct. La mémoire supplémentaire croît avec le nombre de secrets ;
17
+ l'ordre des remplacements et le comportement normal/strict sont préservés.
18
+ - Un comptage incrémental exact des octets JSON évite de sérialiser à répétition
19
+ les échantillons qui grandissent. La troncature du texte évite aussi une
20
+ sérialisation complète en double. Le contenu et les plafonds des réponses
21
+ sont préservés.
22
+
23
+ ## Connecter un agent
24
+
25
+ Installez les sources comme indiqué dans le [README](../README.fr.md). Utilisez
26
+ Node.js 22 ou plus récent et des chemins absolus explicites. Cette définition
27
+ générique utilise le checkout comme répertoire de travail du processus et
28
+ sélectionne directement la configuration des serveurs :
29
+
30
+ ```json
31
+ {
32
+ "command": "node",
33
+ "args": [
34
+ "/chemin/absolu/vers/ftp-deploy-mcp/src/index.js",
35
+ "--config",
36
+ "/chemin/absolu/vers/ftp-servers.json"
37
+ ],
38
+ "cwd": "/chemin/absolu/vers/ftp-deploy-mcp"
39
+ }
40
+ ```
41
+
42
+ Adaptez la configuration englobante au client. S'il n'accepte pas `cwd`, fixez
43
+ explicitement le répertoire de travail du lanceur ; conservez les deux chemins
44
+ absolus dans les arguments. `localRoot`, dans la configuration des serveurs,
45
+ détermine l'arborescence locale autorisée. `FTP_MCP_CONFIG` est une alternative
46
+ à `--config`, qui reste prioritaire. Voir la
47
+ [configuration](../README.fr.md#configuration-des-serveurs).
48
+
49
+ | Client agent | Mode d'intégration |
50
+ | --- | --- |
51
+ | Claude Code / Claude Desktop | Ajoutez la commande et les arguments sous `mcpServers` ; voir la [configuration du client](../README.fr.md#configuration-du-client). |
52
+ | OpenAI Agents SDK pour JavaScript | Créez `MCPServerStdio` avec les options de lancement, connectez-le et ajoutez-le aux `mcpServers` de l'agent. Fermez-le à la fin. Voir le [guide MCP du SDK](https://openai.github.io/openai-agents-js/guides/mcp/). |
53
+ | Gemini CLI | Ajoutez les options de lancement sous `mcpServers` dans `settings.json` ; voir le [guide MCP de Gemini CLI](https://geminicli.com/docs/tools/mcp-server/). |
54
+ | Qwen-Agent | Fournissez une configuration d'outil contenant `mcpServers` avec la commande et les arguments ; voir l'[exemple MCP de Qwen-Agent](https://github.com/QwenLM/Qwen-Agent#how-to-use-mcp). |
55
+
56
+ Ces modes décrivent la compatibilité stdio ; ils ne constituent pas des
57
+ résultats de tests de bout en bout propres à chaque fournisseur. Une
58
+ intégration MCP distante hébergée nécessite un serveur MCP HTTP accessible ou
59
+ un pont distinct. Ce dépôt expose stdio et n'ajoute ni serveur HTTP ni pont.
60
+
61
+ Pour un catalogue stable, OpenAI Agents SDK accepte l'option
62
+ `cacheToolsList: true` dans `MCPServerStdio`. Elle évite de répéter la découverte
63
+ des outils côté client. Invalidez ce cache avec `invalidateToolsCache()` si le
64
+ catalogue change ; voir les [conseils de cache du SDK](https://openai.github.io/openai-agents-js/guides/mcp/#other-things-to-know).
65
+
66
+ ## Cibler les appels
67
+
68
+ - Appelez `ftp_list` avec un `limit` adapté (50 par défaut, 200 au maximum).
69
+ Suivez le `next_offset` renvoyé seulement si d'autres entrées sont utiles.
70
+ La pagination borne la réponse MCP ; l'adaptateur récupère toujours la liste
71
+ du dossier distant.
72
+ - Appelez `ftp_read` avec un `max_bytes` explicite, par exemple 8192 pour un
73
+ petit fichier de configuration. Le défaut est de 256 Kio et le plafond de 1 Mio.
74
+ - Attendez chaque mutation dont dépend la suivante. Terminez par exemple un
75
+ envoi avant de renommer sa destination. Les lectures indépendantes peuvent
76
+ se chevaucher ; les verrous FIFO et les [limites d'admission](./RESOURCE-BOUNDS.fr.md)
77
+ restent actifs.
78
+ - Examinez un appel `ftp_deploy` avec `dry_run: true` avant le déploiement réel.
79
+ Cette simulation n'effectue aucune entrée-sortie réseau et ne vérifie pas
80
+ la cible distante.
81
+
82
+ Des réponses bornées et pertinentes, ainsi que la mesure du nombre d'appels,
83
+ du temps d'exécution et des erreurs suivent les
84
+ [conseils de conception d'outils d'Anthropic](https://www.anthropic.com/engineering/writing-tools-for-agents).
85
+
86
+ ## Reproduire les contrôles
87
+
88
+ Depuis un checkout complet avec les dépendances de développement :
89
+
90
+ ```bash
91
+ npm ci --ignore-scripts
92
+ npm run test:performance
93
+ npm test
94
+ npm run test:eval-runner
95
+ npm run eval:scripted
96
+ ```
97
+
98
+ `test:performance` exécute `test/redaction-performance.test.mjs` et
99
+ `test/agent-performance.test.mjs` ; ces régressions font aussi partie de
100
+ `npm test`.
101
+
102
+ Le benchmark d'exécution exige un checkout intact comme premier argument.
103
+ Créez et installez cette référence une fois, ou utilisez un checkout existant
104
+ du même commit :
105
+
106
+ ```bash
107
+ git worktree add --detach ../ftp-deploy-mcp-baseline a068aa5c3b1e53caaffc2c7907966ff5a2e9cb0f
108
+ npm --prefix ../ftp-deploy-mcp-baseline ci --ignore-scripts
109
+ npm run benchmark:agents -- ../ftp-deploy-mcp-baseline 200
110
+ ```
111
+
112
+ Le dernier argument optionnel est le nombre positif d'itérations (200 par
113
+ défaut). `benchmark:agents` exécute `scripts/benchmark-agent-performance.mjs`
114
+ avec des fixtures locales, vérifie l'équivalence des sorties et rapporte le
115
+ travail de sérialisation et la durée. Sa mesure de découverte couvre l'import
116
+ des modules, l'enregistrement et la liste des outils, sans le handshake stdio
117
+ complet. Consignez le commit, la version de Node.js, le système d'exploitation
118
+ et la charge avec les mesures ; répétez les comparaisons sur la même machine.
119
+ Sur les premières versions de Node.js 22 sans instrumentation des imports,
120
+ les contrôles utilisant ces hooks sont ignorés et les nombres de modules sont
121
+ indisponibles ; les mesures de durée du benchmark restent exécutées.
122
+
123
+ Le microbenchmark de masquage est optionnel et compare des sorties de fixtures
124
+ identiques à celles de l'implémentation précédente. Il affiche des médianes
125
+ indicatives sans assertion sur la durée. Dans un shell POSIX :
126
+
127
+ ```bash
128
+ REDACTION_BENCHMARK=1 node --test test/redaction-performance.test.mjs
129
+ ```
130
+
131
+ Dans PowerShell :
132
+
133
+ ```powershell
134
+ $env:REDACTION_BENCHMARK = '1'
135
+ node --test test/redaction-performance.test.mjs
136
+ Remove-Item Env:REDACTION_BENCHMARK
137
+ ```
138
+
139
+ Les tests et benchmarks mesurent le comportement de l'implémentation et son
140
+ exécution locale. Les [évaluations scriptées](./SCRIPTED-EVALUATIONS.fr.md)
141
+ exercent les handlers MCP sans modèle. Ils ne démontrent ni qualité du modèle,
142
+ ni économie de tokens, ni latence d'un fournisseur, ni débit FTP/SFTP en
143
+ production. Les [instructions d'évaluation d'agents](../evaluations/README.fr.md)
144
+ couvrent séparément les essais avec un modèle.
@@ -0,0 +1,133 @@
1
+ # Agent performance and client integration
2
+
3
+ [Français](./AGENT-PERFORMANCE.fr.md)
4
+
5
+ The performance work reduces repeated local computation in the MCP server.
6
+ It applies to any client using its stdio tools, independently of the model
7
+ provider. The server architecture, tool names, arguments, response schemas,
8
+ transfer verification, cancellation and admission limits remain in place.
9
+
10
+ - The selected FTP/SFTP adapter loads on first use. Its import cost moves to
11
+ the first connection; subsequent imports use Node's module cache.
12
+ - Each redactor caches sorted secret literals and compiled short-secret
13
+ patterns, rebuilding them when a new distinct secret is collected. Extra
14
+ storage grows with the number of secrets; replacement order and normal/strict
15
+ behavior are preserved.
16
+ - Exact incremental JSON byte accounting avoids repeatedly serializing growing
17
+ samples. Text truncation also avoids a duplicate full-result serialization.
18
+ Response content and size limits are preserved.
19
+
20
+ ## Connect an agent
21
+
22
+ Install from source as described in the [README](../README.md). Use Node.js 22
23
+ or newer and explicit absolute paths. This generic launch specification uses
24
+ the checkout as the process working directory and selects the server
25
+ configuration directly:
26
+
27
+ ```json
28
+ {
29
+ "command": "node",
30
+ "args": [
31
+ "/absolute/path/to/ftp-deploy-mcp/src/index.js",
32
+ "--config",
33
+ "/absolute/path/to/ftp-servers.json"
34
+ ],
35
+ "cwd": "/absolute/path/to/ftp-deploy-mcp"
36
+ }
37
+ ```
38
+
39
+ Adapt the surrounding configuration to the client. If it has no `cwd` setting,
40
+ set the launcher's working directory explicitly; keep both argument paths
41
+ absolute. `localRoot` in the server configuration determines the allowed local
42
+ file tree. `FTP_MCP_CONFIG` is an alternative to `--config`; the command-line
43
+ selector takes precedence. See [configuration](../README.md#server-configuration).
44
+
45
+ | Agent client | Integration route |
46
+ | --- | --- |
47
+ | Claude Code / Claude Desktop | Add the command and arguments under `mcpServers`; see [client setup](../README.md#client-setup). |
48
+ | OpenAI Agents SDK for JavaScript | Create `MCPServerStdio` with the launch options, connect it and attach it to the agent's `mcpServers`. Close it when finished. See the [SDK MCP guide](https://openai.github.io/openai-agents-js/guides/mcp/). |
49
+ | Gemini CLI | Add the launch options under `mcpServers` in `settings.json`; see the [Gemini CLI MCP guide](https://geminicli.com/docs/tools/mcp-server/). |
50
+ | Qwen-Agent | Supply a tool configuration containing `mcpServers` with the command and arguments; see the [Qwen-Agent MCP example](https://github.com/QwenLM/Qwen-Agent#how-to-use-mcp). |
51
+
52
+ These routes describe stdio compatibility; they are not provider-specific
53
+ end-to-end test results. Remote hosted MCP integrations require an accessible
54
+ HTTP MCP server or a separate bridge. This repository exposes stdio and adds
55
+ no HTTP server or bridge.
56
+
57
+ For a stable tool catalog, the OpenAI Agents SDK optionally accepts
58
+ `cacheToolsList: true` in `MCPServerStdio`. This avoids repeated client-side
59
+ tool discovery. Invalidate the cache with `invalidateToolsCache()` when the
60
+ catalog changes; see the [SDK caching guidance](https://openai.github.io/openai-agents-js/guides/mcp/#other-things-to-know).
61
+
62
+ ## Keep calls focused
63
+
64
+ - Use `ftp_list` with an appropriate `limit` (default 50, maximum 200). Follow
65
+ the returned `next_offset` only when more entries are needed. Pagination
66
+ bounds the MCP response; the adapter still obtains the remote directory list.
67
+ - Use `ftp_read` with an explicit `max_bytes`, for example 8192 for a small
68
+ configuration file. The default is 256 KiB and the hard maximum is 1 MiB.
69
+ - Await dependent mutations in sequence. For example, finish an upload before
70
+ renaming its destination. Independent reads may overlap; the server's FIFO
71
+ locks and [admission limits](./RESOURCE-BOUNDS.md) still apply.
72
+ - Review an `ftp_deploy` call with `dry_run: true` before executing the deploy.
73
+ The dry run performs no network I/O and does not verify the remote endpoint.
74
+
75
+ Bounded, relevant responses and measurement of tool-call counts, runtime and
76
+ errors follow [Anthropic's tool design guidance](https://www.anthropic.com/engineering/writing-tools-for-agents).
77
+
78
+ ## Reproduce the checks
79
+
80
+ Run from a full repository checkout with development dependencies installed:
81
+
82
+ ```bash
83
+ npm ci --ignore-scripts
84
+ npm run test:performance
85
+ npm test
86
+ npm run test:eval-runner
87
+ npm run eval:scripted
88
+ ```
89
+
90
+ `test:performance` runs `test/redaction-performance.test.mjs` and
91
+ `test/agent-performance.test.mjs`; these regressions also run in `npm test`.
92
+
93
+ The runtime benchmark requires a pristine checkout as its first argument.
94
+ Create and install this baseline once, or use an existing checkout of the same
95
+ commit:
96
+
97
+ ```bash
98
+ git worktree add --detach ../ftp-deploy-mcp-baseline a068aa5c3b1e53caaffc2c7907966ff5a2e9cb0f
99
+ npm --prefix ../ftp-deploy-mcp-baseline ci --ignore-scripts
100
+ npm run benchmark:agents -- ../ftp-deploy-mcp-baseline 200
101
+ ```
102
+
103
+ The optional final argument is the positive iteration count (default 200).
104
+ `benchmark:agents` runs `scripts/benchmark-agent-performance.mjs` with local
105
+ fixtures, checks output equivalence, and reports serialization work and elapsed
106
+ time. Its discovery measurement covers module import, registration and tool
107
+ listing, not the complete stdio handshake. Record the commit, Node.js version,
108
+ operating system and workload alongside timings; repeat comparisons on the
109
+ same machine. On early Node.js 22 releases without module-load instrumentation,
110
+ hook-based checks are skipped and module counts are unavailable; benchmark
111
+ timings still run.
112
+
113
+ The redaction microbenchmark is opt-in and compares identical fixture outputs
114
+ with the previous implementation. It reports diagnostic medians without timing
115
+ assertions. On POSIX shells:
116
+
117
+ ```bash
118
+ REDACTION_BENCHMARK=1 node --test test/redaction-performance.test.mjs
119
+ ```
120
+
121
+ On PowerShell:
122
+
123
+ ```powershell
124
+ $env:REDACTION_BENCHMARK = '1'
125
+ node --test test/redaction-performance.test.mjs
126
+ Remove-Item Env:REDACTION_BENCHMARK
127
+ ```
128
+
129
+ The tests and benchmarks measure implementation behavior and local execution.
130
+ The [scripted evaluations](./SCRIPTED-EVALUATIONS.md) exercise MCP handlers
131
+ without a model. They do not establish model quality, token savings, provider
132
+ latency or production FTP/SFTP throughput. Use the separate
133
+ [agent evaluation instructions](../evaluations/README.md) for model-based work.
@@ -0,0 +1,141 @@
1
+ # Contrat d’erreur MCP
2
+
3
+ [English](./ERROR-CONTRACT.md)
4
+
5
+ Les dix outils legacy et leurs champs de succès restent stables. Lorsque
6
+ `stateDir` est activé, quatre outils durables supplémentaires sont publiés. Neuf outils legacy
7
+ publient un `oneOf` JSON Schema draft-07 avec deux possibilités strictes :
8
+ l’objet de succès existant et `{error}`. `ftp_read` conserve un succès texte
9
+ sans schéma de sortie ; ses erreurs utilisent néanmoins l’enveloppe validée
10
+ en interne. Les clients doivent lire `isError` et les champs stables ci-dessous
11
+ plutôt qu’analyser le texte traduit.
12
+
13
+ ```json
14
+ {
15
+ "isError": true,
16
+ "content": [{ "type": "text", "text": "Erreur : READ_ONLY : …" }],
17
+ "structuredContent": {
18
+ "error": {
19
+ "schema_version": 1,
20
+ "code": "READ_ONLY",
21
+ "message": "…",
22
+ "retryable": false,
23
+ "request_id": "46d0223a-de77-4f28-bf77-f8b9706a4ecb",
24
+ "next_action": "fix_config",
25
+ "effects": "none"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ `request_id` est un nouvel UUID créé par le serveur pour un appel. Il est
32
+ distinct de l’identifiant de corrélation JSON-RPC et n’identifie pas une
33
+ opération durable. Les erreurs durables ajoutent `plan_id` et `operation_id`
34
+ facultatifs, une phase et des compteurs partiels durables. Ces identifiants
35
+ réfèrent uniquement au workflow journalisé et jamais à la corrélation JSON-RPC.
36
+
37
+ ## Décisions stables et effets observés
38
+
39
+ Les valeurs acceptées pour `code` sont :
40
+
41
+ `CONFIG_REQUIRED`, `CONFIG_INVALID`, `SERVER_REQUIRED`, `SERVER_UNKNOWN`,
42
+ `INVALID_ARGUMENT`, `READ_ONLY`, `TRANSPORT_POLICY`, `HOST_KEY_REJECTED`,
43
+ `REMOTE_ROOT_REJECTED`, `PATH_REJECTED`, `NOT_FOUND`, `ALREADY_EXISTS`,
44
+ `TRANSFER_LIMIT`, `TRANSFER_VERIFY`, `TARGET_CHANGED`, `CANCELLED`, `TIMEOUT`,
45
+ `DEPLOY_PARTIAL`, `TRANSPORT_ERROR`, `OUTPUT_LIMIT`, `INTERNAL_ERROR`,
46
+ `SCAN_LIMIT`, `CAPACITY_LIMIT`.
47
+
48
+ Les outils durables utilisent aussi `DURABLE_STATE_DISABLED`,
49
+ `DURABLE_STATE_BUSY`, `DURABLE_STATE_LIMIT`, `DURABLE_STATE_CORRUPT`,
50
+ `DURABLE_STATE_IO`, `DURABLE_STATE_UNSUPPORTED`, `PLAN_NOT_FOUND`,
51
+ `PLAN_EXPIRED`, `PLAN_STALE`, `PLAN_CONSUMED`, `IDEMPOTENCY_CONFLICT`,
52
+ `CONFIRMATION_DECLINED`, `CONFIRMATION_INVALID`,
53
+ `CLIENT_INTERACTION_UNSUPPORTED`, `STATUS_CHANGED`,
54
+ `RECONCILIATION_REQUIRED`, `ROLLBACK_CONFLICT` et `WORKFLOW_UNSUPPORTED`.
55
+ L’état v1 est refusé avant décodage des plans et exige une réinitialisation hors ligne.
56
+
57
+ `SCAN_LIMIT` refuse toute la sélection locale avant connexion, avec `effects:none`
58
+ et `next_action:fix_input`. `CAPACITY_LIMIT` refuse l’admission avant préparation,
59
+ avec `effects:none` et `next_action:retry`. Les deux conservent `retryable:false` ;
60
+ voir les [limites de ressources](./RESOURCE-BOUNDS.fr.md).
61
+
62
+ Les valeurs legacy de `next_action` sont `fix_input`, `fix_config`,
63
+ `select_server`, `inspect_target`, `retry`, `contact_operator` et `none`.
64
+ Les erreurs durables peuvent aussi indiquer `status`, `resume_apply`,
65
+ `resume_rollback`, `offline_recovery` ou `upgrade_client`.
66
+ Les erreurs des outils historiques renvoient prudemment `retryable:false`. Les
67
+ erreurs durables ne renvoient `retryable:true` que pour
68
+ `DURABLE_STATE_BUSY`, `DURABLE_STATE_IO`, `STATUS_CHANGED` et les échecs
69
+ `TIMEOUT` ou `TRANSPORT_ERROR` projetés ; toutes les autres renvoient `false`.
70
+ Ce drapeau guide le client sans jamais autoriser une reprise automatique : il
71
+ faut suivre `next_action`, surtout après un effet `possible` ou `confirmed`.
72
+
73
+ | `effects` | Signification |
74
+ | --- | --- |
75
+ | `none` | Aucune opération mutatrice n’a été lancée par cet appel. |
76
+ | `possible` | Une mutation a été lancée, sans effet encore confirmé. |
77
+ | `confirmed` | Au moins un effet a été acquitté ; d’autres effets peuvent rester incertains. |
78
+
79
+ Les effets comprennent les temporaires, leur nettoyage et les écritures
80
+ locales des téléchargements. Une politique distante de lecture seule
81
+ n’interdit pas ces écritures locales. `confirmed` ne signifie pas que toute
82
+ l’opération a réussi ; zéro fichier terminé ne prouve pas l’absence de
83
+ modification. Un échec de fermeture après promotion conserve les effets confirmés.
84
+
85
+ Les erreurs d’envoi, de téléchargement et de déploiement peuvent inclure les
86
+ champs stricts `partial` : `completed_files`, `completed_bytes`, `failed_files`,
87
+ `total_files` facultatif et `final`. Ce sont des entiers sûrs non négatifs,
88
+ issus des promotions observées et des tentatives de fichier en échec, sans
89
+ analyse du texte. Les octets comptent les fichiers terminés, pas tout le
90
+ trafic réseau ni les temporaires partiellement écrits. `final: false` indique
91
+ un instantané anticipé pendant que le traitement sous-jacent se termine.
92
+ Cet instantané n’est pas révisé ultérieurement dans la réponse.
93
+
94
+ ## Protocole, annulation et limites de sortie
95
+
96
+ Les arguments rejetés par le schéma d’entrée déclaré d’un outil connu donnent
97
+ `INVALID_ARGUMENT` avant son traitement, sa connexion ou une écriture locale.
98
+ Un refus métier ultérieur peut survenir après connexion. Un outil inconnu renvoie
99
+ une erreur de protocole JSON-RPC `-32602` bornée sans reprendre son nom.
100
+ Les trames mal formées conservent le traitement des erreurs de protocole du SDK.
101
+
102
+ L’annulation MCP du client interrompt l’opération et le SDK supprime sa réponse :
103
+ le client ne doit pas attendre une enveloppe `CANCELLED`. Les échéances internes
104
+ sont distinctes et peuvent renvoyer `TIMEOUT`. L’annulation native du SDK v2
105
+ traite chaque identifiant de requête valide, dont le nombre `0` et la chaîne vide
106
+ `""`. Une garde du canal, possédée par le point d’entrée, réserve chaque
107
+ identifiant de requête JSON-RPC entrant, toutes méthodes confondues, avant sa
108
+ remise au SDK. Tout doublon actif ferme la connexion avant l’exécution d’un
109
+ second handler, même si sa méthode diffère. L’identifiant d’un appel d’outil
110
+ n’est libéré qu’après le règlement ou l’échec de l’envoi de sa réponse terminale
111
+ et le règlement réel de tous les tours de handler enregistrés, de sa préparation
112
+ et de sa libération, y compris tous les tours legacy d’entrée requise.
113
+ Si l’annulation supprime la réponse, il reste réservé jusqu’à la fermeture de la
114
+ connexion. Des identifiants concurrents distincts, dont `0`, `""`, `1` et `"0"`,
115
+ restent indépendants. Une réponse dont l’envoi a déjà commencé ne peut pas être
116
+ rappelée. L’annulation n’efface pas les effets.
117
+
118
+ Les résultats d’outils connus sont masqués, bornés puis validés à une seule
119
+ frontière. La limite de 25 000 octets porte sur le `JSON.stringify` UTF-8 du
120
+ résultat complet, texte et données structurées compris, après expansion du
121
+ masquage. Les avertissements de sécurité ont un rôle de rendu interne distinct.
122
+ Des chaînes distantes telles que `Page:` ou `SECURITY WARNING` ne peuvent pas
123
+ acquérir ce rôle. La pagination est reconstruite après réduction des exemples.
124
+ Un repli `OUTPUT_LIMIT` conserve l’UUID, les effets et les compteurs observés.
125
+
126
+ Les décisions typées précèdent la traduction. Les détails natifs restent des
127
+ données de leur source sous un libellé traduit ; une réponse FTP ambiguë,
128
+ dont le code 550, ne devient pas `NOT_FOUND` d’après son texte. Les secrets
129
+ sont masqués dans le texte libre. Seules les constantes publiques propres au
130
+ schéma et l’UUID créé pour ce résultat sont préservés à leurs chemins
131
+ structurés exacts. Les objets Error, causes, piles, identifiants secrets et
132
+ arguments bruts ne sont pas sérialisés. Les secrets de configuration sont
133
+ collectés en privé avant validation, y compris ceux des entrées rejetées et
134
+ les valeurs ENV résolues. Les diagnostics publics du chargeur utilisent le
135
+ même masque. Un JSON mal formé reçoit un diagnostic générique sans extrait
136
+ du parseur ; aucune extraction fiable de secrets depuis ce contenu n’est
137
+ revendiquée. Les blocs FileZilla rejetés sont identifiés par leur index
138
+ numérique, sans reprise des noms ou champs de protocole non fiables.
139
+
140
+ Voir les [langues](./LANGUAGES.fr.md), le [modèle de sécurité](./SECURITY-MODEL.fr.md)
141
+ et les [transferts vérifiés](./TRANSFERS.fr.md) pour les autres limites.
@@ -0,0 +1,137 @@
1
+ # MCP error contract
2
+
3
+ [Français](./ERROR-CONTRACT.fr.md)
4
+
5
+ The ten legacy tool names and success field shapes remain stable. When
6
+ `stateDir` is enabled, four durable tools are additionally advertised. Nine legacy tools publish
7
+ a JSON Schema draft-07 `oneOf` with two strict alternatives: the existing
8
+ success object and `{error}`. `ftp_read` retains text-only success and no
9
+ output schema; its errors still use the internally validated envelope.
10
+ Clients should use `isError` and the stable fields below, rather than parse
11
+ the localized human text.
12
+
13
+ ```json
14
+ {
15
+ "isError": true,
16
+ "content": [{ "type": "text", "text": "Error: READ_ONLY: …" }],
17
+ "structuredContent": {
18
+ "error": {
19
+ "schema_version": 1,
20
+ "code": "READ_ONLY",
21
+ "message": "…",
22
+ "retryable": false,
23
+ "request_id": "46d0223a-de77-4f28-bf77-f8b9706a4ecb",
24
+ "next_action": "fix_config",
25
+ "effects": "none"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ `request_id` is a fresh server-generated UUID for one call. It is unrelated to
32
+ the JSON-RPC correlation ID and is not a durable operation identifier. No
33
+ `operation_id`. Durable errors additionally carry nullable `plan_id` and
34
+ `operation_id`, a phase, and durable partial counters. Those identifiers refer
35
+ only to the sealed journal workflow and are never JSON-RPC correlation IDs.
36
+
37
+ ## Stable decisions and effect observations
38
+
39
+ The accepted `code` values are:
40
+
41
+ `CONFIG_REQUIRED`, `CONFIG_INVALID`, `SERVER_REQUIRED`, `SERVER_UNKNOWN`,
42
+ `INVALID_ARGUMENT`, `READ_ONLY`, `TRANSPORT_POLICY`, `HOST_KEY_REJECTED`,
43
+ `REMOTE_ROOT_REJECTED`, `PATH_REJECTED`, `NOT_FOUND`, `ALREADY_EXISTS`,
44
+ `TRANSFER_LIMIT`, `TRANSFER_VERIFY`, `TARGET_CHANGED`, `CANCELLED`, `TIMEOUT`,
45
+ `DEPLOY_PARTIAL`, `TRANSPORT_ERROR`, `OUTPUT_LIMIT`, `INTERNAL_ERROR`,
46
+ `SCAN_LIMIT`, `CAPACITY_LIMIT`.
47
+
48
+ Durable tools also use `DURABLE_STATE_DISABLED`, `DURABLE_STATE_BUSY`,
49
+ `DURABLE_STATE_LIMIT`, `DURABLE_STATE_CORRUPT`, `DURABLE_STATE_IO`,
50
+ `DURABLE_STATE_UNSUPPORTED`, `PLAN_NOT_FOUND`, `PLAN_EXPIRED`, `PLAN_STALE`,
51
+ `PLAN_CONSUMED`, `IDEMPOTENCY_CONFLICT`, `CONFIRMATION_DECLINED`,
52
+ `CONFIRMATION_INVALID`, `CLIENT_INTERACTION_UNSUPPORTED`, `STATUS_CHANGED`,
53
+ `RECONCILIATION_REQUIRED`, `ROLLBACK_CONFLICT`, and `WORKFLOW_UNSUPPORTED`.
54
+ State v1 is rejected before plan decoding and requires offline reinitialization.
55
+
56
+ `SCAN_LIMIT` refuses the complete local selection before connecting, with
57
+ `effects:none` and `next_action:fix_input`. `CAPACITY_LIMIT` refuses admission
58
+ before preparation, with `effects:none` and `next_action:retry`. Both keep
59
+ `retryable:false`; see [resource limits](./RESOURCE-BOUNDS.md).
60
+
61
+ The accepted legacy `next_action` values are `fix_input`, `fix_config`,
62
+ `select_server`, `inspect_target`, `retry`, `contact_operator`, and `none`.
63
+ Durable errors can additionally direct `status`, `resume_apply`,
64
+ `resume_rollback`, `offline_recovery`, or `upgrade_client`.
65
+ Legacy-tool errors conservatively return `retryable:false`. Durable errors set
66
+ `retryable:true` only for `DURABLE_STATE_BUSY`, `DURABLE_STATE_IO`,
67
+ `STATUS_CHANGED`, and mapped `TIMEOUT` or `TRANSPORT_ERROR` failures; all other
68
+ durable errors return `false`. The flag is guidance for the caller and never
69
+ authorizes an automatic retry: follow `next_action`, especially after any
70
+ `possible` or `confirmed` effect.
71
+
72
+ | `effects` | Meaning |
73
+ | --- | --- |
74
+ | `none` | No mutating operation was dispatched by this call. |
75
+ | `possible` | Mutation was dispatched, without a confirmed effect yet. |
76
+ | `confirmed` | At least one effect was acknowledged; further uncertain effects may exist. |
77
+
78
+ Effects include temporary files, cleanup and local download writes. A remote
79
+ read-only policy does not prohibit local download writes. `confirmed` does
80
+ not mean that the entire operation succeeded, and zero completed files does
81
+ not prove that nothing changed. A transport close failure after promotion
82
+ retains confirmed effects.
83
+
84
+ Upload, download and deployment errors can include strict `partial` fields:
85
+ `completed_files`, `completed_bytes`, `failed_files`, optional `total_files`,
86
+ and `final`. Counts are safe nonnegative integers from observed promotions
87
+ and failed file attempts, not text parsing. Bytes count completed files;
88
+ they do not represent all network traffic or partially written temporaries.
89
+ `final: false` marks an early snapshot while the underlying worker still
90
+ settles. An early snapshot is not later revised on the wire.
91
+
92
+ ## Protocol, cancellation and output boundaries
93
+
94
+ Arguments rejected by a known tool's declared input schema produce
95
+ `INVALID_ARGUMENT` before its handler, connection or filesystem write. Later
96
+ business-policy refusals can occur after connection. An unknown tool returns a
97
+ bounded JSON-RPC `-32602` protocol error without echoing its name. Malformed
98
+ protocol frames retain the SDK's protocol-error handling.
99
+
100
+ MCP peer cancellation aborts the operation, and the SDK suppresses its result;
101
+ clients must not expect a delivered `CANCELLED` envelope. Internal deadlines
102
+ are distinct and may return `TIMEOUT`. SDK v2 cancellation handles every valid
103
+ request ID natively, including numeric `0` and the empty string `""`. An
104
+ entry-owned wire guard reserves every inbound JSON-RPC request ID, across all
105
+ methods, before SDK dispatch. A duplicate active ID closes the connection before
106
+ a second handler runs, including a cross-method duplicate. A tool-call ID is
107
+ released only after both its terminal response send settles or fails and its
108
+ all registered handler-turn workers, preparation and disposal settle, including
109
+ all legacy input-required rounds. If peer cancellation suppresses
110
+ the response, the ID remains reserved until the connection closes. Concurrent
111
+ different IDs, including `0`, `""`, `1` and `"0"`, remain independent. A
112
+ response whose send already started cannot be recalled. Cancellation does not
113
+ undo effects.
114
+
115
+ Known tool results are redacted, bounded and validated once at the boundary.
116
+ The 25,000-byte limit covers UTF-8 `JSON.stringify` of the complete result,
117
+ including both text and structured data, after redaction expansion. Required
118
+ security notices have a separate trusted rendering role. Remote strings such
119
+ as `Page:` and `SECURITY WARNING` cannot acquire that role. Pagination is
120
+ rebuilt after sample shrinking. An `OUTPUT_LIMIT` fallback preserves request
121
+ UUID, effects and observed partial counters.
122
+
123
+ Typed decisions precede translation. Native diagnostic details remain source
124
+ data under a localized wrapper; ambiguous FTP responses, including code 550,
125
+ do not become `NOT_FOUND` based on prose. Secrets are masked in free text.
126
+ Only schema-specific public constants and the UUID minted for this result
127
+ are preserved at their exact structured paths. Error objects, causes, stacks,
128
+ credentials and raw arguments are not serialized. Configuration credentials
129
+ are collected privately before validation, including values from rejected
130
+ entries and resolved environment placeholders. Public loader diagnostics use
131
+ the same mask. Malformed JSON receives a generic syntax diagnostic with no
132
+ parser excerpt; no reliable secret extraction from malformed input is claimed.
133
+ Rejected FileZilla blocks are identified by their numeric block index without
134
+ echoing untrusted names or protocol fields.
135
+
136
+ See [languages](./LANGUAGES.md), the [security model](./SECURITY-MODEL.md)
137
+ and [verified transfers](./TRANSFERS.md) for the remaining operational limits.