@cyanheads/mcp-ts-core 0.5.3 → 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 (143) hide show
  1. package/CLAUDE.md +41 -2
  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 +26 -2
  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 +19 -4
  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-linter/SKILL.md +391 -0
  130. package/skills/api-services/SKILL.md +1 -1
  131. package/skills/api-services/references/graph.md +1 -1
  132. package/skills/api-utils/SKILL.md +1 -1
  133. package/skills/api-utils/references/parsing.md +1 -1
  134. package/skills/api-utils/references/security.md +1 -1
  135. package/skills/design-mcp-server/SKILL.md +2 -2
  136. package/skills/maintenance/SKILL.md +12 -11
  137. package/skills/polish-docs-meta/SKILL.md +24 -9
  138. package/skills/release/SKILL.md +21 -7
  139. package/skills/setup/SKILL.md +4 -8
  140. package/templates/AGENTS.md +23 -1
  141. package/templates/CLAUDE.md +23 -1
  142. package/templates/changelog/unreleased.md +40 -0
  143. 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';
@@ -67,7 +67,7 @@ const createColor = (open: string, close: string, closeRe: RegExp) => (str: stri
67
67
  return open + `${str}`.replace(closeRe, close + open) + close;
68
68
  };
69
69
 
70
- const esc = (code: string) => new RegExp(code.replace('[', '\\['), 'g');
70
+ const esc = (code: string) => new RegExp(code.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
71
71
  const c = {
72
72
  bold: createColor('\x1b[1m', '\x1b[22m', esc('\x1b[22m')),
73
73
  dim: createColor('\x1b[2m', '\x1b[22m', esc('\x1b[22m')),
@@ -411,7 +411,7 @@ const ALL_CHECKS: Check[] = [
411
411
  canFix: false,
412
412
  getCommand: () => ['bun', 'run', 'scripts/lint-mcp.ts'],
413
413
  tip: (c) =>
414
- `Fix definition errors reported above. See ${c.bold('validateDefinitions()')} docs for rule details.`,
414
+ `Fix definition errors above each diagnostic links to its rule in ${c.bold('skills/api-linter/SKILL.md')}.`,
415
415
  },
416
416
  {
417
417
  name: 'Docs Sync',
@@ -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',
@@ -562,7 +577,7 @@ const ALL_CHECKS: Check[] = [
562
577
  return unexpected.length === 0;
563
578
  },
564
579
  tip: (c) =>
565
- `Run ${c.bold(`${PM_CMD} update`)} to upgrade dependencies. Configure allowlist in ${c.bold('devcheck.config.json')}.`,
580
+ `Run ${c.bold(`${PM_CMD} update`)} to upgrade; the ${c.bold('maintenance')} skill then investigates changelogs and adopts upstream changes. Configure allowlist in ${c.bold('devcheck.config.json')}.`,
566
581
  },
567
582
  ];
568
583
 
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
  ```