@cyanheads/mcp-ts-core 0.9.14 → 0.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +559 -0
- package/CLAUDE.md +3 -2
- package/README.md +4 -4
- package/changelog/0.9.x/0.9.15.md +52 -0
- package/changelog/0.9.x/0.9.16.md +11 -0
- package/changelog/template.md +1 -1
- package/dist/core/context.d.ts +18 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +4 -0
- package/dist/core/context.js.map +1 -1
- package/dist/linter/rules/enrichment-rules.d.ts +13 -4
- package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +92 -4
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +4 -0
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +2 -2
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/logs/combined.log +4 -0
- package/dist/logs/error.log +2 -0
- package/dist/logs/interactions.log +0 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +48 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +35 -7
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/package.json +5 -3
- package/scripts/build-changelog.ts +3 -1
- package/scripts/check-skills-sync.ts +42 -8
- package/skills/add-app-tool/SKILL.md +2 -2
- package/skills/add-export/SKILL.md +2 -2
- package/skills/add-service/SKILL.md +2 -2
- package/skills/add-tool/SKILL.md +30 -2
- package/skills/api-context/SKILL.md +4 -2
- package/skills/api-linter/SKILL.md +27 -3
- package/skills/design-mcp-server/SKILL.md +2 -2
- package/skills/git-wrapup/SKILL.md +22 -15
- package/skills/maintenance/SKILL.md +8 -7
- package/skills/orchestrations/workflows/maintenance-release.md +1 -1
- package/skills/polish-docs-meta/SKILL.md +1 -1
- package/skills/polish-docs-meta/references/agent-protocol.md +2 -2
- package/skills/polish-docs-meta/references/readme.md +3 -3
- package/skills/setup/SKILL.md +5 -10
- package/templates/AGENTS.md +2 -1
- package/templates/CLAUDE.md +2 -1
- package/templates/_.mcpbignore +2 -0
- package/templates/changelog/template.md +1 -1
- package/templates/package.json +2 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# Developer Protocol
|
|
2
|
+
|
|
3
|
+
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
+
**Version:** 0.9.16
|
|
5
|
+
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
6
|
+
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
7
|
+
**Zod:** ^4.4.3
|
|
8
|
+
**GitHub:** [cyanheads/mcp-ts-core](https://github.com/cyanheads/mcp-ts-core)
|
|
9
|
+
**npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
10
|
+
**Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
|
|
11
|
+
|
|
12
|
+
> **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never try to edit a file before reading it.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Consumers
|
|
17
|
+
|
|
18
|
+
This package serves two consumer paths. When making changes, know which audience your change affects:
|
|
19
|
+
|
|
20
|
+
| Path | On-ramp | Affected by changes to |
|
|
21
|
+
|:--|:--|:--|
|
|
22
|
+
| **Direct package import** — existing project pulls in the package | `bun add @cyanheads/mcp-ts-core` → `import { createApp, tool, z } from '@cyanheads/mcp-ts-core'` | Public API surface (`src/`) — existing consumers feel changes immediately on upgrade |
|
|
23
|
+
| **Init-scaffolded server** — fresh project bootstrapped from this repo's templates | `bunx @cyanheads/mcp-ts-core init [name]` copies `templates/` into the new directory | `templates/` — only affects newly scaffolded servers, not existing ones |
|
|
24
|
+
|
|
25
|
+
Both paths share the same public API. Init copies starter `package.json`, configs (`tsconfig`, `biome.json`, `vitest.config.ts`), `.env.example`, `Dockerfile`, `CLAUDE.md`/`AGENTS.md`, and example definitions. `_`-prefixed files (e.g. `_.gitignore`) drop the prefix on copy. After init, consult the `setup` skill.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Core Rules
|
|
30
|
+
|
|
31
|
+
- **Logic throws, framework catches.** Pure, stateless `handler` functions, no `try/catch`. Plain `Error` works — framework catches, classifies, formats. Use `McpError(code, message, data, options?)` only when you need a specific JSON-RPC code or structured data; 4th arg `{ cause }` chains.
|
|
32
|
+
- **Full-stack observability.** The framework automatically instruments every tool/resource call — OTel span, duration/payload/memory metrics, structured completion log. Use `ctx.log` for additional domain-specific logging within handlers (external API calls, multi-step operations, business events). `requestId`, `traceId`, `tenantId` auto-correlated. No `console` calls.
|
|
33
|
+
- **Unified Context.** Handlers receive `ctx` with logging (`ctx.log`), tenant-scoped storage (`ctx.state`), optional protocol capabilities (`ctx.elicit`, `ctx.sample`), and cancellation (`ctx.signal`).
|
|
34
|
+
- **Decoupled storage.** `ctx.state` for tenant-scoped KV. Never access persistence backends directly.
|
|
35
|
+
- **Canvas tokens are capabilities, not tenant-scoped state.** A `canvasId` is a 10-char URL-safe token; possession grants full read/write/drop. Shareable between agents and across users in single-tenant deployments. Tools accept token in `input` (omit to create fresh) and return in `output`; collaboration is opt-in via token exchange.
|
|
36
|
+
- **Runtime parity.** All features work across `stdio`/`http`/Worker. Guard non-portable deps via `runtimeCaps` from `/utils` (`isNode`, `isBun`, `isWorkerLike`, `hasBuffer`, `hasProcess`, etc.). Prefer runtime-agnostic abstractions (Hono, Fetch APIs).
|
|
37
|
+
- **Definition linting is build-time only.** Run `bun run lint:mcp` (standalone) or `bun run devcheck` (gate). Not invoked at server startup — new lint rules are additive and never break deployed servers. Every diagnostic links to the rule reference in `api-linter` skill; see that skill for the full rule catalog.
|
|
38
|
+
- **Elicitation for missing input.** Use `ctx.elicit` when the client supports it.
|
|
39
|
+
- **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Exports Reference
|
|
44
|
+
|
|
45
|
+
| Subpath | Key Exports | Purpose |
|
|
46
|
+
|:--------|:------------|:--------|
|
|
47
|
+
| `@cyanheads/mcp-ts-core` | `createApp`, `tool`, `resource`, `prompt`, `appTool`, `appResource`, `APP_RESOURCE_MIME_TYPE`, `Context`, `createFail`, `createRecoveryFor`, `TypedFail`, `TypedRecoveryFor`, `ReasonOf`, `HandlerContext`, `Enrich`, `EnrichHelpers`, `TypedEnrich`, `z` | Main entry point |
|
|
48
|
+
| `/worker` | `createWorkerHandler`, `CloudflareBindings` | Cloudflare Workers entry |
|
|
49
|
+
| `/tools` | `ToolDefinition`, `AnyToolDefinition`, `ToolAnnotations` | Tool definition types |
|
|
50
|
+
| `/resources` | `ResourceDefinition`, `AnyResourceDefinition` | Resource definition types |
|
|
51
|
+
| `/prompts` | `PromptDefinition` | Prompt definition type |
|
|
52
|
+
| `/tasks` | `TaskToolDefinition`, `isTaskToolDefinition` | Task tool escape hatch |
|
|
53
|
+
| `/errors` | `McpError`, `JsonRpcErrorCode`, `notFound`, `validationError`, `unauthorized`, ... | Error types, codes, and factory functions |
|
|
54
|
+
| `/config` | `AppConfig`, `config`, `parseConfig`, `parseEnvConfig`, `resetConfig`, `ConfigSchema`, `FRAMEWORK_NAME`, `FRAMEWORK_VERSION` | Zod-validated config, framework identity, env-var helper |
|
|
55
|
+
| `/auth` | `checkScopes` | Dynamic scope checking |
|
|
56
|
+
| `/storage` | `StorageService` | Storage abstraction |
|
|
57
|
+
| `/storage/types` | `IStorageProvider` | Provider interface |
|
|
58
|
+
| `/canvas` | `DataCanvas`, `CanvasInstance`, `CanvasRegistry`, `IDataCanvasProvider`, `DuckdbProvider`, `spillover`, `inferSchemaFromRows`, `assertReadOnlyQuery`, `quoteIdentifier`, ... | DataCanvas primitive (Tier 3, optional peer dep `@duckdb/node-api`); SQL/analytical workspace + source-agnostic spillover helper |
|
|
59
|
+
| `/utils` | formatting, encoding, network, pagination, logging, runtime, telemetry, token counting, parsers†, sanitization†, scheduling† | All utilities (†optional peer deps) |
|
|
60
|
+
| `/services` | `OpenRouterProvider`, `SpeechService`, `createSpeechProvider`, `ElevenLabsProvider`, `WhisperProvider`, `GraphService`, provider interfaces and types | LLM, Speech (TTS/STT), Graph services |
|
|
61
|
+
| `/linter` | `validateDefinitions`, `LintReport`, `LintDiagnostic`, `LintInput`, `LintSeverity` | Definition validation |
|
|
62
|
+
| `/testing` | `createMockContext`, `getEnrichment` | Test helpers |
|
|
63
|
+
| `/testing/fuzz` | `fuzzTool`, `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, `adversarialArbitrary`, `ADVERSARIAL_STRINGS` | Fuzz testing |
|
|
64
|
+
|
|
65
|
+
All subpaths prefixed with `@cyanheads/mcp-ts-core`. **†Tier 3 modules** require optional peer dependencies — see `package.json` `peerDependencies`. Tier 3 methods that lazy-load deps are **async**.
|
|
66
|
+
|
|
67
|
+
### Import conventions
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// Framework (from node_modules) — z is re-exported, no separate zod import needed
|
|
71
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
72
|
+
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
73
|
+
|
|
74
|
+
// Server's own code (via path alias)
|
|
75
|
+
import { getMyService } from '@/services/my-domain/my-service.js';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Build configs exported for consumer extension: `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`, `biome.json` extends `@cyanheads/mcp-ts-core/biome`, `vitest.config.ts` spreads from `@cyanheads/mcp-ts-core/vitest.config`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Entry Points
|
|
83
|
+
|
|
84
|
+
### Node.js — `createApp(options)`
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
88
|
+
import { allToolDefinitions } from './mcp-server/tools/index.js';
|
|
89
|
+
import { allResourceDefinitions } from './mcp-server/resources/index.js';
|
|
90
|
+
import { allPromptDefinitions } from './mcp-server/prompts/index.js';
|
|
91
|
+
|
|
92
|
+
await createApp({
|
|
93
|
+
name: 'my-mcp-server', // overrides package.json / MCP_SERVER_NAME
|
|
94
|
+
version: '0.1.0', // overrides package.json / MCP_SERVER_VERSION
|
|
95
|
+
tools: allToolDefinitions,
|
|
96
|
+
resources: allResourceDefinitions,
|
|
97
|
+
prompts: allPromptDefinitions,
|
|
98
|
+
instructions: // server-level orientation, sent on every initialize
|
|
99
|
+
'Pre-configured shortcuts:\n- `default` → production API\n' +
|
|
100
|
+
'Other endpoints reachable via `connect({ baseUrl })`.',
|
|
101
|
+
extensions: { // SEP-2133 extensions advertised in capabilities
|
|
102
|
+
'vendor/my-extension': { /* extension config */ },
|
|
103
|
+
},
|
|
104
|
+
setup(core) { // runs after core services init, before transport starts
|
|
105
|
+
initMyService(core.config, core.storage);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**`instructions`** — Optional server-level orientation, surfaced on every `initialize` response as session-level system context. Use for deployment-specific guidance (connection aliases, regional notes, scope hints) instead of repeating in tool descriptions. Client adoption uneven but no downside when set.
|
|
111
|
+
|
|
112
|
+
### Cloudflare Workers — `createWorkerHandler(options)`
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { createWorkerHandler } from '@cyanheads/mcp-ts-core/worker';
|
|
116
|
+
|
|
117
|
+
export default createWorkerHandler({
|
|
118
|
+
tools: allToolDefinitions,
|
|
119
|
+
resources: allResourceDefinitions,
|
|
120
|
+
prompts: allPromptDefinitions,
|
|
121
|
+
instructions: (env) => `Region: ${env.ENVIRONMENT ?? 'production'}`, // string | (env) => string
|
|
122
|
+
setup(core) { initMyService(core.config, core.storage); },
|
|
123
|
+
extraEnvBindings: [['MY_API_KEY', 'MY_API_KEY']], // string values → process.env
|
|
124
|
+
extraObjectBindings: [['MY_CUSTOM_KV', 'MY_CUSTOM_KV']], // KV/R2/D1 → globalThis
|
|
125
|
+
onScheduled: async (controller, env, ctx) => { /* cron */ },
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Per-request `McpServer` factory (security: SDK GHSA-345p-7cg4-v4c7). Requires `compatibility_flags = ["nodejs_compat"]` and `compatibility_date >= "2025-09-01"` in `wrangler.toml`. Only `in-memory`, `cloudflare-r2`, `cloudflare-kv`, `cloudflare-d1` storage in Workers. See `api-workers` skill for full details.
|
|
130
|
+
|
|
131
|
+
### Interfaces
|
|
132
|
+
|
|
133
|
+
`createApp()` returns `Promise<ServerHandle>`. `createWorkerHandler()` returns an `ExportedHandler`.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
interface CoreServices {
|
|
137
|
+
config: AppConfig;
|
|
138
|
+
logger: Logger;
|
|
139
|
+
storage: StorageService;
|
|
140
|
+
rateLimiter: RateLimiter;
|
|
141
|
+
llmProvider?: ILlmProvider;
|
|
142
|
+
speechService?: SpeechService;
|
|
143
|
+
supabase?: SupabaseClient;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface ServerHandle {
|
|
147
|
+
shutdown(signal?: string): Promise<void>;
|
|
148
|
+
readonly services: CoreServices;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Server Structure
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
src/
|
|
158
|
+
index.ts # createApp() entry point
|
|
159
|
+
worker.ts # createWorkerHandler() (if using Workers)
|
|
160
|
+
config/
|
|
161
|
+
server-config.ts # Server-specific env vars (own Zod schema)
|
|
162
|
+
services/
|
|
163
|
+
[domain]/
|
|
164
|
+
[domain]-service.ts # Domain service (init/accessor pattern)
|
|
165
|
+
types.ts # Domain types
|
|
166
|
+
mcp-server/
|
|
167
|
+
tools/definitions/
|
|
168
|
+
[tool-name].tool.ts # Tool definitions
|
|
169
|
+
index.ts # allToolDefinitions barrel
|
|
170
|
+
resources/definitions/
|
|
171
|
+
[resource-name].resource.ts # Resource definitions
|
|
172
|
+
index.ts # allResourceDefinitions barrel
|
|
173
|
+
prompts/definitions/
|
|
174
|
+
[prompt-name].prompt.ts # Prompt definitions
|
|
175
|
+
index.ts # allPromptDefinitions barrel
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**File suffixes:** `.tool.ts` (standard or task), `.resource.ts`, `.prompt.ts`, `.app-tool.ts` (UI-enabled), `.app-resource.ts` (UI resource linked to app tool).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Adding a Tool
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
186
|
+
|
|
187
|
+
export const myTool = tool('my_tool', {
|
|
188
|
+
description: 'Does something useful.',
|
|
189
|
+
annotations: { readOnlyHint: true },
|
|
190
|
+
input: z.object({ query: z.string().describe('Search query') }),
|
|
191
|
+
output: z.object({
|
|
192
|
+
items: z.array(z.object({
|
|
193
|
+
id: z.string().describe('Item ID'),
|
|
194
|
+
name: z.string().describe('Item name'),
|
|
195
|
+
status: z.string().describe('Current status'),
|
|
196
|
+
description: z.string().optional().describe('Item description'),
|
|
197
|
+
})).describe('Matching items'),
|
|
198
|
+
totalCount: z.number().describe('Total matches before pagination'),
|
|
199
|
+
}),
|
|
200
|
+
auth: ['tool:my_tool:read'],
|
|
201
|
+
|
|
202
|
+
async handler(input, ctx) {
|
|
203
|
+
const data = await fetchFromApi(input.query);
|
|
204
|
+
ctx.log.info('Query resolved', { query: input.query, resultCount: data.items.length });
|
|
205
|
+
return data;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
format: (result) => {
|
|
209
|
+
const lines = [`**${result.totalCount} results**\n`];
|
|
210
|
+
for (const item of result.items) {
|
|
211
|
+
lines.push(`### ${item.name}`);
|
|
212
|
+
lines.push(`**ID:** ${item.id} | **Status:** ${item.status}`);
|
|
213
|
+
if (item.description) lines.push(item.description);
|
|
214
|
+
}
|
|
215
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Steps:** Create `src/mcp-server/tools/definitions/[name].tool.ts` (kebab-case) → use `tool('snake_case', {...})` with Zod `.describe()` on all fields → implement `handler(input, ctx)` (pure, throws on failure) → add `auth`/`format` if needed → register in `definitions/index.ts` → `bun run devcheck` → smoke-test with `bun run rebuild && bun run start:stdio` (or `start:http`).
|
|
221
|
+
|
|
222
|
+
**Schema constraint:** Input/output schemas must use JSON-Schema-serializable Zod types only. The MCP SDK converts schemas to JSON Schema for `tools/list` — non-serializable types (`z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`) cause a hard runtime failure. Use structural equivalents instead (e.g., `z.string()` with `.describe('ISO 8601 date')` instead of `z.date()`). The linter validates this at startup.
|
|
223
|
+
|
|
224
|
+
**Form-client safety:** Form-based clients (MCP Inspector, web UIs) send optional fields as empty strings, not `undefined`. Don't reject with `.min(1)` on optional fields — guard for meaningful values in the handler (`if (input.dateRange?.minDate && input.dateRange?.maxDate)`). Test with both omitted and empty-value payloads. When schema-level constraints (regex/length) need to surface in the JSON Schema, wrap in a union with a `z.literal('')` sentinel: `z.union([z.literal(''), z.string().regex(...).describe(...)])` — the linter exempts the literal variant from `describe-on-fields`.
|
|
225
|
+
|
|
226
|
+
**`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
|
|
227
|
+
|
|
228
|
+
- **Parity is enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or startup fails with a `format-parity` lint error.
|
|
229
|
+
- **Primary fix:** render the missing field in `format()`. Use `z.discriminatedUnion` for list/detail variants — each branch is validated separately.
|
|
230
|
+
- **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
|
|
231
|
+
- **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
|
|
232
|
+
|
|
233
|
+
**`enrichment`** (optional): The success-path counterpart to `errors[]` — a `ZodRawShape` of agent-facing context (empty-result notices, query/filter echo, pagination totals) that must reach both client surfaces. Populate via `ctx.enrich(...)` (or `ctx.enrich.notice()` / `.total()` / `.echo()`) in the handler or service layer. The framework merges it into `structuredContent`, advertises `output.extend(enrichment)` as `outputSchema`, and mirrors it into a `content[]` trailer — so it reaches `structuredContent`-only and `content[]`-only clients alike, with no `format()` entry. Keys must be disjoint from `output`; a required field never populated fails the effective-output parse. See `api-context`'s `ctx.enrich`.
|
|
234
|
+
|
|
235
|
+
**Task tools:** Add `task: true` for long-running async operations. Framework manages lifecycle: creates task → returns ID immediately → runs handler in background with `ctx.progress` → stores result/error → `ctx.signal` for cancellation. See `add-tool` skill for full example.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Adding a Resource
|
|
240
|
+
|
|
241
|
+
**Tool coverage.** Not all MCP clients expose resources — many are tool-only. Verify that resource data is also reachable via the tool surface before relying on resources as an access path.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
245
|
+
|
|
246
|
+
export const myResource = resource('myscheme://{itemId}/data', {
|
|
247
|
+
description: 'Retrieve item data by ID.',
|
|
248
|
+
mimeType: 'application/json',
|
|
249
|
+
params: z.object({ itemId: z.string().describe('Item identifier') }),
|
|
250
|
+
auth: ['item:read'],
|
|
251
|
+
async handler(params, ctx) {
|
|
252
|
+
return { id: params.itemId, status: 'active' };
|
|
253
|
+
},
|
|
254
|
+
list: async () => ({
|
|
255
|
+
resources: [{ uri: 'myscheme://all', name: 'All Items', mimeType: 'application/json' }],
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Handler receives `(params, ctx)` — URI on `ctx.uri` if needed. Optional `size` (bytes) for content size metadata. Large lists must use `extractCursor`/`paginateArray` from `/utils`.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Context
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
interface Context {
|
|
268
|
+
readonly requestId: string;
|
|
269
|
+
readonly timestamp: string;
|
|
270
|
+
readonly tenantId?: string;
|
|
271
|
+
readonly traceId?: string;
|
|
272
|
+
readonly spanId?: string;
|
|
273
|
+
readonly auth?: AuthContext;
|
|
274
|
+
readonly log: ContextLogger; // auto-correlated: requestId, traceId, tenantId
|
|
275
|
+
readonly state: ContextState; // tenant-scoped KV storage
|
|
276
|
+
readonly elicit?: (message: string, schema: z.ZodObject<any>) => Promise<ElicitResult>;
|
|
277
|
+
readonly sample?: (messages: SamplingMessage[], opts?: SamplingOpts) => Promise<CreateMessageResult>;
|
|
278
|
+
readonly notifyResourceListChanged?: (() => void) | undefined; // resource list changed
|
|
279
|
+
readonly notifyResourceUpdated?: ((uri: string) => void) | undefined; // resource content changed
|
|
280
|
+
readonly signal: AbortSignal; // cancellation
|
|
281
|
+
readonly progress?: ContextProgress; // present when task: true
|
|
282
|
+
readonly uri?: URL; // present for resource handlers
|
|
283
|
+
readonly enrich: Enrich; // success-path agent context → structuredContent + content[]; typed on HandlerContext<R, E>
|
|
284
|
+
recoveryFor(reason: string): { recovery: { hint: string } } | {}; // opt-in contract resolver
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `ctx.log`
|
|
289
|
+
|
|
290
|
+
Opt-in domain-specific logging. Methods: `debug`, `info`, `notice`, `warning`, `error`. Auto-includes `requestId`, `traceId`, `tenantId`, `spanId`. Use `ctx.log` in handlers; global `logger` for startup/shutdown/background.
|
|
291
|
+
|
|
292
|
+
### `ctx.state`
|
|
293
|
+
|
|
294
|
+
Tenant-scoped KV. Accepts any serializable value — no manual `JSON.stringify`/`JSON.parse` needed.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
await ctx.state.set('item:123', { name: 'Widget', count: 42 });
|
|
298
|
+
await ctx.state.set('item:123', data, { ttl: 3600 }); // with TTL (seconds)
|
|
299
|
+
const item = await ctx.state.get<Item>('item:123'); // T | null
|
|
300
|
+
const safe = await ctx.state.get('item:123', ItemSchema); // Zod-validated T | null
|
|
301
|
+
await ctx.state.delete('item:123');
|
|
302
|
+
const values = await ctx.state.getMany<Item>(['item:1', 'item:2']); // Map<string, T>
|
|
303
|
+
const page = await ctx.state.list('item:', { cursor, limit: 20 }); // { items, cursor? }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Throws `McpError(InvalidRequest)` if `tenantId` missing. Tenant ID resolution:
|
|
307
|
+
|
|
308
|
+
| Mode | `tenantId` source |
|
|
309
|
+
|:-----|:------------------|
|
|
310
|
+
| stdio (any auth) | `'default'` |
|
|
311
|
+
| HTTP + `MCP_AUTH_MODE=none` | `'default'` (single-tenant by design) |
|
|
312
|
+
| HTTP + `MCP_AUTH_MODE=jwt`/`oauth` | JWT `'tid'` claim — fail-closed if absent |
|
|
313
|
+
|
|
314
|
+
### `ctx.elicit` / `ctx.sample`
|
|
315
|
+
|
|
316
|
+
Check for presence before calling:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
if (ctx.elicit) {
|
|
320
|
+
const result = await ctx.elicit('What format?', z.object({
|
|
321
|
+
format: z.enum(['json', 'csv']).describe('Output format'),
|
|
322
|
+
}));
|
|
323
|
+
if (result.action === 'accept') useFormat(result.content.format);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `ctx.progress`
|
|
328
|
+
|
|
329
|
+
Present when `task: true`. Methods: `setTotal(n)`, `increment(amount?)`, `update(message)`.
|
|
330
|
+
|
|
331
|
+
See `api-context` skill for full details.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Error Handling
|
|
336
|
+
|
|
337
|
+
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
errors: [
|
|
341
|
+
{ reason: 'no_match', code: JsonRpcErrorCode.NotFound,
|
|
342
|
+
when: 'No PMID returned data',
|
|
343
|
+
recovery: 'Try pubmed_search_articles to discover valid PMIDs first.' },
|
|
344
|
+
{ reason: 'queue_full', code: JsonRpcErrorCode.RateLimited,
|
|
345
|
+
when: 'Queue at capacity', retryable: true,
|
|
346
|
+
recovery: 'Wait 30 seconds before retrying or reduce batch size.' },
|
|
347
|
+
],
|
|
348
|
+
async handler(input, ctx) {
|
|
349
|
+
// Static recovery — pulled from the contract via ctx.recoveryFor.
|
|
350
|
+
if (queue.full()) throw ctx.fail('queue_full', undefined, { ...ctx.recoveryFor('queue_full') });
|
|
351
|
+
// Dynamic recovery — interpolate runtime context, override the contract default.
|
|
352
|
+
if (!matched) throw ctx.fail('no_match', `No data for ${input.pmids.length} PMIDs`, {
|
|
353
|
+
pmids: input.pmids,
|
|
354
|
+
recovery: { hint: `Use pubmed_search_articles to discover valid PMIDs.` },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**`ctx.recoveryFor(reason)`** returns `{}` when no contract exists (spread-safe). Typed against the declared reason union on `HandlerContext<R>`. Works in services: `throw validationError(msg, { reason: 'X', ...ctx.recoveryFor('X') })`. Opt-in — author spreads explicitly.
|
|
360
|
+
|
|
361
|
+
**Contracts are inline, per-tool.** Don't extract shared `errors[]` constants — locality is the point, and dynamic `recovery` hints need tool-specific context. Declare domain-specific failures only; **baseline codes** (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) are auto-allowed by conformance lint. The lint scans handler source only — service-layer throws still reach clients via auto-classification.
|
|
362
|
+
|
|
363
|
+
**Fallback for ad-hoc throws** (no contract entry fits, prototype tools, service-layer code): use error factories.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { notFound, validationError } from '@cyanheads/mcp-ts-core/errors';
|
|
367
|
+
throw notFound('Item not found', { itemId: '123' });
|
|
368
|
+
throw validationError('Missing required field: name', { field: 'name' });
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Available factories: `invalidParams`, `invalidRequest`, `notFound`, `forbidden`, `unauthorized`, `validationError`, `conflict`, `rateLimited`, `timeout`, `serviceUnavailable`, `configurationError`, `internalError`, `serializationError`, `databaseError`. All accept `(message, data?, options?)` where `options` is `{ cause?: unknown }`.
|
|
372
|
+
|
|
373
|
+
For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { service, data })` from `/utils` — maps the full status table (401/403/408/422/429/5xx) and captures body + `Retry-After`.
|
|
374
|
+
|
|
375
|
+
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: `McpError` code (preserved as-is) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name → `InternalError` fallback.
|
|
376
|
+
|
|
377
|
+
**Error-path parity.** Tool errors: `content[]` carries markdown with `data.recovery.hint`; `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope.
|
|
378
|
+
|
|
379
|
+
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`. See `api-linter` skill.
|
|
380
|
+
|
|
381
|
+
See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Auth
|
|
386
|
+
|
|
387
|
+
Inline `auth` on definitions (primary pattern): `auth: ['tool:my_tool:read']`. Handler factory checks scopes before calling handler. Dynamic scopes via `checkScopes(ctx, [...])` from `/auth`.
|
|
388
|
+
|
|
389
|
+
**Scope naming:** colon-delimited strings. Conventions used in this codebase:
|
|
390
|
+
|
|
391
|
+
| Surface | Pattern | Example |
|
|
392
|
+
|:--------|:--------|:--------|
|
|
393
|
+
| Tools | `tool:<snake_name>:<verb>` | `tool:inventory_search:read` |
|
|
394
|
+
| Resources | `resource:<kebab-name>:<verb>` *or* domain-led `<domain>:<verb>` | `resource:echo-app-ui:read`, `inventory:read` |
|
|
395
|
+
|
|
396
|
+
Pick one convention per server and stay consistent. Verbs are typically `read`, `write`, `admin`.
|
|
397
|
+
|
|
398
|
+
**Modes** (`MCP_AUTH_MODE`): `none` (default) | `jwt` (local secret via `MCP_AUTH_SECRET_KEY`) | `oauth` (JWKS via `OAUTH_ISSUER_URL`, `OAUTH_AUDIENCE`). See `api-auth` skill for claims, CORS, and detailed config.
|
|
399
|
+
|
|
400
|
+
**Granted scopes** union `scp`, `scope`, and `mcp_tool_scopes` JWT claims. `mcp_tool_scopes` is the OIDC escape hatch (Authentik, Keycloak < 26.5, Zitadel). `MCP_AUTH_DISABLE_SCOPE_CHECKS=true` bypasses scope checks while preserving auth-context verification (signature/audience/issuer/expiry). Logs `WARNING` at startup.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Configuration
|
|
405
|
+
|
|
406
|
+
### Core config
|
|
407
|
+
|
|
408
|
+
Managed by `@cyanheads/mcp-ts-core`. Validated via Zod. Precedence: `createApp()` overrides > env vars > `package.json` (reads `name` → `MCP_SERVER_NAME`, `version` → `MCP_SERVER_VERSION`).
|
|
409
|
+
|
|
410
|
+
| Category | Key Variables |
|
|
411
|
+
|:---------|:-------------|
|
|
412
|
+
| Transport | `MCP_TRANSPORT_TYPE` (`stdio`\|`http`), `MCP_HTTP_PORT`, `MCP_HTTP_HOST`, `MCP_HTTP_ENDPOINT_PATH` |
|
|
413
|
+
| Auth | `MCP_AUTH_MODE`, `MCP_AUTH_SECRET_KEY`, `MCP_AUTH_DISABLE_SCOPE_CHECKS`, `OAUTH_*` |
|
|
414
|
+
| Storage | `STORAGE_PROVIDER_TYPE` (`in-memory`\|`filesystem`\|`supabase`\|`cloudflare-r2`\|`cloudflare-kv`\|`cloudflare-d1`) |
|
|
415
|
+
| LLM | `OPENROUTER_API_KEY`, `OPENROUTER_APP_URL/NAME`, `LLM_DEFAULT_*` |
|
|
416
|
+
| Telemetry | `OTEL_ENABLED`, `OTEL_SERVICE_NAME/VERSION`, `OTEL_EXPORTER_OTLP_*` |
|
|
417
|
+
|
|
418
|
+
### Server config (separate schema)
|
|
419
|
+
|
|
420
|
+
Own Zod schema for domain-specific env vars. **Never merge with core's schema.** Lazy-parse — Workers inject env at request time via `injectEnvVars()`, so no top-level `process.env` reads. Prefer `parseEnvConfig(schema, envMap)` from `/config` over `schema.parse(...)` — it maps schema paths to env var names (`MY_API_KEY is missing` vs. `apiKey: expected string`). Raw `ZodError` from `setup()` is still caught and converted, but messages are worse. See `api-config` skill.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Testing
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
import { describe, expect, it } from 'vitest';
|
|
428
|
+
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
429
|
+
import { myTool } from '@/mcp-server/tools/definitions/my-tool.tool.js';
|
|
430
|
+
|
|
431
|
+
describe('myTool', () => {
|
|
432
|
+
it('returns expected output', async () => {
|
|
433
|
+
const ctx = createMockContext();
|
|
434
|
+
const result = await myTool.handler(myTool.input.parse({ query: 'hello' }), ctx);
|
|
435
|
+
expect(result.result).toBe('Found: hello');
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**`createMockContext` options:** `createMockContext()` (minimal), `{ tenantId: 'test-tenant' }` (enables state), `{ sample: vi.fn() }`, `{ elicit: vi.fn() }`, `{ progress: true }` (task progress).
|
|
441
|
+
|
|
442
|
+
**Fuzz testing:** `fuzzTool`/`fuzzResource`/`fuzzPrompt` from `/testing/fuzz` generate valid and adversarial inputs from Zod schemas via `fast-check`, then assert handler invariants (no crashes, no prototype pollution, no stack trace leaks). Returns a `FuzzReport` for custom assertions.
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { fuzzTool } from '@cyanheads/mcp-ts-core/testing/fuzz';
|
|
446
|
+
|
|
447
|
+
it('survives fuzz testing', async () => {
|
|
448
|
+
const report = await fuzzTool(myTool, { numRuns: 100 });
|
|
449
|
+
expect(report.crashes).toHaveLength(0);
|
|
450
|
+
expect(report.leaks).toHaveLength(0);
|
|
451
|
+
expect(report.prototypePollution).toBe(false);
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Options: `numRuns` (valid inputs, default 50), `numAdversarial` (adversarial inputs, default 30), `seed` (reproducibility), `timeout` (per-call ms, default 5000), `ctx` (`MockContextOptions` for stateful handlers). Also exports `zodToArbitrary(schema)` for custom property-based tests and `ADVERSARIAL_STRINGS` for targeted injection testing.
|
|
456
|
+
|
|
457
|
+
**Vitest config:** Extend core config, add `@/` alias: `resolve: { alias: { '@/': new URL('./src/', import.meta.url).pathname } }`. Construct deps in `beforeEach`. Re-init services per suite.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## API Quick References
|
|
462
|
+
|
|
463
|
+
Detailed method signatures, options, and examples live in skill files. Read the relevant skill before starting a task it covers.
|
|
464
|
+
|
|
465
|
+
### Skill versioning
|
|
466
|
+
|
|
467
|
+
Each `skills/<name>/SKILL.md` carries `metadata.version` in frontmatter. The `maintenance` skill's Phase A uses this to sync consumer copies — replaces the **entire skill directory** as one unit. Without a version bump, Phase A skips the skill (content-hash backstop catches drift, but noisier).
|
|
468
|
+
|
|
469
|
+
**Policy:** Bump `metadata.version` when changing any file under `skills/<name>/` — SKILL.md is the single version knob for the directory. Typo/whitespace fixes exempt. One bump per release cycle suffices.
|
|
470
|
+
|
|
471
|
+
Skills live in `skills/<name>/SKILL.md`. Read the relevant skill before starting a task it covers. The full list is discoverable via the agent's skill registry at session start.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Code Style & Checklist
|
|
476
|
+
|
|
477
|
+
- **Validation:** Zod schemas, all fields need `.describe()`. See Adding a Tool for the JSON-Schema-serializable constraint and form-client safety.
|
|
478
|
+
- **Logging:** Framework auto-instruments all handler calls. `ctx.log` for domain-specific logging in handlers, global `logger` for lifecycle/background
|
|
479
|
+
- **Errors:** handlers throw — error factories (`notFound()`, `validationError()`, etc.) when the code matters, plain `Error` for don't-care cases. Framework catches and classifies.
|
|
480
|
+
- **Secrets:** server config only — no hardcoded credentials
|
|
481
|
+
- **Naming:** kebab-case files, snake_case tool/resource/prompt names, correct suffix
|
|
482
|
+
- **JSDoc:** `@fileoverview` + `@module` required on every file
|
|
483
|
+
- **No fabricated signal:** Don't invent synthetic scores or arbitrary "confidence percentages." Surface real signal.
|
|
484
|
+
- **Builders:** `tool()`/`resource()`/`prompt()` with correct fields (`handler`, `input`, `output`, `format`, `auth`, `args`)
|
|
485
|
+
- **`format()` completeness:** must carry the same data as `output` (parity is lint-enforced — see Adding a Tool)
|
|
486
|
+
- **Auth:** via `auth: ['scope']` on definitions (not HOF wrapper)
|
|
487
|
+
- **Presence checks:** `ctx.elicit`/`ctx.sample` checked before use
|
|
488
|
+
- **Task tools:** use `task: true` flag
|
|
489
|
+
- **Pagination:** large resource lists use `extractCursor`/`paginateArray`
|
|
490
|
+
- **Registration:** definitions exported in `definitions/index.ts` barrel
|
|
491
|
+
- **Tests:** `createMockContext()`, `.handler()` tested directly
|
|
492
|
+
- **Gate:** `bun run devcheck` passes (includes MCP definition linting)
|
|
493
|
+
- **Smoke-test:** `bun run rebuild && bun run start:stdio` (or `start:http`)
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Commands
|
|
498
|
+
|
|
499
|
+
| Command | Purpose |
|
|
500
|
+
|:--------|:--------|
|
|
501
|
+
| `bun run build` | Build library output (`scripts/build.ts`) |
|
|
502
|
+
| `bun run rebuild` | Clean and rebuild (`scripts/clean.ts` + `build`) |
|
|
503
|
+
| `bun run devcheck` | **Use often.** Lint, format, typecheck, MCP definition linting, `bun audit`, `bun outdated` |
|
|
504
|
+
| `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
|
|
505
|
+
| `bun run lint:mcp` | Validate MCP definitions against spec |
|
|
506
|
+
| `bun run format` | Auto-fix Biome lint/format issues (safe fixes only) |
|
|
507
|
+
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior, not just formatting |
|
|
508
|
+
| `bun run test` | Unit/integration tests |
|
|
509
|
+
| `bun run start:stdio` | Production mode (stdio, after build) |
|
|
510
|
+
| `bun run start:http` | Production mode (HTTP, after build) |
|
|
511
|
+
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
|
|
512
|
+
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync with `changelog/` (used by devcheck) |
|
|
513
|
+
|
|
514
|
+
After `bun update --latest`, run the `maintenance` skill to investigate changelogs, adopt upstream changes, and sync project skills.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Changelog
|
|
519
|
+
|
|
520
|
+
Directory-based. Source of truth: `changelog/<major.minor>.x/<version>.md` — one file per release (e.g. `changelog/0.5.x/0.5.4.md`), shipped in the npm package for direct agent access. `changelog/template.md` is the format reference (never edited).
|
|
521
|
+
|
|
522
|
+
`CHANGELOG.md` is a **navigation index** — clickable headers + one-line summaries from frontmatter. Regenerated by `bun run changelog:build`; `changelog:check` hard-fails on drift in devcheck. Never hand-edit — edit the per-version file and rerun the build.
|
|
523
|
+
|
|
524
|
+
### Per-version file format
|
|
525
|
+
|
|
526
|
+
```markdown
|
|
527
|
+
---
|
|
528
|
+
summary: "One-line headline, ≤350 chars, no markdown" # required
|
|
529
|
+
breaking: false # optional, default false
|
|
530
|
+
security: false # optional, default false
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
# 0.5.4 — 2026-04-20
|
|
534
|
+
|
|
535
|
+
## Added
|
|
536
|
+
|
|
537
|
+
- ...
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Frontmatter fields:**
|
|
541
|
+
|
|
542
|
+
| Field | Required | Purpose |
|
|
543
|
+
|:------|:---------|:--------|
|
|
544
|
+
| `summary` | yes | Rollup index line. ≤350 chars, no markdown, single line. Write like a GitHub Release title. |
|
|
545
|
+
| `breaking` | no (default `false`) | Flags releases with breaking changes. Renders as `· ⚠️ Breaking` badge in the rollup. Agents running the `maintenance` skill read this to prioritize review. |
|
|
546
|
+
| `security` | no (default `false`) | Flags releases with security fixes. Renders as `· 🛡️ Security` badge in the rollup so users can triage upgrade urgency. Pairs with the `## Security` body section. |
|
|
547
|
+
| `agent-notes` | no | Free-form adoption notes for downstream `maintenance` agents — new files to create, fields to populate, skills to re-run, one-time migration steps. Not rendered in `CHANGELOG.md`; consumed only by agents running the `maintenance` skill on consumer projects. Omit when there's nothing to say. |
|
|
548
|
+
|
|
549
|
+
Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 350 chars or malformed boolean fails `changelog:check`.
|
|
550
|
+
|
|
551
|
+
**Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Omit empty sections. Pre-release versions consolidate as sub-headers inside the final version's file — no separate files per pre-release.
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Publishing
|
|
556
|
+
|
|
557
|
+
If the user requests it, run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test:all`), pushes commits and tags, and publishes to every applicable destination. After pushing, create a GitHub Release on the annotated tag (`gh release create v<VERSION> --verify-tag --notes-from-tag`) — no assets to attach, but the Release surfaces the tag's notes in the repo UI. **Skip the Docker build/push step** — this framework package is consumed via npm, not as a container image.
|
|
558
|
+
|
|
559
|
+
**Tag annotations render as GitHub Release bodies** via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject must omit the version number (GitHub prepends `v<VERSION>:`). Body uses Keep a Changelog sections with bullets. See `changelog/template.md` for the full format reference including an example.
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**Version:** 0.9.
|
|
4
|
+
**Version:** 0.9.16
|
|
5
5
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
6
6
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
7
7
|
**Zod:** ^4.4.3
|
|
@@ -503,7 +503,8 @@ Skills live in `skills/<name>/SKILL.md`. Read the relevant skill before starting
|
|
|
503
503
|
| `bun run devcheck` | **Use often.** Lint, format, typecheck, MCP definition linting, `bun audit`, `bun outdated` |
|
|
504
504
|
| `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
|
|
505
505
|
| `bun run lint:mcp` | Validate MCP definitions against spec |
|
|
506
|
-
| `bun run format` | Auto-fix Biome lint/format issues |
|
|
506
|
+
| `bun run format` | Auto-fix Biome lint/format issues (safe fixes only) |
|
|
507
|
+
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior, not just formatting |
|
|
507
508
|
| `bun run test` | Unit/integration tests |
|
|
508
509
|
| `bun run start:stdio` | Production mode (stdio, after build) |
|
|
509
510
|
| `bun run start:http` | Production mode (HTTP, after build) |
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
7
7
|
|
|
8
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
|
|
9
9
|
|
|
10
10
|
[](https://modelcontextprotocol.io/) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
@@ -73,7 +73,7 @@ cd my-mcp-server
|
|
|
73
73
|
bun install
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
You get a scaffolded project with `CLAUDE.md`, Agent Skills, plugin metadata (Codex + Claude Code), and a `src/` tree ready for your tools. Infrastructure — transports, auth, storage, telemetry, lifecycle, linting — lives in `node_modules`. What's left is domain: which APIs to wrap, which workflows to expose.
|
|
76
|
+
You get a scaffolded project with `CLAUDE.md`/`AGENTS.md`, Agent Skills, plugin metadata (Codex + Claude Code), and a `src/` tree ready for your tools. Infrastructure — transports, auth, storage, telemetry, lifecycle, linting — lives in `node_modules`. What's left is domain: which APIs to wrap, which workflows to expose.
|
|
77
77
|
|
|
78
78
|
Start your coding agent (i.e. Claude Code, Codex) and describe what you want. The agent knows what to do from there. The included Agent Skills cover the full cycle: `setup`, `design-mcp-server`, scaffolding, testing, `security-pass`, `release-and-publish`, `maintenance`, & more.
|
|
79
79
|
|
|
@@ -166,7 +166,7 @@ my-mcp-server/
|
|
|
166
166
|
prompts/definitions/ # Prompt definitions (.prompt.ts)
|
|
167
167
|
package.json
|
|
168
168
|
tsconfig.json # extends @cyanheads/mcp-ts-core/tsconfig.base.json
|
|
169
|
-
CLAUDE.md
|
|
169
|
+
CLAUDE.md / AGENTS.md # Point to core's CLAUDE.md / AGENTS.md for framework docs
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
No `src/utils/`, no `src/storage/`, no `src/types-global/`, no `src/mcp-server/transports/` — infrastructure lives in `node_modules`.
|
|
@@ -187,7 +187,7 @@ All core config is Zod-validated from environment variables. Server-specific con
|
|
|
187
187
|
| `OTEL_ENABLED` | Enable OpenTelemetry | `false` |
|
|
188
188
|
| `OPENROUTER_API_KEY` | OpenRouter LLM API key | — |
|
|
189
189
|
|
|
190
|
-
See [CLAUDE.md](CLAUDE.md) for the full configuration reference.
|
|
190
|
+
See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the full configuration reference.
|
|
191
191
|
|
|
192
192
|
## API overview
|
|
193
193
|
|