@cyanheads/mcp-ts-core 0.5.4 → 0.6.0

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 (142) hide show
  1. package/CLAUDE.md +39 -1
  2. package/README.md +1 -1
  3. package/changelog/0.1.x/0.1.0.md +78 -0
  4. package/changelog/0.1.x/0.1.1.md +28 -0
  5. package/changelog/0.1.x/0.1.10.md +32 -0
  6. package/changelog/0.1.x/0.1.11.md +51 -0
  7. package/changelog/0.1.x/0.1.12.md +21 -0
  8. package/changelog/0.1.x/0.1.13.md +16 -0
  9. package/changelog/0.1.x/0.1.14.md +20 -0
  10. package/changelog/0.1.x/0.1.15.md +24 -0
  11. package/changelog/0.1.x/0.1.16.md +17 -0
  12. package/changelog/0.1.x/0.1.17.md +14 -0
  13. package/changelog/0.1.x/0.1.18.md +18 -0
  14. package/changelog/0.1.x/0.1.19.md +19 -0
  15. package/changelog/0.1.x/0.1.2.md +25 -0
  16. package/changelog/0.1.x/0.1.20.md +21 -0
  17. package/changelog/0.1.x/0.1.21.md +17 -0
  18. package/changelog/0.1.x/0.1.22.md +28 -0
  19. package/changelog/0.1.x/0.1.23.md +23 -0
  20. package/changelog/0.1.x/0.1.24.md +17 -0
  21. package/changelog/0.1.x/0.1.25.md +16 -0
  22. package/changelog/0.1.x/0.1.26.md +22 -0
  23. package/changelog/0.1.x/0.1.27.md +30 -0
  24. package/changelog/0.1.x/0.1.28.md +16 -0
  25. package/changelog/0.1.x/0.1.29.md +19 -0
  26. package/changelog/0.1.x/0.1.3.md +22 -0
  27. package/changelog/0.1.x/0.1.4.md +17 -0
  28. package/changelog/0.1.x/0.1.5.md +25 -0
  29. package/changelog/0.1.x/0.1.6.md +26 -0
  30. package/changelog/0.1.x/0.1.7.md +29 -0
  31. package/changelog/0.1.x/0.1.8.md +33 -0
  32. package/changelog/0.1.x/0.1.9.md +19 -0
  33. package/changelog/0.2.x/0.2.0.md +32 -0
  34. package/changelog/0.2.x/0.2.1.md +12 -0
  35. package/changelog/0.2.x/0.2.10.md +38 -0
  36. package/changelog/0.2.x/0.2.11.md +29 -0
  37. package/changelog/0.2.x/0.2.12.md +31 -0
  38. package/changelog/0.2.x/0.2.2.md +19 -0
  39. package/changelog/0.2.x/0.2.3.md +15 -0
  40. package/changelog/0.2.x/0.2.4.md +24 -0
  41. package/changelog/0.2.x/0.2.5.md +27 -0
  42. package/changelog/0.2.x/0.2.6.md +23 -0
  43. package/changelog/0.2.x/0.2.7.md +23 -0
  44. package/changelog/0.2.x/0.2.8.md +12 -0
  45. package/changelog/0.2.x/0.2.9.md +25 -0
  46. package/changelog/0.3.x/0.3.0.md +45 -0
  47. package/changelog/0.3.x/0.3.1.md +16 -0
  48. package/changelog/0.3.x/0.3.2.md +24 -0
  49. package/changelog/0.3.x/0.3.3.md +31 -0
  50. package/changelog/0.3.x/0.3.4.md +31 -0
  51. package/changelog/0.3.x/0.3.5.md +32 -0
  52. package/changelog/0.3.x/0.3.6.md +48 -0
  53. package/changelog/0.3.x/0.3.7.md +23 -0
  54. package/changelog/0.3.x/0.3.8.md +21 -0
  55. package/changelog/0.4.x/0.4.0.md +38 -0
  56. package/changelog/0.4.x/0.4.1.md +31 -0
  57. package/changelog/0.5.x/0.5.0.md +29 -0
  58. package/changelog/0.5.x/0.5.1.md +18 -0
  59. package/changelog/0.5.x/0.5.2.md +38 -0
  60. package/changelog/0.5.x/0.5.3.md +26 -0
  61. package/changelog/0.5.x/0.5.4.md +29 -0
  62. package/changelog/0.6.x/0.6.0.md +39 -0
  63. package/changelog/unreleased.md +40 -0
  64. package/dist/cli/init.js +1 -0
  65. package/dist/cli/init.js.map +1 -1
  66. package/dist/core/app.d.ts +13 -3
  67. package/dist/core/app.d.ts.map +1 -1
  68. package/dist/core/app.js +20 -13
  69. package/dist/core/app.js.map +1 -1
  70. package/dist/core/index.d.ts +1 -0
  71. package/dist/core/index.d.ts.map +1 -1
  72. package/dist/core/index.js.map +1 -1
  73. package/dist/core/serverManifest.d.ts +237 -0
  74. package/dist/core/serverManifest.d.ts.map +1 -0
  75. package/dist/core/serverManifest.js +310 -0
  76. package/dist/core/serverManifest.js.map +1 -0
  77. package/dist/core/worker.d.ts.map +1 -1
  78. package/dist/core/worker.js +2 -2
  79. package/dist/core/worker.js.map +1 -1
  80. package/dist/linter/rules/landing-rules.d.ts +15 -0
  81. package/dist/linter/rules/landing-rules.d.ts.map +1 -0
  82. package/dist/linter/rules/landing-rules.js +125 -0
  83. package/dist/linter/rules/landing-rules.js.map +1 -0
  84. package/dist/linter/types.d.ts +5 -2
  85. package/dist/linter/types.d.ts.map +1 -1
  86. package/dist/linter/validate.d.ts.map +1 -1
  87. package/dist/linter/validate.js +5 -0
  88. package/dist/linter/validate.js.map +1 -1
  89. package/dist/mcp-server/transports/http/httpTransport.d.ts +4 -3
  90. package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
  91. package/dist/mcp-server/transports/http/httpTransport.js +47 -26
  92. package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
  93. package/dist/mcp-server/transports/http/httpTypes.d.ts +0 -12
  94. package/dist/mcp-server/transports/http/httpTypes.d.ts.map +1 -1
  95. package/dist/mcp-server/transports/http/landing-page.d.ts +48 -0
  96. package/dist/mcp-server/transports/http/landing-page.d.ts.map +1 -0
  97. package/dist/mcp-server/transports/http/landing-page.js +912 -0
  98. package/dist/mcp-server/transports/http/landing-page.js.map +1 -0
  99. package/dist/mcp-server/transports/http/serverCard.d.ts +67 -0
  100. package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -0
  101. package/dist/mcp-server/transports/http/serverCard.js +91 -0
  102. package/dist/mcp-server/transports/http/serverCard.js.map +1 -0
  103. package/dist/mcp-server/transports/manager.d.ts +3 -3
  104. package/dist/mcp-server/transports/manager.d.ts.map +1 -1
  105. package/dist/mcp-server/transports/manager.js +4 -4
  106. package/dist/mcp-server/transports/manager.js.map +1 -1
  107. package/dist/utils/formatting/html.d.ts +76 -0
  108. package/dist/utils/formatting/html.d.ts.map +1 -0
  109. package/dist/utils/formatting/html.js +111 -0
  110. package/dist/utils/formatting/html.js.map +1 -0
  111. package/dist/utils/formatting/index.d.ts +1 -0
  112. package/dist/utils/formatting/index.d.ts.map +1 -1
  113. package/dist/utils/formatting/index.js +1 -0
  114. package/dist/utils/formatting/index.js.map +1 -1
  115. package/dist/utils/index.d.ts +1 -1
  116. package/dist/utils/index.d.ts.map +1 -1
  117. package/dist/utils/index.js +1 -1
  118. package/dist/utils/index.js.map +1 -1
  119. package/package.json +5 -1
  120. package/scripts/build-changelog.ts +222 -0
  121. package/scripts/devcheck.ts +16 -1
  122. package/scripts/tree.ts +3 -0
  123. package/skills/add-app-tool/SKILL.md +2 -4
  124. package/skills/add-prompt/SKILL.md +2 -4
  125. package/skills/add-resource/SKILL.md +2 -4
  126. package/skills/add-service/SKILL.md +2 -4
  127. package/skills/add-tool/SKILL.md +6 -5
  128. package/skills/api-context/SKILL.md +2 -2
  129. package/skills/api-services/SKILL.md +1 -1
  130. package/skills/api-services/references/graph.md +1 -1
  131. package/skills/api-utils/SKILL.md +1 -1
  132. package/skills/api-utils/references/parsing.md +1 -1
  133. package/skills/api-utils/references/security.md +1 -1
  134. package/skills/design-mcp-server/SKILL.md +2 -2
  135. package/skills/maintenance/SKILL.md +12 -11
  136. package/skills/polish-docs-meta/SKILL.md +24 -9
  137. package/skills/release/SKILL.md +21 -7
  138. package/skills/setup/SKILL.md +4 -8
  139. package/templates/AGENTS.md +23 -1
  140. package/templates/CLAUDE.md +23 -1
  141. package/templates/changelog/unreleased.md +40 -0
  142. package/templates/package.json +3 -0
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview Generate CHANGELOG.md as a navigation index from per-version files.
4
+ *
5
+ * Source of truth: `changelog/<major.minor>.x/<version>.md` — each file opens with
6
+ * YAML frontmatter declaring:
7
+ * • summary (required) — ≤250-char headline, no markdown, one line
8
+ * • breaking (optional) — `true` flags releases with breaking changes
9
+ *
10
+ * The rollup is a thin **index**, not a copy of bodies — each entry is just a
11
+ * clickable header + one-line summary. Full content stays in the per-version files.
12
+ *
13
+ * Rendered rollup entry:
14
+ * ## [X.Y.Z](changelog/N.N.x/X.Y.Z.md) — YYYY-MM-DD · ⚠️ Breaking
15
+ *
16
+ * <summary>
17
+ *
18
+ * (The `· ⚠️ Breaking` badge only appears when `breaking: true`.)
19
+ *
20
+ * Modes:
21
+ * • default → regenerate CHANGELOG.md
22
+ * • --check → exit 1 if CHANGELOG.md differs from what would be generated
23
+ *
24
+ * Missing `summary`: warning (not failure) — the entry renders header-only.
25
+ * Summary > 250 chars, or malformed `breaking`: hard error.
26
+ *
27
+ * @module scripts/build-changelog
28
+ */
29
+
30
+ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
31
+ import { resolve } from 'node:path';
32
+ import process from 'node:process';
33
+
34
+ const CHANGELOG_DIR = resolve('changelog');
35
+ const CHANGELOG_PATH = resolve('CHANGELOG.md');
36
+ const EXCLUDED_FILES = new Set(['unreleased.md', 'README.md']);
37
+ const SERIES_PATTERN = /^\d+\.\d+\.x$/;
38
+ const SUMMARY_MAX_LENGTH = 250;
39
+
40
+ const HEADER = `# Changelog
41
+
42
+ All notable changes to this project. Each entry links to its full per-version file in [changelog/](changelog/).
43
+ `;
44
+
45
+ interface VersionEntry {
46
+ path: string;
47
+ series: string;
48
+ version: string;
49
+ }
50
+
51
+ interface Frontmatter {
52
+ breaking: boolean;
53
+ summary: string | null;
54
+ }
55
+
56
+ /**
57
+ * Semver descending compare. Final releases rank above their prereleases
58
+ * (`0.6.0 > 0.6.0-rc.1 > 0.6.0-beta.1`). Prereleases compared lexicographically.
59
+ */
60
+ function compareSemverDesc(a: string, b: string): number {
61
+ const parse = (v: string): [number[], string | null] => {
62
+ const [base, pre = null] = v.split('-', 2) as [string, string | undefined];
63
+ return [base.split('.').map(Number), pre ?? null];
64
+ };
65
+ const [aParts, aPre] = parse(a);
66
+ const [bParts, bPre] = parse(b);
67
+ for (let i = 0; i < 3; i++) {
68
+ const diff = (bParts[i] ?? 0) - (aParts[i] ?? 0);
69
+ if (diff !== 0) return diff;
70
+ }
71
+ if (aPre === bPre) return 0;
72
+ if (aPre === null) return -1;
73
+ if (bPre === null) return 1;
74
+ return bPre.localeCompare(aPre);
75
+ }
76
+
77
+ /**
78
+ * Parse minimal YAML frontmatter. Only recognizes `summary` and `breaking` —
79
+ * other keys are ignored, so the format stays extensible without touching the
80
+ * parser. Throws on malformed values we actually care about.
81
+ */
82
+ function parseFrontmatter(content: string, fileLabel: string): Frontmatter {
83
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?/);
84
+ if (!match) return { summary: null, breaking: false };
85
+
86
+ const block = match[1] as string;
87
+
88
+ // summary: quoted or bare, single line
89
+ let summary: string | null = null;
90
+ const summaryMatch = block.match(/^summary:\s*(.*)$/m);
91
+ if (summaryMatch) {
92
+ let raw = (summaryMatch[1] ?? '').trim();
93
+ if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
94
+ raw = raw.slice(1, -1);
95
+ }
96
+ summary = raw.length > 0 ? raw : null;
97
+ }
98
+ if (summary !== null && summary.length > SUMMARY_MAX_LENGTH) {
99
+ throw new Error(
100
+ `${fileLabel}: summary is ${summary.length} chars, exceeds cap of ${SUMMARY_MAX_LENGTH}. Keep it tight — headline, not paragraph.`,
101
+ );
102
+ }
103
+
104
+ // breaking: must be literal true/false if present
105
+ let breaking = false;
106
+ const breakingMatch = block.match(/^breaking:\s*(\S+)\s*$/m);
107
+ if (breakingMatch) {
108
+ const val = breakingMatch[1];
109
+ if (val !== 'true' && val !== 'false') {
110
+ throw new Error(`${fileLabel}: breaking must be 'true' or 'false', got '${val}'.`);
111
+ }
112
+ breaking = val === 'true';
113
+ }
114
+
115
+ return { summary, breaking };
116
+ }
117
+
118
+ /** Extract the release date from the H1 heading. */
119
+ function extractDate(body: string, fileLabel: string): string {
120
+ const match = body.match(/^#\s+\S+\s+[—–-]\s+(\d{4}-\d{2}-\d{2})/m);
121
+ if (!match) {
122
+ throw new Error(
123
+ `${fileLabel}: H1 heading missing or malformed. Expected '# <version> — YYYY-MM-DD'.`,
124
+ );
125
+ }
126
+ return match[1] as string;
127
+ }
128
+
129
+ function renderEntry(entry: VersionEntry, fm: Frontmatter, date: string): string {
130
+ const link = `changelog/${entry.series}/${entry.version}.md`;
131
+ const breakingBadge = fm.breaking ? ' · ⚠️ Breaking' : '';
132
+ const header = `## [${entry.version}](${link}) — ${date}${breakingBadge}`;
133
+ if (fm.summary) {
134
+ return `${header}\n\n${fm.summary}\n`;
135
+ }
136
+ return `${header}\n`;
137
+ }
138
+
139
+ function collectVersionFiles(): VersionEntry[] {
140
+ const entries: VersionEntry[] = [];
141
+ for (const entry of readdirSync(CHANGELOG_DIR, { withFileTypes: true })) {
142
+ if (!entry.isDirectory() || !SERIES_PATTERN.test(entry.name)) continue;
143
+ const seriesDir = resolve(CHANGELOG_DIR, entry.name);
144
+ for (const file of readdirSync(seriesDir)) {
145
+ if (!file.endsWith('.md') || EXCLUDED_FILES.has(file)) continue;
146
+ entries.push({
147
+ version: file.replace(/\.md$/, ''),
148
+ series: entry.name,
149
+ path: resolve(seriesDir, file),
150
+ });
151
+ }
152
+ }
153
+ return entries.sort((a, b) => compareSemverDesc(a.version, b.version));
154
+ }
155
+
156
+ function buildRollup(): { content: string; missingSummary: string[] } {
157
+ const entries = collectVersionFiles();
158
+
159
+ if (entries.length === 0) {
160
+ throw new Error(`No per-version changelog files found under ${CHANGELOG_DIR}/<major.minor>.x/`);
161
+ }
162
+
163
+ const missingSummary: string[] = [];
164
+ const sections: string[] = [];
165
+
166
+ for (const entry of entries) {
167
+ const fileLabel = `changelog/${entry.series}/${entry.version}.md`;
168
+ const content = readFileSync(entry.path, 'utf-8');
169
+ const fm = parseFrontmatter(content, fileLabel);
170
+ const date = extractDate(content, fileLabel);
171
+
172
+ if (!fm.summary) {
173
+ missingSummary.push(fileLabel);
174
+ }
175
+
176
+ sections.push(renderEntry(entry, fm, date));
177
+ }
178
+
179
+ return {
180
+ content: `${HEADER}\n${sections.join('\n')}`,
181
+ missingSummary,
182
+ };
183
+ }
184
+
185
+ function reportMissingSummaries(missing: string[]): void {
186
+ if (missing.length === 0) return;
187
+ const shown = missing.slice(0, 10);
188
+ const extra = missing.length - shown.length;
189
+ console.warn(`\nWarning: ${missing.length} file(s) missing 'summary' frontmatter:`);
190
+ for (const file of shown) console.warn(` - ${file}`);
191
+ if (extra > 0) console.warn(` ... and ${extra} more`);
192
+ console.warn(`\nBackfill these — see CLAUDE.md § Changelog for the frontmatter format.`);
193
+ }
194
+
195
+ function main(): void {
196
+ const checkOnly = process.argv.includes('--check');
197
+ const { content: generated, missingSummary } = buildRollup();
198
+
199
+ if (checkOnly) {
200
+ let existing = '';
201
+ try {
202
+ existing = readFileSync(CHANGELOG_PATH, 'utf-8');
203
+ } catch {
204
+ // missing file counts as drift
205
+ }
206
+ if (existing === generated) {
207
+ console.log('CHANGELOG.md is in sync with changelog/ directory.');
208
+ reportMissingSummaries(missingSummary);
209
+ process.exit(0);
210
+ }
211
+ console.error('CHANGELOG.md is out of sync with changelog/ directory.');
212
+ console.error('Fix: run `bun run changelog:build` to regenerate.');
213
+ reportMissingSummaries(missingSummary);
214
+ process.exit(1);
215
+ }
216
+
217
+ writeFileSync(CHANGELOG_PATH, generated);
218
+ console.log(`Wrote ${CHANGELOG_PATH} (${generated.split('\n').length - 1} lines).`);
219
+ reportMissingSummaries(missingSummary);
220
+ }
221
+
222
+ main();
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { type ChildProcess, spawn, spawnSync } from 'node:child_process';
3
- import { readFileSync } from 'node:fs';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
4
  import * as path from 'node:path';
5
5
  import process from 'node:process';
6
6
  import { fileURLToPath } from 'node:url';
@@ -421,6 +421,21 @@ const ALL_CHECKS: Check[] = [
421
421
  tip: (c) =>
422
422
  `Edit both files together, or run ${c.bold('cp CLAUDE.md AGENTS.md')} (or reverse) to resync.`,
423
423
  },
424
+ {
425
+ name: 'Changelog Sync',
426
+ flag: '--no-changelog-sync',
427
+ canFix: false,
428
+ // --check exits non-zero if CHANGELOG.md drifts from changelog/*.md.
429
+ // Skipped cleanly when neither CHANGELOG.md nor changelog/ exists (non-mcp-ts-core projects).
430
+ getCommand: () => {
431
+ const hasChangelog = existsSync(path.join(ROOT_DIR, 'changelog'));
432
+ const hasRollup = existsSync(path.join(ROOT_DIR, 'CHANGELOG.md'));
433
+ if (!hasChangelog && !hasRollup) return null;
434
+ return ['bun', 'run', 'scripts/build-changelog.ts', '--check'];
435
+ },
436
+ tip: (c) =>
437
+ `Edit the per-version file in ${c.bold('changelog/')} and run ${c.bold('bun run changelog:build')} to regenerate ${c.bold('CHANGELOG.md')}.`,
438
+ },
424
439
  {
425
440
  name: 'Biome',
426
441
  flag: '--no-lint',
package/scripts/tree.ts CHANGED
@@ -39,6 +39,9 @@ const DEFAULT_IGNORE_PATTERNS: string[] = [
39
39
  'coverage',
40
40
  'logs',
41
41
  '.husky/_',
42
+ // Directory-based changelog — keep series dirs visible (0.1.x/, 0.5.x/) for structural
43
+ // orientation, but collapse out the per-version files. Top-level unreleased.md stays.
44
+ 'changelog/*/*.md',
42
45
  ];
43
46
 
44
47
  interface ParsedArgs {
@@ -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.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -18,9 +18,7 @@ MCP Apps extend the standard tool pattern with an interactive HTML UI rendered i
18
18
 
19
19
  Both builders are exported from `@cyanheads/mcp-ts-core`. They handle `_meta.ui.resourceUri`, the compat key (`ui/resourceUri`), and the correct MIME type (`text/html;profile=mcp-app`) automatically.
20
20
 
21
- For the full API, Context interface, and error codes, read:
22
-
23
- node_modules/@cyanheads/mcp-ts-core/CLAUDE.md
21
+ For the full API, Context interface, and error codes, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
24
22
 
25
23
  ## Steps
26
24
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP prompt template. Use when the user asks to add a prompt, create a reusable message template, or define a prompt for LLM interactions.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.2"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -15,9 +15,7 @@ Prompts use the `prompt()` builder from `@cyanheads/mcp-ts-core`. Each prompt li
15
15
 
16
16
  Prompts are pure message templates — no `Context`, no auth, no side effects.
17
17
 
18
- For the full `prompt()` API, read:
19
-
20
- node_modules/@cyanheads/mcp-ts-core/CLAUDE.md
18
+ For the full `prompt()` API, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
21
19
 
22
20
  ## Steps
23
21
 
@@ -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.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -15,9 +15,7 @@ Resources use the `resource()` builder from `@cyanheads/mcp-ts-core`. Each resou
15
15
 
16
16
  **Tool coverage.** Not all MCP clients expose resources — many are tool-only (Claude Code, Cursor, most chat UIs). Before adding a resource, verify the same data is reachable via the tool surface — either through a dedicated tool, included in another tool's output, or bundled into a broader tool. A resource whose data has no tool path is invisible to a large share of agents.
17
17
 
18
- For the full `resource()` API, pagination utilities, and `Context` interface, read:
19
-
20
- node_modules/@cyanheads/mcp-ts-core/CLAUDE.md
18
+ For the full `resource()` API, pagination utilities, and `Context` interface, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
21
19
 
22
20
  ## Steps
23
21
 
@@ -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.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -15,9 +15,7 @@ Services use the init/accessor pattern: initialized once in `createApp`'s `setup
15
15
 
16
16
  Service methods receive `Context` for correlated logging (`ctx.log`) and tenant-scoped storage (`ctx.state`). Convention: `ctx.elicit` and `ctx.sample` should only be called from tool handlers, not from services.
17
17
 
18
- For the full service pattern, `CoreServices`, and `Context` interface, read:
19
-
20
- node_modules/@cyanheads/mcp-ts-core/CLAUDE.md
18
+ For the full service pattern, `CoreServices`, and `Context` interface, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
21
19
 
22
20
  ## Steps
23
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: "1.6"
7
+ version: "1.7"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -13,9 +13,7 @@ metadata:
13
13
 
14
14
  Tools use the `tool()` builder from `@cyanheads/mcp-ts-core`. Each tool lives in `src/mcp-server/tools/definitions/` with a `.tool.ts` suffix and is registered into `createApp()` in `src/index.ts`. Some larger repos later add `definitions/index.ts` barrels; match the pattern already used by the project you're editing.
15
15
 
16
- For the full `tool()` API, `Context` interface, and error codes, read:
17
-
18
- node_modules/@cyanheads/mcp-ts-core/CLAUDE.md
16
+ For the full `tool()` API, `Context` interface, and error codes, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
19
17
 
20
18
  ## Steps
21
19
 
@@ -164,6 +162,8 @@ async handler(input, ctx) {
164
162
  },
165
163
  ```
166
164
 
165
+ **Note on the `try/catch`:** this is the deliberate exception to the "logic throws, framework catches" rule. Per-item isolation is the whole point of partial-success batch tools — one failed item must not abort the batch, and the framework's partial-success telemetry (below) depends on seeing a populated `failed` array. Don't remove it to conform to the handler-level rule.
166
+
167
167
  Single-item tools don't need this — they either succeed or throw. The partial success question only arises with array inputs.
168
168
 
169
169
  **Telemetry:** The framework automatically detects this pattern — when a handler result contains a non-empty `failed` array, the span gets `mcp.tool.partial_success`, `mcp.tool.batch.succeeded_count`, and `mcp.tool.batch.failed_count` attributes. No manual instrumentation needed.
@@ -242,7 +242,8 @@ import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
242
242
  throw serviceUnavailable(`arXiv API returned HTTP ${status}. Retry in a few seconds.`);
243
243
 
244
244
  // Structured hint for programmatic recovery
245
- throw new McpError(JsonRpcErrorCode.InvalidParams,
245
+ import { invalidParams } from '@cyanheads/mcp-ts-core/errors';
246
+ throw invalidParams(
246
247
  `Date range exceeds 90-day API limit. Narrow the range or split into multiple queries.`,
247
248
  { maxDays: 90, requestedDays: daysBetween },
248
249
  );
@@ -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, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.sample`, `ctx.progress`), and when to use each.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -181,7 +181,7 @@ if (ctx.elicit) {
181
181
  await produceOutput(result.content?.format as string, result.content?.includeHeaders as boolean);
182
182
  } else {
183
183
  // 'decline' or 'cancel' — user opted out
184
- throw new McpError(JsonRpcErrorCode.InvalidRequest, 'User declined input');
184
+ throw invalidRequest('User declined input');
185
185
  }
186
186
  }
187
187
  ```
@@ -4,7 +4,7 @@ description: >
4
4
  API reference for built-in service providers (LLM, Speech, Graph). Use when looking up service interfaces, provider capabilities, or integration patterns.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -111,7 +111,7 @@ const path = await graphService.shortestPath('user:alice', 'user:charlie', conte
111
111
  algorithm: 'bfs',
112
112
  maxLength: 4,
113
113
  });
114
- if (path) console.log(`${path.vertices.length} hops`);
114
+ if (path) context.log.info(`${path.vertices.length} hops`);
115
115
 
116
116
  // Check reachability
117
117
  const connected = await graphService.pathExists('user:alice', 'user:charlie', context, 3);
@@ -4,7 +4,7 @@ description: >
4
4
  API reference for all utilities exported from `@cyanheads/mcp-ts-core/utils`. Use when looking up utility method signatures, options, peer dependencies, or usage patterns.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.0"
7
+ version: "2.1"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -249,7 +249,7 @@ Matches `--- ... ---` at the very start of the document. An empty `---\n---` blo
249
249
  ```ts
250
250
  const { frontmatter, content, hasFrontmatter } = await frontmatterParser.parse<SkillMeta>(markdown);
251
251
  if (hasFrontmatter) {
252
- console.log(frontmatter.name, frontmatter.version);
252
+ logger.info({ name: frontmatter.name, version: frontmatter.version }, 'parsed frontmatter');
253
253
  }
254
254
  ```
255
255
 
@@ -147,7 +147,7 @@ rateLimiter.check(`api:${ctx.tenantId}`, ctx);
147
147
  // Check status without consuming a request
148
148
  const status = rateLimiter.getStatus('api:tenant-123');
149
149
  if (status) {
150
- console.log(`${status.remaining} requests left, resets at ${new Date(status.resetTime)}`);
150
+ logger.info(`${status.remaining} requests left, resets at ${new Date(status.resetTime)}`);
151
151
  }
152
152
 
153
153
  // Runtime reconfiguration
@@ -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.4"
7
+ version: "2.5"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -271,7 +271,7 @@ throw new Error('Not found');
271
271
  "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first."
272
272
 
273
273
  // Good — structured hint in error data
274
- throw new McpError(JsonRpcErrorCode.Forbidden,
274
+ throw forbidden(
275
275
  "Cannot perform 'reset --hard' on protected branch 'main' without explicit confirmation.",
276
276
  { branch: 'main', operation: 'reset --hard', hint: 'Set the confirmed parameter to true to proceed.' },
277
277
  );
@@ -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: "1.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -50,13 +50,18 @@ Do not redo this investigation inline — the `changelog` skill handles tag-form
50
50
 
51
51
  ### 4. Framework review (`@cyanheads/mcp-ts-core`)
52
52
 
53
- If `@cyanheads/mcp-ts-core` was updated, do a deeper pass beyond what the `changelog` skill covers. Read:
53
+ If `@cyanheads/mcp-ts-core` was updated, do a deeper pass beyond what the `changelog` skill covers. The framework ships a **directory-based changelog** grouped by minor series (`.x` semver-wildcard convention) — one file per released version at `node_modules/@cyanheads/mcp-ts-core/changelog/<major.minor>.x/<version>.md`. Read only the files between old and new rather than scanning a monolithic file.
54
54
 
55
- ```bash
56
- node_modules/@cyanheads/mcp-ts-core/CHANGELOG.md
57
- ```
55
+ Example — `0.5.2 → 0.5.4` means reading two new version files:
56
+
57
+ - `node_modules/@cyanheads/mcp-ts-core/changelog/0.5.x/0.5.3.md`
58
+ - `node_modules/@cyanheads/mcp-ts-core/changelog/0.5.x/0.5.4.md`
59
+
60
+ Cross-series updates span multiple directories — e.g., `0.4.1 → 0.5.2` reads `0.5.x/0.5.0.md`, `0.5.x/0.5.1.md`, `0.5.x/0.5.2.md`. Enumerate the series directories under `node_modules/@cyanheads/mcp-ts-core/changelog/` to find the relevant files.
58
61
 
59
- Extract entries between the old and new version. Scan specifically for:
62
+ If the per-version directory isn't present (pre-0.5.5 releases, or downstream package that hasn't adopted the convention), fall back to the monolithic rollup at `node_modules/@cyanheads/mcp-ts-core/CHANGELOG.md` and extract the relevant sections manually.
63
+
64
+ Scan specifically for:
60
65
 
61
66
  | Area | Adoption Check |
62
67
  |:-----|:---------------|
@@ -70,11 +75,7 @@ Extract entries between the old and new version. Scan specifically for:
70
75
 
71
76
  Cross-reference each finding against the server's code. Collect adoption opportunities for Step 6.
72
77
 
73
- **Template review.** The framework also ships `templates/CLAUDE.md` and `templates/AGENTS.md` as scaffolding for consumer agent protocol files. The consumer's `CLAUDE.md`/`AGENTS.md` was copied at init time and has since diverged (local customizations, echo replacements, server-specific sections). Read the upstream template fresh:
74
-
75
- ```bash
76
- node_modules/@cyanheads/mcp-ts-core/templates/CLAUDE.md
77
- ```
78
+ **Template review.** The framework also ships `templates/CLAUDE.md` and `templates/AGENTS.md` as scaffolding for consumer agent protocol files. The consumer's `CLAUDE.md`/`AGENTS.md` was copied at init time and has since diverged (local customizations, echo replacements, server-specific sections). Read the upstream template fresh at `node_modules/@cyanheads/mcp-ts-core/templates/CLAUDE.md`.
78
79
 
79
80
  Skip the mechanical diff — consumer customizations create too much noise to filter. Instead, read end-to-end with fresh eyes, mentally comparing against the current `CLAUDE.md`. Look for: new conventions, updated skill references, expanded checklists, new callouts, clearer explanations, restructured sections. Present findings; let the user cherry-pick what to adopt. Never auto-merge — the consumer's file is theirs.
80
81
 
@@ -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: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -118,22 +118,37 @@ frozenLockfile = false
118
118
  bun = true
119
119
  ```
120
120
 
121
- ### 9. `CHANGELOG.md`
121
+ ### 9. Changelog
122
122
 
123
- If `CHANGELOG.md` doesn't exist, create it with an initial entry. If it exists, verify the latest entry reflects the current state:
123
+ Directory-based, grouped by minor series using the `.x` semver-wildcard convention: per-version files live in `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.1.x/0.1.0.md`), work-in-progress in `changelog/unreleased.md` at the top level, and `CHANGELOG.md` is a rollup artifact regenerated by `bun run changelog:build`. Devcheck's `Changelog Sync` step enforces that `CHANGELOG.md` stays in sync.
124
+
125
+ If `changelog/` doesn't exist, create the structure:
126
+
127
+ 1. Make the `changelog/` directory
128
+ 2. Create `changelog/unreleased.md` from the template (H1 `# Unreleased` + empty Added/Changed/Fixed sections)
129
+ 3. If the server already has a shipped version (e.g. 0.1.0), create the series directory and initial entry: `changelog/0.1.x/0.1.0.md` with H1 `# 0.1.0 — YYYY-MM-DD`, concrete version and date
130
+ 4. Run `bun run changelog:build` to generate `CHANGELOG.md`
131
+
132
+ Per-version file format:
124
133
 
125
134
  ```markdown
126
- # Changelog
135
+ ---
136
+ summary: One-line headline for the rollup index — ≤250 chars, no markdown
137
+ breaking: false
138
+ ---
139
+
140
+ # 0.1.0 — YYYY-MM-DD
127
141
 
128
- ## 0.1.0 YYYY-MM-DD
142
+ Optional narrative intro (1-3 sentences).
129
143
 
130
- Initial release.
144
+ ## Added
131
145
 
132
- ### Added
133
146
  - [list tools, resources, prompts, key capabilities]
134
147
  ```
135
148
 
136
- Use a concrete version and date. Never `[Unreleased]`.
149
+ **Frontmatter:** `summary` is required (powers the CHANGELOG.md index), `breaking` is optional and defaults to `false` (set `true` for releases requiring consumer code changes).
150
+
151
+ Never hand-edit `CHANGELOG.md` — it's a build artifact. Never use `[Unreleased]` as a version header in a released file.
137
152
 
138
153
  ### 10. `LICENSE`
139
154
 
@@ -181,7 +196,7 @@ Both must pass clean.
181
196
  - [ ] `server.json` matches official MCP schema, versions synced, env vars current
182
197
  - [ ] GitHub repo description matches `package.json` description; topics ↔ keywords in sync
183
198
  - [ ] `bunfig.toml` present
184
- - [ ] `CHANGELOG.md` exists with current entry
199
+ - [ ] `changelog/` directory present with per-version files; `CHANGELOG.md` rollup regenerated and in sync
185
200
  - [ ] `LICENSE` file present
186
201
  - [ ] `Dockerfile` OCI labels and runtime config accurate (if present)
187
202
  - [ ] `docs/tree.md` regenerated
@@ -4,7 +4,7 @@ description: >
4
4
  Verify release readiness and publish. The git wrapup protocol handles version bumps, changelog, README, commits, and tagging during the coding session. This skill verifies nothing was missed, runs final checks, and presents the irreversible publish commands.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.4"
8
8
  audience: internal
9
9
  type: workflow
10
10
  ---
@@ -34,13 +34,27 @@ The wrapup protocol bumps versions, but sometimes a file gets missed. **Search f
34
34
 
35
35
  Fix any mismatches. A grep for the **old** version is the fastest way to find stragglers.
36
36
 
37
- ### 3. Verify CHANGELOG.md
37
+ ### 3. Release-ize the Changelog Entry
38
38
 
39
- Confirm the changelog entry:
39
+ The changelog is directory-based, grouped by minor series using the `.x` semver-wildcard convention: per-version files live at `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.5.x/0.5.4.md`), work-in-progress entries go in `changelog/unreleased.md` at the top level, and `CHANGELOG.md` is an auto-generated rollup (`bun run changelog:build`).
40
40
 
41
- - Uses a **concrete version number and date** (never `[Unreleased]`)
42
- - Groups changes correctly: Added, Changed, Fixed, Removed
43
- - Accurately reflects what actually shipped cross-reference with `git log` since the last tag
41
+ If the wrapup already converted `unreleased.md` to the version file, verify it. Otherwise release-ize it now:
42
+
43
+ 1. Determine the series: `0.5.5` `0.5.x/`. Create the directory if it doesn't exist: `mkdir -p changelog/<series>`
44
+ 2. `git mv changelog/unreleased.md changelog/<series>/<version>.md`
45
+ 3. Update the H1: `# Unreleased` → `# <version> — <date>` (em-dash, ISO date)
46
+ 4. **Fill in the frontmatter** at the top of the file:
47
+ - `summary:` — one-line headline, ≤250 chars, no markdown. Write it like a GitHub Release title. Required.
48
+ - `breaking:` — set to `true` if this release requires consumer code changes (API removal, signature change, config rename). Defaults to `false`. Renders `· ⚠️ Breaking` in the rollup when true.
49
+ 5. Verify content:
50
+ - Sections grouped correctly (Added / Changed / Fixed / Removed)
51
+ - Accurately reflects what shipped — cross-reference with `git log` since the last tag
52
+ - If this release absorbed pre-release versions (e.g., `0.6.0-beta.1`), confirm their sub-headers are preserved in the final file
53
+ - **Issue/PR references use full URLs**, not bare `#NN`. GitHub's auto-link only renders inside its own UI; these files are read from `node_modules` too, where bare `#NN` is dead text. Use `[#38](https://github.com/<owner>/<repo>/issues/38)` (or `/pull/NN` for PRs). Only link numbers verified via `gh issue view NN` / `gh pr view NN` — never speculate on future numbers, since GitHub will happily resolve `#42` to whatever unrelated item already owns 42 and pull its title into timeline previews.
54
+ 6. Create a fresh empty `changelog/unreleased.md` from the template (frontmatter stub with empty `summary`, `breaking: false`)
55
+ 7. Regenerate the rollup: `bun run changelog:build` — warnings about missing summaries are expected during the legacy-file backfill period but should not include this release
56
+
57
+ Never hand-edit `CHANGELOG.md` — it's a build artifact. Devcheck's `Changelog Sync` step will fail if it drifts.
44
58
 
45
59
  ### 4. Verify README.md
46
60
 
@@ -117,7 +131,7 @@ mcp-publisher publish
117
131
 
118
132
  - [ ] Version consistent across all files (package.json, server.json ×3, CLAUDE.md, README.md, templates)
119
133
  - [ ] No stale old-version references found in repo
120
- - [ ] CHANGELOG.md has concrete version and date, content matches actual changes
134
+ - [ ] `changelog/<major.minor>.x/<version>.md` has concrete version and date; `changelog/unreleased.md` reset; `CHANGELOG.md` regenerated via `bun run changelog:build`
121
135
  - [ ] README.md current — feature counts, badges, descriptions, examples
122
136
  - [ ] Modified skill versions bumped in YAML frontmatter
123
137
  - [ ] `docs/tree.md` current (if structure changed)