@cyanheads/mcp-ts-core 0.12.9 → 0.13.1

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 (126) hide show
  1. package/AGENTS.md +22 -11
  2. package/CLAUDE.md +22 -11
  3. package/README.md +1 -1
  4. package/biome.json +1 -1
  5. package/changelog/0.13.x/0.13.0.md +48 -0
  6. package/changelog/0.13.x/0.13.1.md +56 -0
  7. package/changelog/template.md +7 -24
  8. package/{tsconfig.base.json → config/tsconfig.base.json} +2 -2
  9. package/dist/cli/init.js +2 -2
  10. package/dist/cli/init.js.map +1 -1
  11. package/dist/config/envValue.d.ts +18 -0
  12. package/dist/config/envValue.d.ts.map +1 -0
  13. package/dist/config/envValue.js +35 -0
  14. package/dist/config/envValue.js.map +1 -0
  15. package/dist/config/index.d.ts +8 -0
  16. package/dist/config/index.d.ts.map +1 -1
  17. package/dist/config/index.js +13 -7
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/config/parseEnvConfig.d.ts +7 -0
  20. package/dist/config/parseEnvConfig.d.ts.map +1 -1
  21. package/dist/config/parseEnvConfig.js +9 -1
  22. package/dist/config/parseEnvConfig.js.map +1 -1
  23. package/dist/core/app.d.ts +82 -2
  24. package/dist/core/app.d.ts.map +1 -1
  25. package/dist/core/app.js +129 -6
  26. package/dist/core/app.js.map +1 -1
  27. package/dist/core/index.d.ts +1 -0
  28. package/dist/core/index.d.ts.map +1 -1
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/worker.d.ts +6 -1
  31. package/dist/core/worker.d.ts.map +1 -1
  32. package/dist/core/worker.js.map +1 -1
  33. package/dist/linter/rules/resource-rules.js +9 -2
  34. package/dist/linter/rules/resource-rules.js.map +1 -1
  35. package/dist/linter/rules/tool-rules.js +4 -1
  36. package/dist/linter/rules/tool-rules.js.map +1 -1
  37. package/dist/linter/validate.js +2 -2
  38. package/dist/linter/validate.js.map +1 -1
  39. package/dist/mcp-server/types.d.ts +10 -3
  40. package/dist/mcp-server/types.d.ts.map +1 -1
  41. package/dist/mcp-server/types.js +4 -3
  42. package/dist/mcp-server/types.js.map +1 -1
  43. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  44. package/dist/services/canvas/core/sqlGate.js +27 -2
  45. package/dist/services/canvas/core/sqlGate.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/README.md +40 -0
  54. package/{skills → framework-skills}/add-app-tool/SKILL.md +2 -2
  55. package/{skills → framework-skills}/add-resource/SKILL.md +2 -2
  56. package/{skills → framework-skills}/add-service/SKILL.md +2 -2
  57. package/{skills → framework-skills}/add-test/SKILL.md +2 -2
  58. package/{skills → framework-skills}/add-tool/SKILL.md +5 -5
  59. package/{skills → framework-skills}/api-config/SKILL.md +21 -3
  60. package/{skills → framework-skills}/api-context/SKILL.md +5 -3
  61. package/{skills → framework-skills}/api-linter/SKILL.md +4 -4
  62. package/{skills → framework-skills}/api-telemetry/SKILL.md +13 -10
  63. package/{skills → framework-skills}/code-simplifier/SKILL.md +12 -6
  64. package/{skills → framework-skills}/design-mcp-server/SKILL.md +2 -2
  65. package/{skills → framework-skills}/maintenance/SKILL.md +30 -21
  66. package/{skills → framework-skills}/orchestrations/SKILL.md +2 -2
  67. package/{skills → framework-skills}/orchestrations/workflows/field-test-fix.md +8 -8
  68. package/{skills → framework-skills}/orchestrations/workflows/fix-wrapup-release.md +5 -5
  69. package/{skills → framework-skills}/orchestrations/workflows/greenfield-build.md +11 -11
  70. package/{skills → framework-skills}/orchestrations/workflows/maintenance-release.md +12 -12
  71. package/{skills → framework-skills}/polish-docs-meta/SKILL.md +18 -10
  72. package/{skills → framework-skills}/polish-docs-meta/references/agent-protocol.md +1 -1
  73. package/{skills → framework-skills}/polish-docs-meta/references/readme.md +93 -73
  74. package/{skills → framework-skills}/release-and-publish/SKILL.md +12 -3
  75. package/{skills → framework-skills}/release-pr-review/SKILL.md +2 -2
  76. package/{skills → framework-skills}/report-issue-framework/SKILL.md +26 -25
  77. package/{skills → framework-skills}/report-issue-local/SKILL.md +28 -24
  78. package/{skills → framework-skills}/setup/SKILL.md +10 -8
  79. package/package.json +13 -13
  80. package/scripts/build.ts +2 -2
  81. package/scripts/check-framework-antipatterns.ts +1 -1
  82. package/scripts/check-skill-versions.ts +16 -9
  83. package/scripts/check-skills-sync.ts +64 -13
  84. package/scripts/clean-mcpb.ts +3 -3
  85. package/scripts/devcheck.ts +18 -15
  86. package/scripts/lint-packaging.ts +158 -24
  87. package/scripts/list-skills.ts +2 -2
  88. package/templates/.claude-plugin/plugin.json +5 -1
  89. package/templates/.env.example +5 -2
  90. package/templates/.github/CONTRIBUTING.md +4 -5
  91. package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +5 -4
  92. package/templates/.github/ISSUE_TEMPLATE/config.yml +6 -1
  93. package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +1 -2
  94. package/templates/AGENTS.md +31 -14
  95. package/templates/CLAUDE.md +31 -14
  96. package/templates/_.mcpbignore +1 -1
  97. package/templates/changelog/template.md +7 -24
  98. package/templates/package.json +3 -2
  99. package/templates/src/index.ts +10 -0
  100. package/templates/src/mcp-server/resources/definitions/echo-app-ui.app-resource.ts +1 -1
  101. package/skills/README.md +0 -38
  102. /package/{skills → framework-skills}/add-export/SKILL.md +0 -0
  103. /package/{skills → framework-skills}/add-prompt/SKILL.md +0 -0
  104. /package/{skills → framework-skills}/add-provider/SKILL.md +0 -0
  105. /package/{skills → framework-skills}/api-auth/SKILL.md +0 -0
  106. /package/{skills → framework-skills}/api-canvas/SKILL.md +0 -0
  107. /package/{skills → framework-skills}/api-errors/SKILL.md +0 -0
  108. /package/{skills → framework-skills}/api-mirror/SKILL.md +0 -0
  109. /package/{skills → framework-skills}/api-services/SKILL.md +0 -0
  110. /package/{skills → framework-skills}/api-services/references/graph.md +0 -0
  111. /package/{skills → framework-skills}/api-services/references/llm.md +0 -0
  112. /package/{skills → framework-skills}/api-services/references/speech.md +0 -0
  113. /package/{skills → framework-skills}/api-testing/SKILL.md +0 -0
  114. /package/{skills → framework-skills}/api-utils/SKILL.md +0 -0
  115. /package/{skills → framework-skills}/api-utils/references/formatting.md +0 -0
  116. /package/{skills → framework-skills}/api-utils/references/parsing.md +0 -0
  117. /package/{skills → framework-skills}/api-utils/references/security.md +0 -0
  118. /package/{skills → framework-skills}/api-workers/SKILL.md +0 -0
  119. /package/{skills → framework-skills}/field-test/SKILL.md +0 -0
  120. /package/{skills → framework-skills}/git-wrapup/SKILL.md +0 -0
  121. /package/{skills → framework-skills}/polish-docs-meta/references/package-meta.md +0 -0
  122. /package/{skills → framework-skills}/polish-docs-meta/references/server-json.md +0 -0
  123. /package/{skills → framework-skills}/security-pass/SKILL.md +0 -0
  124. /package/{skills → framework-skills}/techniques/SKILL.md +0 -0
  125. /package/{skills → framework-skills}/techniques/references/outline-on-overflow.md +0 -0
  126. /package/{skills → framework-skills}/tool-defs-analysis/SKILL.md +0 -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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"}
@@ -0,0 +1,40 @@
1
+ # Framework skills
2
+
3
+ Agent Skills for `@cyanheads/mcp-ts-core`. Each subdirectory contains a `SKILL.md` following the [Agent Skills specification](https://agentskills.io/specification).
4
+
5
+ The directory is `framework-skills/`, not `skills/`, on purpose. Claude Code and Codex auto-load a plugin's root `skills/`, and these are development-time skills for building a server — not skills for the agents that use one. A server that ships a plugin manifest keeps `skills/` free for that second kind.
6
+
7
+ ## Three-Tier Distribution
8
+
9
+ Skills flow through three locations. Each tier has a distinct role:
10
+
11
+ | Tier | Location | Written by | Purpose |
12
+ |:-----|:---------|:-----------|:--------|
13
+ | 1. Package | `node_modules/@cyanheads/mcp-ts-core/framework-skills/` | `npm publish` / `bun publish` | Canonical source. Ships with the package. |
14
+ | 2. Project | `framework-skills/` (project root) | `@cyanheads/mcp-ts-core init` CLI | Project's source of truth. Committed to git. Server-specific skills live here too. |
15
+ | 3. Agent | `.claude/skills/`, `.codex/skills/`, etc. | The agent itself | Agent's working copy. Synced from project `framework-skills/`. Checklists are checked here. |
16
+
17
+ ### Flow
18
+
19
+ ```text
20
+ npm publish init CLI agent sync
21
+ [package framework-skills/] ──────────> [project framework-skills/] ──────────> [.claude/skills/]
22
+ │
23
+ ├── core skills (from package)
24
+ └── server-specific skills (added by devs)
25
+ ```
26
+
27
+ ## Audience
28
+
29
+ Each skill declares `metadata.audience` in its SKILL.md frontmatter:
30
+
31
+ - **`external`** — For consumers building MCP servers. Copied to project `framework-skills/` by `init`.
32
+ - **`internal`** — For core package developers. Stays in `node_modules`, not copied.
33
+
34
+ ## Versioning
35
+
36
+ Skills declare `metadata.version` in frontmatter. The `maintenance` skill's Phase A compares versions after `bun update` and replaces a skill directory when the package version is newer; `init` only fills in what is missing and never overwrites an existing file. To pin a skill against those replacements, bump its local `metadata.version` above the package's.
37
+
38
+ ## Adding Server-Specific Skills
39
+
40
+ Create a new directory in `framework-skills/` with a `SKILL.md` following the same format. The agent will pick it up on next sync. Use the core skills as examples for structure and checklist conventions.
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold an MCP App tool + UI resource pair. Use when the user asks to add a tool with interactive UI, create an MCP App, or build a visual/interactive tool.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -117,7 +117,7 @@ const APP_HTML = `<!DOCTYPE html>
117
117
  applyDocumentTheme,
118
118
  applyHostFonts,
119
119
  applyHostStyleVariables,
120
- } from "https://unpkg.com/@modelcontextprotocol/ext-apps@1/app-with-deps";
120
+ } from "https://unpkg.com/@modelcontextprotocol/ext-apps@2/app-with-deps";
121
121
 
122
122
  const app = new App({ name: "{{TOOL_TITLE}}", version: "1.0.0" });
123
123
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP resource definition. Use when the user asks to add a resource, expose data via URI, or create a readable endpoint.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.5"
7
+ version: "1.6"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -139,7 +139,7 @@ export const articleResource = resource('article://{pmid}', {
139
139
  });
140
140
  ```
141
141
 
142
- Without `errors[]`, the handler receives plain `Context` (no `fail` method) and throws via error factories (`notFound`, `serviceUnavailable`, …) directly. The contract is opt-in. See `skills/api-errors/SKILL.md` for the full pattern, baseline codes, and conformance rules.
142
+ Without `errors[]`, the handler receives plain `Context` (no `fail` method) and throws via error factories (`notFound`, `serviceUnavailable`, …) directly. The contract is opt-in. See `framework-skills/api-errors/SKILL.md` for the full pattern, baseline codes, and conformance rules.
143
143
 
144
144
  ### URI template variable completion
145
145
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new service integration. Use when the user asks to add a service, integrate an external API, or create a reusable domain module with its own initialization and state.
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
  ---
@@ -95,7 +95,7 @@ handler: async (input, ctx) => {
95
95
 
96
96
  ## Resilience (External API Services)
97
97
 
98
- When a service wraps an external API, apply these patterns. For the framework retry contract, see `skills/api-utils/SKILL.md`.
98
+ When a service wraps an external API, apply these patterns. For the framework retry contract, see `framework-skills/api-utils/SKILL.md`.
99
99
 
100
100
  ### Retry wraps the full pipeline
101
101
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a test file for an existing tool, resource, or service. Use when the user asks to add tests, improve coverage, or when a definition exists without a matching test file.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.6"
7
+ version: "1.7"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -15,7 +15,7 @@ Tests use Vitest and `createMockContext` from `@cyanheads/mcp-ts-core/testing`.
15
15
 
16
16
  For the full `createMockContext` API and testing patterns, read:
17
17
 
18
- skills/api-testing/SKILL.md
18
+ framework-skills/api-testing/SKILL.md
19
19
 
20
20
  ## Steps
21
21
 
@@ -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.23"
7
+ version: "2.25"
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';
@@ -169,7 +169,7 @@ export const {{TOOL_EXPORT}} = tool('{{tool_name}}', {
169
169
  });
170
170
  ```
171
171
 
172
- Write it as `return ctx.requestInput(...)` — the `never` return type makes it valid in return position for any output, and it is what lets TypeScript narrow the line below. Full reference (`inputRequired.elicitUrl` / `.createMessage` / `.listRoots`, `requestState`, decline handling): `skills/api-context`.
172
+ Write it as `return ctx.requestInput(...)` — the `never` return type makes it valid in return position for any output, and it is what lets TypeScript narrow the line below. Full reference (`inputRequired.elicitUrl` / `.createMessage` / `.listRoots`, `requestState`, decline handling): `framework-skills/api-context`.
173
173
 
174
174
  ### Registration
175
175
 
@@ -413,7 +413,7 @@ async handler(input, ctx) {
413
413
  },
414
414
  ```
415
415
 
416
- The alternative — declaring `previewData: z.string()` in `output` and emitting the block from `format()` — ships the bytes twice (once in `structuredContent`, once in the block). Reserve `output` for data the agent reasons over; route raw media through `ctx.content`. Test with `getContentBlocks(ctx)`. Full reference: `skills/api-context` § `ctx.content`.
416
+ The alternative — declaring `previewData: z.string()` in `output` and emitting the block from `format()` — ships the bytes twice (once in `structuredContent`, once in the block). Reserve `output` for data the agent reasons over; route raw media through `ctx.content`. Test with `getContentBlocks(ctx)`. Full reference: `framework-skills/api-context` § `ctx.content`.
417
417
 
418
418
  ### Capped lists must disclose truncation
419
419
 
@@ -714,7 +714,7 @@ throw invalidParams(
714
714
  );
715
715
  ```
716
716
 
717
- **Error messages are recovery instructions.** Name what went wrong, why, and what action to take. The message is the agent's only signal — a bare "Not found" is a dead end. See `skills/api-errors/SKILL.md` for the full contract pattern, factories list, auto-classification table, and error-path parity (how `data.recovery.hint` reaches both client surfaces).
717
+ **Error messages are recovery instructions.** Name what went wrong, why, and what action to take. The message is the agent's only signal — a bare "Not found" is a dead end. See `framework-skills/api-errors/SKILL.md` for the full contract pattern, factories list, auto-classification table, and error-path parity (how `data.recovery.hint` reaches both client surfaces).
718
718
 
719
719
  ### Include operational metadata
720
720
 
@@ -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.16"
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 |
@@ -262,6 +278,8 @@ export function getServerConfig(): ServerConfig {
262
278
 
263
279
  **Env booleans — use `z.stringbool()`, never `z.coerce.boolean()`.** `z.coerce.boolean()` runs `Boolean(value)`, so `"false"`, `"0"`, and `"no"` all coerce to `true` — the flag becomes impossible to disable through the environment except by omitting it entirely. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` (case-insensitive) and rejects anything else, so `MY_VERBOSE_LOGGING=false` actually disables and a typo fails loudly at startup instead of silently coercing. Empty string and unset both fall through to `.default()`.
264
280
 
281
+ **Unset means unset.** `parseEnvConfig` and the framework's own config both treat an empty string and a whole-value `${…}` placeholder — what an MCPB or plugin host forwards when a user leaves an option blank and nothing substitutes it — as the variable being absent: an optional field stays `undefined`, a defaulted field takes its default, and a required field fails as missing rather than as a format error against the literal text. A value that merely contains `${…}` is kept. No per-field `z.preprocess` guard is needed for either case.
282
+
265
283
  **Why `parseEnvConfig`?** It maps Zod schema paths to env var names so validation errors name the actual variable at fault. A missing `MY_API_KEY` produces:
266
284
 
267
285
  ```
@@ -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.2"
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.
@@ -594,7 +596,7 @@ async handler(input, ctx) {
594
596
  }
595
597
  ```
596
598
 
597
- The contract is opt-in. See `skills/api-errors/SKILL.md` for the full type-driven pattern, lint rules, and baseline-codes guidance.
599
+ The contract is opt-in. See `framework-skills/api-errors/SKILL.md` for the full type-driven pattern, lint rules, and baseline-codes guidance.
598
600
 
599
601
  ---
600
602
 
@@ -737,7 +739,7 @@ async handler(input, ctx) {
737
739
 
738
740
  The `capped-list-no-truncation` lint rule fires when a cap-like input + array output shape is present without any of: `truncated` or `totalCount` in the declared `enrichment`, or `truncated` or `totalCount` in `output`. Using `ctx.enrich.total(n)` (writes `totalCount`) is also recognized as honest disclosure.
739
741
 
740
- See `add-tool`'s **Tool Response Design** and `skills/api-linter` (`enrichment-*` rules) for the full pattern. Test enrichment with `getEnrichment(ctx)` from `@cyanheads/mcp-ts-core/testing`.
742
+ See `add-tool`'s **Tool Response Design** and `framework-skills/api-linter` (`enrichment-*` rules) for the full pattern. Test enrichment with `getEnrichment(ctx)` from `@cyanheads/mcp-ts-core/testing`.
741
743
 
742
744
  ---
743
745
 
@@ -4,7 +4,7 @@ description: >
4
4
  MCP definition linter rules reference. Use when `bun run lint:mcp` or `bun run devcheck` reports a lint error or warning (`format-parity`, `schema-is-object`, `name-format`, `server-json-*`, etc.) and you need to understand the rule, its severity, and how to fix it. Every rule ID the linter emits has an entry in this doc.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.13"
7
+ version: "1.14"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -18,7 +18,7 @@ The linter validates tool, resource, and prompt definitions against the MCP spec
18
18
  | `bun run lint:mcp` | Manual or CI | Prints errors + warnings, exits non-zero on errors. |
19
19
  | `bun run devcheck` | Pre-commit workflow | Wraps `lint:mcp` alongside typecheck, format, `bun audit`, `bun outdated`. |
20
20
 
21
- Both surface the same `LintReport` from `validateDefinitions()` (exported from `@cyanheads/mcp-ts-core/linter`). Each diagnostic has a stable `rule` ID — that's the anchor you land on via the `See: skills/api-linter/SKILL.md#<rule>` breadcrumb appended to every message.
21
+ Both surface the same `LintReport` from `validateDefinitions()` (exported from `@cyanheads/mcp-ts-core/linter`). Each diagnostic has a stable `rule` ID — that's the anchor you land on via the `See: framework-skills/api-linter/SKILL.md#<rule>` breadcrumb appended to every message.
22
22
 
23
23
  **Severity:**
24
24
  - **error** — MUST-level spec violation; blocks `devcheck`.
@@ -615,7 +615,7 @@ Validate the `landing` config passed to `createApp()` (the config object that dr
615
615
  | `landing-theme-accent` | error | `theme.accent` is present but not a string |
616
616
  | `landing-theme-accent-format` | error | `theme.accent` doesn't match the expected color format |
617
617
 
618
- Diagnostic anchors for these rules are the rule ID — e.g. `skills/api-linter/SKILL.md#landing-shape`. Pass `landing` to `validateDefinitions({ landing, tools, resources, prompts })` to opt in.
618
+ Diagnostic anchors for these rules are the rule ID — e.g. `framework-skills/api-linter/SKILL.md#landing-shape`. Pass `landing` to `validateDefinitions({ landing, tools, resources, prompts })` to opt in.
619
619
 
620
620
  ---
621
621
 
@@ -692,7 +692,7 @@ throw serviceUnavailable('Upstream failed', { upstreamError: e }, { cause: e });
692
692
 
693
693
  Validate the optional `errors[]` declarative contract on tool/resource definitions. Structural rules check the shape of contract entries; conformance rules cross-check the handler body against the declared codes.
694
694
 
695
- When a contract is declared, the handler receives a typed `ctx.fail(reason, …)` keyed by the declared reason union. See `skills/api-errors/SKILL.md` for runtime semantics.
695
+ When a contract is declared, the handler receives a typed `ctx.fail(reason, …)` keyed by the declared reason union. See `framework-skills/api-errors/SKILL.md` for runtime semantics.
696
696
 
697
697
  ### error-contract-type
698
698
 
@@ -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
 
@@ -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