@cyanheads/mcp-ts-core 0.12.6 → 0.12.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 (235) hide show
  1. package/AGENTS.md +9 -3
  2. package/CLAUDE.md +9 -3
  3. package/README.md +132 -77
  4. package/biome.json +1 -1
  5. package/changelog/0.12.x/0.12.6.md +2 -2
  6. package/changelog/0.12.x/0.12.7.md +39 -0
  7. package/changelog/0.12.x/0.12.8.md +55 -0
  8. package/dist/config/index.d.ts +3 -34
  9. package/dist/config/index.d.ts.map +1 -1
  10. package/dist/config/index.js +4 -26
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/core/app.d.ts +0 -8
  13. package/dist/core/app.d.ts.map +1 -1
  14. package/dist/core/app.js +0 -7
  15. package/dist/core/app.js.map +1 -1
  16. package/dist/core/serverManifest.d.ts +0 -7
  17. package/dist/core/serverManifest.d.ts.map +1 -1
  18. package/dist/core/serverManifest.js +1 -13
  19. package/dist/core/serverManifest.js.map +1 -1
  20. package/dist/core/worker.d.ts +1 -1
  21. package/dist/core/worker.d.ts.map +1 -1
  22. package/dist/core/worker.js.map +1 -1
  23. package/dist/linter/rules/enrichment-rules.js +2 -2
  24. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  25. package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
  26. package/dist/linter/rules/format-parity-rules.js +14 -36
  27. package/dist/linter/rules/format-parity-rules.js.map +1 -1
  28. package/dist/linter/rules/prompt-rules.d.ts +1 -1
  29. package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
  30. package/dist/linter/rules/prompt-rules.js +2 -19
  31. package/dist/linter/rules/prompt-rules.js.map +1 -1
  32. package/dist/linter/rules/resource-rules.d.ts +1 -1
  33. package/dist/linter/rules/resource-rules.d.ts.map +1 -1
  34. package/dist/linter/rules/resource-rules.js +9 -39
  35. package/dist/linter/rules/resource-rules.js.map +1 -1
  36. package/dist/linter/rules/schema-rules.d.ts +22 -2
  37. package/dist/linter/rules/schema-rules.d.ts.map +1 -1
  38. package/dist/linter/rules/schema-rules.js +28 -5
  39. package/dist/linter/rules/schema-rules.js.map +1 -1
  40. package/dist/linter/rules/tool-rules.d.ts +1 -1
  41. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  42. package/dist/linter/rules/tool-rules.js +13 -41
  43. package/dist/linter/rules/tool-rules.js.map +1 -1
  44. package/dist/linter/validate.d.ts.map +1 -1
  45. package/dist/linter/validate.js +22 -42
  46. package/dist/linter/validate.js.map +1 -1
  47. package/dist/mcp-server/apps/appBuilders.d.ts.map +1 -1
  48. package/dist/mcp-server/apps/appBuilders.js +2 -16
  49. package/dist/mcp-server/apps/appBuilders.js.map +1 -1
  50. package/dist/mcp-server/handlerContext.d.ts +66 -0
  51. package/dist/mcp-server/handlerContext.d.ts.map +1 -0
  52. package/dist/mcp-server/handlerContext.js +71 -0
  53. package/dist/mcp-server/handlerContext.js.map +1 -0
  54. package/dist/mcp-server/inputRequired.d.ts +7 -1
  55. package/dist/mcp-server/inputRequired.d.ts.map +1 -1
  56. package/dist/mcp-server/inputRequired.js +10 -3
  57. package/dist/mcp-server/inputRequired.js.map +1 -1
  58. package/dist/mcp-server/resources/resource-registration.d.ts +2 -2
  59. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  60. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  61. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +14 -43
  62. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  63. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +11 -50
  64. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  65. package/dist/mcp-server/tools/tool-registration.d.ts +5 -9
  66. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  67. package/dist/mcp-server/tools/tool-registration.js +16 -12
  68. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  69. package/dist/mcp-server/tools/utils/deferredInputSchema.d.ts +39 -0
  70. package/dist/mcp-server/tools/utils/deferredInputSchema.d.ts.map +1 -0
  71. package/dist/mcp-server/tools/utils/deferredInputSchema.js +33 -0
  72. package/dist/mcp-server/tools/utils/deferredInputSchema.js.map +1 -0
  73. package/dist/mcp-server/tools/utils/schemaShape.d.ts +21 -0
  74. package/dist/mcp-server/tools/utils/schemaShape.d.ts.map +1 -1
  75. package/dist/mcp-server/tools/utils/schemaShape.js +8 -6
  76. package/dist/mcp-server/tools/utils/schemaShape.js.map +1 -1
  77. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +25 -45
  78. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  79. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +55 -74
  80. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  81. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
  82. package/dist/mcp-server/transports/http/httpErrorHandler.js +2 -1
  83. package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
  84. package/dist/mcp-server/transports/http/landing-page/handler.d.ts.map +1 -1
  85. package/dist/mcp-server/transports/http/landing-page/handler.js +2 -1
  86. package/dist/mcp-server/transports/http/landing-page/handler.js.map +1 -1
  87. package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts.map +1 -1
  88. package/dist/mcp-server/transports/http/protectedResourceMetadata.js +2 -1
  89. package/dist/mcp-server/transports/http/protectedResourceMetadata.js.map +1 -1
  90. package/dist/mcp-server/transports/http/publicOrigin.d.ts +11 -0
  91. package/dist/mcp-server/transports/http/publicOrigin.d.ts.map +1 -0
  92. package/dist/mcp-server/transports/http/publicOrigin.js +13 -0
  93. package/dist/mcp-server/transports/http/publicOrigin.js.map +1 -0
  94. package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
  95. package/dist/mcp-server/transports/http/serverCard.js +2 -1
  96. package/dist/mcp-server/transports/http/serverCard.js.map +1 -1
  97. package/dist/mcp-server/transports/http/sessionIdUtils.d.ts +4 -0
  98. package/dist/mcp-server/transports/http/sessionIdUtils.d.ts.map +1 -1
  99. package/dist/mcp-server/transports/http/sessionIdUtils.js +3 -13
  100. package/dist/mcp-server/transports/http/sessionIdUtils.js.map +1 -1
  101. package/dist/mcp-server/transports/manager.d.ts +0 -3
  102. package/dist/mcp-server/transports/manager.d.ts.map +1 -1
  103. package/dist/mcp-server/transports/manager.js +0 -7
  104. package/dist/mcp-server/transports/manager.js.map +1 -1
  105. package/dist/services/canvas/core/CanvasRegistry.d.ts +14 -0
  106. package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
  107. package/dist/services/canvas/core/CanvasRegistry.js +3 -2
  108. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  109. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +16 -0
  110. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  111. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +78 -103
  112. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  113. package/dist/services/graph/core/GraphService.d.ts +3 -3
  114. package/dist/services/graph/core/GraphService.js +3 -3
  115. package/dist/services/graph/types.d.ts +2 -79
  116. package/dist/services/graph/types.d.ts.map +1 -1
  117. package/dist/services/graph/types.js +2 -2
  118. package/dist/services/index.d.ts +1 -2
  119. package/dist/services/index.d.ts.map +1 -1
  120. package/dist/services/index.js +0 -1
  121. package/dist/services/index.js.map +1 -1
  122. package/dist/services/mirror/sqlite/handle.d.ts.map +1 -1
  123. package/dist/services/mirror/sqlite/handle.js +22 -36
  124. package/dist/services/mirror/sqlite/handle.js.map +1 -1
  125. package/dist/services/speech/core/ISpeechProvider.d.ts +0 -24
  126. package/dist/services/speech/core/ISpeechProvider.d.ts.map +1 -1
  127. package/dist/services/speech/core/ISpeechProvider.js +1 -28
  128. package/dist/services/speech/core/ISpeechProvider.js.map +1 -1
  129. package/dist/services/speech/core/SpeechService.d.ts.map +1 -1
  130. package/dist/services/speech/core/SpeechService.js +5 -8
  131. package/dist/services/speech/core/SpeechService.js.map +1 -1
  132. package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -1
  133. package/dist/services/speech/providers/elevenlabs.provider.js +1 -0
  134. package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
  135. package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -1
  136. package/dist/services/speech/providers/whisper.provider.js +4 -2
  137. package/dist/services/speech/providers/whisper.provider.js.map +1 -1
  138. package/dist/services/speech/types.d.ts +2 -19
  139. package/dist/services/speech/types.d.ts.map +1 -1
  140. package/dist/storage/core/providerHelpers.d.ts +52 -0
  141. package/dist/storage/core/providerHelpers.d.ts.map +1 -0
  142. package/dist/storage/core/providerHelpers.js +96 -0
  143. package/dist/storage/core/providerHelpers.js.map +1 -0
  144. package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
  145. package/dist/storage/providers/cloudflare/d1Provider.js +1 -4
  146. package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
  147. package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
  148. package/dist/storage/providers/cloudflare/kvProvider.js +4 -31
  149. package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -1
  150. package/dist/storage/providers/cloudflare/r2Provider.d.ts +1 -1
  151. package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
  152. package/dist/storage/providers/cloudflare/r2Provider.js +8 -48
  153. package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
  154. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +1 -1
  155. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
  156. package/dist/storage/providers/fileSystem/fileSystemProvider.js +17 -86
  157. package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -1
  158. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
  159. package/dist/storage/providers/inMemory/inMemoryProvider.js +5 -38
  160. package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
  161. package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -1
  162. package/dist/storage/providers/supabase/supabaseProvider.js +1 -4
  163. package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -1
  164. package/dist/testing/fuzz.d.ts.map +1 -1
  165. package/dist/testing/fuzz.js +17 -31
  166. package/dist/testing/fuzz.js.map +1 -1
  167. package/dist/testing/index.d.ts.map +1 -1
  168. package/dist/testing/index.js +4 -26
  169. package/dist/testing/index.js.map +1 -1
  170. package/dist/utils/internal/error-handler/types.d.ts +0 -4
  171. package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
  172. package/dist/utils/internal/logger.d.ts.map +1 -1
  173. package/dist/utils/internal/logger.js +2 -16
  174. package/dist/utils/internal/logger.js.map +1 -1
  175. package/dist/utils/internal/performance.d.ts +8 -31
  176. package/dist/utils/internal/performance.d.ts.map +1 -1
  177. package/dist/utils/internal/performance.js +173 -295
  178. package/dist/utils/internal/performance.js.map +1 -1
  179. package/dist/utils/security/idGenerator.d.ts.map +1 -1
  180. package/dist/utils/security/idGenerator.js +24 -43
  181. package/dist/utils/security/idGenerator.js.map +1 -1
  182. package/dist/utils/security/sanitization.d.ts +0 -7
  183. package/dist/utils/security/sanitization.d.ts.map +1 -1
  184. package/dist/utils/security/sanitization.js +4 -31
  185. package/dist/utils/security/sanitization.js.map +1 -1
  186. package/dist/utils/security/sensitiveFields.d.ts +14 -0
  187. package/dist/utils/security/sensitiveFields.d.ts.map +1 -0
  188. package/dist/utils/security/sensitiveFields.js +31 -0
  189. package/dist/utils/security/sensitiveFields.js.map +1 -0
  190. package/dist/utils/telemetry/trace.d.ts +8 -10
  191. package/dist/utils/telemetry/trace.d.ts.map +1 -1
  192. package/dist/utils/telemetry/trace.js +19 -18
  193. package/dist/utils/telemetry/trace.js.map +1 -1
  194. package/dist/utils/types/guards.d.ts +0 -102
  195. package/dist/utils/types/guards.d.ts.map +1 -1
  196. package/dist/utils/types/guards.js +0 -114
  197. package/dist/utils/types/guards.js.map +1 -1
  198. package/package.json +26 -25
  199. package/scripts/check-framework-antipatterns.ts +4 -1
  200. package/scripts/devcheck.ts +303 -33
  201. package/scripts/lint-packaging.ts +28 -6
  202. package/skills/add-provider/SKILL.md +18 -4
  203. package/skills/add-tool/SKILL.md +33 -1
  204. package/skills/api-config/SKILL.md +4 -18
  205. package/skills/api-errors/SKILL.md +2 -1
  206. package/skills/api-services/SKILL.md +1 -1
  207. package/skills/api-services/references/speech.md +1 -2
  208. package/skills/api-telemetry/SKILL.md +2 -2
  209. package/skills/api-utils/SKILL.md +2 -2
  210. package/skills/code-simplifier/SKILL.md +47 -20
  211. package/skills/design-mcp-server/SKILL.md +6 -1
  212. package/skills/field-test/SKILL.md +158 -39
  213. package/skills/git-wrapup/SKILL.md +67 -29
  214. package/skills/orchestrations/SKILL.md +17 -6
  215. package/skills/orchestrations/workflows/field-test-fix.md +6 -4
  216. package/skills/orchestrations/workflows/fix-wrapup-release.md +6 -4
  217. package/skills/orchestrations/workflows/greenfield-build.md +2 -2
  218. package/skills/orchestrations/workflows/maintenance-release.md +4 -2
  219. package/skills/release-and-publish/SKILL.md +101 -23
  220. package/skills/release-pr-review/SKILL.md +147 -0
  221. package/templates/AGENTS.md +4 -2
  222. package/templates/CLAUDE.md +4 -2
  223. package/templates/package.json +6 -6
  224. package/dist/mcp-server/transports/ITransport.d.ts +0 -15
  225. package/dist/mcp-server/transports/ITransport.d.ts.map +0 -1
  226. package/dist/mcp-server/transports/ITransport.js +0 -2
  227. package/dist/mcp-server/transports/ITransport.js.map +0 -1
  228. package/dist/services/llm/types.d.ts +0 -16
  229. package/dist/services/llm/types.d.ts.map +0 -1
  230. package/dist/services/llm/types.js +0 -9
  231. package/dist/services/llm/types.js.map +0 -1
  232. package/dist/utils/internal/health.d.ts +0 -60
  233. package/dist/utils/internal/health.d.ts.map +0 -1
  234. package/dist/utils/internal/health.js +0 -46
  235. package/dist/utils/internal/health.js.map +0 -1
@@ -4,7 +4,7 @@ description: >
4
4
  API reference for built-in service providers (LLM, Speech, Graph). Use when looking up service interfaces, provider capabilities, or integration patterns.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -24,7 +24,7 @@ The provider interface — implemented by ElevenLabs (TTS) and Whisper (STT):
24
24
  | `.getSTTProvider()` | `ISpeechProvider` | Throws `McpError(InvalidRequest)` if no STT provider configured |
25
25
  | `.hasTTS()` | `boolean` | Check if TTS is available |
26
26
  | `.hasSTT()` | `boolean` | Check if STT is available |
27
- | `.healthCheck()` | `Promise<{ tts: boolean; stt: boolean }>` | Checks both providers sequentially |
27
+ | `.healthCheck()` | `Promise<{ tts: boolean; stt: boolean }>` | Checks both providers in parallel |
28
28
 
29
29
  ## Providers
30
30
 
@@ -52,7 +52,6 @@ const ttsProvider = speechService.getTTSProvider();
52
52
  const ttsResult = await ttsProvider.textToSpeech({
53
53
  text: 'Hello, world!',
54
54
  voice: { voiceId: 'some-voice-id' },
55
- format: 'mp3',
56
55
  });
57
56
 
58
57
  // Speech-to-Text
@@ -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.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -227,7 +227,7 @@ export async function doWork() {
227
227
  }
228
228
  ```
229
229
 
230
- Span context propagates automatically — `withSpan` calls inside a `tool_execution:*` span appear as children. `runInContext(ctx, fn)` carries the active OTel context across async boundaries (`setTimeout`, `queueMicrotask`).
230
+ Span context propagates automatically — `withSpan` calls inside a `tool_execution:*` span appear as children. `runInContext(ctx, fn)` re-establishes the span `ctx` names as the active one across async boundaries (`setTimeout`, `queueMicrotask`), so spans opened inside `fn` parent to the request's span.
231
231
 
232
232
  For attribute keys, prefer the `ATTR_*` constants exported from `@cyanheads/mcp-ts-core/utils` (telemetry/attributes) over hand-typed strings — keeps you in step with framework conventions and avoids typos. Standard OTel semantic conventions (HTTP, cloud, service, network, etc.) are NOT re-exported — import those directly from `@opentelemetry/semantic-conventions`.
233
233
 
@@ -4,7 +4,7 @@ description: >
4
4
  API reference for all utilities exported from `@cyanheads/mcp-ts-core/utils`. Use when looking up utility method signatures, options, peer dependencies, or usage patterns.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.8"
7
+ version: "2.9"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -163,7 +163,7 @@ Helper API only. For the catalog of what the framework auto-emits (span names, m
163
163
  | Export | Signature | Notes |
164
164
  |:-------|:----------|:------|
165
165
  | `withSpan` | `async <T>(operationName: string, fn: (span: Span) => Promise<T>, attributes?: Record<string, string \| number \| boolean>) -> Promise<T>` | Creates an active span, calls `fn(span)`, sets `OK` on success or records exception + sets `ERROR` on throw, then ends the span. Always rethrows. |
166
- | `runInContext` | `(ctx: RequestContext \| undefined, fn: () => T) -> T` | Runs `fn` inside the currently active OTel context. When `ctx` has no `traceId`/`spanId`, calls `fn` directly. Does not restore a specific span — use for carrying context across async boundaries (`setTimeout`, `queueMicrotask`). |
166
+ | `runInContext` | `(ctx: RequestContext \| undefined, fn: () => T) -> T` | Runs `fn` with the span `ctx` names (`traceId`/`spanId`) re-established as the active OTel span, so spans opened inside `fn` parent to it. When `ctx` has no `traceId`/`spanId`, calls `fn` directly. Use for carrying a request's trace across async boundaries (`setTimeout`, `queueMicrotask`). |
167
167
  | `buildTraceparent` | `(ctx?: RequestContext) -> string \| undefined` | Builds a W3C `traceparent` header (`00-<traceId>-<spanId>-01`) from `ctx` or the active span. Returns `undefined` when neither source yields both IDs. |
168
168
  | `extractTraceparent` | `(headers: Headers \| Record<string, string \| undefined>) -> TraceparentInfo \| undefined` | Parses a W3C `traceparent` header. Returns `undefined` when absent or malformed. `TraceparentInfo: { traceId, spanId, sampled }`. |
169
169
  | `createContextWithParentTrace` | `(parentHeaders: Headers \| Record<string, string \| undefined>, operation: string) -> RequestContext` | Extracts `traceparent` from headers and creates a child `RequestContext` inheriting `traceId`/`parentSpanId`. |
@@ -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.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -21,7 +21,7 @@ Post-session cleanup pass. Reviews what changed, understands how it fits the exi
21
21
 
22
22
  ### Phase 1: Identify changes
23
23
 
24
- Run `git status` to see the shape of the working tree, then `git diff HEAD` for all uncommitted changes (staged and unstaged). Untracked files never appear in the diff — read new files directly. If the tree is clean, review the most recently modified files from the current session.
24
+ Run `git status` to see the shape of the working tree, then `git diff HEAD` for all uncommitted changes (staged and unstaged). Untracked files never appear in the diff — read new files directly. If the diff is empty and there are no untracked files, review the last commit (`git diff HEAD~1 HEAD`); if that is also empty, say the tree is clean and stop. Don't go hunting through the codebase for files to improve.
25
25
 
26
26
  ### Phase 2: Understand the surrounding codebase
27
27
 
@@ -29,7 +29,8 @@ Don't review changes in isolation. Before any modifications:
29
29
 
30
30
  1. **Read the full files** containing changes — not just the diff hunks. Understand imports, surrounding logic, module structure.
31
31
  2. **Identify the project language(s)** and select the relevant transformation rules. Discard inapplicable rules.
32
- 3. **Survey adjacent code** — shared utilities, sibling modules, common patterns. You need to know what already exists before deciding something is missing. For mcp-ts-core projects, check `src/utils/` for project utilities, `src/errors/` for error handling, and `node_modules/@cyanheads/mcp-ts-core/` for framework exports.
32
+ 3. **Survey adjacent code** — shared utilities, sibling modules, common patterns. You need to know what already exists before deciding something is missing.
33
+ 4. **Run the project's gate once before editing** to establish a baseline. Find it in `package.json` scripts — `devcheck` if present, else `check`, else the separate `typecheck` / `lint` / `test` scripts; Python projects gate on `uv run ruff check`, `uv run ruff format --check`, and the configured type checker and test runner. In a Bun project that tests with Vitest, run `bun run test` — bare `bun test` bypasses the script and runs Bun's own runner. If the gate is already red, say so in the summary and don't attribute the failure to your changes.
33
34
 
34
35
  ### Phase 3: Review
35
36
 
@@ -37,29 +38,35 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
37
38
 
38
39
  #### Codebase cohesion
39
40
 
40
- - **Reuse** — Search for existing utilities, helpers, and patterns that could replace newly-written code. For mcp-ts-core projects, prefer `import from '@cyanheads/mcp-ts-core/utils'` over hand-rolled equivalents — pagination helpers, schema builders, retry primitives, and OTel attribute constants are framework-provided.
41
+ - **Reuse** — Search for existing utilities, helpers, and patterns that could replace newly-written code. Check utility directories, shared modules, and files adjacent to the changed ones. If a function already exists that does what the new code does, use it.
41
42
  - **Consolidation** — Flag copy-paste-with-variation: near-duplicate code blocks that should be unified. Only unify if the shared abstraction is genuinely simpler than the duplicated code.
42
43
  - **Consistency** — Check that new code follows the same patterns as the rest of the codebase: naming conventions, error handling style, import patterns, type annotation style. Normalize toward the better variant when the project is inconsistent.
43
- - **Stringly-typed code** — Flag raw strings where constants, string-union types, branded types, or framework attribute constants already exist. For mcp-ts-core projects, the `ATTR_*` constants in `@cyanheads/mcp-ts-core/utils` should replace raw OTel attribute keys.
44
+ - **Stringly-typed code** — Flag raw strings where constants, string-union types, or branded types already exist in the codebase.
44
45
 
45
46
  #### Code quality
46
47
 
47
48
  - **Redundant state** — State that duplicates existing state, cached values that could be derived.
48
49
  - **Unnecessary complexity** — Deep nesting that could be guard clauses, premature abstractions, over-engineered solutions to simple problems.
49
- - **Dead code** — Unreachable branches, unused variables, commented-out code, exports that nothing imports.
50
- - **Defensive code for impossible states** — Guards for cases the type system or framework already prevents. Drop them.
50
+ - **Dead code** — Unreachable branches, unused variables, commented-out code. An export nothing imports is dead in an application or a package-internal module; on a published package's public surface it is API — leave it and note it in the summary.
51
+ - **Defensive code for impossible states** — Guards for cases the type system or upstream validation already prevents. Drop them.
52
+ - **Type escapes** — `any`, `as` casts that paper over a mismatch, non-null `!`, and `@ts-ignore`. Each is a claim the compiler couldn't check: replace with a narrowed type, a type guard, or a parse at the boundary. Keep the ones documenting a genuine type-system or third-party-types limitation, and prefer `@ts-expect-error` with a one-line reason over `@ts-ignore`.
53
+ - **Swallowed errors** — Empty `catch {}`, `catch { return null }`, and `try` blocks that log and continue. A fallback that hides a failure is worse than the crash it prevents: rethrow or let it propagate. When wrapping, preserve the chain (`new Error(msg, { cause })`, `raise X from err`).
54
+ - **Comment noise** — Strip comments that restate the code, commented-out code, and comments describing behavior the diff removed. Keep file headers, export JSDoc, and any comment carrying a *why* — a constraint, a workaround, an upstream bug reference.
51
55
  - **Outdated patterns** — Verbose or legacy syntax where modern equivalents exist. See the transformation tables below.
52
56
 
53
57
  #### Efficiency
54
58
 
55
59
  - **Redundant work** — Repeated computations, duplicate file reads, duplicate network/API calls, N+1 query patterns.
56
60
  - **Missed concurrency** — Independent async operations run sequentially that could run in parallel with `Promise.all` / `Promise.allSettled`.
61
+ - **Unbounded fan-out** — `Promise.all` / `asyncio.gather` over a caller-sized or otherwise unbounded array fires everything at once. Cap it with the project's existing concurrency helper or a batched loop. A fixed handful of independent calls needs no limit.
57
62
  - **No-op updates** — State/store updates inside loops or event handlers that fire unconditionally. Add change-detection so downstream consumers aren't notified when nothing changed.
58
63
  - **TOCTOU** — Pre-checking file/resource existence before operating on it. Operate directly and handle the error instead.
59
64
  - **Overly broad operations** — Reading entire files when only a portion is needed, loading all items when filtering for one.
60
65
 
61
66
  #### mcp-ts-core-specific
62
67
 
68
+ - **Gate** — `bun run devcheck` plus the test suite (`bun run test`) is the project gate in Phase 2 step 4 and Phase 4 step 5.
69
+ - **Framework-provided utilities** — Before hand-rolling, check `src/utils/` and `src/errors/` in the project and `node_modules/@cyanheads/mcp-ts-core/` for framework exports: pagination helpers, schema builders, retry primitives, and the `ATTR_*` OTel attribute constants are framework-provided. Raw OTel attribute keys should be `ATTR_*` imports from `@cyanheads/mcp-ts-core/utils`.
63
70
  - **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
71
  - **Error codes** — `InvalidParams` only for malformed JSON-RPC params shape. `ValidationError` for domain validation. `NotFound` for missing entities. Don't conflate them.
65
72
  - **Ctx usage** — Use `ctx.log`, `ctx.state`, `ctx.enrich` — don't reach for global loggers or request-scoped storage directly. The `ctx` pattern carries tenant scope and OTel context.
@@ -67,19 +74,24 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
67
74
  - **Tool annotations** — `readOnlyHint`, `idempotentHint`, `openWorldHint` should reflect reality. A read-only tool with `readOnlyHint: false` gives clients the wrong picture.
68
75
  - **`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.
69
76
  - **`format()` ↔ `structuredContent` parity** — Different MCP clients forward different surfaces. Tests should assert both surfaces carry equivalent data.
77
+ - **Defensive code** — the "impossible states" the framework already prevents include malformed params (Zod-validated before the handler runs) and unclassified errors (caught and classified after it throws). Guards for either are dead.
78
+ - **Public surface** — the MCP surface (every tool input/output schema advertised to clients) is public API for the "API compatibility" rule; changing one is a breaking change, not a refactor.
70
79
 
71
80
  ### Phase 4: Apply transformations
72
81
 
73
82
  1. **Filter findings ruthlessly.** If a finding is a false positive or not worth the churn, skip it. Don't argue with yourself about borderline cases — move on.
74
- 2. **Transform incrementally** — one category of change at a time (modernize syntax, then reduce nesting, then consolidate).
75
- 3. **Verify equivalence** — all functionality, types, and public interfaces must remain unchanged. Run the project's gates after transforming (`bun run devcheck` and the test suite in mcp-ts-core projects; typecheck/lint/tests elsewhere); a simplification that breaks the build is worse than the verbosity it removed.
76
- 4. **Keep the diff minimal.** Only touch lines that have a real reason to change. Don't reformat untouched code, add comments to code you didn't modify, or "improve" things that are already fine.
83
+ 2. **Stay in scope.** Edit only files in the diff or new this session. Touch a file outside that set only when a finding requires it — importing an existing helper, deleting a private export the diff just orphaned — and only on the lines that finding names. Anything broader goes in the summary as a recommendation, not into the tree.
84
+ 3. **Correctness bugs are not this pass's job.** A real defect doesn't get folded into a cleanup diff — name it in the summary with file and line so it can be handled as its own change.
85
+ 4. **Transform incrementally** — one category of change at a time (modernize syntax, then reduce nesting, then consolidate).
86
+ 5. **Verify equivalence** — all functionality, types, and public interfaces must remain unchanged. Re-run the gate from Phase 2 after transforming; a simplification that breaks the build is worse than the verbosity it removed.
87
+ 6. **Keep the diff minimal.** Only touch lines that have a real reason to change. Don't reformat untouched code, add comments to code you didn't modify, or "improve" things that are already fine. Formatting belongs to the formatter (Biome, ruff): never hand-adjust whitespace, quotes, or import order, and never let a formatting-only hunk into the diff.
88
+ 7. **Never stage, commit, tag, or push.** This pass ends with a dirty working tree and a summary; landing the changes is the caller's call.
77
89
 
78
- When done, briefly summarize what was fixed or confirm the code was already clean.
90
+ When done, briefly summarize what was fixed, what was deliberately skipped, and any defects or out-of-scope recommendations — or confirm the code was already clean.
79
91
 
80
92
  ## Common transformations
81
93
 
82
- The tables below cover TypeScript and Python. For other languages, apply analogous principles: prefer modern idioms, reduce nesting, eliminate dead code, follow project conventions.
94
+ The tables below cover TypeScript and Python. For other languages, apply analogous principles: prefer modern idioms, reduce nesting, eliminate dead code, follow project conventions. Check the project's language floor (`tsconfig` target/lib, `pyproject` `requires-python`) before applying a version-gated row.
83
95
 
84
96
  ### TypeScript (modern ESM, TS 5.x+)
85
97
 
@@ -90,11 +102,17 @@ The tables below cover TypeScript and Python. For other languages, apply analogo
90
102
  | `if (x !== null && x !== undefined)` | `if (x != null)` | Idiomatic null/undefined check |
91
103
  | `arr.filter(x => x !== null) as T[]` | `arr.filter(x => x != null)` | TS 5.5+ infers the type predicate — no cast; on older TS use an explicit `(x): x is T` predicate |
92
104
  | `export { foo } from './foo/index.js'` | Direct imports at call sites | Avoid barrel re-exports inside the package; barrel exports are for public APIs only |
105
+ | `import { readFile } from 'fs/promises'` | `import { readFile } from 'node:fs/promises'` | `node:` protocol — unambiguous, lint-enforced in Biome |
93
106
  | `async function f() { const a = await x(); const b = await y(); }` | `const [a, b] = await Promise.all([x(), y()])` | Parallel when independent |
94
- | `obj.x !== undefined ? obj.x : fallback` | `obj.x ?? fallback` | Nullish coalescing |
107
+ | `value \|\| fallback` | `value ?? fallback` | `\|\|` also swallows `0`, `''`, and `false` — use `??` unless every falsy value really should take the fallback |
108
+ | `obj.x !== undefined ? obj.x : fallback` | `obj.x ?? fallback` | Nullish coalescing — equivalent only when `null` should take the fallback too |
95
109
  | `if (a) { if (b) { if (c) { ... } } }` | Guard clauses with early returns | Reduce nesting |
96
- | `try { risky() } catch (e: any) { ... }` | `try { risky() } catch (e: unknown) { ... }` | Type-safe error handling |
97
- | `enum Status { A, B, C }` | `const Status = { A: 'A', B: 'B', C: 'C' } as const` | Prefer const objects over enums — but switching numeric values to strings changes serialized output; keep values stable if they're persisted (string enums are acceptable) |
110
+ | `try { risky() } catch (e: any) { ... }` | `try { risky() } catch (e) { ... }` | Under `strict` the catch binding is already `unknown`; narrow with a type guard before use |
111
+ | `catch (err) { throw new Error('load failed') }` | `throw new Error('load failed', { cause: err })` | Preserve the cause chain |
112
+ | `[...arr].sort(cmp)` / `arr.slice().sort(cmp)` | `arr.toSorted(cmp)` | Non-mutating array methods (ES2023) — also `toReversed`, `toSpliced`, `with` |
113
+ | `const c = new AbortController(); setTimeout(() => c.abort(), ms)` | `AbortSignal.timeout(ms)` | Built-in timeout signal; combine with a caller's signal via `AbortSignal.any([...])` |
114
+ | `JSON.parse(JSON.stringify(x))` | `structuredClone(x)` | Deep clone that preserves Date, Map, Set, and cycles |
115
+ | `enum Status { A, B, C }` | `const Status = { A: 'A', B: 'B', C: 'C' } as const` | `enum`, `namespace`, and constructor parameter properties are non-erasable syntax rejected by TS 5.8 `erasableSyntaxOnly` and Node type-stripping — but switching numeric values to strings changes serialized output; keep values stable if they're persisted |
98
116
  | `function f(a: string, b: string, c: string, d?: string)` | `function f(opts: FnOptions)` | Options object when >3 params |
99
117
  | `throw new Error('Bad input')` (in a tool handler) | `throw validationError('Bad input', { field: 'x' })` | Use framework error factories so the framework can classify and instrument |
100
118
  | `const ATTR_KEY = 'mcp.tool.name'` | `import { ATTR_MCP_TOOL_NAME } from '@cyanheads/mcp-ts-core/utils'` | Use framework attribute constants |
@@ -105,16 +123,20 @@ The tables below cover TypeScript and Python. For other languages, apply analogo
105
123
  | --- | --- | --- |
106
124
  | `Optional[str]` | `str \| None` | Modern union syntax (3.10+) |
107
125
  | `List[str]`, `Dict[str, int]` | `list[str]`, `dict[str, int]` | Built-in generics (3.9+) |
108
- | `if x == 0: ... elif x == 1: ... elif x == 2: ...` | `match x: case 0: ... case 1: ...` | Structural pattern matching (3.10+) |
109
- | `class Config: def __init__(self, a, b, c): self.a = a ...` | `@dataclass class Config: a: str; b: int; c: float` | Less boilerplate, built-in eq/repr |
126
+ | `T = TypeVar("T")` + `def f(x: T) -> T` | `def f[T](x: T) -> T` | PEP 695 generics (3.12+) — also `class C[T]:` |
127
+ | `TypeAlias = Union[A, B, C]` | `type ABC = A \| B \| C` | `type` statement (3.12+) |
128
+ | `if isinstance(x, Foo): a = x.a; b = x.b` | `match x: case Foo(a=a, b=b): ...` | Structural pattern matching (3.10+) where it destructures — not as a replacement for a flat equality `if/elif` chain |
129
+ | `class Config: def __init__(self, a, b, c): self.a = a ...` | `@dataclass(slots=True) class Config: a: str; b: int; c: float` | Less boilerplate, built-in eq/repr; `frozen=True` when instances shouldn't mutate |
110
130
  | `results = []; for item in items: results.append(transform(item))` | `results = [transform(item) for item in items]` | Idiomatic comprehension |
111
131
  | `f = open('x'); try: ... finally: f.close()` | `with open('x') as f: ...` | Context manager for resources |
132
+ | `os.path.join(d, n)`, `os.path.exists(p)`, `open(p).read()` | `Path(d) / n`, `p.exists()`, `p.read_text()` | `pathlib` over `os.path` string juggling |
133
+ | `datetime.utcnow()` / `datetime.utcfromtimestamp(t)` | `datetime.now(UTC)` / `datetime.fromtimestamp(t, UTC)` | Deprecated in 3.12 — the old calls return naive datetimes that compare wrong against aware ones |
134
+ | `zip(a, b)` | `zip(a, b, strict=True)` | 3.10+ — silently truncating to the shorter input hides bugs |
112
135
  | `m = pattern.match(s)` then `if m: use(m)` | `if (m := pattern.match(s)): use(m)` | Walrus operator where it removes a throwaway assignment |
113
136
  | `"Hello " + name + "!"` | `f"Hello {name}!"` | f-string over concatenation |
114
137
  | `except Exception as e: pass` | `except SpecificError as e: log(e)` | Catch specific, never bare except/pass |
115
138
  | `from module import *` | `from module import specific_name` | Explicit imports only |
116
- | `TypeAlias = Union[A, B, C]` | `type ABC = A \| B \| C` | `type` statement (3.12+) |
117
- | Sequential `await` for independent I/O | `await asyncio.gather(a(), b())` | Parallel when independent |
139
+ | Sequential `await` for independent I/O | `async with asyncio.TaskGroup() as tg: tg.create_task(a()); tg.create_task(b())` | Structured concurrency (3.11+) — cancels siblings on failure and raises an `ExceptionGroup`; `asyncio.gather(..., return_exceptions=True)` stays correct when every result is wanted regardless of failures |
118
140
 
119
141
  ## When NOT to simplify
120
142
 
@@ -124,7 +146,12 @@ Leave code alone when:
124
146
  - **The change is cosmetic.** Renaming a variable from `data` to `result` isn't worth the churn.
125
147
  - **Intentional verbosity for debugging.** Verbose code may exist to make stack traces or logging clearer.
126
148
  - **Performance-critical paths.** A less readable version may exist for measured performance reasons — check before simplifying.
127
- - **API compatibility.** Don't change public function signatures, export shapes, or return types that callers depend on. For mcp-ts-core projects, the public surface includes tool input/output schemas exposed via MCP — changing them is a breaking change to the server's MCP surface.
149
+ - **API compatibility.** Don't change public function signatures, export shapes, or return types that callers depend on.
128
150
  - **Tests.** Don't DRY up test code aggressively — test readability and isolation matter more than deduplication.
129
151
  - **Type workarounds.** Sometimes an `as` cast or `# type: ignore` exists because of a genuine type system limitation — verify before removing.
130
152
  - **The abstraction isn't proven.** Don't create a shared utility for two similar blocks of code. Wait until there are three, and even then only if the abstraction is genuinely simpler than the duplication.
153
+ - **`return await` inside `try` / `finally`.** Collapsing it to `return` is not equivalent — the promise settles outside the block, so `catch` never fires and `finally` runs early. Only strip `await` from a `return` in plain function-body position.
154
+ - **Lazy logging arguments.** `logger.info("loaded %s in %sms", name, ms)` defers formatting until the record is emitted — don't turn it into an f-string.
155
+ - **Awaits that only look independent.** Sequential I/O may be sequential on purpose: rate limits, upstream ordering, a write that must land before the next read. Confirm independence from the code, not from the shape of the calls, before reaching for `Promise.all`.
156
+ - **Generated and vendored files.** Lockfiles, generated clients and schemas, migrations, snapshots, and anything under `dist/` are regenerated, not edited — skip them even when they appear in the diff.
157
+ - **Tool descriptions and `.describe()` prose.** They are the contract an LLM client reads — tightening them for brevity degrades the surface. Treat them as API text, not as comments.
@@ -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.23"
7
+ version: "2.24"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -257,6 +257,8 @@ const wrapupInstructions = tool('git_wrapup_instructions', {
257
257
 
258
258
  Prior art: [`git_wrapup_instructions`](https://github.com/cyanheads/git-mcp-server) walks through staging, commit, and push with repo state inspected. If a server has recurring "how do I do X well given my state" questions, an instruction tool typically beats N topic-specific tools and duplicating guidance in tool descriptions.
259
259
 
260
+ **Suggestions are scoped to what this deployment registers.** A `nextToolSuggestions` entry is an executable call, so it is only correct when its target is enabled under the same configuration — a tool wrapped in `disabledTool()` is absent from `tools/list`, and a suggestion naming it hands the agent a call that fails on dispatch. Build the array from the same config the registration reads, and when the target is off, drop the entry rather than the explanation: `guidance` can still say the capability is unavailable in this deployment and what to do instead. The audit and a worked example live under *Feature-flagged tools* in `add-tool/SKILL.md`.
261
+
260
262
  #### Reference tools
261
263
 
262
264
  **Applies when:** the domain speaks in opaque vocabulary — enum codes, classification systems, identifier formats, per-source coverage windows — that agents must supply as inputs elsewhere. Skip when inputs are self-evident (free text, ISO dates, well-known formats).
@@ -493,6 +495,8 @@ throw notFound(`Paper '${id}' not found on arXiv. Verify the ID format (e.g., '2
493
495
 
494
496
  **During design, settle the full contract for each tool** — reason, code, when-clause, *and the verbatim `recovery` string* — in the tool's section of the design doc; they become the literal `errors: [...]` entries during scaffolding. Hold every recovery string (and zero-hit notice, and resolver `guidance`) to the **no-dead-ends rule: it names the concrete next tool call**, with the reference tool as the most common routing target. Settled at design time these stay sharp; left to implementation they degrade into "check your input." Not every failure needs a contract entry; baseline infrastructure errors (5xx, timeouts, validation) are fine to let bubble.
495
497
 
498
+ **A routing target must be callable in the deployment doing the routing.** A recovery string, notice, or `guidance` line that names a config-gated tool is a dead end wherever that gate is off — the agent is sent to a tool absent from `tools/list`, at the moment it is already recovering from a failure. Prefer routing to ungated tools (the reference tool is a good target precisely because nothing gates it). Where the target genuinely is gated, resolve the text from the same config that decides registration, and say the capability is unavailable in this deployment rather than naming a call that cannot be made. Structured follow-ups are stricter still — see *Instruction tools* above.
499
+
496
500
  #### Design table
497
501
 
498
502
  Summarize each tool:
@@ -701,6 +705,7 @@ Items without an `If …:` prefix apply to every design. Conditional items only
701
705
  - [ ] **If an upstream API has no native search but the relevant set is bounded:** MCP-side list filtering considered — a distinct local filter param (`filter`/`nameContains`, not `query`), filtering the full set, strict token match (fuzzy only when a caller needs typo tolerance)
702
706
  - [ ] **If the server has workflow tools:** call-flow documented (upstream sequence + mode arms) in design doc's Workflow Analysis
703
707
  - [ ] **If state-aware procedural guidance adds value:** instruction tool considered with `nextToolSuggestions` pre-filled from diagnostics
708
+ - [ ] **If any tool is config-gated:** nothing routes to it while the gate is off — recovery strings, notices, and `guidance` name a callable target or state the capability is unavailable, and structured follow-ups naming it are emitted only under the config that registers it
704
709
  - [ ] **If workflow tools have destructive modes:** destructive arm gated on a `ctx.requestInput` confirmation read back from `ctx.inputs`, with `destructiveHint` annotation so clients that never fulfil the round still surface the risk
705
710
  - [ ] **If a parameter determines blast radius:** safe default set (e.g., `mode: 'preview'`, `dryRun: true`, `confirmCount` required)
706
711
  - [ ] **App tools default to no.** If one was proposed, verified there's a real human-in-the-loop in an MCP Apps-capable client justifying the iframe/CSP/`format()`-twin maintenance cost — otherwise dropped in favor of a standard tool