@cyanheads/mcp-ts-core 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/AGENTS.md +14 -4
  2. package/CLAUDE.md +14 -4
  3. package/README.md +2 -2
  4. package/changelog/0.12.x/0.12.2.md +1 -1
  5. package/changelog/0.13.x/0.13.1.md +56 -0
  6. package/changelog/0.13.x/0.13.2.md +43 -0
  7. package/changelog/0.8.x/0.8.11.md +2 -2
  8. package/{tsconfig.base.json → config/tsconfig.base.json} +2 -2
  9. package/dist/config/index.d.ts +8 -0
  10. package/dist/config/index.d.ts.map +1 -1
  11. package/dist/config/index.js +8 -0
  12. package/dist/config/index.js.map +1 -1
  13. package/dist/core/app.d.ts +82 -2
  14. package/dist/core/app.d.ts.map +1 -1
  15. package/dist/core/app.js +129 -6
  16. package/dist/core/app.js.map +1 -1
  17. package/dist/core/index.d.ts +1 -0
  18. package/dist/core/index.d.ts.map +1 -1
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/worker.d.ts +6 -1
  21. package/dist/core/worker.d.ts.map +1 -1
  22. package/dist/core/worker.js.map +1 -1
  23. package/dist/linter/rules/resource-rules.js +9 -2
  24. package/dist/linter/rules/resource-rules.js.map +1 -1
  25. package/dist/linter/rules/tool-rules.js +4 -1
  26. package/dist/linter/rules/tool-rules.js.map +1 -1
  27. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +15 -1
  28. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +21 -5
  30. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  31. package/dist/mcp-server/transports/http/serverCard.d.ts +23 -0
  32. package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
  33. package/dist/mcp-server/transports/http/serverCard.js +7 -0
  34. package/dist/mcp-server/transports/http/serverCard.js.map +1 -1
  35. package/dist/mcp-server/types.d.ts +10 -3
  36. package/dist/mcp-server/types.d.ts.map +1 -1
  37. package/dist/mcp-server/types.js +4 -3
  38. package/dist/mcp-server/types.js.map +1 -1
  39. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  40. package/dist/services/canvas/core/sqlGate.js +27 -2
  41. package/dist/services/canvas/core/sqlGate.js.map +1 -1
  42. package/dist/testing/index.d.ts +5 -0
  43. package/dist/testing/index.d.ts.map +1 -1
  44. package/dist/testing/index.js +7 -2
  45. package/dist/testing/index.js.map +1 -1
  46. package/dist/utils/pagination/pagination.d.ts.map +1 -1
  47. package/dist/utils/pagination/pagination.js +4 -1
  48. package/dist/utils/pagination/pagination.js.map +1 -1
  49. package/dist/utils/parsing/frontmatterParser.d.ts +8 -7
  50. package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
  51. package/dist/utils/parsing/frontmatterParser.js +91 -16
  52. package/dist/utils/parsing/frontmatterParser.js.map +1 -1
  53. package/framework-skills/add-tool/SKILL.md +4 -4
  54. package/framework-skills/api-config/SKILL.md +19 -3
  55. package/framework-skills/api-context/SKILL.md +3 -1
  56. package/framework-skills/api-telemetry/SKILL.md +13 -10
  57. package/framework-skills/api-testing/SKILL.md +3 -1
  58. package/framework-skills/code-simplifier/SKILL.md +12 -6
  59. package/framework-skills/design-mcp-server/SKILL.md +2 -2
  60. package/framework-skills/git-wrapup/SKILL.md +3 -3
  61. package/framework-skills/maintenance/SKILL.md +2 -2
  62. package/framework-skills/polish-docs-meta/SKILL.md +4 -3
  63. package/framework-skills/polish-docs-meta/references/readme.md +12 -8
  64. package/framework-skills/release-and-publish/SKILL.md +12 -3
  65. package/framework-skills/report-issue-framework/SKILL.md +2 -1
  66. package/framework-skills/report-issue-local/SKILL.md +7 -1
  67. package/package.json +7 -7
  68. package/scripts/build.ts +28 -6
  69. package/scripts/clean-mcpb.ts +8 -2
  70. package/scripts/clean.ts +40 -4
  71. package/scripts/devcheck.ts +46 -15
  72. package/scripts/lint-packaging.ts +79 -4
  73. package/templates/.env.example +5 -2
  74. package/templates/AGENTS.md +16 -0
  75. package/templates/CLAUDE.md +16 -0
  76. package/templates/src/index.ts +10 -0
@@ -9,14 +9,88 @@ import { logger } from '../internal/logger.js';
9
9
  import { requestContextService, withExtra, } from '../internal/requestContext.js';
10
10
  import { assertTextInputBudget } from './inputBudget.js';
11
11
  import { yamlParser } from './yamlParser.js';
12
+ /** The `---` fence that opens and closes a frontmatter block. */
13
+ const DELIMITER = '---';
14
+ /** Single-character `\s` test — no quantifier, so no backtracking. */
15
+ const WHITESPACE = /\s/;
12
16
  /**
13
- * Regular expression to extract frontmatter from markdown.
14
- * Matches YAML content between --- delimiters at the start of the document.
15
- * - Group 1: YAML content between delimiters
16
- * - Group 2: Remaining markdown content
17
- * @private
17
+ * Positions a regex `^` matches under the `m` flag: the start of input, and
18
+ * anything immediately after a LineTerminator (LF, CR, LS, PS).
18
19
  */
19
- const frontmatterRegex = /^---\s*\n([\s\S]*?)^---\s*([\s\S]*)$/m;
20
+ function isLineTerminator(char) {
21
+ return char === '\n' || char === '\r' || char === '\u2028' || char === '\u2029';
22
+ }
23
+ /** Index just past the next line terminator at or after `from`, or `-1`. */
24
+ function nextLineStart(text, from) {
25
+ for (let i = from; i < text.length; i++) {
26
+ if (isLineTerminator(text.charAt(i)))
27
+ return i + 1;
28
+ }
29
+ return -1;
30
+ }
31
+ /**
32
+ * End of an opening `---` fence — the index just past the last newline in the
33
+ * whitespace run that follows it, or `-1` when that run carries no newline.
34
+ * Mirrors greedy `\s*` backtracking to the final `\n` it can leave for the
35
+ * literal `\n` that follows.
36
+ */
37
+ function endOfOpeningFence(text, from) {
38
+ let lastNewline = -1;
39
+ for (let i = from; i < text.length && WHITESPACE.test(text.charAt(i)); i++) {
40
+ if (text.charAt(i) === '\n')
41
+ lastNewline = i;
42
+ }
43
+ return lastNewline === -1 ? -1 : lastNewline + 1;
44
+ }
45
+ /** Index of the next line-initial `---` at or after `from`, or `-1`. */
46
+ function findClosingFence(text, from) {
47
+ for (let i = from; i >= 0 && i <= text.length; i = nextLineStart(text, i)) {
48
+ if (text.startsWith(DELIMITER, i))
49
+ return i;
50
+ }
51
+ return -1;
52
+ }
53
+ /**
54
+ * Splits a markdown document into its YAML frontmatter block and the content
55
+ * after it, or returns `null` when no complete block is present.
56
+ *
57
+ * A linear-time index walk replacing the equivalent
58
+ * `/^---\s*\n([\s\S]*?)^---\s*([\s\S]*)$/m`, whose lazy `[\s\S]*?` between two
59
+ * line-anchored fences takes time quadratic in the input when the closing fence
60
+ * is absent — reachable whenever a server hands this parser markdown it
61
+ * received over the wire (CodeQL `js/polynomial-redos`).
62
+ *
63
+ * Behavior is preserved exactly, including the shapes the regex decided
64
+ * implicitly: the opening fence is the first line-initial `---` followed by a
65
+ * whitespace run containing a newline (not necessarily the document's first
66
+ * line); the closing fence is the next line-initial `---`, so a `----` line
67
+ * closes the block and leaves its fourth dash on the content, and a `---`
68
+ * inside the YAML that is not line-initial does not; and the whitespace after
69
+ * the closing fence belongs to neither half.
70
+ *
71
+ * @param markdown - Document to split.
72
+ * @returns The YAML source and the content that follows it, or `null`.
73
+ */
74
+ function splitFrontmatter(markdown) {
75
+ for (let open = 0; open >= 0 && open <= markdown.length; open = nextLineStart(markdown, open)) {
76
+ if (!markdown.startsWith(DELIMITER, open))
77
+ continue;
78
+ const yamlStart = endOfOpeningFence(markdown, open + DELIMITER.length);
79
+ if (yamlStart === -1)
80
+ continue;
81
+ const close = findClosingFence(markdown, yamlStart);
82
+ // No closing fence after the earliest viable opening fence means none after
83
+ // a later one either — every later search window is a subset of this one.
84
+ if (close === -1)
85
+ return null;
86
+ let contentStart = close + DELIMITER.length;
87
+ while (contentStart < markdown.length && WHITESPACE.test(markdown.charAt(contentStart))) {
88
+ contentStart++;
89
+ }
90
+ return { yaml: markdown.slice(yamlStart, close), content: markdown.slice(contentStart) };
91
+ }
92
+ return null;
93
+ }
20
94
  /**
21
95
  * Utility class for extracting and parsing YAML frontmatter from markdown documents.
22
96
  * Supports Obsidian-style and Jekyll-style frontmatter (YAML between `---` delimiters).
@@ -26,11 +100,12 @@ export class FrontmatterParser {
26
100
  /**
27
101
  * Extracts and parses YAML frontmatter from a markdown string.
28
102
  *
29
- * Looks for a `---`-delimited block at the very start of the document. If
30
- * found, the YAML inside is parsed via {@link yamlParser} and the remaining
31
- * markdown is returned separately. An empty `---\n---` block is accepted and
32
- * returns `frontmatter: {}` with `hasFrontmatter: true`. If no frontmatter
33
- * block is present, the original string is returned unchanged.
103
+ * Looks for a `---`-delimited block opening on the first line that starts
104
+ * with `---`. If found, the YAML inside is parsed via {@link yamlParser} and
105
+ * the markdown after the closing fence is returned separately. An empty
106
+ * `---\n---` block is accepted and returns `frontmatter: {}` with
107
+ * `hasFrontmatter: true`. If no complete block is present, the original
108
+ * string is returned unchanged.
34
109
  *
35
110
  * @template T - The expected shape of the parsed frontmatter object. Defaults to `unknown`.
36
111
  * @param markdown - The markdown string that may contain a frontmatter block.
@@ -46,13 +121,13 @@ export class FrontmatterParser {
46
121
  * const md = `---\ntitle: Hello\ntags: [a, b]\n---\n\n# Body`;
47
122
  * const result = await frontmatterParser.parse<{ title: string; tags: string[] }>(md);
48
123
  * // result.frontmatter → { title: 'Hello', tags: ['a', 'b'] }
49
- * // result.content → '\n# Body'
124
+ * // result.content → '# Body'
50
125
  * // result.hasFrontmatter → true
51
126
  * ```
52
127
  */
53
128
  async parse(markdown, context, budget) {
54
129
  assertTextInputBudget(markdown, budget);
55
- const match = markdown.match(frontmatterRegex);
130
+ const match = splitFrontmatter(markdown);
56
131
  if (!match) {
57
132
  // No frontmatter found - return original content
58
133
  const logContext = context ||
@@ -66,8 +141,8 @@ export class FrontmatterParser {
66
141
  hasFrontmatter: false,
67
142
  };
68
143
  }
69
- const yamlContent = match[1] ?? '';
70
- const markdownContent = match[2] ?? '';
144
+ const yamlContent = match.yaml;
145
+ const markdownContent = match.content;
71
146
  const logContext = context ||
72
147
  requestContextService.createRequestContext({
73
148
  operation: 'FrontmatterParser.parse',
@@ -145,7 +220,7 @@ export class FrontmatterParser {
145
220
  *
146
221
  * const result = await frontmatterParser.parse(markdown, context);
147
222
  * console.log(result.frontmatter); // { title: 'My Note', tags: [...], date: '2025-01-15' }
148
- * console.log(result.content); // '\n# Note Content\nThis is the actual note.'
223
+ * console.log(result.content); // '# Note Content\nThis is the actual note.'
149
224
  * console.log(result.hasFrontmatter); // true
150
225
  *
151
226
  * // Markdown without frontmatter
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatterParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/frontmatterParser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frontmatterParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/frontmatterParser.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP tool definition. Use when the user asks to add a tool, create a new tool, or implement a new capability for the server.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.24"
7
+ version: "2.26"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -131,7 +131,7 @@ export const {{TOOL_EXPORT}} = tool('{{tool_name}}', {
131
131
 
132
132
  ### Multi-round-trip variant
133
133
 
134
- A handler that needs something the caller didn't supply returns `ctx.requestInput(...)` and is re-entered with the answers on `ctx.inputs`. There is no mid-handler `await` for user input, and no capability check — the surface is always present, on every transport and both protocol eras. Whether the caller can *answer* is a separate question — a 2025-era HTTP client cannot when the server runs `MCP_SESSION_MODE=stateless` (`api-context` § `ctx.requestInput`). Treat an unanswered round as terminal, never as consent.
134
+ A handler that needs something the caller didn't supply returns `ctx.requestInput(...)` and is re-entered with the answers on `ctx.inputs`. There is no mid-handler `await` for user input, and no capability check — the surface is always present, on every transport and both protocol eras. Whether the caller can *answer* is a separate question — a 2025-era HTTP client cannot when the server runs `MCP_SESSION_MODE=stateless`, which a server needing that leg declares with `createApp({ sessionMode: { require: 'stateful' } })` rather than leaving to a deployment (`api-context` § `ctx.requestInput`). Treat an unanswered round as terminal, never as consent.
135
135
 
136
136
  ```typescript
137
137
  import { inputRequired, tool, z } from '@cyanheads/mcp-ts-core';
@@ -221,8 +221,8 @@ export const submitObservations = getServerConfig().enableWrites
221
221
  | Surface | Disabled tools? |
222
222
  |:---|:---|
223
223
  | `tools/list` (MCP protocol — what clients call) | **No** — disabled tools are skipped at registration |
224
- | `/.well-known/mcp.json` `definitions.tools` (Server Card) | **Yes**, with `disabled` field discovery agents see them as present-but-uncallable |
225
- | `/` (HTML landing page) | **Yes**, in a 4th muted bucket after `read \| write \| destructive` |
224
+ | `/.well-known/mcp.json` (Server Card) | **No** the card carries no per-tool entries at all, so a discovery agent reading it cannot see a disabled tool |
225
+ | `/` (HTML landing page) | **Yes**, in a 4th muted bucket after `read \| write \| destructive` — the only surface where a disabled tool is visible |
226
226
 
227
227
  The wrapper preserves all original definition fields (handler, schemas, auth scopes, error contracts) — when re-enabled, the tool already conforms to every lint rule.
228
228
 
@@ -4,7 +4,7 @@ description: >
4
4
  Reference for core and server configuration in `@cyanheads/mcp-ts-core`. Covers env var tables with defaults, priority order, server-specific Zod schema pattern, and Workers lazy-parsing requirement.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.17"
7
+ version: "1.18"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -25,7 +25,8 @@ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod from environment variable
25
25
 
26
26
  1. `name`/`version`/`title`/`websiteUrl`/`description`/`icons` options passed to `createApp()` or `createWorkerHandler()`
27
27
  2. Environment variables
28
- 3. `package.json` fields
28
+ 3. `sessionMode.default` passed to `createApp()` — a default, so it sits *below* the env var it seeds, unlike the identity options above
29
+ 4. `package.json` fields
29
30
 
30
31
  **Where `package.json` is read from:** the application root — the nearest `package.json` at or above the process entry module (`process.argv[1]`), which is the served package on every launch path (`npx`, `.mcpb`, a client config naming `dist/index.js`), none of which run from the package root. The launching client's working directory is never the anchor: a stdio client starts the server from wherever it happens to be, so reading identity from there makes a server report a foreign project's name and version. When the entry module is a tool installed under the project's own `node_modules` and the process runs from that project — a test runner is the usual case — the project's manifest wins. With no manifest reachable, the framework's own identity is the fallback.
31
32
 
@@ -50,6 +51,7 @@ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod from environment variable
50
51
  | `description` | `string?` | One-line description; wins over `MCP_SERVER_DESCRIPTION` when set |
51
52
  | `icons` | `Implementation['icons']?` | Array of icon objects: `{ src, mimeType?, sizes?: string[], theme?: 'light'\|'dark' }` |
52
53
  | `cacheHints` | `CacheHints?` | Cache hints for the 2026-07-28 cacheable results, keyed by operation — see below |
54
+ | `sessionMode` | `SessionMode \| { default?: SessionMode; require?: 'stateful' }` | Session posture declared in code — see below |
53
55
 
54
56
  #### Cache hints (`cacheHints`)
55
57
 
@@ -69,6 +71,20 @@ await createApp({
69
71
  - A resource's own `cacheHint` overrides the `resources/read` entry for that resource, field by field — see the `add-resource` skill.
70
72
  - Omitting a hint keeps the SDK defaults (`ttlMs: 0`, `cacheScope: 'private'`). Responses to 2025-era clients are never affected.
71
73
 
74
+ #### Session mode (`sessionMode`)
75
+
76
+ Declares the session posture in `src/` instead of leaving it to a deployment's `MCP_SESSION_MODE`. The bare string is shorthand for `{ default }`. HTTP only — `MCP_SESSION_MODE` has no effect on stdio.
77
+
78
+ ```ts
79
+ await createApp({ sessionMode: 'stateless' }); // default only
80
+ await createApp({ sessionMode: { default: 'stateful', require: 'stateful' } }); // and enforced
81
+ ```
82
+
83
+ - **`default`** applies only when `MCP_SESSION_MODE` carries no meaningful value. An empty string and a whole-value unsubstituted `${…}` placeholder both read as unset on the config path, so both fall through to the option rather than to the schema default (`auto`). An explicit `MCP_SESSION_MODE` always wins.
84
+ - **`require: 'stateful'`** fails startup with a `ConfigurationError` naming the conflicting env value when the resolved HTTP mode is `stateless`, before any service is constructed. Declare it when a handler gates a destructive action behind `ctx.requestInput` / `inputRequired.elicit` — see the `MCP_SESSION_MODE` row for why that combination is unusable for 2025-era clients. There is no `require: 'stateless'`; nothing needs statelessness to work.
85
+ - The advertised `transport.sessionMode` follows automatically — `resolveSessionMode` is the single resolution the manifest, the session store, and the `ctx.sessionId` gate all read — and still never publishes `auto`.
86
+ - Cloudflare Workers are outside this contract: `MCP_SESSION_MODE` is not in `CORE_ENV_BINDINGS`, so a `[vars]` entry reaches `process.env` only through `extraEnvBindings`.
87
+
72
88
  ---
73
89
 
74
90
  ### Environment & logging
@@ -90,7 +106,7 @@ await createApp({
90
106
  | `MCP_HTTP_MAX_BODY_BYTES` | `mcpHttpMaxBodyBytes` | `1048576` (1 MiB) | Max **inbound** JSON-RPC request body; oversized requests get `413` before per-request allocation. Does **not** cap upstream data staged into a canvas or response sizes. `0` disables (defer to runtime/proxy). |
91
107
  | `MCP_HTTP_MAX_PORT_RETRIES` | `mcpHttpMaxPortRetries` | `15` | Rungs of the port ladder walked when a bind collides; each rung tries `port + 1`. See [Port binding](#port-binding) |
92
108
  | `MCP_HTTP_PORT_RETRY_DELAY_MS` | `mcpHttpPortRetryDelayMs` | `50` | Delay between port retries (ms) |
93
- | `MCP_SESSION_MODE` | `mcpSessionMode` | `auto` | `stateless` \| `stateful` \| `auto`; `auto` resolves to `stateful`. `stateless` also disables the 2025-era multi-round-trip shim, so v1 HTTP clients cannot answer a `ctx.requestInput` round — 2026-07-28 clients and stdio are unaffected |
109
+ | `MCP_SESSION_MODE` | `mcpSessionMode` | `auto` | `stateless` \| `stateful` \| `auto`; `auto` resolves to `stateful`. Under `stateless`, the 2025-era multi-round-trip shim still runs but its capability gate refuses: each request is served by an instance that never processed `initialize`, so the client-capability view is empty and a `ctx.requestInput` round can never be answered fail-closed, but unconditional, so the tool is unusable for those clients rather than merely guarded. 2026-07-28 clients and stdio are unaffected. Seed it from code with `createApp({ sessionMode })` — see below |
94
110
  | `MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS` | `mcpStatefulSessionStaleTimeoutMs` | `1800000` | 30 min; stale session eviction |
95
111
  | `MCP_HTTP_RESUMABILITY` | `mcpHttpResumability` | `true` | SSE stream replay under stateful HTTP. On by default — selecting a session mode is the opt-in. Kill switch only; no effect on stateless serving or the session-less 2026-07-28 era |
96
112
  | `MCP_HTTP_RESUMABILITY_MAX_EVENTS` | `mcpHttpResumabilityMaxEvents` | `512` | Events retained per session for replay; oldest evicted first. Lower it on a server whose tools return large results |
@@ -4,7 +4,7 @@ description: >
4
4
  Canonical reference for the unified `Context` object passed to every tool and resource handler in `@cyanheads/mcp-ts-core`. Covers the full interface, its `RequestContext` base, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.requestInput`, `ctx.inputs`, `ctx.enrich`, `ctx.content`), and when to use each.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.3"
7
+ version: "2.4"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -328,6 +328,8 @@ One code path serves both eras. A 2026-07-28 client fulfils the embedded request
328
328
 
329
329
  **`MCP_SESSION_MODE` decides whether that second leg exists.** Under `stateful` / `auto` the shim has the session it needs. Under `stateless` each 2025-era request is served by a fresh instance that never saw `initialize`, so its client-capability view is empty and the round trip is refused rather than attempted — fail-closed, but the handler never gets its answer. Ship `stateless` on a server whose destructive tools gate on `ctx.requestInput` and those tools become unusable for v1 HTTP clients. 2026-07-28 clients are unaffected in either mode: that revision has no server→client request channel at all, which is precisely why `input_required` exists. stdio is unaffected in either mode.
330
330
 
331
+ **Declare the requirement rather than documenting it.** `createApp({ sessionMode: { default: 'stateful', require: 'stateful' } })` seeds the mode from code and refuses to start over HTTP when the resolved mode is `stateless`, so the incompatibility surfaces at boot instead of at the first refused confirmation. `MCP_SESSION_MODE` still wins over the default; the requirement is what an operator cannot silently override. Nothing derives this from handler code — `ctx.requestInput` is present on every transport and both eras, so whether a server needs a live session is a decision its author makes. Full precedence and error shape: `api-config` § Session mode.
332
+
331
333
  ### The shape of a multi-round-trip handler
332
334
 
333
335
  Read `ctx.inputs` first, request only what is still missing, and write the call in return position so TypeScript narrows the line below it.
@@ -4,7 +4,7 @@ description: >
4
4
  Catalog of OpenTelemetry instrumentation built into framework `@cyanheads/mcp-ts-core` — spans, metrics, completion logs, env config, runtime caveats, custom instrumentation patterns, and cardinality rules. Use when enabling OTel export, adding custom spans or metrics in services, debugging missing telemetry, looking up attribute names, or deciding what's safe to put on a metric attribute vs. a span.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.8"
7
+ version: "1.9"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -59,18 +59,21 @@ Cloud platform detection auto-populates resource attributes:
59
59
 
60
60
  ## Flush at exit
61
61
 
62
- Spans batch and metrics push on a 15-second cycle, so a process that exits between cycles takes its telemetry with it. `ServerHandle.shutdown()` is the drain: it stops the transport, then force-flushes traces and metrics through the OTLP exporters and closes the logger.
62
+ Spans batch and metrics push on a 15-second cycle, so a process that exits between cycles takes its telemetry with it. `ServerHandle.shutdown()` is the drain: it stops the transport, runs the `teardown` hook, then force-flushes traces and metrics through the OTLP exporters and closes the logger.
63
63
 
64
- | Trigger | Path |
65
- |:--------|:-----|
66
- | `SIGTERM` / `SIGINT` | `shutdown(signal)` |
67
- | `uncaughtException` / `unhandledRejection` | `shutdown(signal)`, then `process.exit(1)` |
68
- | stdin EOF, stdio transport | `shutdown('STDIN_EOF')`, then `process.exit(0)` |
69
- | `ServerHandle.shutdown()` called directly | the same drain, no exit |
64
+ | Trigger | Path | Exit |
65
+ |:--------|:-----|:-----|
66
+ | `SIGTERM` / `SIGINT` | `shutdown(signal)`, then an explicit exit | `0`, or `1` when the backstop fires |
67
+ | `uncaughtException` / `unhandledRejection` | `shutdown(signal)`, then an explicit exit | `1` |
68
+ | stdin EOF, stdio transport | `shutdown('STDIN_EOF')`, then an explicit exit | `0`, backstop or not |
69
+ | a second signal during shutdown | none — the handlers are already detached | the OS default (`143` / `130`) |
70
+ | `ServerHandle.shutdown()` called directly | the same drain | none — exit-free by contract |
70
71
 
71
- **Stdin EOF is a disconnect.** A stdio host closing the pipe runs the cleanup a signal runs, exactly once — the shutdown detaches the signal handlers and the EOF watcher as it starts, so neither can re-enter it — and the process then exits explicitly instead of waiting to run out of handles. Two things follow: the OTLP export leaves the process, and a `setInterval` a service registered without `unref()` can no longer keep the server resident after its client is gone. The path writes nothing to stdout.
72
+ **A signal ends the process.** Every exit-bearing path runs the cleanup exactly once — shutdown detaches the signal handlers and the EOF watcher as it starts, so neither can re-enter it — and then exits explicitly instead of waiting to run out of handles. Two things follow: the OTLP export leaves the process, and a handle registered outside framework teardown (a recursive `fs.watch`, a `setInterval` without `unref()`) can no longer keep the server resident. A second signal arriving mid-shutdown reaches no handler, so the default disposition terminates immediately — the operator's force-kill escape hatch. Neither path writes to stdout.
72
73
 
73
- **The drain is bounded.** Shutdown-on-exit races a 10-second backstop, so a cleanup step that never settles still terminates the process. The logger bounds its own flush separately, per pino instance: a completing callback is awaited in full, and a runtime whose callback never arrives releases shutdown rather than hanging it.
74
+ **The drain is bounded.** Shutdown-on-exit races a 10-second backstop that bounds the shutdown as a whole, not any single await: a step that settles inside the ceiling is never truncated, and only one that never settles is cut. A signal cut exits 1 after a warning naming that step; a stdin-EOF cut exits 0 without one. The logger bounds its own flush separately, per pino instance: a completing callback is awaited in full, and a runtime whose callback never arrives releases shutdown rather than hanging it.
75
+
76
+ **Release what the framework cannot see.** `createApp({ teardown })` is the `setup` counterpart: it runs after the transport stops and before the logger closes, on every shutdown path, with `CoreServices` still alive. Close a watcher, socket, or poller there rather than leaving it for the backstop, which cuts a ref'd handle rather than closing it. An error it raises is logged and never blocks the exit; a hook that never settles is what the ceiling then bounds. Node/Bun only — `createWorkerHandler` does not accept it.
74
77
 
75
78
  Workers has no `ServerHandle` and no `NodeSDK` — flush whatever exporter you wired there yourself, via `ctx.waitUntil()`.
76
79
 
@@ -4,7 +4,7 @@ description: >
4
4
  Testing patterns for MCP tool/resource handlers using `createMockContext` and Vitest. Covers mock context options, handler testing, McpError assertions, format testing, Vitest config setup, and test isolation conventions.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.9"
7
+ version: "1.10"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -124,6 +124,8 @@ toolContractSuite(searchTool, {
124
124
 
125
125
  Use `runToolContract(definition, input, { context })` from `/testing` when a custom test runner or an imperative assertion is a better fit. It intentionally skips transport auth and telemetry; those belong in transport/integration tests.
126
126
 
127
+ Arguments that fail the `input` schema are rejected the way the production handler factory rejects them: `InvalidParams` (`-32602`), with a message naming the tool and every failing field. That is the code a client sees on the wire, so assert it — not `ValidationError` (`-32007`), which stays the classification for a `ZodError` a handler throws itself and for an output-schema rejection.
128
+
127
129
  ---
128
130
 
129
131
  ## `createMockContext` options
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: code-simplifier
3
3
  description: >
4
- Post-session code review and cleanup against a working tree of changes. Analyzes `git diff` to simplify, consolidate, and align changed code with the existing codebase — modernize syntax, remove unnecessary complexity, consolidate duplicated logic, catch efficiency issues. Use after a substantive working session, or when asked to clean up, simplify, reduce slop, consolidate, modernize, tighten up, or de-slop code. For `@cyanheads/mcp-ts-core` projects, includes specific transformations for tool/resource/prompt definitions, the ctx pattern, error factories, and framework idioms.
4
+ Code review and cleanup against a working tree of changes, or against a named path or whole codebase. Analyzes `git diff` (or the named target) to simplify, consolidate, and align code with the existing codebase — modernize syntax, remove unnecessary complexity, consolidate duplicated logic, catch efficiency issues. Use after a substantive working session, or when asked to clean up, simplify, reduce slop, consolidate, modernize, tighten up, de-slop, or scan a codebase. For `@cyanheads/mcp-ts-core` projects, includes specific transformations for tool/resource/prompt definitions, the ctx pattern, error factories, and framework idioms.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
11
11
 
12
12
  # Code Simplifier
13
13
 
14
- Post-session cleanup pass. Reviews what changed, understands how it fits the existing codebase, and makes targeted improvements — modernizing syntax, removing unnecessary complexity, consolidating duplicated logic, catching efficiency issues. Prioritizes codebase cohesion over local perfection.
14
+ Cleanup pass over a session's changes or a named target. Reviews the code in scope, understands how it fits the existing codebase, and makes targeted improvements — modernizing syntax, removing unnecessary complexity, consolidating duplicated logic, catching efficiency issues. Prioritizes codebase cohesion over local perfection.
15
15
 
16
16
  ## Core philosophy
17
17
 
@@ -19,9 +19,12 @@ Post-session cleanup pass. Reviews what changed, understands how it fits the exi
19
19
 
20
20
  ## Procedure
21
21
 
22
- ### Phase 1: Identify changes
22
+ ### Phase 1: Set the scope
23
23
 
24
- Run `git status` to see the shape of the working tree, then `git diff HEAD` for all uncommitted changes (staged and unstaged). Untracked files never appear in the diff — read new files directly. If the diff is empty and there are no untracked files, review the last commit (`git diff HEAD~1 HEAD`); if that is also empty, say the tree is clean and stop. Don't go hunting through the codebase for files to improve.
24
+ Two scopes; the caller's wording picks one, and the diff is the default.
25
+
26
+ - **Diff** (nothing named): run `git status` to see the shape of the working tree, then `git diff HEAD` for all uncommitted changes (staged and unstaged). Untracked files never appear in the diff — read new files directly. If the diff is empty and there are no untracked files, review the last commit (`git diff HEAD~1 HEAD`); if that is also empty, say the tree is clean and stop. Don't go hunting through the codebase for files to improve.
27
+ - **Target** (a named path, module, or "the whole codebase"): the named files are the scope, whatever their git state. Work one module or directory at a time and re-run the gate after each, so a large scan never becomes one unverifiable diff. Take the target as named — don't rank or narrow it by commit history.
25
28
 
26
29
  ### Phase 2: Understand the surrounding codebase
27
30
 
@@ -47,6 +50,8 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
47
50
 
48
51
  - **Redundant state** — State that duplicates existing state, cached values that could be derived.
49
52
  - **Unnecessary complexity** — Deep nesting that could be guard clauses, premature abstractions, over-engineered solutions to simple problems.
53
+ - **Pass-through layers** — Apply the deletion test to a wrapper, helper, or module: if deleting it and inlining its body makes the complexity vanish, it was a pass-through — inline it. If the same logic would reappear across several callers, it earns its keep. An interface, port, or injected dependency with a single implementation and no test double is a hypothetical seam, not a real one — collapse it until something actually varies across it.
54
+ - **Test-only reach** — A function extracted or exported only so a test can get at it is a shape problem, not a cleanup: name it in the summary with the module it belongs to. Don't restructure it here — the tests would have to move with it.
50
55
  - **Dead code** — Unreachable branches, unused variables, commented-out code. An export nothing imports is dead in an application or a package-internal module; on a published package's public surface it is API — leave it and note it in the summary.
51
56
  - **Defensive code for impossible states** — Guards for cases the type system or upstream validation already prevents. Drop them.
52
57
  - **Type escapes** — `any`, `as` casts that paper over a mismatch, non-null `!`, and `@ts-ignore`. Each is a claim the compiler couldn't check: replace with a narrowed type, a type guard, or a parse at the boundary. Keep the ones documenting a genuine type-system or third-party-types limitation, and prefer `@ts-expect-error` with a one-line reason over `@ts-ignore`.
@@ -74,13 +79,14 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
74
79
  - **Tool annotations** — `readOnlyHint`, `idempotentHint`, `openWorldHint` should reflect reality. A read-only tool with `readOnlyHint: false` gives clients the wrong picture.
75
80
  - **`exactOptionalPropertyTypes` boundaries** — If a downstream type insists on the field being present-or-not-present (not present-as-undefined), use a mapped widening type at the boundary. The pattern is documented in the framework.
76
81
  - **`format()` ↔ `structuredContent` parity** — Different MCP clients forward different surfaces. Tests should assert both surfaces carry equivalent data.
82
+ - **Framework layering is not a pass-through** — the init/accessor pair (`initFooService()` / `getFooService()`), the tool definition → service split, and a provider interface the framework selects by config are prescribed convention; the deletion test doesn't apply to them, and a single-implementation service accessor is the framework's seam, not a hypothetical one.
77
83
  - **Defensive code** — the "impossible states" the framework already prevents include malformed params (Zod-validated before the handler runs) and unclassified errors (caught and classified after it throws). Guards for either are dead.
78
84
  - **Public surface** — the MCP surface (every tool input/output schema advertised to clients) is public API for the "API compatibility" rule; changing one is a breaking change, not a refactor.
79
85
 
80
86
  ### Phase 4: Apply transformations
81
87
 
82
88
  1. **Filter findings ruthlessly.** If a finding is a false positive or not worth the churn, skip it. Don't argue with yourself about borderline cases — move on.
83
- 2. **Stay in scope.** Edit only files in the diff or new this session. Touch a file outside that set only when a finding requires it — importing an existing helper, deleting a private export the diff just orphaned — and only on the lines that finding names. Anything broader goes in the summary as a recommendation, not into the tree.
89
+ 2. **Stay in scope.** Edit only files inside the Phase 1 scope — the diff plus files new this session, or the named target. Touch a file outside that set only when a finding requires it — importing an existing helper, deleting a private export the diff just orphaned — and only on the lines that finding names. Anything broader goes in the summary as a recommendation, not into the tree.
84
90
  3. **Correctness bugs are not this pass's job.** A real defect doesn't get folded into a cleanup diff — name it in the summary with file and line so it can be handled as its own change.
85
91
  4. **Transform incrementally** — one category of change at a time (modernize syntax, then reduce nesting, then consolidate).
86
92
  5. **Verify equivalence** — all functionality, types, and public interfaces must remain unchanged. Re-run the gate from Phase 2 after transforming; a simplification that breaks the build is worse than the verbosity it removed.
@@ -4,7 +4,7 @@ description: >
4
4
  Design the tool surface, resources, and service layer for a new MCP server. Use when starting a new server, planning a major feature expansion, or when the user describes a domain/API they want to expose via MCP. Produces a design doc at docs/design.md that drives implementation.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.25"
7
+ version: "2.26"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -251,7 +251,7 @@ Tools that perform multi-step mutations (the Workflow shape) have two safety con
251
251
 
252
252
  **Confirmation-gated destructive modes, with an annotation fallback.** When a workflow's `mode` parameter switches between safe and destructive arms (`draft` vs `send`, `plan` vs `apply`), gate the destructive arm on a confirmation the handler asks for via `ctx.requestInput(...)`, so a human approves before the irreversible step fires. The handler is re-entered with the answer on `ctx.inputs`; it does not `await` mid-call.
253
253
 
254
- The gate is always *reachable* — `ctx.requestInput` is present on every transport and both protocol revisions (2025-11-25 legacy, 2026-07-28 current) — but it is not always *answerable*: a client that never fulfils the `input_required` result simply doesn't retry, and the destructive step never runs. The same holds for a 2025-11-25 HTTP client when the server runs `MCP_SESSION_MODE=stateless`, which disables the legacy round-trip shim the gate refuses and the destructive step never fires. That is the safe outcome, but it makes the tool unusable for those clients, so weigh it before defaulting such a server to `stateless` (`api-context` § `ctx.requestInput`). Keep `destructiveHint: true` in annotations so those clients' own approval flows still surface the risk. A decline is terminal — the handler fails the call rather than re-asking, which would loop until the round budget runs out. The handler shape is in `api-context` § *The shape of a multi-round-trip handler*.
254
+ The gate is always *reachable* — `ctx.requestInput` is present on every transport and both protocol revisions (2025-11-25 legacy, 2026-07-28 current) — but it is not always *answerable*: a client that never fulfils the `input_required` result simply doesn't retry, and the destructive step never runs. The same holds for a 2025-11-25 HTTP client when the server runs `MCP_SESSION_MODE=stateless`: the legacy round-trip shim still runs, but its capability gate refuses because the serving instance never processed `initialize` — the destructive step never fires. That is the safe outcome, but it makes the tool unusable for those clients, so a server built around such a gate declares `createApp({ sessionMode: { default: 'stateful', require: 'stateful' } })` and refuses to start stateless rather than degrading (`api-context` § `ctx.requestInput`). Keep `destructiveHint: true` in annotations so those clients' own approval flows still surface the risk. A decline is terminal — the handler fails the call rather than re-asking, which would loop until the round budget runs out. The handler shape is in `api-context` § *The shape of a multi-round-trip handler*.
255
255
 
256
256
  **Safe defaults on parameters that determine blast radius.** When a workflow accepts a parameter that controls how far-reaching a mutation is, default to the safer value. A bulk file-update tool defaulting `mode: 'preview'` (no writes) means a sloppy agent call shows a diff rather than blasting changes; an apply-plan tool defaulting `dryRun: true` means a misread plan previews rather than executes; an object-delete tool requiring an explicit `confirmCount` matching the result-set size means an unscoped query can't silently nuke a million rows. Agents that genuinely want the destructive behavior have to name it explicitly, which surfaces intent in the tool call and in logs.
257
257
 
@@ -4,7 +4,7 @@ description: >
4
4
  Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts). Verify, commit. Stops at "committed locally on main" — or, when the project releases through a release PR, at "release branch pushed, PR open". No tag, no push to main, no publish: the release-and-publish skill merges, tags, and ships from here. Distilled from the git_wrapup_instructions protocol.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.16"
7
+ version: "1.17"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -82,7 +82,7 @@ Every file that declares a version must be updated. Skip any file that doesn't e
82
82
  - `server.json` — top-level `version` AND every `packages[].version` entry
83
83
  - `manifest.json` (if present) — `version`. Verify `name` is the bare package name (e.g. `bls-mcp-server`, not `@cyanheads/bls-mcp-server`)
84
84
  - `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` (if present) — `version`. Packaging validation fails on a mismatch; `.codex-plugin/mcp.json` is connection config and carries none
85
- - `README.md` — version badge
85
+ - `README.md` — version badge. Packaging validation fails on a mismatch with `package.json`; a literal `-` in a prerelease is escaped as `--` (`Version-0.14.0--rc.1-`)
86
86
  - `CLAUDE.md` / `AGENTS.md` — if they pin a version string
87
87
  - `Dockerfile` — OCI labels if they pin the version
88
88
 
@@ -269,7 +269,7 @@ If the working tree isn't clean or the release commit isn't at HEAD, something w
269
269
  ## Checklist
270
270
 
271
271
  - [ ] Diff reviewed end-to-end before version bump
272
- - [ ] Version bumped in every declaring file (`package.json`, `server.json`, `manifest.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, README badge, `CLAUDE.md`/`AGENTS.md` if they pin a version) — verify by command, not by eye: `v=$(jq -r .version package.json); grep -rl "$v" package.json server.json manifest.json .claude-plugin/plugin.json .codex-plugin/plugin.json README.md | wc -l` must equal the count of files that exist, and `grep -c "Version-$v-" README.md` must print `1`. The README badge is the one no lint reads, so it is the one that ships stale
272
+ - [ ] Version bumped in every declaring file (`package.json`, `server.json`, `manifest.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, README badge, `CLAUDE.md`/`AGENTS.md` if they pin a version) — verify by command, not by eye: `v=$(jq -r .version package.json); grep -rl "$v" package.json server.json manifest.json .claude-plugin/plugin.json .codex-plugin/plugin.json README.md | wc -l` must equal the count of files that exist, and `grep -c "Version-$v-" README.md` must print `1`. `lint:packaging` checks the README badge against `package.json`, so a stale badge now fails `devcheck` instead of shipping unnoticed — the grep still catches a badge written in a shape the check skips
273
273
  - [ ] GH issues addressed by this work commented with what landed (if working from GH issues)
274
274
  - [ ] Docs updated for any new or changed features
275
275
  - [ ] Changelog authored at `changelog/<major.minor>.x/<version>.md`
@@ -4,7 +4,7 @@ description: >
4
4
  Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.7"
7
+ version: "2.8"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -172,7 +172,7 @@ Apply the findings from Steps 3 and 4. Framework changes and third-party library
172
172
 
173
173
  The consumer opted into the framework; its templates, skills, scripts, linter rules, conventions, and new APIs that supersede local code are authoritative. Adopt them now — not as a follow-up.
174
174
 
175
- - **Synced skill content from Phase A** — `git diff framework-skills/` for every skill that was updated. Each updated body is new framework guidance; apply it to matching surfaces in this server. Examples: `add-tool` gains a section on output formatting → audit existing tool definitions against that section; `api-errors` documents a new contract pattern → adopt across error surfaces; `security-pass` adds a new check → run it against the surface. Skill updates aren't metadata.
175
+ - **Synced skill content from Phase A** — `git diff framework-skills/` for every skill that was updated. Each updated body is new framework guidance; apply it to matching surfaces in this server. Examples: `add-tool` gains a section on output formatting → audit existing tool definitions against that section; `api-errors` documents a new contract pattern → adopt across error surfaces; `security-pass` adds a new check → run it against the surface; `polish-docs-meta/references/readme.md` changes → re-audit `README.md` against it section by section (structure, Features shape, hosted callout) and restructure what no longer matches. Skill updates aren't metadata.
176
176
  - **Breaking changes** — fix call sites. Not optional.
177
177
  - **Deprecations** — migrate now, while context is fresh.
178
178
  - **New linter rules** — if the rule now flags existing code, fix the code; don't silence the rule.
@@ -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.15"
7
+ version: "2.17"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -204,7 +204,7 @@ If the project ships as an `.mcpb` bundle for Claude Desktop (check for `manifes
204
204
  **`package.json` scripts:**
205
205
 
206
206
  - `bundle` — builds the `.mcpb` (`mcpb pack`, then `scripts/clean-mcpb.ts` prunes dev deps and strips dependency-shipped agent docs)
207
- - `lint:packaging` — validates `manifest.json` ↔ `server.json` env var consistency (run by `devcheck`)
207
+ - `lint:packaging` — validates `manifest.json` ↔ `server.json` env var consistency, plus the version-parity checks below (run by `devcheck`, which gates the step on `manifest.json`, a plugin manifest, `.mcpbignore`, or `README.md`)
208
208
 
209
209
  **Cross-file consistency:**
210
210
 
@@ -219,8 +219,9 @@ If the project ships as an `.mcpb` bundle for Claude Desktop (check for `manifes
219
219
  - 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`)
220
220
  - `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.
221
221
 
222
- **README install badges:**
222
+ **README badges:**
223
223
 
224
+ - The static version badge (`img.shields.io/badge/Version-<x.y.z>-`) must carry the `package.json` `version` — `lint:packaging` enforces the match, and errors on a badge whose segment is not a readable version. shields.io escapes a literal `-` as `--`, so a prerelease is written `Version-0.14.0--rc.1-`. A live `img.shields.io/npm/v/<pkg>` badge cannot drift and is skipped
224
225
  - If `manifest.json` exists, the README should include the Claude Desktop install badge linking to `releases/latest/download/<name>.mcpb`
225
226
  - If the package is published to npm, include Cursor and VS Code install badges
226
227
  - See `references/readme.md` for badge format and config generation commands
@@ -123,18 +123,22 @@ If a public hosted instance is available, **promote it to a top-level callout**
123
123
 
124
124
  Keep the full connection-config JSON block inside a `### Public Hosted Instance` subsection under Getting Started (covered below). This callout is just the visibility pointer.
125
125
 
126
+ No hosted instance → omit the callout, the Getting Started subsection, and any hosted mention in the Overview. Never state the absence ("no public hosted instance"); self-hosting is the default a reader already assumes.
127
+
126
128
  ### Overview
127
129
 
128
130
  The first section after the header rule. Two parts: a short description paragraph, then one two-column table per primitive type. This is what a visitor reads to decide whether the server is for them, so it must scan in one screen.
129
131
 
130
- **Description paragraph:** two or three sentences — what the server sits on top of (the upstream APIs), what a user can do with it (the headline workflows, action verbs), and how it runs (transports, the hosted endpoint if any). Not a count, and not "an MCP server that…" framing.
132
+ **Description paragraph:** two or three sentences — what the server sits on top of (the upstream APIs), what a user can do with it (the headline workflows, action verbs), and how it runs (transports, plus the hosted endpoint when one exists). Not a count.
133
+
134
+ The opening sentence names the subject, not the container. The reader is already on an MCP server's repo page — the `<h1>`, the badge row, and the framework line all say so — so an opener that restates it ("An MCP server over…", "An MCP calculator…", "An MCP server that…") spends the most-read sentence on nothing. Lead with the domain or the upstream as a noun phrase, article optional: "Calculator powered by math.js.", "Seismic data from USGS ComCat and the EMSC SeismicPortal.", "The Acme v2 API — projects, tasks, and team activity.", "Read, write, and search Obsidian vault notes over the Local REST API plugin." The words "MCP server" appear at most once in the paragraph, and never as its first noun.
131
135
 
132
136
  **Primitive tables:** a `### Tools` table, then `### Resources` and `### Prompts` tables when the server has any. Omit a heading whose table would be empty, but keep a one-row table rather than folding it into another. Two columns, Name/Description, one-line descriptions — the detail lives in the Capability reference.
133
137
 
134
138
  ```markdown
135
139
  ## Overview
136
140
 
137
- An MCP server over the Acme v2 API. Search projects, manage tasks, and track team activity from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
141
+ Project management over the Acme v2 API. Search projects, manage tasks, and track team activity from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
138
142
 
139
143
  ### Tools
140
144
 
@@ -201,12 +205,12 @@ Link an examples file from an entry when one exists: `[View detailed examples](.
201
205
 
202
206
  ### Features
203
207
 
204
- A one-sentence framework line naming what a user gets from the framework (transports, auth, storage, observability not how the code is organized; contributor facts belong in the Development guide), then two bullet groups: domain-specific capabilities, then agent-friendly output design.
208
+ The framework line below, verbatim — it names what a user gets from the framework (transports, auth, storage, observability), so it replaces any framework-feature bullets; contributor facts about code organization belong in the Development guide. Then two labeled bullet groups, both always present: `<Upstream>-specific:` (3–5 bullets on the server's own integration), then `Agent-friendly output:`.
205
209
 
206
210
  ```markdown
207
211
  ## Features
208
212
 
209
- Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
213
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
210
214
 
211
215
  Acme-specific:
212
216
 
@@ -221,7 +225,7 @@ Agent-friendly output:
221
225
  - Discriminated output contracts — typed status and source fields let callers branch on data, not string parsing
222
226
  ```
223
227
 
224
- 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:
228
+ The **Agent-friendly output** subsection documents output-design choices that make the server work well as an AI-agent backend. Always include it, with 2–4 bullets in the "Pattern concrete detail" form, each naming fields or behavior verified in the server's source — not aspirational framework capabilities. Drop a pattern the server doesn't have (no batch tools → no partial-failure bullet) rather than claiming it. Examples of what fits:
225
229
 
226
230
  - Provenance: source labels (`viaSource`, `source`), license/access-level fields, effective-query echo, best-effort warnings on lossy tiers
227
231
  - Partial failure: per-item status in batch operations, structured error rows alongside successes, recovery hints ("Next Step" text)
@@ -434,10 +438,10 @@ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `
434
438
 
435
439
  ### Cloudflare Workers
436
440
 
437
- 1. **Build the Worker bundle:**
441
+ 1. **Run locally under wrangler:**
438
442
 
439
443
  \`\`\`sh
440
- bun run build:worker
444
+ bun run deploy:dev
441
445
  \`\`\`
442
446
 
443
447
  2. **Deploy:**
@@ -447,7 +451,7 @@ bun run deploy:prod
447
451
  \`\`\`
448
452
  ```
449
453
 
450
- Include the Docker or Workers subsection only if the server supports it. The Docker trailing paragraph (log directory, OTEL build arg) is important — it documents Dockerfile behavior that isn't obvious from the build command.
454
+ Include the Docker subsection only if the server ships a Dockerfile, and the Workers subsection only if it ships a `src/worker.ts` entry. The Docker trailing paragraph (log directory, OTEL build arg) is important — it documents Dockerfile behavior that isn't obvious from the build command.
451
455
 
452
456
  ### Project Structure
453
457