@cyanheads/mcp-ts-core 0.4.1 → 0.5.2

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.
@@ -7,42 +7,47 @@ Structure and content guide for creating or updating a README for an MCP server
7
7
  Use this section order. Omit sections that don't apply (e.g., skip Docker/Workers if the server doesn't deploy there).
8
8
 
9
9
  ```text
10
- # {Server Name} ← centered HTML block
11
- Badges row ← npm, Docker, Version, MCP Spec, SDK, License, Status, TS, Bun, Coverage
10
+ # {Server Name} ← centered HTML block
11
+ [Public hosted callout if present] ← centered HTML block, directly under badges
12
+ Badges row ← npm, Docker, Version, Framework, MCP SDK, License, TS, Bun, Coverage
12
13
  ---
13
- ## Tools ← summary table, then per-tool subsections
14
- ## Resources (if any) summary table
15
- ## Prompts (if any) ← summary table
16
- ## Features framework + domain-specific bullets
17
- ## Getting Started hosted instance (if any), MCP client config, prerequisites, install
18
- ## Configuration env var table
19
- ## Running the Server dev, production, Workers/Docker
20
- ## Project Structure directory/purpose table
21
- ## Development Guide link to CLAUDE.md, key rules
22
- ## Contributing brief
23
- ## License ← one line
14
+ ## Tools grouping sentence → summary table per-tool subsections
15
+ ## Resources and prompts (if any) single combined table (Type / Name / Description)
16
+ ## Features ← framework bullets + domain-specific bullets
17
+ ## Getting started hosted (if any), bunx/npx/docker configs, HTTP one-liner, prerequisites, install
18
+ ## Configuration env var table + `.env.example` pointer
19
+ ## Running the server dev, production, Workers/Docker
20
+ ## Project structure directory/purpose table
21
+ ## Development guide link to CLAUDE.md, key rules
22
+ ## Contributing brief
23
+ ## License one line
24
24
  ```
25
25
 
26
26
  ## Section Guide
27
27
 
28
28
  ### Title Block
29
29
 
30
- Centered HTML. The `<h1>` is the server name — use the scoped package name if published under a scope (e.g., `@cyanheads/my-mcp-server`). The `<p>` is a bold one-liner: what the server wraps, key capabilities, transport/deployment options. Follow with a count line summarizing the MCP surface (tools, resources, prompts) separated by ` · `, then a badge row.
30
+ Centered HTML. The `<h1>` is the server name — use the scoped package name if published under a scope (e.g., `@cyanheads/my-mcp-server`). The `<p>` is a bold one-liner: what the server wraps, key capabilities, transport/deployment options. **Nest the surface count as a `<div>` inside the same `<p>`**, separated by `•` (U+2022 bullet) not as a second `<p>`. This matches the shipping convention across `@cyanheads/*` servers.
31
31
 
32
32
  ```html
33
33
  <div align="center">
34
34
  <h1>@cyanheads/my-mcp-server</h1>
35
- <p><b>MCP server for the Acme API. Search projects, manage tasks, track teams. STDIO & Streamable HTTP</b></p>
36
- <p><b>7 Tools · 2 Resources · 1 Prompt</b></p>
35
+ <p><b>MCP server for the Acme API search projects, manage tasks, track teams. STDIO or Streamable HTTP.</b>
36
+ <div>7 Tools 2 Resources 1 Prompt</div>
37
+ </p>
37
38
  </div>
38
39
 
39
40
  <div align="center">
40
41
 
41
- [![npm](https://img.shields.io/npm/v/my-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/my-mcp-server) [![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-259?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.27.1-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/)
42
+ [![npm](https://img.shields.io/npm/v/@cyanheads/my-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/my-mcp-server) [![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-259?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/)
43
+
44
+ [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
42
45
 
43
46
  </div>
44
47
  ```
45
48
 
49
+ The header tagline must match the `package.json` `description`.
50
+
46
51
  **Badge selection:** All badges use `style=flat-square`. Include what applies — don't add badges for things the server doesn't have:
47
52
 
48
53
  | Badge | When to include |
@@ -51,19 +56,41 @@ Centered HTML. The `<h1>` is the server name — use the scoped package name if
51
56
  | Docker | Published to ghcr.io or Docker Hub |
52
57
  | Version | Always — link to CHANGELOG.md |
53
58
  | Framework | Always — links to `@cyanheads/mcp-ts-core` on npm |
54
- | MCP Spec | Always — link to the spec version implemented |
55
59
  | MCP SDK | Always — show the `@modelcontextprotocol/sdk` version |
56
60
  | License | Always |
57
- | Status | Optional — Stable, Beta, etc. |
58
61
  | TypeScript | Always |
59
- | Bun | If using Bun |
62
+ | Bun | If using Bun (standard for this framework) |
63
+ | MCP Spec | Optional — rarely included; the SDK badge usually suffices |
64
+ | Status | Optional — Stable, Beta, etc. |
60
65
  | Code Coverage | If coverage is tracked |
61
66
 
62
67
  Add a `---` horizontal rule after the badge block.
63
68
 
69
+ ### Public Hosted Callout (if present)
70
+
71
+ If a public hosted instance is available, **promote it to a top-level callout** immediately below the badge block — don't bury it inside Getting Started. This is the highest-value piece of information for a visitor who wants to try the server with zero install.
72
+
73
+ ```html
74
+ <div align="center">
75
+
76
+ **Public Hosted Server:** [https://my-server.example.com/mcp](https://my-server.example.com/mcp)
77
+
78
+ </div>
79
+ ```
80
+
81
+ Keep the full connection-config JSON block inside a `### Public Hosted Instance` subsection under Getting Started (covered below). This callout is just the visibility pointer.
82
+
64
83
  ### Tools
65
84
 
66
- This is the most important section — it tells humans and LLMs exactly what the server exposes. Two layers: summary table, then per-tool subsections for tools with non-trivial behavior.
85
+ This is the most important section — it tells humans and LLMs exactly what the server exposes. Three layers: a **grouping framing sentence**, a summary table, then per-tool subsections for tools with non-trivial behavior.
86
+
87
+ **Grouping framing sentence:** Lead with one sentence that explains how the tool surface is organized. Richer than a bare count — tells the reader what mental model to apply. Examples:
88
+
89
+ - "Seventeen tools grouped by shape — workflow helpers orchestrate common flows end-to-end, primitive tools expose fine-grained CRUD, and the instruction tool returns procedural guidance merged with live account state."
90
+ - "Nine tools for working with PubMed and NCBI data:"
91
+ - "Five tools covering project lifecycle — discovery, task CRUD, and team analytics."
92
+
93
+ If the tools aren't meaningfully grouped, a single sentence count ("Seven tools for working with Acme data:") is acceptable.
67
94
 
68
95
  **Summary table:**
69
96
 
@@ -79,8 +106,6 @@ Seven tools for working with Acme data:
79
106
  | `get_task` | Fetch one or more tasks by ID, with full or summary data. |
80
107
  ```
81
108
 
82
- Lead with a one-line count: "Seven tools for working with X data:" (or "Three tools", etc.).
83
-
84
109
  **Per-tool subsections:**
85
110
 
86
111
  Below the table, add a `### tool_name` subsection for each tool that has meaningful detail beyond its one-line description. Include:
@@ -110,34 +135,35 @@ Fetch one or more tasks by ID, with full data or concise summaries.
110
135
  - Batch fetch up to 5 tasks at once
111
136
  - Full data includes subtasks, comments, attachments, and history
112
137
  - Partial success reporting when some tasks in a batch fail
113
-
114
- [View detailed examples](./examples/get_task.md)
115
138
  ```
116
139
 
117
140
  Skip the per-tool subsection for simple tools where the table description says everything (e.g., a `get_field_values` lookup tool).
118
141
 
119
- ### Resources (if any)
142
+ ### Resources and Prompts (combined)
143
+
144
+ **Use a single combined table with a `Type` column** rather than separate `## Resources` and `## Prompts` sections. This is the shipping convention — it scales better when a server has only 1 or 2 of each, and co-locates related content.
120
145
 
121
146
  ```markdown
122
- ## Resources
147
+ ## Resources and prompts
123
148
 
124
- | URI Pattern | Description |
125
- |:------------|:------------|
126
- | `acme://projects/{projectId}` | Project details by ID |
127
- | `acme://tasks/{taskId}` | Task details by ID |
149
+ | Type | Name | Description |
150
+ |:---|:---|:---|
151
+ | Resource | `acme://projects/{projectId}` | Project details by ID |
152
+ | Resource | `acme://tasks/{taskId}` | Task details by ID |
153
+ | Prompt | `project_summary` | Summarize a project's status and open tasks |
128
154
  ```
129
155
 
130
- ### Prompts (if any)
156
+ Use singular ("Resource and prompt") if there's only one of each.
131
157
 
132
- ```markdown
133
- ## Prompts
158
+ **Always include the tool-coverage note** directly under the table. Many MCP clients are tool-only and don't surface resources — this tells both the reader and downstream agents that the data is still reachable:
134
159
 
135
- | Prompt | Description |
136
- |:-------|:------------|
137
- | `project_summary` | Summarize a project's status and open tasks |
160
+ ```markdown
161
+ All resource data is also reachable via tools. Large collections (`projects`, `tasks`) are not exposed as resources — use the `list` operation on the corresponding tool instead.
138
162
  ```
139
163
 
140
- Derive all tool/resource/prompt tables directly from the actual definitions. Use the real names and descriptions from the Zod schemas.
164
+ If a prompt has an associated design doc or reference, link it in the same paragraph: `Design reference for the prompt: [\`docs/email-design-playbook.md\`](./docs/email-design-playbook.md).`
165
+
166
+ Derive all tool/resource/prompt rows directly from the actual definitions. Use the real names and descriptions from the Zod schemas.
141
167
 
142
168
  ### Features
143
169
 
@@ -146,90 +172,166 @@ Two subsection groups: framework capabilities, then domain-specific capabilities
146
172
  ```markdown
147
173
  ## Features
148
174
 
149
- Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
175
+ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
150
176
 
151
- - Declarative tool definitions — single file per tool, framework handles registration and validation
152
- - Unified error handling across all tools
153
- - Pluggable auth (`none`, `jwt`, `oauth`)
177
+ - Declarative tool, resource, and prompt definitions — single file per primitive, framework handles registration and validation
178
+ - Unified error handling handlers throw, framework catches, classifies, and formats
179
+ - Pluggable auth: `none`, `jwt`, `oauth`
154
180
  - Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
155
181
  - Structured logging with optional OpenTelemetry tracing
156
- - Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase
182
+ - STDIO and Streamable HTTP transports
157
183
 
158
184
  Acme-specific:
159
185
 
160
186
  - Type-safe client for the Acme v2 API
161
187
  - Automatic cleaning and simplification of API responses for agent consumption
188
+ - Workflow tools parallelize related sub-requests under a configurable concurrency limit
162
189
  ```
163
190
 
164
191
  ### Getting Started
165
192
 
166
- Lead with the lowest-friction option. If a public hosted instance exists, show that first. Then self-hosted via `bunx`/`npx`. Then manual clone/install.
193
+ Lead with the lowest-friction option. If a public hosted instance exists, show that first. Then the **three standard install configs in order — `bunx`, `npx`, `docker run`** — followed by the HTTP one-liner quickstart, then prerequisites and install steps.
167
194
 
168
- ```markdown
169
- ## Getting Started
195
+ **Standard three-block pattern** (the house style across shipping `@cyanheads/*` servers):
170
196
 
171
- ### Public Hosted Instance
197
+ ```markdown
198
+ ## Getting started
172
199
 
173
- A public instance is available at `https://my-server.example.com/mcp` no installation required:
200
+ Add the following to your MCP client configuration file. See [`docs/api-key.md`](./docs/api-key.md) for how to generate an API key.
174
201
 
175
202
  \`\`\`json
176
203
  {
177
204
  "mcpServers": {
178
205
  "my-server": {
179
- "type": "streamable-http",
180
- "url": "https://my-server.example.com/mcp"
206
+ "type": "stdio",
207
+ "command": "bunx",
208
+ "args": ["@cyanheads/my-mcp-server@latest"],
209
+ "env": {
210
+ "MCP_TRANSPORT_TYPE": "stdio",
211
+ "MCP_LOG_LEVEL": "info",
212
+ "ACME_API_KEY": "your-api-key"
213
+ }
181
214
  }
182
215
  }
183
216
  }
184
217
  \`\`\`
185
218
 
186
- ### Self-Hosted / Local
187
-
188
- Add to your MCP client config (e.g., `claude_desktop_config.json`):
219
+ Or with npx (no Bun required):
189
220
 
190
221
  \`\`\`json
191
222
  {
192
223
  "mcpServers": {
193
- "my-mcp-server": {
224
+ "my-server": {
194
225
  "type": "stdio",
195
- "command": "bunx",
196
- "args": ["my-mcp-server@latest"],
226
+ "command": "npx",
227
+ "args": ["-y", "@cyanheads/my-mcp-server@latest"],
197
228
  "env": {
198
- "ACME_API_KEY": "your-api-key",
199
- "MCP_TRANSPORT_TYPE": "stdio"
229
+ "MCP_TRANSPORT_TYPE": "stdio",
230
+ "MCP_LOG_LEVEL": "info",
231
+ "ACME_API_KEY": "your-api-key"
200
232
  }
201
233
  }
202
234
  }
203
235
  }
204
236
  \`\`\`
205
237
 
238
+ Or with Docker:
239
+
240
+ \`\`\`json
241
+ {
242
+ "mcpServers": {
243
+ "my-server": {
244
+ "type": "stdio",
245
+ "command": "docker",
246
+ "args": [
247
+ "run", "-i", "--rm",
248
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
249
+ "-e", "ACME_API_KEY=your-api-key",
250
+ "ghcr.io/cyanheads/my-mcp-server:latest"
251
+ ]
252
+ }
253
+ }
254
+ }
255
+ \`\`\`
256
+
257
+ For Streamable HTTP, set the transport and start the server:
258
+
259
+ \`\`\`sh
260
+ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 ACME_API_KEY=... bun run start:http
261
+ # Server listens at http://localhost:3010/mcp
262
+ \`\`\`
263
+ ```
264
+
265
+ Refer to "your MCP client configuration file" generically — don't prescribe `claude_desktop_config.json` by name. Different clients use different config paths and the server isn't client-specific.
266
+
267
+ **If a public hosted instance exists**, precede the three-block pattern with a `### Public Hosted Instance` subsection and wrap the local configs in a `### Self-Hosted / Local` subsection:
268
+
269
+ ```markdown
270
+ ### Public Hosted Instance
271
+
272
+ A public instance is available at `https://my-server.example.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:
273
+
274
+ \`\`\`json
275
+ {
276
+ "mcpServers": {
277
+ "my-server": {
278
+ "type": "streamable-http",
279
+ "url": "https://my-server.example.com/mcp"
280
+ }
281
+ }
282
+ }
283
+ \`\`\`
284
+
285
+ ### Self-Hosted / Local
286
+
287
+ [bunx / npx / docker blocks here]
288
+ ```
289
+
290
+ **Prerequisites:** Include a Bun version line and any domain-specific setup (API key format, rate-limit tiers, required accounts). Don't just list Bun — readers need to know what else to prepare.
291
+
292
+ ```markdown
206
293
  ### Prerequisites
207
294
 
208
- - [Bun v1.2.0](https://bun.sh/) or higher.
295
+ - [Bun v1.3.2](https://bun.sh/) or higher (or Node.js v22+).
296
+ - An Acme API key — see [`docs/api-key.md`](./docs/api-key.md) for how to generate one.
297
+ ```
298
+
299
+ **Installation:** Standard four steps (clone, cd, install, configure env):
209
300
 
301
+ ```markdown
210
302
  ### Installation
211
303
 
212
304
  1. **Clone the repository:**
305
+
213
306
  \`\`\`sh
214
307
  git clone https://github.com/cyanheads/my-mcp-server.git
215
308
  \`\`\`
216
309
 
217
310
  2. **Navigate into the directory:**
311
+
218
312
  \`\`\`sh
219
313
  cd my-mcp-server
220
314
  \`\`\`
221
315
 
222
316
  3. **Install dependencies:**
317
+
223
318
  \`\`\`sh
224
319
  bun install
225
320
  \`\`\`
321
+
322
+ 4. **Configure environment:**
323
+
324
+ \`\`\`sh
325
+ cp .env.example .env
326
+ # edit .env and set required vars
327
+ \`\`\`
226
328
  ```
227
329
 
228
- Omit the hosted instance subsection if there isn't one. Omit the clone/install steps if the server is npm-only (not meant to be cloned).
330
+ Omit the clone/install steps if the server is npm-only (not meant to be cloned).
229
331
 
230
332
  ### Configuration
231
333
 
232
- Table of environment variables. Include framework vars only if the server uses non-default values.
334
+ Table of environment variables. Include framework vars only if the server uses non-default values. Mark required vars with bold **Required.** in the description rather than a separate column. **Close with a pointer to `.env.example`** for the full list of optional overrides.
233
335
 
234
336
  ```markdown
235
337
  ## Configuration
@@ -238,6 +340,7 @@ Table of environment variables. Include framework vars only if the server uses n
238
340
  |:---------|:------------|:--------|
239
341
  | `ACME_API_KEY` | **Required.** API key for the Acme service. | — |
240
342
  | `ACME_BASE_URL` | API base URL. | `https://api.acme.com` |
343
+ | `ACME_TIMEOUT_MS` | Per-request timeout in milliseconds. | `60000` |
241
344
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
242
345
  | `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
243
346
  | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
@@ -245,74 +348,105 @@ Table of environment variables. Include framework vars only if the server uses n
245
348
  | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
246
349
  | `STORAGE_PROVIDER_TYPE` | Storage backend. | `in-memory` |
247
350
  | `OTEL_ENABLED` | Enable OpenTelemetry. | `false` |
351
+
352
+ See [`.env.example`](./.env.example) for the full list of optional overrides.
248
353
  ```
249
354
 
250
- Source from the server config Zod schema and `.env.example`. Mark required vars with bold **Required.** in the description rather than a separate column.
355
+ Source from the server config Zod schema and `.env.example`.
251
356
 
252
357
  ### Running the Server
253
358
 
254
- Separate from Getting Started. Show build + run commands, and Workers/Docker deployment if applicable.
359
+ Separate from Getting Started. Show dev, build + run, and Workers/Docker deployment if applicable.
255
360
 
256
361
  ```markdown
257
- ## Running the Server
362
+ ## Running the server
363
+
364
+ ### Local development
258
365
 
259
- ### Local Development
366
+ - **Hot-reload dev mode:**
367
+
368
+ \`\`\`sh
369
+ bun run dev:stdio
370
+ bun run dev:http
371
+ \`\`\`
260
372
 
261
373
  - **Build and run the production version:**
374
+
262
375
  \`\`\`sh
263
- bun run build
264
- bun run start:http # or start:stdio
376
+ # One-time build
377
+ bun run rebuild
378
+
379
+ # Run the built server
380
+ bun run start:stdio
381
+ # or
382
+ bun run start:http
265
383
  \`\`\`
266
384
 
267
385
  - **Run checks and tests:**
386
+
268
387
  \`\`\`sh
269
- bun run devcheck # Lints, formats, type-checks
270
- bun run test # Runs test suite
388
+ bun run devcheck # Lint, format, typecheck, security
389
+ bun run test # Vitest test suite
390
+ bun run lint:mcp # Validate MCP definitions against spec
271
391
  \`\`\`
272
392
 
393
+ ### Docker
394
+
395
+ \`\`\`sh
396
+ docker build -t my-mcp-server .
397
+ docker run --rm -e ACME_API_KEY=your-key -p 3010:3010 my-mcp-server
398
+ \`\`\`
399
+
400
+ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/my-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
401
+
273
402
  ### Cloudflare Workers
274
403
 
275
404
  1. **Build the Worker bundle:**
405
+
276
406
  \`\`\`sh
277
407
  bun run build:worker
278
408
  \`\`\`
279
409
 
280
410
  2. **Deploy:**
411
+
281
412
  \`\`\`sh
282
413
  bun run deploy:prod
283
414
  \`\`\`
284
415
  ```
285
416
 
286
- Include the Docker or Workers subsection only if the server supports it.
417
+ Include the Docker or Workers subsection only if the server supports it. The Docker trailing paragraph (log directory, OTEL build arg) is important — it documents Dockerfile behavior that isn't obvious from the build command.
287
418
 
288
419
  ### Project Structure
289
420
 
290
421
  Directory/purpose table orienting contributors to the codebase.
291
422
 
292
423
  ```markdown
293
- ## Project Structure
424
+ ## Project structure
294
425
 
295
426
  | Directory | Purpose |
296
427
  |:----------|:--------|
428
+ | `src/index.ts` | `createApp()` entry point — registers tools/resources/prompts and inits services. |
429
+ | `src/config` | Server-specific environment variable parsing and validation with Zod. |
297
430
  | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). |
298
431
  | `src/mcp-server/resources` | Resource definitions (`*.resource.ts`). |
432
+ | `src/mcp-server/prompts` | Prompt definitions (`*.prompt.ts`). |
299
433
  | `src/services` | Domain service integrations. |
300
- | `src/config` | Environment variable parsing and validation with Zod. |
301
434
  | `tests/` | Unit and integration tests mirroring `src/`. |
302
435
  ```
303
436
 
304
437
  ### Development Guide
305
438
 
306
- Brief — link to CLAUDE.md for full details. State 2-3 key rules.
439
+ Brief — link to CLAUDE.md for full details. State 3-4 key rules. **Include the "validate → normalize → never fabricate" bullet** — it's the canonical anti-hallucination convention for external API wrappers and reinforces the framework's `no fabricated signal` principle.
307
440
 
308
441
  ```markdown
309
- ## Development Guide
442
+ ## Development guide
310
443
 
311
444
  See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
312
445
 
313
446
  - Handlers throw, framework catches — no `try/catch` in tool logic
314
- - Use `ctx.log` for domain-specific logging, `ctx.state` for storage
315
- - Register new tools and resources in the `index.ts` barrel files
447
+ - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage
448
+ - Register new tools and resources via the barrels in `src/mcp-server/*/definitions/index.ts`
449
+ - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
316
450
  ```
317
451
 
318
452
  ### Contributing
@@ -320,7 +454,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
320
454
  ```markdown
321
455
  ## Contributing
322
456
 
323
- Issues and pull requests are welcome. Run checks before submitting:
457
+ Issues and pull requests are welcome. Run checks and tests before submitting:
324
458
 
325
459
  \`\`\`sh
326
460
  bun run devcheck
@@ -343,8 +477,12 @@ Apache-2.0 — see [LICENSE](LICENSE) for details.
343
477
  - **Accuracy over aspiration.** Only document what exists. Don't describe planned features as if they're implemented.
344
478
  - **Tools first.** The tool surface is the most important content. Lead with it.
345
479
  - **Tables over prose** for structured data (tools, config, directories). Scannable and diff-friendly.
346
- - **Two-layer tool docs.** Summary table for quick scanning, per-tool subsections for detail. Skip subsections for trivial tools.
480
+ - **Two-layer tool docs.** Grouping sentence + summary table for quick scanning, per-tool subsections for detail. Skip subsections for trivial tools.
481
+ - **Combined resources + prompts.** Single table with a `Type` column, not separate sections.
482
+ - **Promote hosted instances.** If there's a public URL, put it in a top-level callout under the badges — not buried in Getting Started.
483
+ - **Three install configs.** `bunx`, `npx`, `docker run` in that order. Each as a complete MCP-client JSON block.
347
484
  - **Real names from code.** Tool names, env vars, and URIs must match the source exactly. Copy from the definitions, don't paraphrase.
348
- - **Lowest friction first.** In Getting Started, lead with the easiest option (hosted instance > bunx > clone).
485
+ - **Lowest friction first.** Hosted instance > bunx > npx > docker > clone.
349
486
  - **No badges unless publishing.** Badges for unpublished packages are noise.
487
+ - **Client-agnostic framing.** Say "your MCP client configuration file", not `claude_desktop_config.json`.
350
488
  - **Keep it current.** Update the README whenever tools are added or removed.
@@ -4,14 +4,14 @@ description: >
4
4
  Post-init orientation for an MCP server built on @cyanheads/mcp-ts-core. Use after running `@cyanheads/mcp-ts-core init` to understand the project structure, conventions, and skill sync model. Also use when onboarding to an existing project for the first time.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
11
11
 
12
12
  ## Context
13
13
 
14
- This skill assumes `@cyanheads/mcp-ts-core init` has already run. The CLI created the project's `CLAUDE.md` and `AGENTS.md` for different agents, copied external skills to `skills/`, and scaffolded the directory structure with echo definitions as starting points. This skill covers what was created and what to do next.
14
+ This skill assumes `npx @cyanheads/mcp-ts-core init [name]` has already run. The CLI created the project's `CLAUDE.md` and `AGENTS.md` for different agents, copied external skills to `skills/`, and scaffolded the directory structure with echo definitions as starting points. This skill covers what was created and what to do next.
15
15
 
16
16
  ## Agent Protocol File
17
17
 
@@ -91,7 +91,7 @@ mkdir -p .claude/skills && cp -R skills/* .claude/skills/
91
91
 
92
92
  **For other agents** (Codex, Cursor, Windsurf, etc.) — copy to the equivalent directory (e.g., `.codex/skills/`, `.cursor/skills/`).
93
93
 
94
- After the initial copy, use the `maintenance` skill to keep them in sync after package updates.
94
+ This step is the **bootstrap** — it creates the agent directory. From then on, use the `maintenance` skill to refresh it after package updates (Phase B). Maintenance only refreshes directories that already exist; it won't create a new agent directory on your behalf.
95
95
 
96
96
  ## Project Scaffolding
97
97
 
@@ -14,8 +14,7 @@
14
14
 
15
15
  1. **Read the framework API** — `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`
16
16
  2. **Run the `setup` skill** — read `skills/setup/SKILL.md` and follow its checklist (project orientation, agent protocol file selection, echo definition cleanup, skill sync)
17
- 3. **Check for Bun** — run `bun --version`. If [Bun](https://bun.sh) is available, it's the recommended runtime: update `package.json` scripts to use `bun` instead of `tsx` (e.g., `"devcheck": "bun scripts/devcheck.ts"`), prefer `bun run` over `npm run` for all commands, and update the Commands table and Checklist in this file to match.
18
- 4. **Design the server** — read `skills/design-mcp-server/SKILL.md` and work through it with the user to map the domain into tools, resources, and services before scaffolding
17
+ 3. **Design the server** — read `skills/design-mcp-server/SKILL.md` and work through it with the user to map the domain into tools, resources, and services before scaffolding
19
18
 
20
19
  ---
21
20
 
@@ -25,13 +24,13 @@ When the user asks what to do next, what's left, or needs direction, suggest rel
25
24
 
26
25
  1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
27
26
  2. **Run the `design-mcp-server` skill** — if the tool/resource surface hasn't been mapped yet, work through domain design
28
- 3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-resource`, `add-prompt` skills
27
+ 3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
29
28
  4. **Add services** — scaffold domain service integrations using the `add-service` skill
30
29
  5. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
31
30
  6. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
32
31
  7. **Run `devcheck`** — lint, format, typecheck, and security audit
33
32
  8. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
34
- 9. **Run the `maintenance` skill** — sync skills and dependencies after framework updates
33
+ 9. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
35
34
 
36
35
  Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
37
36
 
@@ -77,6 +76,7 @@ export const searchItems = tool('search_items', {
77
76
 
78
77
  // format() populates content[] — the only field most LLM clients forward to
79
78
  // the model. Render all data the LLM needs, not just a count or title.
79
+ // Enforced at lint time: every field in `output` must appear in the rendered text.
80
80
  format: (result) => [{
81
81
  type: 'text',
82
82
  text: result.items.map(i => `**${i.id}**: ${i.name}`).join('\n'),
@@ -122,20 +122,26 @@ export const reviewCode = prompt('review_code', {
122
122
 
123
123
  ```ts
124
124
  // src/config/server-config.ts — lazy-parsed, separate from framework config
125
+ import { z } from '@cyanheads/mcp-ts-core';
126
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
127
+
125
128
  const ServerConfigSchema = z.object({
126
- myApiKey: z.string().describe('External API key'),
129
+ apiKey: z.string().describe('External API key'),
127
130
  maxResults: z.coerce.number().default(100),
128
131
  });
132
+
129
133
  let _config: z.infer<typeof ServerConfigSchema> | undefined;
130
134
  export function getServerConfig() {
131
- _config ??= ServerConfigSchema.parse({
132
- myApiKey: process.env.MY_API_KEY,
133
- maxResults: process.env.MY_MAX_RESULTS,
135
+ _config ??= parseEnvConfig(ServerConfigSchema, {
136
+ apiKey: 'MY_API_KEY',
137
+ maxResults: 'MY_MAX_RESULTS',
134
138
  });
135
139
  return _config;
136
140
  }
137
141
  ```
138
142
 
143
+ `parseEnvConfig` maps Zod schema paths → env var names so validation errors name the actual variable (`MY_API_KEY`) rather than the internal path (`apiKey`). It throws a `ConfigurationError` the framework catches and prints as a clean startup banner.
144
+
139
145
  ---
140
146
 
141
147
  ## Context
@@ -215,7 +221,7 @@ src/
215
221
 
216
222
  Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
217
223
 
218
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, re-copy to pick up changes.
224
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
219
225
 
220
226
  Available skills:
221
227
 
@@ -232,7 +238,7 @@ Available skills:
232
238
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
233
239
  | `devcheck` | Lint, format, typecheck, audit |
234
240
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
235
- | `maintenance` | Sync skills and dependencies after updates |
241
+ | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
236
242
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
237
243
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
238
244
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
@@ -250,6 +256,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
250
256
 
251
257
  ## Commands
252
258
 
259
+ **Runtime:** Scripts use `tsx` — both `npm run <cmd>` and `bun run <cmd>` work. Use whichever package manager you have; `bun` is slightly faster for invoking scripts but not required.
260
+
253
261
  | Command | Purpose |
254
262
  |:--------|:--------|
255
263
  | `npm run build` | Compile TypeScript |
@@ -281,7 +289,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
281
289
 
282
290
  ## Checklist
283
291
 
284
- - [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, etc.)
292
+ - [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`)
285
293
  - [ ] Optional nested objects: handler guards for empty inner values from form-based clients (`if (input.obj?.field && ...)`, not just `if (input.obj)`)
286
294
  - [ ] JSDoc `@fileoverview` + `@module` on every file
287
295
  - [ ] `ctx.log` for logging, `ctx.state` for storage