@cyanheads/mcp-ts-core 0.13.6 → 0.13.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 (267) hide show
  1. package/AGENTS.md +5 -5
  2. package/CLAUDE.md +5 -5
  3. package/README.md +57 -52
  4. package/biome.json +1 -1
  5. package/changelog/0.13.x/0.13.7.md +77 -0
  6. package/changelog/0.13.x/0.13.8.md +101 -0
  7. package/config/tsconfig.base.json +2 -2
  8. package/dist/config/index.d.ts +9 -0
  9. package/dist/config/index.d.ts.map +1 -1
  10. package/dist/config/index.js +61 -11
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/core/app.d.ts.map +1 -1
  13. package/dist/core/app.js +35 -6
  14. package/dist/core/app.js.map +1 -1
  15. package/dist/core/context.d.ts +9 -1
  16. package/dist/core/context.d.ts.map +1 -1
  17. package/dist/core/context.js +17 -16
  18. package/dist/core/context.js.map +1 -1
  19. package/dist/core/worker.d.ts +2 -0
  20. package/dist/core/worker.d.ts.map +1 -1
  21. package/dist/core/worker.js +9 -1
  22. package/dist/core/worker.js.map +1 -1
  23. package/dist/linter/rules/enrichment-rules.d.ts +3 -2
  24. package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
  25. package/dist/linter/rules/enrichment-rules.js +9 -2
  26. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  27. package/dist/linter/rules/handler-body-rules.d.ts.map +1 -1
  28. package/dist/linter/rules/handler-body-rules.js +10 -4
  29. package/dist/linter/rules/handler-body-rules.js.map +1 -1
  30. package/dist/linter/rules/schema-rules.d.ts +5 -0
  31. package/dist/linter/rules/schema-rules.d.ts.map +1 -1
  32. package/dist/linter/rules/schema-rules.js +44 -17
  33. package/dist/linter/rules/schema-rules.js.map +1 -1
  34. package/dist/mcp-server/handlerContext.d.ts +6 -0
  35. package/dist/mcp-server/handlerContext.d.ts.map +1 -1
  36. package/dist/mcp-server/handlerContext.js +3 -0
  37. package/dist/mcp-server/handlerContext.js.map +1 -1
  38. package/dist/mcp-server/outputContract.d.ts +33 -0
  39. package/dist/mcp-server/outputContract.d.ts.map +1 -0
  40. package/dist/mcp-server/outputContract.js +43 -0
  41. package/dist/mcp-server/outputContract.js.map +1 -0
  42. package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
  43. package/dist/mcp-server/prompts/prompt-registration.js +6 -3
  44. package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
  45. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  46. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +10 -2
  47. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  48. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +16 -5
  49. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +70 -14
  51. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  52. package/dist/mcp-server/transports/auth/lib/authUtils.js +4 -1
  53. package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
  54. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -1
  55. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +1 -1
  56. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -1
  57. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -1
  58. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +2 -5
  59. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -1
  60. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
  61. package/dist/mcp-server/transports/http/httpErrorHandler.js +15 -5
  62. package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
  63. package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
  64. package/dist/mcp-server/transports/http/sessionStore.js +2 -2
  65. package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
  66. package/dist/services/canvas/core/CanvasRegistry.js +1 -1
  67. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  68. package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
  69. package/dist/services/canvas/core/DataCanvas.js +7 -5
  70. package/dist/services/canvas/core/DataCanvas.js.map +1 -1
  71. package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
  72. package/dist/services/canvas/core/canvasFactory.js +2 -2
  73. package/dist/services/canvas/core/canvasFactory.js.map +1 -1
  74. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  75. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +25 -16
  76. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  77. package/dist/services/llm/providers/openrouter.provider.js +1 -1
  78. package/dist/services/llm/providers/openrouter.provider.js.map +1 -1
  79. package/dist/services/speech/providers/elevenlabs.provider.js +3 -3
  80. package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
  81. package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -1
  82. package/dist/services/speech/providers/whisper.provider.js +5 -5
  83. package/dist/services/speech/providers/whisper.provider.js.map +1 -1
  84. package/dist/storage/core/IStorageProvider.d.ts +5 -2
  85. package/dist/storage/core/IStorageProvider.d.ts.map +1 -1
  86. package/dist/storage/core/StorageService.d.ts.map +1 -1
  87. package/dist/storage/core/StorageService.js +3 -6
  88. package/dist/storage/core/StorageService.js.map +1 -1
  89. package/dist/storage/core/providerHelpers.d.ts +29 -8
  90. package/dist/storage/core/providerHelpers.d.ts.map +1 -1
  91. package/dist/storage/core/providerHelpers.js +49 -11
  92. package/dist/storage/core/providerHelpers.js.map +1 -1
  93. package/dist/storage/core/storageFactory.d.ts.map +1 -1
  94. package/dist/storage/core/storageFactory.js +12 -15
  95. package/dist/storage/core/storageFactory.js.map +1 -1
  96. package/dist/storage/core/storageValidation.d.ts +13 -13
  97. package/dist/storage/core/storageValidation.d.ts.map +1 -1
  98. package/dist/storage/core/storageValidation.js +49 -125
  99. package/dist/storage/core/storageValidation.js.map +1 -1
  100. package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
  101. package/dist/storage/providers/cloudflare/d1Provider.js +9 -7
  102. package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
  103. package/dist/storage/providers/cloudflare/kvProvider.d.ts +2 -0
  104. package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
  105. package/dist/storage/providers/cloudflare/kvProvider.js +12 -10
  106. package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -1
  107. package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
  108. package/dist/storage/providers/cloudflare/r2Provider.js +11 -8
  109. package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
  110. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +1 -0
  111. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
  112. package/dist/storage/providers/fileSystem/fileSystemProvider.js +14 -12
  113. package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -1
  114. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +6 -1
  115. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
  116. package/dist/storage/providers/inMemory/inMemoryProvider.js +15 -10
  117. package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
  118. package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -1
  119. package/dist/storage/providers/supabase/supabaseProvider.js +5 -1
  120. package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -1
  121. package/dist/testing/fuzz.d.ts.map +1 -1
  122. package/dist/testing/fuzz.js +7 -1
  123. package/dist/testing/fuzz.js.map +1 -1
  124. package/dist/testing/index.d.ts +21 -6
  125. package/dist/testing/index.d.ts.map +1 -1
  126. package/dist/testing/index.js +57 -10
  127. package/dist/testing/index.js.map +1 -1
  128. package/dist/types-global/errors.d.ts +7 -4
  129. package/dist/types-global/errors.d.ts.map +1 -1
  130. package/dist/types-global/errors.js.map +1 -1
  131. package/dist/utils/formatting/codeSpan.d.ts +27 -0
  132. package/dist/utils/formatting/codeSpan.d.ts.map +1 -0
  133. package/dist/utils/formatting/codeSpan.js +42 -0
  134. package/dist/utils/formatting/codeSpan.js.map +1 -0
  135. package/dist/utils/formatting/diffFormatter.d.ts.map +1 -1
  136. package/dist/utils/formatting/diffFormatter.js +7 -15
  137. package/dist/utils/formatting/diffFormatter.js.map +1 -1
  138. package/dist/utils/formatting/markdownBuilder.d.ts +12 -5
  139. package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -1
  140. package/dist/utils/formatting/markdownBuilder.js +14 -2
  141. package/dist/utils/formatting/markdownBuilder.js.map +1 -1
  142. package/dist/utils/formatting/partialResult.d.ts +28 -2
  143. package/dist/utils/formatting/partialResult.d.ts.map +1 -1
  144. package/dist/utils/formatting/partialResult.js +46 -2
  145. package/dist/utils/formatting/partialResult.js.map +1 -1
  146. package/dist/utils/formatting/tableFormatter.d.ts.map +1 -1
  147. package/dist/utils/formatting/tableFormatter.js +5 -9
  148. package/dist/utils/formatting/tableFormatter.js.map +1 -1
  149. package/dist/utils/formatting/treeFormatter.d.ts.map +1 -1
  150. package/dist/utils/formatting/treeFormatter.js +5 -9
  151. package/dist/utils/formatting/treeFormatter.js.map +1 -1
  152. package/dist/utils/internal/error-handler/errorHandler.d.ts +21 -8
  153. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
  154. package/dist/utils/internal/error-handler/errorHandler.js +70 -38
  155. package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
  156. package/dist/utils/internal/error-handler/mappings.d.ts +18 -1
  157. package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -1
  158. package/dist/utils/internal/error-handler/mappings.js +23 -1
  159. package/dist/utils/internal/error-handler/mappings.js.map +1 -1
  160. package/dist/utils/internal/error-handler/types.d.ts +2 -0
  161. package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
  162. package/dist/utils/internal/logger.d.ts +75 -3
  163. package/dist/utils/internal/logger.d.ts.map +1 -1
  164. package/dist/utils/internal/logger.js +181 -52
  165. package/dist/utils/internal/logger.js.map +1 -1
  166. package/dist/utils/internal/performance.d.ts +16 -1
  167. package/dist/utils/internal/performance.d.ts.map +1 -1
  168. package/dist/utils/internal/performance.js +59 -20
  169. package/dist/utils/internal/performance.js.map +1 -1
  170. package/dist/utils/network/fetchWithTimeout.d.ts +11 -5
  171. package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
  172. package/dist/utils/network/fetchWithTimeout.js +50 -23
  173. package/dist/utils/network/fetchWithTimeout.js.map +1 -1
  174. package/dist/utils/network/retry.d.ts +16 -8
  175. package/dist/utils/network/retry.d.ts.map +1 -1
  176. package/dist/utils/network/retry.js +19 -8
  177. package/dist/utils/network/retry.js.map +1 -1
  178. package/dist/utils/overflow/outlineOnOverflow.d.ts +18 -2
  179. package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
  180. package/dist/utils/overflow/outlineOnOverflow.js +28 -3
  181. package/dist/utils/overflow/outlineOnOverflow.js.map +1 -1
  182. package/dist/utils/pagination/pagination.d.ts +3 -1
  183. package/dist/utils/pagination/pagination.d.ts.map +1 -1
  184. package/dist/utils/pagination/pagination.js +10 -2
  185. package/dist/utils/pagination/pagination.js.map +1 -1
  186. package/dist/utils/parsing/csvParser.d.ts.map +1 -1
  187. package/dist/utils/parsing/csvParser.js +4 -2
  188. package/dist/utils/parsing/csvParser.js.map +1 -1
  189. package/dist/utils/parsing/htmlExtractor.js +1 -1
  190. package/dist/utils/parsing/htmlExtractor.js.map +1 -1
  191. package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
  192. package/dist/utils/parsing/jsonParser.js +3 -1
  193. package/dist/utils/parsing/jsonParser.js.map +1 -1
  194. package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
  195. package/dist/utils/parsing/xmlParser.js +3 -1
  196. package/dist/utils/parsing/xmlParser.js.map +1 -1
  197. package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
  198. package/dist/utils/parsing/yamlParser.js +3 -1
  199. package/dist/utils/parsing/yamlParser.js.map +1 -1
  200. package/dist/utils/security/idGenerator.d.ts.map +1 -1
  201. package/dist/utils/security/idGenerator.js +20 -4
  202. package/dist/utils/security/idGenerator.js.map +1 -1
  203. package/dist/utils/security/sanitization.d.ts +46 -15
  204. package/dist/utils/security/sanitization.d.ts.map +1 -1
  205. package/dist/utils/security/sanitization.js +203 -96
  206. package/dist/utils/security/sanitization.js.map +1 -1
  207. package/dist/utils/telemetry/attributes.d.ts +16 -1
  208. package/dist/utils/telemetry/attributes.d.ts.map +1 -1
  209. package/dist/utils/telemetry/attributes.js +16 -1
  210. package/dist/utils/telemetry/attributes.js.map +1 -1
  211. package/dist/utils/telemetry/instrumentation.d.ts +13 -3
  212. package/dist/utils/telemetry/instrumentation.d.ts.map +1 -1
  213. package/dist/utils/telemetry/instrumentation.js +104 -17
  214. package/dist/utils/telemetry/instrumentation.js.map +1 -1
  215. package/framework-skills/add-app-tool/SKILL.md +12 -18
  216. package/framework-skills/add-prompt/SKILL.md +3 -1
  217. package/framework-skills/add-provider/SKILL.md +14 -4
  218. package/framework-skills/add-resource/SKILL.md +3 -3
  219. package/framework-skills/add-tool/SKILL.md +22 -7
  220. package/framework-skills/api-auth/SKILL.md +3 -1
  221. package/framework-skills/api-canvas/SKILL.md +4 -4
  222. package/framework-skills/api-config/SKILL.md +9 -5
  223. package/framework-skills/api-context/SKILL.md +10 -7
  224. package/framework-skills/api-errors/SKILL.md +20 -11
  225. package/framework-skills/api-linter/SKILL.md +14 -10
  226. package/framework-skills/api-telemetry/SKILL.md +38 -13
  227. package/framework-skills/api-testing/SKILL.md +25 -15
  228. package/framework-skills/api-utils/SKILL.md +10 -10
  229. package/framework-skills/api-utils/references/formatting.md +1 -1
  230. package/framework-skills/api-utils/references/parsing.md +2 -2
  231. package/framework-skills/api-utils/references/security.md +13 -10
  232. package/framework-skills/code-simplifier/SKILL.md +31 -18
  233. package/framework-skills/design-mcp-server/SKILL.md +62 -35
  234. package/framework-skills/git-wrapup/SKILL.md +19 -10
  235. package/framework-skills/maintenance/SKILL.md +3 -3
  236. package/framework-skills/orchestrations/SKILL.md +1 -1
  237. package/framework-skills/orchestrations/workflows/greenfield-build.md +15 -8
  238. package/framework-skills/polish-docs-meta/SKILL.md +2 -2
  239. package/framework-skills/polish-docs-meta/references/package-meta.md +1 -1
  240. package/framework-skills/polish-docs-meta/references/readme.md +4 -3
  241. package/framework-skills/release-and-publish/SKILL.md +7 -5
  242. package/framework-skills/release-pr-review/SKILL.md +18 -1
  243. package/framework-skills/report-issue-framework/SKILL.md +2 -2
  244. package/framework-skills/report-issue-local/SKILL.md +3 -3
  245. package/framework-skills/security-pass/SKILL.md +11 -3
  246. package/framework-skills/techniques/SKILL.md +1 -1
  247. package/framework-skills/techniques/references/outline-on-overflow.md +12 -7
  248. package/framework-skills/tool-defs-analysis/SKILL.md +3 -3
  249. package/package.json +30 -36
  250. package/scripts/check-skill-versions.ts +103 -22
  251. package/scripts/devcheck.ts +4 -3
  252. package/scripts/lint-packaging.ts +38 -1
  253. package/templates/.env.example +7 -1
  254. package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +1 -1
  255. package/templates/AGENTS.md +2 -2
  256. package/templates/CLAUDE.md +2 -2
  257. package/templates/Dockerfile +30 -10
  258. package/templates/package.json +4 -3
  259. package/templates/src/mcp-server/prompts/definitions/echo.prompt.ts +2 -4
  260. package/templates/src/mcp-server/resources/definitions/echo-app-ui.app-resource.ts +51 -14
  261. package/templates/src/mcp-server/resources/definitions/echo.resource.ts +1 -1
  262. package/templates/src/mcp-server/tools/definitions/echo-app.app-tool.ts +2 -3
  263. package/templates/src/mcp-server/tools/definitions/echo.tool.ts +1 -1
  264. package/dist/utils/telemetry/index.d.ts +0 -12
  265. package/dist/utils/telemetry/index.d.ts.map +0 -1
  266. package/dist/utils/telemetry/index.js +0 -12
  267. package/dist/utils/telemetry/index.js.map +0 -1
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.13.6
4
+ **Version:** 0.13.8
5
5
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
7
7
  **Zod:** ^4.6.5
@@ -323,7 +323,7 @@ Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `
323
323
 
324
324
  ### `ctx.state`
325
325
 
326
- Tenant-scoped KV. Accepts any serializable value — no manual `JSON.stringify`/`JSON.parse` needed.
326
+ Tenant-scoped KV. Accepts any JSON-serializable value — no manual `JSON.stringify`/`JSON.parse` needed — and reads return its JSON form (a `Date` comes back as its ISO string, a `Map` as `{}`), never the object that was written. A `bigint`, a cyclic reference, or a top-level `undefined`, function, or symbol rejects with `McpError(SerializationError)` before anything is written.
327
327
 
328
328
  ```ts
329
329
  await ctx.state.set('item/123', { name: 'Widget', count: 42 });
@@ -416,7 +416,7 @@ Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`,
416
416
 
417
417
  For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { service, data })` from `/utils` — maps the full status table (401/403/408/422/429/5xx) and captures body + `Retry-After`.
418
418
 
419
- **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`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
419
+ **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`.
420
420
 
421
421
  **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)` for whichever of `data.reason` / `data.retryable` 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. `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.
422
422
 
@@ -483,7 +483,7 @@ describe('myTool', () => {
483
483
 
484
484
  **`createMockContext` options:** `createMockContext()` (state included), `{ tenantId: 'test-tenant' }` (explicit tenant; defaults to `'default'`, as stdio resolves it), `{ errors: myTool.errors }` (typed `ctx.fail`), `{ inputResponses }` / `{ requestState }` (seed `ctx.inputs` to drive a multi-round-trip handler's second round directly), plus `auth`, `sessionId`, `signal`, `requestId`, `uri`, and the four `notify*` callbacks.
485
485
 
486
- **`ctx.state` in tests is the production path.** The mock backs it with a real `StorageService` over an `InMemoryProvider`, so key validation (`[a-zA-Z0-9_.\-/]+` — colons rejected) and TTL expiry behave exactly as they do in a deployment. Passing `errors` narrows the return type to `HandlerContext<ReasonOf<…>>`, which is what a definition declaring a contract types its handler's `ctx` as — so `definition.handler(input, ctx)` typechecks.
486
+ **`ctx.state` in tests is the production path.** The mock backs it with a real `StorageService` over an `InMemoryProvider`, so key validation (`[a-zA-Z0-9_.\-/]+` — colons rejected), the JSON round-trip of every value, and TTL expiry behave exactly as they do in a deployment. Passing `errors` narrows the return type to `HandlerContext<ReasonOf<…>>`, which is what a definition declaring a contract types its handler's `ctx` as — so `definition.handler(input, ctx)` typechecks.
487
487
 
488
488
  **HTTP/session fixtures:** `createFetchMock(routes)` provides a strict fetch-compatible fake with ordered routes, captured `Request` objects, one-shot responses, and optional global install/restore. `createMockSession(options)` returns `{ sessionId, tenantId, ctx }` for handlers that branch on durable HTTP session identity.
489
489
 
@@ -520,7 +520,7 @@ Detailed method signatures, options, and examples live in skill files. Read the
520
520
 
521
521
  Each `framework-skills/<name>/SKILL.md` carries `metadata.version` in frontmatter. The `maintenance` skill's Phase A uses this to sync consumer copies — replaces the **entire skill directory** as one unit. Without a version bump, Phase A skips the skill (content-hash backstop catches drift, but noisier).
522
522
 
523
- **Policy:** Bump `metadata.version` when changing any file under `framework-skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. One bump per release cycle suffices. Enforced by `bun run devcheck` (`scripts/check-skill-versions.ts`): a SKILL.md body change vs `HEAD` without a `metadata.version` bump surfaces as a warning; whitespace-only edits never trigger it, and a genuine typo fix opts out via `devcheck.config.json` `skillVersions.ignore`.
523
+ **Policy:** Bump `metadata.version` when changing any file under `framework-skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. **Exactly one step per release, however many edits land:** before bumping, compare the version against the last release tag (`git show $(git describe --tags --abbrev=0):framework-skills/<name>/SKILL.md`) and skip the bump when it has already moved. Enforced by `bun run devcheck` (`scripts/check-skill-versions.ts`), both directions as warnings: a SKILL.md body change vs `HEAD` without a `metadata.version` bump while the version still matches the last release tag, and a version more than one step (next minor, or next major at `.0`) past the last release tag. Whitespace-only edits never trigger the first, and a genuine typo fix opts out via `devcheck.config.json` `skillVersions.ignore`.
524
524
 
525
525
  Skills live in `framework-skills/<name>/SKILL.md`; the full list is discoverable via the agent's skill registry at session start. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and these are development-time skills, not skills for the agents that use a server. `skills/` stays free for that second kind.
526
526
 
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.13.6
4
+ **Version:** 0.13.8
5
5
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
7
7
  **Zod:** ^4.6.5
@@ -323,7 +323,7 @@ Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `
323
323
 
324
324
  ### `ctx.state`
325
325
 
326
- Tenant-scoped KV. Accepts any serializable value — no manual `JSON.stringify`/`JSON.parse` needed.
326
+ Tenant-scoped KV. Accepts any JSON-serializable value — no manual `JSON.stringify`/`JSON.parse` needed — and reads return its JSON form (a `Date` comes back as its ISO string, a `Map` as `{}`), never the object that was written. A `bigint`, a cyclic reference, or a top-level `undefined`, function, or symbol rejects with `McpError(SerializationError)` before anything is written.
327
327
 
328
328
  ```ts
329
329
  await ctx.state.set('item/123', { name: 'Widget', count: 42 });
@@ -416,7 +416,7 @@ Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`,
416
416
 
417
417
  For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { service, data })` from `/utils` — maps the full status table (401/403/408/422/429/5xx) and captures body + `Retry-After`.
418
418
 
419
- **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`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
419
+ **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`.
420
420
 
421
421
  **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)` for whichever of `data.reason` / `data.retryable` 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. `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.
422
422
 
@@ -483,7 +483,7 @@ describe('myTool', () => {
483
483
 
484
484
  **`createMockContext` options:** `createMockContext()` (state included), `{ tenantId: 'test-tenant' }` (explicit tenant; defaults to `'default'`, as stdio resolves it), `{ errors: myTool.errors }` (typed `ctx.fail`), `{ inputResponses }` / `{ requestState }` (seed `ctx.inputs` to drive a multi-round-trip handler's second round directly), plus `auth`, `sessionId`, `signal`, `requestId`, `uri`, and the four `notify*` callbacks.
485
485
 
486
- **`ctx.state` in tests is the production path.** The mock backs it with a real `StorageService` over an `InMemoryProvider`, so key validation (`[a-zA-Z0-9_.\-/]+` — colons rejected) and TTL expiry behave exactly as they do in a deployment. Passing `errors` narrows the return type to `HandlerContext<ReasonOf<…>>`, which is what a definition declaring a contract types its handler's `ctx` as — so `definition.handler(input, ctx)` typechecks.
486
+ **`ctx.state` in tests is the production path.** The mock backs it with a real `StorageService` over an `InMemoryProvider`, so key validation (`[a-zA-Z0-9_.\-/]+` — colons rejected), the JSON round-trip of every value, and TTL expiry behave exactly as they do in a deployment. Passing `errors` narrows the return type to `HandlerContext<ReasonOf<…>>`, which is what a definition declaring a contract types its handler's `ctx` as — so `definition.handler(input, ctx)` typechecks.
487
487
 
488
488
  **HTTP/session fixtures:** `createFetchMock(routes)` provides a strict fetch-compatible fake with ordered routes, captured `Request` objects, one-shot responses, and optional global install/restore. `createMockSession(options)` returns `{ sessionId, tenantId, ctx }` for handlers that branch on durable HTTP session identity.
489
489
 
@@ -520,7 +520,7 @@ Detailed method signatures, options, and examples live in skill files. Read the
520
520
 
521
521
  Each `framework-skills/<name>/SKILL.md` carries `metadata.version` in frontmatter. The `maintenance` skill's Phase A uses this to sync consumer copies — replaces the **entire skill directory** as one unit. Without a version bump, Phase A skips the skill (content-hash backstop catches drift, but noisier).
522
522
 
523
- **Policy:** Bump `metadata.version` when changing any file under `framework-skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. One bump per release cycle suffices. Enforced by `bun run devcheck` (`scripts/check-skill-versions.ts`): a SKILL.md body change vs `HEAD` without a `metadata.version` bump surfaces as a warning; whitespace-only edits never trigger it, and a genuine typo fix opts out via `devcheck.config.json` `skillVersions.ignore`.
523
+ **Policy:** Bump `metadata.version` when changing any file under `framework-skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. **Exactly one step per release, however many edits land:** before bumping, compare the version against the last release tag (`git show $(git describe --tags --abbrev=0):framework-skills/<name>/SKILL.md`) and skip the bump when it has already moved. Enforced by `bun run devcheck` (`scripts/check-skill-versions.ts`), both directions as warnings: a SKILL.md body change vs `HEAD` without a `metadata.version` bump while the version still matches the last release tag, and a version more than one step (next minor, or next major at `.0`) past the last release tag. Whitespace-only edits never trigger the first, and a genuine typo fix opts out via `devcheck.config.json` `skillVersions.ignore`.
524
524
 
525
525
  Skills live in `framework-skills/<name>/SKILL.md`; the full list is discoverable via the agent's skill registry at session start. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and these are development-time skills, not skills for the agents that use a server. `skills/` stays free for that second kind.
526
526
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  <div align="center">
2
2
  <h1>@cyanheads/mcp-ts-core</h1>
3
- <p><b>Agent-native TypeScript framework for building MCP servers. Includes runtime infrastructure and agent skills for building, testing, and shipping servers.</b></p>
4
- <p>Give your agent the infrastructure, patterns, and skills to build and ship your server.</p>
3
+ <p><b>Agent-native TypeScript framework for building MCP servers.</b></p>
4
+ <p>Runtime infrastructure for your server, and the agent skills to build, test, and ship it.</p>
5
5
  </div>
6
6
 
7
7
  <div align="center">
8
8
 
9
- [![Version](https://img.shields.io/badge/Version-0.13.6-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2026--07--28-8A2BE2.svg?style=flat-square)](https://modelcontextprotocol.io/specification/2026-07-28)
9
+ [![Version](https://img.shields.io/badge/Version-0.13.8-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2026--07--28-8A2BE2.svg?style=flat-square)](https://modelcontextprotocol.io/specification/2026-07-28)
10
10
 
11
11
  [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
12
12
 
@@ -16,17 +16,17 @@
16
16
 
17
17
  ---
18
18
 
19
- ## Build AI tools for anything you can describe.
19
+ ## Build AI tools for anything you can describe
20
20
 
21
- Connect an API, a dataset, or a workflow to an AI agent through the Model Context Protocol (MCP). Your project holds the domain code; `@cyanheads/mcp-ts-core` provides the auth, storage, logging, and deployment underneath it.
21
+ Connect an API, a dataset, or a workflow to an AI agent through the Model Context Protocol (MCP). Your project holds the domain code; `@cyanheads/mcp-ts-core` handles the auth, storage, logging, and transports underneath it.
22
22
 
23
- **Agent-native means your agent knows what to do.** Every scaffold includes framework documentation and Agent Skills: reusable workflows for designing tools, writing tests, reviewing security, and publishing releases. You decide what the server should do; your agent has the patterns and checks to help implement it.
23
+ **Agent-native.** Every scaffold ships the framework reference and a set of Agent Skills: workflows for designing tools, writing tests, reviewing security, and cutting releases. You decide what the server does; your agent follows the skills to build it.
24
24
 
25
- **The framework stays a dependency.** Infrastructure fixes arrive through package upgrades — run the `maintenance` skill and your agent updates core, pulls the latest skills, and integrates them into your project.
25
+ **The framework stays a dependency.** Infrastructure fixes arrive as package upgrades. Run the `maintenance` skill and your agent bumps core, syncs the latest skills, and adopts what changed.
26
26
 
27
27
  ## Quick start
28
28
 
29
- Servers can run on Bun, Node.js 24 or later, or Cloudflare Workers.
29
+ Servers run on Bun, Node.js 24+, or Cloudflare Workers.
30
30
 
31
31
  ```bash
32
32
  bunx @cyanheads/mcp-ts-core init my-mcp-server
@@ -34,17 +34,15 @@ cd my-mcp-server
34
34
  bun install
35
35
  ```
36
36
 
37
- Open the project in Claude Code, Codex, or your preferred agent and give it a concrete starting point:
37
+ The scaffold includes a source tree, build and test configuration, `CLAUDE.md`/`AGENTS.md`, Agent Skills, and plugin metadata for Claude Code and Codex. Open it in Claude Code, Codex, or another agent and describe what you want:
38
38
 
39
39
  > Build an MCP server for my team's inventory API. We need to find products, check stock across warehouses, investigate stock movements, and record adjustments and transfers. Let's get started.
40
40
 
41
- The scaffold includes a source tree, build and test configuration, `CLAUDE.md`/`AGENTS.md`, Agent Skills, and plugin metadata for Claude Code and Codex.
42
-
43
- Already have a TypeScript project? Install the framework directly with `bun add @cyanheads/mcp-ts-core` and register your definitions with `createApp()`.
41
+ Already have a TypeScript project? Run `bun add @cyanheads/mcp-ts-core` and register your definitions with `createApp()`.
44
42
 
45
43
  ## A tool is a schema and a function
46
44
 
47
- Here's a complete server that searches a small catalog. To try it in the scaffolded project, replace `src/index.ts` with:
45
+ This is a complete server that searches a three-item catalog. To try it, replace the scaffold's `src/index.ts` with it:
48
46
 
49
47
  ```ts
50
48
  import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
@@ -79,7 +77,7 @@ bun run rebuild
79
77
  bun run start:http
80
78
  ```
81
79
 
82
- Connect your MCP client to `http://127.0.0.1:3010/mcp` (Streamable HTTP), or configure stdio with `bun /absolute/path/to/dist/index.js`.
80
+ Point your MCP client at `http://127.0.0.1:3010/mcp` (Streamable HTTP), or have the client launch it over stdio with `bun /absolute/path/to/dist/index.js`.
83
81
 
84
82
  ## What comes with it
85
83
 
@@ -91,13 +89,13 @@ Connect your MCP client to `http://127.0.0.1:3010/mcp` (Streamable HTTP), or con
91
89
  | Run locally or host a service | stdio and HTTP on Bun/Node.js; a separate entry point for Cloudflare Workers |
92
90
  | Understand failures and catch mistakes | Structured logs, optional OpenTelemetry, definition linting, contract tests, and fuzz testing |
93
91
 
94
- Optional integrations such as DuckDB, Supabase, and the OpenTelemetry SDK are peer dependencies, installed when you need them.
92
+ Optional integrations (DuckDB, Supabase, the OpenTelemetry SDK) are peer dependencies; install them when you need them.
95
93
 
96
94
  ## Give agents useful results
97
95
 
98
- Use `enrichment` and `ctx.enrich()` for result context such as totals, applied filters, and empty-result notices. Declare failures and recovery guidance in `errors`, then throw with the typed `ctx.fail()`. Both contracts are visible to clients before a call.
96
+ Two declared contracts shape what an agent gets back. `enrichment` carries success-path context (totals, the parsed query, empty-result notices), populated with `ctx.enrich()`. `errors` lists each expected failure with its recovery guidance, and the handler throws one with the typed `ctx.fail()`.
99
97
 
100
- Here, `runSearch(query, limit)` returns `{ items, total, parsed }` (matches, total before the limit, and parsed query), or `null` if the index is unavailable:
98
+ `runSearch(query, limit)` stands in for your search backend. It returns `{ items, total, parsed }`, or `null` when the index is down:
101
99
 
102
100
  ```ts
103
101
  import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
@@ -143,13 +141,13 @@ const search = tool('search', {
143
141
  await createApp({ tools: [search] });
144
142
  ```
145
143
 
146
- Enrichment and error contracts are advertised through `tools/list` and checked by the definition linter. `ctx.recoveryFor()` includes the declared recovery hint in the error response.
144
+ Both contracts are advertised in `tools/list`, so clients see them before calling, and the definition linter checks the handler against them. `ctx.recoveryFor()` adds the declared recovery hint to the error response.
147
145
 
148
146
  ### Same data across client surfaces
149
147
 
150
- MCP hosts differ in what they expose to the agent: some use `content[]`, some use `structuredContent`, and some use both. The framework keeps tool-result data in sync across both surfaces, so the agent receives the same information whichever one its host exposes. `structuredContent` carries structured JSON; `content[]` carries the same data as text.
148
+ MCP hosts differ in which part of a tool result they hand the agent: `structuredContent` (JSON), `content[]` (text), or both. The framework fills both with the same data, so the agent sees the same result on any host.
151
149
 
152
- `format()` controls the text representation, and the format-parity linter enforces that every output field is represented. Without a custom formatter, the framework uses JSON text. Declared enrichment is mirrored into both surfaces automatically. For example, this formatter presents the item names as a markdown list:
150
+ `format()` renders the text side; without one, `content[]` gets JSON. The format-parity lint rule fails `lint:mcp` if any output field is missing from the rendered text. Enrichment needs no `format()` entry, because the framework adds it to both surfaces. This formatter renders the items as a markdown list:
153
151
 
154
152
  ```ts
155
153
  format: (result) => [{
@@ -162,7 +160,7 @@ format: (result) => [{
162
160
 
163
161
  ### Resources
164
162
 
165
- Resources expose data at a URI. This definition delegates the lookup to your own `getItem()` service:
163
+ Resources expose data at a URI. This one reads from your own `getItem()` service:
166
164
 
167
165
  ```ts
168
166
  import { resource, z } from '@cyanheads/mcp-ts-core';
@@ -183,7 +181,7 @@ Everything registers through `createApp()` in your entry point:
183
181
  ```ts
184
182
  await createApp({
185
183
  name: 'my-mcp-server',
186
- version: '0.1.0',
184
+ title: 'my-mcp-server', // display name in client UIs
187
185
  tools: allToolDefinitions,
188
186
  resources: allResourceDefinitions,
189
187
  prompts: allPromptDefinitions,
@@ -191,16 +189,17 @@ await createApp({
191
189
  });
192
190
  ```
193
191
 
194
- It also works on Cloudflare Workers with `createWorkerHandler()` — same definitions, different entry point.
192
+ On Cloudflare Workers, `createWorkerHandler()` takes the same definitions from a separate entry point.
195
193
 
196
194
  ## Runtime and integration details
197
195
 
198
- - **Auth and storage:** Declare `auth: ['scope']` on a definition to check access before dispatch. Choose JWT or OAuth authentication. Tenant-scoped `ctx.state` supports in-memory, filesystem, Supabase, and Cloudflare D1/KV/R2 storage; select the backend through configuration.
199
- - **Client interaction:** Return `ctx.requestInput(...)` to request confirmation, model sampling, or the client's roots. The handler runs again with responses available on `ctx.inputs`.
200
- - **Protocol compatibility:** HTTP supports the 2026-07-28 revision's per-request `_meta` envelope and session-based 2025-era clients. The SDK's compatibility layer handles input requests for older clients.
201
- - **Server presentation:** `instructions` provides guidance during initialization without repeating it in every tool description. Identity fields such as `title`, `websiteUrl`, `description`, and `icons` populate client server information, the `/.well-known/mcp.json` server card, and the HTTP landing page.
202
- - **Definition checks:** `lint:mcp` checks names, schemas, scopes, annotations, format parity, and JSON Schema portability at build time. These checks do not run at server startup.
203
- - **DataCanvas:** An optional DuckDB workspace for SQL queries across API results and CSV/Parquet/JSON exports. Agents can share a workspace through an opaque canvas token. Enable it with `CANVAS_PROVIDER_TYPE=duckdb` and install `@duckdb/node-api`; it requires Bun or Node.js. See [brapi-mcp-server](https://github.com/cyanheads/brapi-mcp-server#working-with-dataframes) for a walkthrough of loading API results into a dataframe and querying them with SQL.
196
+ - **Auth and storage:** Declare `auth: ['scope']` on a definition and the scope is checked, under JWT or OAuth, before the handler runs. `ctx.state` is tenant-scoped storage over in-memory, filesystem, Supabase, or Cloudflare D1/KV/R2, chosen by config.
197
+ - **Client interaction:** Return `ctx.requestInput(...)` to ask the user for input, the client's model for a sample, or the client for its roots. The handler runs again with the answers on `ctx.inputs`.
198
+ - **Protocol compatibility:** HTTP serves 2026-07-28 clients (per-request `_meta` envelope) and session-based 2025-era clients. The SDK's compatibility layer handles input requests for the older ones.
199
+ - **Server presentation:** `instructions` gives the model server-wide guidance once, at `initialize`, instead of in every tool description. Identity fields (`title`, `websiteUrl`, `description`, `icons`) populate the client's server info, the `/.well-known/mcp.json` server card, and the HTTP landing page.
200
+ - **Definition checks:** `lint:mcp` checks names, schemas, scopes, annotations, format parity, and JSON Schema portability. It runs at build time, never at startup, so a new rule can't break a deployed server.
201
+ - **DataCanvas:** An optional DuckDB workspace where agents run SQL across staged API results and export CSV, Parquet, or JSON. Agents share a workspace by passing its canvas token. Enable it with `CANVAS_PROVIDER_TYPE=duckdb` and `@duckdb/node-api` (Bun or Node.js only). [brapi-mcp-server](https://github.com/cyanheads/brapi-mcp-server#working-with-dataframes) walks through loading API results into a dataframe and querying it.
202
+ - **Mirror:** The `/mirror` module keeps a persistent local copy of a bulk upstream dataset in embedded SQLite with an optional FTS5 index, so tools query it locally instead of paging the live API on every call. You write the `sync` ingester and the schema; the framework handles storage, resumable initial loads, and incremental refreshes. Bun or Node.js only (`better-sqlite3` is an optional peer on Node). [faa-aircraft-registry-mcp-server](https://github.com/cyanheads/faa-aircraft-registry-mcp-server) serves the full FAA registry this way.
204
203
 
205
204
  See the [framework reference](CLAUDE.md) for configuration and handler patterns, and the [observability guide](docs/telemetry/observability.md) for Pino logging and OpenTelemetry traces and metrics.
206
205
 
@@ -221,14 +220,14 @@ my-mcp-server/
221
220
  prompts/definitions/ # Prompt definitions (.prompt.ts)
222
221
  package.json
223
222
  tsconfig.json # extends @cyanheads/mcp-ts-core/tsconfig.base.json
224
- CLAUDE.md / AGENTS.md # Point to core's CLAUDE.md / AGENTS.md for framework docs
223
+ CLAUDE.md / AGENTS.md # Server conventions; points to core's framework reference
225
224
  ```
226
225
 
227
- Framework infrastructure lives in `node_modules`; your source tree contains the server's definitions, configuration, and domain services.
226
+ Framework infrastructure lives in `node_modules`; your source tree holds the server's definitions, configuration, and domain services.
228
227
 
229
228
  ## Configuration
230
229
 
231
- All core config is Zod-validated from environment variables. Server-specific config uses a separate Zod schema with lazy parsing.
230
+ Core config comes from environment variables, validated with Zod. Server-specific variables get their own schema, parsed lazily so Workers can inject env at request time.
232
231
 
233
232
  | Variable | Description | Default |
234
233
  |:---------|:------------|:--------|
@@ -240,7 +239,9 @@ All core config is Zod-validated from environment variables. Server-specific con
240
239
  | `STORAGE_PROVIDER_TYPE` | `in-memory`, `filesystem`, `supabase`, `cloudflare-d1`/`kv`/`r2` | `in-memory` |
241
240
  | `CANVAS_PROVIDER_TYPE` | `none` or `duckdb` (optional peer dependency `@duckdb/node-api`) | `none` |
242
241
  | `OTEL_ENABLED` | Enable OpenTelemetry | `false` |
243
- | `OPENROUTER_API_KEY` | OpenRouter LLM API key | — |
242
+ | `LOG_TOOL_FAILURE_PAYLOADS` | Log each failed tool call's arguments and result, redacted by key name (a secret inside a free-form value is not caught) | `false` |
243
+ | `LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES` | Cap per logged payload, in UTF-8 bytes | `16384` |
244
+ | `OPENROUTER_API_KEY` | API key for the optional OpenRouter LLM provider (`/services`) | — |
244
245
 
245
246
  See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the full configuration reference.
246
247
 
@@ -250,7 +251,7 @@ See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the full configuration reference.
250
251
 
251
252
  | Function | Purpose |
252
253
  |:---------|:--------|
253
- | `createApp(options)` | Bun or Node.js server — handles full lifecycle |
254
+ | `createApp(options)` | Bun or Node.js server; manages startup and shutdown |
254
255
  | `createWorkerHandler(options)` | Cloudflare Workers — returns an `ExportedHandler` |
255
256
 
256
257
  ### Builders
@@ -265,7 +266,7 @@ See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the full configuration reference.
265
266
 
266
267
  ### Context
267
268
 
268
- Handlers receive a shared `Context`, with typed helpers for declared enrichment and error contracts:
269
+ Tool and resource handlers receive a `Context`. `ctx.enrich` and `ctx.fail` are typed against the definition's declared contracts:
269
270
 
270
271
  | Property | Type | Description |
271
272
  |:---------|:-----|:------------|
@@ -276,7 +277,7 @@ Handlers receive a shared `Context`, with typed helpers for declared enrichment
276
277
  | `ctx.enrich` | `Enrich` / `TypedEnrich<E>` | Add declared result context to structured output and text content |
277
278
  | `ctx.content` | `ContentCollect` | Attach image/audio blocks to `content[]` — `content.image(data, mimeType)`, `content.audio(...)`, or a raw block |
278
279
  | `ctx.fail` | `(reason, msg?, data?) => McpError` | Creates an error for `throw ctx.fail(...)`; available with a declared `errors` contract |
279
- | `ctx.recoveryFor` | `(reason) => object` | Resolves a declared recovery hint to `{ recovery: { hint } }` — spread into `ctx.fail`'s data argument |
280
+ | `ctx.recoveryFor` | `(reason) => object` | Resolves a declared recovery hint to `{ recovery: { hint } }`, for `ctx.fail`'s data argument |
280
281
  | `ctx.signal` | `AbortSignal` | Cancellation signal |
281
282
  | `ctx.notifyResourceUpdated` | `Function?` | Notify subscribed clients a resource changed |
282
283
  | `ctx.notifyResourceListChanged` | `Function?` | Notify clients the resource list changed |
@@ -298,6 +299,7 @@ import { checkScopes } from '@cyanheads/mcp-ts-core/auth';
298
299
  import { markdown, fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
299
300
  import { OpenRouterProvider, GraphService } from '@cyanheads/mcp-ts-core/services';
300
301
  import type { DataCanvas, CanvasInstance } from '@cyanheads/mcp-ts-core/canvas';
302
+ import { defineMirror, sqliteMirrorStore } from '@cyanheads/mcp-ts-core/mirror';
301
303
  import { validateDefinitions } from '@cyanheads/mcp-ts-core/linter';
302
304
  import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
303
305
  import { mcpTest, toolContractSuite } from '@cyanheads/mcp-ts-core/testing/vitest';
@@ -308,21 +310,23 @@ See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the complete exports reference.
308
310
 
309
311
  ## Examples
310
312
 
311
- The `examples/` directory contains a reference server consuming core through public exports, demonstrating core patterns:
313
+ `examples/` holds a reference server built only on the public exports. `examples/index.ts` (Node/Bun) and `examples/worker.ts` (Cloudflare Workers) register the same `definitions/index.ts` barrels.
312
314
 
313
- | Tool | Pattern |
314
- |:-----|:--------|
315
- | `template_echo_message` | Basic tool with `format`, `auth` |
316
- | `template_cat_fact` | External API call, error factories |
317
- | `template_madlibs_elicitation` | `ctx.requestInput` / `ctx.inputs` for multi-round-trip input |
318
- | `template_image_test` | Image content blocks |
319
- | `template_data_explorer` | MCP Apps with a linked HTML UI resource |
315
+ | Kind | Name | Pattern |
316
+ |:-----|:-----|:--------|
317
+ | Tool | `template_echo_message` | `errors[]` contract with `ctx.fail`, `inputAliases`, full-fidelity `format()` |
318
+ | Tool | `template_cat_fact` | `fetchWithTimeout`, a typed not-found contract, enrichment echo |
319
+ | Tool | `template_image_test` | `ctx.content.image` |
320
+ | Tool | `template_madlibs_elicitation` | `return ctx.requestInput`, a declined-input contract with `severity` |
321
+ | Tool | `template_data_explorer` | `appTool`/`appResource`, host theming, `cacheHint` |
322
+ | Resource | `echo://{message}` | Templated resource |
323
+ | Resource | `ui://template-data-explorer/app.html` | UI resource paired with `template_data_explorer` |
324
+ | Prompt | `code_review` | `completable()` argument, `code` argument |
320
325
 
321
326
  ## Testing
322
327
 
323
328
  ```ts
324
329
  import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
325
- import { mcpTest, toolContractSuite } from '@cyanheads/mcp-ts-core/testing/vitest';
326
330
  import { myTool } from '@/mcp-server/tools/definitions/my-tool.tool.js';
327
331
 
328
332
  const ctx = createMockContext();
@@ -330,11 +334,11 @@ const input = myTool.input.parse({ query: 'test' });
330
334
  const result = await myTool.handler(input, ctx);
331
335
  ```
332
336
 
333
- `createMockContext()` provides a recording `log`, a working `state`, and a `signal`. State runs on a real `StorageService` over an in-memory provider — the same key validation and TTL expiry a deployed server applies — scoped to tenant `'default'` unless `{ tenantId }` says otherwise. Pass `{ errors: myTool.errors }` for a typed `ctx.fail` matching the definition's contract, and `{ inputResponses, requestState }` to drive a multi-round-trip handler into its second round.
337
+ `createMockContext()` gives you a recording `log`, a `signal`, and a `state` backed by a real `StorageService` over an in-memory provider, so key validation, TTL expiry, and the JSON round-trip of stored values behave as they do in production: a `Date` reads back as its ISO string, and a value JSON cannot encode rejects. It uses tenant `'default'` unless you pass `{ tenantId }`. Pass `{ errors: myTool.errors }` for a typed `ctx.fail`, or `{ inputResponses, requestState }` to start a multi-round-trip handler at its second round.
334
338
 
335
- `/testing` also exports `createMockSession()` for session-bound contexts, `createFetchMock()` for upstream HTTP boundaries, and `runToolContract()` to drive a definition through schema, handler, formatting, and error-envelope checks. `/testing/vitest` adds the `mcpTest` fixtures (`ctx`, `session`, `fetchMock`, `storage`) and `toolContractSuite()`.
339
+ `/testing` also exports `createMockSession()` for session-bound contexts, `createFetchMock()` as a strict fake for upstream HTTP, and `runToolContract()`, which runs a definition through schema, handler, formatting, and error-envelope checks. `/testing/vitest` adds the `mcpTest` fixtures (`ctx`, `session`, `fetchMock`, `storage`) and `toolContractSuite()`.
336
340
 
337
- For fuzz testing, `/testing/fuzz` uses `fast-check` to generate valid inputs from Zod schemas and adversarial payloads that probe for crashes, data leaks, and prototype pollution:
341
+ `/testing/fuzz` uses `fast-check` to generate valid inputs from your Zod schemas plus adversarial payloads, then checks for crashes, stack-trace leaks, and prototype pollution:
338
342
 
339
343
  ```ts
340
344
  import { fuzzTool } from '@cyanheads/mcp-ts-core/testing/fuzz';
@@ -345,13 +349,13 @@ expect(report.leaks).toHaveLength(0);
345
349
  expect(report.prototypePollution).toBe(false);
346
350
  ```
347
351
 
348
- Also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL_STRINGS` for custom property-based tests.
352
+ It also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL_STRINGS` for custom property-based tests.
349
353
 
350
354
  ## Documentation
351
355
 
352
- - **[CLAUDE.md/AGENTS.md](CLAUDE.md)** — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package and is auto-accessible in your project after `init`.
353
- - **[docs/telemetry/](docs/telemetry/)** — OpenTelemetry: full catalog of spans, metrics, and attributes the framework emits ([observability.md](docs/telemetry/observability.md)), plus an example Grafana dashboard and vendor-agnostic query recipes for Datadog, New Relic, Honeycomb ([dashboards.md](docs/telemetry/dashboards.md)).
354
- - **[CHANGELOG.md](CHANGELOG.md)** — Version history. Each entry includes a summary, migration notes, and links to commits/issues. Directory-based changelogs that work well for Agents. Entries include agent-specific notes per version as needed.
356
+ - **[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`.
357
+ - **[docs/telemetry/](docs/telemetry/)**: every span, metric, and attribute the framework emits ([observability.md](docs/telemetry/observability.md)), plus an example Grafana dashboard and query recipes for Datadog, New Relic, and Honeycomb ([dashboards.md](docs/telemetry/dashboards.md)).
358
+ - **[CHANGELOG.md](CHANGELOG.md)**: version history, indexing one file per release under `changelog/`. Each has a summary, migration notes, and links to commits and issues; releases that need downstream changes carry `agent-notes` for the `maintenance` skill to act on.
355
359
 
356
360
  ## Development
357
361
 
@@ -360,6 +364,7 @@ bun run rebuild # clean + build (scripts/clean.ts + scripts/build.ts)
360
364
  bun run devcheck # full gate: lint/format, typecheck, MCP defs, framework antipatterns, docs/skills/changelog sync, audit, outdated, secrets/TODO scan
361
365
  bun run lint:mcp # validate MCP definitions against spec
362
366
  bun run test:all # rebuild + coverage + Node.js + Workers + integration
367
+ bun run test:package # pack the tarball and consume it as an external project would
363
368
  ```
364
369
 
365
370
  ## License
package/biome.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "useIgnoreFile": true
7
7
  },
8
8
  "files": {
9
- "includes": ["src/**", "scripts/**", "tests/**", "*.json", "*.js", "*.ts"]
9
+ "includes": ["src/**", "scripts/**", "tests/**", "examples/**", "*.json", "*.js", "*.ts"]
10
10
  },
11
11
  "formatter": {
12
12
  "enabled": true,
@@ -0,0 +1,77 @@
1
+ ---
2
+ summary: "Server stack traces no longer reach clients through McpError.data, in-memory storage round-trips values as JSON like every other provider, OTEL_EXPORTER_OTLP_ENDPOINT is honored, and the unused MCP client, ext-apps, and dotenv dependencies are gone."
3
+ breaking: true
4
+ security: true
5
+ agent-notes: |
6
+ Adoption steps for a consumer upgrading from 0.13.6.
7
+
8
+ 1. The framework no longer installs `@modelcontextprotocol/client` (now a
9
+ devDependency), `@modelcontextprotocol/ext-apps`, or `dotenv`. A server
10
+ whose own code or tests import any of them without declaring it adds it —
11
+ tests that drive a `Client` need `bun add -d @modelcontextprotocol/client@^2.0.0`.
12
+ 2. Raise the server's `zod` to `^4.6.5`, the new peer floor, so a single copy
13
+ installs alongside the framework.
14
+ 3. Stored values now round-trip as JSON on every provider, `in-memory` and
15
+ `createMockContext().state` included. Fix code and tests that expect the
16
+ written object back by identity, or a `Date` / `Map` to survive: a `Date`
17
+ reads back as its ISO string, so validate reads with `z.string()`, not
18
+ `z.date()`. A value JSON cannot encode now throws
19
+ `McpError(SerializationError)` on write, and fails a whole `setMany`.
20
+ 4. `McpError.data` from `ErrorHandler.handleError` / `tryCatch` no longer
21
+ carries `originalStack` or `causeChain`; anything reading them there reads
22
+ the log record instead (`rootCause` and `originalMessage` stay). A prompt
23
+ whose `generate()` throws a non-`McpError` now answers with code and
24
+ message only.
25
+ 5. With `OTEL_ENABLED=true`, `OTEL_EXPORTER_OTLP_ENDPOINT` alone now exports
26
+ traces and metrics to `<base>/v1/traces` and `<base>/v1/metrics`. OTel log
27
+ records are never exported, `OTEL_METRICS_EXPORTER` / `OTEL_LOGS_EXPORTER`
28
+ are not consulted, and a deployment with no endpoint set exports nothing
29
+ (it used to send metrics and logs to `localhost:4318`). Confirm a
30
+ deployment that sets only the base endpoint wants traces sent there.
31
+ 6. The `filesystem` provider writes its envelope as compact single-line JSON.
32
+ Existing indented files still read; update anything that inspects the files
33
+ on disk and expects the old formatting.
34
+ 7. `.env` loads through `process.loadEnvFile()` with the same no-override
35
+ rule, but a `.env` that exists and cannot be read now fails startup with a
36
+ `ConfigurationError`.
37
+ 8. Drop `validator` / `@types/validator` if they were installed only for
38
+ `sanitizeUrl` / `sanitizeNumber`. `sanitizeUrl` now passes single-label
39
+ hosts such as `localhost` (loopback IP literals always passed); a server
40
+ that must refuse internal hosts checks the host itself.
41
+ 9. Optional, to match the scaffold: `packageManager` `bun@1.4.2` and
42
+ `oven/bun:1.4.2` Dockerfile tags. The `maintenance` template review picks up
43
+ the `templates/CLAUDE.md` edits (the `ctx.state` line, a bounded `limit`).
44
+ ---
45
+
46
+ # 0.13.7 — 2026-09-25
47
+
48
+ ## Changed
49
+
50
+ - **Stored values round-trip as JSON on every provider** ([#522](https://github.com/cyanheads/mcp-ts-core/issues/522)) — `in-memory`, and so `createMockContext().state`, holds JSON text and parses on each read: a `Date` reads back as its ISO string, a `Map` as `{}`, and a returned object never shares identity with the one written.
51
+ - **The `filesystem` provider writes compact JSON** — the stored envelope is one line; files written by earlier versions still read.
52
+ - **`sanitizeUrl` and `sanitizeNumber` validate without a peer dependency** — built-in checks replace `validator`; `sanitizeNumber` accepts the same plain decimals as before. `sanitizeUrl` now accepts single-label hosts (`localhost`), `_` in host labels, and a trailing-dot host, and refuses a host that parses differently from how it is written (`http://evil.com\@good.com`, which `validator` passed). It stays a format check, not an SSRF guard: loopback and private IP literals pass, as they did before.
53
+ - **Two-segment tool names are scoped to complete-action verbs** ([#248](https://github.com/cyanheads/mcp-ts-core/issues/248)) — `design-mcp-server` and `add-tool` keep `git_pull`-style names, while an object-taking verb (`search`, `get`, `connect`) always carries its noun.
54
+ - **The scaffold's echo app UI follows the host theme** — it reads the MCP Apps style variables over a light/dark baseline. New scaffolds only.
55
+ - **The example server registers through definition barrels** — `examples/index.ts` and `examples/worker.ts` share one `definitions/index.ts` per primitive plus server `instructions`, and `examples/` is now typechecked, linted, and smoke-tested.
56
+ - Skill versions: `add-app-tool` 1.5 → 1.6, `add-prompt` 1.3 → 1.4, `add-provider` 1.1 → 1.2, `add-resource` 1.6 → 1.7, `add-tool` 2.29 → 2.30, `api-canvas` 2.3 → 2.4, `api-config` 1.19 → 1.20, `api-context` 2.5 → 2.6, `api-errors` 1.15 → 1.16, `api-linter` 1.17 → 1.18, `api-telemetry` 1.12 → 1.13, `api-testing` 1.10 → 1.11, `api-utils` 2.11 → 2.12, `code-simplifier` 1.5 → 1.6, `design-mcp-server` 2.28 → 2.29, `git-wrapup` 1.19 → 1.25, `maintenance` 2.8 → 2.9, `orchestrations` 1.10 → 1.11, `polish-docs-meta` 2.17 → 2.18, `release-and-publish` 2.19 → 2.20, `release-pr-review` 1.4 → 1.5, `report-issue-framework` 1.12 → 1.13, `report-issue-local` 1.10 → 1.11, `security-pass` 1.8 → 1.10, `tool-defs-analysis` 1.6 → 1.7.
57
+
58
+ ## Fixed
59
+
60
+ - **`OTEL_EXPORTER_OTLP_ENDPOINT` is honored** ([#523](https://github.com/cyanheads/mcp-ts-core/issues/523)) — it resolves `<base>/v1/traces` and `<base>/v1/metrics` when the signal-specific variable is unset. `NodeSDK` gets explicit metric readers and an empty log-processor list, so its env defaults no longer export outside the framework config.
61
+ - **Unencodable values are rejected on every provider** ([#539](https://github.com/cyanheads/mcp-ts-core/issues/539)) — a `bigint`, a cyclic reference, or a top-level `undefined`, function, or symbol throws `McpError(SerializationError)` before anything is written; one such value fails a whole `setMany`.
62
+ - **A client that disconnects mid-body is `RequestCancelled`** ([#507](https://github.com/cyanheads/mcp-ts-core/issues/507)) — `httpErrorHandler` resolves the error against the inbound request's signal, so the hang-up logs at info with no stack and answers 499 instead of a `-32004` 504.
63
+ - **The default tenant comes from parsed config** ([#520](https://github.com/cyanheads/mcp-ts-core/issues/520)) — a blank or `${…}` `MCP_AUTH_MODE` on HTTP resolves tenant `'default'`, matching the `none` that config reads it as, instead of leaving `ctx.state` without a tenant.
64
+ - **The shipped `tsconfig.base.json` anchors `outDir` and `@/*` to `${configDir}`** ([#521](https://github.com/cyanheads/mcp-ts-core/issues/521)) — a project extending it without restating them emits into its own `dist/`, never into the installed package; `test:package` checks this on TypeScript 7 and 6.
65
+ - **Partial-success telemetry reads `partialResultSchema()`'s own keys** ([#524](https://github.com/cyanheads/mcp-ts-core/issues/524)) — `mcp.tool.partial_success` and `mcp.tool.batch.*` follow a custom `failedKey` / `succeededKey`, including through `.extend()`, `.pick()`, `.omit()`, and a `.shape` spread.
66
+ - **`logger.close()` runs after a failed telemetry flush** ([#540](https://github.com/cyanheads/mcp-ts-core/issues/540)) — the failure is logged as a warning first, so the final log lines survive an unreachable collector.
67
+
68
+ ## Security
69
+
70
+ - **Server stack traces no longer reach clients through `McpError.data`** ([#519](https://github.com/cyanheads/mcp-ts-core/issues/519)) — `ErrorHandler.handleError` and `tryCatch` keep `originalStack` and `causeChain` in the log record, and `prompts/get` forwards only the thrown `McpError`'s own `data`.
71
+
72
+ ## Dependencies
73
+
74
+ - `@modelcontextprotocol/client` ^2.0.0 moves from `dependencies` to `devDependencies`; `@modelcontextprotocol/ext-apps` ^2.0.0 and `dotenv` ^17.4.2 are removed, `dotenv` in favor of `process.loadEnvFile()` ([#525](https://github.com/cyanheads/mcp-ts-core/issues/525)).
75
+ - Peer `zod` ^4.4.3 → ^4.6.5, matching the dependency range; the optional `validator` peer is removed.
76
+ - Dev: `@cloudflare/workers-types` 5.20260910.1 → 5.20260922.1, `@socketsecurity/bun-security-scanner` ^1.1.2 → ^1.1.3, `@supabase/supabase-js` ^2.116.0 → ^2.117.0, `@types/node` 26.5.1 → 26.6.2, `defuddle` ^0.19.3 → ^0.19.4, `fast-check` ^4.10.1 → ^4.10.2, `ignore` ^7.0.9 → ^7.0.10, `openai` ^7.15.0 → ^7.21.0, `repomix` ^1.18.0 → ^1.18.1; `validator`, `@types/validator`, and `bun-types` removed. The `depcheck` script and `package.json` block give way to the list in `devcheck.config.json`.
77
+ - Bun 1.4.0 → 1.4.2 (`packageManager`, Docker base images, and the scaffold, whose `@types/node` and `@socketsecurity/bun-security-scanner` pins follow the framework's).