@cyanheads/mcp-ts-core 0.12.4 → 0.12.6

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 (153) hide show
  1. package/AGENTS.md +26 -15
  2. package/CLAUDE.md +26 -15
  3. package/README.md +1 -1
  4. package/biome.json +1 -1
  5. package/changelog/0.12.x/0.12.5.md +18 -0
  6. package/changelog/0.12.x/0.12.6.md +44 -0
  7. package/dist/config/appRoot.d.ts +28 -0
  8. package/dist/config/appRoot.d.ts.map +1 -0
  9. package/dist/config/appRoot.js +141 -0
  10. package/dist/config/appRoot.js.map +1 -0
  11. package/dist/config/index.d.ts.map +1 -1
  12. package/dist/config/index.js +11 -39
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/core/app.d.ts +2 -2
  15. package/dist/core/app.d.ts.map +1 -1
  16. package/dist/core/app.js +6 -2
  17. package/dist/core/app.js.map +1 -1
  18. package/dist/core/index.d.ts +5 -5
  19. package/dist/core/index.js +2 -2
  20. package/dist/core/worker.d.ts +1 -1
  21. package/dist/core/worker.js +1 -1
  22. package/dist/mcp-server/apps/appBuilders.d.ts +2 -2
  23. package/dist/mcp-server/apps/appBuilders.js +2 -2
  24. package/dist/mcp-server/cacheHints.d.ts +1 -1
  25. package/dist/mcp-server/prompts/prompt-registration.d.ts +1 -1
  26. package/dist/mcp-server/prompts/prompt-registration.js +1 -1
  27. package/dist/mcp-server/resources/resource-registration.d.ts +3 -3
  28. package/dist/mcp-server/resources/resource-registration.js +1 -1
  29. package/dist/mcp-server/resources/resourceSubscriptions.d.ts +1 -1
  30. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -2
  31. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +4 -4
  32. package/dist/mcp-server/server.d.ts +4 -4
  33. package/dist/mcp-server/server.js +1 -1
  34. package/dist/mcp-server/tools/tool-registration.d.ts +3 -3
  35. package/dist/mcp-server/tools/tool-registration.js +2 -2
  36. package/dist/mcp-server/tools/utils/disabled-tool.d.ts +1 -1
  37. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +1 -1
  38. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +4 -4
  39. package/dist/mcp-server/transports/auth/authFactory.d.ts +1 -1
  40. package/dist/mcp-server/transports/auth/authFactory.js +2 -2
  41. package/dist/mcp-server/transports/auth/authMiddleware.d.ts +1 -1
  42. package/dist/mcp-server/transports/auth/authMiddleware.js +1 -1
  43. package/dist/mcp-server/transports/auth/lib/authUtils.js +1 -1
  44. package/dist/mcp-server/transports/auth/lib/claimParser.d.ts +1 -1
  45. package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +1 -1
  46. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +2 -2
  47. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +1 -1
  48. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +2 -2
  49. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +1 -1
  50. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts +1 -1
  51. package/dist/mcp-server/transports/http/httpServer.d.ts +1 -1
  52. package/dist/mcp-server/transports/http/httpServer.js +1 -1
  53. package/dist/mcp-server/transports/http/httpTransport.d.ts +3 -3
  54. package/dist/mcp-server/transports/http/httpTransport.js +12 -12
  55. package/dist/mcp-server/transports/http/landing-page/handler.d.ts +1 -1
  56. package/dist/mcp-server/transports/http/sessionStore.js +1 -1
  57. package/dist/mcp-server/transports/manager.d.ts +2 -2
  58. package/dist/mcp-server/transports/manager.js +3 -3
  59. package/dist/services/llm/providers/openrouter.provider.d.ts +1 -1
  60. package/dist/storage/core/StorageService.d.ts +1 -1
  61. package/dist/storage/core/StorageService.js +1 -1
  62. package/dist/storage/core/storageFactory.d.ts +2 -2
  63. package/dist/storage/core/storageFactory.js +6 -6
  64. package/dist/storage/core/storageValidation.d.ts.map +1 -1
  65. package/dist/storage/core/storageValidation.js +5 -3
  66. package/dist/storage/core/storageValidation.js.map +1 -1
  67. package/dist/storage/providers/cloudflare/d1Provider.d.ts +1 -1
  68. package/dist/storage/providers/cloudflare/d1Provider.js +1 -1
  69. package/dist/storage/providers/cloudflare/kvProvider.d.ts +1 -1
  70. package/dist/storage/providers/cloudflare/kvProvider.js +1 -1
  71. package/dist/storage/providers/cloudflare/r2Provider.d.ts +1 -1
  72. package/dist/storage/providers/cloudflare/r2Provider.js +1 -1
  73. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +1 -1
  74. package/dist/storage/providers/fileSystem/fileSystemProvider.js +1 -1
  75. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +3 -1
  76. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
  77. package/dist/storage/providers/inMemory/inMemoryProvider.js +11 -7
  78. package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
  79. package/dist/storage/providers/supabase/supabaseProvider.d.ts +2 -2
  80. package/dist/storage/providers/supabase/supabaseProvider.js +1 -1
  81. package/dist/testing/fuzz.d.ts.map +1 -1
  82. package/dist/testing/fuzz.js +92 -53
  83. package/dist/testing/fuzz.js.map +1 -1
  84. package/dist/utils/formatting/diffFormatter.d.ts +2 -2
  85. package/dist/utils/formatting/diffFormatter.js +4 -4
  86. package/dist/utils/formatting/markdownBuilder.d.ts +8 -0
  87. package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -1
  88. package/dist/utils/formatting/markdownBuilder.js +16 -1
  89. package/dist/utils/formatting/markdownBuilder.js.map +1 -1
  90. package/dist/utils/formatting/tableFormatter.d.ts +2 -2
  91. package/dist/utils/formatting/tableFormatter.js +3 -3
  92. package/dist/utils/formatting/treeFormatter.d.ts +3 -3
  93. package/dist/utils/formatting/treeFormatter.js +4 -4
  94. package/dist/utils/internal/error-handler/errorHandler.js +6 -6
  95. package/dist/utils/internal/error-handler/helpers.js +1 -1
  96. package/dist/utils/internal/error-handler/types.d.ts +1 -1
  97. package/dist/utils/internal/health.js +2 -2
  98. package/dist/utils/internal/lazyImport.js +1 -1
  99. package/dist/utils/internal/logger.d.ts +34 -3
  100. package/dist/utils/internal/logger.d.ts.map +1 -1
  101. package/dist/utils/internal/logger.js +87 -5
  102. package/dist/utils/internal/logger.js.map +1 -1
  103. package/dist/utils/internal/performance.d.ts +1 -1
  104. package/dist/utils/internal/performance.js +6 -6
  105. package/dist/utils/internal/requestContext.d.ts +1 -1
  106. package/dist/utils/internal/requestContext.js +2 -2
  107. package/dist/utils/metrics/tokenCounter.d.ts +1 -1
  108. package/dist/utils/metrics/tokenCounter.js +2 -2
  109. package/dist/utils/network/fetchWithTimeout.d.ts +1 -1
  110. package/dist/utils/network/fetchWithTimeout.js +6 -6
  111. package/dist/utils/network/httpError.js +1 -1
  112. package/dist/utils/network/retry.d.ts +1 -1
  113. package/dist/utils/network/retry.js +1 -1
  114. package/dist/utils/pagination/pagination.d.ts +2 -2
  115. package/dist/utils/pagination/pagination.js +4 -4
  116. package/dist/utils/parsing/csvParser.d.ts +4 -4
  117. package/dist/utils/parsing/csvParser.js +6 -6
  118. package/dist/utils/parsing/dateParser.d.ts +3 -3
  119. package/dist/utils/parsing/dateParser.js +6 -6
  120. package/dist/utils/parsing/frontmatterParser.d.ts +2 -2
  121. package/dist/utils/parsing/frontmatterParser.js +3 -3
  122. package/dist/utils/parsing/htmlExtractor.d.ts +3 -3
  123. package/dist/utils/parsing/htmlExtractor.js +5 -5
  124. package/dist/utils/parsing/jsonParser.d.ts +4 -4
  125. package/dist/utils/parsing/jsonParser.js +6 -6
  126. package/dist/utils/parsing/pdfParser.d.ts +2 -2
  127. package/dist/utils/parsing/pdfParser.js +4 -4
  128. package/dist/utils/parsing/xmlParser.d.ts +4 -4
  129. package/dist/utils/parsing/xmlParser.js +5 -5
  130. package/dist/utils/parsing/yamlParser.d.ts +4 -4
  131. package/dist/utils/parsing/yamlParser.js +6 -6
  132. package/dist/utils/scheduling/scheduler.d.ts +3 -3
  133. package/dist/utils/scheduling/scheduler.js +5 -5
  134. package/dist/utils/security/rateLimiter.d.ts +2 -2
  135. package/dist/utils/security/rateLimiter.js +3 -3
  136. package/dist/utils/security/rateLimiter.js.map +1 -1
  137. package/dist/utils/security/sanitization.d.ts +3 -3
  138. package/dist/utils/security/sanitization.js +7 -7
  139. package/dist/utils/telemetry/instrumentation.d.ts.map +1 -1
  140. package/dist/utils/telemetry/instrumentation.js +7 -8
  141. package/dist/utils/telemetry/instrumentation.js.map +1 -1
  142. package/dist/utils/telemetry/trace.d.ts +1 -1
  143. package/dist/utils/telemetry/trace.js +1 -1
  144. package/package.json +13 -7
  145. package/scripts/devcheck.ts +6 -4
  146. package/skills/api-config/SKILL.md +4 -2
  147. package/skills/api-utils/SKILL.md +2 -2
  148. package/skills/api-utils/references/formatting.md +1 -1
  149. package/skills/api-workers/SKILL.md +3 -3
  150. package/skills/setup/SKILL.md +14 -2
  151. package/templates/AGENTS.md +2 -2
  152. package/templates/CLAUDE.md +2 -2
  153. package/templates/_bunfig.toml +15 -0
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.12.4
4
+ **Version:** 0.12.6
5
5
  **Engines:** Bun ≥1.3.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.5.4
@@ -59,7 +59,7 @@ Both paths share the same public API. Init copies starter `package.json`, config
59
59
  | `/utils` | formatting, encoding, network, pagination, overflow (`outlineOnOverflow`, `OUTLINE_VARIANT`, `selectSections`, `formatOutline`), logging, runtime, telemetry, token counting, parsers†, sanitization†, scheduling† | All utilities (†optional peer deps) |
60
60
  | `/services` | `OpenRouterProvider`, `SpeechService`, `createSpeechProvider`, `ElevenLabsProvider`, `WhisperProvider`, `GraphService`, provider interfaces and types | LLM, Speech (TTS/STT), Graph services |
61
61
  | `/linter` | `validateDefinitions`, `LintReport`, `LintDiagnostic`, `LintInput`, `LintSeverity` | Definition validation |
62
- | `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage` | Test kit for handlers and upstream HTTP boundaries |
62
+ | `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage`, `expectInputRequired` | Test kit for handlers and upstream HTTP boundaries |
63
63
  | `/testing/fuzz` | `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, `adversarialArbitrary`, `ADVERSARIAL_STRINGS` | Fuzz testing |
64
64
  | `/testing/vitest` | `mcpTest`, `toolContractSuite`, `McpTestFixtures` (+ re-exported `/testing` helpers) | Vitest fixtures and tool conformance suites (optional peer `vitest`) |
65
65
 
@@ -76,7 +76,7 @@ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
76
76
  import { getMyService } from '@/services/my-domain/my-service.js';
77
77
  ```
78
78
 
79
- Build configs exported for consumer extension: `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`, `biome.json` extends `@cyanheads/mcp-ts-core/biome`, `vitest.config.ts` spreads from `@cyanheads/mcp-ts-core/vitest.config`.
79
+ Build configs exported for consumer extension: `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`, `biome.json` extends `@cyanheads/mcp-ts-core/biome`, `vitest.config.ts` folds in `@cyanheads/mcp-ts-core/vitest.config` via `mergeConfig`.
80
80
 
81
81
  ---
82
82
 
@@ -115,7 +115,7 @@ await createApp({
115
115
 
116
116
  **Identity fields** — Optional `title`, `websiteUrl`, `description`, `icons` (SEP-973) pass through to the SDK's `initialize` serverInfo and to the server manifest, keeping the `/.well-known/mcp.json` server card and landing page consistent with what `initialize` reports. Explicit `description` wins over `MCP_SERVER_DESCRIPTION`/package.json.
117
117
 
118
- **Also available** — `landing` (`LandingConfig`, HTTP transport only: landing-page config, all fields optional), `context: { exposeStatelessSessionId }` (populate `ctx.sessionId` from the SDK's per-request token in stateless HTTP mode; default `false`), and `cacheHints` (2026-07-28 `ttlMs`/`cacheScope` per cacheable operation — see Adding a Resource for the per-resource override).
118
+ **Also available** — `landing` (`LandingConfig`, HTTP transport only: landing-page config, all fields optional), `context: { exposeStatelessSessionId }` (populate `ctx.sessionId` from the SDK's per-request token in stateless HTTP mode; default `false`), `eventBus` (the `ServerEventBus` backing `subscriptions/listen`; defaults to an in-process bus — supply one for a multi-isolate or multi-process runtime, Workers most of all), and `cacheHints` (2026-07-28 `ttlMs`/`cacheScope` per cacheable operation — see Adding a Resource for the per-resource override).
119
119
 
120
120
  ### Cloudflare Workers — `createWorkerHandler(options)`
121
121
 
@@ -146,6 +146,7 @@ interface CoreServices {
146
146
  logger: Logger;
147
147
  storage: StorageService;
148
148
  rateLimiter: RateLimiter;
149
+ notify: ServerNotifier; // out-of-request list-changed / resource-updated publishing
149
150
  canvas?: DataCanvas; // present when CANVAS_PROVIDER_TYPE=duckdb; never on Workers
150
151
  llmProvider?: ILlmProvider;
151
152
  speechService?: SpeechService;
@@ -299,7 +300,7 @@ interface Context {
299
300
  readonly uri?: URL; // present for resource handlers
300
301
  readonly content: ContentCollect; // media blocks → prepended to content[]; never in structuredContent
301
302
  readonly enrich: Enrich; // success-path agent context → structuredContent + content[]; typed on HandlerContext<R, E>
302
- recoveryFor(reason: string): { recovery: { hint: string } } | {}; // opt-in contract resolver
303
+ recoveryFor(reason: string): { recovery: { hint: string } } | Record<string, never>; // opt-in contract resolver
303
304
  }
304
305
  ```
305
306
 
@@ -388,7 +389,7 @@ async handler(input, ctx) {
388
389
 
389
390
  **`ctx.recoveryFor(reason)`** returns `{}` when no contract exists (spread-safe). Typed against the declared reason union on `HandlerContext<R>`. Works in services: `throw validationError(msg, { reason: 'X', ...ctx.recoveryFor('X') })`. Opt-in — author spreads explicitly.
390
391
 
391
- **Contracts are inline, per-tool.** Don't extract shared `errors[]` constants — locality is the point, and dynamic `recovery` hints need tool-specific context. Declare domain-specific failures only; **baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) are auto-allowed by conformance lint. The lint scans handler source only — service-layer throws still reach clients via auto-classification.
392
+ **Contracts are inline, per-tool.** Don't extract shared `errors[]` constants — locality is the point, and dynamic `recovery` hints need tool-specific context. Declare domain-specific failures only; **baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) are auto-allowed by conformance lint. The lint scans handler source only — service-layer throws still reach clients via auto-classification.
392
393
 
393
394
  **Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code): use error factories.
394
395
 
@@ -398,7 +399,7 @@ throw notFound('Item not found', { itemId: '123' });
398
399
  throw validationError('Missing required field: name', { field: 'name' });
399
400
  ```
400
401
 
401
- Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`, `unauthorized`, `validationError`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`. All accept `(message, data?, options?)` where `options` is `{ cause?: unknown }`.
402
+ Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`, `unauthorized`, `validationError`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`, `requestCancelled`. All accept `(message, data?, options?)` where `options` is `{ cause?: unknown }`.
402
403
 
403
404
  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`.
404
405
 
@@ -435,7 +436,7 @@ Pick one convention per server and stay consistent. Verbs are typically `read`,
435
436
 
436
437
  ### Core config
437
438
 
438
- Managed by `@cyanheads/mcp-ts-core`. Validated via Zod. Precedence: `createApp()` overrides > env vars > `package.json` (reads `name` → `MCP_SERVER_NAME`, `version` → `MCP_SERVER_VERSION`).
439
+ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod. Precedence: `createApp()` overrides > env vars > `package.json` (reads `name` → `MCP_SERVER_NAME`, `version` → `MCP_SERVER_VERSION`). That manifest is the one at the application root — the nearest `package.json` above the entry module — never the launching client's working directory, and relative `logsPath`/`LOGS_DIR` values resolve against the same root.
439
440
 
440
441
  | Category | Key Variables |
441
442
  |:---------|:-------------|
@@ -467,7 +468,7 @@ describe('myTool', () => {
467
468
  });
468
469
  ```
469
470
 
470
- **`createMockContext` options:** `createMockContext()` (state included), `{ tenantId: 'test-tenant' }` (explicit tenant; defaults to `'default'`, as stdio resolves it), `{ errors: myTool.errors }` (typed `ctx.fail`), `{ elicit: vi.fn() }`, `{ progress: true }` (task progress).
471
+ **`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.
471
472
 
472
473
  **`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.
473
474
 
@@ -544,16 +545,26 @@ Skills live in `skills/<name>/SKILL.md`; the full list is discoverable via the a
544
545
  | `bun run lint:mcp` | Validate MCP definitions against spec |
545
546
  | `bun run format` | Auto-fix Biome lint/format issues (safe fixes only) |
546
547
  | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior, not just formatting |
547
- | `bun run test` | Unit/compliance/smoke/fuzz/typecheck suites (Bun runtime) |
548
- | `bun run test:all` | Release gate: `test:coverage` + `test:node` + `test:worker` + `test:integration` |
549
- | `bun run test:node` | Same suites + integration under real Node (bypasses the bun-node PATH shim) |
550
- | `bun run test:leaks` | Suites with Vitest async-leak detection (`--detect-async-leaks`) |
551
- | `bun run test:typecheck` | Typecheck project only — `.test-d.ts` contracts with `@ts-expect-error` negative cases |
548
+ | `bun run tree` | Regenerate `docs/tree.md` after the directory structure changes |
549
+ | `bun run test` | Every root project — unit, leak-gate, compliance, smoke, fuzz, typecheck (Bun runtime) |
550
+ | `bun run test:unit` / `:smoke` / `:fuzz` / `:compliance` / `:typecheck` | One root project via `--project`. `test:typecheck` runs the `.test-d.ts` contracts, whose `@ts-expect-error` cases are the negative assertions |
551
+ | `bun run test:leak-gate` | The retention gate's own sentinel suite. Each case spawns a full Vitest run, so it is excluded from the `unit` project |
552
+ | `bun run test:coverage` | Root projects with coverage thresholds enforced |
553
+ | `bun run test:integration` | Real server subprocesses over stdio and HTTP |
554
+ | `bun run test:worker` | The framework under real `workerd`, then a standalone Worker bundle through the Wrangler toolchain — real Node on both legs |
555
+ | `bun run test:package` | Rebuilds, packs the tarball, and consumes it as an external project would (exports, declarations, both runtimes) |
556
+ | `bun run test:node` | Root projects + integration under real Node via `scripts/with-node.ts`, which bypasses Bun's `node` PATH shim |
557
+ | `bun run test:order` | Root projects on real Node in shuffled file order under a pinned seed — catches inter-file state leakage |
558
+ | `bun run test:leaks` | Real-Node root async-resource retention gate; [scope and evidence](tests/leaks/README.md) |
559
+ | `bun run test:all` | Release gate: `rebuild` → `test:coverage` → `test:node` → `test:worker` → `test:integration`. `test:package` and `test:leaks` are separate lanes |
560
+ | `bun run bench` / `bench:node` / `bench:io` / `bench:io:bun` | Microbenchmarks (`tests/benchmarks/micro`) and opt-in transport I/O measurements (`tests/benchmarks/io`) |
552
561
  | `bun run start:stdio` | Production mode (stdio, after build) |
553
562
  | `bun run start:http` | Production mode (HTTP, after build) |
554
563
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
555
564
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync with `changelog/` (used by devcheck) |
556
565
 
566
+ Lane configs live in `tests/config/` (`vitest.integration.ts`, `vitest.worker.ts`, `vitest.worker-bundle.ts`, `vitest.package.ts`, `vitest.benchmark.ts`, `vitest.performance.ts`, `vitest.leaks.ts`, `vitest.leak-sentinels.ts`). Two configs stay at the repo root: `vitest.config.ts`, the root `projects` list a bare `vitest` invocation discovers, and `vitest.config.base.mjs`, the published `./vitest.config` export.
567
+
557
568
  After `bun update --latest`, run the `maintenance` skill to investigate changelogs, adopt upstream changes, and sync project skills.
558
569
 
559
570
  ---
@@ -591,7 +602,7 @@ security: false # optional, default fals
591
602
 
592
603
  Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 350 chars or malformed boolean fails `changelog:check`.
593
604
 
594
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Omit empty sections. Pre-release versions consolidate as sub-headers inside the final version's file — no separate files per pre-release.
605
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Omit empty sections. Pre-release versions consolidate as sub-headers inside the final version's file — no separate files per pre-release.
595
606
 
596
607
  ---
597
608
 
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.12.4
4
+ **Version:** 0.12.6
5
5
  **Engines:** Bun ≥1.3.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.5.4
@@ -59,7 +59,7 @@ Both paths share the same public API. Init copies starter `package.json`, config
59
59
  | `/utils` | formatting, encoding, network, pagination, overflow (`outlineOnOverflow`, `OUTLINE_VARIANT`, `selectSections`, `formatOutline`), logging, runtime, telemetry, token counting, parsers†, sanitization†, scheduling† | All utilities (†optional peer deps) |
60
60
  | `/services` | `OpenRouterProvider`, `SpeechService`, `createSpeechProvider`, `ElevenLabsProvider`, `WhisperProvider`, `GraphService`, provider interfaces and types | LLM, Speech (TTS/STT), Graph services |
61
61
  | `/linter` | `validateDefinitions`, `LintReport`, `LintDiagnostic`, `LintInput`, `LintSeverity` | Definition validation |
62
- | `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage` | Test kit for handlers and upstream HTTP boundaries |
62
+ | `/testing` | `createMockContext`, `createMockSession`, `createFetchMock`, `runToolContract`, `createMockLogger`, `getEnrichment`, `getContentBlocks`, `createInMemoryStorage`, `expectInputRequired` | Test kit for handlers and upstream HTTP boundaries |
63
63
  | `/testing/fuzz` | `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, `adversarialArbitrary`, `ADVERSARIAL_STRINGS` | Fuzz testing |
64
64
  | `/testing/vitest` | `mcpTest`, `toolContractSuite`, `McpTestFixtures` (+ re-exported `/testing` helpers) | Vitest fixtures and tool conformance suites (optional peer `vitest`) |
65
65
 
@@ -76,7 +76,7 @@ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
76
76
  import { getMyService } from '@/services/my-domain/my-service.js';
77
77
  ```
78
78
 
79
- Build configs exported for consumer extension: `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`, `biome.json` extends `@cyanheads/mcp-ts-core/biome`, `vitest.config.ts` spreads from `@cyanheads/mcp-ts-core/vitest.config`.
79
+ Build configs exported for consumer extension: `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`, `biome.json` extends `@cyanheads/mcp-ts-core/biome`, `vitest.config.ts` folds in `@cyanheads/mcp-ts-core/vitest.config` via `mergeConfig`.
80
80
 
81
81
  ---
82
82
 
@@ -115,7 +115,7 @@ await createApp({
115
115
 
116
116
  **Identity fields** — Optional `title`, `websiteUrl`, `description`, `icons` (SEP-973) pass through to the SDK's `initialize` serverInfo and to the server manifest, keeping the `/.well-known/mcp.json` server card and landing page consistent with what `initialize` reports. Explicit `description` wins over `MCP_SERVER_DESCRIPTION`/package.json.
117
117
 
118
- **Also available** — `landing` (`LandingConfig`, HTTP transport only: landing-page config, all fields optional), `context: { exposeStatelessSessionId }` (populate `ctx.sessionId` from the SDK's per-request token in stateless HTTP mode; default `false`), and `cacheHints` (2026-07-28 `ttlMs`/`cacheScope` per cacheable operation — see Adding a Resource for the per-resource override).
118
+ **Also available** — `landing` (`LandingConfig`, HTTP transport only: landing-page config, all fields optional), `context: { exposeStatelessSessionId }` (populate `ctx.sessionId` from the SDK's per-request token in stateless HTTP mode; default `false`), `eventBus` (the `ServerEventBus` backing `subscriptions/listen`; defaults to an in-process bus — supply one for a multi-isolate or multi-process runtime, Workers most of all), and `cacheHints` (2026-07-28 `ttlMs`/`cacheScope` per cacheable operation — see Adding a Resource for the per-resource override).
119
119
 
120
120
  ### Cloudflare Workers — `createWorkerHandler(options)`
121
121
 
@@ -146,6 +146,7 @@ interface CoreServices {
146
146
  logger: Logger;
147
147
  storage: StorageService;
148
148
  rateLimiter: RateLimiter;
149
+ notify: ServerNotifier; // out-of-request list-changed / resource-updated publishing
149
150
  canvas?: DataCanvas; // present when CANVAS_PROVIDER_TYPE=duckdb; never on Workers
150
151
  llmProvider?: ILlmProvider;
151
152
  speechService?: SpeechService;
@@ -299,7 +300,7 @@ interface Context {
299
300
  readonly uri?: URL; // present for resource handlers
300
301
  readonly content: ContentCollect; // media blocks → prepended to content[]; never in structuredContent
301
302
  readonly enrich: Enrich; // success-path agent context → structuredContent + content[]; typed on HandlerContext<R, E>
302
- recoveryFor(reason: string): { recovery: { hint: string } } | {}; // opt-in contract resolver
303
+ recoveryFor(reason: string): { recovery: { hint: string } } | Record<string, never>; // opt-in contract resolver
303
304
  }
304
305
  ```
305
306
 
@@ -388,7 +389,7 @@ async handler(input, ctx) {
388
389
 
389
390
  **`ctx.recoveryFor(reason)`** returns `{}` when no contract exists (spread-safe). Typed against the declared reason union on `HandlerContext<R>`. Works in services: `throw validationError(msg, { reason: 'X', ...ctx.recoveryFor('X') })`. Opt-in — author spreads explicitly.
390
391
 
391
- **Contracts are inline, per-tool.** Don't extract shared `errors[]` constants — locality is the point, and dynamic `recovery` hints need tool-specific context. Declare domain-specific failures only; **baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) are auto-allowed by conformance lint. The lint scans handler source only — service-layer throws still reach clients via auto-classification.
392
+ **Contracts are inline, per-tool.** Don't extract shared `errors[]` constants — locality is the point, and dynamic `recovery` hints need tool-specific context. Declare domain-specific failures only; **baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) are auto-allowed by conformance lint. The lint scans handler source only — service-layer throws still reach clients via auto-classification.
392
393
 
393
394
  **Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code): use error factories.
394
395
 
@@ -398,7 +399,7 @@ throw notFound('Item not found', { itemId: '123' });
398
399
  throw validationError('Missing required field: name', { field: 'name' });
399
400
  ```
400
401
 
401
- Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`, `unauthorized`, `validationError`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`. All accept `(message, data?, options?)` where `options` is `{ cause?: unknown }`.
402
+ Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`, `unauthorized`, `validationError`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`, `requestCancelled`. All accept `(message, data?, options?)` where `options` is `{ cause?: unknown }`.
402
403
 
403
404
  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`.
404
405
 
@@ -435,7 +436,7 @@ Pick one convention per server and stay consistent. Verbs are typically `read`,
435
436
 
436
437
  ### Core config
437
438
 
438
- Managed by `@cyanheads/mcp-ts-core`. Validated via Zod. Precedence: `createApp()` overrides > env vars > `package.json` (reads `name` → `MCP_SERVER_NAME`, `version` → `MCP_SERVER_VERSION`).
439
+ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod. Precedence: `createApp()` overrides > env vars > `package.json` (reads `name` → `MCP_SERVER_NAME`, `version` → `MCP_SERVER_VERSION`). That manifest is the one at the application root — the nearest `package.json` above the entry module — never the launching client's working directory, and relative `logsPath`/`LOGS_DIR` values resolve against the same root.
439
440
 
440
441
  | Category | Key Variables |
441
442
  |:---------|:-------------|
@@ -467,7 +468,7 @@ describe('myTool', () => {
467
468
  });
468
469
  ```
469
470
 
470
- **`createMockContext` options:** `createMockContext()` (state included), `{ tenantId: 'test-tenant' }` (explicit tenant; defaults to `'default'`, as stdio resolves it), `{ errors: myTool.errors }` (typed `ctx.fail`), `{ elicit: vi.fn() }`, `{ progress: true }` (task progress).
471
+ **`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.
471
472
 
472
473
  **`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.
473
474
 
@@ -544,16 +545,26 @@ Skills live in `skills/<name>/SKILL.md`; the full list is discoverable via the a
544
545
  | `bun run lint:mcp` | Validate MCP definitions against spec |
545
546
  | `bun run format` | Auto-fix Biome lint/format issues (safe fixes only) |
546
547
  | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior, not just formatting |
547
- | `bun run test` | Unit/compliance/smoke/fuzz/typecheck suites (Bun runtime) |
548
- | `bun run test:all` | Release gate: `test:coverage` + `test:node` + `test:worker` + `test:integration` |
549
- | `bun run test:node` | Same suites + integration under real Node (bypasses the bun-node PATH shim) |
550
- | `bun run test:leaks` | Suites with Vitest async-leak detection (`--detect-async-leaks`) |
551
- | `bun run test:typecheck` | Typecheck project only — `.test-d.ts` contracts with `@ts-expect-error` negative cases |
548
+ | `bun run tree` | Regenerate `docs/tree.md` after the directory structure changes |
549
+ | `bun run test` | Every root project — unit, leak-gate, compliance, smoke, fuzz, typecheck (Bun runtime) |
550
+ | `bun run test:unit` / `:smoke` / `:fuzz` / `:compliance` / `:typecheck` | One root project via `--project`. `test:typecheck` runs the `.test-d.ts` contracts, whose `@ts-expect-error` cases are the negative assertions |
551
+ | `bun run test:leak-gate` | The retention gate's own sentinel suite. Each case spawns a full Vitest run, so it is excluded from the `unit` project |
552
+ | `bun run test:coverage` | Root projects with coverage thresholds enforced |
553
+ | `bun run test:integration` | Real server subprocesses over stdio and HTTP |
554
+ | `bun run test:worker` | The framework under real `workerd`, then a standalone Worker bundle through the Wrangler toolchain — real Node on both legs |
555
+ | `bun run test:package` | Rebuilds, packs the tarball, and consumes it as an external project would (exports, declarations, both runtimes) |
556
+ | `bun run test:node` | Root projects + integration under real Node via `scripts/with-node.ts`, which bypasses Bun's `node` PATH shim |
557
+ | `bun run test:order` | Root projects on real Node in shuffled file order under a pinned seed — catches inter-file state leakage |
558
+ | `bun run test:leaks` | Real-Node root async-resource retention gate; [scope and evidence](tests/leaks/README.md) |
559
+ | `bun run test:all` | Release gate: `rebuild` → `test:coverage` → `test:node` → `test:worker` → `test:integration`. `test:package` and `test:leaks` are separate lanes |
560
+ | `bun run bench` / `bench:node` / `bench:io` / `bench:io:bun` | Microbenchmarks (`tests/benchmarks/micro`) and opt-in transport I/O measurements (`tests/benchmarks/io`) |
552
561
  | `bun run start:stdio` | Production mode (stdio, after build) |
553
562
  | `bun run start:http` | Production mode (HTTP, after build) |
554
563
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
555
564
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync with `changelog/` (used by devcheck) |
556
565
 
566
+ Lane configs live in `tests/config/` (`vitest.integration.ts`, `vitest.worker.ts`, `vitest.worker-bundle.ts`, `vitest.package.ts`, `vitest.benchmark.ts`, `vitest.performance.ts`, `vitest.leaks.ts`, `vitest.leak-sentinels.ts`). Two configs stay at the repo root: `vitest.config.ts`, the root `projects` list a bare `vitest` invocation discovers, and `vitest.config.base.mjs`, the published `./vitest.config` export.
567
+
557
568
  After `bun update --latest`, run the `maintenance` skill to investigate changelogs, adopt upstream changes, and sync project skills.
558
569
 
559
570
  ---
@@ -591,7 +602,7 @@ security: false # optional, default fals
591
602
 
592
603
  Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 350 chars or malformed boolean fails `changelog:check`.
593
604
 
594
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Omit empty sections. Pre-release versions consolidate as sub-headers inside the final version's file — no separate files per pre-release.
605
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Omit empty sections. Pre-release versions consolidate as sub-headers inside the final version's file — no separate files per pre-release.
595
606
 
596
607
  ---
597
608
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-0.12.4-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)
8
+ [![Version](https://img.shields.io/badge/Version-0.12.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
9
 
10
10
  [![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.3.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.11/schema.json",
3
3
  "vcs": {
4
4
  "enabled": true,
5
5
  "clientKind": "git",
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Server identity now resolves from the served package instead of process.cwd() or the framework's own install path, closing two independent bugs. A server's setup() logging is buffered and replayed instead of dropped, MarkdownBuilder.codeBlock sizes its fence to survive embedded backticks, and init ships bunfig.toml again."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.12.5 — 2026-09-02
8
+
9
+ ## Changed
10
+
11
+ - Shipped skills: `api-config` 1.15, `api-utils` 2.8, `setup` 1.10.
12
+
13
+ ## Fixed
14
+
15
+ - **Server identity anchors on the served package, not the caller's working directory** ([#374](https://github.com/cyanheads/mcp-ts-core/issues/374), [#373](https://github.com/cyanheads/mcp-ts-core/issues/373)) — a new `resolveAppRoot()` finds the nearest `package.json` above the process entry module, replacing the `process.cwd()` read behind name/version/description/keywords and the framework's own install-path anchor behind a relative `logsPath`.
16
+ - **Records logged from a server's `setup()` hook are no longer dropped** ([#381](https://github.com/cyanheads/mcp-ts-core/issues/381)) — `Logger` buffers up to 250 pre-init records and replays them once `initialize()` runs; a startup failure before that drains them to stderr instead.
17
+ - **`MarkdownBuilder.codeBlock()` sizes its fence past the longest backtick run in the content** ([#375](https://github.com/cyanheads/mcp-ts-core/issues/375)) — a payload containing a run at least as long as the old fixed triple-backtick fence could break out and be reparsed as the tool's own output; unaffected content still renders byte-for-byte.
18
+ - **`bunx @cyanheads/mcp-ts-core init` scaffolds with `bunfig.toml` again** ([#383](https://github.com/cyanheads/mcp-ts-core/issues/383)) — renamed to `templates/_bunfig.toml`, since Bun's packer silently dropped the un-prefixed path from the published tarball. `scripts/verify-package.ts` now cross-checks `bun pm pack` against `npm pack` so a future divergence fails the release gate.
@@ -0,0 +1,44 @@
1
+ ---
2
+ summary: "Five source fixes: detached storage writes after a capacity sweep, an unevictable empty rate-limit key, a module-load cursor key that broke standalone Worker startup, fuzz that never reached pattern-validated handlers, and a leaked shutdown deadline. The test suite trades type-only assertions for behavior and gains three lanes."
3
+ breaking: false
4
+ security: true
5
+ agent-notes: |
6
+ Scaffold sync only: re-sync a project copy of CLAUDE.md/AGENTS.md for the
7
+ Dependencies and RequestCancelled bullet under Changed. Nothing else to adopt.
8
+ ---
9
+
10
+ # 0.12.6 — 2026-09-06
11
+
12
+ ## Added
13
+
14
+ - **`bun run test:leaks` is an async-resource retention gate** ([#404](https://github.com/cyanheads/mcp-ts-core/issues/404)) — it runs the root runtime projects on real Node, observes every allocation from file collection through worker teardown, and exits nonzero on retained operations, missing evidence, or forced termination. Its sentinel suite runs separately as `bun run test:leak-gate`. Scope and evidence: [tests/leaks/README.md](tests/leaks/README.md).
15
+ - **Benchmark lanes** — `bun run bench` / `bench:node` for micro workloads over the tool pipeline, storage, and rate limiter, and `bench:io` / `bench:io:bun` for opt-in HTTP, Workerd, and native I/O measurements. Reports carry runtime and machine provenance; no timing threshold enters `test:all`. See [tests/benchmarks/README.md](tests/benchmarks/README.md).
16
+ - **`bun run test:worker` also boots the built bundle standalone** ([#406](https://github.com/cyanheads/mcp-ts-core/issues/406)) — a Wrangler-hosted lane outside the Workerd test pool's module loader, which is where a startup-time failure is observable at all.
17
+
18
+ ## Changed
19
+
20
+ - **Lane configs live in `tests/config/`** — `vitest.integration.ts`, `vitest.worker.ts`, and `vitest.package.ts` moved there, joined by the new worker-bundle, benchmark, performance, and leak configs. `vitest.config.ts` and the published `vitest.config.base.mjs` stay at the repository root.
21
+ - **`scripts/with-node.ts` selects the real Node binary** for `test:node`, `test:order`, `test:worker`, and `bench:node`, replacing the inline shell each script carried.
22
+ - **Scaffolded `CLAUDE.md` / `AGENTS.md`** — the changelog section order ends with `Dependencies`, and `RequestCancelled` is listed among the baseline error codes a tool need not declare.
23
+ - **Test assertions moved from type shapes to behavior** — ten type-only suites that imported no runtime value, plus a duplicate `Context` suite, are gone (~2,700 lines). The behavior they claimed to cover is now exercised directly: `Context` state bounds and cancellation between I/O steps, the multi-round-trip input helper, format parity across every enum member, and Worker lifecycle-failure, storage-failure, and wire-contract paths.
24
+ - **Every suite passes in shuffled order** — session stores, mirror handles, sockets, and response bodies that outlived their cases are settled per test, and paired tests no longer depend on file order. The disabled OpenRouter provider suite is rewritten and running again.
25
+ - **The skip-only telemetry suite is gone** ([#362](https://github.com/cyanheads/mcp-ts-core/issues/362)) — `instrumentation.test.ts` carried nine skips and a todo attributing them to Bun; `instrumentation.lifecycle.test.ts` now covers all of it, including the lightweight-mode and idempotency cases the skips masked.
26
+ - Shipped skills: `api-workers` 1.8.
27
+
28
+ ## Fixed
29
+
30
+ - **`InMemoryProvider` reattaches the tenant map after a capacity sweep** ([#403](https://github.com/cyanheads/mcp-ts-core/issues/403)) — a sweep that removed the target tenant left `set()` writing into a detached map, so the value was unreachable while `size` still counted it. `setMany()` now preflights against the snapshot it counted rather than a later one.
31
+ - **The cursor HMAC key is generated on first use, not at module load** ([#406](https://github.com/cyanheads/mcp-ts-core/issues/406)) — Workerd forbids random generation during global evaluation, so a standalone bundle failed to start. Cursor encoding, truncation, and tenant/tamper rejection are unchanged.
32
+ - **`fuzzTool` and `fuzzResource` reach pattern-validated handlers** ([#385](https://github.com/cyanheads/mcp-ts-core/issues/385), [#350](https://github.com/cyanheads/mcp-ts-core/issues/350)) — a `.regex()` field now drives generation instead of rejecting every sample at parse, resource runs receive the definition's `errors[]`, and tool, resource, and prompt results are validated after the call rather than assumed.
33
+ - **`shutdownOpenTelemetry()` clears its timeout on every path** — a rejected shutdown left the deadline timer armed.
34
+ - **The root `.mcpbignore` anchors its development-directory patterns** ([#343](https://github.com/cyanheads/mcp-ts-core/issues/343)) — `/.claude/`, `/.agents/`, and `/skills/` no longer strip matching paths under `node_modules`, and `devcheck` runs Packaging whenever `.mcpbignore` exists, not only alongside a manifest.
35
+
36
+ ## Security
37
+
38
+ - **An empty rate-limit key is evicted like any other** ([#405](https://github.com/cyanheads/mcp-ts-core/issues/405)) — the eviction candidate was tested for truthiness, so `''` was selected and never deleted; a server whose `keyGenerator` can produce it grew the tracked map past `maxTrackedKeys` under distinct-key churn.
39
+
40
+ ## Dependencies
41
+
42
+ - `@opentelemetry/resources`, `@opentelemetry/sdk-metrics`, `@opentelemetry/sdk-trace-node` 2.10.0 → 2.11.0
43
+ - `@supabase/supabase-js` 2.112.4 → 2.114.0
44
+ - `ignore` 7.0.7 → 7.0.8, `tsc-alias` 1.9.2 → 1.9.4 (lockfile only; the declared ranges are unchanged)
@@ -0,0 +1,28 @@
1
+ /** The subset of `package.json` fields that feed server identity. */
2
+ export type PackageManifest = {
3
+ name?: string;
4
+ version?: string;
5
+ description?: string;
6
+ keywords?: string[];
7
+ };
8
+ /** A resolved application root: the manifest and the directory holding it. */
9
+ export interface AppRoot {
10
+ /** Absolute path to the directory containing the manifest. */
11
+ dir: string;
12
+ /** The identity fields read from that directory's `package.json`. */
13
+ manifest: PackageManifest;
14
+ }
15
+ /**
16
+ * Resolves the consuming application's root, cached after the first call — the
17
+ * install location does not change at runtime. See {@link computeAppRoot} for
18
+ * the resolution order.
19
+ *
20
+ * @returns The resolved root, or `undefined` in Workers and when no manifest is found.
21
+ */
22
+ export declare function resolveAppRoot(): AppRoot | undefined;
23
+ /**
24
+ * Clears the cached root so the next {@link resolveAppRoot} call re-resolves.
25
+ * Exists for tests that move the entry module or working directory.
26
+ */
27
+ export declare function resetAppRootCache(): void;
28
+ //# sourceMappingURL=appRoot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appRoot.d.ts","sourceRoot":"","sources":["../../src/config/appRoot.ts"],"names":[],"mappings":"AAaA,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,WAAW,OAAO;IACtB,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,QAAQ,EAAE,eAAe,CAAC;CAC3B;AA2GD;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,OAAO,GAAG,SAAS,CAMpD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @fileoverview Resolves the consuming application's root — the directory of
3
+ * the nearest `package.json` at or above the process entry module. Server
4
+ * identity (name, version, description, keywords) and relative filesystem
5
+ * paths anchor here, so they follow the installed server rather than whatever
6
+ * directory the launching client happened to be in.
7
+ * @module src/config/appRoot
8
+ */
9
+ import { readFileSync, realpathSync, statSync } from 'node:fs';
10
+ import { dirname, resolve, sep } from 'node:path';
11
+ import { runtimeCaps } from '../utils/internal/runtime.js';
12
+ /** Reads the identity fields of `<dir>/package.json`, or `undefined` if absent/unreadable. */
13
+ function readManifest(dir) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8'));
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ const manifest = {};
22
+ if (typeof parsed.name === 'string')
23
+ manifest.name = parsed.name;
24
+ if (typeof parsed.version === 'string')
25
+ manifest.version = parsed.version;
26
+ if (typeof parsed.description === 'string')
27
+ manifest.description = parsed.description;
28
+ if (Array.isArray(parsed.keywords)) {
29
+ manifest.keywords = parsed.keywords.filter((k) => typeof k === 'string');
30
+ }
31
+ return manifest;
32
+ }
33
+ /** Walks up from `startDir` and returns the first directory holding a readable `package.json`. */
34
+ function findManifestUpward(startDir) {
35
+ let dir = startDir;
36
+ for (;;) {
37
+ const manifest = readManifest(dir);
38
+ if (manifest)
39
+ return { dir, manifest };
40
+ const parent = dirname(dir);
41
+ if (parent === dir)
42
+ return undefined;
43
+ dir = parent;
44
+ }
45
+ }
46
+ /**
47
+ * Returns the directory that owns the `node_modules` tree containing `dir`,
48
+ * or `undefined` when `dir` is not inside one.
49
+ */
50
+ function nodeModulesOwner(dir) {
51
+ const segments = dir.split(sep);
52
+ const index = segments.lastIndexOf('node_modules');
53
+ if (index <= 0)
54
+ return undefined;
55
+ return segments.slice(0, index).join(sep) || sep;
56
+ }
57
+ /** Absolute, symlink-resolved path, or `undefined` when it cannot be read. */
58
+ function realPath(path) {
59
+ try {
60
+ return realpathSync(resolve(path));
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ }
66
+ /**
67
+ * Directory of the process entry module. `process.argv[1]` may name a file, a
68
+ * directory (`node .`), or a symlinked bin shim, so the path is resolved
69
+ * through the filesystem before its directory is taken.
70
+ */
71
+ function entryDirectory() {
72
+ const entry = process.argv[1];
73
+ if (!entry)
74
+ return undefined;
75
+ const real = realPath(entry);
76
+ if (!real)
77
+ return undefined;
78
+ try {
79
+ return statSync(real).isDirectory() ? real : dirname(real);
80
+ }
81
+ catch {
82
+ return dirname(real);
83
+ }
84
+ }
85
+ /**
86
+ * Resolves the application root without consulting the cache.
87
+ *
88
+ * Resolution order:
89
+ *
90
+ * 1. The nearest `package.json` at or above the entry module (`process.argv[1]`).
91
+ * This is the installed server's own package on every stdio launch path —
92
+ * `npx`, `.mcpb` bundles, a client config naming `dist/index.js` — none of
93
+ * which run from the package root.
94
+ * 2. When that manifest belongs to a tool installed under the application's own
95
+ * `node_modules` and the process runs from the directory owning it — a test
96
+ * runner spawning the process is the common case — the owning directory's
97
+ * manifest wins. A dependency's manifest is never the application's identity.
98
+ * 3. The nearest `package.json` at or above `process.cwd()`, when there is no
99
+ * entry module to anchor on (`node -e`, an embedded host).
100
+ */
101
+ function computeAppRoot() {
102
+ if (!runtimeCaps.isNode || !runtimeCaps.hasProcess || runtimeCaps.isWorkerLike)
103
+ return undefined;
104
+ const cwd = realPath(process.cwd());
105
+ const entryDir = entryDirectory();
106
+ const fromEntry = entryDir ? findManifestUpward(entryDir) : undefined;
107
+ if (!fromEntry)
108
+ return cwd ? findManifestUpward(cwd) : undefined;
109
+ const owner = nodeModulesOwner(fromEntry.dir);
110
+ if (owner && owner === cwd) {
111
+ const manifest = readManifest(owner);
112
+ if (manifest)
113
+ return { dir: owner, manifest };
114
+ }
115
+ return fromEntry;
116
+ }
117
+ let _appRoot;
118
+ let _resolved = false;
119
+ /**
120
+ * Resolves the consuming application's root, cached after the first call — the
121
+ * install location does not change at runtime. See {@link computeAppRoot} for
122
+ * the resolution order.
123
+ *
124
+ * @returns The resolved root, or `undefined` in Workers and when no manifest is found.
125
+ */
126
+ export function resolveAppRoot() {
127
+ if (!_resolved) {
128
+ _appRoot = computeAppRoot();
129
+ _resolved = true;
130
+ }
131
+ return _appRoot;
132
+ }
133
+ /**
134
+ * Clears the cached root so the next {@link resolveAppRoot} call re-resolves.
135
+ * Exists for tests that move the entry module or working directory.
136
+ */
137
+ export function resetAppRootCache() {
138
+ _appRoot = undefined;
139
+ _resolved = false;
140
+ }
141
+ //# sourceMappingURL=appRoot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appRoot.js","sourceRoot":"","sources":["../../src/config/appRoot.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB,wEAAwE;AACxE,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,iBAAiB,QAAkC,CAAC;AAyDjE,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgXd,CAAC;AAGL,QAAA,MAAM,WAAW,kBAAmB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6MrE,CAAC;AAIF;;;;;;;;GAQG;AACH,QAAA,MAAM,WAAW,kBAAmB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAAG,IAExE,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBV,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,wEAAwE;AACxE,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,iBAAiB,QAAkC,CAAC;AAgCjE,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgXd,CAAC;AAGL,QAAA,MAAM,WAAW,kBAAmB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+MrE,CAAC;AAIF;;;;;;;;GAQG;AACH,QAAA,MAAM,WAAW,kBAAmB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAAG,IAExE,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBV,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC"}