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
@@ -0,0 +1,435 @@
1
+ # Release Guide
2
+
3
+ **English** | [Français](./RELEASE.fr.md)
4
+
5
+ This is the maintainer checklist for the GitHub source release v0.3.1 and,
6
+ separately, a future first npm and Official MCP Registry publication. A GitHub
7
+ release does not make the npm identifier, `npx` command or registry entry
8
+ available. The published GitHub v0.3.0 tag and release remain immutable.
9
+
10
+ Check available source assets on [GitHub Releases](https://github.com/alebgl77/ftp-deploy-mcp/releases/latest).
11
+ The npm and MCP workflows are manual (`workflow_dispatch`); do not dispatch
12
+ them as part of a source-only GitHub release.
13
+
14
+ The MCP runtime supports Node.js >=22. Release tooling requires **Node.js 24.20.0 exactly**: archive creation and inspection, source inventories, installed-graph checks, registry verification and `npm run test:release` reject other versions before performing those operations. Use the qualified Node distribution, including its bundled npm, and record Node/libuv/npm/tar versions with `node scripts/release-toolchain.mjs`. Consumer runtime qualification may still execute the MCP server with Node 22 while invoking the external release tools separately with Node 24.20.0.
15
+
16
+ CI retains all nine runtime jobs (Node 22/24/26 on Linux, macOS and Windows). The complete release suite runs separately on all three operating systems with Node 24.20.0. macOS has three predefined full passes; any failed pass fails the job. These are fixed qualification passes, with no failure retries, skipped cases or increased archive limits. Supply-chain and npm/MCP publication workflows use the same exact release Node version.
17
+
18
+ Pull requests also run dependency review and reject newly introduced high or
19
+ critical vulnerabilities. CodeQL analyzes JavaScript/TypeScript on pushes,
20
+ pull requests and a weekly schedule. The supply-chain job uploads its validated
21
+ CycloneDX 1.5 production SBOM as a 14-day CI artifact only after its complete
22
+ package identity set matches `npm ls --omit=dev --all`; the SBOM is not added
23
+ to npm packages or publication artifacts. The SBOM gate compares identities
24
+ rather than CycloneDX edge layout because npm may hoist or deduplicate paths;
25
+ the separate release-graph gate validates the installed production graph
26
+ against the authoritative lockfile.
27
+
28
+ ## Manual prerequisites
29
+
30
+ Complete these outside the repository before dispatching npm or MCP publication:
31
+
32
+ - Confirm control of the intended npm package name and maintainer access to the
33
+ npm account or organization.
34
+ - Enforce npm two-factor authentication and use a dedicated maintainer account
35
+ with least privilege.
36
+ - For a package that already exists, configure npm Trusted Publishing for
37
+ `alebgl77/ftp-deploy-mcp` and workflow filename `release.yml`. No named GitHub
38
+ environment is configured by these workflows.
39
+ - For the **first** publication, npm requires the package to exist before a
40
+ trust relationship can be configured. A maintainer must create a short-lived
41
+ granular token with the minimum available publish scope and the required
42
+ non-interactive/2FA permission, and store it only as `NPM_TOKEN` in GitHub
43
+ Actions secrets. After the first successful publish, configure Trusted
44
+ Publishing, remove that secret, and revoke the token immediately. Never
45
+ commit, paste into an issue/chat, or print the token. Do not create a
46
+ persistent token as a workaround. See [npm trust prerequisites](https://docs.npmjs.com/cli/v11/commands/npm-trust/)
47
+ and [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
48
+ - Confirm that the release workflow requests npm provenance and has only the
49
+ permissions it needs. Repository automation cannot create npm ownership or
50
+ approve a new package name.
51
+ - Establish the publisher identity and namespace required by the official MCP
52
+ registry, and confirm access to its current publishing tool. A prepared
53
+ manifest or third-party index listing is not a registry publication.
54
+ - Confirm that GitHub environments, required reviewers, branch protection, and
55
+ release permissions are in place.
56
+
57
+ Record who completed each manual prerequisite and when. Do not start npm or MCP
58
+ publication while any ownership or credential step is unresolved. These registry
59
+ prerequisites do not establish whether a GitHub source release is ready.
60
+
61
+ ## Version and changelog gate
62
+
63
+ For the v0.3.1 GitHub release:
64
+
65
+ 1. Set `version` in [package.json](../package.json), the root and root-package
66
+ metadata of `package-lock.json`, and `server.json` to `0.3.1`. The source lockfile
67
+ is the sole authoritative lockfile; do not keep a parallel `npm-shrinkwrap.json`.
68
+ 2. Set the MCP server's reported version to the same value. Search the source
69
+ and generated metadata for stale release strings; expected historical
70
+ references in the changelog are exempt.
71
+ 3. Use the paired heading `## [0.3.1] - 2026-10-17` in
72
+ [CHANGELOG.md](../CHANGELOG.md) and [CHANGELOG.fr.md](../CHANGELOG.fr.md).
73
+ Confirm the effective release date is a valid calendar date, update it only
74
+ if the actual release day changes, and preserve the complete 0.3.0 history.
75
+ 4. Confirm every user-visible minor-release change is documented in both
76
+ [README.md](../README.md) and [README.fr.md](../README.fr.md).
77
+ 5. Verify the release includes atomic replacement for newly written sensitive
78
+ configuration and tests its failure path. This is a release gate, not a
79
+ documentation-only claim.
80
+ 6. Confirm `node src/index.js --version`, package metadata, source lockfile and tag
81
+ agree. Verify npm and Official MCP Registry versions separately only after
82
+ their respective publications.
83
+
84
+ Use a dedicated release commit. Never move or reuse the published v0.2.0 tag.
85
+
86
+ ## Review the source and installed dependency graphs
87
+
88
+ The sole source authority is `package-lock.json`, with six exact direct pins and
89
+ 112 non-root records: 110 production and two development. Source/Docker/user
90
+ installs use `npm ci --omit=dev --ignore-scripts`; contributors use
91
+ `npm ci --ignore-scripts`. No shrinkwrap is retained. npm 12 no longer reads or
92
+ writes shrinkwrap files, including in dependencies; see the
93
+ [official npm lockfile documentation](https://docs.npmjs.com/cli/v12/configuring-npm/package-lock-json/).
94
+
95
+ For an approved dependency update, change only the intended exact versions and
96
+ regenerate the source lock with scripts disabled. Record Node/npm versions and
97
+ review every graph change, registry URL, integrity value and optional/platform
98
+ constraint. Keep the original reviewed source inventory as the authority when
99
+ checking installations; never update it from the installed graph.
100
+
101
+ The built-in-only graph verifier reads actual package manifests and installed
102
+ lock records, including the hidden lock when present. It resolves transitive
103
+ dependencies and peers from their real locations, including hoisted and nested
104
+ packages. It rejects version/resolution/integrity drift, undeclared extras,
105
+ missing required packages and changed dependency declarations. Declared optional
106
+ omissions and source-proven development packages are reported separately; a
107
+ present optional dependency with changed bytes or metadata is not an omission.
108
+ No dependency code is imported by this verifier.
109
+
110
+ ## Qualify the source installation archive
111
+
112
+ There are two fixed distributions. `ftp-deploy-mcp-0.3.1-source.tar.gz` contains
113
+ 90 regular files under `package/`, including the source lock. It is an
114
+ installation archive, not the complete repository: tests, maintainer scripts and
115
+ the HTML guide remain in the repository. GitHub's automatic source ZIP/tar
116
+ contains the complete repository. The separate npm archive
117
+ `ftp-deploy-mcp-0.3.1.tgz` contains 89 files and no lockfile.
118
+
119
+ Capture all 90 reviewed source files before installation, tests or packing.
120
+ Keep the portable inventory outside the checkout and retain its original SHA256
121
+ independently of any generated proof. A source-only snapshot qualifies a pre-tag
122
+ candidate; publication requires the actual clean tracked tag/event checkout.
123
+ For a pre-tag candidate, run from the intended checkout (Bash):
124
+
125
+ ```bash
126
+ source_root="$(pwd -P)"
127
+ release_tmp="$(mktemp -d)"
128
+ inventory="$release_tmp/source-inventory.json"
129
+ node scripts/release-artifact.mjs snapshot "$inventory" --source-root "$source_root" --source-only
130
+ ```
131
+
132
+ Record the original snapshot output in `inventory_sha256` before continuing.
133
+ Never replace it with a value reread from a rewritten proof.
134
+
135
+ ```bash
136
+ npm ci --ignore-scripts
137
+ npm test
138
+ npm run test:release
139
+ node scripts/release-gate.mjs --source-only --runtime
140
+ node scripts/release-artifact.mjs build-source "$release_tmp/source-pack.json" --source-root "$source_root" --inventory "$inventory" --inventory-sha256 "$inventory_sha256" --source-only
141
+ node scripts/release-artifact.mjs preflight "$release_tmp/source-pack.json" --source-root "$source_root" --inventory "$inventory" --inventory-sha256 "$inventory_sha256" --distribution source
142
+ ```
143
+
144
+ The builder writes only captured bytes into a private staging directory and uses
145
+ real tar with bounded stdout. It validates every byte before exclusive archive
146
+ and metadata writes. Retain the preflight's original canonical `tarball` path
147
+ and SHA512 `integrity` outputs in those variables, then install in a fresh folder:
148
+
149
+ ```bash
150
+ mkdir "$release_tmp/consumer"
151
+ tar --ignore-zeros -xzf "$tarball" -C "$release_tmp/consumer"
152
+ (cd "$release_tmp/consumer/package" && npm ci --omit=dev --ignore-scripts)
153
+ node scripts/release-graph.mjs --source-root "$source_root" --inventory "$inventory" --inventory-sha256 "$inventory_sha256" --product-root "$release_tmp/consumer/package" --install-root "$release_tmp/consumer/package" --output "$release_tmp/source-graph.json" --source-only
154
+ npm audit --prefix "$release_tmp/consumer/package" --omit=dev --audit-level=moderate
155
+ node scripts/release-artifact.mjs check "$release_tmp/source-pack.json.verified.json" --source-root "$source_root" --inventory "$inventory" --inventory-sha256 "$inventory_sha256" --expected-integrity "$integrity" --expected-tarball "$tarball" --distribution source --source-only
156
+ ```
157
+
158
+ Run the independent consumer runtime/transport cases, audits and production SBOM
159
+ checks against this extracted installation before approving it. The installed
160
+ product needs no `.git`; its Git provenance comes from the external checkout and
161
+ original inventory. Never install the source tar.gz as an npm package. Users
162
+ extract `package/`, run `npm ci --omit=dev --ignore-scripts` there, then run
163
+ `node src/index.js setup` explicitly. Contributor commands run from the full
164
+ repository, not from this installation archive.
165
+
166
+ The proof binds an explicit `source` or `npm` distribution, exact filename,
167
+ `package/` prefix, scope, commit, inventory SHA256, original integrity and
168
+ canonical artifact path. The controller supplies the expected distribution;
169
+ `inspect`/`check` default to npm and refuse a source proof. Changing a proof or
170
+ renaming an archive cannot change its distribution.
171
+
172
+ ## Keep npm qualification separate
173
+
174
+ npm remains unqualified: a real npm 10.9.8 consumer changed ten production
175
+ dependencies. Its failed candidate and reports are retained, never promoted to
176
+ source-release evidence or attached as a qualified asset. A future matching npm
177
+ graph would establish only the tested environment/date, not reproducibility of
178
+ future npm installs. To inspect a separate npm candidate with the original
179
+ 90-file source inventory:
180
+
181
+ ```bash
182
+ npm pack --ignore-scripts --json --pack-destination "$release_tmp" > "$release_tmp/npm-pack.json"
183
+ node scripts/release-artifact.mjs preflight "$release_tmp/npm-pack.json" --source-root "$source_root" --inventory "$inventory" --inventory-sha256 "$inventory_sha256" --distribution npm
184
+ ```
185
+
186
+ Inspection checks all 89 npm files against the source; the source lock remains
187
+ external authority. Install this exact npm candidate in an isolated prefix with
188
+ scripts disabled, then invoke `release-graph.mjs` with `--product-root` pointing
189
+ to `<prefix>/node_modules/ftp-deploy-mcp` and `--install-root` to the prefix.
190
+ Any graph difference must fail before smoke tests, artifact transfer or npm/MCP
191
+ publication. A source-only pass never authorizes a registry publication.
192
+
193
+ ## Artifact validation and provenance limits
194
+
195
+ The fixed allowlists exclude local configuration, credentials, tests, temporary
196
+ files and maintainer state. Complete package metadata and the source lock are
197
+ structurally validated; each shipped byte must match the original inventory.
198
+ Publication commands receive real `GITHUB_REF` and `GITHUB_SHA` from GitHub;
199
+ HEAD and the exact tag target must match that event. Never invent this context.
200
+ Use `snapshot` without `--source-only`, `inspect` instead of `preflight`, and
201
+ `check` without `--source-only` only in that real publication context.
202
+
203
+ Compressed bytes are read once with an 8 MiB cap and provide the original SHA512.
204
+ Strict built-in gzip decoding has a 32 MiB total output cap, including TAR headers
205
+ and padding. `engine.bytesWritten` must be an integer within the input length;
206
+ any unconsumed byte must be NUL. Terminal NUL padding is allowed, nonzero hidden
207
+ tails are rejected. Every inspection tar call receives the same decoded raw TAR,
208
+ without `-z`, and uses `--ignore-zeros`. Untrusted archive paths are never
209
+ extracted to disk during inspection.
210
+
211
+ | Inspection limit | Maximum |
212
+ |---|---|
213
+ | Compressed archive | 8 MiB |
214
+ | Complete decoded TAR, headers and padding included | 32 MiB |
215
+ | Each expected or extracted file | 1 MiB |
216
+ | Complete inspection | 90 seconds |
217
+ | Each tar invocation | 15 seconds, or the shorter remaining budget |
218
+
219
+ These are input/output/time limits, not an exact memory bound. The deadline is
220
+ checked before and after synchronous gzip decoding and cannot preempt it. Failed
221
+ validation emits no new proof or publication outputs. Recheck original inventory,
222
+ source bytes, integrity and path immediately before publication. The controls
223
+ assume trusted workflow code and runner, not a fully compromised runner.
224
+
225
+ Git commit identity, checkout byte inventory and archive digest are distinct.
226
+ CRLF/LF checkout conversion can produce different valid inventories on different
227
+ systems. Qualification binds the exact Windows-built asset to its recorded
228
+ checkout bytes; the nine OS/Node CI jobs qualify their own checkouts, not that
229
+ Windows archive on every OS. After the final commit, establish an inventory for
230
+ that commit and reinspect the same frozen bytes before release. Retain the
231
+ pre-commit evidence as historical evidence, not as the final commit binding.
232
+ On Windows, use native PowerShell paths, environment assignments and output
233
+ handling instead of the Bash syntax above.
234
+
235
+ ## Separate qualification from publication
236
+
237
+ The npm qualification job has `contents: read`, no OIDC permission and no
238
+ secrets. It captures the source inventory before installation/tests/packing,
239
+ then installs, tests, audits and qualifies the exact archive and consumer.
240
+ The publication job starts on a fresh runner after qualification succeeds.
241
+ Both checkouts are pinned to the event's `github.sha`; HEAD and the actual tag
242
+ target must match that commit. Build outputs cannot select the privileged code.
243
+
244
+ Only the `.tgz`, pack JSON and source inventory cross the job boundary through
245
+ the exact upload artifact ID, downloaded outside the fresh checkout. No scripts,
246
+ `node_modules`, caches, environment files or first-runner proof are transferred.
247
+ The inventory is portable: commit, release identity, relative paths, sizes and
248
+ byte hashes. Each command validates its own explicit canonical source root;
249
+ moving roots never rewrites the original inventory bytes or SHA256.
250
+
251
+ Qualification job outputs retain the original inventory SHA256 and archive
252
+ SHA512. Fresh privileged `inspect` must enforce that original expected integrity
253
+ against both pack JSON and compressed bytes before creating a local proof or
254
+ outputs. It binds the portable inventory to the fresh source and newly downloaded
255
+ canonical archive path. The final `check` uses the original inventory digest and
256
+ the privileged inspection's integrity/path outputs; npm publishes only that
257
+ checked path with `--provenance` and lifecycle scripts disabled. Registry
258
+ verification retains those same original bindings.
259
+
260
+ The privileged inspection receives `SOURCE_INVENTORY_SHA256` and
261
+ `BUILD_INTEGRITY` from the original qualification job outputs:
262
+
263
+ ```bash
264
+ node scripts/release-artifact.mjs inspect "$RUNNER_TEMP/release-input/release-pack.json" --source-root "$GITHUB_WORKSPACE" --inventory "$RUNNER_TEMP/release-input/source-inventory.json" --inventory-sha256 "$SOURCE_INVENTORY_SHA256" --expected-integrity "$BUILD_INTEGRITY"
265
+ ```
266
+
267
+ The first qualification inspection emits the original archive digest; this
268
+ fresh privileged inspection must require it, not choose a new expected value.
269
+
270
+ Privileged jobs run only approved validators using Node built-ins, the npm CLI
271
+ or the pinned MCP publisher. They run no `npm ci`/`npm install`, project tests,
272
+ consumer smoke checks or `--runtime`. `NPM_TOKEN`, when needed for bootstrap,
273
+ exists only in the final npm publish step. The MCP workflow uses the same job
274
+ separation. Its nonprivileged job first captures the inventory, then `fetch-npm`
275
+ verifies registry identity and SHA512, requires the fixed official HTTPS URL,
276
+ downloads within the 8 MiB/time bounds and inspects all 89 files. It installs that
277
+ actual download without scripts, checks the graph and runs consumer checks. The
278
+ job output retains this qualified archive's original integrity. The privileged
279
+ job receives it and requires it from the registry again before authentication:
280
+
281
+ ```bash
282
+ node scripts/release-artifact.mjs verify-npm --source-root "$GITHUB_WORKSPACE" --expected-integrity "$QUALIFIED_INTEGRITY" --distribution npm
283
+ ```
284
+
285
+ The standalone `verify-npm` form therefore enforces expected integrity too.
286
+ No dependency files or code from the first runner are transferred; only the fresh
287
+ event-pinned checkout supplies the published `server.json`.
288
+
289
+ ## GitHub release and assets
290
+
291
+ After independent qualification and review, use the final verified commit,
292
+ require its complete public CI, then create v0.3.1. Retain the exact validated
293
+ `ftp-deploy-mcp-0.3.1-source.tar.gz` bytes. Prepare `SHA256SUMS` for this archive,
294
+ the standalone bilingual HTML guide and final evidence/SBOM; upload only these
295
+ verified files. Publish EN/FR notes that accurately describe the source scope,
296
+ then download again and verify hashes. Record commit, tag, actual time, names
297
+ and digests.
298
+
299
+ Attach no drifting npm `.tgz`. GitHub's automatic ZIP/tar remains the separate
300
+ complete repository. Asset uploads remain pending until verified; npm and MCP
301
+ remain unqualified/pending. A source release does not make npm, `npx` or the
302
+ Official MCP Registry available.
303
+
304
+ ## Publish npm
305
+
306
+ 1. Review the prepared release workflow and confirm its trigger matches the
307
+ intended tag policy.
308
+ 2. Create an annotated tag whose name exactly matches the version:
309
+ `git tag -a v0.3.1 -m "v0.3.1"`. If the GitHub source release already created
310
+ this tag, verify and reuse its unchanged target; do not recreate it.
311
+ 3. After maintainer approval and credential readiness, push the release commit
312
+ and tag. Ensure the workflow is present on the default branch, then dispatch
313
+ it explicitly on the tag:
314
+
315
+ ```bash
316
+ gh workflow run release.yml --ref v0.3.1
317
+ ```
318
+ 4. Require tests, version-consistency checks, installed graph equality, tarball inspection, and provenance
319
+ generation to pass before the publish step.
320
+ 5. Wait for that workflow run to finish successfully. Its last step compares
321
+ the public npm name, exact version, `mcpName`, and SHA512 integrity to the
322
+ validated archive. Only 404 propagation responses are retried (six attempts,
323
+ five-second delay, 15-second request timeout); authentication, HTTP errors,
324
+ invalid metadata, and mismatched integrity fail closed. A failed check after
325
+ publish does **not** prove npm publication failed: inspect the exact version
326
+ before doing anything else, and do not rerun npm publication blindly.
327
+ 6. For the first-publish bootstrap, configure Trusted Publishing and revoke and
328
+ remove the token immediately after success. For later releases, leave
329
+ `NPM_TOKEN` absent so npm authenticates with GitHub OIDC. Verify the trust
330
+ configuration before the next release; do not republish the same version to
331
+ test it.
332
+ 7. Verify the public artifact independently:
333
+
334
+ ```bash
335
+ npm view ftp-deploy-mcp@0.3.1 name version mcpName dist.integrity
336
+ npm view ftp-deploy-mcp@0.3.1 dist.tarball
337
+ npx -y ftp-deploy-mcp@0.3.1 --version
338
+ ```
339
+
340
+ Compare the registry integrity/version with the validated tarball and tag.
341
+ Only after these checks pass should the README call `npx` a currently available
342
+ installation method.
343
+
344
+ Create the GitHub release from the same immutable tag and copy the relevant
345
+ changelog entry. Do not move or reuse a published tag.
346
+
347
+ ## Publish to the MCP registry
348
+
349
+ This step remains pending. Publish only after separate npm qualification, including the installed graph. The MCP workflow downloads and qualifies the actual published npm archive again; identity alone is insufficient:
350
+
351
+ 1. Review `server.json` against schema `2025-12-11`; it must agree with the tag,
352
+ source lockfile root/root-package versions, runtime version, npm identifier,
353
+ and `mcpName`.
354
+ 2. Dispatch the separate workflow on exactly the same tag:
355
+
356
+ ```bash
357
+ gh workflow run publish-mcp.yml --ref v0.3.1
358
+ ```
359
+
360
+ 3. The nonprivileged job also qualifies the actual npm download and installed graph. Its
361
+ fresh privileged successor verifies structural metadata, the exact npm
362
+ version, `mcpName` and the qualified integrity **before** MCP authentication, without dependency
363
+ installation, tests or `--runtime`.
364
+ 4. It verifies the pinned official publisher archive, then runs
365
+ `mcp-publisher login github-oidc` and `mcp-publisher publish`. GitHub OIDC
366
+ proves the `io.github.alebgl77/` namespace; no dedicated MCP secret is used.
367
+ The publisher validates the manifest during publication. The workflow logs
368
+ out afterwards. See the [official GitHub Actions guide](https://modelcontextprotocol.io/registry/github-actions)
369
+ and [registry quickstart](https://modelcontextprotocol.io/registry/quickstart).
370
+ 5. From a clean environment, find the official registry entry, install it by
371
+ its documented route, start the server, and confirm `--version` plus an MCP
372
+ `ftp_list_servers` call.
373
+ 6. Check that Glama and MCP Index discovery listings point to the canonical
374
+ repository and released version, but do not treat those third-party pages as
375
+ registry verification.
376
+
377
+ The workflow pins `mcp-publisher` **v1.8.1**, Linux amd64, SHA256:
378
+
379
+ ```text
380
+ a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
381
+ ```
382
+
383
+ On 2026-09-03, the downloaded archive matched the digest returned by the
384
+ [official GitHub release API](https://api.github.com/repos/modelcontextprotocol/registry/releases/tags/v1.8.1)
385
+ for the [v1.8.1 release](https://github.com/modelcontextprotocol/registry/releases/tag/v1.8.1).
386
+ The workflow verifies this SHA256 before extracting or executing the binary;
387
+ it never resolves a mutable `latest` URL. This is a pinned digest verification,
388
+ not a claim that a Sigstore signature was independently verified. Updating the
389
+ tool requires reviewing and re-verifying both the version and digest.
390
+
391
+ If npm succeeded but MCP publication failed, correct the MCP-specific issue
392
+ and rerun only `publish-mcp.yml` on the unchanged tag. Do not attempt to recreate
393
+ an existing npm version. The MCP Registry is a preview service; check its live
394
+ entry after publishing before making availability claims.
395
+
396
+ ## Rollback and incident response
397
+
398
+ Before publication, stop the workflow and fix the release commit. If an
399
+ unpublished tag was pushed, remove it only after confirming no artifact was
400
+ created, then create a corrected tag.
401
+
402
+ After npm publication, assume the version is immutable:
403
+
404
+ - stop MCP registry publication if it has not happened;
405
+ - deprecate the broken npm version with a precise warning;
406
+ - fix forward with a new patch version and a new tag;
407
+ - use npm unpublish only for a qualifying emergency and within npm policy, not
408
+ as a normal rollback;
409
+ - never overwrite, move, or reuse the published tag/version;
410
+ - withdraw or deprecate the MCP registry version if the registry supports it,
411
+ then publish the fixed patch version;
412
+ - revoke any bootstrap token, rotate exposed credentials, and preserve logs and
413
+ integrity values for incident review;
414
+ - revert README availability claims if users can no longer install a safe
415
+ version.
416
+
417
+ For a suspected security issue, pause the release and follow
418
+ [SECURITY.md](../SECURITY.md).
419
+
420
+ ## Post-release checks
421
+
422
+ For the source release, verify its archive, checksum, source graph, runtime and
423
+ external commit inventory. Perform the following registry checks only after
424
+ separate npm/MCP publication has actually succeeded.
425
+
426
+ - Confirm npm provenance is visible and refers to the expected repository,
427
+ workflow, commit, and tag.
428
+ - Confirm source, tarball, GitHub release, npm, server `--version`, and MCP
429
+ registry all report the same version.
430
+ - Test source install, exact-version `npx`, and registry installation from
431
+ clean environments.
432
+ - Update both README availability notices and add the verified `npx` path.
433
+ - Announce only installation paths that were actually tested.
434
+ - Monitor private security reports and release failures, and prepare a patch
435
+ rather than altering the published artifact.
@@ -0,0 +1,144 @@
1
+ # Limites du parcours local et de l’admission des appels
2
+
3
+ [English](./RESOURCE-BOUNDS.md)
4
+
5
+ ## Sélection d’un déploiement
6
+
7
+ Chaque serveur configuré possède deux politiques facultatives, qui doivent être
8
+ des entiers positifs représentables sans perte. Les arguments des outils ne
9
+ peuvent modifier aucune de ces politiques.
10
+
11
+ | Champ | Défaut | Maximum |
12
+ | --- | ---: | ---: |
13
+ | `maxScanEntries` | 100000 entrées visitées | 1000000 |
14
+ | `maxScanDepth` | 64 niveaux de dossiers sous la racine | 256 |
15
+
16
+ La racine compte pour une entrée visitée, à la profondeur zéro. Chaque entrée
17
+ découverte compte une fois : dossiers, fichiers exclus et liens symboliques
18
+ compris. La descente ne compte pas le dossier une seconde fois. Atteindre
19
+ exactement une limite est accepté ; l’entrée suivante refuse toute la sélection
20
+ avec `SCAN_LIMIT`. La profondeur est inclusive : les fichiers d’un dossier au
21
+ niveau configuré restent éligibles, mais un sous-dossier non élagué est refusé
22
+ avant son ouverture. Un sous-arbre élagué ne nécessite aucune descente et ses
23
+ descendants non découverts ne sont pas comptés.
24
+
25
+ `maxDeployFiles` borne séparément les fichiers sélectionnés dès leur accumulation.
26
+ Les contrôles existants d’octets par fichier et par déploiement restent actifs.
27
+ Un échec de sélection précède toute connexion ou mutation distante, en simulation
28
+ comme en déploiement réel. `SCAN_LIMIT` renvoie `effects:none`, `retryable:false`
29
+ et `next_action:fix_input`.
30
+
31
+ Le parcours lit les dossiers séquentiellement avec `opendir` asynchrone, un tampon
32
+ de 32 entrées par handle et au plus `maxScanDepth + 1` handles ouverts. Il vérifie
33
+ l’annulation et le délai autour des lectures, puis rend la main à la boucle
34
+ événementielle toutes les 256 entrées découvertes. Toutes les fermetures sont
35
+ attendues, même après échec ou annulation. Un appel au système de fichiers ou une
36
+ fermeture lente peut encore dépasser le délai réel ; une réponse anticipée de
37
+ dépassement de délai ne signifie pas que le nettoyage est terminé.
38
+
39
+ Seules les exclusions intégrées prouvées des sous-arbres `node_modules`, `.git`
40
+ et `.ftp-mcp` permettent l’élagage. Les exclusions personnalisées et les motifs
41
+ d’inclusion ne l’autorisent jamais ; leurs règles existantes restent appliquées
42
+ à chaque fichier. Cela corrige un ancien test par sentinelle qui produisait des
43
+ faux positifs : `exclude:["**/__ftp_deploy_probe__"]` ne masque plus
44
+ `docs/wanted.txt`. Certains fichiers omis à tort peuvent désormais apparaître
45
+ dans la sélection. Examinez une simulation après avoir changé les exclusions.
46
+
47
+ Les liens symboliques rencontrés sont ignorés. Les contrôles lexicaux et
48
+ canoniques de `localRoot` et la nouvelle validation des sources à l’envoi restent
49
+ actifs. Ils ne suppriment pas les courses malveillantes sur les chemins causées
50
+ par un autre programme exécuté sous le même compte système.
51
+
52
+ ## Admission des appels d’outil
53
+
54
+ Un isolate Node admet au plus 64 appels d’outil pour tous les registres partageant
55
+ le module d’admission. Il n’existe ni file d’admission supplémentaire ni reprise
56
+ automatique. Un outil inconnu conserve l’erreur de protocole `-32602` ; des
57
+ arguments invalides conservent `INVALID_ARGUMENT`. Une requête déjà annulée ne
58
+ lance aucune préparation ni aucun handler. L’admission suit la validation du
59
+ schéma et précède la préparation.
60
+
61
+ Le 65e appel renvoie `CAPACITY_LIMIT`, `effects:none`, `retryable:false` et
62
+ `next_action:retry`. Réessayez explicitement après la fin effective d’un appel
63
+ actif. Chaque appel conserve sa place pendant la préparation, l’exécution et le
64
+ nettoyage, y compris lorsqu’une entrée-sortie non coopérative se termine après
65
+ une annulation ou un dépassement de délai. Les verrous FIFO existants par cible
66
+ sont inchangés ; leurs appels en attente occupent aussi une place d’admission.
67
+ Les identifiants de corrélation, dont `0` numérique, `""`, `1` numérique et `"0"`
68
+ texte, restent distincts dans la garde du canal possédée par le point d’entrée.
69
+ Cette garde réserve chaque identifiant de requête entrant, toutes méthodes
70
+ confondues, avant sa remise au SDK et ferme la connexion sur tout doublon actif.
71
+
72
+ Ces limites ne coordonnent pas les processus, isolates ou hôtes distincts. Elles
73
+ ne bornent ni les tampons d’entrée du transport ni l’analyse des requêtes refusées.
74
+ Les limites du parcours local ne bornent pas le tampon distant sous-jacent de
75
+ `list()` ; la sortie MCP paginée conserve l’implémentation de liste existante.
76
+
77
+ ## Bornes de l'état durable et du statut
78
+
79
+ `stateLimits` borne strictement les octets d'état, octets/fichiers de sauvegarde,
80
+ plans, opérations, fichiers/octets de plan et octets/événements de journal. La
81
+ publication d'un plan prouve toute sa réservation avant écriture. Apply et
82
+ rollback consultent le budget avant chaque intention durable et événement
83
+ terminal ; les flux s'exécutent hors du court verrou d'admission mais sous le
84
+ verrou persistant de l'alias exact.
85
+
86
+ `ftp_deploy_status` est sans écriture ni verrou : il rejoue un préfixe de journal
87
+ dont l'intégrité est vérifiée, puis confirme que plan, association, journal et
88
+ tête n'ont pas changé. Une concurrence renvoie `STATUS_CHANGED` ou
89
+ `DURABLE_STATE_BUSY`, jamais une page mélangée. Une page accepte au plus 100
90
+ lignes et s'arrête avant que le résultat structuré complet dépasse 18 Kio ;
91
+ `next_offset` vaut toujours `offset + count` lorsqu'une ligne reste.
92
+
93
+ Sauvegardes et journaux sont sensibles. Gardez `stateDir` privé et hors de toute
94
+ arborescence `localRoot`. Ne copiez pas un état v1 : il est refusé avant décodage
95
+ des plans et exige une réinitialisation hors ligne.
96
+
97
+ ## Cycle de vie interne des outils préparés
98
+
99
+ Le registre expose une interface d’intégration interne ; les outils qui ne
100
+ l’activent pas utilisent toujours leur résolveur `timeoutFor` ordinaire.
101
+ L’interface elle-même n’active aucune reprise ni identifiant de requête
102
+ persistant et ne modifie ni les descripteurs ni les résultats des outils qui ne
103
+ l’utilisent pas.
104
+
105
+ Une inscription peut fournir les callbacks privés appariés `prepare(args,
106
+ operation)` et `disposePrepared(prepared, operation)`, tous deux obligatoirement
107
+ des fonctions. Le handler reçoit la valeur préparée en troisième argument. Une
108
+ préparation réussie transfère la propriété de sa ressource même si elle renvoie
109
+ `undefined` ; le registre attend exactement une libération après le handler ou
110
+ tout échec ultérieur. Une préparation qui rejette doit fermer chaque ressource
111
+ acquise avant son rejet : le registre ne peut pas libérer un contexte jamais
112
+ renvoyé. Le premier échec est conservé si la libération échoue aussi ; un échec
113
+ de libération après un handler réussi devient une erreur d’outil bornée ordinaire.
114
+
115
+ Préparation, traitement et libération s’exécutent dans le worker admis observé
116
+ par le transport. Une annulation ou un dépassement de délai peut produire une
117
+ réponse anticipée, mais le slot d’admission reste occupé jusqu’à la terminaison
118
+ réelle, préparation tardive et libération comprises. L’identifiant de requête du
119
+ canal reste réservé jusqu’au règlement ou à l’échec de l’envoi de la réponse
120
+ terminale et jusqu’à la terminaison réelle de tous les tours de handler enregistrés,
121
+ y compris tous les tours legacy d’entrée requise. Si l’annulation du client
122
+ supprime la réponse, l’identifiant reste réservé jusqu’à la fermeture de la
123
+ connexion. La libération s’exécute même après l’annulation et ne doit pas passer
124
+ par `operation.step`, dont le contrôle d’annulation empêcherait le nettoyage. Un
125
+ travail non coopératif peut donc retenir la capacité indéfiniment ; le délai
126
+ n’interrompt pas du JavaScript ou des entrées-sorties arbitraires.
127
+
128
+ L’option privée `preparedTimeoutMs` du registre vaut 120000 par défaut et accepte
129
+ un entier sûr positif jusqu’à 3600000. Les outils préparés ne consultent jamais
130
+ `timeoutFor`. Le futur assemblage fournit le plus grand délai serveur validé.
131
+ Après sélection du serveur, la préparation appelle
132
+ `operation.shortenTimeout(server.operationTimeoutMs)`, puis `operation.check()`
133
+ avant tout effet. Le raccourcissement part du début initial de l’opération, ne
134
+ prolonge jamais l’échéance courante et annule immédiatement une échéance déjà
135
+ dépassée. Une opération terminée ne peut pas créer un autre timer.
136
+
137
+ `operation.runPreparation(run)` est à usage unique, sans imbrication. Son plafond
138
+ fixe de 10000 ms commence avant l’appel du callback et reste borné par l’échéance
139
+ principale. Les contrôles avant et après le callback imposent aussi ce plafond
140
+ si la boucle événementielle n’a pas exécuté le timer. Son expiration annule
141
+ irréversiblement toute l’opération avec `TIMEOUT`. Une préparation réussie retire
142
+ seulement le plafond de l’étape et conserve l’échéance principale. Le callback
143
+ réel reste attendu après une réponse anticipée ; les timers sont supprimés à
144
+ l’annulation et à la terminaison réelle.