@jimhoyd/urlcode 0.5.0 → 0.5.5

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.
@@ -0,0 +1,422 @@
1
+ # Tooling SDK and local MCP
2
+
3
+ The tooling API consolidates authoring operations without starting a runtime:
4
+
5
+ - `inspectProject(project, {origin?, target?, offset?, limit?})` loads and
6
+ semantically compiles the project and returns route metadata, revision hash and
7
+ target compatibility. Route pages default to 100 entries, maximum 1,000.
8
+ Compatibility contains global `compatible`, `requirementCount` and `issueCount`
9
+ plus a separate `issues` page. Both arrays use the same zero-based `offset`
10
+ and `limit`, independently: an issue page is indexed over all compatibility
11
+ issues, not filtered to the route page. Compatibility includes `hasMore` for
12
+ its issue page. Empty pages never imply compatibility; the verdict and counts
13
+ always cover the entire project. Full requirement arrays are omitted because
14
+ route entries already contain their capability names.
15
+ - `validateProject(project, options)` returns the same compilation verdict and
16
+ compatibility verdict and global counts without route or issue pages.
17
+ `firstIssue` is the first issue across the whole project, or null; supplied
18
+ pagination settings do not change it. The low-level
19
+ `analyzeCompiledCapabilities` API still returns the complete report.
20
+ For unusually long route paths, request smaller pages to fit the MCP output
21
+ byte limit; page entry bounds do not override that transport limit.
22
+ - `explainRoute(project, path, options)` selects the route for a path and
23
+ describes its effective behavior from the compiled IR: methods, handler (with
24
+ its destination, module and export, file, extension or link collection),
25
+ the middleware chain in order, validated inputs (parameters and the request
26
+ body policy), the policies in effect with each compiled policy's inventory,
27
+ extension requirements, the cache outcome (the policy strategy, an explicit
28
+ header, an asset declaration, or the `no-store` the runtime forces on
29
+ extension, proxy and conditional routes), binding names (never values),
30
+ egress origins, response headers, capabilities and per-target support. A miss
31
+ returns `matched: false` with the nearest route patterns. With `extensions`
32
+ (a host file's registrations) each extension requirement also reports whether
33
+ a provider is registered, whether its revision pin matches and whether the
34
+ requirement satisfies the provider's policy schema; nothing is activated.
35
+ `explainProject(project, options)` returns every route the same way.
36
+ - `buildManifest(project, options)` returns the generated semantic manifest
37
+ described under [`urlcode manifest`](#explain-and-manifest).
38
+ - `getCapabilities(target?)` describes local implementation support and separate
39
+ deployment evidence.
40
+ - `getCapability(name)` returns one catalog entry: kind, summary, resolved schema
41
+ fragments, constraints, required operator grants, per-target support, refused
42
+ targets and the bundled recipes and cookbook routes that use it. Unknown names
43
+ throw a `ConfigError` listing the valid names.
44
+ - `getSchemaFragment(path)` returns only the fragment of
45
+ `schemas/urlcode.schema.json` for a dotted path (`route`, `redirect`,
46
+ `policies.cache`, `site.sitemap`) with local `$ref`s inlined; `schemaPathNames()`
47
+ lists the accepted top-level names. Both read bundled package data only.
48
+ - `previewImport(options)` and `previewExport(project, format, acknowledgment?)`
49
+ return conversion reports and candidate text, never writing files. Provider
50
+ semantic differences require the existing explicit acknowledgment and remain
51
+ non-lossless.
52
+ - `listRecipes()` and `showRecipe(name)` expose the fixed bundled recipe catalog.
53
+ - `inspectExtensions({project, hostFile?})` reports each operator-registered
54
+ extension's name, contract version, targets, credential headers, configuration
55
+ and policy JSON Schemas, machine-readable project hook contracts, whether the project declares it, whether its revision
56
+ pin matches and where routes mount or require it, plus the project's declared
57
+ names. With `hostFile` it executes that trusted operator module under the
58
+ `--host-file` rules (absolute path, outside the project) and releases it
59
+ afterwards; without one it lists declarations only. `describeExtensions(project,
60
+ registrations?)` produces the same report from registrations already in hand.
61
+ Neither activates an extension. See [EXTENSIONS.md](EXTENSIONS.md).
62
+ - `buildContext(project, {target?, hostFile?, budget?})` returns the compact
63
+ project context an authoring agent needs before it writes anything (see
64
+ below); `renderContext` produces the YAML rendering and `estimateTokens`
65
+ the characters-per-token estimate the budget uses.
66
+
67
+ ## Project context
68
+
69
+ `urlcode context [--project DIR] [--target T] [--host-file F] [--budget N]
70
+ [--json] [--stats]` emits one deterministic YAML document (JSON with
71
+ `--json`) derived only from the compiled project and the capability catalog,
72
+ never from prose. It uses the same loader and semantic compiler as
73
+ `inspectProject`: no binding values, guest execution, environment reads or
74
+ network. Keys always appear in this order:
75
+
76
+ - `urlcode` (package version) and `schema` (`"1"`).
77
+ - `project`: entry file, route count, handlers used with counts, extensions
78
+ declared, policies in effect at the top level and the number of routes each
79
+ policy applies to, requested env and secret binding names,
80
+ `site` keys, and `files` (include, function and middleware paths). With
81
+ `--host-file`, `host` counts the operator module's extensions and plugins
82
+ without activating them.
83
+ - `routes`: path, methods and handler per route, sorted by path.
84
+ - `constraints`: a fixed list that holds for every project (network and Node
85
+ built-ins available to trusted code and withdrawn by `sandbox: true`, no
86
+ regex routes, one handler per route, exact or `{param}` path segments,
87
+ subtree mounts only for static and extension routes, no YAML interpolation,
88
+ injected `env`/`secrets` by operator grant only), each with a value and a
89
+ note spelling out how it differs between the two trust modes.
90
+ - `targets`: for each capability target (or the one `--target`), which of this
91
+ project's used features are supported, conditional, refused or unknown.
92
+ - `commands`: the exact `validate`, `test`, `audit --expect-routes N` (N is
93
+ the compiled route count), `routes` and `capabilities` invocations.
94
+
95
+ `--budget N` drops sections in a fixed order until the YAML rendering fits
96
+ the estimate: per-route detail, then `targets`, then the constraint notes
97
+ (keys and values stay), then `project.files`, then `commands`. The dropped
98
+ sections are listed under `omitted`. The estimate is `ceil(characters / 4)`;
99
+ there is no tokenizer dependency, so treat both numbers as approximate. A
100
+ budget the smallest rendering cannot meet is an error rather than an
101
+ overrun. `--stats` writes a JSON line to stderr comparing the estimated size
102
+ of the shipped documentation (`docs/*.md` and `llms.txt`) with the emitted
103
+ context, labeled `estimate: characters/4`. The MCP tool `get_context` takes
104
+ `target` and `budget` and returns the same object with `--project .` in the
105
+ commands; it never takes a host file or any other path.
106
+
107
+ Inspection reads declared configuration and function source graphs to validate
108
+ references and compute revision hashes. It compiles route and policy semantics
109
+ using dummy binding values. It never reads environment or dotenv credentials,
110
+ starts guest execution, follows network destinations, or opens operator link
111
+ stores. The result contains no raw compiled route, binding values or source text.
112
+ Inspection is not deployment readiness: missing operator grants, live service
113
+ availability, asset snapshot activation and provider behavior require their own
114
+ checks. Build output remains an explicit separate build API/CLI operation.
115
+
116
+ ## Feature planning
117
+
118
+ After `get_context`, use `urlcode plan-feature "goal" --project DIR --target
119
+ self-hosted --json` (MCP `plan_feature {goal, target?}`) when the next question
120
+ is which already-supported contract applies. It returns a bounded structured
121
+ plan: matching local recipes and capability decisions for the current revision,
122
+ operator-owned extension prerequisites and their registration/target status,
123
+ inert locked-artifact status, a deliberately small route/config outline where a
124
+ recipe defines one, application-code boundaries, explicit gaps, and the next
125
+ bounded calls. It never returns generated application code.
126
+
127
+ The goal is a 1–512 character string reduced to at most sixteen normalized
128
+ terms; the returned JSON is capped at 32 KiB (an estimated token count is
129
+ included). It only uses the compiled project, packaged capability/recipe data,
130
+ the already-verified local artifact cache and registrations that the operator
131
+ already supplied to the CLI/MCP session. It does not open a host file itself,
132
+ read binding values, execute guest or extension code, fetch a service, or make
133
+ a project change. A locked schema artifact remains inert and a registered
134
+ extension remains an operator decision: neither lets YAML select a package,
135
+ storage provider, key or grant. Canonical extension ordering is resolved only
136
+ by the operator-approved composition/scaffold contract, not by this planner.
137
+
138
+ The package root also exports existing operator-invoked workflow APIs:
139
+ `buildCloudflare(project, options)` compiles and writes a Cloudflare artifact;
140
+ `buildStatic(project, options)` compiles redirects and static files into plain
141
+ objects and redirect metadata for S3 + CloudFront (see [static
142
+ hosting](STATIC.md)); `runProjectTests(project, options)` starts the local
143
+ runtime, executes request fixtures and closes it; `scaffoldProject(project,
144
+ {dryRun})` creates missing placeholders while preserving existing files;
145
+ `initProject(destination)` creates the standard starter; and
146
+ `addRedirect(project, destination, alias?)` updates project YAML under the
147
+ authoring lock. `CloudflareBuildOptions`, `CloudflareBuildReport`,
148
+ `StaticBuildOptions`, `StaticBuildReport`, `ProjectTestOptions`,
149
+ `ProjectTestResult`, `ScaffoldReport` and `ScaffoldUnresolved` describe these
150
+ existing operations.
151
+
152
+ These SDK functions have explicit write or execution effects and are available
153
+ to trusted callers only. Project tests use normal runtime activation, grants and
154
+ sandboxing; granted proxy/signal fixtures can perform real outbound operations.
155
+ Compilation and authoring write caller-selected destinations under each existing
156
+ helper's documented rules. They are **not** MCP tools. MCP remains limited to
157
+ the read-only operations below; adding a package-root export does not grant an
158
+ assistant file-write, guest-execution, deployment or network authority.
159
+
160
+ ## Project review
161
+
162
+ `urlcode review [--project DIR] [--target T] [--host-file F] [--json]` (MCP
163
+ `review_project {target?}`) is an opt-in, read-only static review of the
164
+ compiled project plus its own `function`/`middleware` source, for the narrow,
165
+ agent-facing question "which of this generated code looks like avoidable
166
+ framework plumbing, and what is the supported alternative?" It scans only the
167
+ project's own root-confined source graph (the same `function`/`middleware`
168
+ file resolution `explain` and `manifest` use): no project code is executed, no
169
+ environment variable or secret is read, and no network call is made. Findings
170
+ are grouped:
171
+
172
+ - `native-alternative`: an already-supported declarative capability appears to
173
+ cover the behavior (for example `request.body.schema` in place of
174
+ hand-written `JSON.parse` plus field checks, or one route per method in
175
+ place of a hand-written `request.method` dispatch table).
176
+ - `extension-alternative`: the project **declares** an extension that could
177
+ plausibly own the behavior. Without `--host-file`, the required operator
178
+ setup (registration, revision pin) is stated as unconfirmed — a declaration
179
+ is never reported as an active or executable extension. With `--host-file`,
180
+ the already-loaded operator registrations (the same ones `explain` and
181
+ `plan-feature` accept; the host file is trusted operator code outside the
182
+ project, never project code, and review only reads the registrations it
183
+ already returned — it still never activates or calls into an extension)
184
+ sharpen the finding to state whether that specific extension is actually
185
+ registered and, if so, whether the registration is pinned to this project's
186
+ current revision (`registered`/`revisionPinned` on the observation). A
187
+ registered extension is still never reported as active or executable —
188
+ only as registered, which is a narrower, verifiable claim.
189
+ - `gap`: no current native or extension composition covers the pattern (for
190
+ example durable, cross-instance counters); this is reported as a real
191
+ capability gap, not a mistake to silently patch.
192
+ - `manual-review`: a security- or durable-state-sensitive pattern (manually
193
+ assembled cookies/sessions, a direct outbound network call, or hand-written
194
+ logic that duplicates a `policies.*` block already declared for the route)
195
+ that this tool never classifies automatically. Trusted, unsandboxed
196
+ execution is an explicit supported mode (`SPIKE-DEFAULT-TRUST-MODEL.md`);
197
+ nothing here claims a function is unsafe solely because it is trusted.
198
+
199
+ Its scope covers seven signals, each with source location, a short bounded
200
+ excerpt (untrusted project text, never executed or treated as instructions), a
201
+ confidence level and a plain-language reason:
202
+
203
+ - Hand-written JSON body validation (`manual-body-validation`).
204
+ - Manually assembled `Set-Cookie`/session construction (`manual-cookie-session`).
205
+ - Module-scope mutable state later mutated in the same file (`global-mutable-state`).
206
+ - A direct outbound call, `fetch`/`http(s).request`/`http(s).get` (`outbound-network-call`).
207
+ - Hand-written `request.method` branching or a `switch (request.method)`
208
+ dispatch table (`method-dispatch`), reported as `native-alternative`:
209
+ declaring one route per method is the native alternative (see
210
+ `get_capability("methods")`); URLCode has no per-method-function YAML shape
211
+ to point at instead (`YAML-REFERENCE.md` is explicit that "a `methods:` map
212
+ of per-method functions is not implemented").
213
+ - Hand-rolled request counting paired with a `429`/`Retry-After` response
214
+ (`manual-rate-limit`). Reported as `native-alternative` (pointing at
215
+ `get_capability("policies.throttle")`) when `policies.throttle` is not
216
+ effectively declared for the route, or as `manual-review` when it already
217
+ is — duplicating an active policy is a real conflict, not just a missed
218
+ opportunity, and needs a human decision to remove one side.
219
+ - Two or more hand-set security response headers, from `X-Frame-Options`,
220
+ `Content-Security-Policy`, `Strict-Transport-Security`,
221
+ `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy` or
222
+ `X-XSS-Protection` (`manual-security-headers`). Reported as
223
+ `native-alternative` (pointing at `get_capability("policies.security")`)
224
+ when `policies.security` is not effectively declared for the route, or as
225
+ `manual-review` when it already is, for the same reason as rate limiting.
226
+
227
+ Both policy-duplication signals cross-reference each route's actual *effective*
228
+ policy (project/profile defaults plus the route's own `policies` block, the
229
+ same resolution `compilePolicies` performs) before deciding whether to word a
230
+ finding as "you could declare this" (`native-alternative`) or "this duplicates
231
+ what's already declared" (`manual-review`) — never the reverse, and never a
232
+ claim of duplication against a policy that was never declared for that route.
233
+
234
+ It is deliberately conservative and does not attempt every signal a generated
235
+ project could exhibit. Hand-written conditional redirect logic (branching in
236
+ code toward what could be a declarative `match`/`conditional` route) was
237
+ considered and set aside: ordinary application branching that happens to end
238
+ in a redirect is common and mostly has nothing to do with routing
239
+ configuration, so a bounded source-text signal for it would be prone to
240
+ false positives against legitimate business logic — an uncertain observation
241
+ is preferable to an incorrect automatic refactor.
242
+
243
+ ## Explain and manifest
244
+
245
+ `urlcode explain [/route] [--project DIR] [--target T] [--host-file F] [--json]`
246
+ prints what `explainRoute` returns: one route in detail, or without a path a
247
+ one-line-per-route table (methods, handler, state, execution mode, middleware
248
+ count, policies, cache outcome and target support). `--target` narrows the support columns to
249
+ one deployment target; `--host-file` supplies the operator registry so
250
+ extension requirements show their provider. An unknown route exits 1 and names
251
+ the nearest patterns. Everything comes from the compiled configuration: no
252
+ request is evaluated, no function runs and no binding is read.
253
+
254
+ `urlcode manifest [--project DIR] [--json]` emits the semantic manifest:
255
+ `schemaVersion`, the `urlcode` version, the entry file and its includes, the
256
+ `revision` (the same digest `inspectExtensionRevision` returns, so an operator
257
+ pin can be checked against it), the config `configVersion`, every route (path,
258
+ methods, handler, state, execution mode (`sandbox`, with `sandboxReason` when
259
+ the route declares one), middleware, inputs, policy names, extension
260
+ requirements, cache outcome, binding names, egress origins, capabilities and
261
+ per-target support), the union of capabilities used, extension declarations
262
+ (version, configuration keys, mounts and protected routes), recipe provenance
263
+ (from a `recipe.yaml` beside the entry file when one exists), external
264
+ requirements (environment and secret names, proxy and signal origins,
265
+ extensions), the function and middleware
266
+ modules with the routes that use them, and per-target compatibility. Without
267
+ `--json` a short summary prints. The manifest is deterministic: the same
268
+ project produces the same bytes. `urlcode build` writes the same document as
269
+ `manifest.json` beside its output, and `buildManifest` returns it from the SDK.
270
+ It is generated output, never a checked-in source of truth; regenerate it
271
+ rather than editing it.
272
+
273
+ `serveMcp({project, input?, output?, origin?, allowAuthoring?, hostFile?})` serves one
274
+ operator-selected root on stdio. Its tools are `inspect`, `validate`,
275
+ `capabilities`, `get_capability`, `get_schema`, `explain`, `get_manifest`,
276
+ `import_preview`, `export_preview`, `recipes_list`, `recipes_show`,
277
+ `search_recipes`, `search_examples`, `list_skills`, `get_skill`, `search_docs`,
278
+ `get_example`, `validate_yaml`, `explain_error`, `get_extension_artifacts`,
279
+ `get_extension_artifact`, `get_context`, `plan_feature` and `review_project`. The skill,
280
+ documentation and example tools read only a fixed package-owned manifest; no
281
+ tool argument names an arbitrary local path or remote URL. The CLI equivalent of `search_docs` is
282
+ `urlcode docs search TEXT [--json]`, which returns the same at most three bounded excerpts. `validate_yaml` checks supplied
283
+ YAML syntax and schema only, while `validate` compiles the selected local project.
284
+ `get_extension_artifacts` validates the project-selected
285
+ `urlcode.extensions.lock.json` and cache, then returns artifact metadata,
286
+ status and allowlisted member paths. `get_extension_artifact` accepts only a
287
+ locked artifact name and one of those relative JSON/Markdown member paths; it
288
+ revalidates the cache and reads at most 512 KiB directly from the signed archive.
289
+ Both are local, read-only and inert: they never download, install, update or
290
+ activate an extension and never substitute for `get_extensions`, which reports
291
+ the operator-registered executable contract.
292
+ When the operator starts
293
+ the server with `--host-file`, it loads that trusted module once for the session
294
+ and additionally advertises `get_extensions`, which returns the
295
+ `inspectExtensions` report; without the option the tool is absent and calls to
296
+ it are rejected. Tools accept no project/file/output path argument; recipe names
297
+ come from the fixed catalog, `get_capability` names from the capability catalog,
298
+ `get_schema` paths from the bundled schema, and the two searches match bundled
299
+ metadata locally (see [recipes](RECIPES.md)).
300
+ There is no shell, arbitrary file read, remote fetch, binding access, write or
301
+ route-execution tool without the explicit [authoring mode](#authoring-mode) flag. Configuration includes and module references retain the
302
+ runtime's existing root containment checks. Returned project and recipe content
303
+ is data, not trusted instructions for the consuming agent.
304
+
305
+ The server implements the MCP **2025-11-25** lifecycle and stdio framing. Clients
306
+ initialize, verify the returned protocol version, then send
307
+ `notifications/initialized` before tool operations. Other requested revisions
308
+ negotiate to this explicit supported version; a client that cannot support it
309
+ must disconnect. Newer lifecycle revisions are not claimed. Requests use UTF-8
310
+ newline-delimited JSON-RPC 2.0, with one request at a time and stream backpressure.
311
+ There is a 1 MiB input-frame and output-message limit; oversized input terminates
312
+ the session after a fixed error, and truncated/invalid frames return protocol
313
+ errors. Import text is additionally capped at 512 KiB. Tool schemas reject
314
+ unknown arguments. Tool operation errors are generic to avoid exposing local
315
+ source paths, credentials or configuration excerpts; inspect locally for details.
316
+
317
+ ## Registering the server
318
+
319
+ `urlcode init` (and `init --with`) writes `.mcp.json` at the project root, the
320
+ shape Claude Code and Codex read:
321
+
322
+ ```json
323
+ { "mcpServers": { "urlcode": { "command": "urlcode", "args": ["mcp", "--project", "."] } } }
324
+ ```
325
+
326
+ For an `init --with` site the file sits beside `host.mjs` and passes
327
+ `--project app`. An existing `.mcp.json` is never overwritten. The file registers
328
+ the read-only server only: `--allow-authoring` (and `--host-file`) are operator
329
+ choices added by hand, never by `init` or by an agent.
330
+
331
+ - **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
332
+ server and asks for approval on first use. A project that pins the runtime in its
333
+ `package.json` (`--template redirects`, `--with`, `--manifest`) gets
334
+ `"command": "npx"` with `--no --package @jimhoyd/urlcode urlcode mcp ...`, which runs the
335
+ installed copy and never fetches (do not use a bare `npx urlcode`: that names an unrelated
336
+ registry package). A project without one keeps the bare `urlcode` command for a global
337
+ install; for a local-only install replace it with `"node"` and prefix the arguments with
338
+ `node_modules/@jimhoyd/urlcode/dist/cli.js`.
339
+ - **Codex** reads the same `mcpServers` shape; alternatively register it in
340
+ `~/.codex/config.toml`:
341
+
342
+ ```toml
343
+ [mcp_servers.urlcode]
344
+ command = "urlcode"
345
+ args = ["mcp", "--project", "."]
346
+ ```
347
+ - **Any stdio client** spawns `urlcode mcp --project DIR` with the project as the
348
+ working directory, speaks newline-delimited JSON-RPC 2.0 over stdin/stdout,
349
+ and follows the 2025-11-25 lifecycle described above. Nothing listens on a
350
+ port; closing stdin ends the session.
351
+
352
+ The generated `AGENTS.md` and the packaged skill tell agents to prefer
353
+ `get_context`, `get_capability`, `get_schema`, `search_recipes`, `explain` and
354
+ `get_manifest` when the server is registered and to fall back to the matching
355
+ CLI commands otherwise.
356
+
357
+ ## Authoring mode
358
+
359
+ `urlcode mcp --allow-authoring --project DIR` adds six tools to the twenty-two read
360
+ tools above. The flag is honored from the operator's command line only: no
361
+ tool argument, environment variable or client capability enables it, and
362
+ without it the server is exactly the read-only server described above.
363
+
364
+ What it can do, all inside the selected project root (resolved with realpath):
365
+
366
+ - `create_route {path, handler, middleware?, file?}` adds one route to
367
+ `urlcode.yaml` or to an include listed in it. `handler` is a route object
368
+ (`{redirect: {...}}`, `{function: {...}}`, `{page: {...}}`, ...) or a short
369
+ form: an `http(s)://` URL becomes a redirect, a `.js`/`.mjs` path becomes a
370
+ function whose `{param}` path segments expand to required bounded string
371
+ parameters and matching `args`. `middleware` entries are sources or objects.
372
+ The merged project is checked before the write (schema, duplicate routes,
373
+ the `auth` short form, and the same reference compilation `urlcode add`
374
+ performs when every referenced source exists). The edit runs under the
375
+ authoring lock and replaces the file atomically. Missing sources are listed
376
+ in `missingSources` for `scaffold_feature`.
377
+ - `add_recipe {name, destination, dryRun?}` runs `recipes add` into a new
378
+ directory under the project. The parent must exist; an existing destination
379
+ is refused, never merged. `dryRun` reports the destination and writes nothing.
380
+ - `scaffold_feature {dryRun?}` runs `urlcode scaffold`: placeholder modules,
381
+ pages and directories for references the YAML makes and the disk lacks.
382
+ Existing files are preserved, never overwritten.
383
+ - `run_validate`, `run_test`, `run_audit` spawn `urlcode validate --local`,
384
+ `urlcode test` and `urlcode audit` against the project with a minimal
385
+ environment (`PATH` only), a two-minute deadline and stdout/stderr each capped
386
+ at 32 KiB. The result carries `exitCode`, `signal`, `stdout`, `stderr` and
387
+ `truncated`. `run_test` activates the local runtime and executes fixtures,
388
+ under the same rules as the CLI.
389
+
390
+ Every tool returns `validation`, the `validateProject` verdict of the project
391
+ after the operation (or `valid: false` with a generic note; use `run_validate`
392
+ for the CLI report).
393
+
394
+ What it cannot do:
395
+
396
+ - Write outside the project root. Paths are project-relative; absolute paths,
397
+ `..`, backslashes, drive letters, and any symlink on the walk are refused
398
+ before the write, and the recipe, scaffold and `urlcode add` paths keep their
399
+ own containment checks.
400
+ - Touch `.env*`, anything under `.git`, `node_modules`, `package.json`,
401
+ credential files (`.pem`, `.key`, `.p12`, `.pfx`), the authoring lock, or
402
+ operator files by name: `*policy*.json`, `*compliance*`, `host.mjs` /
403
+ `host-file.mjs` and link stores (`.sqlite`, `.db` and their WAL/SHM files).
404
+ Operator files belong outside the checkout in the first place.
405
+ - Create or change grants, read bindings or secret values, deploy, build, run
406
+ arbitrary commands, delete or edit existing files (except the one YAML file a
407
+ `create_route` targets), or serve a project other than the one the operator
408
+ selected.
409
+
410
+ Authoring mode is a local, unauthenticated stdio process for an operator who
411
+ already trusts the assistant to edit this checkout. Review the resulting diff
412
+ as you would any contributor's before running `serve` or deploying.
413
+
414
+ Only tools are advertised. Resources, prompts, subscriptions, sampling,
415
+ elicitation, HTTP transport, cancellation and durable tasks are not implemented.
416
+ Closing stdin ends the session after the current bounded operation. Existing
417
+ configuration-loader and semantic-compiler deadlines still apply. This local
418
+ process is not an authenticated remote service or an independent security review.
419
+
420
+ Protocol references: [MCP stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports),
421
+ [MCP lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle),
422
+ and [MCP tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).