@cyanheads/mcp-ts-core 0.10.7 → 0.10.8

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 (37) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +1 -1
  4. package/changelog/0.10.x/0.10.8.md +19 -0
  5. package/dist/core/app.d.ts +6 -1
  6. package/dist/core/app.d.ts.map +1 -1
  7. package/dist/core/app.js.map +1 -1
  8. package/dist/core/context.d.ts +63 -1
  9. package/dist/core/context.d.ts.map +1 -1
  10. package/dist/core/context.js +56 -0
  11. package/dist/core/context.js.map +1 -1
  12. package/dist/core/index.d.ts +1 -1
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/logs/combined.log +8 -8
  16. package/dist/logs/error.log +4 -4
  17. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +9 -1
  19. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  20. package/dist/services/canvas/core/sqlGate.d.ts +2 -0
  21. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  22. package/dist/services/canvas/core/sqlGate.js +2 -0
  23. package/dist/services/canvas/core/sqlGate.js.map +1 -1
  24. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  25. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +39 -0
  26. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  27. package/dist/testing/index.d.ts +15 -1
  28. package/dist/testing/index.d.ts.map +1 -1
  29. package/dist/testing/index.js +22 -1
  30. package/dist/testing/index.js.map +1 -1
  31. package/package.json +1 -1
  32. package/skills/add-tool/SKILL.md +15 -1
  33. package/skills/api-canvas/SKILL.md +3 -1
  34. package/skills/api-config/SKILL.md +2 -2
  35. package/skills/api-context/SKILL.md +55 -2
  36. package/skills/api-telemetry/SKILL.md +2 -2
  37. package/skills/polish-docs-meta/SKILL.md +2 -2
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP tool definition. Use when the user asks to add a tool, create a new tool, or implement a new capability for the server.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.14"
7
+ version: "2.15"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -252,6 +252,20 @@ enrichmentTrailer: {
252
252
 
253
253
  `structuredContent` always keeps the full structured value; `enrichmentTrailer` only controls the human-facing `content[]` line.
254
254
 
255
+ ### Image / audio output belongs in `ctx.content`
256
+
257
+ When a tool produces image or audio bytes for the calling model to *see or hear* — a rendered chart, a generated frame, synthesized speech — emit them via `ctx.content`, not an `output` field. `ctx.content.image(data, mimeType)` / `.audio(data, mimeType)` prepend a content block to `content[]` after `format()` runs and **never** write to `structuredContent`, so the base64 is carried once instead of duplicating into the typed output. Like `ctx.enrich`, it lives on the base `Context` and is callable from the service layer.
258
+
259
+ ```ts
260
+ async handler(input, ctx) {
261
+ const png = await render(input.spec); // base64 PNG
262
+ ctx.content.image(png, 'image/png'); // → content[] block, not structuredContent
263
+ return { width: input.spec.w, height: input.spec.h }; // typed result stays small
264
+ },
265
+ ```
266
+
267
+ The alternative — declaring `previewData: z.string()` in `output` and emitting the block from `format()` — ships the bytes twice (once in `structuredContent`, once in the block). Reserve `output` for data the agent reasons over; route raw media through `ctx.content`. Test with `getContentBlocks(ctx)`. Full reference: `skills/api-context` § `ctx.content`.
268
+
255
269
  ### Capped lists must disclose truncation
256
270
 
257
271
  When a tool accepts a cap-like input (`limit`, `per_page`, `page_size`, `max_results`, `max_items`) and returns an array, disclose when the cap was hit — the agent otherwise treats a partial set as complete.
@@ -4,7 +4,7 @@ description: >
4
4
  DataCanvas primitive reference — a Tier 3 SQL/analytical workspace for tabular MCP servers, backed by DuckDB. Use when registering tables from upstream APIs, running ad-hoc SQL across them, and exporting results. Covers the acquire → register → query → export flow, per-table TTL, the token-sharing pattern for multi-agent collaboration, env config, and Cloudflare Workers fail-closed behavior.
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
  ---
@@ -150,6 +150,8 @@ Run SQL across registered tables. Returns at most `rowLimit` rows (default 10 00
150
150
 
151
151
  Querying a table that does not exist throws `NotFound` (`data.reason: 'missing_table'`) with a recovery hint to re-stage the table or call `describe()`. This happens when a table has expired (per-table TTL), been dropped, or the name is mistyped. The error is `NotFound`, not `ValidationError` — agents should re-stage, not fix the SQL shape.
152
152
 
153
+ A `SELECT` that parses but fails to prepare for any other reason — a mistyped column, an unknown function, an invalid expression — throws `ValidationError` (`data.reason: 'invalid_sql'`) and preserves the DuckDB binder detail in `data.binderMessage` (e.g. `Referenced column "x" not found...`, often with a candidate suggestion). This is distinct from `non_select_statement`, reserved for statements that genuinely aren't `SELECT`s — here the shape is fine, so the agent should fix the named column or function.
154
+
153
155
  ```ts
154
156
  const result = await instance.query(`
155
157
  SELECT germplasmName, COUNT(*) AS n
@@ -4,7 +4,7 @@ description: >
4
4
  Reference for core and server configuration in `@cyanheads/mcp-ts-core`. Covers env var tables with defaults, priority order, server-specific Zod schema pattern, and Workers lazy-parsing requirement.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -167,7 +167,7 @@ Activated when both `SUPABASE_URL` and `SUPABASE_ANON_KEY` are set.
167
167
  | Env Var | `AppConfig` field | Default | Notes |
168
168
  |:--------|:-----------------|:--------|:------|
169
169
  | `OTEL_ENABLED` | `openTelemetry.enabled` | `false` | Enable OpenTelemetry export |
170
- | `OTEL_SERVICE_NAME` | `openTelemetry.serviceName` | `package.json` `name` | |
170
+ | `OTEL_SERVICE_NAME` | `openTelemetry.serviceName` | `createApp` `name` → `package.json` `name` | Seeded from `createApp({ name })` when unset; an env value wins |
171
171
  | `OTEL_SERVICE_VERSION` | `openTelemetry.serviceVersion` | `package.json` `version` | |
172
172
  | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | `openTelemetry.tracesEndpoint` | — | OTLP traces endpoint URL |
173
173
  | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | `openTelemetry.metricsEndpoint` | — | OTLP metrics endpoint URL |
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: api-context
3
3
  description: >
4
- Canonical reference for the unified `Context` object passed to every tool and resource handler in `@cyanheads/mcp-ts-core`. Covers the full interface, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.progress`, `ctx.enrich`), and when to use each.
4
+ Canonical reference for the unified `Context` object passed to every tool and resource handler in `@cyanheads/mcp-ts-core`. Covers the full interface, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.progress`, `ctx.enrich`, `ctx.content`), and when to use each.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.8"
7
+ version: "1.9"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -63,6 +63,12 @@ interface Context {
63
63
  // declared fields. Kind-tagged helpers: enrich.notice / .total / .echo.
64
64
  readonly enrich: Enrich;
65
65
 
66
+ // Non-text content blocks (image/audio bytes) for the calling model — prepended
67
+ // to content[] after format() runs, never placed in structuredContent. Always
68
+ // present (no-op when never called). Helpers: content.image / .audio; content(block)
69
+ // pushes a raw ContentBlock.
70
+ readonly content: ContentCollect;
71
+
66
72
  // Opt-in contract resolver — always present (returns {} when no contract is attached
67
73
  // or the reason is unknown), strictly typed on HandlerContext<R> against declared reasons.
68
74
  recoveryFor(reason: string): { recovery: { hint: string } } | {};
@@ -637,6 +643,52 @@ See `add-tool`'s **Tool Response Design** and `skills/api-linter` (`enrichment-*
637
643
 
638
644
  ---
639
645
 
646
+ ## `ctx.content`
647
+
648
+ Always present on `Context`. Collects **non-text content blocks** — image or audio bytes the calling model should see or hear — and prepends them to the tool's `content[]` after `format()` runs. Collected blocks **never** enter `structuredContent`, so the base64 payload is carried once (in `content[]`) instead of duplicating into the typed output field. The media counterpart to `ctx.enrich`: both ride alongside the domain result without bloating it.
649
+
650
+ ```ts
651
+ export const renderChart = tool('render_chart', {
652
+ description: 'Render a chart from a series and return its summary.',
653
+ input: z.object({ series: z.array(z.number()).describe('Data points') }),
654
+ output: z.object({ points: z.number().describe('Number of points plotted') }),
655
+ async handler(input, ctx) {
656
+ const png = await draw(input.series); // base64 PNG
657
+ ctx.content.image(png, 'image/png'); // → content[] block, NOT structuredContent
658
+ return { points: input.series.length }; // the typed result stays small
659
+ },
660
+ });
661
+ ```
662
+
663
+ Without `ctx.content`, the only way to surface bytes to the model is to declare them in `output` and emit an image block from `format()` — which ships the base64 twice (once in `structuredContent`, once in the block). `ctx.content` removes the duplication.
664
+
665
+ ### Signature
666
+
667
+ ```ts
668
+ // Callable — push a raw ContentBlock (escape hatch for embedded resources, resource links):
669
+ ctx.content(block: ContentBlock): void
670
+
671
+ // Typed helpers for the two base64 media blocks:
672
+ ctx.content.image(data: string, mimeType: string): void // → { type: 'image', data, mimeType }
673
+ ctx.content.audio(data: string, mimeType: string): void // → { type: 'audio', data, mimeType }
674
+ ```
675
+
676
+ ### Behavior
677
+
678
+ | Aspect | Detail |
679
+ |:-------|:-------|
680
+ | `content[]` only | Blocks are prepended to `content[]` and never written to `structuredContent`. Data meant for the typed result stays on the handler's return value. |
681
+ | Order | `content[]` is `[...collected blocks, ...format()/JSON output, ...enrichment trailer]` — media first, domain content next, enrichment trailer last. |
682
+ | Accumulation | Each call appends; blocks render in call order. |
683
+ | No-op | A handler that never calls `ctx.content` produces a `content[]` / `structuredContent` byte-identical to before — the feature is purely additive and opt-in. |
684
+ | Error path | If the handler throws, collected blocks are dropped — a failed call returns the error result only, never a partial image. |
685
+ | Service usage | Services accepting `ctx: Context` can call `ctx.content(...)`; the blocks reach `content[]` exactly as if the handler had. |
686
+ | No schema involvement | Blocks bypass `output` entirely, so no linter rule requires them in `format()` and they never appear in the advertised `outputSchema`. |
687
+
688
+ Test content blocks with `getContentBlocks(ctx)` from `@cyanheads/mcp-ts-core/testing`.
689
+
690
+ ---
691
+
640
692
  ## Quick reference
641
693
 
642
694
  | Property | Type | Present when |
@@ -652,6 +704,7 @@ See `add-tool`'s **Tool Response Design** and `skills/api-linter` (`enrichment-*
652
704
  | `ctx.state` | `ContextState` | Always (throws if `tenantId` missing) |
653
705
  | `ctx.signal` | `AbortSignal` | Always |
654
706
  | `ctx.enrich` | `Enrich` | Always; typed on `HandlerContext<R, E>` when an `enrichment` block is declared |
707
+ | `ctx.content` | `ContentCollect` | Always — prepends image/audio blocks to `content[]`, never `structuredContent` |
655
708
  | `ctx.elicit` | `function \| undefined` | Client supports elicitation |
656
709
  | `ctx.notifyResourceListChanged` | `function \| undefined` | Always in handler ctx; delivery request-scoped (see [§ list-changed notifications](#list-changed-notifications-ctxnotify)) |
657
710
  | `ctx.notifyResourceUpdated` | `function \| undefined` | Always in handler ctx; delivery request-scoped |
@@ -4,7 +4,7 @@ description: >
4
4
  Catalog of OpenTelemetry instrumentation built into framework `@cyanheads/mcp-ts-core` — spans, metrics, completion logs, env config, runtime caveats, custom instrumentation patterns, and cardinality rules. Use when enabling OTel export, adding custom spans or metrics in services, debugging missing telemetry, looking up attribute names, or deciding what's safe to put on a metric attribute vs. a span.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.2"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -28,7 +28,7 @@ OTel is **off by default**. `OTEL_ENABLED=true` alone does nothing — you also
28
28
  | `OTEL_ENABLED` | `false` | Master switch. Must be `true` to start the SDK. |
29
29
  | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | — | OTLP/HTTP traces endpoint (e.g. `http://localhost:4318/v1/traces`). |
30
30
  | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | — | OTLP/HTTP metrics endpoint (e.g. `http://localhost:4318/v1/metrics`). |
31
- | `OTEL_SERVICE_NAME` | `package.json` `name` | `service.name` resource attribute. |
31
+ | `OTEL_SERVICE_NAME` | `createApp` `name` → `package.json` `name` | `service.name` resource attribute. Seeded from `createApp({ name })` when unset; an env value wins. |
32
32
  | `OTEL_SERVICE_VERSION` | `package.json` `version` | `service.version` resource attribute. |
33
33
  | `OTEL_TRACES_SAMPLER_ARG` | `1.0` | Trace sampling ratio (0–1) for `TraceIdRatioBasedSampler`. |
34
34
  | `OTEL_LOG_LEVEL` | `INFO` | OTel diagnostic logger level (`NONE`/`ERROR`/`WARN`/`INFO`/`DEBUG`/`VERBOSE`/`ALL`). |
@@ -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.7"
7
+ version: "2.8"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -76,7 +76,7 @@ Key fields: `name`, `description`, `repository`, `author`, `homepage`, `bugs`, `
76
76
 
77
77
  **`name` must communicate the server's domain at a glance.** See `references/package-meta.md` for the naming convention — ambiguous abbreviations and acronym-only names fail the scannability test for humans and agents alike.
78
78
 
79
- **`name` and `title` in `createApp()` / `createWorkerHandler()` must match the unscoped `package.json` `name`** — display identity is the machine name on every surface; `lint:packaging` (run by `devcheck`) enforces the match and warns when the pair is partial. `description` is never duplicated into the entrypoint — `package.json` is the canonical source (the framework derives the served description from it).
79
+ **`name` and `title` in `createApp()` / `createWorkerHandler()` must match the unscoped `package.json` `name`** — display identity is the machine name on every surface; `lint:packaging` (run by `devcheck`) enforces the match and warns when the pair is partial. `description` is never duplicated into the entrypoint — `package.json` is the canonical source (the framework derives the served description from it). Adopting the pair also seeds `OTEL_SERVICE_NAME` when unset, so telemetry's `service.name` switches to the machine name on first boot — expect a one-time series split in backends keyed on the old scoped label.
80
80
 
81
81
  **`description` is the canonical source.** Every other surface (README header, `server.json`, Dockerfile OCI label, GitHub repo description) derives from it. Write it here first, then propagate.
82
82