@cyanheads/mcp-ts-core 0.9.5 → 0.9.7

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.5
4
+ **Version:** 0.9.7
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
@@ -548,4 +548,6 @@ Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 3
548
548
 
549
549
  ## Publishing
550
550
 
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. 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 annotations render as GitHub Release bodies** via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject must omit the version number (GitHub prepends `v<VERSION>:`). Body uses Keep a Changelog sections with bullets. See `changelog/template.md` for the full format reference including an example.
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.5-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.7-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": "warn"
48
+ "noDuplicateDependencies": "off"
49
49
  },
50
50
  "complexity": {
51
51
  "noUselessUndefined": "error"
@@ -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
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "biome noDuplicateDependencies off, structured tag annotation format, release artifact verification, skills updates"
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.9.7 — 2026-05-23
7
+
8
+ ## Changed
9
+
10
+ - `biome.json`: `noDuplicateDependencies` warn → off (zod intentionally in both `dependencies` and `peerDependencies`)
11
+ - `CLAUDE.md`/`AGENTS.md`: tag annotation guidance rewritten — structured markdown format with `changelog/template.md` reference
12
+ - `changelog/template.md`: tag annotation format example added (subject, prose intro, sectioned bullets, dep arrows, test footer)
13
+ - `templates/CLAUDE.md`/`templates/AGENTS.md`: install badge config updated (split `command`/`args`, `env` for API keys), tag annotation guidance added
14
+ - `templates/changelog/template.md`: tag annotation format example synced from core
15
+ - `skills/multi-server-orchestration/references/wrapup-pass.md` v1.2: structured tag annotation format replaces flat-string guidance
16
+ - `skills/release-and-publish/SKILL.md`: Step 9 — verify published artifacts are reachable (npm, MCP Registry, GH Release, GHCR)
17
+ - `skills/multi-server-orchestration/references/greenfield-buildout.md`: recommended additional phases (design gate, test quality review, field test, pre-launch sweep) and 5 new gotchas
18
+ - `skills/polish-docs-meta/references/readme.md`: agent-friendly output subsection added to Features section guidance
@@ -66,6 +66,32 @@ security: false
66
66
  Never speculate on a future number — `#42` for an upcoming PR silently
67
67
  resolves to whatever real item already owns 42, and timeline previews pull
68
68
  in that unrelated item's metadata.
69
+
70
+ TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
71
+ via `gh release create --notes-from-tag`. The tag is a derivative of this
72
+ changelog entry — a condensed, scannable version, not a copy. Format:
73
+
74
+ <theme — omit version number, GitHub prepends it>
75
+ ← blank line
76
+ <1-2 sentence context: what this release does>
77
+ ← blank line
78
+ Dependency bumps: ← section header
79
+ ← blank line
80
+ - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
81
+ ← blank line
82
+ Changed: ← only sections with entries
83
+ ← blank line
84
+ - `format()` output includes `query` in text mode
85
+ ← blank line
86
+ Added:
87
+ ← blank line
88
+ - `manifest.json` scaffolded for MCPB bundle support
89
+ - Install badges (Claude Desktop, Cursor, VS Code)
90
+ ← blank line
91
+ <N> tests pass; `bun run devcheck` clean. ← footer
92
+
93
+ Never a flat comma-separated string. Always structured markdown with
94
+ sections. The tag must scan well as a rendered GitHub Release page.
69
95
  -->
70
96
 
71
97
  ## Added
@@ -1,7 +1,7 @@
1
- {"level":40,"time":1779525180302,"env":"testing","version":"0.9.5","pid":19119,"transport":"http","requestId":"8I7DR-QK3Q5","timestamp":"2026-05-23T08:33:00.301Z","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":1779525182035,"env":"testing","version":"0.9.5","pid":19119,"transport":"http","requestId":"8I7DR-QK3Q5","timestamp":"2026-05-23T08:33:00.301Z","operation":"TransportManager.start","component":"HttpTransportSetup","sessionId":"not-a-real-session-1779525182035","msg":"Session validation failed - invalid or hijacked session"}
3
- {"level":50,"time":1779525186246,"env":"testing","version":"0.0.0-test","pid":19207,"requestId":"5BTFB-9TCTE","timestamp":"2026-05-23T08:33:06.245Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5798bcd4468074f87ca9d8bce75ddd7d971b63fce0ae460a8a1c93ef422b742e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"5798bcd4468074f87ca9d8bce75ddd7d971b63fce0ae460a8a1c93ef422b742e","toolName":"scoped_echo","requestId":"5BTFB-9TCTE","timestamp":"2026-05-23T08:33:06.245Z","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":1779525186253,"env":"testing","version":"0.0.0-test","pid":19207,"requestId":"KLWMG-6WVT6","timestamp":"2026-05-23T08:33:06.253Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"00625ce1a231065e2a92c4423cf94c6f9dc139a6e16bfbba8888d34226fde894","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":"00625ce1a231065e2a92c4423cf94c6f9dc139a6e16bfbba8888d34226fde894","toolName":"scoped_echo","requestId":"KLWMG-6WVT6","timestamp":"2026-05-23T08:33:06.253Z","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":1779525187678,"env":"testing","version":"0.9.5","pid":19245,"requestId":"ET1UM-D2PSZ","timestamp":"2026-05-23T08:33:07.678Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"ET1UM-D2PSZ","timestamp":"2026-05-23T08:33:07.678Z","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":1779525187693,"env":"testing","version":"0.9.5","pid":19245,"requestId":"KHCYN-C8IAD","timestamp":"2026-05-23T08:33:07.693Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"KHCYN-C8IAD","timestamp":"2026-05-23T08:33:07.693Z","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":1779525187697,"env":"testing","version":"0.9.5","pid":19245,"requestId":"GVI8J-0B2W0","timestamp":"2026-05-23T08:33:07.697Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"GVI8J-0B2W0","timestamp":"2026-05-23T08:33:07.697Z","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
+ {"level":40,"time":1779554652742,"env":"testing","version":"0.9.7","pid":56972,"transport":"http","requestId":"5DGIW-LONS7","timestamp":"2026-05-23T16:44:12.741Z","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":1779554654681,"env":"testing","version":"0.9.7","pid":56972,"transport":"http","requestId":"5DGIW-LONS7","timestamp":"2026-05-23T16:44:12.741Z","operation":"TransportManager.start","component":"HttpTransportSetup","sessionId":"not-a-real-session-1779554654681","msg":"Session validation failed - invalid or hijacked session"}
3
+ {"level":50,"time":1779554658688,"env":"testing","version":"0.0.0-test","pid":57052,"requestId":"KD5VX-QNDSK","timestamp":"2026-05-23T16:44:18.688Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"eca1f32c9116477ca19cd419874e3d2189fa58a29e6868d4454379f09d08c761","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"eca1f32c9116477ca19cd419874e3d2189fa58a29e6868d4454379f09d08c761","toolName":"scoped_echo","requestId":"KD5VX-QNDSK","timestamp":"2026-05-23T16:44:18.688Z","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":1779554658696,"env":"testing","version":"0.0.0-test","pid":57052,"requestId":"0T35D-7ME6T","timestamp":"2026-05-23T16:44:18.696Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5c8698220b504698a993704832e28f177cbcea7ed9d4ee20b1755f87ff2be9d8","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":"5c8698220b504698a993704832e28f177cbcea7ed9d4ee20b1755f87ff2be9d8","toolName":"scoped_echo","requestId":"0T35D-7ME6T","timestamp":"2026-05-23T16:44:18.696Z","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":1779554659793,"env":"testing","version":"0.9.7","pid":57092,"requestId":"710FM-RJENE","timestamp":"2026-05-23T16:44:19.792Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"710FM-RJENE","timestamp":"2026-05-23T16:44:19.792Z","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":1779554659808,"env":"testing","version":"0.9.7","pid":57092,"requestId":"9GLH9-PHGBC","timestamp":"2026-05-23T16:44:19.808Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"9GLH9-PHGBC","timestamp":"2026-05-23T16:44:19.808Z","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":1779554659811,"env":"testing","version":"0.9.7","pid":57092,"requestId":"T10IM-N5OHM","timestamp":"2026-05-23T16:44:19.811Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"T10IM-N5OHM","timestamp":"2026-05-23T16:44:19.811Z","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 +1,5 @@
1
- {"level":50,"time":1779525186246,"env":"testing","version":"0.0.0-test","pid":19207,"requestId":"5BTFB-9TCTE","timestamp":"2026-05-23T08:33:06.245Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5798bcd4468074f87ca9d8bce75ddd7d971b63fce0ae460a8a1c93ef422b742e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"5798bcd4468074f87ca9d8bce75ddd7d971b63fce0ae460a8a1c93ef422b742e","toolName":"scoped_echo","requestId":"5BTFB-9TCTE","timestamp":"2026-05-23T08:33:06.245Z","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":1779525186253,"env":"testing","version":"0.0.0-test","pid":19207,"requestId":"KLWMG-6WVT6","timestamp":"2026-05-23T08:33:06.253Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"00625ce1a231065e2a92c4423cf94c6f9dc139a6e16bfbba8888d34226fde894","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":"00625ce1a231065e2a92c4423cf94c6f9dc139a6e16bfbba8888d34226fde894","toolName":"scoped_echo","requestId":"KLWMG-6WVT6","timestamp":"2026-05-23T08:33:06.253Z","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":1779525187678,"env":"testing","version":"0.9.5","pid":19245,"requestId":"ET1UM-D2PSZ","timestamp":"2026-05-23T08:33:07.678Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"ET1UM-D2PSZ","timestamp":"2026-05-23T08:33:07.678Z","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":1779525187693,"env":"testing","version":"0.9.5","pid":19245,"requestId":"KHCYN-C8IAD","timestamp":"2026-05-23T08:33:07.693Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"KHCYN-C8IAD","timestamp":"2026-05-23T08:33:07.693Z","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":1779525187697,"env":"testing","version":"0.9.5","pid":19245,"requestId":"GVI8J-0B2W0","timestamp":"2026-05-23T08:33:07.697Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"GVI8J-0B2W0","timestamp":"2026-05-23T08:33:07.697Z","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
+ {"level":50,"time":1779554658688,"env":"testing","version":"0.0.0-test","pid":57052,"requestId":"KD5VX-QNDSK","timestamp":"2026-05-23T16:44:18.688Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"eca1f32c9116477ca19cd419874e3d2189fa58a29e6868d4454379f09d08c761","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"eca1f32c9116477ca19cd419874e3d2189fa58a29e6868d4454379f09d08c761","toolName":"scoped_echo","requestId":"KD5VX-QNDSK","timestamp":"2026-05-23T16:44:18.688Z","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":1779554658696,"env":"testing","version":"0.0.0-test","pid":57052,"requestId":"0T35D-7ME6T","timestamp":"2026-05-23T16:44:18.696Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5c8698220b504698a993704832e28f177cbcea7ed9d4ee20b1755f87ff2be9d8","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":"5c8698220b504698a993704832e28f177cbcea7ed9d4ee20b1755f87ff2be9d8","toolName":"scoped_echo","requestId":"0T35D-7ME6T","timestamp":"2026-05-23T16:44:18.696Z","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":1779554659793,"env":"testing","version":"0.9.7","pid":57092,"requestId":"710FM-RJENE","timestamp":"2026-05-23T16:44:19.792Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"710FM-RJENE","timestamp":"2026-05-23T16:44:19.792Z","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":1779554659808,"env":"testing","version":"0.9.7","pid":57092,"requestId":"9GLH9-PHGBC","timestamp":"2026-05-23T16:44:19.808Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"9GLH9-PHGBC","timestamp":"2026-05-23T16:44:19.808Z","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":1779554659811,"env":"testing","version":"0.9.7","pid":57092,"requestId":"T10IM-N5OHM","timestamp":"2026-05-23T16:44:19.811Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"T10IM-N5OHM","timestamp":"2026-05-23T16:44:19.811Z","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."}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
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",
@@ -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
  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
 
@@ -179,6 +179,24 @@ Run only after the user authorizes. Same shape as Phase 5: per-target sub-agent
179
179
 
180
180
  Version bumps live with the change that warrants them per the global protocol's git rules — don't manufacture extra commits.
181
181
 
182
+ ## Recommended additional phases
183
+
184
+ The 13-phase pattern above covers scaffold-to-ship. Experience from multi-server pipeline runs surfaces additional phases that earn their cost. Orchestrators building a prescriptive pipeline layer on top of this reference should consider inserting these:
185
+
186
+ **Design gate (after Phase 3).** A third agent per target reads ONLY `docs/design.md` (no idea.md, no skill) and gives a pass/fail verdict. The point is cold-read validation: does the design make sense on its own? Filter out noise — only flag genuine structural issues, not style preferences. If all pass, proceed to build. If any fail, spawn focused fix agents.
187
+
188
+ **Test quality review (after Phase 11/12).** Dedicated agents that audit test coverage and quality: are all tools tested? Are error paths exercised? Are edge cases covered? Build agents write tests as a secondary concern; dedicated test agents treat it as primary.
189
+
190
+ **Field test (after test quality review).** Run the `field-test` skill per target — exercise tools with real inputs, report issues. May be blocked by API key requirements or rate limits. If blocked, note it and skip. If field tests surface issues, spawn fix agents and re-field-test.
191
+
192
+ **Pre-launch sweep (before final wrap-up).** Singular-purpose verification agents per target:
193
+ 1. **Metadata sync** — descriptions aligned across package.json, manifest.json, server.json (100-char condensed), README header, GH repo description. Keywords/topics include baseline MCP terms. Versions consistent.
194
+ 2. **README accuracy** — README matches current code (tool names, parameter lists, examples, config table, feature list).
195
+ 3. **PII/secrets sweep** — no hardcoded credentials, API keys, personal emails, internal paths, or sensitive context in tracked files.
196
+ 4. **Error code semantic audit** — InvalidParams only for malformed JSON-RPC params shape; ValidationError for domain validation; NotFound for missing entities.
197
+
198
+ These agents fix, not just report. Re-run devcheck + test after each.
199
+
182
200
  ## Gotchas specific to greenfield build-out
183
201
 
184
202
  | # | Gotcha | Mitigation |
@@ -190,6 +208,11 @@ Version bumps live with the change that warrants them per the global protocol's
190
208
  | 5 | `init` defaults package name to the cwd; if the project was scaffolded inside an outer dir, the name is wrong | Verify in Phase 4 prompt: "Check the substituted package name in `package.json`, `server.json`, and the agent protocol matches the intended server name." |
191
209
  | 6 | Sub-agent creates GH repo public by default if `gh repo create` omits `--private` | Restate the scenario hard rule in every fanout that touches `gh`: "Repo must be private before any push." |
192
210
  | 7 | Wrap-up agents have been observed reporting "pushed" while the remote ref didn't actually land — the push only succeeded on a subsequent op | Verify after every Bash-git fanout: `git ls-remote --tags origin` and `gh repo view --json defaultBranchRef` per target |
211
+ | 8 | LICENSE file missing from scaffolded projects despite package.json declaring Apache-2.0 | Copy from `node_modules/@cyanheads/mcp-ts-core/LICENSE` during scaffold phase |
212
+ | 9 | Description drift across surfaces — agents set descriptions independently, creating inconsistencies across package.json, manifest.json, server.json, README, and GH repo description | Pre-launch sweep phase catches this; or define the canonical description once in docs/design.md and have agents reference it |
213
+ | 10 | `server.json` `isRequired` flags set without verifying upstream API reality | Pre-launch sweep should verify each env var's `isRequired` against actual API behavior (does it work without the key?) |
214
+ | 11 | Build agents exhaust context and leave tests half-written — finish agents inherit the gap but don't prioritize test coverage | Dedicated test quality review phase after build/finish is the backstop |
215
+ | 12 | Field-test agents can't run if the API requires a key the user hasn't provided | Check API key requirements before spawning field-test agents; skip with a note if blocked |
193
216
 
194
217
  ## Checklist
195
218
 
@@ -202,6 +225,7 @@ The orchestrator's checklist for a full N-target greenfield build:
202
225
  - [ ] Phase 1 — `docs/idea.md` authored per target
203
226
  - [ ] Phase 2 — design fanout — `docs/design.md` with Decisions Log per target
204
227
  - [ ] Phase 3 — critical review fanout — `docs/design.md` hardened per target
228
+ - [ ] **Recommended: design gate — cold-read pass/fail per target**
205
229
  - [ ] Phase 4 — setup + repo fanout — repos created private, working tree unstaged, no commits
206
230
  - [ ] Phase 5 — v0.1.0 wrap-up fanout (Bash git only) — commits + annotated tags + pushes; verified via `git log` and `git ls-remote --tags origin`
207
231
  - [ ] Phase 6 — polish docs/meta fanout against named gold-standard
@@ -211,6 +235,9 @@ The orchestrator's checklist for a full N-target greenfield build:
211
235
  - [ ] Phase 10 — build fanout — implementation + tests + green devcheck per target
212
236
  - [ ] **If any Phase 10 agent didn't finish:** Phase 11 — narrow-scope finish fanout per incomplete target
213
237
  - [ ] Phase 12 — simplify fanout — `code-simplifier` pass per target
238
+ - [ ] **Recommended: test quality review — dedicated test coverage and quality audit**
239
+ - [ ] **Recommended: field test — `field-test` skill per target (skip if API key blocker)**
240
+ - [ ] **Recommended: pre-launch sweep — metadata sync, README accuracy, PII sweep, error code audit**
214
241
  - [ ] All targets: green `bun run devcheck` and `bun run test`
215
242
  - [ ] Phase 13 — final wrap-up fanout (Bash git only) — version bump, changelog file, regenerated rollup, commit + annotated tag + push; verified
216
243
  - [ ] Final read-only verification: tags pushed, repos still private, no stray uncommitted work
@@ -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.2"
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
@@ -110,7 +110,14 @@ One sub-agent per target. The agent executes the seven acceptance criteria via B
110
110
  > 4. **Regenerate derived artifacts.** `bun run changelog:build` (rebuilds `CHANGELOG.md` rollup from per-version files); `bun run tree` (regenerates `docs/tree.md` if the file structure changed).
111
111
  > 5. **Verification gate.** `bun run devcheck` must pass against the tree being committed. `bun run test:all` if it exists, otherwise `bun run test`. Both green. Halt if not green — do NOT bypass.
112
112
  > 6. **Atomic Conventional Commits.** Version bumps ride with the change that warrants them. For a focused patch, this is ONE commit covering the work + the version bump + changelog + regenerated artifacts. Subject form: `feat: <version> — <one-line theme>` or `chore(release): v<version> — <theme>`. Plain `-m` only — no heredoc, no `Co-authored-by`, no `Generated with` trailers. No marketing adjectives.
113
- > 7. **Annotated tag** `v<version>` (`-a`, NOT lightweight) with terse message: release theme, notable changes, dep arrows in `pkg ^old ^new` form if applicable. Not a CHANGELOG copy. Length is earned.
113
+ > 7. **Annotated tag** `v<version>` (`-a`, NOT lightweight). The tag body renders as the GitHub Release page via `--notes-from-tag` it must be structured markdown, never a flat comma-separated string. Format:
114
+ > - **Subject line:** release theme — omit version number (GitHub prepends `v<VERSION>:`)
115
+ > - **Prose intro:** 1-2 sentences — what this release does, why it matters
116
+ > - **Sections:** Keep a Changelog names (Dependency bumps / Added / Changed / Fixed / Removed) — only sections with entries, each with bulleted items
117
+ > - **Dep arrows:** `pkg ^old → ^new` form, one per line
118
+ > - **Footer:** test count + devcheck status
119
+ > - **Backlinks:** link framework issues where relevant (`cyanheads/mcp-ts-core#50`)
120
+ > - Not a CHANGELOG copy — terse, scannable. No marketing adjectives. Length is earned.
114
121
  >
115
122
  > Constraints:
116
123
  > - **Bash `git` only.** Do not use `git-mcp-server` (`mcp__git-mcp-server__*`) tools — session state leaks across parallel agents in the same orchestrator session.
@@ -152,7 +159,7 @@ Then produces a consolidated report:
152
159
  | 1 | Sub-agent pushes prematurely despite "local only" instruction | Restate "Do NOT push" in the Phase 4 prompt verbatim; verify post-fanout that `git log @{u}..HEAD` shows the wrap-up commit (i.e., still ahead of origin) |
153
160
  | 2 | Version bump skipped a file (`server.json`'s per-package entries, README badge, Dockerfile OCI labels, `manifest.json`) | Phase 4 prompt enumerates every version-bearing file; the `grep -rn "<current-version>"` step catches stragglers |
154
161
  | 3 | Tag at `v<version>` already exists (leftover from failed prior run, or two orchestrator runs against the same target) | Sub-agent halts and surfaces conflict; never `git tag -d` without orchestrator authorization. Inspect with `git tag -l "v<version>"` and `git show v<version>` |
155
- | 4 | Annotated tag message bloats into a CHANGELOG copy | Restate in Phase 4 prompt: terse theme + notable changes + dep arrows in `pkg ^old ^new` form. Length is earned |
162
+ | 4 | Annotated tag message bloats into a CHANGELOG copy, or collapses into a flat comma-separated string | Phase 4 prompt has explicit structured format: subject, prose intro, sectioned bullets, dep arrows, test footer. Both extremes (too long, too flat) are format violations |
156
163
  | 5 | Marketing adjectives slip into commit/tag messages | Restate the no-marketing rule in the prompt body; orchestrator spot-checks during Phase 5 (`git log --format='%s%n%b' -1`, `git show v<version>`) |
157
164
  | 6 | Sub-agent uses `git-mcp-server` instead of Bash git | Phase 4 prompt restates Hard Rule 1 from parent SKILL.md; session-state leak across parallel agents is real |
158
165
  | 7 | Verification gate skipped because "the change is small" | Restate "Halt if not green — do NOT bypass" in the prompt; the wrap-up protocol forbids bypass and the orchestrator confirms green status in Phase 5 |
@@ -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.1"
7
+ version: "2.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -181,7 +181,11 @@ If the project ships as an `.mcpb` bundle for Claude Desktop (check for `manifes
181
181
 
182
182
  - `manifest.json` version matches `package.json` version
183
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` and `description` match `package.json`
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.
185
189
 
186
190
  **README install badges:**
187
191
 
@@ -182,7 +182,7 @@ Derive all tool/resource/prompt rows directly from the actual definitions. Use t
182
182
 
183
183
  ### Features
184
184
 
185
- Two subsection groups: framework capabilities, then domain-specific capabilities. Bullet lists, not prose.
185
+ Three subsection groups: framework capabilities, domain-specific capabilities, then agent-friendly output design. Bullet lists, not prose.
186
186
 
187
187
  ```markdown
188
188
  ## Features
@@ -201,8 +201,21 @@ Acme-specific:
201
201
  - Type-safe client for the Acme v2 API
202
202
  - Automatic cleaning and simplification of API responses for agent consumption
203
203
  - Workflow tools parallelize related sub-requests under a configurable concurrency limit
204
+
205
+ Agent-friendly output:
206
+
207
+ - Provenance on every response — source labels, effective-query echo, and confidence/coverage caveats so agents can reason about trust
208
+ - Graceful partial failure — batch tools return per-item success/error rows instead of failing the request, with structured status codes and actionable next-step text
209
+ - Discriminated output contracts — typed status and source fields let callers branch on data, not string parsing
204
210
  ```
205
211
 
212
+ The **Agent-friendly output** subsection documents output-design choices that make the server work well as an AI-agent backend. Include it when the server exhibits at least two of these patterns. Write bullets grounded in the server's actual behavior — not aspirational framework capabilities. Examples of what fits:
213
+
214
+ - Provenance: source labels (`viaSource`, `source`), license/access-level fields, effective-query echo, best-effort warnings on lossy tiers
215
+ - Partial failure: per-item status in batch operations, structured error rows alongside successes, recovery hints ("Next Step" text)
216
+ - Discriminated outputs: union types on `source` or `status` fields, typed `unavailable` reasons, per-tier outcome traces (`triedTiers`)
217
+ - Response shaping: stripping upstream noise, normalizing inconsistent schemas, deduplicating nested structures
218
+
206
219
  ### Getting Started
207
220
 
208
221
  Lead with the lowest-friction option. If a public hosted instance exists, show that first. Then the **three standard install configs in order — `bunx`, `npx`, `docker run`** — followed by the HTTP one-liner quickstart, then prerequisites and install steps.
@@ -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)
@@ -184,6 +184,17 @@ Print clickable URLs for every destination that succeeded:
184
184
 
185
185
  Skip any destination that was skipped in its step.
186
186
 
187
+ ### 9. Verify artifacts are reachable
188
+
189
+ Confirm each published artifact is actually live — don't rely on a successful push exit code alone. For each destination that succeeded:
190
+
191
+ - **npm**: `npm view <package.json#name>@<version> version` — must return the version string
192
+ - **MCP Registry**: `curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=<mcpName>"` — response must include `<version>` for the `io.github.cyanheads/<repo>` entry
193
+ - **GitHub Release**: `gh release view v<VERSION> -R <OWNER>/<REPO> --json assets --jq '.assets[].name'` — must list the `.mcpb` file
194
+ - **GHCR**: fetch an anonymous bearer token, then `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/<OWNER>/<REPO>/manifests/<VERSION>"` — must return HTTP 200
195
+
196
+ If any check fails, halt and report which destination is unreachable. A successful `docker push` or `bun publish` exit code does not guarantee the artifact is queryable — registry propagation delays, auth scoping, and partial failures all exist.
197
+
187
198
  ## Checklist
188
199
 
189
200
  - [ ] Working tree clean; HEAD tagged `v<version>`
@@ -196,4 +207,5 @@ Skip any destination that was skipped in its step.
196
207
  - [ ] `bun run publish-mcp` succeeds (if `server.json` present)
197
208
  - [ ] `bun run bundle` + `gh release create --verify-tag --notes-from-tag` succeeds (if `manifest.json` present)
198
209
  - [ ] Docker buildx multi-arch push succeeds (if `Dockerfile` present)
210
+ - [ ] All published artifacts verified reachable (npm, MCP Registry, GH Release asset, GHCR manifest)
199
211
  - [ ] Deployed artifact URLs reported to the user
@@ -330,16 +330,22 @@ When you complete a skill's checklist, check the boxes and add a completion time
330
330
 
331
331
  Both install links route through HTTPS endpoints (`cursor.com/en/install-mcp` and `vscode.dev/redirect`) — GitHub-rendered markdown strips non-HTTP URL schemes from anchors, so a raw `cursor://` or `vscode:` link won't click through from github.com.
332
332
 
333
- Generate the encoded configs (replace `<PACKAGE_NAME>` and adjust the install command — `npx -y …` is the standard shape for an npm-published stdio server):
333
+ Generate the encoded configs (replace `<PACKAGE_NAME>` and add env vars for any required API keys):
334
334
 
335
335
  ```bash
336
- # Cursor: base64-encoded JSON. `command` is a single string (the full invocation), not split into command/args.
337
- echo -n '{"command":"npx -y <PACKAGE_NAME>"}' | base64
338
-
339
- # VS Code: URL-encoded JSON. Split into command + args.
340
- node -p 'encodeURIComponent(JSON.stringify({name:"<PACKAGE_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"]}))'
336
+ # Cursor: base64-encoded JSON. Split command/args, add env when keys are needed.
337
+ echo -n '{"command":"npx","args":["-y","<PACKAGE_NAME>"],"env":{"API_KEY":"your-api-key"}}' | base64
338
+ # Without env (no required keys):
339
+ echo -n '{"command":"npx","args":["-y","<PACKAGE_NAME>"]}' | base64
340
+
341
+ # VS Code: URL-encoded JSON. Same shape plus a `name` field.
342
+ node -p 'encodeURIComponent(JSON.stringify({name:"<SHORT_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"],env:{API_KEY:"your-api-key"}}))'
343
+ # Without env:
344
+ node -p 'encodeURIComponent(JSON.stringify({name:"<SHORT_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"]}))'
341
345
  ```
342
346
 
347
+ Both clients use the same `{command, args, env}` shape (matching `mcp.json` schema). VS Code adds a top-level `name` field. Omit `env` entirely when no API keys are needed — don't include empty objects or framework-only vars like `MCP_TRANSPORT_TYPE`.
348
+
343
349
  The Claude Desktop badge requires the bundle to ship with a stable filename — `bun run bundle` outputs `dist/<PACKAGE_NAME>.mcpb`, and `release-and-publish` attaches that file to the GitHub Release. `releases/latest/download/<PACKAGE_NAME>.mcpb` then redirects to the most recent release.
344
350
 
345
351
  ---
@@ -365,6 +371,8 @@ security: false # optional — true flags security fi
365
371
 
366
372
  **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
367
373
 
374
+ **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
375
+
368
376
  ---
369
377
 
370
378
  ## Imports
@@ -330,16 +330,22 @@ When you complete a skill's checklist, check the boxes and add a completion time
330
330
 
331
331
  Both install links route through HTTPS endpoints (`cursor.com/en/install-mcp` and `vscode.dev/redirect`) — GitHub-rendered markdown strips non-HTTP URL schemes from anchors, so a raw `cursor://` or `vscode:` link won't click through from github.com.
332
332
 
333
- Generate the encoded configs (replace `<PACKAGE_NAME>` and adjust the install command — `npx -y …` is the standard shape for an npm-published stdio server):
333
+ Generate the encoded configs (replace `<PACKAGE_NAME>` and add env vars for any required API keys):
334
334
 
335
335
  ```bash
336
- # Cursor: base64-encoded JSON. `command` is a single string (the full invocation), not split into command/args.
337
- echo -n '{"command":"npx -y <PACKAGE_NAME>"}' | base64
338
-
339
- # VS Code: URL-encoded JSON. Split into command + args.
340
- node -p 'encodeURIComponent(JSON.stringify({name:"<PACKAGE_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"]}))'
336
+ # Cursor: base64-encoded JSON. Split command/args, add env when keys are needed.
337
+ echo -n '{"command":"npx","args":["-y","<PACKAGE_NAME>"],"env":{"API_KEY":"your-api-key"}}' | base64
338
+ # Without env (no required keys):
339
+ echo -n '{"command":"npx","args":["-y","<PACKAGE_NAME>"]}' | base64
340
+
341
+ # VS Code: URL-encoded JSON. Same shape plus a `name` field.
342
+ node -p 'encodeURIComponent(JSON.stringify({name:"<SHORT_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"],env:{API_KEY:"your-api-key"}}))'
343
+ # Without env:
344
+ node -p 'encodeURIComponent(JSON.stringify({name:"<SHORT_NAME>",command:"npx",args:["-y","<PACKAGE_NAME>"]}))'
341
345
  ```
342
346
 
347
+ Both clients use the same `{command, args, env}` shape (matching `mcp.json` schema). VS Code adds a top-level `name` field. Omit `env` entirely when no API keys are needed — don't include empty objects or framework-only vars like `MCP_TRANSPORT_TYPE`.
348
+
343
349
  The Claude Desktop badge requires the bundle to ship with a stable filename — `bun run bundle` outputs `dist/<PACKAGE_NAME>.mcpb`, and `release-and-publish` attaches that file to the GitHub Release. `releases/latest/download/<PACKAGE_NAME>.mcpb` then redirects to the most recent release.
344
350
 
345
351
  ---
@@ -365,6 +371,8 @@ security: false # optional — true flags security fi
365
371
 
366
372
  **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
367
373
 
374
+ **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
375
+
368
376
  ---
369
377
 
370
378
  ## Imports
@@ -66,6 +66,32 @@ security: false
66
66
  Never speculate on a future number — `#42` for an upcoming PR silently
67
67
  resolves to whatever real item already owns 42, and timeline previews pull
68
68
  in that unrelated item's metadata.
69
+
70
+ TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
71
+ via `gh release create --notes-from-tag`. The tag is a derivative of this
72
+ changelog entry — a condensed, scannable version, not a copy. Format:
73
+
74
+ <theme — omit version number, GitHub prepends it>
75
+ ← blank line
76
+ <1-2 sentence context: what this release does>
77
+ ← blank line
78
+ Dependency bumps: ← section header
79
+ ← blank line
80
+ - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
81
+ ← blank line
82
+ Changed: ← only sections with entries
83
+ ← blank line
84
+ - `format()` output includes `query` in text mode
85
+ ← blank line
86
+ Added:
87
+ ← blank line
88
+ - `manifest.json` scaffolded for MCPB bundle support
89
+ - Install badges (Claude Desktop, Cursor, VS Code)
90
+ ← blank line
91
+ <N> tests pass; `bun run devcheck` clean. ← footer
92
+
93
+ Never a flat comma-separated string. Always structured markdown with
94
+ sections. The tag must scan well as a rendered GitHub Release page.
69
95
  -->
70
96
 
71
97
  ## Added