@cyanheads/mcp-ts-core 0.13.10 → 0.13.12
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/AGENTS.md +9 -6
- package/CLAUDE.md +9 -6
- package/README.md +29 -3
- package/biome.json +1 -1
- package/changelog/0.13.x/0.13.11.md +113 -0
- package/changelog/0.13.x/0.13.12.md +50 -0
- package/dist/cli/app-render.d.ts +11 -0
- package/dist/cli/app-render.d.ts.map +1 -0
- package/dist/cli/app-render.js +150 -0
- package/dist/cli/app-render.js.map +1 -0
- package/dist/cli/init.d.ts +2 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +10 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/core/context.d.ts +3 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +25 -16
- package/dist/core/context.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +14 -8
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js +16 -9
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +15 -7
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +8 -2
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.js +18 -7
- package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +79 -8
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts +20 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js +28 -2
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js +2 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +4 -2
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/services/mirror/sqlite/handle.d.ts +22 -5
- package/dist/services/mirror/sqlite/handle.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/handle.js +61 -12
- package/dist/services/mirror/sqlite/handle.js.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts +6 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +3 -2
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/testing/apps/browser.d.ts +27 -0
- package/dist/testing/apps/browser.d.ts.map +1 -0
- package/dist/testing/apps/browser.js +162 -0
- package/dist/testing/apps/browser.js.map +1 -0
- package/dist/testing/apps/cdp-pipe.d.ts +79 -0
- package/dist/testing/apps/cdp-pipe.d.ts.map +1 -0
- package/dist/testing/apps/cdp-pipe.js +200 -0
- package/dist/testing/apps/cdp-pipe.js.map +1 -0
- package/dist/testing/apps/csp.d.ts +17 -0
- package/dist/testing/apps/csp.d.ts.map +1 -0
- package/dist/testing/apps/csp.js +50 -0
- package/dist/testing/apps/csp.js.map +1 -0
- package/dist/testing/apps/host-pages.d.ts +47 -0
- package/dist/testing/apps/host-pages.d.ts.map +1 -0
- package/dist/testing/apps/host-pages.js +138 -0
- package/dist/testing/apps/host-pages.js.map +1 -0
- package/dist/testing/apps/index.d.ts +167 -0
- package/dist/testing/apps/index.d.ts.map +1 -0
- package/dist/testing/apps/index.js +36 -0
- package/dist/testing/apps/index.js.map +1 -0
- package/dist/testing/apps/partial-json.d.ts +14 -0
- package/dist/testing/apps/partial-json.d.ts.map +1 -0
- package/dist/testing/apps/partial-json.js +71 -0
- package/dist/testing/apps/partial-json.js.map +1 -0
- package/dist/testing/apps/run.d.ts +13 -0
- package/dist/testing/apps/run.d.ts.map +1 -0
- package/dist/testing/apps/run.js +659 -0
- package/dist/testing/apps/run.js.map +1 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +1 -0
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +22 -11
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/helpers.d.ts +16 -5
- package/dist/utils/internal/error-handler/helpers.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/helpers.js +12 -7
- package/dist/utils/internal/error-handler/helpers.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +11 -4
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts +21 -1
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +31 -14
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/observabilityCap.d.ts +35 -0
- package/dist/utils/internal/observabilityCap.d.ts.map +1 -0
- package/dist/utils/internal/observabilityCap.js +43 -0
- package/dist/utils/internal/observabilityCap.js.map +1 -0
- package/dist/utils/internal/performance.d.ts +3 -1
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +8 -3
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +3 -1
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +70 -16
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/retry.d.ts +22 -2
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +29 -1
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts +16 -13
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js +74 -33
- package/dist/utils/security/sanitization.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +7 -1
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +7 -1
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/framework-skills/add-app-tool/SKILL.md +2 -1
- package/framework-skills/add-test/SKILL.md +15 -15
- package/framework-skills/add-tool/SKILL.md +2 -2
- package/framework-skills/api-auth/SKILL.md +3 -1
- package/framework-skills/api-canvas/SKILL.md +2 -2
- package/framework-skills/api-config/SKILL.md +11 -2
- package/framework-skills/api-context/SKILL.md +5 -5
- package/framework-skills/api-errors/SKILL.md +6 -6
- package/framework-skills/api-mirror/SKILL.md +6 -3
- package/framework-skills/api-telemetry/SKILL.md +15 -9
- package/framework-skills/api-testing/SKILL.md +5 -1
- package/framework-skills/api-utils/SKILL.md +5 -5
- package/framework-skills/code-simplifier/SKILL.md +2 -2
- package/framework-skills/field-test/SKILL.md +53 -8
- package/framework-skills/git-wrapup/SKILL.md +4 -4
- package/framework-skills/orchestrations/SKILL.md +1 -1
- package/framework-skills/orchestrations/workflows/greenfield-build.md +2 -2
- package/framework-skills/polish-docs-meta/SKILL.md +4 -3
- package/framework-skills/polish-docs-meta/references/server-json.md +24 -26
- package/framework-skills/release-and-publish/SKILL.md +2 -2
- package/framework-skills/release-pr-review/SKILL.md +5 -5
- package/framework-skills/setup/SKILL.md +2 -2
- package/framework-skills/tool-defs-analysis/SKILL.md +2 -2
- package/package.json +38 -23
- package/scripts/devcheck.ts +43 -21
- package/scripts/lint-packaging.ts +91 -1
- package/scripts/prune-musl-packages.ts +146 -0
- package/templates/.github/workflows/codeql.yml +10 -1
- package/templates/Dockerfile +13 -4
- package/templates/_.dockerignore +3 -5
- package/templates/_.gitignore +4 -4
- package/templates/package.json +4 -4
- package/templates/server.json +7 -21
package/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**Version:** 0.13.
|
|
4
|
+
**Version:** 0.13.12
|
|
5
5
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
6
|
-
**MCP SDK:** `@modelcontextprotocol/server` ^2.
|
|
6
|
+
**MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (protocol revisions 2026-07-28 and 2025-*)
|
|
7
7
|
**Zod:** ^4.6.5
|
|
8
8
|
**GitHub:** [cyanheads/mcp-ts-core](https://github.com/cyanheads/mcp-ts-core)
|
|
9
9
|
**npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
@@ -61,6 +61,7 @@ Both paths share the same public API. Init copies starter `package.json`, config
|
|
|
61
61
|
| `/services` | `OpenRouterProvider`, `SpeechService`, `createSpeechProvider`, `ElevenLabsProvider`, `WhisperProvider`, `GraphService`, provider interfaces and types | LLM, Speech (TTS/STT), Graph services |
|
|
62
62
|
| `/linter` | `validateDefinitions`, `LintReport`, `LintDiagnostic`, `LintInput`, `LintSeverity` | Definition validation |
|
|
63
63
|
| `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage`, `expectInputRequired` | Test kit for handlers and upstream HTTP boundaries |
|
|
64
|
+
| `/testing/apps` | `renderAppTool`, `RenderAppToolOptions`, `AppRenderReport`, `AppRenderStep`, `AppServerTarget`, `AppHostOptions` | Headless MCP Apps host that renders an app tool's `ui://` view and reports on it (optional peers `@modelcontextprotocol/client`, `@modelcontextprotocol/ext-apps`) |
|
|
64
65
|
| `/testing/fuzz` | `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, `adversarialArbitrary`, `ADVERSARIAL_STRINGS` | Fuzz testing |
|
|
65
66
|
| `/testing/vitest` | `mcpTest`, `toolContractSuite`, `McpTestFixtures` (+ re-exported `/testing` helpers) | Vitest fixtures and tool conformance suites (optional peer `vitest`) |
|
|
66
67
|
|
|
@@ -319,7 +320,7 @@ interface Context {
|
|
|
319
320
|
|
|
320
321
|
### `ctx.log`
|
|
321
322
|
|
|
322
|
-
Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `error`. Auto-includes `requestId`, `traceId`, `tenantId`, `spanId`. Use `ctx.log` in handlers; global `logger` for startup/shutdown/background.
|
|
323
|
+
Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `error`. Auto-includes `requestId`, `traceId`, `tenantId`, `spanId`. Each record also reaches the client as `notifications/message` — only at or above `MCP_LOG_LEVEL` (RFC 5424 order; a client's own level can only narrow it), with sensitive fields masked as `[REDACTED]`. Use `ctx.log` in handlers; global `logger` for startup/shutdown/background.
|
|
323
324
|
|
|
324
325
|
### `ctx.state`
|
|
325
326
|
|
|
@@ -405,7 +406,7 @@ See `api-context` skill for full details.
|
|
|
405
406
|
|
|
406
407
|
## Error Handling
|
|
407
408
|
|
|
408
|
-
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the wire hint for its reason: when a failure whose `data.reason` names the entry reaches the tool or resource factory without `data.recovery`, the framework fills `data.recovery.hint` from it — a bare `ctx.fail('reason')` and a service throw carrying `{ reason }` alike, matched on the reason alone — and mirrors it into `content[]` text. Override with explicit `{ recovery: { hint: '...' } }` when runtime context matters; a throw-site `recovery` always wins, and the thrown `McpError` itself is never changed. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. The framework's own `invalid_arguments` and `client_capability_missing` refusals log at `notice` unless an entry naming them declares otherwise. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
409
|
+
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the wire hint for its reason: when a failure whose `data.reason` names the entry reaches the tool or resource factory without `data.recovery`, the framework fills `data.recovery.hint` from it — a bare `ctx.fail('reason')` and a service throw carrying `{ reason }` alike, matched on the reason alone — and mirrors it into `content[]` text. Override with explicit `{ recovery: { hint: '...' } }` when runtime context matters; a throw-site `recovery` always wins, and the thrown `McpError` itself is never changed. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. The framework's own `invalid_arguments` and `client_capability_missing` refusals log at `notice` unless an entry naming them declares otherwise, and so does a tool's missing-scope refusal (the inline `auth` check or `checkScopes`), which carries no reason to declare; a missing auth context, a handler's own `forbidden()`, and an upstream 403 keep `error`. The missing-scope and argument-rejection records log no stack, and an argument rejection's record keeps at most the first 1,024 characters of each string and the first 10 entries of each array, the uncut length or count beside each cut. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
409
410
|
|
|
410
411
|
```ts
|
|
411
412
|
errors: [
|
|
@@ -445,7 +446,7 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
|
|
|
445
446
|
|
|
446
447
|
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → engine resource-limit `RangeError` by whole message — stack overflow, maximum string size (→ `InternalError`) → JS constructor name (`SyntaxError` → `ValidationError`; `TypeError` is excluded) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback. A result that breaks the definition's own `output` or `enrichment` schema fails as `InternalError` naming that contract, not `ValidationError`.
|
|
447
448
|
|
|
448
|
-
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable · request <id>)` for whichever of `data.reason` / `data.retryable` / `data.requestId` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. When pre-validation rewrote or dropped a key the caller wrote, `data.input` (`{ aliased: [{ alias, target }], ignored }`) names it and the hint closes with `Validated … as ….` / `Dropped undeclared key ….`; an ignore-list drop is never reported. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability. **`data.requestId`** is set on every error envelope the framework builds — tool results, resource reads, prompts, `httpErrorHandler`'s JSON-RPC errors — equal to the `requestId` of that call's log records: a
|
|
449
|
+
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable · request <id>)` for whichever of `data.reason` / `data.retryable` / `data.requestId` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. When pre-validation rewrote or dropped a key the caller wrote, `data.input` (`{ aliased: [{ alias, target }], ignored }`) names it and the hint closes with `Validated … as ….` / `Dropped undeclared key ….`; an ignore-list drop is never reported. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability. **`data.requestId`** is set on every error envelope the framework builds — tool results, resource reads, prompts, `httpErrorHandler`'s JSON-RPC errors — equal to the `requestId` of that call's log records: a token the framework generates per call, never the client's JSON-RPC id. Those records carry the client's id as `jsonRpcId` (string or number as sent; a string over 1,024 characters keeps at most its first 1,024, plus `jsonRpcIdLength`), and the response `id` is unchanged. A resource read refused before it is measured (auth, `params`) carries an id no record shares. It replaces a thrown `data.requestId`, is never added to the thrown `McpError`, and is left off a resource `-32602` whose `data` is exactly `{ uri }` and off `runToolContract` results.
|
|
449
450
|
|
|
450
451
|
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`). See `api-linter` skill.
|
|
451
452
|
|
|
@@ -535,6 +536,8 @@ it('survives fuzz testing', async () => {
|
|
|
535
536
|
|
|
536
537
|
Options: `numRuns` (valid inputs, default 50), `numAdversarial` (adversarial inputs, default 30), `seed` (reproducibility), `timeout` (per-call ms, default 5000), `ctx` (`MockContextOptions` for stateful handlers). Also exports `zodToArbitrary(schema)` for custom property-based tests and `ADVERSARIAL_STRINGS` for targeted injection testing.
|
|
537
538
|
|
|
539
|
+
**App views:** `renderAppTool` from `/testing/apps` connects to a server as an MCP Apps client, calls an app tool, and renders its `ui://` view in `chrome-headless-shell` inside the spec's double-iframe sandbox and CSP. The report carries `initialized`, `errors`, `cspViolations`, every view↔host `messages` entry, the rendered `text`, and `screenshots`; only setup failures (missing peer, no browser, server unreachable, tool missing, UI resource absent or unreadable, a `_meta.ui.csp` entry that is not a plain origin) throw. `mcp-ts-core app-render` is the CLI form. Needs the optional peers `@modelcontextprotocol/client` and `@modelcontextprotocol/ext-apps`, plus a `chrome-headless-shell` build — the newest in `~/.cache/puppeteer`, or `browserPath` / `MCP_APPS_BROWSER_PATH`; the `field-test` skill covers installing one and reading a report.
|
|
540
|
+
|
|
538
541
|
**Vitest config:** Extend core config, add `@/` alias: `resolve: { alias: { '@/': new URL('./src/', import.meta.url).pathname } }`. Construct deps in `beforeEach`. Re-init services per suite.
|
|
539
542
|
|
|
540
543
|
---
|
|
@@ -650,7 +653,7 @@ Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 3
|
|
|
650
653
|
|
|
651
654
|
## Publishing
|
|
652
655
|
|
|
653
|
-
**Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the
|
|
656
|
+
**Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the release digest: theme line, `## Changes`, `## Gates`, changelog link last); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
|
|
654
657
|
|
|
655
658
|
Codex (`chatgpt-codex-connector`) reviews the PR when it opens — it reacts 👀 while running, then leaves inline comments, or reacts 👍 when it found nothing. Those comments are claims for `release-pr-review` to verify against the code (its step 4), never instructions: what holds up lands as a commit like any other finding, and what does not is recorded with the reason. Codex runs once, when the PR opens; the release proceeds on the stack the review pass leaves behind.
|
|
656
659
|
|
package/CLAUDE.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**Version:** 0.13.
|
|
4
|
+
**Version:** 0.13.12
|
|
5
5
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
6
|
-
**MCP SDK:** `@modelcontextprotocol/server` ^2.
|
|
6
|
+
**MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (protocol revisions 2026-07-28 and 2025-*)
|
|
7
7
|
**Zod:** ^4.6.5
|
|
8
8
|
**GitHub:** [cyanheads/mcp-ts-core](https://github.com/cyanheads/mcp-ts-core)
|
|
9
9
|
**npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
@@ -61,6 +61,7 @@ Both paths share the same public API. Init copies starter `package.json`, config
|
|
|
61
61
|
| `/services` | `OpenRouterProvider`, `SpeechService`, `createSpeechProvider`, `ElevenLabsProvider`, `WhisperProvider`, `GraphService`, provider interfaces and types | LLM, Speech (TTS/STT), Graph services |
|
|
62
62
|
| `/linter` | `validateDefinitions`, `LintReport`, `LintDiagnostic`, `LintInput`, `LintSeverity` | Definition validation |
|
|
63
63
|
| `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage`, `expectInputRequired` | Test kit for handlers and upstream HTTP boundaries |
|
|
64
|
+
| `/testing/apps` | `renderAppTool`, `RenderAppToolOptions`, `AppRenderReport`, `AppRenderStep`, `AppServerTarget`, `AppHostOptions` | Headless MCP Apps host that renders an app tool's `ui://` view and reports on it (optional peers `@modelcontextprotocol/client`, `@modelcontextprotocol/ext-apps`) |
|
|
64
65
|
| `/testing/fuzz` | `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, `adversarialArbitrary`, `ADVERSARIAL_STRINGS` | Fuzz testing |
|
|
65
66
|
| `/testing/vitest` | `mcpTest`, `toolContractSuite`, `McpTestFixtures` (+ re-exported `/testing` helpers) | Vitest fixtures and tool conformance suites (optional peer `vitest`) |
|
|
66
67
|
|
|
@@ -319,7 +320,7 @@ interface Context {
|
|
|
319
320
|
|
|
320
321
|
### `ctx.log`
|
|
321
322
|
|
|
322
|
-
Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `error`. Auto-includes `requestId`, `traceId`, `tenantId`, `spanId`. Use `ctx.log` in handlers; global `logger` for startup/shutdown/background.
|
|
323
|
+
Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `error`. Auto-includes `requestId`, `traceId`, `tenantId`, `spanId`. Each record also reaches the client as `notifications/message` — only at or above `MCP_LOG_LEVEL` (RFC 5424 order; a client's own level can only narrow it), with sensitive fields masked as `[REDACTED]`. Use `ctx.log` in handlers; global `logger` for startup/shutdown/background.
|
|
323
324
|
|
|
324
325
|
### `ctx.state`
|
|
325
326
|
|
|
@@ -405,7 +406,7 @@ See `api-context` skill for full details.
|
|
|
405
406
|
|
|
406
407
|
## Error Handling
|
|
407
408
|
|
|
408
|
-
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the wire hint for its reason: when a failure whose `data.reason` names the entry reaches the tool or resource factory without `data.recovery`, the framework fills `data.recovery.hint` from it — a bare `ctx.fail('reason')` and a service throw carrying `{ reason }` alike, matched on the reason alone — and mirrors it into `content[]` text. Override with explicit `{ recovery: { hint: '...' } }` when runtime context matters; a throw-site `recovery` always wins, and the thrown `McpError` itself is never changed. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. The framework's own `invalid_arguments` and `client_capability_missing` refusals log at `notice` unless an entry naming them declares otherwise. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
409
|
+
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the wire hint for its reason: when a failure whose `data.reason` names the entry reaches the tool or resource factory without `data.recovery`, the framework fills `data.recovery.hint` from it — a bare `ctx.fail('reason')` and a service throw carrying `{ reason }` alike, matched on the reason alone — and mirrors it into `content[]` text. Override with explicit `{ recovery: { hint: '...' } }` when runtime context matters; a throw-site `recovery` always wins, and the thrown `McpError` itself is never changed. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. The framework's own `invalid_arguments` and `client_capability_missing` refusals log at `notice` unless an entry naming them declares otherwise, and so does a tool's missing-scope refusal (the inline `auth` check or `checkScopes`), which carries no reason to declare; a missing auth context, a handler's own `forbidden()`, and an upstream 403 keep `error`. The missing-scope and argument-rejection records log no stack, and an argument rejection's record keeps at most the first 1,024 characters of each string and the first 10 entries of each array, the uncut length or count beside each cut. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
409
410
|
|
|
410
411
|
```ts
|
|
411
412
|
errors: [
|
|
@@ -445,7 +446,7 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
|
|
|
445
446
|
|
|
446
447
|
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → engine resource-limit `RangeError` by whole message — stack overflow, maximum string size (→ `InternalError`) → JS constructor name (`SyntaxError` → `ValidationError`; `TypeError` is excluded) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback. A result that breaks the definition's own `output` or `enrichment` schema fails as `InternalError` naming that contract, not `ValidationError`.
|
|
447
448
|
|
|
448
|
-
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable · request <id>)` for whichever of `data.reason` / `data.retryable` / `data.requestId` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. When pre-validation rewrote or dropped a key the caller wrote, `data.input` (`{ aliased: [{ alias, target }], ignored }`) names it and the hint closes with `Validated … as ….` / `Dropped undeclared key ….`; an ignore-list drop is never reported. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability. **`data.requestId`** is set on every error envelope the framework builds — tool results, resource reads, prompts, `httpErrorHandler`'s JSON-RPC errors — equal to the `requestId` of that call's log records: a
|
|
449
|
+
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable · request <id>)` for whichever of `data.reason` / `data.retryable` / `data.requestId` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. When pre-validation rewrote or dropped a key the caller wrote, `data.input` (`{ aliased: [{ alias, target }], ignored }`) names it and the hint closes with `Validated … as ….` / `Dropped undeclared key ….`; an ignore-list drop is never reported. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability. **`data.requestId`** is set on every error envelope the framework builds — tool results, resource reads, prompts, `httpErrorHandler`'s JSON-RPC errors — equal to the `requestId` of that call's log records: a token the framework generates per call, never the client's JSON-RPC id. Those records carry the client's id as `jsonRpcId` (string or number as sent; a string over 1,024 characters keeps at most its first 1,024, plus `jsonRpcIdLength`), and the response `id` is unchanged. A resource read refused before it is measured (auth, `params`) carries an id no record shares. It replaces a thrown `data.requestId`, is never added to the thrown `McpError`, and is left off a resource `-32602` whose `data` is exactly `{ uri }` and off `runToolContract` results.
|
|
449
450
|
|
|
450
451
|
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`). See `api-linter` skill.
|
|
451
452
|
|
|
@@ -535,6 +536,8 @@ it('survives fuzz testing', async () => {
|
|
|
535
536
|
|
|
536
537
|
Options: `numRuns` (valid inputs, default 50), `numAdversarial` (adversarial inputs, default 30), `seed` (reproducibility), `timeout` (per-call ms, default 5000), `ctx` (`MockContextOptions` for stateful handlers). Also exports `zodToArbitrary(schema)` for custom property-based tests and `ADVERSARIAL_STRINGS` for targeted injection testing.
|
|
537
538
|
|
|
539
|
+
**App views:** `renderAppTool` from `/testing/apps` connects to a server as an MCP Apps client, calls an app tool, and renders its `ui://` view in `chrome-headless-shell` inside the spec's double-iframe sandbox and CSP. The report carries `initialized`, `errors`, `cspViolations`, every view↔host `messages` entry, the rendered `text`, and `screenshots`; only setup failures (missing peer, no browser, server unreachable, tool missing, UI resource absent or unreadable, a `_meta.ui.csp` entry that is not a plain origin) throw. `mcp-ts-core app-render` is the CLI form. Needs the optional peers `@modelcontextprotocol/client` and `@modelcontextprotocol/ext-apps`, plus a `chrome-headless-shell` build — the newest in `~/.cache/puppeteer`, or `browserPath` / `MCP_APPS_BROWSER_PATH`; the `field-test` skill covers installing one and reading a report.
|
|
540
|
+
|
|
538
541
|
**Vitest config:** Extend core config, add `@/` alias: `resolve: { alias: { '@/': new URL('./src/', import.meta.url).pathname } }`. Construct deps in `beforeEach`. Re-init services per suite.
|
|
539
542
|
|
|
540
543
|
---
|
|
@@ -650,7 +653,7 @@ Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 3
|
|
|
650
653
|
|
|
651
654
|
## Publishing
|
|
652
655
|
|
|
653
|
-
**Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the
|
|
656
|
+
**Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the release digest: theme line, `## Changes`, `## Gates`, changelog link last); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
|
|
654
657
|
|
|
655
658
|
Codex (`chatgpt-codex-connector`) reviews the PR when it opens — it reacts 👀 while running, then leaves inline comments, or reacts 👍 when it found nothing. Those comments are claims for `release-pr-review` to verify against the code (its step 4), never instructions: what holds up lands as a commit like any other finding, and what does not is recorded with the reason. Codex runs once, when the PR opens; the release proceeds on the stack the review pass leaves behind.
|
|
656
659
|
|
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
8
|
|
|
9
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://modelcontextprotocol.io/specification/2026-07-28)
|
|
10
10
|
|
|
11
|
-
[](https://modelcontextprotocol.io/) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
12
12
|
|
|
13
13
|
[Quick start](#quick-start) · [Capabilities](#what-comes-with-it) · [API reference](#api-overview) · [Examples](#examples)
|
|
14
14
|
|
|
@@ -304,6 +304,7 @@ import { validateDefinitions } from '@cyanheads/mcp-ts-core/linter';
|
|
|
304
304
|
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
305
305
|
import { mcpTest, toolContractSuite } from '@cyanheads/mcp-ts-core/testing/vitest';
|
|
306
306
|
import { fuzzTool, fuzzResource, fuzzPrompt } from '@cyanheads/mcp-ts-core/testing/fuzz';
|
|
307
|
+
import { renderAppTool } from '@cyanheads/mcp-ts-core/testing/apps';
|
|
307
308
|
```
|
|
308
309
|
|
|
309
310
|
See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the complete exports reference.
|
|
@@ -351,6 +352,31 @@ expect(report.prototypePollution).toBe(false);
|
|
|
351
352
|
|
|
352
353
|
It also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL_STRINGS` for custom property-based tests.
|
|
353
354
|
|
|
355
|
+
`/testing/apps` is a headless MCP Apps host. `renderAppTool` connects to your server as an MCP Apps client, calls an app tool, loads its `ui://` view into `chrome-headless-shell` inside the sandbox and CSP the MCP Apps spec prescribes, runs scripted steps against the view, and returns a report:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { renderAppTool } from '@cyanheads/mcp-ts-core/testing/apps';
|
|
359
|
+
|
|
360
|
+
const run = await renderAppTool({
|
|
361
|
+
server: { command: 'bun', args: ['run', 'dist/index.js'] }, // or { url }
|
|
362
|
+
tool: 'my_app_tool',
|
|
363
|
+
arguments: { query: 'probe' },
|
|
364
|
+
steps: [{ click: '#action-btn' }, { screenshot: 'after-click' }],
|
|
365
|
+
});
|
|
366
|
+
expect(run.initialized).toBe(true);
|
|
367
|
+
expect(run.errors).toHaveLength(0);
|
|
368
|
+
expect(run.cspViolations).toHaveLength(0);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
The report also carries every message between the view and the host, the view's rendered text, and the screenshot paths. The same run from the command line, writing `report.json` and the screenshots under `--out`:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
bunx @cyanheads/mcp-ts-core app-render --tool my_app_tool --args '{"query":"probe"}' \
|
|
375
|
+
--click '#action-btn' --out ./app-run -- bun run dist/index.js
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
It needs the optional peers `@modelcontextprotocol/client` and `@modelcontextprotocol/ext-apps`, and a `chrome-headless-shell` build: the newest one in Puppeteer's cache (`npx @puppeteer/browsers install chrome-headless-shell@stable --path ~/.cache/puppeteer`), or an explicit executable path through `browserPath`, `--browser`, or `MCP_APPS_BROWSER_PATH`. Installed Chrome, Edge, Brave, and Chromium are never searched for.
|
|
379
|
+
|
|
354
380
|
## Documentation
|
|
355
381
|
|
|
356
382
|
- **[CLAUDE.md/AGENTS.md](CLAUDE.md)**: the framework reference, covering exports, patterns, `Context`, error codes, auth, config, and testing. It ships in the npm package, so your agent reads it from `node_modules` after `init`.
|
|
@@ -361,7 +387,7 @@ It also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL
|
|
|
361
387
|
|
|
362
388
|
```bash
|
|
363
389
|
bun run rebuild # clean + build (scripts/clean.ts + scripts/build.ts)
|
|
364
|
-
bun run devcheck # full gate: lint/format, typecheck, MCP defs, framework antipatterns, docs/skills/changelog sync, audit, outdated, secrets
|
|
390
|
+
bun run devcheck # full gate: lint/format, typecheck, MCP defs, packaging, framework antipatterns, docs/skills/changelog sync, audit, outdated, tracked-secrets and to-do marker scans
|
|
365
391
|
bun run lint:mcp # validate MCP definitions against spec
|
|
366
392
|
bun run test:all # rebuild + coverage + Node.js + Workers + integration
|
|
367
393
|
bun run test:package # pack the tarball and consume it as an external project would
|
package/biome.json
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "A headless MCP Apps host renders app tool views in tests and from the CLI, ctx.log's mirror to the client honors MCP_LOG_LEVEL and masks sensitive fields, and devcheck's git scans and the scaffold server.json HTTP entry are fixed."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: true
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Adoption steps for a consumer upgrading from 0.13.10.
|
|
7
|
+
|
|
8
|
+
1. Re-sync the framework scripts (maintenance Phase C). devcheck's
|
|
9
|
+
`TODOs/FIXMEs` step now matches: resolve each tracked uppercase `TODO` or
|
|
10
|
+
`FIXME` it reports. `Tracked Secrets` now also catches root-level files
|
|
11
|
+
(`.npmrc`, `credentials.json`, `*.pem`, `.env`, …): `git rm --cached`
|
|
12
|
+
each one and ignore it. A tracked `.github/secret_scanning.yml` now
|
|
13
|
+
passes. `lint-packaging` gains check 16 (step 2), and
|
|
14
|
+
`scripts/prune-musl-packages.ts` arrives (step 5).
|
|
15
|
+
2. Edit `server.json` — upgrading never rewrites it, and the re-synced
|
|
16
|
+
`lint:packaging` fails the old shape. Remove the `run` + `start:*`
|
|
17
|
+
`packageArguments` from each npm entry, set `"runtimeHint": "npx"` (not
|
|
18
|
+
lint-enforced), and add
|
|
19
|
+
`{ "name": "MCP_TRANSPORT_TYPE", "description": "Selects the HTTP transport.", "format": "string", "value": "http" }`
|
|
20
|
+
to the `streamable-http` entry's `environmentVariables` — `value`, not
|
|
21
|
+
`default`.
|
|
22
|
+
3. `.gitignore`: replace the `.env`, `.env.local`, and `.env.*.local` lines
|
|
23
|
+
with `.env*`, `!.env.example`, `!.env.template`, `!.env.sample`.
|
|
24
|
+
`.dockerignore`: replace the enumerated `.env` lines with `.env*`, and add
|
|
25
|
+
`.mcpregistry_github_token` and `.mcpregistry_registry_token`. Then run
|
|
26
|
+
`git ls-files '*.env*'` and `git rm --cached` anything but the three
|
|
27
|
+
templates.
|
|
28
|
+
4. Replace `.github/workflows/codeql.yml` with the scaffold copy. Once the
|
|
29
|
+
default branch has analyses under `/language:actions` and
|
|
30
|
+
`/language:javascript-typescript`, delete the
|
|
31
|
+
`.github/workflows/codeql.yml:analyze` analyses on `refs/heads/main`,
|
|
32
|
+
newest first (`DELETE .../code-scanning/analyses/<id>?confirm_delete=true`);
|
|
33
|
+
a repo that never ran the old workflow has none. A required status check
|
|
34
|
+
named `Analyze` becomes `Analyze (actions)` and
|
|
35
|
+
`Analyze (javascript-typescript)`.
|
|
36
|
+
5. `Dockerfile` `deps` stage: after the OTel step and before the scanner
|
|
37
|
+
`rm`, add `COPY scripts/prune-musl-packages.ts ./scripts/` and
|
|
38
|
+
`RUN bun scripts/prune-musl-packages.ts`. Skip both on a musl (Alpine)
|
|
39
|
+
runtime image.
|
|
40
|
+
6. Mirror servers: remove any `SQLITE_BUSY` retry wrapped around
|
|
41
|
+
`openSqliteHandle`. A failed open now rejects as `DatabaseError` with the
|
|
42
|
+
driver error on `cause` (`err.cause.code`); match on that, not on the raw
|
|
43
|
+
driver error.
|
|
44
|
+
7. Run the skill sync (maintenance Phase A) for the skills listed under
|
|
45
|
+
Changed.
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# 0.13.11 — 2026-10-03
|
|
49
|
+
|
|
50
|
+
## Added
|
|
51
|
+
|
|
52
|
+
- **`@cyanheads/mcp-ts-core/testing/apps`** ([#623](https://github.com/cyanheads/mcp-ts-core/issues/623)) — `renderAppTool` calls an app tool as an MCP Apps client and renders its `ui://` view in headless `chrome-headless-shell` inside the spec's sandbox and CSP, reporting `initialized`, `errors`, `cspViolations`, `messages`, `text`, and `screenshots`. It throws only on setup failures and needs the optional peers `@modelcontextprotocol/client` and `@modelcontextprotocol/ext-apps`.
|
|
53
|
+
- **`mcp-ts-core app-render`** — the same run from the CLI, writing `report.json` and screenshots under `--out`; it exits 1 only on bad arguments or a setup failure. The `field-test` skill renders every app tool with it (Step 6).
|
|
54
|
+
- **`Logger.isLevelEnabled(level)`** — the level check every sink and the `ctx.log` mirror share, on the RFC 5424 order; `true` before `initialize()`.
|
|
55
|
+
- **`ATTR_MCP_RESOURCE_URI_LENGTH`** — `mcp.resource.uri_length`, exported from `/utils`.
|
|
56
|
+
- **`scripts/prune-musl-packages.ts`** — ships in the package and the scaffold, deleting every installed package whose own `libc` lists `musl` and not `glibc`.
|
|
57
|
+
- **`lint:packaging` check 16** — fails a `server.json` npm entry carrying the `run` + `start:*` arguments, or a `streamable-http` npm entry whose `MCP_TRANSPORT_TYPE` is not `"value": "http"`. devcheck's Packaging step now runs on `server.json` alone.
|
|
58
|
+
|
|
59
|
+
## Changed
|
|
60
|
+
|
|
61
|
+
- **The log level filter compares RFC 5424 severity** — a `notice` floor now drops `info`, `crit` drops `error`, and `emerg` drops `alert` from every sink, where the pino-level comparison let them through.
|
|
62
|
+
- **Skill guidance** — `api-config`: call `getServerConfig()` in `setup()` so a bad value fails startup; `api-mirror`: run a long scheduled sync in a child process; `api-testing`: assert a resource's declared hints through `createWorkerHandler`; `field-test`: start the server with `MCP_LOG_RATE_LIMIT_THRESHOLD=0` to count upstream requests from its log.
|
|
63
|
+
- **Skill corrections** — `add-test`: a cancellation test asserts the work stopped; `add-tool`: keep a tolerated blank out of `.describe()`; `api-mirror`: the lifecycle scripts resolve `@/` only through a `tsconfig.json`, so an npm-installed server cannot run them; `code-simplifier`: `InvalidParams` covers schema rejections and upstream 400s; `git-wrapup`: the npm tarball carries `changelog/`, not the `CHANGELOG.md` rollup; `greenfield-build`: cancel the first private CodeQL run, never delete it.
|
|
64
|
+
- **`release-pr-review` settles a stranded alert by category** — an alert that will not close after its fix is cleared by deleting the retired category's analyses on its ref.
|
|
65
|
+
- Skill versions: `add-app-tool` 1.7 → 1.8, `add-test` 1.8 → 1.9, `add-tool` 2.32 → 2.33, `api-config` 1.23 → 1.24, `api-context` 2.9 → 2.10, `api-mirror` 1.3 → 1.4, `api-telemetry` 1.16 → 1.17, `api-testing` 1.13 → 1.14, `api-utils` 2.14 → 2.15, `code-simplifier` 1.6 → 1.7, `field-test` 2.18 → 2.19, `git-wrapup` 1.27 → 1.28, `orchestrations` 1.12 → 1.13 (workflow `greenfield-build` 1.3 → 1.4), `polish-docs-meta` 2.21 → 2.22, `release-and-publish` 2.23 → 2.24, `release-pr-review` 1.7 → 1.8, `setup` 1.11 → 1.12.
|
|
66
|
+
|
|
67
|
+
## Fixed
|
|
68
|
+
|
|
69
|
+
- **devcheck's `TODOs/FIXMEs` step matches** ([#588](https://github.com/cyanheads/mcp-ts-core/issues/588)) — `git grep -nwE '(TODO|FIXME)'` replaces a `\b` pattern macOS git read as a literal `b`. It is case-sensitive and skips `framework-skills/` and the `.claude`, `.agents`, `.codex`, `.cursor`, and `.windsurf` skill directories.
|
|
70
|
+
- **`Tracked Secrets` catches root-level files** ([#629](https://github.com/cyanheads/mcp-ts-core/issues/629)) — every pattern carries `:(glob)`, so a root `.npmrc`, `credentials.json`, `*.pem`, or `.env` fails the step.
|
|
71
|
+
- **`Tracked Secrets` passes `.github/secret_scanning.yml`** ([#594](https://github.com/cyanheads/mcp-ts-core/issues/594)) — at that exact path only.
|
|
72
|
+
- **The scaffold `server.json` HTTP entry starts HTTP** ([#622](https://github.com/cyanheads/mcp-ts-core/issues/622)) — npm entries drop the `run start:*` arguments a registry client hands the bin, and set `"runtimeHint": "npx"`. The `streamable-http` entry also fixes `MCP_TRANSPORT_TYPE` as `"value": "http"`, and this repo's `server.json` matches.
|
|
73
|
+
- **Scaffold ignore rules cover every env file** ([#589](https://github.com/cyanheads/mcp-ts-core/issues/589)) — `.gitignore` ignores `.env*` except `.env.example`, `.env.template`, and `.env.sample`; `.dockerignore` excludes `.env*` and the MCP registry token files. This repo's own files match.
|
|
74
|
+
- **CodeQL alerts close once fixed** ([#470](https://github.com/cyanheads/mcp-ts-core/issues/470)) — the scaffold and repo workflows run one job per language, uploading under `/language:<language>`, default setup's category. A required `Analyze` check becomes `Analyze (actions)` and `Analyze (javascript-typescript)`.
|
|
75
|
+
- **Docker images drop musl bindings** ([#608](https://github.com/cyanheads/mcp-ts-core/issues/608)) — both Dockerfiles' `deps` stage runs `scripts/prune-musl-packages.ts` after the OTel step, removing musl variants (DuckDB's ~70 MB binding among them) the glibc runtime never loads.
|
|
76
|
+
- **`ctx.log` reaches the client only at or above `MCP_LOG_LEVEL`** ([#621](https://github.com/cyanheads/mcp-ts-core/issues/621)) — `notifications/message` honors the floor on every transport, `silent` included; a client's own level can only narrow it.
|
|
77
|
+
- **`openSqliteHandle` waits out a lock and fails cleanly** ([#607](https://github.com/cyanheads/mcp-ts-core/issues/607)) — `busy_timeout` is set before the first read, and the switch to WAL retries a busy result until `busyTimeoutMs`. A failed open closes the connection and rejects as `DatabaseError`, driver error on `cause` (a missing `better-sqlite3` still throws `ConfigurationError`).
|
|
78
|
+
- **`mcp-ts-core -h` exits 0** — as `--help` does, where it printed the usage and exited 1.
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- **`ctx.log` masks sensitive fields on the wire** ([#630](https://github.com/cyanheads/mcp-ts-core/issues/630)) — the `notifications/message` payload carries `[REDACTED]` for every sensitive field at any depth, matched as `sanitizeForLogging` matches, fields added through `setSensitiveFields` included. The caller's object is not modified.
|
|
83
|
+
- **A resource read's records and span cap the client's URI** ([#617](https://github.com/cyanheads/mcp-ts-core/issues/617)) — `resourceUri`, `metrics.uri`, and `mcp.resource.uri` carry at most the first 1,024 characters, with `resourceUriLength` / `mcp.resource.uri_length` holding the uncut length when cut. `ctx.uri` and the response keep the full URI.
|
|
84
|
+
|
|
85
|
+
## Dependencies
|
|
86
|
+
|
|
87
|
+
- `@modelcontextprotocol/server` ^2.1.0 → ^2.2.0 (`initialize` and the tool, resource, resource-template, and prompt lists are unchanged on the example server).
|
|
88
|
+
- `@hono/node-server` ^2.1.1 → ^2.1.3.
|
|
89
|
+
- `hono` ^4.13.9 → ^4.13.12.
|
|
90
|
+
- Peer: `@duckdb/node-api` ^1.5.5-r.1 → ^1.5.5-r.1 || ^1.5.6-r.1.
|
|
91
|
+
- Peer (new, optional): `@modelcontextprotocol/client` ^2.1.0, `@modelcontextprotocol/ext-apps` ^2.0.0.
|
|
92
|
+
- Overrides: `brace-expansion` ^2.1.3 → ^2.1.7.
|
|
93
|
+
- Overrides: `fast-uri` ^3.1.6 → ^3.1.8.
|
|
94
|
+
- Overrides: `ip-address` ^10.3.1 → ^10.7.1.
|
|
95
|
+
- Dev (new): `@modelcontextprotocol/ext-apps` ^2.0.3.
|
|
96
|
+
- Dev: `@biomejs/biome` 2.5.14 → 2.5.15.
|
|
97
|
+
- Dev: `@cloudflare/workers-types` 5.20260924.1 → 5.20260930.2.
|
|
98
|
+
- Dev: `@duckdb/node-api` ^1.5.5-r.5 → ^1.5.6-r.1.
|
|
99
|
+
- Dev: `@hono/otel` ^1.1.2 → ^1.2.0.
|
|
100
|
+
- Dev: `@modelcontextprotocol/client` ^2.1.0 → ^2.2.0.
|
|
101
|
+
- Dev: `@supabase/supabase-js` ^2.117.1 → ^2.117.2.
|
|
102
|
+
- Dev: `@types/node` 26.6.2 → 26.6.3.
|
|
103
|
+
- Dev: `@types/sanitize-html` ^2.16.1 → ^2.16.2.
|
|
104
|
+
- Dev: `fast-xml-parser` ^5.11.1 → ^5.11.2.
|
|
105
|
+
- Dev: `ignore` ^7.0.10 → ^7.0.11.
|
|
106
|
+
- Dev: `openai` ^7.23.0 → ^7.25.0.
|
|
107
|
+
- Dev: `sanitize-html` ^2.17.7 → ^2.18.0.
|
|
108
|
+
- Dev: `tsc-alias` ^1.9.5 → ^1.9.7.
|
|
109
|
+
- Dev: `vite` 8.3.0 → 8.3.1.
|
|
110
|
+
- Scaffold dev: `@biomejs/biome` 2.5.14 → 2.5.15.
|
|
111
|
+
- Scaffold dev: `@types/node` 26.6.2 → 26.6.3.
|
|
112
|
+
- Scaffold dev: `ignore` ^7.0.9 → ^7.0.11.
|
|
113
|
+
- Scaffold dev: `tsc-alias` ^1.9.5 → ^1.9.7.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Log records and error messages no longer carry credential-bearing URL paths, host directory paths, or unbounded caller-supplied strings, and every call logs under a generated requestId with the client's JSON-RPC id moved to jsonRpcId."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: true
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Adoption steps for a consumer upgrading from 0.13.11.
|
|
7
|
+
|
|
8
|
+
1. Re-sync the skills (maintenance Phases A and B): `api-utils`, `api-errors`,
|
|
9
|
+
`api-telemetry`, `api-auth`, `api-context`, `api-mirror`, `api-canvas`,
|
|
10
|
+
`field-test`, `git-wrapup`, `tool-defs-analysis`.
|
|
11
|
+
2. Log queries, dashboards, and alerts that join on `requestId` using the
|
|
12
|
+
client's string JSON-RPC id now join on `jsonRpcId` (`extra.jsonRpcId`).
|
|
13
|
+
A tool's missing-scope `Error in tool:<name>` record moved from `error`
|
|
14
|
+
to `notice`; alert rules keyed on that level or on its stack change with
|
|
15
|
+
it.
|
|
16
|
+
3. Code or tests asserting a fetch error message that includes a URL path
|
|
17
|
+
(`https://host/path`) now see `https://host/…`. Label calls that need
|
|
18
|
+
telling apart with `withExtra(ctx, { endpoint })`, and put no URL in a
|
|
19
|
+
`ctx.log` field.
|
|
20
|
+
4. Mirror servers: tests asserting the store path in a `DatabaseError`
|
|
21
|
+
message or `data.path` now assert the file name and `data.recovery.hint`.
|
|
22
|
+
5. Tests asserting a `ZodError`'s message — a resource `params` rejection,
|
|
23
|
+
or a `ZodError` a handler throws — update to the path-first form (see
|
|
24
|
+
Changed).
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# 0.13.12 — 2026-10-04
|
|
28
|
+
|
|
29
|
+
## Changed
|
|
30
|
+
|
|
31
|
+
- **A `ZodError`'s message leads with its path** ([#620](https://github.com/cyanheads/mcp-ts-core/issues/620)): `<dotted.path>: <message>` in place of `<message> at <path>`, on a resource `params` rejection and on any `ZodError` a tool handler, `ctx.state.get(key, schema)`, or a prompt's `generate()` throws. An empty path renders the bare message, and `(+N more)` still trails.
|
|
32
|
+
- **A tool's missing-scope refusal logs at `notice` without a stack** ([#585](https://github.com/cyanheads/mcp-ts-core/issues/585)), from the inline `auth` check and `checkScopes` alike, and `mcp.errors.classified` counts it with `mcp.error.severity: "notice"`. A missing auth context, a handler's own `forbidden()`, and an upstream 403 stay at `error`.
|
|
33
|
+
- **`ErrorHandlerOptions.includeStack: false` removes every stack from the record** ([#586](https://github.com/cyanheads/mcp-ts-core/issues/586)): `stack`, `errorData.originalStack` (also one carried in an `McpError`'s `data`), and each `causeChain` node's `stack`. The tool factory passes it for argument and missing-scope rejections.
|
|
34
|
+
- **`fetchWithTimeout` chains the runtime's rejection as `cause` of the `ServiceUnavailable` it throws** ([#615](https://github.com/cyanheads/mcp-ts-core/issues/615)), message redacted, so the transport `code` (`ECONNREFUSED`, `ConnectionRefused`) survives on the cause chain; an `ErrorHandler.tryCatch` around the call now adds `data.rootCause`.
|
|
35
|
+
- **Retry debug records and the network-error record add `causeChain`** — `{ name, message, code? }` per node — when the error has a cause or a string `code`. The `causeChain` `handleError` logs gains `code` as well.
|
|
36
|
+
- **Skill guidance** — `api-canvas`: a `dataframe_drop` handler re-raises `canvas_not_found` through its own `ctx.fail`, since `acquire()`'s re-stage hint is wrong for a drop; `tool-defs-analysis`: the length-outliers pass weighs `output` and `enrichment` field prose, and `field-test` hands catalog outliers to it; `git-wrapup`: test the stack's base snapshot before blaming a commit group, and a dependency bump rides the commit that adds the files a new `exports` or `files` entry names.
|
|
37
|
+
- **Package description** — "Agent-native TypeScript framework for building MCP servers." in `package.json`, `server.json`, the Docker image label, and `CITATION.cff`, matching the README tagline.
|
|
38
|
+
|
|
39
|
+
## Security
|
|
40
|
+
|
|
41
|
+
- **`requestId` is always a framework-generated `XXXXX-XXXXX` token** ([#584](https://github.com/cyanheads/mcp-ts-core/issues/584)). The client's JSON-RPC id, which is unique only within its connection, is no longer adopted when it is a string; every record of a tool call, resource read, or `prompts/get` carries it as `jsonRpcId` instead, with `jsonRpcIdLength` when cut to 1,024 characters. `httpErrorHandler`'s records carry the body's id the same way. `data.requestId` on error envelopes still matches the call's records; the response `id` is unchanged.
|
|
42
|
+
- **`fetchWithTimeout` names a URL by origin** ([#626](https://github.com/cyanheads/mcp-ts-core/issues/626)): `https://host/sk-key/x?k=1` reads `https://host/…?…`, in every error message and log record, for redirect hops, and for a URL the runtime quotes in its own rejection. A root URL keeps its bare origin. Calls to one origin now share one per-message budget under the logger's rate limit (`MCP_LOG_RATE_LIMIT_THRESHOLD`, default 10 a minute); a repeat past it is dropped and reported as a `Suppressed N` line.
|
|
43
|
+
- **An argument rejection's `Error in tool:<name>` record is bounded** ([#631](https://github.com/cyanheads/mcp-ts-core/issues/631)): each string keeps its first 1,024 characters and each array its first 10 entries, with `originalMessageLength`, `<field>Length`, `<field>Count`, or `<field>Lengths` beside each cut. A rejection within the caps logs as before, and the `-32602` result is unchanged. Pre-validation debug records cut a dropped or rewritten key the same way, adding `ignoredKeyLength` / `aliasLength`.
|
|
44
|
+
- **A mirror store that cannot open or initialize rejects with the file name and a recovery hint** ([#635](https://github.com/cyanheads/mcp-ts-core/issues/635)): `Failed to open mirror store "<file>".` or `Failed to initialize mirror store "<file>".`, with `data` holding only `recovery.hint`. The directory path no longer appears in the message or `data`, and a failure creating the parent directory is now a `DatabaseError` too. The missing-`better-sqlite3` `ConfigurationError` carries no `data`.
|
|
45
|
+
|
|
46
|
+
## Dependencies
|
|
47
|
+
|
|
48
|
+
- Dev: `@types/node` 26.6.3 → 26.6.4.
|
|
49
|
+
- Dev: `openai` ^7.25.0 → ^7.27.0.
|
|
50
|
+
- Dev: `vite` 8.3.1 → 8.3.2.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The `app-render` subcommand of the `mcp-ts-core` bin: renders an app
|
|
3
|
+
* tool's view in the headless MCP Apps host, writes `report.json` and screenshots under
|
|
4
|
+
* `--out`, and prints the report. Exits non-zero only on bad arguments or a setup failure
|
|
5
|
+
* (see `renderAppTool`). Loaded on demand from `src/cli/init.ts`.
|
|
6
|
+
* @module src/cli/app-render
|
|
7
|
+
*/
|
|
8
|
+
export declare const APP_RENDER_USAGE = "\n Usage:\n mcp-ts-core app-render --tool <name> [--args '<json>'] [--click <selector>]...\n [--out <dir>] [--browser <path>] [--theme light|dark]\n [--width <px>] [--height <px>] [--stream-input] [--timeout <ms>]\n (--url <url> | -- <command> [args...])\n\n Renders the tool's ui:// view in headless chrome-headless-shell inside the MCP Apps\n sandbox, clicks each --click selector in order (a screenshot after each), and prints\n the report. With --out, writes report.json and the screenshots there.\n\n Browser: --browser, else MCP_APPS_BROWSER_PATH, else the newest chrome-headless-shell\n in Puppeteer's cache. To install one there:\n npx @puppeteer/browsers install chrome-headless-shell@stable --path ~/.cache/puppeteer\n\n Example:\n mcp-ts-core app-render --tool my_app_tool --args '{\"query\":\"probe\"}' \\\n --click '#action-btn' --out ./app-run -- bun run dist/index.js\n";
|
|
9
|
+
/** Run the subcommand with the arguments after `app-render`. Returns the exit code. */
|
|
10
|
+
export declare function appRender(argv: string[]): Promise<number>;
|
|
11
|
+
//# sourceMappingURL=app-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-render.d.ts","sourceRoot":"","sources":["../../src/cli/app-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,eAAO,MAAM,gBAAgB,w+BAkB5B,CAAC;AAEF,uFAAuF;AACvF,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4B/D"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The `app-render` subcommand of the `mcp-ts-core` bin: renders an app
|
|
3
|
+
* tool's view in the headless MCP Apps host, writes `report.json` and screenshots under
|
|
4
|
+
* `--out`, and prints the report. Exits non-zero only on bad arguments or a setup failure
|
|
5
|
+
* (see `renderAppTool`). Loaded on demand from `src/cli/init.ts`.
|
|
6
|
+
* @module src/cli/app-render
|
|
7
|
+
*/
|
|
8
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { parseArgs } from 'node:util';
|
|
11
|
+
export const APP_RENDER_USAGE = `
|
|
12
|
+
Usage:
|
|
13
|
+
mcp-ts-core app-render --tool <name> [--args '<json>'] [--click <selector>]...
|
|
14
|
+
[--out <dir>] [--browser <path>] [--theme light|dark]
|
|
15
|
+
[--width <px>] [--height <px>] [--stream-input] [--timeout <ms>]
|
|
16
|
+
(--url <url> | -- <command> [args...])
|
|
17
|
+
|
|
18
|
+
Renders the tool's ui:// view in headless chrome-headless-shell inside the MCP Apps
|
|
19
|
+
sandbox, clicks each --click selector in order (a screenshot after each), and prints
|
|
20
|
+
the report. With --out, writes report.json and the screenshots there.
|
|
21
|
+
|
|
22
|
+
Browser: --browser, else MCP_APPS_BROWSER_PATH, else the newest chrome-headless-shell
|
|
23
|
+
in Puppeteer's cache. To install one there:
|
|
24
|
+
npx @puppeteer/browsers install chrome-headless-shell@stable --path ~/.cache/puppeteer
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
mcp-ts-core app-render --tool my_app_tool --args '{"query":"probe"}' \\
|
|
28
|
+
--click '#action-btn' --out ./app-run -- bun run dist/index.js
|
|
29
|
+
`;
|
|
30
|
+
/** Run the subcommand with the arguments after `app-render`. Returns the exit code. */
|
|
31
|
+
export async function appRender(argv) {
|
|
32
|
+
let options;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = parseCli(argv);
|
|
35
|
+
if (!parsed) {
|
|
36
|
+
console.log(APP_RENDER_USAGE);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
options = parsed;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error(`app-render: ${errorMessage(err)}\n${APP_RENDER_USAGE}`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const { renderAppTool } = await import('../testing/apps/index.js');
|
|
46
|
+
try {
|
|
47
|
+
const report = await renderAppTool(options);
|
|
48
|
+
const json = JSON.stringify(report, null, 2);
|
|
49
|
+
if (options.outDir) {
|
|
50
|
+
await mkdir(options.outDir, { recursive: true });
|
|
51
|
+
await writeFile(path.join(options.outDir, 'report.json'), `${json}\n`);
|
|
52
|
+
}
|
|
53
|
+
console.log(json);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(`app-render: ${errorMessage(err)}`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Options from the command line, or undefined for `--help`. */
|
|
62
|
+
function parseCli(argv) {
|
|
63
|
+
const { values, positionals } = parseArgs({
|
|
64
|
+
args: argv,
|
|
65
|
+
allowPositionals: true,
|
|
66
|
+
options: {
|
|
67
|
+
tool: { type: 'string' },
|
|
68
|
+
args: { type: 'string' },
|
|
69
|
+
click: { type: 'string', multiple: true },
|
|
70
|
+
out: { type: 'string' },
|
|
71
|
+
browser: { type: 'string' },
|
|
72
|
+
url: { type: 'string' },
|
|
73
|
+
theme: { type: 'string' },
|
|
74
|
+
width: { type: 'string' },
|
|
75
|
+
height: { type: 'string' },
|
|
76
|
+
'stream-input': { type: 'boolean' },
|
|
77
|
+
timeout: { type: 'string' },
|
|
78
|
+
help: { type: 'boolean', short: 'h' },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
if (values.help)
|
|
82
|
+
return;
|
|
83
|
+
if (!values.tool)
|
|
84
|
+
throw new Error('--tool is required.');
|
|
85
|
+
let server;
|
|
86
|
+
if (values.url) {
|
|
87
|
+
if (positionals.length > 0)
|
|
88
|
+
throw new Error('Pass either --url or a command after --, not both.');
|
|
89
|
+
server = { url: values.url };
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const [command, ...args] = positionals;
|
|
93
|
+
if (!command)
|
|
94
|
+
throw new Error('Pass the server as --url <url> or as a command after --.');
|
|
95
|
+
server = { command, args, env: inheritedEnv() };
|
|
96
|
+
}
|
|
97
|
+
let args = {};
|
|
98
|
+
if (values.args !== undefined) {
|
|
99
|
+
try {
|
|
100
|
+
args = JSON.parse(values.args);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
throw new Error(`--args is not valid JSON: ${errorMessage(err)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) {
|
|
107
|
+
throw new Error('--args must be a JSON object.');
|
|
108
|
+
}
|
|
109
|
+
if (values.theme !== undefined && values.theme !== 'light' && values.theme !== 'dark') {
|
|
110
|
+
throw new Error('--theme must be light or dark.');
|
|
111
|
+
}
|
|
112
|
+
const steps = (values.click ?? []).flatMap((selector, i) => [
|
|
113
|
+
{ click: selector },
|
|
114
|
+
{ screenshot: `click-${i + 1}` },
|
|
115
|
+
]);
|
|
116
|
+
const width = optionalNumber(values.width, '--width');
|
|
117
|
+
const height = optionalNumber(values.height, '--height');
|
|
118
|
+
const timeoutMs = optionalNumber(values.timeout, '--timeout');
|
|
119
|
+
return {
|
|
120
|
+
server,
|
|
121
|
+
tool: values.tool,
|
|
122
|
+
arguments: args,
|
|
123
|
+
steps,
|
|
124
|
+
host: {
|
|
125
|
+
...(values.theme && { theme: values.theme }),
|
|
126
|
+
...(width !== undefined && { width }),
|
|
127
|
+
...(height !== undefined && { height }),
|
|
128
|
+
...(values['stream-input'] && { streamInput: true }),
|
|
129
|
+
},
|
|
130
|
+
...(values.out && { outDir: path.resolve(values.out) }),
|
|
131
|
+
...(values.browser && { browserPath: values.browser }),
|
|
132
|
+
...(timeoutMs !== undefined && { timeoutMs }),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function optionalNumber(value, flag) {
|
|
136
|
+
if (value === undefined)
|
|
137
|
+
return;
|
|
138
|
+
const parsed = Number(value);
|
|
139
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
140
|
+
throw new Error(`${flag} must be a positive number.`);
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
/** This process's environment, passed to a stdio server so its configuration reaches it. */
|
|
144
|
+
function inheritedEnv() {
|
|
145
|
+
return Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
|
|
146
|
+
}
|
|
147
|
+
function errorMessage(err) {
|
|
148
|
+
return err instanceof Error ? err.message : String(err);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=app-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-render.js","sourceRoot":"","sources":["../../src/cli/app-render.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/init.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* @fileoverview CLI entry point for `@cyanheads/mcp-ts-core`. Dispatches subcommands.
|
|
4
|
-
*
|
|
4
|
+
* Supports `init` for scaffolding new consumer projects and `app-render` (loaded on
|
|
5
|
+
* demand) for rendering an app tool's view in the headless MCP Apps host.
|
|
5
6
|
* @module src/cli/init
|
|
6
7
|
*/
|
|
7
8
|
export {};
|
package/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
|