@ambitresearch/genie 1.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.
- package/README.md +15 -0
- package/dist/auth/bearer.d.ts +49 -0
- package/dist/auth/bearer.d.ts.map +1 -0
- package/dist/auth/bearer.js +159 -0
- package/dist/auth/bearer.js.map +1 -0
- package/dist/auth/oauth/authorize.d.ts +19 -0
- package/dist/auth/oauth/authorize.d.ts.map +1 -0
- package/dist/auth/oauth/authorize.js +111 -0
- package/dist/auth/oauth/authorize.js.map +1 -0
- package/dist/auth/oauth/config.d.ts +10 -0
- package/dist/auth/oauth/config.d.ts.map +1 -0
- package/dist/auth/oauth/config.js +16 -0
- package/dist/auth/oauth/config.js.map +1 -0
- package/dist/auth/oauth/dcr.d.ts +15 -0
- package/dist/auth/oauth/dcr.d.ts.map +1 -0
- package/dist/auth/oauth/dcr.js +38 -0
- package/dist/auth/oauth/dcr.js.map +1 -0
- package/dist/auth/oauth/jwt.d.ts +13 -0
- package/dist/auth/oauth/jwt.d.ts.map +1 -0
- package/dist/auth/oauth/jwt.js +46 -0
- package/dist/auth/oauth/jwt.js.map +1 -0
- package/dist/auth/oauth/metadata.d.ts +9 -0
- package/dist/auth/oauth/metadata.d.ts.map +1 -0
- package/dist/auth/oauth/metadata.js +19 -0
- package/dist/auth/oauth/metadata.js.map +1 -0
- package/dist/auth/oauth/router.d.ts +32 -0
- package/dist/auth/oauth/router.d.ts.map +1 -0
- package/dist/auth/oauth/router.js +159 -0
- package/dist/auth/oauth/router.js.map +1 -0
- package/dist/auth/oauth/store.d.ts +55 -0
- package/dist/auth/oauth/store.d.ts.map +1 -0
- package/dist/auth/oauth/store.js +63 -0
- package/dist/auth/oauth/store.js.map +1 -0
- package/dist/auth/oauth/token.d.ts +21 -0
- package/dist/auth/oauth/token.d.ts.map +1 -0
- package/dist/auth/oauth/token.js +77 -0
- package/dist/auth/oauth/token.js.map +1 -0
- package/dist/auth/oidc/group-policy.d.ts +43 -0
- package/dist/auth/oidc/group-policy.d.ts.map +1 -0
- package/dist/auth/oidc/group-policy.js +54 -0
- package/dist/auth/oidc/group-policy.js.map +1 -0
- package/dist/auth/oidc/verifier.d.ts +42 -0
- package/dist/auth/oidc/verifier.d.ts.map +1 -0
- package/dist/auth/oidc/verifier.js +145 -0
- package/dist/auth/oidc/verifier.js.map +1 -0
- package/dist/auth/token-cli.d.ts +9 -0
- package/dist/auth/token-cli.d.ts.map +1 -0
- package/dist/auth/token-cli.js +98 -0
- package/dist/auth/token-cli.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +149 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/redact.d.ts +20 -0
- package/dist/config/redact.d.ts.map +1 -0
- package/dist/config/redact.js +77 -0
- package/dist/config/redact.js.map +1 -0
- package/dist/config/secrets.d.ts +100 -0
- package/dist/config/secrets.d.ts.map +1 -0
- package/dist/config/secrets.js +130 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/framework/html.d.ts +103 -0
- package/dist/framework/html.d.ts.map +1 -0
- package/dist/framework/html.js +271 -0
- package/dist/framework/html.js.map +1 -0
- package/dist/framework/interface.d.ts +130 -0
- package/dist/framework/interface.d.ts.map +1 -0
- package/dist/framework/interface.js +112 -0
- package/dist/framework/interface.js.map +1 -0
- package/dist/framework/react.d.ts +133 -0
- package/dist/framework/react.d.ts.map +1 -0
- package/dist/framework/react.js +243 -0
- package/dist/framework/react.js.map +1 -0
- package/dist/framework/vue.d.ts +99 -0
- package/dist/framework/vue.d.ts.map +1 -0
- package/dist/framework/vue.js +375 -0
- package/dist/framework/vue.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +81 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +141 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/component-response.d.ts +111 -0
- package/dist/llm/component-response.d.ts.map +1 -0
- package/dist/llm/component-response.js +167 -0
- package/dist/llm/component-response.js.map +1 -0
- package/dist/llm/generated-files.d.ts +10 -0
- package/dist/llm/generated-files.d.ts.map +1 -0
- package/dist/llm/generated-files.js +26 -0
- package/dist/llm/generated-files.js.map +1 -0
- package/dist/llm/prompts/CHANGELOG.md +70 -0
- package/dist/llm/prompts/generate-component.system.md +99 -0
- package/dist/llm/prompts/refine-component.system.md +112 -0
- package/dist/llm/prompts.d.ts +66 -0
- package/dist/llm/prompts.d.ts.map +1 -0
- package/dist/llm/prompts.js +78 -0
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/retry.d.ts +183 -0
- package/dist/llm/retry.d.ts.map +1 -0
- package/dist/llm/retry.js +475 -0
- package/dist/llm/retry.js.map +1 -0
- package/dist/llm/schema.d.ts +129 -0
- package/dist/llm/schema.d.ts.map +1 -0
- package/dist/llm/schema.js +147 -0
- package/dist/llm/schema.js.map +1 -0
- package/dist/llm/validate.d.ts +107 -0
- package/dist/llm/validate.d.ts.map +1 -0
- package/dist/llm/validate.js +174 -0
- package/dist/llm/validate.js.map +1 -0
- package/dist/manifest/compiler.d.ts +112 -0
- package/dist/manifest/compiler.d.ts.map +1 -0
- package/dist/manifest/compiler.js +306 -0
- package/dist/manifest/compiler.js.map +1 -0
- package/dist/manifest/ensure.d.ts +46 -0
- package/dist/manifest/ensure.d.ts.map +1 -0
- package/dist/manifest/ensure.js +49 -0
- package/dist/manifest/ensure.js.map +1 -0
- package/dist/manifest/index.d.ts +13 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +13 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/middleware/plan-guard.d.ts +167 -0
- package/dist/middleware/plan-guard.d.ts.map +1 -0
- package/dist/middleware/plan-guard.js +190 -0
- package/dist/middleware/plan-guard.js.map +1 -0
- package/dist/plans/index.d.ts +71 -0
- package/dist/plans/index.d.ts.map +1 -0
- package/dist/plans/index.js +252 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/schemas/component.schema.json +114 -0
- package/dist/server.d.ts +96 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +258 -0
- package/dist/server.js.map +1 -0
- package/dist/store/empty-manifest.d.ts +58 -0
- package/dist/store/empty-manifest.d.ts.map +1 -0
- package/dist/store/empty-manifest.js +59 -0
- package/dist/store/empty-manifest.js.map +1 -0
- package/dist/store/git-host.d.ts +120 -0
- package/dist/store/git-host.d.ts.map +1 -0
- package/dist/store/git-host.js +796 -0
- package/dist/store/git-host.js.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/interface.d.ts +322 -0
- package/dist/store/interface.d.ts.map +1 -0
- package/dist/store/interface.js +100 -0
- package/dist/store/interface.js.map +1 -0
- package/dist/store/kit-files.d.ts +88 -0
- package/dist/store/kit-files.d.ts.map +1 -0
- package/dist/store/kit-files.js +199 -0
- package/dist/store/kit-files.js.map +1 -0
- package/dist/store/local.d.ts +70 -0
- package/dist/store/local.d.ts.map +1 -0
- package/dist/store/local.js +651 -0
- package/dist/store/local.js.map +1 -0
- package/dist/store/manifest.d.ts +44 -0
- package/dist/store/manifest.d.ts.map +1 -0
- package/dist/store/manifest.js +116 -0
- package/dist/store/manifest.js.map +1 -0
- package/dist/store/viewer-assets.d.ts +20 -0
- package/dist/store/viewer-assets.d.ts.map +1 -0
- package/dist/store/viewer-assets.js +118 -0
- package/dist/store/viewer-assets.js.map +1 -0
- package/dist/sync/anchor.d.ts +125 -0
- package/dist/sync/anchor.d.ts.map +1 -0
- package/dist/sync/anchor.js +210 -0
- package/dist/sync/anchor.js.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +15 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +160 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +288 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/tools/bind_kit.d.ts +29 -0
- package/dist/tools/bind_kit.d.ts.map +1 -0
- package/dist/tools/bind_kit.js +70 -0
- package/dist/tools/bind_kit.js.map +1 -0
- package/dist/tools/conjure.d.ts +141 -0
- package/dist/tools/conjure.d.ts.map +1 -0
- package/dist/tools/conjure.js +655 -0
- package/dist/tools/conjure.js.map +1 -0
- package/dist/tools/conjure_screen.d.ts +174 -0
- package/dist/tools/conjure_screen.d.ts.map +1 -0
- package/dist/tools/conjure_screen.js +458 -0
- package/dist/tools/conjure_screen.js.map +1 -0
- package/dist/tools/create_kit.d.ts +32 -0
- package/dist/tools/create_kit.d.ts.map +1 -0
- package/dist/tools/create_kit.js +131 -0
- package/dist/tools/create_kit.js.map +1 -0
- package/dist/tools/create_project.d.ts +267 -0
- package/dist/tools/create_project.d.ts.map +1 -0
- package/dist/tools/create_project.js +546 -0
- package/dist/tools/create_project.js.map +1 -0
- package/dist/tools/delete_files.d.ts +93 -0
- package/dist/tools/delete_files.d.ts.map +1 -0
- package/dist/tools/delete_files.js +276 -0
- package/dist/tools/delete_files.js.map +1 -0
- package/dist/tools/delete_project.d.ts +43 -0
- package/dist/tools/delete_project.d.ts.map +1 -0
- package/dist/tools/delete_project.js +118 -0
- package/dist/tools/delete_project.js.map +1 -0
- package/dist/tools/get_kit.d.ts +28 -0
- package/dist/tools/get_kit.d.ts.map +1 -0
- package/dist/tools/get_kit.js +95 -0
- package/dist/tools/get_kit.js.map +1 -0
- package/dist/tools/get_project.d.ts +37 -0
- package/dist/tools/get_project.d.ts.map +1 -0
- package/dist/tools/get_project.js +71 -0
- package/dist/tools/get_project.js.map +1 -0
- package/dist/tools/list_components.d.ts +42 -0
- package/dist/tools/list_components.d.ts.map +1 -0
- package/dist/tools/list_components.js +125 -0
- package/dist/tools/list_components.js.map +1 -0
- package/dist/tools/list_files.d.ts +32 -0
- package/dist/tools/list_files.d.ts.map +1 -0
- package/dist/tools/list_files.js +109 -0
- package/dist/tools/list_files.js.map +1 -0
- package/dist/tools/list_kits.d.ts +14 -0
- package/dist/tools/list_kits.d.ts.map +1 -0
- package/dist/tools/list_kits.js +41 -0
- package/dist/tools/list_kits.js.map +1 -0
- package/dist/tools/list_projects.d.ts +23 -0
- package/dist/tools/list_projects.d.ts.map +1 -0
- package/dist/tools/list_projects.js +66 -0
- package/dist/tools/list_projects.js.map +1 -0
- package/dist/tools/plan.d.ts +16 -0
- package/dist/tools/plan.d.ts.map +1 -0
- package/dist/tools/plan.js +146 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/preview.d.ts +289 -0
- package/dist/tools/preview.d.ts.map +1 -0
- package/dist/tools/preview.js +672 -0
- package/dist/tools/preview.js.map +1 -0
- package/dist/tools/read_file.d.ts +40 -0
- package/dist/tools/read_file.d.ts.map +1 -0
- package/dist/tools/read_file.js +114 -0
- package/dist/tools/read_file.js.map +1 -0
- package/dist/tools/refine.d.ts +220 -0
- package/dist/tools/refine.d.ts.map +1 -0
- package/dist/tools/refine.js +501 -0
- package/dist/tools/refine.js.map +1 -0
- package/dist/tools/validate.d.ts +74 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +224 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/write_files.d.ts +158 -0
- package/dist/tools/write_files.d.ts.map +1 -0
- package/dist/tools/write_files.js +541 -0
- package/dist/tools/write_files.js.map +1 -0
- package/dist/transport.d.ts +77 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +448 -0
- package/dist/transport.js.map +1 -0
- package/dist/ui/card-asset-broker.d.ts +42 -0
- package/dist/ui/card-asset-broker.d.ts.map +1 -0
- package/dist/ui/card-asset-broker.js +479 -0
- package/dist/ui/card-asset-broker.js.map +1 -0
- package/dist/ui/grid-resource.d.ts +252 -0
- package/dist/ui/grid-resource.d.ts.map +1 -0
- package/dist/ui/grid-resource.js +680 -0
- package/dist/ui/grid-resource.js.map +1 -0
- package/dist/ui/viewer-static/index.html +95 -0
- package/dist/ui/viewer-static/viewer.css +451 -0
- package/dist/ui/viewer-static/viewer.js +1405 -0
- package/dist/validate/full-scan.d.ts +87 -0
- package/dist/validate/full-scan.d.ts.map +1 -0
- package/dist/validate/full-scan.js +299 -0
- package/dist/validate/full-scan.js.map +1 -0
- package/dist/validate/index.d.ts +15 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +18 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/marker.d.ts +89 -0
- package/dist/validate/marker.d.ts.map +1 -0
- package/dist/validate/marker.js +87 -0
- package/dist/validate/marker.js.map +1 -0
- package/dist/validate/phash.d.ts +135 -0
- package/dist/validate/phash.d.ts.map +1 -0
- package/dist/validate/phash.js +307 -0
- package/dist/validate/phash.js.map +1 -0
- package/dist/validate/render.d.ts +38 -0
- package/dist/validate/render.d.ts.map +1 -0
- package/dist/validate/render.js +155 -0
- package/dist/validate/render.js.map +1 -0
- package/dist/watch/index.d.ts +12 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +12 -0
- package/dist/watch/index.js.map +1 -0
- package/dist/watch/watcher.d.ts +65 -0
- package/dist/watch/watcher.d.ts.map +1 -0
- package/dist/watch/watcher.js +238 -0
- package/dist/watch/watcher.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool: write_files (M1-08; re-plumbed onto KitStore in M1-14a-1b / DRO-565).
|
|
3
|
+
*
|
|
4
|
+
* Writes up to 256 files per call into a KIT (the plan's `kitId` — the same
|
|
5
|
+
* readable surface `read_file`/`list_files`/`delete_files` see). Every `path`
|
|
6
|
+
* must match at least one glob in the plan's `writes`; the plan is resolved via
|
|
7
|
+
* `planId` against the M1-07 plan registry (`../plans/index.ts`), which must be
|
|
8
|
+
* live (exists + not expired).
|
|
9
|
+
*
|
|
10
|
+
* Each file is sourced from exactly one of:
|
|
11
|
+
* - `localPath` — resolved against the plan's `localDir` (the harness-local
|
|
12
|
+
* SOURCE base); the server reads the bytes directly from disk, so file
|
|
13
|
+
* contents never enter model context (the whole point of the localPath
|
|
14
|
+
* indirection). Note the split of roles: `localDir` is where uploads are
|
|
15
|
+
* READ FROM; the kit is where they are WRITTEN TO.
|
|
16
|
+
* - `data` — inline content (utf-8 or base64, per `encoding`), for content
|
|
17
|
+
* the caller only has in-memory (e.g. LLM-generated text).
|
|
18
|
+
*
|
|
19
|
+
* ── Store re-plumb (DRO-565) ─────────────────────────────────────────────────
|
|
20
|
+
* This tool used to own an fs-native rename-to-temp/rename-back transaction and
|
|
21
|
+
* wrote straight into `plan.localDir`. That transaction now lives behind the
|
|
22
|
+
* `KitStore.writeFiles(kitId, ops)` primitive (`store/interface.ts`), so the
|
|
23
|
+
* SAME verb runs against `LocalFsKitStore` (disk, rename transaction) or
|
|
24
|
+
* `GitHostKitStore` (contents-API commit on a branch) with the AC10 atomicity +
|
|
25
|
+
* rollback contract preserved. The tool keeps ALL plan-gating and per-file
|
|
26
|
+
* validation (planId, writes-glob membership, duplicate rejection, localPath
|
|
27
|
+
* containment + streaming decision, byte cap, encoding) and hands the store a
|
|
28
|
+
* resolved `WriteOp[]` — mirroring how `delete_files` keeps `deletes`-glob
|
|
29
|
+
* gating tool-side and calls `store.deleteFile(plan.kitId, path)`.
|
|
30
|
+
*
|
|
31
|
+
* The atomicity/streaming/rollback semantics themselves are documented on the
|
|
32
|
+
* `KitStore.writeFiles` primitive and its two adapters; this file is now the
|
|
33
|
+
* validation + wire-error-shaping layer in front of them.
|
|
34
|
+
*/
|
|
35
|
+
import { stat } from "node:fs/promises";
|
|
36
|
+
import { isAbsolute, resolve } from "node:path";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
import { withPlanGuard } from "../middleware/plan-guard.js";
|
|
39
|
+
import { getPlan, isPathInsideLocalDir, pathMatchesGlobs, PlanNotFoundError, } from "../plans/index.js";
|
|
40
|
+
import { RollbackIncompleteError, WriteFailedError } from "../store/interface.js";
|
|
41
|
+
import { isValidBase64Content } from "../store/kit-files.js";
|
|
42
|
+
export const WRITE_FILES_TOOL_NAME = "mcp__genie__write_files";
|
|
43
|
+
/** M1-08 AC3 — hard ceiling on files per call. */
|
|
44
|
+
export const MAX_FILES_PER_CALL = 256;
|
|
45
|
+
/**
|
|
46
|
+
* Byte-cap for a single `write_files` call's total decoded payload.
|
|
47
|
+
*
|
|
48
|
+
* RFC §17.5 flags Anthropic's exact server-side cap as unconfirmed for their
|
|
49
|
+
* hosted endpoint; PRD §EC-029 sets genie's own default at 16 MiB with a
|
|
50
|
+
* configurable ceiling. `GENIE_WRITE_BYTE_CAP` overrides the default; values
|
|
51
|
+
* that are non-numeric or ≤ 0 fall back to it.
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_WRITE_BYTE_CAP = 16 * 1024 * 1024;
|
|
54
|
+
function resolveByteCap(env) {
|
|
55
|
+
const raw = env["GENIE_WRITE_BYTE_CAP"];
|
|
56
|
+
if (raw === undefined)
|
|
57
|
+
return DEFAULT_WRITE_BYTE_CAP;
|
|
58
|
+
const n = Number(raw);
|
|
59
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_WRITE_BYTE_CAP;
|
|
60
|
+
}
|
|
61
|
+
// ─── write_files' own error taxonomy ─────────────────────────────────────────
|
|
62
|
+
//
|
|
63
|
+
// Repo convention (see ListFilesError in tools/list_files.ts, ProjectStoreError
|
|
64
|
+
// in tools/create_project.ts): each tool owns its error classes colocated in
|
|
65
|
+
// its own file rather than a shared cross-tool errors module. The one
|
|
66
|
+
// plan-layer error this tool can also see — PlanNotFoundError — is imported
|
|
67
|
+
// from `../plans/index.ts` instead of redefined here.
|
|
68
|
+
/** M1-08 AC3 — more than 256 files were supplied to `write_files` in one call. */
|
|
69
|
+
export class TooManyFilesError extends Error {
|
|
70
|
+
count;
|
|
71
|
+
max;
|
|
72
|
+
code = "TooManyFilesError";
|
|
73
|
+
constructor(count, max) {
|
|
74
|
+
super(`write_files accepts at most ${max} files per call; received ${count}.`);
|
|
75
|
+
this.count = count;
|
|
76
|
+
this.max = max;
|
|
77
|
+
this.name = "TooManyFilesError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Two or more files in the same call target the same `path` (Copilot review
|
|
82
|
+
* finding on PR #106). Without this check, `resolvedLocalPaths` — keyed by
|
|
83
|
+
* `file.path` — would silently drop all but the last entry's `localPath`
|
|
84
|
+
* (so an earlier duplicate would stage the WRONG file's bytes), and
|
|
85
|
+
* `writtenPaths` would list the same path twice as if two distinct files had
|
|
86
|
+
* been written, when in fact whichever entry committed last (an unspecified,
|
|
87
|
+
* input-order-dependent race between the two renames) is the only one that
|
|
88
|
+
* survives. Rejected up front, before any per-file validation, so partial
|
|
89
|
+
* ambiguity is never staged.
|
|
90
|
+
*/
|
|
91
|
+
export class DuplicatePathError extends Error {
|
|
92
|
+
path;
|
|
93
|
+
code = "DuplicatePathError";
|
|
94
|
+
constructor(path) {
|
|
95
|
+
super(`Path "${path}" appears more than once in this write_files call; every path must be unique.`);
|
|
96
|
+
this.path = path;
|
|
97
|
+
this.name = "DuplicatePathError";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* M1-08 AC4 — a file's `path` is rejected by the plan boundary: either it
|
|
102
|
+
* doesn't match any glob in the plan's `writes` (`reason: "glob"`), or it
|
|
103
|
+
* resolves outside the plan's `localDir` even though a glob DID match
|
|
104
|
+
* (`reason: "escapesLocalDir"` — e.g. an absolute path under a permissive
|
|
105
|
+
* `**`; see the containment check in `writeFiles`). Both failure modes share
|
|
106
|
+
* one error class/code (`PathOutsidePlanError`) so callers can branch on a
|
|
107
|
+
* single `code`, but the message and `reason` field distinguish them —
|
|
108
|
+
* a Copilot review finding on PR #106 flagged the message as misleadingly
|
|
109
|
+
* glob-specific even when the actual cause was the containment check.
|
|
110
|
+
*/
|
|
111
|
+
export class PathOutsidePlanError extends Error {
|
|
112
|
+
path;
|
|
113
|
+
reason;
|
|
114
|
+
code = "PathOutsidePlanError";
|
|
115
|
+
constructor(path, reason = "glob") {
|
|
116
|
+
super(reason === "escapesLocalDir"
|
|
117
|
+
? `Path "${path}" resolves outside the plan's localDir, even though it may match a writes pattern.`
|
|
118
|
+
: `Path "${path}" does not match any pattern in the plan's writes.`);
|
|
119
|
+
this.path = path;
|
|
120
|
+
this.reason = reason;
|
|
121
|
+
this.name = "PathOutsidePlanError";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** M1-08 AC6 — a file's `localPath` resolves outside the plan's `localDir`. */
|
|
125
|
+
export class LocalPathEscapeError extends Error {
|
|
126
|
+
localPath;
|
|
127
|
+
localDir;
|
|
128
|
+
code = "LocalPathEscapeError";
|
|
129
|
+
constructor(localPath, localDir) {
|
|
130
|
+
super(`localPath "${localPath}" resolves outside the plan's localDir "${localDir}".`);
|
|
131
|
+
this.localPath = localPath;
|
|
132
|
+
this.localDir = localDir;
|
|
133
|
+
this.name = "LocalPathEscapeError";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** M1-08 AC7 — a file set neither `localPath` nor `data`, or set both. */
|
|
137
|
+
export class InvalidFileInputError extends Error {
|
|
138
|
+
path;
|
|
139
|
+
code = "InvalidFileInputError";
|
|
140
|
+
constructor(path, reason) {
|
|
141
|
+
super(reason === "both"
|
|
142
|
+
? `File "${path}" set both localPath and data; exactly one is required.`
|
|
143
|
+
: `File "${path}" set neither localPath nor data; exactly one is required.`);
|
|
144
|
+
this.path = path;
|
|
145
|
+
this.name = "InvalidFileInputError";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** M1-08 AC7 — `data` was not valid base64 when `encoding: "base64"` was declared. */
|
|
149
|
+
export class InvalidEncodingError extends Error {
|
|
150
|
+
path;
|
|
151
|
+
code = "InvalidEncodingError";
|
|
152
|
+
constructor(path) {
|
|
153
|
+
super(`File "${path}" declared encoding "base64" but data is not valid base64.`);
|
|
154
|
+
this.path = path;
|
|
155
|
+
this.name = "InvalidEncodingError";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** M1-08 AC9 — the call's total decoded byte size exceeds the configured cap. */
|
|
159
|
+
export class PayloadTooLargeError extends Error {
|
|
160
|
+
totalBytes;
|
|
161
|
+
maxBytes;
|
|
162
|
+
retryMaxFiles;
|
|
163
|
+
code = "PayloadTooLargeError";
|
|
164
|
+
constructor(totalBytes, maxBytes, retryMaxFiles) {
|
|
165
|
+
super(`write_files payload is ${totalBytes} bytes, exceeding the ${maxBytes} byte cap. ` +
|
|
166
|
+
"Halve the file count and retry.");
|
|
167
|
+
this.totalBytes = totalBytes;
|
|
168
|
+
this.maxBytes = maxBytes;
|
|
169
|
+
this.retryMaxFiles = retryMaxFiles;
|
|
170
|
+
this.name = "PayloadTooLargeError";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* AC10 write-failure taxonomy now lives in the store layer (`store/interface.ts`),
|
|
175
|
+
* because the store owns the atomic write transaction after the DRO-565 re-plumb.
|
|
176
|
+
* Re-exported here so existing importers of these names from `write_files.js`
|
|
177
|
+
* (tests, in-process callers) keep resolving them.
|
|
178
|
+
*/
|
|
179
|
+
export { RollbackIncompleteError, WriteFailedError } from "../store/interface.js";
|
|
180
|
+
// ─── Input/output shapes ─────────────────────────────────────────────────────
|
|
181
|
+
const fileInputSchema = z
|
|
182
|
+
.object({
|
|
183
|
+
path: z
|
|
184
|
+
.string()
|
|
185
|
+
.min(1)
|
|
186
|
+
.describe("Project-relative path to write, matched against the plan's writes globs"),
|
|
187
|
+
localPath: z
|
|
188
|
+
.string()
|
|
189
|
+
.min(1)
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Path on disk to read content from, resolved against the plan's localDir"),
|
|
192
|
+
data: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Inline content — utf-8 text, or base64 when encoding is base64"),
|
|
196
|
+
encoding: z
|
|
197
|
+
.enum(["utf-8", "base64"])
|
|
198
|
+
.optional()
|
|
199
|
+
.describe('Encoding of `data`. Defaults to "utf-8".'),
|
|
200
|
+
mimeType: z.string().optional().describe("Optional MIME type hint (not currently persisted)"),
|
|
201
|
+
})
|
|
202
|
+
.describe("A single file to write: exactly one of localPath or data must be set");
|
|
203
|
+
const writeFilesInputSchema = {
|
|
204
|
+
planId: z.string().min(1).describe("planId returned by a prior plan call"),
|
|
205
|
+
files: z.array(fileInputSchema).describe("Files to write (max 256 per call)"),
|
|
206
|
+
};
|
|
207
|
+
const writeFilesOutputSchema = {
|
|
208
|
+
writtenPaths: z.array(z.string()).describe("Paths written, in the same order as the input"),
|
|
209
|
+
};
|
|
210
|
+
/** Structured tool-error payload shape shared by every branch below. */
|
|
211
|
+
function toolError(payload) {
|
|
212
|
+
return {
|
|
213
|
+
isError: true,
|
|
214
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Register the `mcp__genie__write_files` tool.
|
|
219
|
+
*
|
|
220
|
+
* @param server The MCP server to register against. `planId`/`writes` are
|
|
221
|
+
* validated against the shared M1-07 plan registry
|
|
222
|
+
* (`../plans/index.ts`) via the M1-13 plan-guard middleware
|
|
223
|
+
* (`../middleware/plan-guard.ts`) — the guard owns AC5's planId
|
|
224
|
+
* presence/existence/expiry checks and AC4's path-vs-`writes`-
|
|
225
|
+
* glob check; this file's own remaining validation covers
|
|
226
|
+
* per-file structural rules (duplicates, localPath containment,
|
|
227
|
+
* encoding, byte cap) that the guard is intentionally
|
|
228
|
+
* tool-agnostic about.
|
|
229
|
+
* @param kitStore The injected kit backend (M1-14a-1b / DRO-565). The physical
|
|
230
|
+
* write is `kitStore.writeFiles(plan.kitId, ops)` — the kit is
|
|
231
|
+
* the destination (same surface read_file/list_files see), and
|
|
232
|
+
* the atomic rename-transaction (LocalFs) / contents-API commit
|
|
233
|
+
* (GitHost) lives behind that primitive. Mirrors how
|
|
234
|
+
* `registerDeleteFilesTool(server, kitStore)` routes deletes.
|
|
235
|
+
*/
|
|
236
|
+
export function registerWriteFilesTool(server, kitStore) {
|
|
237
|
+
server.registerTool(WRITE_FILES_TOOL_NAME, {
|
|
238
|
+
title: "Write files",
|
|
239
|
+
description: "Write up to 256 files per call into the plan's kit. Every path must match a " +
|
|
240
|
+
"glob in the plan's writes. Reads content from localPath (resolved against the " +
|
|
241
|
+
"plan's localDir, the local SOURCE base) so file contents never enter model " +
|
|
242
|
+
"context, or from inline data for in-memory content. Atomic per call — if any " +
|
|
243
|
+
"file fails, nothing is written. Requires a planId from mcp__genie__plan covering " +
|
|
244
|
+
"every path being written; call preview afterwards to show the result.",
|
|
245
|
+
inputSchema: writeFilesInputSchema,
|
|
246
|
+
outputSchema: writeFilesOutputSchema,
|
|
247
|
+
},
|
|
248
|
+
// M1-13 plan-vs-write guard (DRO-239). The middleware validates planId
|
|
249
|
+
// presence/existence/expiry and every file path against the plan's writes
|
|
250
|
+
// globs BEFORE the handler runs, so the four verbs share one identical
|
|
251
|
+
// plan-boundary check. Rejections surface as the canonical JSON-RPC
|
|
252
|
+
// `-32602` shape and are audit-logged as `plan.guard.reject`. The guard
|
|
253
|
+
// hands the resolved plan through in `ctx.plan`, so the handler reaches
|
|
254
|
+
// `plan.kitId` (write destination) and `plan.localDir` (localPath source
|
|
255
|
+
// base) without a second getPlan hit.
|
|
256
|
+
//
|
|
257
|
+
// The core `writeFiles(store, plan, args)` still performs the same plan
|
|
258
|
+
// check as defense-in-depth (a direct in-process caller — see the
|
|
259
|
+
// "writeFiles (core logic)" test suite — bypasses the MCP layer and this
|
|
260
|
+
// middleware with it), so removing the checks from the core would silently
|
|
261
|
+
// open a gap for those callers.
|
|
262
|
+
withPlanGuard({ mode: "writes" }, async (args) => {
|
|
263
|
+
try {
|
|
264
|
+
const result = await writeFiles(kitStore, args);
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
267
|
+
structuredContent: result,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
if (error instanceof TooManyFilesError) {
|
|
272
|
+
// AC3 — plain structured error; no retry-hint contract specified
|
|
273
|
+
// for the file-count ceiling (that's AC9's byte-cap path, below).
|
|
274
|
+
return toolError({
|
|
275
|
+
code: "TooManyFilesError",
|
|
276
|
+
message: error.message,
|
|
277
|
+
count: error.count,
|
|
278
|
+
max: error.max,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (error instanceof PayloadTooLargeError) {
|
|
282
|
+
// AC9 — literal contract: "return HTTP-equivalent error code -32099
|
|
283
|
+
// with { retryWith: { maxFiles: <half> } } in data". That's the
|
|
284
|
+
// shape of a JSON-RPC error object ({ code, message, data }), which
|
|
285
|
+
// this mirrors exactly. Note this issue's AC9 and RFC §6.2's error
|
|
286
|
+
// taxonomy disagree on the number — the RFC assigns byte-cap
|
|
287
|
+
// overflow to genie.byteCapExceeded (-32031) and reserves -32099
|
|
288
|
+
// for genie.internal (unanticipated errors). This implements AC9's
|
|
289
|
+
// literal number as written (the graded contract for this issue)
|
|
290
|
+
// and flags the discrepancy in the PR description for the
|
|
291
|
+
// maintainer to reconcile.
|
|
292
|
+
return toolError({
|
|
293
|
+
code: -32099,
|
|
294
|
+
message: error.message,
|
|
295
|
+
data: {
|
|
296
|
+
retryWith: { maxFiles: error.retryMaxFiles },
|
|
297
|
+
totalBytes: error.totalBytes,
|
|
298
|
+
maxBytes: error.maxBytes,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (error instanceof PlanNotFoundError) {
|
|
303
|
+
// Ordinarily unreachable at the wire level — the M1-13 plan-guard
|
|
304
|
+
// middleware (above) catches PlanNotFoundError first and returns
|
|
305
|
+
// the canonical -32602 shape. Kept as a belt-and-suspenders branch
|
|
306
|
+
// in case `writeFiles` throws it from a code path the middleware
|
|
307
|
+
// didn't traverse (e.g. a future refactor that bypasses the guard,
|
|
308
|
+
// or a race where a plan expires between the guard's getPlan and
|
|
309
|
+
// the core's getPlan). Behaviour matches shipped M1-07: same
|
|
310
|
+
// PlanNotFoundError for "never existed" and "expired".
|
|
311
|
+
return toolError({
|
|
312
|
+
code: "PlanNotFoundError",
|
|
313
|
+
message: error.message,
|
|
314
|
+
planId: error.planId,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (error instanceof RollbackIncompleteError) {
|
|
318
|
+
// AC10's rollback guarantee could not be fully honored — this is
|
|
319
|
+
// more severe than a plain WriteFailedError (which promises a
|
|
320
|
+
// clean, fully-restored tree), so it gets its own code rather than
|
|
321
|
+
// being silently folded into that message.
|
|
322
|
+
return toolError({
|
|
323
|
+
code: "RollbackIncompleteError",
|
|
324
|
+
message: error.message,
|
|
325
|
+
commitError: error.commitError,
|
|
326
|
+
rollbackFailures: error.rollbackFailures,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (error instanceof PathOutsidePlanError ||
|
|
330
|
+
error instanceof LocalPathEscapeError ||
|
|
331
|
+
error instanceof InvalidFileInputError ||
|
|
332
|
+
error instanceof InvalidEncodingError ||
|
|
333
|
+
error instanceof WriteFailedError ||
|
|
334
|
+
error instanceof DuplicatePathError) {
|
|
335
|
+
return toolError({ code: error.code, message: error.message, ...errorFields(error) });
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
/** Extra structured fields per own-error-type, beyond `code`/`message` (for client consumption). */
|
|
342
|
+
function errorFields(error) {
|
|
343
|
+
if (error instanceof PathOutsidePlanError)
|
|
344
|
+
return { path: error.path, reason: error.reason };
|
|
345
|
+
if (error instanceof LocalPathEscapeError)
|
|
346
|
+
return { localPath: error.localPath, localDir: error.localDir };
|
|
347
|
+
if (error instanceof InvalidFileInputError)
|
|
348
|
+
return { path: error.path };
|
|
349
|
+
if (error instanceof InvalidEncodingError)
|
|
350
|
+
return { path: error.path };
|
|
351
|
+
if (error instanceof DuplicatePathError)
|
|
352
|
+
return { path: error.path };
|
|
353
|
+
return { path: error.path, cause: error.cause }; // WriteFailedError
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Validate + execute a `write_files` call against a resolved plan, committing
|
|
357
|
+
* the batch into the plan's KIT via the injected store.
|
|
358
|
+
*
|
|
359
|
+
* Order of validation (fails fast, before any write) — keep this list in
|
|
360
|
+
* lockstep with the code below; downstream steps assume every prior step has
|
|
361
|
+
* passed (e.g. the `resolvedLocalPaths` map keyed by `file.path` assumes step 3
|
|
362
|
+
* rejected duplicates):
|
|
363
|
+
* 1. AC5 — planId exists + not expired (`getPlan`, from the M1-07
|
|
364
|
+
* plan registry — throws `PlanNotFoundError` for either
|
|
365
|
+
* failure mode).
|
|
366
|
+
* 2. AC3 — `files.length <= 256`.
|
|
367
|
+
* 3. Structural — no two `files[]` entries share the same destination
|
|
368
|
+
* `path` (`DuplicatePathError`; Copilot review finding
|
|
369
|
+
* on PR #106).
|
|
370
|
+
* 4. AC4 — every `path` matches a plan `writes` glob.
|
|
371
|
+
* 5. Security — every destination `path` is kit-relative and contained
|
|
372
|
+
* (no absolute path, no `..`/`.` segment). Destinations
|
|
373
|
+
* resolve against the KIT now (not `localDir`), so this is
|
|
374
|
+
* a lexical containment check (`PathOutsidePlanError` with
|
|
375
|
+
* `reason: "escapesLocalDir"`; RFC §10 T-13). The store
|
|
376
|
+
* re-checks via `safePath` as defense-in-depth.
|
|
377
|
+
* 6. AC7 — every file sets exactly one of `localPath` / `data`.
|
|
378
|
+
* 7. AC6 — every `localPath` resolves inside the plan's `localDir`
|
|
379
|
+
* (the SOURCE base — where uploads are read from).
|
|
380
|
+
* 8. AC9 — total decoded payload size <= the configured byte cap.
|
|
381
|
+
* Only once every file in the batch passes is the batch handed to
|
|
382
|
+
* `store.writeFiles(plan.kitId, ops)` for the atomic commit (AC10).
|
|
383
|
+
*
|
|
384
|
+
* @param store The injected kit backend. The destination is `plan.kitId`; the
|
|
385
|
+
* physical atomic write (rename transaction on LocalFs / contents-API commit
|
|
386
|
+
* on GitHost) lives behind `store.writeFiles`, so this function is now purely
|
|
387
|
+
* validation + WriteOp assembly.
|
|
388
|
+
*/
|
|
389
|
+
export async function writeFiles(store, args, env = process.env) {
|
|
390
|
+
// AC5 — planId must exist and be unexpired. Propagates PlanNotFoundError as-is.
|
|
391
|
+
const plan = await getPlan(args.planId);
|
|
392
|
+
// AC3 — file-count ceiling, before any per-file validation.
|
|
393
|
+
if (args.files.length > MAX_FILES_PER_CALL) {
|
|
394
|
+
throw new TooManyFilesError(args.files.length, MAX_FILES_PER_CALL);
|
|
395
|
+
}
|
|
396
|
+
// Structural check (Copilot review finding on PR #106): reject duplicate
|
|
397
|
+
// destination `path`s before any per-file validation depends on paths
|
|
398
|
+
// being unique. `resolvedLocalPaths` below is keyed by `file.path` — a
|
|
399
|
+
// second entry with the same path would silently overwrite the first's
|
|
400
|
+
// resolved `localPath` source, and `writtenPaths` would otherwise list the
|
|
401
|
+
// same path twice as if two distinct files had committed.
|
|
402
|
+
const seenPaths = new Set();
|
|
403
|
+
for (const file of args.files) {
|
|
404
|
+
if (seenPaths.has(file.path)) {
|
|
405
|
+
throw new DuplicatePathError(file.path);
|
|
406
|
+
}
|
|
407
|
+
seenPaths.add(file.path);
|
|
408
|
+
}
|
|
409
|
+
// AC4 — every path must match the plan's writes globs.
|
|
410
|
+
for (const file of args.files) {
|
|
411
|
+
if (!pathMatchesGlobs(file.path, plan.writes)) {
|
|
412
|
+
throw new PathOutsidePlanError(file.path);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Security (Copilot review finding on PR #106; RFC §10 T-13 — "Path
|
|
416
|
+
// traversal in write_files overwrites /etc/passwd"): a glob match alone does
|
|
417
|
+
// NOT guarantee the destination stays inside the kit — an absolute path like
|
|
418
|
+
// "/etc/passwd" matches a permissive glob such as `**` under micromatch, and
|
|
419
|
+
// resolving it against a kit root would escape. Post-re-plumb the destination
|
|
420
|
+
// is the KIT (not `localDir`), and the store's `safePath` will reject an
|
|
421
|
+
// escape too — but we reject up front so the whole call fails before any
|
|
422
|
+
// WriteOp is built, keeping the all-or-nothing guarantee at the tool
|
|
423
|
+
// boundary. The check is now purely lexical (kit-relative, no `.`/`..`
|
|
424
|
+
// segment, not absolute) since the tool no longer holds a concrete kit root.
|
|
425
|
+
for (const file of args.files) {
|
|
426
|
+
if (!isKitRelativeContained(file.path)) {
|
|
427
|
+
throw new PathOutsidePlanError(file.path, "escapesLocalDir");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// AC7 — exactly one of localPath/data per file.
|
|
431
|
+
for (const file of args.files) {
|
|
432
|
+
const hasLocalPath = file.localPath !== undefined;
|
|
433
|
+
const hasData = file.data !== undefined;
|
|
434
|
+
if (hasLocalPath && hasData) {
|
|
435
|
+
throw new InvalidFileInputError(file.path, "both");
|
|
436
|
+
}
|
|
437
|
+
if (!hasLocalPath && !hasData) {
|
|
438
|
+
throw new InvalidFileInputError(file.path, "missing");
|
|
439
|
+
}
|
|
440
|
+
if (hasData && file.encoding === "base64" && !isValidBase64Content(file.data)) {
|
|
441
|
+
throw new InvalidEncodingError(file.path);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// AC6 — every localPath must resolve inside the plan's localDir (the SOURCE
|
|
445
|
+
// base). Resolve once up front (rather than re-resolving during the WriteOp
|
|
446
|
+
// build) so this check and the actual read always agree on the same absolute
|
|
447
|
+
// path. isPathInsideLocalDir(path, localDir) does its own resolution of
|
|
448
|
+
// `path` against `localDir` (correctly anchoring a relative localPath there,
|
|
449
|
+
// rather than against process.cwd()) — see plans/index.ts.
|
|
450
|
+
const resolvedLocalPaths = new Map();
|
|
451
|
+
for (const file of args.files) {
|
|
452
|
+
if (file.localPath === undefined)
|
|
453
|
+
continue;
|
|
454
|
+
if (!isPathInsideLocalDir(file.localPath, plan.localDir)) {
|
|
455
|
+
throw new LocalPathEscapeError(file.localPath, plan.localDir);
|
|
456
|
+
}
|
|
457
|
+
resolvedLocalPaths.set(file.path, resolve(plan.localDir, file.localPath));
|
|
458
|
+
}
|
|
459
|
+
// AC9 — total decoded byte-size cap, checked before any write lands.
|
|
460
|
+
const maxBytes = resolveByteCap(env);
|
|
461
|
+
let totalBytes = 0;
|
|
462
|
+
for (const file of args.files) {
|
|
463
|
+
if (file.data !== undefined) {
|
|
464
|
+
totalBytes += byteLengthOf(file.data, file.encoding ?? "utf-8");
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const localPath = resolvedLocalPaths.get(file.path);
|
|
468
|
+
if (localPath !== undefined) {
|
|
469
|
+
totalBytes += await sizeOf(localPath, file.path);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (totalBytes > maxBytes) {
|
|
474
|
+
throw new PayloadTooLargeError(totalBytes, maxBytes, Math.max(1, Math.floor(args.files.length / 2)));
|
|
475
|
+
}
|
|
476
|
+
// Build the resolved WriteOp[] — inline `data` becomes `{ path, content }`,
|
|
477
|
+
// a `localPath` becomes `{ path, sourcePath }` (the store STREAMS from it, so
|
|
478
|
+
// a large upload never fully buffers here). Then hand the batch to the store
|
|
479
|
+
// for the atomic commit into the kit (AC10).
|
|
480
|
+
const ops = args.files.map((file) => {
|
|
481
|
+
if (file.localPath !== undefined) {
|
|
482
|
+
const sourcePath = resolvedLocalPaths.get(file.path);
|
|
483
|
+
if (sourcePath === undefined) {
|
|
484
|
+
// Unreachable: every localPath file populated the map above.
|
|
485
|
+
throw new WriteFailedError(file.path, "internal: unresolved localPath");
|
|
486
|
+
}
|
|
487
|
+
return { path: file.path, sourcePath };
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
path: file.path,
|
|
491
|
+
content: decodeData(file.data, file.encoding ?? "utf-8", file.path),
|
|
492
|
+
};
|
|
493
|
+
});
|
|
494
|
+
return store.writeFiles(plan.kitId, ops);
|
|
495
|
+
}
|
|
496
|
+
/** Decoded byte length of inline `data`, without materializing extra copies for utf-8. */
|
|
497
|
+
function byteLengthOf(data, encoding) {
|
|
498
|
+
if (encoding === "base64") {
|
|
499
|
+
// Base64 expands 3 bytes -> 4 chars; strip padding for an exact count.
|
|
500
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
501
|
+
return Math.floor((data.length * 3) / 4) - padding;
|
|
502
|
+
}
|
|
503
|
+
return Buffer.byteLength(data, "utf-8");
|
|
504
|
+
}
|
|
505
|
+
async function sizeOf(localPath, publicPath) {
|
|
506
|
+
try {
|
|
507
|
+
const info = await stat(localPath);
|
|
508
|
+
return info.size;
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
throw new WriteFailedError(publicPath, describeError(error, "source file not found"));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* True if a destination `path` is a safe kit-relative path: not absolute, and
|
|
516
|
+
* with no `.`/`..` segment (on either separator). Post-re-plumb the destination
|
|
517
|
+
* is the kit (the tool holds no concrete kit root), so containment is a lexical
|
|
518
|
+
* check — a relative path with no dot-segment can never escape the kit root the
|
|
519
|
+
* store resolves it against, and an absolute path always does. The store's
|
|
520
|
+
* `safePath` re-verifies as defense-in-depth. Mirrors delete_files' own
|
|
521
|
+
* kit-relative path guard.
|
|
522
|
+
*/
|
|
523
|
+
function isKitRelativeContained(path) {
|
|
524
|
+
if (isAbsolute(path))
|
|
525
|
+
return false;
|
|
526
|
+
return !path.split(/[/\\]/).some((seg) => seg === "." || seg === "..");
|
|
527
|
+
}
|
|
528
|
+
function decodeData(data, encoding, path) {
|
|
529
|
+
try {
|
|
530
|
+
return Buffer.from(data, encoding === "base64" ? "base64" : "utf-8");
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
throw new WriteFailedError(path, describeError(error, "invalid data encoding"));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function describeError(error, fallback) {
|
|
537
|
+
if (error instanceof Error)
|
|
538
|
+
return error.message;
|
|
539
|
+
return fallback;
|
|
540
|
+
}
|
|
541
|
+
//# sourceMappingURL=write_files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write_files.js","sourceRoot":"","sources":["../../src/tools/write_files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type RequestListener } from "node:http";
|
|
2
|
+
import type { Readable, Writable } from "node:stream";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { type OidcVerifier } from "./auth/oidc/verifier.js";
|
|
5
|
+
export type TransportKind = "stdio" | "http";
|
|
6
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
7
|
+
export declare function normalizeListenHost(host: string): string;
|
|
8
|
+
export declare function formatHttpEndpoint(host: string, port: number): string;
|
|
9
|
+
/** Register session-scoped resources that must close with their MCP server. */
|
|
10
|
+
export declare function registerServerDisposer(server: McpServer, disposer: () => void | Promise<void>): void;
|
|
11
|
+
/** Return the transport selected for a started server, if startup has begun. */
|
|
12
|
+
export declare function getServerTransportKind(server: McpServer): TransportKind | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve which transport to use (RFC §5.2 transport multiplexer):
|
|
15
|
+
* 1. explicit `kind` argument (from the --transport CLI flag), else
|
|
16
|
+
* 2. MCP_TRANSPORT env var, else
|
|
17
|
+
* 3. auto-detect: a TTY on stdin means a human launched us → HTTP;
|
|
18
|
+
* otherwise a harness is piping JSON-RPC over stdio → stdio.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveTransport(kind?: string): TransportKind;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve whether preview URLs are reachable from the MCP client. HTTP is
|
|
23
|
+
* conservative by default because a loopback bind can still sit behind a
|
|
24
|
+
* reverse proxy or tunnel; same-machine HTTP deployments must opt into local
|
|
25
|
+
* URLs explicitly.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolvePreviewLocality(transportKind: TransportKind, locality?: string, env?: NodeJS.ProcessEnv): "local" | "remote";
|
|
28
|
+
export interface StreamableHttpHandlerOptions {
|
|
29
|
+
sessionIdleTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Enables static `Authorization: Bearer genie_<token>` authentication
|
|
32
|
+
* (M5-02, DRO-274). When it is the only configured credential source, every
|
|
33
|
+
* `/mcp` request must use one; when OAuth or OIDC is also configured, any
|
|
34
|
+
* one enabled source may authenticate the request. `/health` is exempt.
|
|
35
|
+
*/
|
|
36
|
+
requireBearerAuth?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Public issuer URL for OAuth (DRO-273 / M5-01), e.g. `http://127.0.0.1:3000`.
|
|
39
|
+
* When set and `OAUTH_HS256_KEY` is a valid signing key, the OAuth 2.0 +
|
|
40
|
+
* DCR endpoints (`/.well-known/oauth-authorization-server`, `/register`,
|
|
41
|
+
* `/authorize`, `/token`) are mounted on this HTTP server. OAuth is
|
|
42
|
+
* opt-in: omit `oauthIssuer`, or leave `OAUTH_HS256_KEY` unset, to run
|
|
43
|
+
* without it.
|
|
44
|
+
*/
|
|
45
|
+
oauthIssuer?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Pre-built OIDC relying-party verifier (M5-04, DRO-276). When supplied,
|
|
48
|
+
* a bearer token issued by the configured EXTERNAL OIDC provider and
|
|
49
|
+
* asserting membership in the required group can authenticate `/mcp` (see
|
|
50
|
+
* `auth/oidc/verifier.ts`). Independent of the other credential sources: a
|
|
51
|
+
* deployment can enable any combination, and acceptance by any one source
|
|
52
|
+
* authorizes the request. A token invalid for all sources gets 401; a
|
|
53
|
+
* valid OIDC token missing the required group gets 403 (AC6).
|
|
54
|
+
*/
|
|
55
|
+
oidcVerifier?: OidcVerifier;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create the stateful Streamable HTTP request handler. Each initialized client
|
|
59
|
+
* receives its own MCP server + transport, keyed by Mcp-Session-Id, so
|
|
60
|
+
* initialize-scoped capabilities never leak between clients.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createStreamableHttpRequestHandler(serverFactory: () => McpServer, options?: StreamableHttpHandlerOptions): RequestListener;
|
|
63
|
+
export interface StartOptions {
|
|
64
|
+
kind?: string;
|
|
65
|
+
port?: number;
|
|
66
|
+
host?: string;
|
|
67
|
+
/** Required for HTTP; must include every caller-added registration per session. */
|
|
68
|
+
serverFactory?: () => McpServer;
|
|
69
|
+
/** Injectable stdio streams for embedders and EOF lifecycle tests. */
|
|
70
|
+
stdioInput?: Readable;
|
|
71
|
+
stdioOutput?: Writable;
|
|
72
|
+
/** HTTP only — enable static `Authorization: Bearer` tokens (M5-02, DRO-274). */
|
|
73
|
+
requireBearerAuth?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Start the server on the resolved transport. Returns the kind actually used. */
|
|
76
|
+
export declare function startTransport(server: McpServer, opts?: StartOptions): Promise<TransportKind>;
|
|
77
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGnF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAepD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrE;AAMD,+EAA+E;AAC/E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACnC,IAAI,CAeN;AASD,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAEnF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAO7D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,aAAa,EAC5B,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,GAAG,QAAQ,CAOpB;AAmDD,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkCD;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,MAAM,SAAS,EAC9B,OAAO,GAAE,4BAAiC,GACzC,eAAe,CAsQjB;AA+CD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,SAAS,CAAC;IAChC,sEAAsE;IACtE,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,aAAa,CAAC,CAmBxB"}
|