@cyanheads/mcp-ts-core 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/AGENTS.md +15 -9
  2. package/CLAUDE.md +15 -9
  3. package/README.md +5 -6
  4. package/changelog/0.10.x/0.10.2.md +35 -0
  5. package/changelog/0.10.x/0.10.3.md +46 -0
  6. package/dist/core/app.d.ts +36 -0
  7. package/dist/core/app.d.ts.map +1 -1
  8. package/dist/core/app.js +9 -4
  9. package/dist/core/app.js.map +1 -1
  10. package/dist/core/context.d.ts +57 -15
  11. package/dist/core/context.d.ts.map +1 -1
  12. package/dist/core/context.js +12 -1
  13. package/dist/core/context.js.map +1 -1
  14. package/dist/core/index.d.ts +5 -2
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/index.js +1 -0
  17. package/dist/core/index.js.map +1 -1
  18. package/dist/core/serverManifest.d.ts +18 -0
  19. package/dist/core/serverManifest.d.ts.map +1 -1
  20. package/dist/core/serverManifest.js +10 -2
  21. package/dist/core/serverManifest.js.map +1 -1
  22. package/dist/linter/rules/enrichment-rules.d.ts +20 -0
  23. package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
  24. package/dist/linter/rules/enrichment-rules.js +74 -9
  25. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  26. package/dist/linter/rules/index.d.ts +2 -2
  27. package/dist/linter/rules/index.d.ts.map +1 -1
  28. package/dist/linter/rules/index.js +2 -2
  29. package/dist/linter/rules/index.js.map +1 -1
  30. package/dist/linter/rules/schema-rules.d.ts +4 -0
  31. package/dist/linter/rules/schema-rules.d.ts.map +1 -1
  32. package/dist/linter/rules/schema-rules.js +13 -0
  33. package/dist/linter/rules/schema-rules.js.map +1 -1
  34. package/dist/linter/rules/tool-rules.d.ts +12 -0
  35. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  36. package/dist/linter/rules/tool-rules.js +48 -1
  37. package/dist/linter/rules/tool-rules.js.map +1 -1
  38. package/dist/linter/types.d.ts +17 -0
  39. package/dist/linter/types.d.ts.map +1 -1
  40. package/dist/linter/validate.d.ts.map +1 -1
  41. package/dist/linter/validate.js +55 -1
  42. package/dist/linter/validate.js.map +1 -1
  43. package/dist/logs/combined.log +4 -0
  44. package/dist/logs/error.log +2 -0
  45. package/dist/logs/interactions.log +0 -0
  46. package/dist/mcp-server/notifications.d.ts +4 -4
  47. package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
  48. package/dist/mcp-server/prompts/prompt-registration.js +1 -0
  49. package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
  50. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts +6 -0
  51. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts.map +1 -1
  52. package/dist/mcp-server/prompts/utils/promptDefinition.js.map +1 -1
  53. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  54. package/dist/mcp-server/resources/resource-registration.js +3 -0
  55. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  56. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +9 -0
  57. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -1
  58. package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -1
  59. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +13 -2
  60. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  61. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +23 -12
  62. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  63. package/dist/mcp-server/server.d.ts +18 -2
  64. package/dist/mcp-server/server.d.ts.map +1 -1
  65. package/dist/mcp-server/server.js +4 -1
  66. package/dist/mcp-server/server.js.map +1 -1
  67. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  68. package/dist/mcp-server/tools/tool-registration.js +2 -0
  69. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  70. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +14 -2
  71. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  72. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +37 -13
  73. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  74. package/dist/services/canvas/core/CanvasInstance.d.ts +1 -1
  75. package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
  76. package/dist/services/canvas/core/CanvasInstance.js +36 -11
  77. package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
  78. package/dist/services/canvas/core/CanvasRegistry.d.ts +50 -3
  79. package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
  80. package/dist/services/canvas/core/CanvasRegistry.js +163 -6
  81. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  82. package/dist/services/canvas/spillover.d.ts +6 -0
  83. package/dist/services/canvas/spillover.d.ts.map +1 -1
  84. package/dist/services/canvas/spillover.js +1 -0
  85. package/dist/services/canvas/spillover.js.map +1 -1
  86. package/dist/services/canvas/types.d.ts +21 -0
  87. package/dist/services/canvas/types.d.ts.map +1 -1
  88. package/dist/testing/index.d.ts +8 -14
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/index.js +10 -11
  91. package/dist/testing/index.js.map +1 -1
  92. package/package.json +7 -7
  93. package/skills/add-prompt/SKILL.md +33 -2
  94. package/skills/add-resource/SKILL.md +25 -1
  95. package/skills/add-service/SKILL.md +2 -2
  96. package/skills/add-test/SKILL.md +3 -3
  97. package/skills/add-tool/SKILL.md +38 -1
  98. package/skills/api-canvas/SKILL.md +23 -3
  99. package/skills/api-config/SKILL.md +12 -3
  100. package/skills/api-context/SKILL.md +57 -31
  101. package/skills/api-linter/SKILL.md +77 -3
  102. package/skills/api-mirror/SKILL.md +30 -1
  103. package/skills/api-testing/SKILL.md +2 -16
  104. package/skills/code-simplifier/SKILL.md +2 -2
  105. package/skills/design-mcp-server/SKILL.md +2 -1
  106. package/skills/polish-docs-meta/SKILL.md +1 -1
  107. package/skills/polish-docs-meta/references/agent-protocol.md +1 -1
  108. package/skills/report-issue-framework/SKILL.md +2 -2
  109. package/skills/security-pass/SKILL.md +6 -11
  110. package/templates/AGENTS.md +17 -5
  111. package/templates/CLAUDE.md +17 -5
  112. package/templates/Dockerfile +17 -0
  113. package/dist/mcp-server/roots/roots-registration.d.ts +0 -22
  114. package/dist/mcp-server/roots/roots-registration.d.ts.map +0 -1
  115. package/dist/mcp-server/roots/roots-registration.js +0 -25
  116. package/dist/mcp-server/roots/roots-registration.js.map +0 -1
@@ -4,7 +4,7 @@ description: >
4
4
  MCP definition linter rules reference. Use when `bun run lint:mcp` or `bun run devcheck` reports a lint error or warning (`format-parity`, `schema-is-object`, `name-format`, `server-json-*`, etc.) and you need to understand the rule, its severity, and how to fix it. Every rule ID the linter emits has an entry in this doc.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.6"
7
+ version: "1.7"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -46,14 +46,14 @@ Grouped by family. Jump to any rule ID via its anchor.
46
46
  | Schema | `schema-is-object`, `describe-on-fields`, `schema-serializable` | [Schema rules](#schema-rules) |
47
47
  | Portability | `schema-format-portability`, `schema-anyof-needs-type`, `schema-no-discriminator-keyword`, `schema-no-defs`, `schema-dialect-tag` | [Portability rules](#portability-rules) |
48
48
  | Names | `name-required`, `name-format`, `name-unique` | [Name rules](#name-rules) |
49
- | Tools | `description-required`, `handler-required`, `auth-type`, `auth-scope-format`, `annotation-type`, `annotation-coherence`, `meta-ui-type`, `meta-ui-resource-uri-required`, `meta-ui-resource-uri-scheme`, `app-tool-resource-pairing` | [Tool rules](#tool-rules) |
49
+ | Tools | `description-required`, `handler-required`, `auth-type`, `auth-scope-format`, `annotation-type`, `annotation-coherence`, `meta-ui-type`, `meta-ui-resource-uri-required`, `meta-ui-resource-uri-scheme`, `app-tool-resource-pairing`, `canvas-consumer-missing` | [Tool rules](#tool-rules) |
50
50
  | Resources | `uri-template-required`, `uri-template-valid`, `resource-name-not-uri`, `template-params-align` | [Resource rules](#resource-rules) |
51
51
  | Landing | `landing-*` (23 rules — shape, tagline, logo, links, repo, envExample, connectSnippets, theme) | [Landing config rules](#landing-config-rules) |
52
52
  | Prompts | `generate-required` | [Prompt rules](#prompt-rules) |
53
53
  | Handler body | `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error` | [Handler body rules](#handler-body-rules) |
54
54
  | Error contract (structural) | `error-contract-type`, `error-contract-empty`, `error-contract-entry-type`, `error-contract-code-type`, `error-contract-code-unknown`, `error-contract-code-unknown-error`, `error-contract-reason-required`, `error-contract-reason-format`, `error-contract-reason-unique`, `error-contract-when-required`, `error-contract-retryable-type`, `error-contract-recovery-required`, `error-contract-recovery-empty`, `error-contract-recovery-min-words` | [Error contract rules](#error-contract-rules) |
55
55
  | Error contract (conformance) | `error-contract-conformance`, `error-contract-prefer-fail` | [Error contract rules](#error-contract-rules) |
56
- | Enrichment | `enrichment-type`, `enrichment-empty`, `enrichment-field-type`, `enrichment-output-collision`, `enrichment-prefer-block`, `enrichment-trailer-render`, `enrichment-trailer-orphan`, `enrichment-trailer-unknown-field` | [Enrichment rules](#enrichment-rules) |
56
+ | Enrichment | `enrichment-type`, `enrichment-empty`, `enrichment-field-type`, `enrichment-output-collision`, `enrichment-prefer-block`, `enrichment-trailer-render`, `enrichment-trailer-orphan`, `enrichment-trailer-unknown-field`, `capped-list-no-truncation` | [Enrichment rules](#enrichment-rules) |
57
57
  | server.json | ~40 rules prefixed `server-json-*` | [server.json rules](#server-json-rules) |
58
58
 
59
59
  ---
@@ -367,6 +367,29 @@ An app tool's `_meta.ui.resourceUri` must match the `uriTemplate` of a registere
367
367
 
368
368
  **Fix:** either correct the `resourceUri` to match an existing resource, or register the resource it references. Use the `add-app-tool` skill's paired scaffold to avoid this.
369
369
 
370
+ ### canvas-consumer-missing
371
+
372
+ **Severity:** warning
373
+
374
+ Fires when the registered tool set contains at least one tool whose output schema has a depth-0 field named `canvas_id` or `canvasId`, but no consumer tool is registered — that is, no tool name ends with `_dataframe_query` and no extra names are listed in `canvasConsumers`.
375
+
376
+ A canvas token with no query path is dead output: the agent receives the token but has no tool to send it to. The fix runs in either direction:
377
+
378
+ - **Complete the integration** — add the standard `<prefix>_dataframe_query` and `<prefix>_dataframe_describe` consumers (see `api-canvas`).
379
+ - **Remove the staging** — when the data isn't row-shaped (nested, heterogeneous, single-record payloads), SQL access adds nothing. Drop the DataCanvas integration rather than adding tools to justify it.
380
+
381
+ **Knob:** suppress via `LintInput.canvasConsumers`:
382
+
383
+ ```ts
384
+ // Accept a non-standard query tool name:
385
+ validateDefinitions({ tools, canvasConsumers: ['my_sql_query'] });
386
+
387
+ // Disable the rule entirely:
388
+ validateDefinitions({ tools, canvasConsumers: false });
389
+ ```
390
+
391
+ **Env var:** `MCP_LINT_CANVAS_CONSUMERS` — comma-separated tool names; the literal `false` disables. A programmatic `LintInput.canvasConsumers` takes precedence over the env var. Servers that need the knob set `MCP_LINT_CANVAS_CONSUMERS=my_query_tool` in their `.env` or CI environment.
392
+
370
393
  ---
371
394
 
372
395
  ## Resource rules
@@ -777,6 +800,57 @@ Fires when an `enrichmentTrailer` key doesn't match any declared `enrichment` fi
777
800
 
778
801
  **Fix:** rename the trailer key to a declared enrichment field, or remove it.
779
802
 
803
+ ### capped-list-no-truncation
804
+
805
+ **Severity:** warning
806
+
807
+ Fires when a tool:
808
+ 1. has a depth-0 input field named `limit`, `per_page`, `page_size`, `max_results`, or `max_items` (case-insensitive; camelCase twins like `perPage`, `maxResults` match too), AND
809
+ 2. has at least one depth-0 array-typed `output` field, AND
810
+ 3. declares no truncation disclosure.
811
+
812
+ **Disclosure-present (rule silent) when** any of the following is true:
813
+ - The declared `enrichment` shape has a `truncated` or `totalCount` key (`ctx.enrich.truncated()` and `ctx.enrich.total()` satisfy this).
814
+ - The `output` schema has a depth-0 `truncated` or `totalCount` field.
815
+
816
+ A silently capped list leaves the agent unaware that results were cut off — it may treat a partial set as complete. Use `ctx.enrich.truncated({ shown, cap })` for the one-liner:
817
+
818
+ ```ts
819
+ // In the enrichment block:
820
+ enrichment: {
821
+ truncated: z.boolean().describe('True when the list was capped at the limit.'),
822
+ shown: z.number().describe('Number of items returned.'),
823
+ cap: z.number().describe('The limit applied.'),
824
+ },
825
+
826
+ // In the handler:
827
+ if (items.length >= input.limit) {
828
+ ctx.enrich.truncated({ shown: items.length, cap: input.limit });
829
+ }
830
+ ```
831
+
832
+ Or use `ctx.enrich.total(n)` when the upstream total is known — that writes `totalCount`, which is also recognized as honest disclosure.
833
+
834
+ **Threshold bound:** when the list is sorted by the cap key and the upstream total is unknowable (e.g. an API returning only the page), the smallest shown value upper-bounds all omitted items. Pass it as `ceiling`:
835
+
836
+ ```ts
837
+ ctx.enrich.truncated({ shown: items.length, cap: input.limit, ceiling: items.at(-1)?.count });
838
+ ```
839
+
840
+ Declare `truncationCeiling: z.number().optional()` in the `enrichment` block to surface it.
841
+
842
+ **Knob:** suppress via `LintInput.truncationAllowlist`:
843
+
844
+ ```ts
845
+ // Exempt a specific tool:
846
+ validateDefinitions({ tools, truncationAllowlist: ['my_search_tool'] });
847
+
848
+ // Disable the rule entirely:
849
+ validateDefinitions({ tools, truncationAllowlist: false });
850
+ ```
851
+
852
+ **Env var:** `MCP_LINT_TRUNCATION_ALLOWLIST` — comma-separated tool names; the literal `false` disables. A programmatic `LintInput.truncationAllowlist` takes precedence.
853
+
780
854
  ---
781
855
 
782
856
  ## Escape hatches
@@ -4,7 +4,7 @@ description: >
4
4
  Stand up a persistent, self-refreshing local mirror of a bulk upstream dataset with the MirrorService (@cyanheads/mcp-ts-core/mirror). Use when a server wraps a large or slow API and should query a synced local index (embedded SQLite + FTS5) instead of paginating the live API per request.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -92,6 +92,35 @@ The service owns `runSync` + state; it does not schedule. Wire "self-refreshing"
92
92
  - **Refresh** — register `runSync({ mode: 'refresh' })` on a cron via `schedulerService` from `@cyanheads/mcp-ts-core/utils`, inside `setup()`. Gate on transport (HTTP) when stdio operators run it out-of-band.
93
93
  - **Init** — run out-of-band (a CLI script / one-shot), never on startup: a full init can take hours and must not block the server. It is idempotent and resumable — re-running after an interrupt continues from the persisted cursor.
94
94
 
95
+ ### Shipping the mirror CLI in a production Docker image
96
+
97
+ The scaffold `Dockerfile` copies only `dist/` to the runtime stage. A mirror lifecycle script (`mirror:init`, `mirror:refresh`, `mirror:verify`) that imports through the `@/` path alias fails under `docker exec` — `@/` resolves to `src/` via the source `tsconfig.json`, and `src/` never reaches the image.
98
+
99
+ On the Bun runtime image (`oven/bun`), two stanzas fix it — no build change, no `rootDir` surgery, and the `mirror:*` package scripts stay identical between a dev checkout and the image.
100
+
101
+ Add the following to the runtime stage of `Dockerfile`, after the `COPY --from=build .../dist ./dist` line:
102
+
103
+ ```dockerfile
104
+ # Copy mirror lifecycle scripts. The shared context shim (_mirror-context.ts)
105
+ # is imported by the three named scripts, so it must travel with them.
106
+ COPY --from=build /usr/src/app/scripts/<your>-mirror-init.ts \
107
+ /usr/src/app/scripts/<your>-mirror-refresh.ts \
108
+ /usr/src/app/scripts/<your>-mirror-verify.ts \
109
+ /usr/src/app/scripts/_mirror-context.ts \
110
+ ./scripts/
111
+
112
+ # Bun honors tsconfig `paths` at runtime — map `@/` to the compiled `./dist/`
113
+ # so the .ts scripts resolve their alias imports against the build output.
114
+ # In a dev checkout the source tsconfig.json maps @/* → ./src/*; in the image
115
+ # this emitted one maps @/* → ./dist/*. Same `bun run mirror:*` command, both
116
+ # environments — the only lever is which tsconfig.json is on disk.
117
+ RUN echo '{"compilerOptions":{"baseUrl":".","paths":{"@/*":["./dist/*"]}}}' > tsconfig.json
118
+ ```
119
+
120
+ **Caveat:** this relies on Bun's runtime `paths` resolution. A Node runtime image (no native `.ts` execution) needs the scripts compiled into `dist/` instead — a separate tsconfig pass with a different `rootDir` is required in that case.
121
+
122
+ **`package.json` `files[]`:** add `scripts/_mirror-context.ts` and the three named lifecycle scripts so the npm tarball and `.mcpb` bundle carry them. Consumers installing from npm need them for `docker exec` access.
123
+
95
124
  ## Checklist
96
125
 
97
126
  - [ ] `defineMirror({ name, store, sync })`; the server holds the instance (one per mirror)
@@ -4,7 +4,7 @@ description: >
4
4
  Testing patterns for MCP tool/resource handlers using `createMockContext` and Vitest. Covers mock context options, handler testing, McpError assertions, format testing, Vitest config setup, and test isolation conventions.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -27,7 +27,6 @@ import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
27
27
  createMockContext() // minimal — ctx.state operations throw without tenantId
28
28
  createMockContext({ tenantId: 'test-tenant' }) // enables ctx.state (tenant-scoped in-memory storage)
29
29
  createMockContext({ errors: myTool.errors }) // attaches typed ctx.fail keyed by the contract reasons
30
- createMockContext({ sample: vi.fn().mockResolvedValue(...) }) // with MCP sampling
31
30
  createMockContext({ elicit: vi.fn().mockResolvedValue(...) }) // with elicitation
32
31
  createMockContext({ progress: true }) // with task progress (ctx.progress populated)
33
32
  createMockContext({ requestId: 'my-id' }) // override request ID (default: 'test-request-id')
@@ -52,7 +51,6 @@ interface MockContextOptions {
52
51
  progress?: boolean;
53
52
  sessionId?: string;
54
53
  requestId?: string;
55
- sample?: (messages: SamplingMessage[], opts?: SamplingOpts) => Promise<CreateMessageResult>;
56
54
  signal?: AbortSignal;
57
55
  tenantId?: string;
58
56
  uri?: URL;
@@ -61,7 +59,7 @@ interface MockContextOptions {
61
59
 
62
60
  | Option | Effect |
63
61
  |:-------|:-------|
64
- | _(none)_ | Minimal context — `ctx.state` operations throw without `tenantId`; `ctx.elicit`/`ctx.sample`/`ctx.progress` are `undefined` |
62
+ | _(none)_ | Minimal context — `ctx.state` operations throw without `tenantId`; `ctx.elicit`/`ctx.progress` are `undefined` |
65
63
  | `auth` | Sets `ctx.auth` for scope-checking tests |
66
64
  | `elicit` | Assigns a function to `ctx.elicit` for testing elicitation calls |
67
65
  | `errors` | Attaches a typed `ctx.fail` against the contract — same wiring the production handler factory uses. Pass `myTool.errors` directly. |
@@ -72,7 +70,6 @@ interface MockContextOptions {
72
70
  | `sessionId` | Sets `ctx.sessionId` for handlers that branch on session ID |
73
71
  | `progress` | Populates `ctx.progress` with real state-tracking implementation (see below) |
74
72
  | `requestId` | Overrides `ctx.requestId` (default: `'test-request-id'`) |
75
- | `sample` | Assigns a function to `ctx.sample` for testing sampling calls |
76
73
  | `signal` | Overrides `ctx.signal` — useful for cancellation testing |
77
74
  | `tenantId` | Sets `ctx.tenantId` and enables `ctx.state` operations with in-memory storage |
78
75
  | `uri` | Sets `ctx.uri` for resource handler testing |
@@ -164,17 +161,6 @@ it('uses elicitation when available', async () => {
164
161
  expect(elicit).toHaveBeenCalledOnce();
165
162
  });
166
163
 
167
- it('uses sampling when available', async () => {
168
- const sample = vi.fn().mockResolvedValue({
169
- role: 'assistant',
170
- content: { type: 'text', text: 'Summary text' },
171
- });
172
- const ctx = createMockContext({ sample });
173
- const input = myTool.input.parse({ query: 'summarize this' });
174
- const result = await myTool.handler(input, ctx);
175
- expect(result.summary).toBeDefined();
176
- });
177
-
178
164
  it('handles missing elicitation gracefully', async () => {
179
165
  // ctx.elicit is undefined — handler must check before calling
180
166
  const ctx = createMockContext();
@@ -4,7 +4,7 @@ description: >
4
4
  Post-session code review and cleanup against a working tree of changes. Analyzes `git diff` to simplify, consolidate, and align changed code with the existing codebase — modernize syntax, remove unnecessary complexity, consolidate duplicated logic, catch efficiency issues. Use after a substantive working session, or when asked to clean up, simplify, reduce slop, consolidate, modernize, tighten up, or de-slop code. For `@cyanheads/mcp-ts-core` projects, includes specific transformations for tool/resource/prompt definitions, the ctx pattern, error factories, and framework idioms.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -62,7 +62,7 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
62
62
 
63
63
  - **Error throwing patterns** — Prefer framework error factories (`McpError`, `validationError`, `notFound`, `httpErrorFromResponse`) over raw `throw new Error()`. Tool handlers should throw — the framework catches, classifies, and instruments.
64
64
  - **Error codes** — `InvalidParams` only for malformed JSON-RPC params shape. `ValidationError` for domain validation. `NotFound` for missing entities. Don't conflate them.
65
- - **Ctx usage** — Use `ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.sample` — don't reach for global loggers, request-scoped storage, or sampling APIs directly. The `ctx` pattern carries tenant scope and OTel context.
65
+ - **Ctx usage** — Use `ctx.log`, `ctx.state`, `ctx.elicit` — don't reach for global loggers or request-scoped storage directly. The `ctx` pattern carries tenant scope and OTel context.
66
66
  - **Zod schemas** — Every tool input/output field needs `.describe()`. Zod 4 requires `z.record(z.string(), z.string())` not `z.record(z.string())`. Use `.optional()` rather than `.nullish()` unless null is semantically distinct from absent.
67
67
  - **Tool annotations** — `readOnlyHint`, `idempotentHint`, `openWorldHint` should reflect reality. A read-only tool with `readOnlyHint: false` gives clients the wrong picture.
68
68
  - **`exactOptionalPropertyTypes` boundaries** — If a downstream type insists on the field being present-or-not-present (not present-as-undefined), use a mapped widening type at the boundary. The pattern is documented in the framework.
@@ -4,7 +4,7 @@ description: >
4
4
  Design the tool surface, resources, and service layer for a new MCP server. Use when starting a new server, planning a major feature expansion, or when the user describes a domain/API they want to expose via MCP. Produces a design doc at docs/design.md that drives implementation.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.17"
7
+ version: "2.18"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -348,6 +348,7 @@ output: z.object({
348
348
  }),
349
349
  ```
350
350
 
351
+ - **Capped lists disclose truncation.** When a tool accepts a cap-like input (`limit`, `per_page`, `page_size`, `max_results`, `max_items`) and returns an array, the handler must disclose when the cap was hit. Standard fields: `truncated: true`, `shown`, `cap` in the `enrichment` block via `ctx.enrich.truncated({ shown, cap })`. `ctx.enrich.total(n)` (writes `totalCount`) is also recognized. Silent caps leave the agent treating a partial set as complete. The `capped-list-no-truncation` lint rule enforces this; see `api-linter` and `api-context`'s `ctx.enrich.truncated()` section.
351
352
  - **Truncate large output with counts.** When a list exceeds a reasonable display size, show the top N and append "...and X more". Don't silently drop results.
352
353
  - **Spill big *analytical* results to a queryable surface.** When a tool's row set is something an agent would run SQL over (aggregate, group, join) *and* can exceed any reasonable context budget — paginated APIs, streamed exports, big query results — pair an inline preview with a `DataCanvas` table holding the full set. **Two rules gate this:** (1) it must earn its keep on *shape, not size* — a discovery/search surface of categorical metadata (titles, IDs) is not analytical and doesn't get a canvas regardless of row count; for name→ID resolution over a bounded list use [MCP-side list filtering](#mcp-side-list-filtering); (2) the `canvas_id` is reachable only if the same server **also exposes a `dataframe_query` tool** — emit one without the other and the handle is dead output. Compute distributions or refinement hints across the full result, not the preview, so aggregate signal stays honest. See `api-canvas` for the `spillover()` helper and both rules in full.
353
354
  - **Outline one large *document* into sections.** When a single tool call returns one document-shaped record (not many rows) that can exceed context — a ~130KB FDA drug label, a big API entity dominated by a few fat fields — return a section *outline* (top-level keys + per-section byte size) instead of truncating, and let the agent re-call with `sections: [...]` to pull only what it needs. The `outlineOnOverflow()` helper (`@cyanheads/mcp-ts-core/utils`) measures the payload and returns a `full | outline` discriminated union; declare its `OUTLINE_VARIANT` as a branch of the tool's `output` so `format()`-parity is enforced per branch. Pure measure + key-slice — Workers-portable, unlike canvas-bound `spillover()`. Distinct from spillover on *shape*: spillover splits a row collection, this outlines one fat record. See the `techniques` skill's `outline-on-overflow` reference.
@@ -4,7 +4,7 @@ description: >
4
4
  Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.5"
7
+ version: "2.6"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -46,7 +46,7 @@ Compare the structure diagram against the actual directory layout. If it still r
46
46
 
47
47
  ### 4. Update the Context Table
48
48
 
49
- Review the `ctx` feature table. Remove rows for features the server doesn't use (e.g., `ctx.elicit`, `ctx.sample` if no tools call them). Add any custom context usage that's become important. The table should reflect what this server actually uses, not the full framework surface.
49
+ Review the `ctx` feature table. Remove rows for features the server doesn't use (e.g., `ctx.elicit` if no tools call it). Add any custom context usage that's become important. The table should reflect what this server actually uses, not the full framework surface.
50
50
 
51
51
  ### 5. Update Server Config Example
52
52
 
@@ -4,7 +4,7 @@ description: >
4
4
  File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -148,7 +148,7 @@ Format: `bug(<scope>): concise description`
148
148
  | `tool` | Tool builder, handler, format, annotations |
149
149
  | `resource` | Resource builder, handler, list, params |
150
150
  | `prompt` | Prompt builder, generate, args |
151
- | `context` | Context, logger, state, progress, elicit, sample |
151
+ | `context` | Context, logger, state, progress, elicit |
152
152
  | `config` | AppConfig, parseConfig, env parsing |
153
153
  | `errors` | McpError, error factories, typed contracts (`errors[]` / `ctx.fail`), conformance lint, `httpErrorFromResponse`, auto-classification |
154
154
  | `auth` | Auth modes, scope checking, JWT/OAuth |
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: security-pass
3
3
  description: >
4
- Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / sampling / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
4
+ Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: audit
10
10
  ---
@@ -44,7 +44,7 @@ find src/mcp-server/prompts/definitions -name "*.prompt.ts" 2>/dev/null | sort
44
44
  find src/services -maxdepth 1 -mindepth 1 -type d | sort
45
45
  ```
46
46
 
47
- Note: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which handlers use `ctx.sample` or `ctx.elicit`, which services hold module-scope state, whether the server reads `roots`.
47
+ Note: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which handlers use `ctx.elicit`, which services hold module-scope state, whether the server reads `roots`.
48
48
 
49
49
  **If transport is streamable HTTP or SSE**, also capture:
50
50
 
@@ -162,9 +162,6 @@ grep -rnE "\b(exec|spawn|execSync|spawnSync)\b" src/
162
162
  # Merges — prototype pollution
163
163
  grep -rn "Object.assign\b\|structuredClone" src/
164
164
 
165
- # Sampling — LLM-generated content flowing back into server logic
166
- grep -rn "ctx.sample\|sampling/createMessage" src/
167
-
168
165
  # Roots — client-shared filesystem
169
166
  grep -rn "roots/list\|ctx.roots" src/
170
167
 
@@ -182,9 +179,7 @@ grep -rn "\.passthrough()\|\.catchall(" src/mcp-server/
182
179
  - User-JSON merges reject `__proto__`, `constructor`, `prototype` keys?
183
180
  - **Input schemas `.strict()`** — unknown fields rejected, not silently passed to downstream code that destructures with `...rest`?
184
181
  - **Output schemas without `.passthrough()` / `.catchall()`** — no accidental exfiltration of fields your schema didn't declare?
185
- - Sampling responses (`ctx.sample` result) treated as untrusted input schema-validated before reaching any other sink, never concatenated into prompts, shells, or queries?
186
-
187
- **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization; `await ctx.sample(...)` result interpolated into a shell, SQL, or URL.
182
+ **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization.
188
183
 
189
184
  #### Axis 6 — Tenant isolation
190
185
 
@@ -333,14 +328,14 @@ End with:
333
328
  ## Checklist
334
329
 
335
330
  - [ ] Scope confirmed (whole server / module / diff)
336
- - [ ] Map built: tools / resources / prompts, services, upstream APIs, auth mode, sampling / elicit / roots usage
331
+ - [ ] Map built: tools / resources / prompts, services, upstream APIs, auth mode, elicit / roots usage
337
332
  - [ ] Deployment surface reviewed (if HTTP): bind address, Origin allowlist, session ID, unauth routes, auth-spec compliance
338
333
  - [ ] `fuzzTool` started in parallel
339
334
  - [ ] Axis 1 — LLM-facing surfaces (tool / resource / prompt output + descriptions) framed and static
340
335
  - [ ] Axis 2 — scope granularity audited
341
336
  - [ ] Axis 3 — destructive ops verified to elicit, elicit response schema-validated
342
337
  - [ ] Axis 4 — upstream auth + token passthrough reviewed
343
- - [ ] Axis 5 — input sinks (URL / path / roots / shell / proto / sampling / schema strictness / ReDoS) checked
338
+ - [ ] Axis 5 — input sinks (URL / path / roots / shell / proto / schema strictness / ReDoS) checked
344
339
  - [ ] Axis 6 — tenant isolation: module-scope state swept
345
340
  - [ ] Axis 7 — leakage back: errors / outputs / `ctx.log` / `console.*` / telemetry / constant-time comparisons
346
341
  - [ ] Axis 8 — resource bounds on loops / retries / pagination / parse size+depth / per-tenant rate
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
49
49
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
50
50
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
51
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
51
+ - **Check `ctx.elicit`** for presence before calling.
52
52
  - **Secrets in env vars only** — never hardcoded.
53
53
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
54
54
 
@@ -156,9 +156,22 @@ export function getServerConfig() {
156
156
 
157
157
  For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
158
158
 
159
- ### Server instructions
159
+ ### Server identity and instructions
160
160
 
161
- `createApp({ instructions })` optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
161
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
162
+
163
+ ```ts
164
+ await createApp({
165
+ name: 'my-mcp-server',
166
+ title: 'My Server', // human-readable display name
167
+ websiteUrl: 'https://github.com/owner/repo', // canonical homepage URL
168
+ description: 'One-line description.', // wins over MCP_SERVER_DESCRIPTION
169
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
170
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
171
+ });
172
+ ```
173
+
174
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
162
175
 
163
176
  ---
164
177
 
@@ -170,8 +183,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
183
  |:---------|:------------|
171
184
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
172
185
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
173
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
174
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
186
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
175
187
  | `ctx.signal` | `AbortSignal` for cancellation. |
176
188
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
177
189
  | `ctx.requestId` | Unique request ID. |
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
49
49
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
50
50
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
51
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
51
+ - **Check `ctx.elicit`** for presence before calling.
52
52
  - **Secrets in env vars only** — never hardcoded.
53
53
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
54
54
 
@@ -156,9 +156,22 @@ export function getServerConfig() {
156
156
 
157
157
  For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
158
158
 
159
- ### Server instructions
159
+ ### Server identity and instructions
160
160
 
161
- `createApp({ instructions })` optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
161
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
162
+
163
+ ```ts
164
+ await createApp({
165
+ name: 'my-mcp-server',
166
+ title: 'My Server', // human-readable display name
167
+ websiteUrl: 'https://github.com/owner/repo', // canonical homepage URL
168
+ description: 'One-line description.', // wins over MCP_SERVER_DESCRIPTION
169
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
170
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
171
+ });
172
+ ```
173
+
174
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
162
175
 
163
176
  ---
164
177
 
@@ -170,8 +183,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
183
  |:---------|:------------|
171
184
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
172
185
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
173
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
174
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
186
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
175
187
  | `ctx.signal` | `AbortSignal` for cancellation. |
176
188
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
177
189
  | `ctx.requestId` | Unique request ID. |
@@ -71,12 +71,29 @@ RUN if [ "$OTEL_ENABLED" = "true" ]; then \
71
71
  # Copy the compiled application code from the build stage
72
72
  COPY --from=build /usr/src/app/dist ./dist
73
73
 
74
+ # Mirror CLI (MirrorService adopters only — Tier 3, opt-in):
75
+ # Copy your mirror lifecycle scripts and emit a runtime tsconfig so Bun resolves
76
+ # the @/ path alias against ./dist/ rather than ./src/.
77
+ # See the api-mirror skill for the full recipe.
78
+ #
79
+ # COPY --from=build /usr/src/app/scripts/<your>-mirror-init.ts \
80
+ # /usr/src/app/scripts/<your>-mirror-refresh.ts \
81
+ # /usr/src/app/scripts/<your>-mirror-verify.ts \
82
+ # /usr/src/app/scripts/_mirror-context.ts \
83
+ # ./scripts/
84
+ # RUN echo '{"compilerOptions":{"baseUrl":".","paths":{"@/*":["./dist/*"]}}}' > tsconfig.json
85
+
74
86
  # The 'oven/bun' image already provides a non-root user named 'bun'.
75
87
  # We will use this existing user for enhanced security.
76
88
 
77
89
  # Create and set permissions for the log directory, assigning ownership to the 'bun' user.
78
90
  RUN mkdir -p /var/log/{{PACKAGE_NAME}} && chown -R bun:bun /var/log/{{PACKAGE_NAME}}
79
91
 
92
+ # Writable data dirs for on-disk SQLite stores (catalog index / observations
93
+ # mirror), owned by the runtime user. Mount a volume over either in production.
94
+ RUN mkdir -p /usr/src/app/.cache /usr/src/app/.mirror \
95
+ && chown -R bun:bun /usr/src/app/.cache /usr/src/app/.mirror
96
+
80
97
  # Switch to the non-root user
81
98
  USER bun
82
99
 
@@ -1,22 +0,0 @@
1
- /**
2
- * @fileoverview Service for implementing MCP roots capability.
3
- * Roots provide filesystem/workspace context awareness for the server.
4
- *
5
- * MCP Roots Specification:
6
- * @see {@link https://modelcontextprotocol.io/specification/2025-06-18/basic/roots | MCP Roots}
7
- * @module src/mcp-server/roots/roots-registration
8
- */
9
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
- import type { logger as defaultLogger } from '../../utils/internal/logger.js';
11
- export declare class RootsRegistry {
12
- private logger;
13
- constructor(logger: typeof defaultLogger);
14
- /**
15
- * Registers roots handlers on the given MCP server.
16
- * Note: In MCP, roots are typically provided BY THE CLIENT to the server.
17
- * This implementation provides a placeholder for demonstration.
18
- * In production, roots would be received from the client via client.listRoots().
19
- */
20
- registerAll(_server: McpServer): void;
21
- }
22
- //# sourceMappingURL=roots-registration.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"roots-registration.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/roots/roots-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG1E,qBAAa,aAAa;IACZ,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,OAAO,aAAa;IAEhD;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;CActC"}
@@ -1,25 +0,0 @@
1
- import { requestContextService } from '../../utils/internal/requestContext.js';
2
- export class RootsRegistry {
3
- logger;
4
- constructor(logger) {
5
- this.logger = logger;
6
- }
7
- /**
8
- * Registers roots handlers on the given MCP server.
9
- * Note: In MCP, roots are typically provided BY THE CLIENT to the server.
10
- * This implementation provides a placeholder for demonstration.
11
- * In production, roots would be received from the client via client.listRoots().
12
- */
13
- registerAll(_server) {
14
- const context = requestContextService.createRequestContext({
15
- operation: 'RootsRegistry.registerAll',
16
- });
17
- this.logger.debug('Roots capability enabled (client-provided roots)', context);
18
- // Note: The MCP SDK handles roots automatically via the client-server protocol.
19
- // Servers receive roots from clients, not the other way around.
20
- // This is just a placeholder to demonstrate the capability is enabled.
21
- // To access roots in your tools, use sdkContext to query the client.
22
- this.logger.debug('Roots capability registered successfully', context);
23
- }
24
- }
25
- //# sourceMappingURL=roots-registration.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"roots-registration.js","sourceRoot":"","sources":["../../../src/mcp-server/roots/roots-registration.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE3E,MAAM,OAAO,aAAa;IACJ;IAApB,YAAoB,MAA4B;QAA5B,WAAM,GAAN,MAAM,CAAsB;IAAG,CAAC;IAEpD;;;;;OAKG;IACH,WAAW,CAAC,OAAkB;QAC5B,MAAM,OAAO,GAAG,qBAAqB,CAAC,oBAAoB,CAAC;YACzD,SAAS,EAAE,2BAA2B;SACvC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kDAAkD,EAAE,OAAO,CAAC,CAAC;QAE/E,gFAAgF;QAChF,gEAAgE;QAChE,uEAAuE;QACvE,qEAAqE;QAErE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE,OAAO,CAAC,CAAC;IACzE,CAAC;CACF"}