@cyanheads/mcp-ts-core 0.9.6 → 0.9.8
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/CLAUDE.md +3 -3
- package/README.md +12 -8
- package/biome.json +1 -1
- package/changelog/0.9.x/0.9.7.md +18 -0
- package/changelog/0.9.x/0.9.8.md +24 -0
- package/changelog/template.md +26 -0
- package/package.json +3 -4
- package/skills/add-app-tool/SKILL.md +6 -4
- package/skills/add-export/SKILL.md +10 -8
- package/skills/add-prompt/SKILL.md +15 -8
- package/skills/add-provider/SKILL.md +29 -12
- package/skills/add-resource/SKILL.md +20 -11
- package/skills/add-service/SKILL.md +15 -17
- package/skills/add-test/SKILL.md +50 -9
- package/skills/add-tool/SKILL.md +13 -6
- package/skills/api-auth/SKILL.md +3 -2
- package/skills/api-canvas/SKILL.md +43 -6
- package/skills/api-config/SKILL.md +6 -0
- package/skills/api-context/SKILL.md +9 -3
- package/skills/api-errors/SKILL.md +5 -5
- package/skills/api-linter/SKILL.md +32 -9
- package/skills/api-services/SKILL.md +1 -1
- package/skills/api-services/references/graph.md +1 -1
- package/skills/api-services/references/speech.md +1 -1
- package/skills/api-telemetry/SKILL.md +5 -5
- package/skills/api-testing/SKILL.md +9 -1
- package/skills/api-utils/SKILL.md +1 -1
- package/skills/api-workers/SKILL.md +12 -5
- package/skills/design-mcp-server/SKILL.md +20 -8
- package/skills/field-test/SKILL.md +9 -7
- package/skills/git-wrapup/SKILL.md +218 -0
- package/skills/maintenance/SKILL.md +8 -6
- package/skills/migrate-mcp-ts-template/SKILL.md +11 -7
- package/skills/multi-server-orchestration/SKILL.md +17 -5
- package/skills/multi-server-orchestration/references/greenfield-buildout.md +32 -2
- package/skills/multi-server-orchestration/references/maintenance-pass.md +11 -3
- package/skills/multi-server-orchestration/references/release-and-publish-pass.md +14 -25
- package/skills/multi-server-orchestration/references/wrapup-pass.md +15 -36
- package/skills/polish-docs-meta/SKILL.md +3 -1
- package/skills/polish-docs-meta/references/package-meta.md +1 -1
- package/skills/polish-docs-meta/references/readme.md +14 -1
- package/skills/release-and-publish/SKILL.md +20 -7
- package/skills/report-issue-framework/SKILL.md +5 -3
- package/skills/report-issue-local/SKILL.md +10 -5
- package/skills/setup/SKILL.md +13 -8
- package/skills/tool-defs-analysis/SKILL.md +6 -3
- package/templates/AGENTS.md +14 -6
- package/templates/CLAUDE.md +14 -6
- package/templates/changelog/template.md +26 -0
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.8
|
|
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
|
|
@@ -316,7 +316,7 @@ if (ctx.elicit) {
|
|
|
316
316
|
const result = await ctx.elicit('What format?', z.object({
|
|
317
317
|
format: z.enum(['json', 'csv']).describe('Output format'),
|
|
318
318
|
}));
|
|
319
|
-
if (result.action === 'accept') useFormat(result.
|
|
319
|
+
if (result.action === 'accept') useFormat(result.content.format);
|
|
320
320
|
}
|
|
321
321
|
```
|
|
322
322
|
|
|
@@ -550,4 +550,4 @@ Badge order when both set: `· ⚠️ Breaking · 🛡️ Security`. Summary > 3
|
|
|
550
550
|
|
|
551
551
|
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.
|
|
552
552
|
|
|
553
|
-
**Tag
|
|
553
|
+
**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/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
[](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
|
|
11
11
|
|
|
12
12
|
[](https://modelcontextprotocol.io/) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
13
13
|
|
|
@@ -23,6 +23,7 @@ The framework handles the plumbing: transports, auth, config, logging, telemetry
|
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
|
|
26
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
26
27
|
|
|
27
28
|
const greet = tool('greet', {
|
|
28
29
|
description: 'Greet someone by name and return a personalized message.',
|
|
@@ -38,7 +39,7 @@ const greet = tool('greet', {
|
|
|
38
39
|
reason: 'name_blocked',
|
|
39
40
|
code: JsonRpcErrorCode.Forbidden,
|
|
40
41
|
when: 'The provided name is on the configured block list.',
|
|
41
|
-
recovery: 'Use a different name.',
|
|
42
|
+
recovery: 'Use a different name that is not on the block list.',
|
|
42
43
|
},
|
|
43
44
|
],
|
|
44
45
|
handler: async (input, ctx) => {
|
|
@@ -66,7 +67,7 @@ Start your coding agent (i.e. Claude Code, Codex) and describe what you want. Th
|
|
|
66
67
|
|
|
67
68
|
### What you get
|
|
68
69
|
|
|
69
|
-
Here's what tool definitions look like:
|
|
70
|
+
Here's what tool definitions look like. Add `format()` to render structured output as markdown — different MCP clients read different surfaces (`structuredContent` vs `content[]`), and `format()` ensures both carry the same data:
|
|
70
71
|
|
|
71
72
|
```ts
|
|
72
73
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
@@ -151,13 +152,13 @@ It also works on Cloudflare Workers with `createWorkerHandler()` — same defini
|
|
|
151
152
|
- **Unified Context** — one `ctx` for logging, tenant-scoped storage, elicitation, sampling, cancellation, and task progress.
|
|
152
153
|
- **Auth** — `auth: ['scope']` on definitions, checked before dispatch (no wrapper code). Modes: `none`, `jwt`, or `oauth` (local secret or JWKS).
|
|
153
154
|
- **Task tools** — `task: true` for long-running ops; framework manages create/poll/progress/complete/cancel.
|
|
154
|
-
- **Definition linter** — validates names, schemas, auth scopes, annotations, format-parity, and cross-vendor JSON Schema portability at
|
|
155
|
+
- **Definition linter** — validates names, schemas, auth scopes, annotations, format-parity, and cross-vendor JSON Schema portability at build time. Run via `lint:mcp` or `devcheck` — not invoked at server startup.
|
|
155
156
|
- **Typed error contracts** — declare `errors: [{ reason, code, when, recovery, retryable? }]` and handlers get a typed `ctx.fail(reason, …)`. Contracts publish in `tools/list` so clients preview failure modes; the linter cross-checks the handler. Factories (`notFound()`, `httpErrorFromResponse()`, …) cover ad-hoc throws; plain `Error` auto-classifies.
|
|
156
157
|
- **Multi-backend storage** — `in-memory`, filesystem, Supabase, Cloudflare D1/KV/R2. Swap via env var; handlers don't change.
|
|
157
158
|
- **DataCanvas (optional)** — Tier 3 SQL/analytical workspace backed by DuckDB. Register tabular data from upstream APIs, run SQL across registered tables, export CSV/Parquet/JSON. Token-sharing model (opaque `canvas_id`) for multi-agent collaboration; sliding TTL + per-tenant scoping. Opt-in via `CANVAS_PROVIDER_TYPE=duckdb`; fails closed on Workers.
|
|
158
159
|
- **Observability** — Pino logging + optional OpenTelemetry traces/metrics. Request correlation and tool metrics automatic.
|
|
159
160
|
- **Tiered dependencies** — parsers, OTEL SDK, Supabase, OpenAI as optional peers. Install what you use.
|
|
160
|
-
- **Agent-first DX** — ships `CLAUDE.md` / `AGENTS.md`
|
|
161
|
+
- **Agent-first DX** — ships `CLAUDE.md` / `AGENTS.md` and Agent Skills that give your coding agent full framework knowledge — it can scaffold tools, write tests, run security audits, and ship releases without you writing the boilerplate.
|
|
161
162
|
|
|
162
163
|
## Server structure
|
|
163
164
|
|
|
@@ -206,7 +207,7 @@ See [CLAUDE.md](CLAUDE.md) for the full configuration reference.
|
|
|
206
207
|
| Function | Purpose |
|
|
207
208
|
|:---------|:--------|
|
|
208
209
|
| `createApp(options)` | Node.js server — handles full lifecycle |
|
|
209
|
-
| `createWorkerHandler(options)` | Cloudflare Workers — returns
|
|
210
|
+
| `createWorkerHandler(options)` | Cloudflare Workers — returns an `ExportedHandler` |
|
|
210
211
|
|
|
211
212
|
### Builders
|
|
212
213
|
|
|
@@ -228,9 +229,12 @@ Handlers receive a unified `Context` object:
|
|
|
228
229
|
| `ctx.state` | `ContextState` | Tenant-scoped key-value storage |
|
|
229
230
|
| `ctx.elicit` | `Function?` | Ask the user for input (when client supports it) |
|
|
230
231
|
| `ctx.sample` | `Function?` | Request LLM completion from the client |
|
|
232
|
+
| `ctx.fail` | `(reason, msg?, data?) => McpError` | Typed error throw — reason checked against `errors[]` contract at compile time |
|
|
231
233
|
| `ctx.signal` | `AbortSignal` | Cancellation signal |
|
|
232
234
|
| `ctx.notifyResourceUpdated` | `Function?` | Notify subscribed clients a resource changed |
|
|
233
235
|
| `ctx.notifyResourceListChanged` | `Function?` | Notify clients the resource list changed |
|
|
236
|
+
| `ctx.notifyPromptListChanged` | `Function?` | Notify clients the prompt list changed |
|
|
237
|
+
| `ctx.notifyToolListChanged` | `Function?` | Notify clients the tool list changed |
|
|
234
238
|
| `ctx.progress` | `ContextProgress?` | Task progress reporting (when `task: true`) |
|
|
235
239
|
| `ctx.requestId` | `string` | Unique request ID |
|
|
236
240
|
| `ctx.tenantId` | `string?` | Tenant ID (JWT `tid` claim, or `'default'` for stdio and HTTP+`MCP_AUTH_MODE=none`) |
|
|
@@ -296,9 +300,9 @@ Also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL_ST
|
|
|
296
300
|
|
|
297
301
|
## Documentation
|
|
298
302
|
|
|
299
|
-
- **[CLAUDE.md/AGENTS.md](CLAUDE.md)** — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package
|
|
303
|
+
- **[CLAUDE.md/AGENTS.md](CLAUDE.md)** — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package and is auto-accessible in your project after `init`.
|
|
300
304
|
- **[docs/telemetry/](docs/telemetry/)** — OpenTelemetry: full catalog of spans, metrics, and attributes the framework emits ([observability.md](docs/telemetry/observability.md)), plus an example Grafana dashboard and vendor-agnostic query recipes for Datadog, New Relic, Honeycomb ([dashboards.md](docs/telemetry/dashboards.md)).
|
|
301
|
-
- **[CHANGELOG.md](CHANGELOG.md)** — Version history
|
|
305
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — Version history. Each entry includes a summary, migration notes, and links to commits/issues.
|
|
302
306
|
|
|
303
307
|
## Development
|
|
304
308
|
|
package/biome.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "biome noDuplicateDependencies off, structured tag annotation format, release artifact verification, skills updates"
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.9.7 — 2026-05-23
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
|
|
10
|
+
- `biome.json`: `noDuplicateDependencies` warn → off (zod intentionally in both `dependencies` and `peerDependencies`)
|
|
11
|
+
- `CLAUDE.md`/`AGENTS.md`: tag annotation guidance rewritten — structured markdown format with `changelog/template.md` reference
|
|
12
|
+
- `changelog/template.md`: tag annotation format example added (subject, prose intro, sectioned bullets, dep arrows, test footer)
|
|
13
|
+
- `templates/CLAUDE.md`/`templates/AGENTS.md`: install badge config updated (split `command`/`args`, `env` for API keys), tag annotation guidance added
|
|
14
|
+
- `templates/changelog/template.md`: tag annotation format example synced from core
|
|
15
|
+
- `skills/multi-server-orchestration/references/wrapup-pass.md` v1.2: structured tag annotation format replaces flat-string guidance
|
|
16
|
+
- `skills/release-and-publish/SKILL.md`: Step 9 — verify published artifacts are reachable (npm, MCP Registry, GH Release, GHCR)
|
|
17
|
+
- `skills/multi-server-orchestration/references/greenfield-buildout.md`: recommended additional phases (design gate, test quality review, field test, pre-launch sweep) and 5 new gotchas
|
|
18
|
+
- `skills/polish-docs-meta/references/readme.md`: agent-friendly output subsection added to Features section guidance
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Docs fixes, README context table updates, skill sync, @hono/node-server ^2.0.3 → ^2.0.4"
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.9.8 — 2026-05-24
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`git-wrapup` skill** — codifies the version-bump, changelog, commit, and tag workflow as a reusable agent skill.
|
|
12
|
+
- **`ctx.fail`** documented in README context table — typed error throw checked against the `errors[]` contract at compile time.
|
|
13
|
+
- **`ctx.notifyPromptListChanged`**, **`ctx.notifyToolListChanged`** documented in README context table.
|
|
14
|
+
|
|
15
|
+
## Changed
|
|
16
|
+
|
|
17
|
+
- **README** — clarified `format()` purpose, corrected linter description (build-time, not startup), updated `createWorkerHandler` return type, expanded Agent-first DX description.
|
|
18
|
+
- **Skills** — bulk refresh across 37 skill files; version bumps and content updates from upstream.
|
|
19
|
+
- **`@hono/node-server`** ^2.0.3 → ^2.0.4.
|
|
20
|
+
- **`@cloudflare/workers-types`** ^4.20260523.1 → ^4.20260524.1.
|
|
21
|
+
|
|
22
|
+
## Fixed
|
|
23
|
+
|
|
24
|
+
- **`ctx.elicit` example** in CLAUDE.md/AGENTS.md — `result.data.format` → `result.content.format` to match SDK's `ElicitResult.content` property.
|
package/changelog/template.md
CHANGED
|
@@ -66,6 +66,32 @@ security: false
|
|
|
66
66
|
Never speculate on a future number — `#42` for an upcoming PR silently
|
|
67
67
|
resolves to whatever real item already owns 42, and timeline previews pull
|
|
68
68
|
in that unrelated item's metadata.
|
|
69
|
+
|
|
70
|
+
TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
|
|
71
|
+
via `gh release create --notes-from-tag`. The tag is a derivative of this
|
|
72
|
+
changelog entry — a condensed, scannable version, not a copy. Format:
|
|
73
|
+
|
|
74
|
+
<theme — omit version number, GitHub prepends it>
|
|
75
|
+
← blank line
|
|
76
|
+
<1-2 sentence context: what this release does>
|
|
77
|
+
← blank line
|
|
78
|
+
Dependency bumps: ← section header
|
|
79
|
+
← blank line
|
|
80
|
+
- `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
|
|
81
|
+
← blank line
|
|
82
|
+
Changed: ← only sections with entries
|
|
83
|
+
← blank line
|
|
84
|
+
- `format()` output includes `query` in text mode
|
|
85
|
+
← blank line
|
|
86
|
+
Added:
|
|
87
|
+
← blank line
|
|
88
|
+
- `manifest.json` scaffolded for MCPB bundle support
|
|
89
|
+
- Install badges (Claude Desktop, Cursor, VS Code)
|
|
90
|
+
← blank line
|
|
91
|
+
<N> tests pass; `bun run devcheck` clean. ← footer
|
|
92
|
+
|
|
93
|
+
Never a flat comma-separated string. Always structured markdown with
|
|
94
|
+
sections. The tag must scan well as a rendered GitHub Release page.
|
|
69
95
|
-->
|
|
70
96
|
|
|
71
97
|
## Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"devDependencies": {
|
|
171
171
|
"@biomejs/biome": "2.4.15",
|
|
172
172
|
"@cloudflare/vitest-pool-workers": "^0.16.9",
|
|
173
|
-
"@cloudflare/workers-types": "^4.
|
|
173
|
+
"@cloudflare/workers-types": "^4.20260524.1",
|
|
174
174
|
"@duckdb/node-api": "^1.5.3-r.1",
|
|
175
175
|
"@hono/otel": "^1.1.2",
|
|
176
176
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.218.0",
|
|
@@ -228,7 +228,6 @@
|
|
|
228
228
|
"cloudflare-workers",
|
|
229
229
|
"declarative",
|
|
230
230
|
"framework",
|
|
231
|
-
"llm",
|
|
232
231
|
"mcp",
|
|
233
232
|
"mcp-server",
|
|
234
233
|
"mcp-framework",
|
|
@@ -270,7 +269,7 @@
|
|
|
270
269
|
},
|
|
271
270
|
"dependencies": {
|
|
272
271
|
"@hono/mcp": "^0.3.0",
|
|
273
|
-
"@hono/node-server": "^2.0.
|
|
272
|
+
"@hono/node-server": "^2.0.4",
|
|
274
273
|
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
275
274
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
276
275
|
"@opentelemetry/api": "^1.9.1",
|
|
@@ -30,11 +30,11 @@ MCP Apps extend the standard tool pattern with an interactive HTML UI rendered i
|
|
|
30
30
|
|
|
31
31
|
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.
|
|
32
32
|
|
|
33
|
-
For the full API, Context interface, and error codes, read `
|
|
33
|
+
For the full API, Context interface, and error codes, read the framework's `CLAUDE.md` (loaded at session start).
|
|
34
34
|
|
|
35
35
|
## Steps
|
|
36
36
|
|
|
37
|
-
1. **
|
|
37
|
+
1. **Confirm the three conditions** in "When to Use" apply — if any is uncertain, default to `add-tool` instead. Then **gather** the tool's name, purpose, input/output shape, and what the UI should display from the user's request — ask only if genuinely absent
|
|
38
38
|
2. **Choose a URI** — convention: `ui://{{tool-name}}/app.html`
|
|
39
39
|
3. **Create the app tool** at `src/mcp-server/tools/definitions/{{tool-name}}.app-tool.ts`
|
|
40
40
|
4. **Create the app resource** at `src/mcp-server/resources/definitions/{{tool-name}}-ui.app-resource.ts`
|
|
@@ -233,10 +233,12 @@ If the repo already uses `definitions/index.ts` barrels, update those instead of
|
|
|
233
233
|
- [ ] App resource created at `src/mcp-server/resources/definitions/{{tool-name}}-ui.app-resource.ts` using `appResource()`
|
|
234
234
|
- [ ] `resourceUri` matches between tool and resource (`ui://{{tool-name}}/app.html`)
|
|
235
235
|
- [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types
|
|
236
|
-
- [ ] `format()`
|
|
237
|
-
- [ ] App resource `_meta.ui.csp`
|
|
236
|
+
- [ ] `format()` first block is `JSON.stringify(result)` — the full output object for the UI to parse via `app.ontoolresult`. Subsequent blocks are human-readable, content-complete fallback for non-app hosts and LLMs
|
|
237
|
+
- [ ] App resource `_meta.ui.csp.resourceDomains` lists every external domain loaded by the UI
|
|
238
238
|
- [ ] UI bundles or inlines the client SDK for the shipped HTML, and handles `app.ontoolresult`
|
|
239
239
|
- [ ] UI applies host context updates via `app.onhostcontextchanged`
|
|
240
|
+
- [ ] App resource has a `list` callback returning at least one URI so resource-aware clients can discover it
|
|
240
241
|
- [ ] Both registered in the project's existing `createApp()` arrays (directly or via barrels)
|
|
242
|
+
- [ ] Handler tested directly via `createMockContext()`, or `add-test` skill run to scaffold the test file
|
|
241
243
|
- [ ] `bun run devcheck` passes (linter validates `_meta.ui` and tool/resource pairing)
|
|
242
244
|
- [ ] Smoke-tested with `bun run rebuild && bun run start:stdio` (or `start:http`)
|
|
@@ -13,7 +13,7 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
Subpath exports are defined in `package.json` under the `exports` field. Each subpath maps to a source entry point that gets compiled to `dist/`. The exports catalog in `CLAUDE.md` must stay in sync with `package.json`.
|
|
15
15
|
|
|
16
|
-
The build uses `tsconfig.build.json` (not `tsconfig.json`) with `rootDir: ./src` and `include: ["src/**/*"]`. This means every source file at `src/foo/bar.ts` compiles to `dist/foo/bar.js` — the `dist/`
|
|
16
|
+
The build uses `tsconfig.build.json` (not `tsconfig.json`) with `rootDir: ./src` and `include: ["src/**/*"]`. This means every source file at `src/foo/bar.ts` compiles to `dist/foo/bar.js` — the `dist/` path in each export entry must match wherever `tsc` produces the compiled output for the named source file. Choose your source file location to produce the `dist/` path you want in the export entry.
|
|
17
17
|
|
|
18
18
|
## Steps
|
|
19
19
|
|
|
@@ -28,16 +28,16 @@ The build uses `tsconfig.build.json` (not `tsconfig.json`) with `rootDir: ./src`
|
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
3. **Update the exports catalog** in `CLAUDE.md` — add a row to the table
|
|
31
|
+
3. **Update the exports catalog** in both `CLAUDE.md` and `AGENTS.md` — add a row to the table. These files must stay byte-identical; the simplest approach is `cp CLAUDE.md AGENTS.md` after editing
|
|
32
32
|
4. **Build** with `bun run build` to generate `dist/` output
|
|
33
|
-
5. **Verify the export**
|
|
33
|
+
5. **Verify the export** resolves through the package's `exports` map:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
# Confirm the compiled file exists at the expected dist path
|
|
37
37
|
ls dist/utils/new-util.js
|
|
38
38
|
|
|
39
|
-
# Confirm the
|
|
40
|
-
bun -e "import('
|
|
39
|
+
# Confirm the subpath export resolves correctly (tests the exports map, not just the dist file)
|
|
40
|
+
bun -e "import('@cyanheads/mcp-ts-core/newutil').then(m => console.log(Object.keys(m)))"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
6. **Run `bun run devcheck`** to verify
|
|
@@ -46,7 +46,7 @@ The build uses `tsconfig.build.json` (not `tsconfig.json`) with `rootDir: ./src`
|
|
|
46
46
|
|
|
47
47
|
| Convention | Rule |
|
|
48
48
|
|:-----------|:-----|
|
|
49
|
-
| Subpath | lowercase, no underscores (e.g., `utils/
|
|
49
|
+
| Subpath | all-lowercase, no underscores (e.g., `utils`, `storage/types`, `testing/fuzz`) |
|
|
50
50
|
| Source file | kebab-case (e.g., `error-handler.ts`) |
|
|
51
51
|
| Export name | camelCase for values, PascalCase for types |
|
|
52
52
|
|
|
@@ -54,7 +54,9 @@ The build uses `tsconfig.build.json` (not `tsconfig.json`) with `rootDir: ./src`
|
|
|
54
54
|
|
|
55
55
|
- [ ] Source entry point file created with JSDoc header
|
|
56
56
|
- [ ] Subpath added to `package.json` `exports` with `types` and `import` conditions
|
|
57
|
-
- [ ] Exports catalog in `CLAUDE.md`
|
|
57
|
+
- [ ] Exports catalog updated in both `CLAUDE.md` and `AGENTS.md` (must be byte-identical)
|
|
58
|
+
- [ ] If the new export has optional peer dependencies: entries added to both `peerDependencies` and `peerDependenciesMeta` in `package.json`
|
|
58
59
|
- [ ] `bun run build` succeeds
|
|
59
|
-
- [ ] Compiled file exists at expected `dist/` path and
|
|
60
|
+
- [ ] Compiled file exists at expected `dist/` path and subpath import resolves correctly
|
|
61
|
+
- [ ] Integration test at `tests/integration/package-consumer.int.test.ts` updated: new subpath added to the import spec list and `toHaveLength` count incremented
|
|
60
62
|
- [ ] `bun run devcheck` passes
|
|
@@ -11,15 +11,13 @@ metadata:
|
|
|
11
11
|
|
|
12
12
|
## Context
|
|
13
13
|
|
|
14
|
-
Prompts use the `prompt()` builder from `@cyanheads/mcp-ts-core`. Each prompt lives in `src/mcp-server/prompts/definitions/` with a `.prompt.ts` suffix
|
|
14
|
+
Prompts use the `prompt()` builder from `@cyanheads/mcp-ts-core`. Each prompt lives in `src/mcp-server/prompts/definitions/` with a `.prompt.ts` suffix. The standard registration pattern uses a `definitions/index.ts` barrel that collects all prompts into an `allPromptDefinitions` array for `createApp()`. Fresh scaffolds start with direct imports in `src/index.ts` — the barrel is introduced as definitions grow. Match the pattern already used by the project you're editing.
|
|
15
15
|
|
|
16
|
-
Prompts are pure message templates — no `Context`, no auth, no side effects.
|
|
17
|
-
|
|
18
|
-
For the full `prompt()` API, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
|
|
16
|
+
Prompts are pure message templates — no `Context`, no auth, no side effects. `generate` can be sync or async (returns `PromptMessage[] | Promise<PromptMessage[]>`).
|
|
19
17
|
|
|
20
18
|
## Steps
|
|
21
19
|
|
|
22
|
-
1. **
|
|
20
|
+
1. **Gather** the prompt's name, purpose, and arguments from the user's request — ask only if genuinely absent
|
|
23
21
|
2. **Create the file** at `src/mcp-server/prompts/definitions/{{prompt-name}}.prompt.ts`
|
|
24
22
|
3. **Register** the prompt in the project's existing `createApp()` prompt list (directly in `src/index.ts` for fresh scaffolds, or via a barrel if the repo already has one)
|
|
25
23
|
4. **Run `bun run devcheck`** to verify
|
|
@@ -36,6 +34,8 @@ import { prompt, z } from '@cyanheads/mcp-ts-core';
|
|
|
36
34
|
|
|
37
35
|
export const {{PROMPT_EXPORT}} = prompt('{{prompt_name}}', {
|
|
38
36
|
description: '{{PROMPT_DESCRIPTION}}',
|
|
37
|
+
// args is optional — omit entirely for prompts with no parameters.
|
|
38
|
+
// When present, all fields need .describe(). Only JSON-Schema-serializable types allowed.
|
|
39
39
|
args: z.object({
|
|
40
40
|
// All fields need .describe()
|
|
41
41
|
}),
|
|
@@ -86,14 +86,21 @@ await createApp({
|
|
|
86
86
|
});
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
If the repo already uses `src/mcp-server/prompts/definitions/index.ts`,
|
|
89
|
+
If the repo already uses `src/mcp-server/prompts/definitions/index.ts`, add the export to that barrel instead:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
export { {{PROMPT_EXPORT}} } from './{{prompt-name}}.prompt.js';
|
|
93
|
+
```
|
|
90
94
|
|
|
91
95
|
## Checklist
|
|
92
96
|
|
|
93
97
|
- [ ] File created at `src/mcp-server/prompts/definitions/{{prompt-name}}.prompt.ts`
|
|
94
|
-
- [ ]
|
|
98
|
+
- [ ] Prompt name passed to `prompt()` uses snake_case
|
|
99
|
+
- [ ] `description` field set (lint warns if absent, but `devcheck` won't hard-fail — verify it's present)
|
|
100
|
+
- [ ] All Zod `args` fields have `.describe()` annotations — or `args` omitted entirely for no-parameter prompts
|
|
101
|
+
- [ ] `args` fields use only JSON-Schema-serializable Zod types (no `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.custom()`, etc.)
|
|
95
102
|
- [ ] JSDoc `@fileoverview` and `@module` header present
|
|
96
|
-
- [ ] `generate` function returns
|
|
103
|
+
- [ ] `generate` function present and returns at least one `{ role, content: { type: 'text', text } }` message
|
|
97
104
|
- [ ] No side effects — prompts are pure templates
|
|
98
105
|
- [ ] Registered in the project's existing `createApp()` prompt list (directly or via barrel)
|
|
99
106
|
- [ ] `bun run devcheck` passes
|
|
@@ -15,6 +15,10 @@ Providers implement interfaces defined in core. They are selected at runtime via
|
|
|
15
15
|
(e.g., `STORAGE_PROVIDER_TYPE`). Tier 3 providers lazy-load their dependencies to keep the
|
|
16
16
|
core bundle small.
|
|
17
17
|
|
|
18
|
+
Providers live inside the package source tree — import the interface via relative path
|
|
19
|
+
(e.g., `import type { IStorageProvider } from '../core/IStorageProvider.js'`), not via the
|
|
20
|
+
package subpath exports (those are for consumers).
|
|
21
|
+
|
|
18
22
|
## Provider interfaces
|
|
19
23
|
|
|
20
24
|
| Domain | Interface file |
|
|
@@ -32,9 +36,11 @@ these flags drive routing in `SpeechService`.
|
|
|
32
36
|
|
|
33
37
|
Provider file location and naming differ by domain:
|
|
34
38
|
|
|
35
|
-
- **Storage** — nested subdirectory, PascalCase file
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
- **Storage** — nested subdirectory, camelCase directory name, PascalCase-suffixed provider file.
|
|
40
|
+
Each provider gets its own subdirectory for the provider file plus any co-located types:
|
|
41
|
+
`src/storage/providers/{{providerName}}/{{providerName}}Provider.ts`
|
|
42
|
+
(e.g., `src/storage/providers/inMemory/inMemoryProvider.ts`,
|
|
43
|
+
`src/storage/providers/supabase/supabaseProvider.ts` + `supabase.types.ts`)
|
|
38
44
|
|
|
39
45
|
- **LLM / Speech** — flat directory, kebab-case with `.provider.ts` suffix:
|
|
40
46
|
`src/services/llm/providers/{{provider-name}}.provider.ts`
|
|
@@ -63,8 +69,11 @@ Provider file location and naming differ by domain:
|
|
|
63
69
|
|
|
64
70
|
5. **Register the provider** — the registration point differs by domain:
|
|
65
71
|
|
|
66
|
-
- **Storage** —
|
|
67
|
-
|
|
72
|
+
- **Storage** — two changes required:
|
|
73
|
+
1. Add the new provider string to the `z.enum` for `STORAGE_PROVIDER_TYPE` in
|
|
74
|
+
`src/config/index.ts` — without this, the config schema rejects the env var at runtime.
|
|
75
|
+
2. Add a `case` to the `switch` in `src/storage/core/storageFactory.ts`
|
|
76
|
+
inside `createStorageProvider()`. Import the new provider class at the top of that file.
|
|
68
77
|
|
|
69
78
|
- **Speech** — two changes required:
|
|
70
79
|
1. Add the new provider string literal to the `provider` union in
|
|
@@ -75,8 +84,10 @@ Provider file location and naming differ by domain:
|
|
|
75
84
|
|
|
76
85
|
- **LLM** — currently only one provider exists (`OpenRouterProvider`); it is
|
|
77
86
|
instantiated directly in `src/core/app.ts` rather than through a factory switch.
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
There is no factory pattern yet — adding a second provider requires introducing
|
|
88
|
+
one (a selector env var, a factory function, and a conditional in `app.ts`).
|
|
89
|
+
Read `src/core/app.ts` to understand the current instantiation site before
|
|
90
|
+
designing the wiring.
|
|
80
91
|
|
|
81
92
|
6. **Update the Worker-compatible provider list** if the new storage provider runs in
|
|
82
93
|
Cloudflare Workers. The list is an inline array in `storageFactory.ts` at the
|
|
@@ -90,8 +101,11 @@ Provider file location and naming differ by domain:
|
|
|
90
101
|
Add the new provider string to this array. Non-storage providers have no equivalent
|
|
91
102
|
gate.
|
|
92
103
|
|
|
93
|
-
7. **Add the dependency**
|
|
94
|
-
|
|
104
|
+
7. **Add the dependency** if Tier 3: add to both `peerDependencies` and
|
|
105
|
+
`peerDependenciesMeta` (with `{ "optional": true }`) in `package.json`.
|
|
106
|
+
Without the `peerDependenciesMeta` entry, the dep appears required rather than optional.
|
|
107
|
+
8. **Run `bun run rebuild`** — since this is package source, verify the build output compiles.
|
|
108
|
+
9. **Run `bun run devcheck`** to verify.
|
|
95
109
|
|
|
96
110
|
## Checklist
|
|
97
111
|
|
|
@@ -99,8 +113,11 @@ Provider file location and naming differ by domain:
|
|
|
99
113
|
- [ ] Interface fully implemented (including `name`, `supportsTTS`/`supportsSTT` for speech)
|
|
100
114
|
- [ ] Tier 3 dependencies lazy-loaded (not top-level imports)
|
|
101
115
|
- [ ] Registered in the correct factory for the domain (see Step 5)
|
|
102
|
-
- [ ]
|
|
116
|
+
- [ ] Storage: provider string added to `z.enum` in `src/config/index.ts`
|
|
103
117
|
- [ ] Storage: Worker-compatible array in `storageFactory.ts` updated if applicable
|
|
104
|
-
- [ ]
|
|
118
|
+
- [ ] Speech: `provider` literal added to `SpeechProviderConfig` union in `types.ts`
|
|
119
|
+
- [ ] LLM: `src/core/app.ts` instantiation logic updated if adding a second LLM provider
|
|
120
|
+
- [ ] Optional peer dependency added to both `peerDependencies` and `peerDependenciesMeta` in `package.json` if Tier 3
|
|
121
|
+
- [ ] `bun run rebuild` succeeds
|
|
105
122
|
- [ ] `bun run devcheck` passes
|
|
106
|
-
- [ ]
|
|
123
|
+
- [ ] Test file created at `src/storage/providers/{{name}}/{{name}}Provider.test.ts` (or equivalent path for the domain) and `bun run test` passes
|
|
@@ -11,15 +11,13 @@ metadata:
|
|
|
11
11
|
|
|
12
12
|
## Context
|
|
13
13
|
|
|
14
|
-
Resources use the `resource()` builder from `@cyanheads/mcp-ts-core`. Each resource lives in `src/mcp-server/resources/definitions/` with a `.resource.ts` suffix
|
|
14
|
+
Resources use the `resource()` builder from `@cyanheads/mcp-ts-core`. Each resource lives in `src/mcp-server/resources/definitions/` with a `.resource.ts` suffix. The standard registration pattern uses a `definitions/index.ts` barrel that collects all resources into an `allResourceDefinitions` array for `createApp()`. Fresh scaffolds start with direct imports in `src/index.ts` — the barrel is introduced as definitions grow. Match the pattern already used by the project you're editing.
|
|
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 `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`.
|
|
19
|
-
|
|
20
18
|
## Steps
|
|
21
19
|
|
|
22
|
-
1. **
|
|
20
|
+
1. **Gather** the resource's URI template, purpose, and data shape from the user's request — ask only if genuinely absent
|
|
23
21
|
2. **Design the URI** — use `{paramName}` for path parameters (e.g., `myscheme://{itemId}/data`)
|
|
24
22
|
3. **Create the file** at `src/mcp-server/resources/definitions/{{resource-name}}.resource.ts`
|
|
25
23
|
4. **Register** the resource in the project's existing `createApp()` resource list (directly in `src/index.ts` for fresh scaffolds, or via a barrel if the repo already has one)
|
|
@@ -105,7 +103,11 @@ await createApp({
|
|
|
105
103
|
});
|
|
106
104
|
```
|
|
107
105
|
|
|
108
|
-
If the repo already uses `src/mcp-server/resources/definitions/index.ts`,
|
|
106
|
+
If the repo already uses `src/mcp-server/resources/definitions/index.ts`, add the export to that barrel instead:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
export { {{RESOURCE_EXPORT}} } from './{{resource-name}}.resource.js';
|
|
110
|
+
```
|
|
109
111
|
|
|
110
112
|
### Optional: declarative `errors[]` contract
|
|
111
113
|
|
|
@@ -118,11 +120,14 @@ export const articleResource = resource('article://{pmid}', {
|
|
|
118
120
|
description: 'Read an article by PMID.',
|
|
119
121
|
errors: [
|
|
120
122
|
{ reason: 'no_pmid_match', code: JsonRpcErrorCode.NotFound,
|
|
121
|
-
when: 'PMID not found in the index.'
|
|
123
|
+
when: 'PMID not found in the index.',
|
|
124
|
+
recovery: 'Use pubmed_search_articles to discover valid PMIDs first.' },
|
|
122
125
|
{ reason: 'withdrawn', code: JsonRpcErrorCode.NotFound,
|
|
123
|
-
when: 'Article was withdrawn upstream.'
|
|
126
|
+
when: 'Article was withdrawn upstream.',
|
|
127
|
+
recovery: 'Check PubMed directly for retraction or withdrawal notices.' },
|
|
124
128
|
{ reason: 'upstream_throttled', code: JsonRpcErrorCode.RateLimited,
|
|
125
|
-
when: 'Upstream PubMed quota hit.', retryable: true
|
|
129
|
+
when: 'Upstream PubMed quota hit.', retryable: true,
|
|
130
|
+
recovery: 'Wait a few seconds and retry the request.' },
|
|
126
131
|
],
|
|
127
132
|
params: z.object({ pmid: z.string().describe('PubMed ID') }),
|
|
128
133
|
async handler(params, ctx) {
|
|
@@ -142,21 +147,25 @@ Beyond `description`, `params`, `handler`, and `list`, the builder also supports
|
|
|
142
147
|
|
|
143
148
|
| Field | Purpose |
|
|
144
149
|
|:------|:--------|
|
|
150
|
+
| `name` | Short human-readable name for `resources/list`. Defaults to a slug derived from the URI template if omitted. |
|
|
145
151
|
| `output` | Optional Zod schema for runtime validation of the handler return value (parity with `tool()`'s `output`). |
|
|
146
152
|
| `format` | Optional formatter mapping the handler's return to the `ReadResourceResult.contents[]` shape. Default: string passthrough; objects serialized to JSON. Override when you need to attach permissions, custom encodings, or split into multiple content items. |
|
|
147
153
|
| `annotations` | Resource annotations (e.g., `audience`, `priority`) — see `ResourceAnnotations`. |
|
|
148
154
|
| `title` | Human-readable display title (defaults to `name`). |
|
|
155
|
+
| `examples` | Array of `{ name, uri }` example entries surfaced in `resources/list` for discoverability. |
|
|
149
156
|
|
|
150
157
|
## Checklist
|
|
151
158
|
|
|
152
159
|
- [ ] File created at `src/mcp-server/resources/definitions/{{resource-name}}.resource.ts`
|
|
153
|
-
- [ ] URI template
|
|
160
|
+
- [ ] Resource name passed to `resource()` uses a valid URI template with `{paramName}` syntax
|
|
154
161
|
- [ ] All Zod `params` fields have `.describe()` annotations
|
|
162
|
+
- [ ] `output` schema added if the handler returns structured data that benefits from runtime validation
|
|
155
163
|
- [ ] JSDoc `@fileoverview` and `@module` header present
|
|
156
164
|
- [ ] `handler(params, ctx)` is pure — throws on failure, no try/catch
|
|
157
|
-
- [ ]
|
|
165
|
+
- [ ] If `errors[]` contract declared: every entry has a `recovery` field (≥5 words, lint-enforced)
|
|
166
|
+
- [ ] Data is reachable via the tool surface — confirm by checking `src/mcp-server/tools/definitions/` for a tool that exposes this data, or document why this resource is resources-only
|
|
158
167
|
- [ ] `list()` function provided if the resource is discoverable
|
|
159
|
-
- [ ] Pagination used for large result sets (`extractCursor`/`paginateArray`)
|
|
168
|
+
- [ ] Pagination used for large result sets (`extractCursor`/`paginateArray`) — applies to both `handler` data and `list()` catalogs with many entries
|
|
160
169
|
- [ ] Registered in the project's existing `createApp()` resource list (directly or via barrel)
|
|
161
170
|
- [ ] `bun run devcheck` passes
|
|
162
171
|
- [ ] Smoke-tested with `bun run rebuild && bun run start:stdio` (or `start:http`)
|
|
@@ -15,11 +15,11 @@ 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 `
|
|
18
|
+
For the full service pattern, `CoreServices`, and `Context` interface, read the framework's `CLAUDE.md` (loaded at session start).
|
|
19
19
|
|
|
20
20
|
## Steps
|
|
21
21
|
|
|
22
|
-
1. **
|
|
22
|
+
1. **Gather** the service domain name and what it integrates with from the user's request — ask only if genuinely absent
|
|
23
23
|
2. **Create the directory** at `src/services/{{domain}}/`
|
|
24
24
|
3. **Create the service file** at `src/services/{{domain}}/{{domain}}-service.ts`
|
|
25
25
|
4. **Create types** at `src/services/{{domain}}/types.ts` if needed
|
|
@@ -71,19 +71,16 @@ export function get{{ServiceName}}(): {{ServiceName}} {
|
|
|
71
71
|
|
|
72
72
|
### Entry point registration
|
|
73
73
|
|
|
74
|
+
Add the `setup()` callback and import to the existing `createApp()` call — preserve the existing tool/resource/prompt arrays:
|
|
75
|
+
|
|
74
76
|
```typescript
|
|
75
|
-
// src/index.ts
|
|
76
|
-
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
77
|
+
// In src/index.ts (or src/worker.ts for Worker-only servers)
|
|
77
78
|
import { init{{ServiceName}} } from './services/{{domain}}/{{domain}}-service.js';
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setup(core) {
|
|
84
|
-
init{{ServiceName}}(core.config, core.storage);
|
|
85
|
-
},
|
|
86
|
-
});
|
|
80
|
+
// Add setup() alongside existing options:
|
|
81
|
+
setup(core) {
|
|
82
|
+
init{{ServiceName}}(core.config, core.storage);
|
|
83
|
+
},
|
|
87
84
|
```
|
|
88
85
|
|
|
89
86
|
### Usage in tool handlers
|
|
@@ -133,13 +130,13 @@ async fetchItem(id: string, ctx: Context): Promise<Item> {
|
|
|
133
130
|
### Key principles
|
|
134
131
|
|
|
135
132
|
1. **Calibrate backoff to the upstream.** 200–500ms for ephemeral failures, 1–2s for rate-limited APIs, 2–5s for service degradation. The default `baseDelayMs: 1000` suits most APIs.
|
|
136
|
-
2. **Check HTTP status before parsing.** `fetchWithTimeout` already throws
|
|
133
|
+
2. **Check HTTP status before parsing.** `fetchWithTimeout` already throws on non-OK responses with granular status mapping (401→`Unauthorized`, 403→`Forbidden`, 404→`NotFound`, 408/425→`Timeout`, 422→`ValidationError`, 429→`RateLimited`, 5xx→`ServiceUnavailable`/`InternalError`) — this prevents feeding HTML error pages into XML/JSON parsers.
|
|
137
134
|
3. **Classify parse failures by content.** If the upstream returns HTTP 200 with an HTML error page, detect it and throw `ServiceUnavailable` (transient) instead of `SerializationError` (non-transient).
|
|
138
135
|
4. **Exhausted retries say so.** `withRetry` automatically enriches the final error with attempt count — callers know retries were already attempted.
|
|
139
136
|
|
|
140
137
|
### When you need finer-grained HTTP error classification
|
|
141
138
|
|
|
142
|
-
`fetchWithTimeout`
|
|
139
|
+
`fetchWithTimeout` already maps status codes to appropriate error codes (see key principle 2 above). Use `httpErrorFromResponse` instead when you need `Retry-After` header capture, request body passthrough in error data, or custom `service`/`data` fields on the thrown error:
|
|
143
140
|
|
|
144
141
|
```typescript
|
|
145
142
|
import { httpErrorFromResponse, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
@@ -289,11 +286,12 @@ Silent truncation is a data integrity bug — the caller thinks it has all resul
|
|
|
289
286
|
## Checklist
|
|
290
287
|
|
|
291
288
|
- [ ] Directory created at `src/services/{{domain}}/`
|
|
292
|
-
- [ ] Service file created
|
|
289
|
+
- [ ] Service file created — `init` function accepts `(config: AppConfig, storage: StorageService)` and stores the instance
|
|
290
|
+
- [ ] Accessor function exported — throws `Error` if not initialized
|
|
293
291
|
- [ ] JSDoc `@fileoverview` and `@module` header present
|
|
292
|
+
- [ ] No `console` calls — use `ctx.log` for service-level logging
|
|
294
293
|
- [ ] Service methods accept `Context` for logging and storage
|
|
295
|
-
- [ ] `init` function registered in `setup()` callback in `src/index.ts`
|
|
296
|
-
- [ ] Accessor throws `Error` if not initialized
|
|
294
|
+
- [ ] `init` function registered in `setup()` callback in the server's entry point (`src/index.ts` or `src/worker.ts`)
|
|
297
295
|
- [ ] If wrapping external API: retry covers full pipeline (fetch + parse), backoff calibrated
|
|
298
296
|
- [ ] If wrapping external API: raw/domain types reflect real upstream sparsity; missing values are preserved as unknown, not fabricated into concrete facts
|
|
299
297
|
- [ ] If wrapping external API: batch endpoints used where available, field selection applied, pagination handled
|