@cyanheads/mcp-ts-core 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +7 -9
  3. package/biome.json +1 -1
  4. package/dist/mcp-server/apps/appBuilders.d.ts +9 -1
  5. package/dist/mcp-server/apps/appBuilders.d.ts.map +1 -1
  6. package/dist/mcp-server/apps/appBuilders.js +64 -2
  7. package/dist/mcp-server/apps/appBuilders.js.map +1 -1
  8. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +16 -8
  9. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -1
  10. package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -1
  11. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  12. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +11 -1
  13. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  14. package/package.json +15 -15
  15. package/skills/add-app-tool/SKILL.md +54 -32
  16. package/skills/add-prompt/SKILL.md +16 -11
  17. package/skills/add-resource/SKILL.md +16 -11
  18. package/skills/add-service/SKILL.md +47 -6
  19. package/skills/add-test/SKILL.md +23 -24
  20. package/skills/add-tool/SKILL.md +55 -13
  21. package/skills/api-testing/SKILL.md +56 -10
  22. package/skills/api-workers/SKILL.md +9 -7
  23. package/skills/design-mcp-server/SKILL.md +10 -19
  24. package/skills/devcheck/SKILL.md +20 -6
  25. package/skills/field-test/SKILL.md +17 -2
  26. package/skills/maintenance/SKILL.md +4 -2
  27. package/skills/migrate-mcp-ts-template/SKILL.md +14 -12
  28. package/skills/polish-docs-meta/SKILL.md +4 -4
  29. package/skills/setup/SKILL.md +9 -9
  30. package/templates/AGENTS.md +6 -1
  31. package/templates/CLAUDE.md +6 -2
  32. package/templates/src/mcp-server/resources/definitions/echo-app-ui.app-resource.ts +25 -4
@@ -4,7 +4,7 @@ description: >
4
4
  Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -19,15 +19,15 @@ metadata:
19
19
 
20
20
  ```ts
21
21
  import { createWorkerHandler } from '@cyanheads/mcp-ts-core/worker';
22
- import { allToolDefinitions } from './mcp-server/tools/index.js';
23
- import { allResourceDefinitions } from './mcp-server/resources/index.js';
24
- import { allPromptDefinitions } from './mcp-server/prompts/index.js';
22
+ import { echoTool } from './mcp-server/tools/definitions/echo.tool.js';
23
+ import { echoResource } from './mcp-server/resources/definitions/echo.resource.js';
24
+ import { echoPrompt } from './mcp-server/prompts/definitions/echo.prompt.js';
25
25
  import { initMyService } from './services/my-domain/my-service.js';
26
26
 
27
27
  export default createWorkerHandler({
28
- tools: allToolDefinitions,
29
- resources: allResourceDefinitions,
30
- prompts: allPromptDefinitions,
28
+ tools: [echoTool],
29
+ resources: [echoResource],
30
+ prompts: [echoPrompt],
31
31
  setup(core) {
32
32
  initMyService(core.config, core.storage);
33
33
  },
@@ -39,6 +39,8 @@ export default createWorkerHandler({
39
39
  });
40
40
  ```
41
41
 
42
+ Fresh scaffolds register definitions directly in the entry point as shown above. If your project later adds barrel files for definitions, importing arrays from those barrels is also fine.
43
+
42
44
  ### Options
43
45
 
44
46
  | Option | Type | Purpose |
@@ -4,7 +4,7 @@ description: >
4
4
  Design the tool surface, resources, and service layer for a new MCP server. Use when starting a new server, planning a major feature expansion, or when the user describes a domain/API they want to expose via MCP. Produces a design doc at docs/design.md that drives implementation.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.1"
7
+ version: "2.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -117,9 +117,7 @@ const gitBranch = tool('git_branch', {
117
117
  ```ts
118
118
  // Workflow tool — search + local filter pipeline, not a raw API proxy
119
119
  const findEligibleStudies = tool('clinicaltrials_find_eligible_studies', {
120
- description: 'Matches patient demographics and medical profile to eligible clinical trials. '
121
- + 'Filters by age, sex, conditions, location, and healthy volunteer status. '
122
- + 'Returns ranked list of matching studies with eligibility explanations.',
120
+ description: 'Matches patient demographics and medical profile to eligible clinical trials. Filters by age, sex, conditions, location, and healthy volunteer status. Returns ranked list of matching studies with eligibility explanations.',
123
121
  // handler: listStudies() → filter by eligibility → rank by location proximity → slice
124
122
  });
125
123
  ```
@@ -142,21 +140,20 @@ The description is the LLM's primary signal for tool selection. It must answer:
142
140
 
143
141
  - **Be concrete about capability.** "Search for clinical trial studies using queries and filters" beats "Interact with studies."
144
142
  - **Include operational guidance when it matters.** If the tool has prerequisites, constraints, or gotchas the LLM needs to know, say so in the description. Don't add boilerplate workflow hints when the tool is self-explanatory.
143
+ - **Don't leak implementation details.** Descriptions are for the consumer, not the author. Internal endpoint paths, API call counts, internal parameter name mappings, and routing logic don't belong — describe what the tool does and when to use it, not how it's wired up.
145
144
 
146
145
  ```ts
147
146
  // Good — describes a prerequisite the LLM must know
148
- description: 'Set the session working directory for all git operations. '
149
- + 'This allows subsequent git commands to omit the path parameter.'
147
+ description: 'Set the session working directory for all git operations. This allows subsequent git commands to omit the path parameter.'
150
148
 
151
149
  // Good — self-explanatory, no workflow hints needed
152
150
  description: 'Show the working tree status including staged, unstaged, and untracked files.'
153
151
 
154
152
  // Good — warns about constraints
155
- description: 'Fetches trial results data for completed studies. '
156
- + 'Only available for studies where hasResults is true.'
153
+ description: 'Fetches trial results data for completed studies. Only available for studies where hasResults is true.'
157
154
  ```
158
155
 
159
- Context-dependent: a simple read-only tool needs a one-line description. A tool with prerequisites, modes, or non-obvious behavior needs more. Match depth of description to complexity of tool.
156
+ Descriptions should be as long as needed concise but complete. Don't artificially truncate, and don't pad with filler.
160
157
 
161
158
  #### Parameter descriptions
162
159
 
@@ -171,14 +168,11 @@ Every `.describe()` is prompt text the LLM reads. Parameters should convey: what
171
168
  ```ts
172
169
  // Good — explains cost, recommends action, names the alternative
173
170
  fields: z.array(z.string()).optional()
174
- .describe('Specific fields to return (reduces payload size). '
175
- + 'STRONGLY RECOMMENDED — without this, the full study record (~70KB each) is returned. '
176
- + 'Use full data only when you need detailed eligibility criteria, locations, or results.'),
171
+ .describe('Specific fields to return (reduces payload size). Without this, the full study record (~70KB each) is returned. Use full data only when you need detailed eligibility criteria, locations, or results.'),
177
172
 
178
173
  // Good — explains what the flag does AND how to override
179
174
  autoExclude: z.boolean().default(true)
180
- .describe('Automatically exclude lock files and generated files from diff output '
181
- + 'to reduce context bloat. Set to false if you need to inspect these files.'),
175
+ .describe('Automatically exclude lock files and generated files from diff output to reduce context bloat. Set to false if you need to inspect these files.'),
182
176
 
183
177
  // Good — names the format and gives one example
184
178
  nctIds: z.union([z.string(), z.array(z.string()).max(5)])
@@ -201,8 +195,7 @@ The output schema and `format` function control what the LLM reads back. Design
201
195
  output: z.object({
202
196
  diff: z.string().describe('Unified diff output.'),
203
197
  excludedFiles: z.array(z.string()).optional()
204
- .describe('Files automatically excluded from the diff (e.g., lock files). '
205
- + 'Call again with autoExclude=false to include them.'),
198
+ .describe('Files automatically excluded from the diff (e.g., lock files). Call again with autoExclude=false to include them.'),
206
199
  }),
207
200
  ```
208
201
 
@@ -244,9 +237,7 @@ When a tool wraps a complex query language or filter system, provide a simple sh
244
237
  ```ts
245
238
  // text_search handles the common case; query handles everything else
246
239
  text_search: z.string().optional()
247
- .describe('Convenience shortcut: full-text search across title and abstract. '
248
- + 'Equivalent to {"_or":[{"_text_any":{"title":"..."}},{"_text_any":{"abstract":"..."}}]}. '
249
- + 'For more control, use the query parameter directly.'),
240
+ .describe('Convenience shortcut: full-text search across title and abstract. For structured filters or field-specific matching, use the query parameter instead.'),
250
241
  query: z.record(z.unknown()).optional()
251
242
  .describe('Full query object for structured filters. Supports operators: _eq, _gt, _and, _or, ...'),
252
243
  ```
@@ -4,37 +4,51 @@ description: >
4
4
  Lint, format, typecheck, and verify the project is clean. Use after making changes, before committing, or when the user asks to verify quality.
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
  ## What It Runs
13
13
 
14
- `bun run devcheck` runs lint and typecheck:
14
+ `bun run devcheck` runs a broader check suite than just lint + types. By default it includes local hygiene checks, MCP definition linting, Biome, TypeScript, and slow dependency/security checks unless `--fast` is passed. Tests are opt-in via `--test`.
15
15
 
16
16
  | Check | Tool | Notes |
17
17
  |:------|:-----|:------|
18
+ | TODOs / FIXMEs | `git grep` | Fails on tracked TODO/FIXME markers outside excluded files |
19
+ | Tracked secrets | `git ls-files` | Flags tracked `.env`, keys, credentials, and similar sensitive files |
20
+ | MCP definitions | `bun run scripts/lint-mcp.ts` | Validates tool/resource/prompt definitions against framework rules |
18
21
  | Biome | `biome check` | Unified lint + format — read-only by default |
19
22
  | TypeScript | `tsc --noEmit` | Full project type check |
23
+ | Unused dependencies | `depcheck` | Runs by default; network-free but slower on large repos |
24
+ | Security audit | `bun audit` | Runs by default unless `--fast` or `--no-audit` |
25
+ | Outdated dependencies | `bun outdated` | Runs by default unless `--fast` or `--no-deps` |
26
+ | Tests | `vitest run` | Off by default; enable with `bun run devcheck --test` |
20
27
 
21
- To auto-fix lint/format issues, run `bun run format` (which runs `biome check --fix .`).
28
+ To auto-fix lint/format issues, run `bun run format`.
22
29
 
23
30
  ## Steps
24
31
 
25
32
  1. Run `bun run devcheck`
26
- 2. Read the output both checks run sequentially
27
- 3. Fix any lint or type errors in source files
33
+ 2. Read the failing checks in the summary and per-check output
34
+ 3. Fix the reported issues
28
35
  4. Re-run `bun run devcheck` until clean
29
- 5. Do not consider this skill complete until the command exits successfully with no errors
36
+ 5. If the change touches runtime behavior, also run `bun run devcheck --test` or `bun run test`
37
+ 6. Do not consider this skill complete until the required commands exit successfully with no errors
30
38
 
31
39
  ## Common Issues
32
40
 
33
41
  | Check | Error Type | Typical Fix |
34
42
  |:------|:-----------|:------------|
43
+ | TODOs / FIXMEs | Tracked work markers | Resolve or remove the marker before committing |
44
+ | Tracked secrets | Sensitive files in git | Add to `.gitignore` and remove from the index |
45
+ | MCP definitions | Definition lint errors | Fix schema/name/annotation issues reported by `lint-mcp` |
35
46
  | Biome | Lint/format errors | Run `bun run format` to auto-fix, or address the flagged rule manually |
36
47
  | TypeScript | Type errors | Fix type mismatches, missing properties, incorrect generics |
48
+ | Security audit | Vulnerabilities in direct deps | Update or replace the affected dependency |
49
+ | Outdated deps | Stale package versions | Run `bun update` or allowlist intentionally pinned packages |
37
50
 
38
51
  ## Checklist
39
52
 
40
53
  - [ ] `bun run devcheck` exits with no errors
54
+ - [ ] Tests run when needed (`bun run devcheck --test` or `bun run test`)
@@ -4,14 +4,14 @@ description: >
4
4
  Exercise tools, resources, and prompts with real-world inputs to verify behavior end-to-end. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface. Calls each definition with realistic and adversarial inputs and produces a report of issues, pain points, and recommendations.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: debug
10
10
  ---
11
11
 
12
12
  ## Context
13
13
 
14
- Unit tests (`add-test` skill) verify handler logic with mocked context. Field testing verifies the full picture: real server, real transport, real inputs, real outputs. It catches issues that unit tests miss — bad descriptions, awkward input shapes, unhelpful error messages, missing format functions, schema mismatches, and surprising edge-case behavior.
14
+ Unit tests (`add-test` skill) verify handler logic with mocked context. Field testing verifies the full picture: real server, real transport, real inputs, real outputs. It catches issues that unit tests miss — bad descriptions, awkward input shapes, unhelpful error messages, missing format functions, schema mismatches, silent divergence between `structuredContent` and model-visible `content[]`, and surprising edge-case behavior.
15
15
 
16
16
  **Actively use** the tools — don't just read their code.
17
17
 
@@ -36,12 +36,17 @@ For every tool, resource, and prompt, run through these categories:
36
36
  | Category | What to test |
37
37
  |:---------|:-------------|
38
38
  | **Happy path** | Realistic input that should succeed. Verify output shape matches the output schema. Verify format function produces sensible content blocks. |
39
+ | **`structuredContent` parity** | Compare the raw handler return, the data captured in `structuredContent`, and what `format()` renders into `content[]`. Flag fields or records that exist in the handler result or `structuredContent` but are absent from `content[]` unless the omission is explicit and acceptable for the tool's purpose. Most LLM clients only see `content[]`, so silent formatter drops are real bugs. |
39
40
  | **Variations** | Different valid input combinations — optional fields omitted, optional fields included, different enum values, min/max boundaries. |
41
+ | **Field selection / projection** | For tools with `fields`, `include`, `expand`, `view`, or similar parameters, call the tool with non-default selections. Verify the handler returns the requested fields and `format()` renders each requested field rather than a hardcoded summary subset. |
40
42
  | **Edge cases** | Empty strings, zero values, very long inputs, special characters, Unicode. |
41
43
  | **Error paths** | Missing required fields, wrong types, nonexistent IDs, inputs that should trigger domain errors. Verify errors are clear and actionable — they should name what went wrong, why, and what to do next. |
42
44
  | **Empty results** | Inputs that match nothing. Verify the response explains *why* (echoes criteria, suggests broadening) rather than returning a bare empty array. |
43
45
  | **Partial success** | For tools that operate on multiple items, test cases where some succeed and some fail. Verify both outcomes are reported — not just the successes. |
46
+ | **Annotations** | Review tool `annotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) against actual behavior. If a tool is marked read-only, verify it does not mutate state. If it is marked idempotent, verify retries with the same input are safe. If it is marked open-world false, verify it is not silently depending on live external systems. |
47
+ | **Workflow chaining** | For servers with multi-step workflows, execute 1-2 representative chains end-to-end. Example: search → detail → follow-up action. Verify each step returns the IDs, cursors, URIs, tokens, or state needed for the next step without guessing. |
44
48
  | **Response quality** | Inspect successful responses for: (1) chaining IDs needed for follow-up calls, (2) operational metadata (counts, applied filters, truncation notices), (3) filtering transparency (if anything was excluded, does the response say what and how to include it?), (4) reasonable response size (not dumping unbounded data into context). See the `add-tool` skill's **Tool Response Design** section for the full set of patterns. |
49
+ | **Resilience** | For tools backed by external APIs or slow subsystems, test or explicitly note rate-limit, timeout, and transient-failure behavior. Verify retries/backoff happen where intended, or at minimum that the error message clearly tells the user whether to retry, wait, or change input. |
45
50
  | **Descriptions** | Read every field's `.describe()` — would a user/LLM understand what to provide? Flag vague or missing descriptions. |
46
51
 
47
52
  #### Resources
@@ -91,10 +96,15 @@ Cross-cutting observations that aren't tied to a single definition:
91
96
 
92
97
  - Inconsistent error message patterns across tools
93
98
  - Missing format functions (raw JSON returned to user)
99
+ - `structuredContent` contains data that `content[]` silently drops
100
+ - Requested projected fields are returned programmatically but not rendered for the model
94
101
  - Description quality issues (vague, missing, or misleading)
95
102
  - Schema design issues (required fields that should be optional, missing defaults, overly broad types, non-JSON-Schema-serializable types like `z.custom()` or `z.date()`)
103
+ - Annotation hints that do not match real behavior (`readOnlyHint`, `idempotentHint`, `openWorldHint`)
96
104
  - Response quality issues (empty results with no context, silent filtering, missing chaining IDs, oversized payloads, no operational metadata)
105
+ - Multi-step workflows that cannot be completed because intermediate outputs omit required IDs, cursors, or URIs
97
106
  - Error messages that don't guide recovery (generic "not found" instead of naming alternatives)
107
+ - Resilience issues (rate limits, timeouts, transient upstream failures handled poorly or explained poorly)
98
108
  - Performance observations (unexpectedly slow responses)
99
109
 
100
110
  ---
@@ -106,7 +116,12 @@ Cross-cutting observations that aren't tied to a single definition:
106
116
  - [ ] All registered prompts tested (happy path + defaults)
107
117
  - [ ] Error messages reviewed for clarity and recovery guidance
108
118
  - [ ] Empty-result responses reviewed for context (criteria echo, suggestions)
119
+ - [ ] `structuredContent` and `content[]` reviewed for parity
120
+ - [ ] Field-selection / projection behavior reviewed where applicable
109
121
  - [ ] Response quality reviewed (chaining IDs, metadata, filtering transparency, payload size)
122
+ - [ ] Tool annotations reviewed against actual behavior
123
+ - [ ] Representative multi-step workflows exercised where applicable
124
+ - [ ] External API resilience reviewed where applicable (rate limits, timeouts, transient failures)
110
125
  - [ ] Descriptions reviewed for completeness and accuracy
111
126
  - [ ] Format functions verified (or absence noted)
112
127
  - [ ] Summary report presented to user
@@ -4,7 +4,7 @@ description: >
4
4
  Sync skills and dependencies after package updates. Use after running `bun update @cyanheads/mcp-ts-core` to ensure project skills are up to date, or periodically to check for drift.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -35,7 +35,8 @@ After `bun update @cyanheads/mcp-ts-core`, the package may have newer skills tha
35
35
  2. For any major version bumps, review changelogs before proceeding
36
36
  3. Run `bun update` to apply updates
37
37
  4. Run `bun audit` to check for vulnerabilities introduced by the update
38
- 5. Run `bun run devcheck` to confirm lint, types, security, and tests still pass
38
+ 5. Run `bun run devcheck` to confirm lint, definitions, types, and dependency/security checks still pass
39
+ 6. Run `bun run test` to confirm runtime behavior still passes
39
40
 
40
41
  ## Checklist
41
42
 
@@ -44,3 +45,4 @@ After `bun update @cyanheads/mcp-ts-core`, the package may have newer skills tha
44
45
  - [ ] Dependencies updated (`bun update`)
45
46
  - [ ] `bun audit` passes (no new vulnerabilities)
46
47
  - [ ] `bun run devcheck` passes
48
+ - [ ] `bun run test` passes
@@ -4,7 +4,7 @@ description: >
4
4
  Migrate an existing mcp-ts-template fork to use @cyanheads/mcp-ts-core as a package dependency. Use when a project was cloned/forked from github.com/cyanheads/mcp-ts-template and carries framework source code in its own src/ — this skill rewrites those internal imports to package subpath imports and removes the bundled framework files.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.1"
7
+ version: "2.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -23,7 +23,7 @@ For the full exports catalog, see `CLAUDE.md` → Exports Reference.
23
23
  2. **Search for all `@/` imports** across `src/` that reference framework internals
24
24
  3. **Rewrite each import** using the mapping table below
25
25
  4. **Identify framework source files** now provided by the package (see candidates below) — review each for server-specific additions before cleaning up
26
- 5. **Update entry point** (`src/index.ts`) to use `createApp()` from the package
26
+ 5. **Update entry point** (`src/index.ts`) to use `createApp()` from the package and the project's chosen registration pattern (fresh scaffold default: direct imports in `src/index.ts`)
27
27
  6. **Update build configs**:
28
28
  - `tsconfig.json` extends `@cyanheads/mcp-ts-core/tsconfig.base.json`
29
29
  - `biome.json` extends `@cyanheads/mcp-ts-core/biome`
@@ -113,19 +113,19 @@ Framework files within directories that contain server code:
113
113
 
114
114
  ## Entry point rewrite
115
115
 
116
- Replace the fork's `src/index.ts` with:
116
+ Replace the fork's `src/index.ts` with the scaffold-default direct-registration pattern:
117
117
 
118
118
  ```ts
119
119
  #!/usr/bin/env node
120
120
  import { createApp } from '@cyanheads/mcp-ts-core';
121
- import { allToolDefinitions } from './mcp-server/tools/definitions/index.js';
122
- import { allResourceDefinitions } from './mcp-server/resources/definitions/index.js';
123
- import { allPromptDefinitions } from './mcp-server/prompts/definitions/index.js';
121
+ import { echoTool } from './mcp-server/tools/definitions/echo.tool.js';
122
+ import { echoResource } from './mcp-server/resources/definitions/echo.resource.js';
123
+ import { echoPrompt } from './mcp-server/prompts/definitions/echo.prompt.js';
124
124
 
125
125
  await createApp({
126
- tools: allToolDefinitions,
127
- resources: allResourceDefinitions,
128
- prompts: allPromptDefinitions,
126
+ tools: [echoTool],
127
+ resources: [echoResource],
128
+ prompts: [echoPrompt],
129
129
  });
130
130
  ```
131
131
 
@@ -133,15 +133,17 @@ Add `setup()` if the server initializes services:
133
133
 
134
134
  ```ts
135
135
  await createApp({
136
- tools: allToolDefinitions,
137
- resources: allResourceDefinitions,
138
- prompts: allPromptDefinitions,
136
+ tools: [echoTool],
137
+ resources: [echoResource],
138
+ prompts: [echoPrompt],
139
139
  setup(core) {
140
140
  initMyService(core.config, core.storage);
141
141
  },
142
142
  });
143
143
  ```
144
144
 
145
+ If the migrated project already has `definitions/index.ts` barrels and you want to keep them, that is fine. The important part is removing imports from framework internals and registering definitions consistently.
146
+
145
147
  ## Checklist
146
148
 
147
149
  - [ ] `@cyanheads/mcp-ts-core` installed as a dependency
@@ -4,7 +4,7 @@ description: >
4
4
  Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -23,7 +23,7 @@ Prefer running after implementation is complete, but safe to re-run at any point
23
23
 
24
24
  - [ ] All tools/resources/prompts implemented and registered
25
25
  - [ ] `bun run devcheck` passes
26
- - [ ] Tests pass (`npm test`)
26
+ - [ ] Tests pass (`bun run test`)
27
27
 
28
28
  If these aren't met, address them first.
29
29
 
@@ -166,7 +166,7 @@ Run the full check suite one last time:
166
166
 
167
167
  ```bash
168
168
  bun run devcheck
169
- npm test
169
+ bun run test
170
170
  ```
171
171
 
172
172
  Both must pass clean.
@@ -186,4 +186,4 @@ Both must pass clean.
186
186
  - [ ] `Dockerfile` OCI labels and runtime config accurate (if present)
187
187
  - [ ] `docs/tree.md` regenerated
188
188
  - [ ] `bun run devcheck` passes
189
- - [ ] `npm test` passes
189
+ - [ ] `bun run test` passes
@@ -4,23 +4,23 @@ 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.1"
7
+ version: "1.2"
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` (identical content), 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 `@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.
15
15
 
16
16
  ## Agent Protocol File
17
17
 
18
- The init CLI generates both `CLAUDE.md` and `AGENTS.md` with identical content. Keep the one your agent uses, discard the other:
18
+ The init CLI generates both `CLAUDE.md` and `AGENTS.md` with the same purpose. Keep one authoritative file for the agent you actually use:
19
19
 
20
20
  - **Claude Code** — keep `CLAUDE.md`, discard `AGENTS.md`
21
21
  - **All other agents** (Codex, Cursor, Windsurf, etc.) — keep `AGENTS.md`, discard `CLAUDE.md`
22
22
 
23
- Both files serve the same purpose: project-specific agent instructions. Only one should exist in the committed project.
23
+ Both files serve the same purpose: project-specific agent instructions. Prefer committing one authoritative copy rather than trying to keep both in sync by hand.
24
24
 
25
25
  For the full framework API, read:
26
26
 
@@ -34,7 +34,7 @@ What `init` actually creates:
34
34
 
35
35
  ```text
36
36
  CLAUDE.md # Agent protocol (project-specific)
37
- AGENTS.md # Same contentdiscard whichever you don't use
37
+ AGENTS.md # Alternate agent protocol file keep the one your agent uses
38
38
  .github/ISSUE_TEMPLATE/ # GitHub issue templates (bug report, feature request)
39
39
  skills/ # Project skills (source of truth)
40
40
  src/
@@ -95,19 +95,19 @@ After the initial copy, use the `maintenance` skill to keep them in sync after p
95
95
 
96
96
  ## Project Scaffolding
97
97
 
98
- After installing dependencies (`npm install`, or `bun install` if using Bun), complete these one-time setup tasks:
98
+ After installing dependencies (prefer `bun install`; `npm install` also works), complete these one-time setup tasks:
99
99
 
100
- 1. **Update dependencies to latest** — `npx npm-check-updates -u && npm install` (or `bun update --latest` if using Bun). The scaffolded `package.json` pins minimum versions from when the framework was published; updating ensures you start with the latest compatible releases.
100
+ 1. **Update dependencies to latest** — `bun update --latest` (or `npx npm-check-updates -u && npm install` if using npm). The scaffolded `package.json` pins minimum versions from when the framework was published; updating ensures you start with the latest compatible releases.
101
101
  2. **Initialize git** — `git init && git add -A && git commit -m "chore: scaffold from @cyanheads/mcp-ts-core"`
102
102
  3. **Verify agent protocol placeholders** — if the `init` CLI was run without a `[name]` argument, `{{PACKAGE_NAME}}` may remain as a literal in `CLAUDE.md`/`AGENTS.md` and `package.json`. Replace it with the actual server name.
103
103
 
104
104
  ## Checklist
105
105
 
106
- - [ ] Agent protocol file selected — keep `CLAUDE.md` or `AGENTS.md`, discard the other
106
+ - [ ] Agent protocol file selected — keep one authoritative file (`CLAUDE.md` or `AGENTS.md`)
107
107
  - [ ] `{{PACKAGE_NAME}}` placeholders replaced in agent protocol file (if not auto-substituted by init)
108
108
  - [ ] Core framework CLAUDE.md read (`node_modules/@cyanheads/mcp-ts-core/CLAUDE.md`)
109
109
  - [ ] Unused echo definitions cleaned up (and unregistered from `src/index.ts`)
110
110
  - [ ] Skills copied to agent directory (`cp -R skills/* .claude/skills/` or equivalent)
111
111
  - [ ] Project structure understood (definitions directories, entry point)
112
- - [ ] `npm run devcheck` passes
112
+ - [ ] `bun run devcheck` passes
113
113
  - [ ] If new server: proceed to `design-mcp-server` skill to plan the tool surface
@@ -1,7 +1,7 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** {{PACKAGE_NAME}}
4
- **Version:** 0.1.0
4
+ **Version:** 0.1.1
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
6
6
 
7
7
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -224,6 +224,7 @@ Available skills:
224
224
  | `setup` | Post-init project orientation |
225
225
  | `design-mcp-server` | Design tool surface, resources, and services for a new server |
226
226
  | `add-tool` | Scaffold a new tool definition |
227
+ | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
227
228
  | `add-resource` | Scaffold a new resource definition |
228
229
  | `add-prompt` | Scaffold a new prompt definition |
229
230
  | `add-service` | Scaffold a new service integration |
@@ -281,10 +282,14 @@ import { getMyService } from '@/services/my-domain/my-service.js';
281
282
  ## Checklist
282
283
 
283
284
  - [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, etc.)
285
+ - [ ] Optional nested objects: handler guards for empty inner values from form-based clients (`if (input.obj?.field && ...)`, not just `if (input.obj)`)
284
286
  - [ ] JSDoc `@fileoverview` + `@module` on every file
285
287
  - [ ] `ctx.log` for logging, `ctx.state` for storage
286
288
  - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
287
289
  - [ ] `format()` renders all data the LLM needs — `content[]` is the only field most clients forward to the model
290
+ - [ ] If wrapping external API: raw/domain/output schemas reviewed against real upstream sparsity/nullability before finalizing required vs optional fields
291
+ - [ ] If wrapping external API: normalization and `format()` preserve uncertainty; do not fabricate facts from missing upstream data
292
+ - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
288
293
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
289
294
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
290
295
  - [ ] `npm run devcheck` passes
@@ -1,7 +1,7 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** {{PACKAGE_NAME}}
4
- **Version:** 0.1.0
4
+ **Version:** 0.1.1
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
6
6
 
7
7
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -25,7 +25,7 @@ When the user asks what to do next, what's left, or needs direction, suggest rel
25
25
 
26
26
  1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
27
27
  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
28
+ 3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
29
29
  4. **Add services** — scaffold domain service integrations using the `add-service` skill
30
30
  5. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
31
31
  6. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
@@ -224,6 +224,7 @@ Available skills:
224
224
  | `setup` | Post-init project orientation |
225
225
  | `design-mcp-server` | Design tool surface, resources, and services for a new server |
226
226
  | `add-tool` | Scaffold a new tool definition |
227
+ | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
227
228
  | `add-resource` | Scaffold a new resource definition |
228
229
  | `add-prompt` | Scaffold a new prompt definition |
229
230
  | `add-service` | Scaffold a new service integration |
@@ -286,6 +287,9 @@ import { getMyService } from '@/services/my-domain/my-service.js';
286
287
  - [ ] `ctx.log` for logging, `ctx.state` for storage
287
288
  - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
288
289
  - [ ] `format()` renders all data the LLM needs — `content[]` is the only field most clients forward to the model
290
+ - [ ] If wrapping external API: raw/domain/output schemas reviewed against real upstream sparsity/nullability before finalizing required vs optional fields
291
+ - [ ] If wrapping external API: normalization and `format()` preserve uncertainty; do not fabricate facts from missing upstream data
292
+ - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
289
293
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
290
294
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
291
295
  - [ ] `npm run devcheck` passes
@@ -62,7 +62,12 @@ const APP_HTML = `<!DOCTYPE html>
62
62
  </div>
63
63
 
64
64
  <script type="module">
65
- import { App } from "https://unpkg.com/@modelcontextprotocol/ext-apps@1/app-with-deps";
65
+ import {
66
+ App,
67
+ applyDocumentTheme,
68
+ applyHostFonts,
69
+ applyHostStyleVariables,
70
+ } from "https://unpkg.com/@modelcontextprotocol/ext-apps@1/app-with-deps";
66
71
 
67
72
  const app = new App({ name: "Echo App", version: "1.0.0" });
68
73
  const messageEl = document.getElementById("message");
@@ -70,6 +75,18 @@ const APP_HTML = `<!DOCTYPE html>
70
75
  const inputEl = document.getElementById("input");
71
76
  const sendBtn = document.getElementById("send");
72
77
 
78
+ function applyHostContext(hostContext) {
79
+ if (hostContext?.theme) {
80
+ applyDocumentTheme(hostContext.theme);
81
+ }
82
+ if (hostContext?.styles?.variables) {
83
+ applyHostStyleVariables(hostContext.styles.variables);
84
+ }
85
+ if (hostContext?.styles?.css?.fonts) {
86
+ applyHostFonts(hostContext.styles.css.fonts);
87
+ }
88
+ }
89
+
73
90
  function render(content) {
74
91
  const text = content?.find(c => c.type === "text")?.text;
75
92
  if (!text) return;
@@ -82,6 +99,7 @@ const APP_HTML = `<!DOCTYPE html>
82
99
 
83
100
  // Receive initial tool result pushed by the host
84
101
  app.ontoolresult = (result) => render(result.content);
102
+ app.onhostcontextchanged = applyHostContext;
85
103
 
86
104
  // Send new echo from the UI
87
105
  sendBtn.addEventListener("click", async () => {
@@ -106,7 +124,10 @@ const APP_HTML = `<!DOCTYPE html>
106
124
  if (e.key === "Enter") sendBtn.click();
107
125
  });
108
126
 
109
- await app.connect();
127
+ app.connect().then(() => {
128
+ const hostContext = app.getHostContext();
129
+ if (hostContext) applyHostContext(hostContext);
130
+ });
110
131
  </script>
111
132
  </body>
112
133
  </html>`;
@@ -119,13 +140,13 @@ export const echoAppUiResource = appResource('ui://template-echo-app/app.html',
119
140
  description:
120
141
  'Interactive HTML app for the echo app tool. Displayed as a sandboxed iframe ' +
121
142
  'by MCP Apps-capable hosts.',
143
+ params: ParamsSchema,
144
+ auth: ['resource:echo-app-ui:read'],
122
145
  _meta: {
123
146
  ui: {
124
147
  csp: { resourceDomains: ['https://unpkg.com'] },
125
148
  },
126
149
  },
127
- params: ParamsSchema,
128
- auth: ['resource:echo-app-ui:read'],
129
150
 
130
151
  handler(_params, ctx) {
131
152
  ctx.log.debug('Serving echo app UI.', { resourceUri: ctx.uri?.href });