@cyanheads/mcp-ts-core 0.9.4 → 0.9.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.
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.9.4
4
+ **Version:** 0.9.6
5
5
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
7
7
  **Zod:** ^4.4.3
@@ -32,8 +32,8 @@ Both paths share the same public API. Init copies starter `package.json`, config
32
32
  - **Full-stack observability.** The framework automatically instruments every tool/resource call — OTel span, duration/payload/memory metrics, structured completion log. Use `ctx.log` for additional domain-specific logging within handlers (external API calls, multi-step operations, business events). `requestId`, `traceId`, `tenantId` auto-correlated. No `console` calls.
33
33
  - **Unified Context.** Handlers receive `ctx` with logging (`ctx.log`), tenant-scoped storage (`ctx.state`), optional protocol capabilities (`ctx.elicit`, `ctx.sample`), and cancellation (`ctx.signal`).
34
34
  - **Decoupled storage.** `ctx.state` for tenant-scoped KV. Never access persistence backends directly.
35
- - **Canvas tokens are capabilities, not tenant-scoped state.** A `canvasId` is a 10-char URL-safe token; possession grants full read/write/drop on that canvas. Tokens are designed to be shareable — between agents in one session, and across users in single-tenant deployments (see tenant resolution table under `ctx.state`). Tools should accept the token in `input` (or omit to create fresh) and return it in `output`; collaboration is opt-in via explicit token exchange.
36
- - **Runtime parity.** All features work with `stdio`/`http` and Worker bundle. Guard non-portable deps via `runtimeCaps` from `@cyanheads/mcp-ts-core/utils` — a frozen capability object (`isNode`, `isBun`, `isWorkerLike`, `hasBuffer`, `hasProcess`, etc.) computed once at module load. Prefer runtime-agnostic abstractions (Hono + `@hono/mcp`, Fetch APIs).
35
+ - **Canvas tokens are capabilities, not tenant-scoped state.** A `canvasId` is a 10-char URL-safe token; possession grants full read/write/drop. Shareable between agents and across users in single-tenant deployments. Tools accept token in `input` (omit to create fresh) and return in `output`; collaboration is opt-in via token exchange.
36
+ - **Runtime parity.** All features work across `stdio`/`http`/Worker. Guard non-portable deps via `runtimeCaps` from `/utils` (`isNode`, `isBun`, `isWorkerLike`, `hasBuffer`, `hasProcess`, etc.). Prefer runtime-agnostic abstractions (Hono, Fetch APIs).
37
37
  - **Definition linting is build-time only.** Run `bun run lint:mcp` (standalone) or `bun run devcheck` (gate). Not invoked at server startup — new lint rules are additive and never break deployed servers. Every diagnostic links to the rule reference in `api-linter` skill; see that skill for the full rule catalog.
38
38
  - **Elicitation for missing input.** Use `ctx.elicit` when the client supports it.
39
39
 
@@ -106,7 +106,7 @@ await createApp({
106
106
  });
107
107
  ```
108
108
 
109
- **`instructions`** — Optional server-level orientation text. Surfaces on every `initialize` response so spec-compliant clients can forward it to the model as session-level system context. Use for deployment-specific guidance (configured connection aliases, regional notes, scope hints, shortcuts) instead of leaking that text into every tool description. Client adoption is uneven, but clients that ignore the field are no worse off than they are today — strict improvement when set.
109
+ **`instructions`** — Optional server-level orientation, surfaced on every `initialize` response as session-level system context. Use for deployment-specific guidance (connection aliases, regional notes, scope hints) instead of repeating in tool descriptions. Client adoption uneven but no downside when set.
110
110
 
111
111
  ### Cloudflare Workers — `createWorkerHandler(options)`
112
112
 
@@ -125,8 +125,6 @@ export default createWorkerHandler({
125
125
  });
126
126
  ```
127
127
 
128
- `instructions` on the Worker handler accepts either a plain string or a `(env) => string` resolver so deployment env (injected at request time) can shape the text.
129
-
130
128
  Per-request `McpServer` factory (security: SDK GHSA-345p-7cg4-v4c7). Requires `compatibility_flags = ["nodejs_compat"]` and `compatibility_date >= "2025-09-01"` in `wrangler.toml`. Only `in-memory`, `cloudflare-r2`, `cloudflare-kv`, `cloudflare-d1` storage in Workers. See `api-workers` skill for full details.
131
129
 
132
130
  ### Interfaces
@@ -260,56 +258,6 @@ Handler receives `(params, ctx)` — URI on `ctx.uri` if needed. Optional `size`
260
258
 
261
259
  ---
262
260
 
263
- ## Adding a Prompt
264
-
265
- ```ts
266
- import { prompt, z } from '@cyanheads/mcp-ts-core';
267
-
268
- export const codeReview = prompt('code_review', {
269
- description: 'Review code for security and best practices.',
270
- args: z.object({
271
- code: z.string().describe('Code to review'),
272
- language: z.string().optional().describe('Programming language'),
273
- }),
274
- generate: (args) => [
275
- { role: 'user', content: { type: 'text', text: `Review this ${args.language ?? ''} code:\n${args.code}` } },
276
- ],
277
- });
278
- ```
279
-
280
- Prompts are pure message templates — no `Context`, no auth, no side effects.
281
-
282
- ---
283
-
284
- ## Adding a Service
285
-
286
- Init/accessor pattern — initialized in `setup()`, accessed at request time.
287
-
288
- ```ts
289
- export class MyService {
290
- constructor(private readonly config: AppConfig, private readonly storage: StorageService) {}
291
- async doWork(input: string, ctx: Context): Promise<string> {
292
- ctx.log.debug('Working', { input });
293
- return `done: ${input}`;
294
- }
295
- }
296
-
297
- let _service: MyService | undefined;
298
- export function initMyService(config: AppConfig, storage: StorageService): void {
299
- _service = new MyService(config, storage);
300
- }
301
- export function getMyService(): MyService {
302
- if (!_service) throw new Error('MyService not initialized — call initMyService() in setup()');
303
- return _service;
304
- }
305
- ```
306
-
307
- Usage: `getMyService().doWork(input.query, ctx)`. Convention: `ctx.elicit`/`ctx.sample` only from tool handlers, not services.
308
-
309
- **API efficiency:** Prefer batch endpoints over N+1 individual requests. Use field selection to minimize payload. Cross-reference batch responses against requested IDs to detect missing items. See `add-service` skill for patterns.
310
-
311
- ---
312
-
313
261
  ## Context
314
262
 
315
263
  ```ts
@@ -404,11 +352,9 @@ async handler(input, ctx) {
404
352
  }
405
353
  ```
406
354
 
407
- **`ctx.recoveryFor(reason)`** always present on `Context`, returns `{}` when no contract is attached or the reason is unknown (spread-safe). Strictly typed on `HandlerContext<R>` against the declared reason union. Works in services accepting `ctx`: `throw validationError(msg, { reason: 'X', ...ctx.recoveryFor('X') })`. No auto-population — author opts in by typing the helper.
408
-
409
- **Declare contracts inline on each tool.** The contract is part of the tool's public surface — one file should give the full picture (input, output, errors, handler, format). Don't extract a shared `errors[]` constant; per-tool repetition is the intended cost of locality, and dynamic `recovery` hints need tool-specific context.
355
+ **`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.
410
356
 
411
- The contract describes the **public failure surface**declare domain-specific failures only. **Baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble from anywhere and are auto-allowed by the conformance lint, so you don't need to enumerate them per-tool. The conformance lint scans handler source text only — failures thrown from called services aren't visible to it (still reach the client correctly via the auto-classifier, just without lint enforcement).
357
+ **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.
412
358
 
413
359
  **Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code): use error factories.
414
360
 
@@ -424,9 +370,9 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
424
370
 
425
371
  **Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: `McpError` code (preserved as-is) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name → `InternalError` fallback.
426
372
 
427
- **Error-path parity.** Tool errors apply the same parity as success: `content[]` carries markdown with `data.recovery.hint` mirrored in; `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw to the SDK via JSON-RPC error envelope (no parity wiring).
373
+ **Error-path parity.** Tool errors: `content[]` carries markdown with `data.recovery.hint`; `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope.
428
374
 
429
- The startup linter checks handler bodies for `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, plus contract conformance (`error-contract-conformance` for undeclared non-baseline codes, `error-contract-prefer-fail` for declared codes thrown directly instead of via `ctx.fail`) — all warnings, surfaced in `bun run devcheck`.
375
+ **Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`. See `api-linter` skill.
430
376
 
431
377
  See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
432
378
 
@@ -447,7 +393,7 @@ Pick one convention per server and stay consistent. Verbs are typically `read`,
447
393
 
448
394
  **Modes** (`MCP_AUTH_MODE`): `none` (default) | `jwt` (local secret via `MCP_AUTH_SECRET_KEY`) | `oauth` (JWKS via `OAUTH_ISSUER_URL`, `OAUTH_AUDIENCE`). See `api-auth` skill for claims, CORS, and detailed config.
449
395
 
450
- **Granted scopes** union `scp`, `scope`, and `mcp_tool_scopes` JWT claims. `mcp_tool_scopes` is the escape hatch for OIDC providers (Authentik, Keycloak < 26.5, Zitadel) that ignore property mappings overriding `scope` in `authorization_code` flow. When no custom claim can be injected, `MCP_AUTH_DISABLE_SCOPE_CHECKS=true` bypasses both `withRequiredScopes` and `checkScopes` after auth-context presence check (signature/audience/issuer/expiry intact). Startup logs `WARNING` when active.
396
+ **Granted scopes** union `scp`, `scope`, and `mcp_tool_scopes` JWT claims. `mcp_tool_scopes` is the OIDC escape hatch (Authentik, Keycloak < 26.5, Zitadel). `MCP_AUTH_DISABLE_SCOPE_CHECKS=true` bypasses scope checks while preserving auth-context verification (signature/audience/issuer/expiry). Logs `WARNING` at startup.
451
397
 
452
398
  ---
453
399
 
@@ -514,41 +460,11 @@ Detailed method signatures, options, and examples live in skill files. Read the
514
460
 
515
461
  ### Skill versioning
516
462
 
517
- Each `skills/<name>/SKILL.md` carries a `metadata.version` string in its frontmatter. The downstream `maintenance` skill's Phase A reads this field to decide whether to replace a consumer's local copy Phase A replaces the **entire skill directory** (SKILL.md plus everything under `references/`, `templates/`, etc.) as one unit. When content changes anywhere in the directory without a version bump on SKILL.md, Phase A skips the skill and drift surfaces only through the noisier content-hash backstop.
518
-
519
- **Policy:** When you change SKILL.md, **or any file under `skills/<name>/`** (references, templates, examples anything the skill ships), bump `metadata.version` on that skill's SKILL.md in the same edit. The reference files themselves don't carry versions; SKILL.md is the single knob for the whole directory. Pure typo and whitespace fixes are exempt. One bump per release cycle is sufficient — if the file already carries an unreleased bump, additional edits within the same cycle don't each need their own.
520
-
521
- | Skill | Path | Covers |
522
- |:------|:-----|:-------|
523
- | `api-utils` | `skills/api-utils/SKILL.md` | formatting, parsing, security, network, pagination, runtime, scheduling, types, logger, requestContext, errorHandler, telemetry helpers (`withSpan`, `createCounter`, …) |
524
- | `api-telemetry` | `skills/api-telemetry/SKILL.md` | OTel catalog: span names, metric names + attributes, completion log fields, env config, runtime support, cardinality rules |
525
- | `api-services` | `skills/api-services/SKILL.md` | LLM (OpenRouter), Speech (ElevenLabs TTS, Whisper STT), Graph (CRUD, traversal, pathfinding) |
526
- | `api-context` | `skills/api-context/SKILL.md` | Context interface, createContext, ContextLogger/State/Progress |
527
- | `api-errors` | `skills/api-errors/SKILL.md` | McpError, JsonRpcErrorCode, error handling patterns |
528
- | `api-auth` | `skills/api-auth/SKILL.md` | Auth modes, scopes, JWT/OAuth strategies |
529
- | `api-config` | `skills/api-config/SKILL.md` | AppConfig, parseConfig, env vars |
530
- | `api-testing` | `skills/api-testing/SKILL.md` | createMockContext, test patterns, MockContextOptions |
531
- | `api-workers` | `skills/api-workers/SKILL.md` | createWorkerHandler, CloudflareBindings, Worker runtime |
532
- | `api-canvas` | `skills/api-canvas/SKILL.md` | DataCanvas primitive: acquire/register/query/export, token-sharing model, SQL gate, lifecycle, spillover pattern |
533
- | `api-linter` | `skills/api-linter/SKILL.md` | Definition lint rules (`format-parity`, `schema-*`, `name-*`, `server-json-*`, …) — look here when devcheck reports a lint diagnostic |
534
- | `add-tool` | `skills/add-tool/SKILL.md` | Scaffold a new MCP tool definition |
535
- | `add-app-tool` | `skills/add-app-tool/SKILL.md` | Scaffold an MCP App tool + UI resource pair |
536
- | `add-resource` | `skills/add-resource/SKILL.md` | Scaffold a new MCP resource definition |
537
- | `add-prompt` | `skills/add-prompt/SKILL.md` | Scaffold a new MCP prompt definition |
538
- | `add-service` | `skills/add-service/SKILL.md` | Scaffold a new domain service |
539
- | `add-test` | `skills/add-test/SKILL.md` | Scaffold test file for a tool, resource, or service |
540
- | `field-test` | `skills/field-test/SKILL.md` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
541
- | `security-pass` | `skills/security-pass/SKILL.md` | Review server for MCP-flavored security gaps: output injection, scope blast radius, elicit gaps, upstream auth, input sinks, tenant isolation, leakage, resource bounds |
542
- | `add-provider` | `skills/add-provider/SKILL.md` | Add a new provider implementation |
543
- | `add-export` | `skills/add-export/SKILL.md` | Add a new subpath export |
544
- | `design-mcp-server` | `skills/design-mcp-server/SKILL.md` | Design tool surface, resources, and service layer for a new server |
545
- | `setup` | `skills/setup/SKILL.md` | Initialize a new consumer server from the template |
546
- | `polish-docs-meta` | `skills/polish-docs-meta/SKILL.md` | Finalize docs, README, metadata, and agent protocol for shipping |
547
- | `report-issue-framework` | `skills/report-issue-framework/SKILL.md` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
548
- | `report-issue-local` | `skills/report-issue-local/SKILL.md` | File a bug or feature request against this server's own repo via `gh` CLI |
549
- | `release-and-publish` | `skills/release-and-publish/SKILL.md` | Post-wrapup ship workflow: verification gate, push, publish to npm/MCP Registry/GHCR |
550
- | `maintenance` | `skills/maintenance/SKILL.md` | Dependency updates, housekeeping tasks |
551
- | `migrate-mcp-ts-template` | `skills/migrate-mcp-ts-template/SKILL.md` | Migrate legacy template fork to package dependency |
463
+ Each `skills/<name>/SKILL.md` carries `metadata.version` in frontmatter. The `maintenance` skill's Phase A uses this to sync consumer copies — replaces the **entire skill directory** as one unit. Without a version bump, Phase A skips the skill (content-hash backstop catches drift, but noisier).
464
+
465
+ **Policy:** Bump `metadata.version` when changing any file under `skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. One bump per release cycle suffices.
466
+
467
+ Skills live in `skills/<name>/SKILL.md`. Read the relevant skill before starting a task it covers. The full list is discoverable via the agent's skill registry at session start.
552
468
 
553
469
  ---
554
470
 
@@ -581,7 +497,7 @@ Each `skills/<name>/SKILL.md` carries a `metadata.version` string in its frontma
581
497
  | `bun run build` | Build library output (`scripts/build.ts`) |
582
498
  | `bun run rebuild` | Clean and rebuild (`scripts/clean.ts` + `build`) |
583
499
  | `bun run devcheck` | **Use often.** Lint, format, typecheck, MCP definition linting, `bun audit`, `bun outdated` |
584
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so a stale-lockfile false positive can disguise an already-patched dep. If the advisory survives, it's real. |
500
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
585
501
  | `bun run lint:mcp` | Validate MCP definitions against spec |
586
502
  | `bun run format` | Auto-fix Biome lint/format issues |
587
503
  | `bun run test` | Unit/integration tests |
@@ -596,11 +512,9 @@ After `bun update --latest`, run the `maintenance` skill to investigate changelo
596
512
 
597
513
  ## Changelog
598
514
 
599
- Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth is `changelog/<major.minor>.x/<version>.md` — one standalone file per release (e.g. `changelog/0.5.x/0.5.4.md`). Per-version files ship in the npm package so agents can read a specific version directly from `node_modules` without parsing a monolithic file.
515
+ Directory-based. Source of truth: `changelog/<major.minor>.x/<version>.md` — one file per release (e.g. `changelog/0.5.x/0.5.4.md`), shipped in the npm package for direct agent access. `changelog/template.md` is the format reference (never edited).
600
516
 
601
- At release time, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a format reference never edited; consult it for frontmatter and section layout when scaffolding a new file. Be concise and accurate.
602
-
603
- `CHANGELOG.md` is a **navigation index**, not a copy of bodies — each entry is a clickable header + one-line summary pulled from the per-version file's frontmatter. Regenerated by `bun run changelog:build`. Devcheck runs `changelog:check` and hard-fails on drift. Never hand-edit `CHANGELOG.md` — edit the per-version file and rerun the build.
517
+ `CHANGELOG.md` is a **navigation index** clickable headers + one-line summaries from frontmatter. Regenerated by `bun run changelog:build`; `changelog:check` hard-fails on drift in devcheck. Never hand-edit edit the per-version file and rerun the build.
604
518
 
605
519
  ### Per-version file format
606
520
 
@@ -626,14 +540,14 @@ security: false # optional, default fals
626
540
  | `breaking` | no (default `false`) | Flags releases with breaking changes. Renders as `· ⚠️ Breaking` badge in the rollup. Agents running the `maintenance` skill read this to prioritize review. |
627
541
  | `security` | no (default `false`) | Flags releases with security fixes. Renders as `· 🛡️ Security` badge in the rollup so users can triage upgrade urgency. Pairs with the `## Security` body section. |
628
542
 
629
- When both flags are set, badges render in fixed order: `· ⚠️ Breaking · 🛡️ Security`. Summary > 350 chars or a malformed boolean fails `changelog:check`. Missing `summary` emits a warning and renders the rollup entry as header-only.
630
-
631
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
543
+ Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 350 chars or malformed boolean fails `changelog:check`.
632
544
 
633
- Pre-release versions (`0.6.0-beta.1`, `0.6.0-rc.1`, etc.) are consolidated as `##`/`###` sub-headers inside the final version's per-version file (`changelog/0.6.x/0.6.0.md`) when the final ships — they share the final version's frontmatter, no separate files per pre-release.
545
+ **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.
634
546
 
635
547
  ---
636
548
 
637
549
  ## Publishing
638
550
 
639
- If the user requests it, run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test:all`), pushes commits and tags, and publishes to every applicable destination. The full reference:
551
+ If the user requests it, run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test:all`), pushes commits and tags, and publishes to every applicable destination. After pushing, create a GitHub Release on the annotated tag (`gh release create v<VERSION> --verify-tag --notes-from-tag`) — no assets to attach, but the Release surfaces the tag's notes in the repo UI. **Skip the Docker build/push step** — this framework package is consumed via npm, not as a container image.
552
+
553
+ **Tag annotation subjects must omit the version number.** GitHub prepends `v<VERSION>:` to the release title when using `--notes-from-tag`. A subject like `0.9.5 — some change` renders as `v0.9.5: 0.9.5 — some change`. Write the subject as just the description: `mcpbignore recursive-match fix, zod to dependencies`.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.9.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-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
10
+ [![Version](https://img.shields.io/badge/Version-0.9.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-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
11
11
 
12
12
  [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-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/)
13
13
 
package/biome.json CHANGED
@@ -45,7 +45,7 @@
45
45
  "noImportCycles": "error",
46
46
  "noUnusedExpressions": "error",
47
47
  "noDeprecatedImports": "error",
48
- "noDuplicateDependencies": "error"
48
+ "noDuplicateDependencies": "warn"
49
49
  },
50
50
  "complexity": {
51
51
  "noUselessUndefined": "error"
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "mcpbignore recursive-match fix, zod promoted to dependencies, polish-docs-meta MCPB step, maintenance template-file adoption, CLAUDE.md condensed"
3
+ ---
4
+
5
+ # 0.9.5 — 2026-05-23
6
+
7
+ ## Fixed
8
+
9
+ - `.mcpbignore` template: removed bare `src/`, `tests/`, `coverage/` entries that matched recursively and stripped `node_modules/**/build/src/`, breaking `@opentelemetry/api` CJS entry in MCPB bundles ([#146](https://github.com/cyanheads/mcp-ts-core/issues/146))
10
+ - `zod` added to `dependencies` (was peer-only); stale `node_modules` could leave zod unresolved since the framework imports it at runtime
11
+
12
+ ## Changed
13
+
14
+ - `CLAUDE.md`/`AGENTS.md`: condensed prose across Error Handling, Auth, Changelog, Entry Points, Core Rules; removed Adding a Prompt, Adding a Service sections (covered by skills), removed 30-row skill table (redundant with session skill registry)
15
+ - `biome.json`: `noDuplicateDependencies` downgraded to `warn` — intentional `dependencies` + `peerDependencies` overlap for zod
16
+ - `skills/polish-docs-meta` v2.0 → v2.1: new Step 10 for MCPB bundling artifacts (`manifest.json`, `.mcpbignore`, `bundle`/`lint:packaging` scripts, README install badges)
17
+ - `skills/maintenance` v2.3 → v2.4: Step 4 scan table gains "New template-scaffolded files" row — flags missing files like `manifest.json` and `.mcpbignore` for adoption ([#147](https://github.com/cyanheads/mcp-ts-core/issues/147))
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "lint-packaging validates manifest name scope and user_config fields; multi-server-orchestration and polish-docs-meta skill gaps from pipeline run 2"
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.9.6 — 2026-05-23
7
+
8
+ ## Added
9
+
10
+ - **lint-packaging** validates `manifest.json` `name` doesn't contain an npm scope prefix (`@scope/`) — catches the scoped-name copy from `package.json` that renders in the mcpb install dialog ([#148](https://github.com/cyanheads/mcp-ts-core/issues/148))
11
+ - **lint-packaging** validates every `user_config` entry has `title` and `type` fields — catches the missing-field failures that `mcpb pack` would otherwise surface at release time ([#149](https://github.com/cyanheads/mcp-ts-core/issues/149))
12
+ - **lint-packaging** manifest-only checks (name scope, user_config fields) now run independently of `server.json`; cross-validation checks remain conditional on `server.json` presence
13
+
14
+ ## Changed
15
+
16
+ - **polish-docs-meta** v2.2: cross-file consistency section expanded — scope-stripped manifest name, `user_config` field completeness, `isRequired` accuracy, description alignment across 5 surfaces, baseline `package.json` keywords/topics
17
+ - **multi-server-orchestration** v1.2: parent gotcha table adds `--notes-from-tag` + `--repo` incompatibility
18
+ - **maintenance-pass** v1.2: pre-flight checks for `publish-mcp` script and `manifest.json` existence; Phase 3.5 double-check/normalization pass; gotchas for `internal`-audience skill sync, scoped manifest name, missing `user_config` fields
19
+ - **release-and-publish-pass** v1.3: Phase 5 adds MCPB bundle step (`bun run bundle` + `gh release create` with `.mcpb` asset); gotchas for `--notes-from-tag`+`--repo`, post-version tag movement, `user_config` field validation
20
+ - **wrapup-pass** v1.1: Phase 4 version bump step notes manifest name scope check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-core",
5
5
  "description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
6
6
  "main": "dist/core/index.js",
@@ -217,8 +217,7 @@
217
217
  "unpdf": "^1.6.2",
218
218
  "validator": "^13.15.35",
219
219
  "vite": "8.0.14",
220
- "vitest": "^4.1.7",
221
- "zod": "^4.4.3"
220
+ "vitest": "^4.1.7"
222
221
  },
223
222
  "keywords": [
224
223
  "agent",
@@ -278,7 +277,8 @@
278
277
  "dotenv": "^17.4.2",
279
278
  "hono": "^4.12.22",
280
279
  "jose": "^6.2.3",
281
- "pino": "^10.3.1"
280
+ "pino": "^10.3.1",
281
+ "zod": "^4.4.3"
282
282
  },
283
283
  "peerDependencies": {
284
284
  "@duckdb/node-api": "^1.5.0",
@@ -8,12 +8,16 @@
8
8
  * `npm run lint:packaging`.
9
9
  *
10
10
  * Checks:
11
- * 1. Every `${user_config.X}` reference in manifest `mcp_config.env` must
11
+ * 1. Manifest `name` must not contain a scope prefix (`@scope/`).
12
+ * 2. Every `user_config` entry must include `title` and `type` fields.
13
+ * 3. Every `${user_config.X}` reference in manifest `mcp_config.env` must
12
14
  * appear in server.json stdio `environmentVariables[]` (the registry
13
15
  * advertises the configurable knob the bundle surfaces).
14
- * 2. Every required stdio env var in server.json (no default) must appear
16
+ * 4. Every required stdio env var in server.json (no default) must appear
15
17
  * as a key in manifest `mcp_config.env` (the bundle can receive it).
16
18
  *
19
+ * Checks 1–2 run with `manifest.json` alone; 3–4 require `server.json`.
20
+ *
17
21
  * Skips cleanly when `manifest.json` is absent — consumers who deleted it for
18
22
  * an HTTP-only deploy should not fail this check.
19
23
  *
@@ -37,9 +41,16 @@ interface ServerJson {
37
41
  packages?: ServerJsonPackage[];
38
42
  }
39
43
 
44
+ interface ManifestUserConfigEntry {
45
+ title?: unknown;
46
+ type?: unknown;
47
+ [key: string]: unknown;
48
+ }
49
+
40
50
  interface Manifest {
51
+ name?: string;
41
52
  server?: { mcp_config?: { env?: Record<string, string> } };
42
- user_config?: Record<string, unknown>;
53
+ user_config?: Record<string, ManifestUserConfigEntry>;
43
54
  }
44
55
 
45
56
  const USER_CONFIG_REF = /^\$\{user_config\.([\w-]+)\}$/;
@@ -67,42 +78,59 @@ function main(): void {
67
78
  process.exit(1);
68
79
  }
69
80
 
70
- const serverJson = tryReadJson<ServerJson>(resolve('server.json'));
71
- if (!serverJson) {
72
- console.log('No server.json — skipping cross-validation.');
73
- process.exit(0);
74
- }
75
-
76
- const manifestEnv = manifest.server?.mcp_config?.env ?? {};
77
- const manifestEnvKeys = new Set(Object.keys(manifestEnv));
78
-
79
- const manifestUserConfigKeys = new Set(
80
- Object.entries(manifestEnv)
81
- .filter(([, v]) => typeof v === 'string' && USER_CONFIG_REF.test(v))
82
- .map(([k]) => k),
83
- );
84
-
85
- const stdioEnvVars = (serverJson.packages ?? [])
86
- .filter((p) => p.transport?.type === 'stdio')
87
- .flatMap((p) => p.environmentVariables ?? []);
88
- const stdioEnvNames = new Set(stdioEnvVars.map((v) => v.name));
89
- const requiredStdioEnvNames = new Set(
90
- stdioEnvVars.filter((v) => v.isRequired === true && v.default == null).map((v) => v.name),
91
- );
92
-
93
- const missingInServerJson = [...manifestUserConfigKeys].filter((k) => !stdioEnvNames.has(k));
94
- const missingInManifest = [...requiredStdioEnvNames].filter((k) => !manifestEnvKeys.has(k));
95
-
96
81
  const errors: string[] = [];
97
- if (missingInServerJson.length > 0) {
82
+
83
+ if (manifest.name?.includes('/')) {
98
84
  errors.push(
99
- `manifest.json references user_config env var(s) not advertised in server.json stdio environmentVariables[]: ${missingInServerJson.join(', ')}`,
85
+ `manifest.json "name" contains a scope prefix ("${manifest.name}") use the bare package name (e.g. "${manifest.name.split('/').pop()}")`,
100
86
  );
101
87
  }
102
- if (missingInManifest.length > 0) {
103
- errors.push(
104
- `server.json declares required stdio env var(s) without default missing from manifest.json mcp_config.env: ${missingInManifest.join(', ')}`,
88
+
89
+ const userConfig = manifest.user_config ?? {};
90
+ for (const [key, entry] of Object.entries(userConfig)) {
91
+ if (typeof entry !== 'object' || entry === null) continue;
92
+ const missing = (['title', 'type'] as const).filter(
93
+ (f) => typeof entry[f] !== 'string' || (entry[f] as string).length === 0,
94
+ );
95
+ if (missing.length > 0) {
96
+ errors.push(
97
+ `manifest.json user_config["${key}"] is missing required field(s): ${missing.join(', ')} — mcpb pack will reject this`,
98
+ );
99
+ }
100
+ }
101
+
102
+ const serverJson = tryReadJson<ServerJson>(resolve('server.json'));
103
+ if (serverJson) {
104
+ const manifestEnv = manifest.server?.mcp_config?.env ?? {};
105
+ const manifestEnvKeys = new Set(Object.keys(manifestEnv));
106
+
107
+ const manifestUserConfigKeys = new Set(
108
+ Object.entries(manifestEnv)
109
+ .filter(([, v]) => typeof v === 'string' && USER_CONFIG_REF.test(v))
110
+ .map(([k]) => k),
105
111
  );
112
+
113
+ const stdioEnvVars = (serverJson.packages ?? [])
114
+ .filter((p) => p.transport?.type === 'stdio')
115
+ .flatMap((p) => p.environmentVariables ?? []);
116
+ const stdioEnvNames = new Set(stdioEnvVars.map((v) => v.name));
117
+ const requiredStdioEnvNames = new Set(
118
+ stdioEnvVars.filter((v) => v.isRequired === true && v.default == null).map((v) => v.name),
119
+ );
120
+
121
+ const missingInServerJson = [...manifestUserConfigKeys].filter((k) => !stdioEnvNames.has(k));
122
+ const missingInManifest = [...requiredStdioEnvNames].filter((k) => !manifestEnvKeys.has(k));
123
+
124
+ if (missingInServerJson.length > 0) {
125
+ errors.push(
126
+ `manifest.json references user_config env var(s) not advertised in server.json stdio environmentVariables[]: ${missingInServerJson.join(', ')}`,
127
+ );
128
+ }
129
+ if (missingInManifest.length > 0) {
130
+ errors.push(
131
+ `server.json declares required stdio env var(s) without default missing from manifest.json mcp_config.env: ${missingInManifest.join(', ')}`,
132
+ );
133
+ }
106
134
  }
107
135
 
108
136
  if (errors.length === 0) {
@@ -4,7 +4,7 @@ description: >
4
4
  Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.3"
7
+ version: "2.4"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -78,6 +78,7 @@ Scan specifically for:
78
78
  | Config changes | New env vars, renamed keys, changed defaults |
79
79
  | Linter rules | New definition-lint rules that may now flag existing tools/resources |
80
80
  | New or materially-changed skills | Note new skills or workflow changes (renamed steps, new checklist items) worth surfacing at end-of-run. Don't auto-invoke — some skills (e.g. `security-pass`) are user-triggered. The per-version changelog entries (e.g. 0.6.14 calling out `skills/security-pass/ (v1.0)`) name what changed. |
81
+ | New template-scaffolded files | Compare `templates/` in the package against the project root. Files that `init` would create for a new project but don't exist in this project are adoption candidates — create them with project-specific values (version, name, description, env vars from `server.json`). Examples: `manifest.json`, `.mcpbignore`. Skip files the project has intentionally opted out of (documented in CLAUDE.md or a code comment). |
81
82
 
82
83
  Cross-reference each finding against the server's code. Collect adoption opportunities for Step 6.
83
84
 
@@ -4,7 +4,7 @@ description: >
4
4
  Orchestrate parallel sub-agent fanouts across one or more MCP server projects — the same workflow run independently per target. Use for greenfield builds across N new servers, maintenance passes across N existing ones, or any repeatable workflow that benefits from fresh-context per-target sub-agents. Encodes the orient template every sub-agent needs (CLAUDE.md chain + list-skills + spec artifacts), the universal hard rules around git tooling and authorization, common gotchas that bite across runs, and a router into per-scenario references for the phase pattern.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.2"
8
8
  audience: internal
9
9
  type: workflow
10
10
  ---
@@ -118,6 +118,7 @@ These commonly bite across all scenarios. Scenario-specific gotchas live in thei
118
118
  | 6 | Independent agents diverge on incidental conventions (scoping, scripts, README hero) | Plan an explicit normalization pass; don't expect alignment for free |
119
119
  | 7 | Sub-agent skips the orient block and proceeds with pattern-matched defaults | Put orient as a numbered prerequisite in the prompt with "Only after that, begin the task"; spot-check the first tool calls in the agent's response |
120
120
  | 8 | Sub-agent runs `git stash` to "test something safely" | Restate the global rule verbatim in every sub-agent prompt that may touch git: "NEVER `git stash` for any reason." |
121
+ | 9 | `gh release create --notes-from-tag` fails when combined with `--repo` flag | `gh` CLI limitation. Always `cd` into the target repo dir for `gh release` commands; don't use `--repo` with `--notes-from-tag` |
121
122
 
122
123
  ## Extending the pattern
123
124
 
@@ -4,7 +4,7 @@ description: >
4
4
  Multi-server-orchestration reference for maintenance passes. Drives parallel `bun update --latest`, changelog investigation (via the changelog skill), framework adoption per the maintenance skill's auto-adopt rule, skill/script sync (Phase A/B/C), and verification across N existing MCP server projects. Optional Bash-git wrap-up fanout commits adoptions after user authorization.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.2"
8
8
  audience: internal
9
9
  type: reference
10
10
  ---
@@ -27,7 +27,9 @@ Before spawning any sub-agents:
27
27
  2. **Verify each target is a git repo with a clean working tree.** Run `git status` per target. Halt if any is dirty — user fixes locally and re-invokes.
28
28
  3. **Verify each target has `scripts/list-skills.ts` and the `list-skills` package script.** Both ship via `init`; older projects pick them up on the next `maintenance` Phase C sync — which is what's about to run. If missing at this moment, that's fine; note it and the sub-agent will pick them up.
29
29
  4. **Verify each target has the `maintenance` skill available** (in `skills/` or `.claude/skills/`). If missing, that's a sign the project hasn't been initialized from a recent framework version — flag it; the sub-agent can still work from the package's copy at `node_modules/@cyanheads/mcp-ts-core/skills/maintenance/SKILL.md`.
30
- 5. **Clarify the user authorization scope.** Do they want sub-agents to commit/push at the end, or stop at "changes applied, working tree dirty, awaiting review"? Default is the latter.
30
+ 5. **Check `publish-mcp` script presence** when `server.json` exists. If missing, flag it the maintenance sub-agent or a follow-up agent should add it.
31
+ 6. **Check `manifest.json` existence.** If missing and the project has `server.json`, flag it — the maintenance sub-agent should scaffold it from `templates/manifest.json` during framework adoption (Step 6).
32
+ 7. **Clarify the user authorization scope.** Do they want sub-agents to commit/push at the end, or stop at "changes applied, working tree dirty, awaiting review"? Default is the latter.
31
33
 
32
34
  ## Phase pattern
33
35
 
@@ -83,6 +85,19 @@ The orchestrator collects each sub-agent's Step 8 summary and produces a consoli
83
85
 
84
86
  Wait for user direction before Phase 4 — they may want to inspect diffs locally first.
85
87
 
88
+ ### Phase 3.5: Double-check / normalization pass (recommended)
89
+
90
+ Independent maintenance agents diverge on incidental choices and miss adoption sites under context pressure. A lightweight follow-up fanout catches gaps before wrap-up:
91
+
92
+ - **Adoption gaps** — features the updated skills say to do that weren't applied (error code semantic audit, missing scaffolding files like `manifest.json`/`.mcpbignore`, `publish-mcp` script)
93
+ - **Audience compliance** — only skills with `metadata.audience: external` should be in project `skills/`; agents sometimes sync `internal`-audience skills
94
+ - **Content accuracy** — `isRequired` flags in `server.json` match the upstream API's actual requirement; `manifest.json` `name` doesn't include the npm scope prefix; `user_config` entries have required `title` and `type` fields
95
+ - **Cross-target consistency** — if a feature shows up in 3 of 5 Step 8 summaries, the other 2 likely missed it
96
+
97
+ These agents should **fix, not just report** — analysis-only agents create unnecessary follow-up. After fixing, re-run `bun run rebuild && bun run devcheck && bun run test`.
98
+
99
+ Use a lighter model (e.g. Sonnet) for this pass — it's verification and targeted fixes, not deep adoption work.
100
+
86
101
  ### Phase 4: Wrap-up fanout (optional, Bash git only)
87
102
 
88
103
  Run only after explicit user authorization. Per-target commit decisions are driven by the change shape:
@@ -110,10 +125,13 @@ If the maintenance pass should drive a version bump (breaking framework upgrade,
110
125
  | 5 | Sub-agent runs write git commands despite instruction (commit/push/reset/stash/etc.) | Restate the no-write-git list + the no-`stash` rule in the prompt body; verify via `git log --oneline -1` per target after Phase 2 — should show no new commits since pre-flight |
111
126
  | 6 | Targets at the same framework version produce inconsistent adoption choices | Usually means one agent missed a site; spot-check the Step 8 "Features adopted" lists across targets. If a feature shows up for 3 of 4, the 4th likely missed it — spawn a narrow finish-pass agent for that target |
112
127
  | 7 | Big monorepo or many adoptions cause context exhaustion in a sub-agent | Narrow the prompt: if a target has many breaking framework changes, split the sub-agent's work into "update deps + verify" and "adopt features" as two phases against that target |
128
+ | 8 | Agent syncs `internal`-audience skills into project `skills/` | Restate in the prompt: "Only sync skills with `metadata.audience: external`." The maintenance skill's Phase A step 2 says this, but agents miss it under context pressure |
129
+ | 9 | `manifest.json` scaffolded with scoped name from `package.json` (e.g. `@cyanheads/bls-mcp-server`) — renders in the mcpb install dialog | Double-check pass should verify `manifest.json` `name` doesn't contain `/`; the `polish-docs-meta` skill's cross-file consistency section covers this |
130
+ | 10 | `manifest.json` `user_config` entries missing required `title`/`type` fields — `mcpb pack` fails at release time | Agents scaffold from memory, not the template. Double-check pass should verify all `user_config` entries have `title` and `type` |
113
131
 
114
132
  ## Checklist
115
133
 
116
- - [ ] Pre-flight: target list confirmed, clean working trees verified, `list-skills` presence noted per target, `maintenance` skill availability noted per target, auth scope clarified with user
134
+ - [ ] Pre-flight: target list confirmed, clean working trees verified, `list-skills` presence noted per target, `maintenance` skill availability noted per target, `publish-mcp` and `manifest.json` presence noted, auth scope clarified with user
117
135
  - [ ] Phase 2: maintenance fanout spawned; each sub-agent returned a Step 8 summary
118
136
  - [ ] All targets: green `bun run devcheck` and `bun run test` post-adoption
119
137
  - [ ] Phase 3: consolidated roll-up presented to user with per-target headlines, cross-target patterns, open decisions, outliers
@@ -4,7 +4,7 @@ description: >
4
4
  Multi-server-orchestration reference for release-and-publish passes — the end-to-end ship workflow combining wrap-up (version bump, changelog, commit, annotated tag) and publish (push + npm + MCP Registry + GHCR) across N MCP server projects. Drives parallel verification → README polish → wrap-up (Bash git, local only — distilled from `git_wrapup_instructions`) → publish (via the standalone `release-and-publish` skill per target) → optional GH issue closure. Phase 5 runs serial when npm 2FA prompts interactively; parallel when bypass is configured. Disambiguated from the single-target `release-and-publish` skill it invokes per target.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: internal
9
9
  type: reference
10
10
  ---
@@ -137,8 +137,9 @@ Each target invokes the `release-and-publish` skill end-to-end. Execution mode d
137
137
  > 3. Push commits and tags to origin via Bash git
138
138
  > 4. `bun publish --access public` (npm — uses the configured bypass token)
139
139
  > 5. `bun run publish-mcp` if `server.json` exists (MCP Registry)
140
- > 6. `docker buildx build --platform linux/amd64,linux/arm64 --push ...` if `Dockerfile` exists (GHCR)
141
- > 7. Report deployed artifact URLs (npm, MCP Registry, GHCR)
140
+ > 6. `bun run bundle` + `gh release create v<version> --verify-tag --notes-from-tag <dist/*.mcpb>` if `manifest.json` exists (MCPB GitHub Release). Must run from inside the repo dir — `--notes-from-tag` is incompatible with `--repo`.
141
+ > 7. `docker buildx build --platform linux/amd64,linux/arm64 --push ...` if `Dockerfile` exists (GHCR)
142
+ > 8. Report deployed artifact URLs (npm, MCP Registry, GitHub Release, GHCR)
142
143
  >
143
144
  > Honor the skill's retry/halt protocol — transient network errors retry up to 2× with backoff; idempotent-success signals ("version already exists", "cannot publish duplicate version") are treated as success and proceed. Bash git for all git ops. Never skip the verification gate.
144
145
 
@@ -176,6 +177,9 @@ Skip Phase 6 for any target whose Phase 5 didn't complete — there's nothing to
176
177
  | 8 | Annotated tag message bloats into a CHANGELOG copy | Restate the rule in Phase 4 prompt: terse release theme + notable changes + dep arrows in `pkg ^old → ^new` form. Length is earned |
177
178
  | 9 | Sub-agent uses `git-mcp-server` instead of Bash git in Phase 4 or 5 | Hard Rule 1 restated explicitly in every fanout prompt that touches git |
178
179
  | 10 | Failed publish leaves a tag pushed but no npm package — looks shipped, isn't | Collect per-destination status per target in Phase 5 roll-up; surface partial-state targets explicitly to the user before Phase 6 |
180
+ | 11 | `gh release create --notes-from-tag` fails with `--repo` flag | `gh` CLI limitation. Always `cd` into the target repo dir for `gh release` commands instead of using `--repo` |
181
+ | 12 | Post-version doc changes (install badges, description fixes) land after the version commit — tag points at stale content | Move the tag forward: delete remote release, delete remote+local tag, recreate tag at new HEAD with same annotation, re-push, recreate release with `.mcpb`. This is authorized within the pipeline for same-day follow-ups |
182
+ | 13 | `mcpb pack` fails because `manifest.json` `user_config` entries are missing `title`/`type` fields | Verify `manifest.json` `user_config` entries during Phase 3 or a pre-Phase-5 check; the `polish-docs-meta` skill's cross-file consistency section covers this |
179
183
 
180
184
  ## Checklist
181
185
 
@@ -4,7 +4,7 @@ description: >
4
4
  Multi-server-orchestration reference for git wrap-up passes — distilled from `git-mcp-server`'s `git_wrapup_instructions` protocol. Drives parallel verification → optional doc review → wrap-up (version bump, changelog, commit, annotated tag — Bash git, local only, no push) → roll-up across N MCP server projects. Stops at "committed and tagged locally". No push, no publish — those are separate, separately-authorized workflows.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: internal
9
9
  type: reference
10
10
  ---
@@ -100,7 +100,7 @@ One sub-agent per target. The agent executes the seven acceptance criteria via B
100
100
  > 2. **Version bump.** Start from current `package.json` `version`; apply the bump intent (`[patch/minor/major or explicit string]`). Bump every place version is declared:
101
101
  > - `package.json` `version`
102
102
  > - `server.json` `version` at the top level AND every `packages[].version` entry
103
- > - `manifest.json` (if present) `version`
103
+ > - `manifest.json` (if present) `version`. Also verify `name` doesn't include the npm scope prefix — it should be the bare package name (e.g. `bls-mcp-server`, not `@cyanheads/bls-mcp-server`)
104
104
  > - README version badge and any hero pinning
105
105
  > - Dockerfile OCI labels (if pinned to version)
106
106
  > - Any agent-instruction file (`CLAUDE.md`, `AGENTS.md`) that pins the version
@@ -4,7 +4,7 @@ description: >
4
4
  Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.0"
7
+ version: "2.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -163,11 +163,42 @@ Never hand-edit `CHANGELOG.md` when using this pattern — it's a build artifact
163
163
 
164
164
  **Monolithic** — maintain `CHANGELOG.md` directly in [Keep a Changelog](https://keepachangelog.com/) format. To collapse from the template default: delete the `changelog/` directory, remove `changelog:build` and `changelog:check` from `package.json` scripts (and from `devcheck.config.json` if referenced), and drop `"changelog/"` from the `files` array. The `release` skill's directory-specific steps then don't apply — just edit `CHANGELOG.md` and bump version at release time.
165
165
 
166
- ### 10. `LICENSE`
166
+ ### 10. MCPB Bundling Artifacts
167
+
168
+ If the project ships as an `.mcpb` bundle for Claude Desktop (check for `manifest.json` at the project root), verify the full artifact set is present and consistent. If the project doesn't ship `.mcpb` bundles, skip this step.
169
+
170
+ **Files that must exist:**
171
+
172
+ - `manifest.json` — MCPB manifest with `mcp_config.env`, `user_config`, and metadata
173
+ - `.mcpbignore` — controls what's excluded from the bundle
174
+
175
+ **`package.json` scripts:**
176
+
177
+ - `bundle` — builds the `.mcpb` (e.g., `mcpb pack --output dist/`)
178
+ - `lint:packaging` — validates `manifest.json` ↔ `server.json` env var consistency (run by `devcheck`)
179
+
180
+ **Cross-file consistency:**
181
+
182
+ - `manifest.json` version matches `package.json` version
183
+ - Env var names in `manifest.json` (`mcp_config.env` + `user_config`) match `server.json` `environmentVariables` — `lint:packaging` enforces this, but verify the set is complete
184
+ - `manifest.json` `name` matches `package.json` name **without the npm scope prefix** (e.g. `bls-mcp-server`, not `@cyanheads/bls-mcp-server`); `description` matches `package.json`
185
+ - `manifest.json` `user_config` entries must include `title` and `type` fields — `mcpb pack` validates these
186
+ - `server.json` env var `isRequired` must match the upstream API's actual requirement — if the API works without the value (rate-limited, DEMO_KEY fallback, polite pool), mark `isRequired: false` and describe the tradeoff in the description
187
+ - Server description aligned across all surfaces: `package.json`, `manifest.json`, `server.json` (condensed, hard 100-char limit), README header `<p><b>`, and GitHub repo description (`gh repo edit --description`)
188
+ - `package.json` `keywords` include baseline terms: `mcp`, `mcp-server`, `model-context-protocol`, `typescript`, `bun`, `stdio`, `streamable-http`, plus data-domain terms. GitHub repo topics (`gh repo edit --add-topic`) should match.
189
+
190
+ **README install badges:**
191
+
192
+ - If `manifest.json` exists, the README should include the Claude Desktop install badge linking to `releases/latest/download/<name>.mcpb`
193
+ - If the package is published to npm, include Cursor and VS Code install badges
194
+ - See `references/readme.md` for badge format and config generation commands
195
+ - See the **Bundling** section of `templates/CLAUDE.md` for `base64` / `encodeURIComponent` generation
196
+
197
+ ### 11. `LICENSE`
167
198
 
168
199
  Confirm a license file exists. If not, ask the user which license to use (default: Apache-2.0, matching the scaffolded `package.json`). Create the file.
169
200
 
170
- ### 11. `Dockerfile`
201
+ ### 12. `Dockerfile`
171
202
 
172
203
  If a `Dockerfile` exists, verify the OCI labels and runtime config match the actual server:
173
204
 
@@ -178,7 +209,7 @@ If a `Dockerfile` exists, verify the OCI labels and runtime config match the act
178
209
 
179
210
  If no `Dockerfile` exists and the server is deployed via HTTP transport, consider scaffolding one — the template is available via `npx @cyanheads/mcp-ts-core init`.
180
211
 
181
- ### 12. `docs/tree.md`
212
+ ### 13. `docs/tree.md`
182
213
 
183
214
  Regenerate the directory structure:
184
215
 
@@ -188,7 +219,7 @@ bun run tree
188
219
 
189
220
  Review the output for anything unexpected (leftover files, missing directories).
190
221
 
191
- ### 13. Final Verification
222
+ ### 14. Final Verification
192
223
 
193
224
  Run the full check suite one last time:
194
225
 
@@ -210,6 +241,7 @@ Both must pass clean.
210
241
  - [ ] GitHub repo description matches `package.json` description; topics ↔ keywords in sync
211
242
  - [ ] `bunfig.toml` present
212
243
  - [ ] Changelog current — either monolithic `CHANGELOG.md` (hand-edited, Keep a Changelog) or directory-based (`changelog/<minor>.x/<version>.md` + rollup regenerated and in sync)
244
+ - [ ] MCPB artifacts consistent (if `manifest.json` present) — version synced, env vars match `server.json`, `bundle` + `lint:packaging` scripts exist, README install badges present
213
245
  - [ ] `LICENSE` file present
214
246
  - [ ] `Dockerfile` OCI labels and runtime config accurate (if present)
215
247
  - [ ] `docs/tree.md` regenerated
@@ -4,7 +4,7 @@ description: >
4
4
  Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit, annotated tag) is already complete — this skill is the post-wrapup publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.4"
7
+ version: "2.5"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -131,7 +131,7 @@ Halt on any publisher error other than "cannot publish duplicate version".
131
131
 
132
132
  Only if `manifest.json` exists at the repo root (otherwise skip).
133
133
 
134
- Build the bundle, then create a Release on the existing annotated tag and attach the `.mcpb`. The Release sits on top of the tag from wrapup — `--verify-tag` enforces that the tag already exists on the remote and prevents `gh` from creating a lightweight tag that would shadow the annotated one. `--notes-from-tag` pulls release notes directly from the annotated tag message (no duplication).
134
+ Build the bundle, then create a Release on the existing annotated tag and attach the `.mcpb`. The Release sits on top of the tag from wrapup — `--verify-tag` enforces that the tag already exists on the remote and prevents `gh` from creating a lightweight tag that would shadow the annotated one. `--notes-from-tag` pulls release notes directly from the annotated tag message (no duplication). Note: GitHub prepends `v<VERSION>:` to the release title, so the tag annotation subject must omit the version number to avoid stutter.
135
135
 
136
136
  ```bash
137
137
  bun run bundle # produces dist/<name>.mcpb (stable filename, no version)
@@ -295,7 +295,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
295
295
  | `npm run rebuild` | Clean + build |
296
296
  | `npm run clean` | Remove build artifacts |
297
297
  | `npm run devcheck` | Lint + format + typecheck + security + changelog sync |
298
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
298
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
299
299
  | `npm run tree` | Generate directory structure doc |
300
300
  | `npm run format` | Auto-fix formatting |
301
301
  | `npm test` | Run tests |
@@ -1,6 +1,3 @@
1
- src/
2
- tests/
3
- coverage/
4
1
  .env*
5
2
  .mcpregistry_*
6
3
  .claude/
@@ -1,7 +0,0 @@
1
- {"level":40,"time":1779513956718,"env":"testing","version":"0.9.4","pid":13469,"transport":"http","requestId":"4J7N0-VMN48","timestamp":"2026-05-23T05:25:56.717Z","operation":"TransportManager.start","component":"HttpTransportSetup","msg":"MCP_ALLOWED_ORIGINS is not set — CORS is wildcard for CLI clients; browser Origin headers are restricted to loopback. Set MCP_ALLOWED_ORIGINS for production deployments accepting remote browser origins."}
2
- {"level":40,"time":1779513958440,"env":"testing","version":"0.9.4","pid":13469,"transport":"http","requestId":"4J7N0-VMN48","timestamp":"2026-05-23T05:25:56.717Z","operation":"TransportManager.start","component":"HttpTransportSetup","sessionId":"not-a-real-session-1779513958439","msg":"Session validation failed - invalid or hijacked session"}
3
- {"level":50,"time":1779513962377,"env":"testing","version":"0.0.0-test","pid":13573,"requestId":"T1SL6-PDB7E","timestamp":"2026-05-23T05:26:02.376Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b3cf5e7052663460e341825d87d31fc524d2fbe329b2fbc6a0d01e2d01d6f0ac","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"b3cf5e7052663460e341825d87d31fc524d2fbe329b2fbc6a0d01e2d01d6f0ac","toolName":"scoped_echo","requestId":"T1SL6-PDB7E","timestamp":"2026-05-23T05:26:02.376Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:146:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:184:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
4
- {"level":50,"time":1779513962384,"env":"testing","version":"0.0.0-test","pid":13573,"requestId":"UKSVK-KPL9I","timestamp":"2026-05-23T05:26:02.383Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"fc5e475d003413db328e15b2ffa9ee921e0926a9f2e4afff18d07b1136990f19","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"fc5e475d003413db328e15b2ffa9ee921e0926a9f2e4afff18d07b1136990f19","toolName":"scoped_echo","requestId":"UKSVK-KPL9I","timestamp":"2026-05-23T05:26:02.383Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:146:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:184:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
5
- {"level":50,"time":1779513963761,"env":"testing","version":"0.9.4","pid":13581,"requestId":"36WC3-ADICM","timestamp":"2026-05-23T05:26:03.760Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"36WC3-ADICM","timestamp":"2026-05-23T05:26:03.760Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:79:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
6
- {"level":50,"time":1779513963776,"env":"testing","version":"0.9.4","pid":13581,"requestId":"1J9GZ-FP2NN","timestamp":"2026-05-23T05:26:03.776Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"1J9GZ-FP2NN","timestamp":"2026-05-23T05:26:03.776Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:72:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
7
- {"level":50,"time":1779513963778,"env":"testing","version":"0.9.4","pid":13581,"requestId":"XK1MI-79PW3","timestamp":"2026-05-23T05:26:03.778Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"XK1MI-79PW3","timestamp":"2026-05-23T05:26:03.778Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:79:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
@@ -1,5 +0,0 @@
1
- {"level":50,"time":1779513962377,"env":"testing","version":"0.0.0-test","pid":13573,"requestId":"T1SL6-PDB7E","timestamp":"2026-05-23T05:26:02.376Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b3cf5e7052663460e341825d87d31fc524d2fbe329b2fbc6a0d01e2d01d6f0ac","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"b3cf5e7052663460e341825d87d31fc524d2fbe329b2fbc6a0d01e2d01d6f0ac","toolName":"scoped_echo","requestId":"T1SL6-PDB7E","timestamp":"2026-05-23T05:26:02.376Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:146:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:184:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1779513962384,"env":"testing","version":"0.0.0-test","pid":13573,"requestId":"UKSVK-KPL9I","timestamp":"2026-05-23T05:26:02.383Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"fc5e475d003413db328e15b2ffa9ee921e0926a9f2e4afff18d07b1136990f19","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"fc5e475d003413db328e15b2ffa9ee921e0926a9f2e4afff18d07b1136990f19","toolName":"scoped_echo","requestId":"UKSVK-KPL9I","timestamp":"2026-05-23T05:26:02.383Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:146:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:184:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
3
- {"level":50,"time":1779513963761,"env":"testing","version":"0.9.4","pid":13581,"requestId":"36WC3-ADICM","timestamp":"2026-05-23T05:26:03.760Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"36WC3-ADICM","timestamp":"2026-05-23T05:26:03.760Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:79:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
4
- {"level":50,"time":1779513963776,"env":"testing","version":"0.9.4","pid":13581,"requestId":"1J9GZ-FP2NN","timestamp":"2026-05-23T05:26:03.776Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"1J9GZ-FP2NN","timestamp":"2026-05-23T05:26:03.776Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:72:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
5
- {"level":50,"time":1779513963778,"env":"testing","version":"0.9.4","pid":13581,"requestId":"XK1MI-79PW3","timestamp":"2026-05-23T05:26:03.778Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"XK1MI-79PW3","timestamp":"2026-05-23T05:26:03.778Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:79:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
File without changes