@cyanheads/mcp-ts-core 0.10.2 → 0.10.3

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 (74) hide show
  1. package/AGENTS.md +14 -8
  2. package/CLAUDE.md +14 -8
  3. package/README.md +5 -6
  4. package/changelog/0.10.x/0.10.2.md +4 -4
  5. package/changelog/0.10.x/0.10.3.md +46 -0
  6. package/dist/core/app.d.ts +36 -0
  7. package/dist/core/app.d.ts.map +1 -1
  8. package/dist/core/app.js +9 -4
  9. package/dist/core/app.js.map +1 -1
  10. package/dist/core/context.d.ts +35 -15
  11. package/dist/core/context.d.ts.map +1 -1
  12. package/dist/core/context.js +0 -1
  13. package/dist/core/context.js.map +1 -1
  14. package/dist/core/index.d.ts +5 -2
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/index.js +1 -0
  17. package/dist/core/index.js.map +1 -1
  18. package/dist/core/serverManifest.d.ts +18 -0
  19. package/dist/core/serverManifest.d.ts.map +1 -1
  20. package/dist/core/serverManifest.js +10 -2
  21. package/dist/core/serverManifest.js.map +1 -1
  22. package/dist/logs/combined.log +4 -4
  23. package/dist/logs/error.log +2 -2
  24. package/dist/mcp-server/notifications.d.ts +4 -4
  25. package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
  26. package/dist/mcp-server/prompts/prompt-registration.js +1 -0
  27. package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
  28. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts +6 -0
  29. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts.map +1 -1
  30. package/dist/mcp-server/prompts/utils/promptDefinition.js.map +1 -1
  31. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  32. package/dist/mcp-server/resources/resource-registration.js +3 -0
  33. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  34. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +9 -0
  35. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -1
  36. package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -1
  37. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +13 -2
  38. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  39. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +23 -12
  40. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  41. package/dist/mcp-server/server.d.ts +18 -2
  42. package/dist/mcp-server/server.d.ts.map +1 -1
  43. package/dist/mcp-server/server.js +4 -1
  44. package/dist/mcp-server/server.js.map +1 -1
  45. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/tool-registration.js +2 -0
  47. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  48. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +14 -2
  49. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +37 -13
  51. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  52. package/dist/testing/index.d.ts +8 -14
  53. package/dist/testing/index.d.ts.map +1 -1
  54. package/dist/testing/index.js +10 -11
  55. package/dist/testing/index.js.map +1 -1
  56. package/package.json +2 -2
  57. package/skills/add-prompt/SKILL.md +33 -2
  58. package/skills/add-resource/SKILL.md +25 -1
  59. package/skills/add-service/SKILL.md +2 -2
  60. package/skills/add-test/SKILL.md +3 -3
  61. package/skills/api-config/SKILL.md +12 -3
  62. package/skills/api-context/SKILL.md +17 -31
  63. package/skills/api-testing/SKILL.md +2 -16
  64. package/skills/code-simplifier/SKILL.md +2 -2
  65. package/skills/polish-docs-meta/SKILL.md +1 -1
  66. package/skills/polish-docs-meta/references/agent-protocol.md +1 -1
  67. package/skills/report-issue-framework/SKILL.md +2 -2
  68. package/skills/security-pass/SKILL.md +6 -11
  69. package/templates/AGENTS.md +17 -5
  70. package/templates/CLAUDE.md +17 -5
  71. package/dist/mcp-server/roots/roots-registration.d.ts +0 -22
  72. package/dist/mcp-server/roots/roots-registration.d.ts.map +0 -1
  73. package/dist/mcp-server/roots/roots-registration.js +0 -25
  74. package/dist/mcp-server/roots/roots-registration.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
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",
@@ -193,7 +193,7 @@
193
193
  "@supabase/supabase-js": "^2.108.1",
194
194
  "@types/bun": "^1.3.14",
195
195
  "@types/js-yaml": "^4.0.9",
196
- "@types/node": "^25.9.2",
196
+ "@types/node": "25.9.3",
197
197
  "@types/papaparse": "^5.5.2",
198
198
  "@types/sanitize-html": "^2.16.1",
199
199
  "@types/validator": "^13.15.10",
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP prompt template. Use when the user asks to add a prompt, create a reusable message template, or define a prompt for LLM interactions.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -30,14 +30,18 @@ Prompts are pure message templates — no `Context`, no auth, no side effects. `
30
30
  * @module mcp-server/prompts/definitions/{{PROMPT_NAME}}
31
31
  */
32
32
 
33
- import { prompt, z } from '@cyanheads/mcp-ts-core';
33
+ import { completable, prompt, z } from '@cyanheads/mcp-ts-core';
34
34
 
35
35
  export const {{PROMPT_EXPORT}} = prompt('{{prompt_name}}', {
36
36
  description: '{{PROMPT_DESCRIPTION}}',
37
+ // title is optional — human-readable display name surfaced in prompts/list.
38
+ // title: '{{PROMPT_DISPLAY_TITLE}}',
37
39
  // args is optional — omit entirely for prompts with no parameters.
38
40
  // When present, all fields need .describe(). Only JSON-Schema-serializable types allowed.
41
+ // Wrap any field with completable() to enable argument autocompletion.
39
42
  args: z.object({
40
43
  // All fields need .describe()
44
+ // language: completable(z.string().describe('Language'), async (partial) => matchingLanguages(partial)),
41
45
  }),
42
46
  generate: (args) => [
43
47
  {
@@ -92,13 +96,40 @@ If the repo already uses `src/mcp-server/prompts/definitions/index.ts`, add the
92
96
  export { {{PROMPT_EXPORT}} } from './{{prompt-name}}.prompt.js';
93
97
  ```
94
98
 
99
+ ## Argument autocompletion
100
+
101
+ Wrap any `args` field with `completable()` (re-exported from `@cyanheads/mcp-ts-core`) to enable argument autocompletion. The SDK auto-installs `completion/complete` handling and advertises the `completions` capability when any registered prompt has a completable argument — no other changes are needed.
102
+
103
+ ```typescript
104
+ import { completable, prompt, z } from '@cyanheads/mcp-ts-core';
105
+
106
+ export const codeReview = prompt('code_review', {
107
+ description: 'Review code for issues.',
108
+ title: 'Code Review',
109
+ args: z.object({
110
+ language: completable(
111
+ z.string().describe('Programming language'),
112
+ async (partial) => ['typescript', 'python', 'rust'].filter((l) => l.startsWith(partial)),
113
+ ),
114
+ code: z.string().describe('Code to review'),
115
+ }),
116
+ generate: (args) => [
117
+ { role: 'user', content: { type: 'text', text: `Review this ${args.language} code:\n${args.code}` } },
118
+ ],
119
+ });
120
+ ```
121
+
122
+ `completable()` is transparent to the linter — it does not affect `describe-on-fields` or `schema-serializable` rules. All completable-wrapped fields still require `.describe()` on the underlying schema.
123
+
95
124
  ## Checklist
96
125
 
97
126
  - [ ] File created at `src/mcp-server/prompts/definitions/{{prompt-name}}.prompt.ts`
98
127
  - [ ] Prompt name passed to `prompt()` uses snake_case
99
128
  - [ ] `description` field set (lint warns if absent, but `devcheck` won't hard-fail — verify it's present)
129
+ - [ ] `title` field set if a human-readable display name is needed in `prompts/list`
100
130
  - [ ] All Zod `args` fields have `.describe()` annotations — or `args` omitted entirely for no-parameter prompts
101
131
  - [ ] `args` fields use only JSON-Schema-serializable Zod types (no `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.custom()`, etc.)
132
+ - [ ] If using `completable()`, the underlying schema still has `.describe()` on each wrapped field
102
133
  - [ ] JSDoc `@fileoverview` and `@module` header present
103
134
  - [ ] `generate` function present and returns at least one `{ role, content: { type: 'text', text } }` message
104
135
  - [ ] No side effects — prompts are pure templates
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP resource definition. Use when the user asks to add a resource, expose data via URI, or create a readable endpoint.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -141,6 +141,29 @@ export const articleResource = resource('article://{pmid}', {
141
141
 
142
142
  Without `errors[]`, the handler receives plain `Context` (no `fail` method) and throws via error factories (`notFound`, `serviceUnavailable`, …) directly. The contract is opt-in. See `skills/api-errors/SKILL.md` for the full pattern, baseline codes, and conformance rules.
143
143
 
144
+ ### URI template variable completion
145
+
146
+ Add a `complete` map to enable autocompletion for URI template variables. The SDK auto-installs `completion/complete` handling and advertises the `completions` capability when a registered resource template has completion callbacks — no other changes needed.
147
+
148
+ ```typescript
149
+ import { resource, z } from '@cyanheads/mcp-ts-core';
150
+
151
+ const ITEM_IDS = ['item-001', 'item-002', 'item-abc'];
152
+
153
+ export const itemResource = resource('items://{itemId}', {
154
+ description: 'Retrieve an item by ID.',
155
+ params: z.object({ itemId: z.string().describe('Item identifier') }),
156
+ handler: (params) => ({ id: params.itemId }),
157
+ list: () => ({ resources: ITEM_IDS.map((id) => ({ uri: `items://${id}`, name: id })) }),
158
+ // Per-variable completion callbacks — keys must match URI template variable names.
159
+ complete: {
160
+ itemId: async (partial) => ITEM_IDS.filter((id) => id.startsWith(partial)),
161
+ },
162
+ });
163
+ ```
164
+
165
+ Only applies to templated resources (URI templates with `{variable}` syntax). Static URIs don't support completion.
166
+
144
167
  ### Other `resource()` options
145
168
 
146
169
  Beyond `description`, `params`, `handler`, and `list`, the builder also supports:
@@ -153,6 +176,7 @@ Beyond `description`, `params`, `handler`, and `list`, the builder also supports
153
176
  | `annotations` | Resource annotations (e.g., `audience`, `priority`) — see `ResourceAnnotations`. |
154
177
  | `title` | Human-readable display title (defaults to `name`). |
155
178
  | `examples` | Array of `{ name, uri }` example entries surfaced in `resources/list` for discoverability. |
179
+ | `complete` | Per-variable completion callbacks for URI template variables. Keys match template variable names. Enables `completion/complete` and the `completions` capability. |
156
180
 
157
181
  ## Checklist
158
182
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new service integration. Use when the user asks to add a service, integrate an external API, or create a reusable domain module with its own initialization and state.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -13,7 +13,7 @@ metadata:
13
13
 
14
14
  Services use the init/accessor pattern: initialized once in `createApp`'s `setup()` callback, then accessed at request time via a lazy getter. Each service lives in `src/services/[domain]/` with an init function and accessor.
15
15
 
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.
16
+ Service methods receive `Context` for correlated logging (`ctx.log`) and tenant-scoped storage (`ctx.state`). Convention: `ctx.elicit` should only be called from tool handlers, not from services.
17
17
 
18
18
  For the full service pattern, `CoreServices`, and `Context` interface, read the framework's `CLAUDE.md`/`AGENTS.md` (loaded at session start).
19
19
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a test file for an existing tool, resource, or service. Use when the user asks to add tests, improve coverage, or when a definition exists without a matching test file.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -36,7 +36,7 @@ Read the handler and identify:
36
36
  | **Input variations** | Optional fields omitted, defaults applied, boundary values |
37
37
  | **Error paths** | Invalid state, missing resources, service failures → correct error thrown |
38
38
  | **`ctx.state` usage** | Use `createMockContext({ tenantId: 'test' })` to enable storage |
39
- | **`ctx.elicit` / `ctx.sample`** | Mock with `vi.fn()`, also test the absent case (undefined) |
39
+ | **`ctx.elicit`** | Mock with `vi.fn()`, also test the absent case (undefined) |
40
40
  | **`ctx.progress`** | Use `createMockContext({ progress: true })` for task tools |
41
41
  | **`ctx.fail` (typed contract)** | Definitions with `errors[]` need `fail` attached to the mock ctx — `createMockContext({ errors: myTool.errors })` does it for you. Assert on `data.reason` (stable per-contract entry), not just `code`. |
42
42
  | **`format` function** | Test separately if defined — it's pure, no ctx needed. Verify it renders the IDs and fields the model needs, not just a count or title. For projection-style tools, test non-default field selections. |
@@ -286,7 +286,7 @@ When scaffolding tests for an existing handler, use the Zod schemas to generate
286
286
  - [ ] Happy path tested with valid input → expected output
287
287
  - [ ] Error paths tested (at least one `.rejects.toThrow()`)
288
288
  - [ ] `format` function tested if defined
289
- - [ ] `createMockContext` options match handler's ctx usage (`tenantId`, `progress`, `elicit`, `sample`)
289
+ - [ ] `createMockContext` options match handler's ctx usage (`tenantId`, `progress`, `elicit`)
290
290
  - [ ] Service re-initialized in `beforeEach` if handler depends on a service singleton
291
291
  - [ ] If handler has optional fields: tested with empty-string inner values (form-client simulation)
292
292
  - [ ] If wrapping external API: sparse-payload case tested — fixture omits at least one optional upstream field; output still validates and `format()` renders uncertainty honestly instead of inventing values
@@ -4,7 +4,7 @@ description: >
4
4
  Reference for core and server configuration in `@cyanheads/mcp-ts-core`. Covers env var tables with defaults, priority order, server-specific Zod schema pattern, and Workers lazy-parsing requirement.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.6"
7
+ version: "1.7"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -23,7 +23,7 @@ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod from environment variable
23
23
 
24
24
  **Priority (highest to lowest):**
25
25
 
26
- 1. `name`/`version` overrides passed to `createApp()` or `createWorkerHandler()`
26
+ 1. `name`/`version`/`title`/`websiteUrl`/`description`/`icons` options passed to `createApp()` or `createWorkerHandler()`
27
27
  2. Environment variables
28
28
  3. `package.json` fields
29
29
 
@@ -35,9 +35,18 @@ Managed by `@cyanheads/mcp-ts-core`. Validated via Zod from environment variable
35
35
  |:--------|:-----------------|:--------|:------|
36
36
  | `MCP_SERVER_NAME` | `mcpServerName` | `package.json` `name` | Overrides package name |
37
37
  | `MCP_SERVER_VERSION` | `mcpServerVersion` | `package.json` `version` | Overrides package version |
38
- | `MCP_SERVER_DESCRIPTION` | `mcpServerDescription` | `package.json` `description` | Optional |
38
+ | `MCP_SERVER_DESCRIPTION` | `mcpServerDescription` | `package.json` `description` | Optional; `createApp({ description })` wins when set |
39
39
  | `PACKAGE_NAME` | `pkg.name` | `package.json` `name` | Rarely needed |
40
40
  | `PACKAGE_VERSION` | `pkg.version` | `package.json` `version` | Rarely needed |
41
+
42
+ **SDK identity fields** (API-only, no env var equivalent — passed to `createApp()` / `createWorkerHandler()`, forwarded to `initialize` and `/.well-known/mcp.json`):
43
+
44
+ | Option | Type | Notes |
45
+ |:-------|:-----|:------|
46
+ | `title` | `string?` | Human-readable display name shown in client listings |
47
+ | `websiteUrl` | `string?` | Canonical homepage / repository URL |
48
+ | `description` | `string?` | One-line description; wins over `MCP_SERVER_DESCRIPTION` when set |
49
+ | `icons` | `Implementation['icons']?` | Array of icon objects: `{ src, mimeType?, sizes?: string[], theme?: 'light'\|'dark' }` |
41
50
  | `NODE_ENV` | `environment` | `development` | Aliases: `dev`→`development`, `prod`→`production`, `test`→`testing` |
42
51
  | `MCP_LOG_LEVEL` | `logLevel` | `debug` | Aliases: `warn`→`warning`, `err`→`error`, `fatal`/`silent`→`emerg`, `trace`→`debug`, `information`→`info` |
43
52
  | `LOGS_DIR` | `logsPath` | `<project-root>/logs` | Node.js only; absolute or relative to project root |
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: api-context
3
3
  description: >
4
- Canonical reference for the unified `Context` object passed to every tool and resource handler in `@cyanheads/mcp-ts-core`. Covers the full interface, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.sample`, `ctx.progress`, `ctx.enrich`), and when to use each.
4
+ Canonical reference for the unified `Context` object passed to every tool and resource handler in `@cyanheads/mcp-ts-core`. Covers the full interface, all sub-APIs (`ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.progress`, `ctx.enrich`), and when to use each.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
11
11
 
12
12
  ## Overview
13
13
 
14
- Every tool and resource handler receives a single `Context` (`ctx`) argument. It provides request identity, structured logging, tenant-scoped storage, optional protocol capabilities (elicitation, sampling), cancellation, and task progress — all auto-correlated to the current request.
14
+ Every tool and resource handler receives a single `Context` (`ctx`) argument. It provides request identity, structured logging, tenant-scoped storage, optional protocol capabilities (elicitation), cancellation, and task progress — all auto-correlated to the current request.
15
15
 
16
16
  The framework auto-instruments every handler call (OTel span, duration, payload metrics). Use `ctx.log` for domain-specific logging and `ctx.state` for storage inside handlers. Use the global `logger` and `StorageService` directly only in lifecycle/background code (`setup()`, services).
17
17
 
@@ -39,8 +39,7 @@ interface Context {
39
39
  readonly state: ContextState;
40
40
 
41
41
  // Optional protocol capabilities (undefined when client doesn't support them)
42
- readonly elicit?: (message: string, schema: z.ZodObject<z.ZodRawShape>) => Promise<ElicitResult>;
43
- readonly sample?: (messages: SamplingMessage[], opts?: SamplingOpts) => Promise<CreateMessageResult>;
42
+ readonly elicit?: ElicitFn; // callable (message, schema) for form mode + .url(message, url) see § ctx.elicit
44
43
 
45
44
  // List-changed / resource-updated notifications — wired in every handler ctx;
46
45
  // delivery is request-scoped (see § list-changed notifications)
@@ -254,9 +253,11 @@ await ctx.state.set(sessionKey, value);
254
253
 
255
254
  ---
256
255
 
257
- ## `ctx.elicit` / `ctx.sample`
256
+ ## `ctx.elicit`
258
257
 
259
- Both are optional — `undefined` when the connected client doesn't support the capability. Check for presence before calling. A simple truthiness check is enough; no type guards needed.
258
+ Optional — `undefined` when the connected client doesn't advertise the `elicitation` capability (checked per request, after the initialize handshake). Check for presence before calling. A simple truthiness check is enough; no type guards needed.
259
+
260
+ `ctx.elicit` is an `ElicitFn` (exported from the main entry): directly callable for form-mode elicitation, with a `.url(message, url)` method for URL-mode. On the wire, the Zod schema is converted to the restricted flat JSON Schema the MCP spec requires — handlers never deal with that detail.
260
261
 
261
262
  ### `ctx.elicit` — ask the user for structured input
262
263
 
@@ -295,37 +296,23 @@ interface ElicitResult {
295
296
 
296
297
  > **Note:** `content` is not typed against the Zod schema you pass — it is a `Record` of primitives. Validate `content` against your schema manually (e.g. `MySchema.parse(result.content)`) when `action === 'accept'`.
297
298
 
298
- **Convention:** Only call `ctx.elicit` from tool handlers, not from services.
299
-
300
- ### `ctx.sample` — request an LLM completion from the client
299
+ ### `ctx.elicit.url` hand the user an external link
301
300
 
302
- Requests a completion from the client's LLM via the MCP sampling protocol. Useful for AI-assisted tool behavior without managing a separate LLM provider.
301
+ URL-mode elicitation (MCP 2025-11-25): instead of an inline form, the client directs the user to an external URL authorization flows, hosted forms. The framework generates the protocol-required `elicitationId` internally.
303
302
 
304
303
  ```ts
305
- if (ctx.sample) {
306
- const result = await ctx.sample(
307
- [
308
- { role: 'user', content: { type: 'text', text: `Summarize: ${data}` } },
309
- ],
310
- { maxTokens: 500 },
304
+ if (ctx.elicit) {
305
+ const result = await ctx.elicit.url(
306
+ 'Authorize access to your account',
307
+ 'https://example.com/oauth/authorize?state=...',
311
308
  );
312
- return { summary: result.content.text };
309
+ if (result.action !== 'accept') throw forbidden('Authorization declined');
313
310
  }
314
311
  ```
315
312
 
316
- `SamplingOpts`:
313
+ `result.content` is absent in URL mode — the interaction completes out-of-band; only `action` reports the outcome.
317
314
 
318
- ```ts
319
- interface SamplingOpts {
320
- includeContext?: 'none' | 'thisServer' | 'allServers';
321
- maxTokens?: number;
322
- modelPreferences?: ModelPreferences;
323
- stopSequences?: string[];
324
- temperature?: number;
325
- }
326
- ```
327
-
328
- **Convention:** Only call `ctx.sample` from tool handlers, not from services.
315
+ **Convention:** Only call `ctx.elicit` from tool handlers, not from services.
329
316
 
330
317
  ---
331
318
 
@@ -666,7 +653,6 @@ See `add-tool`'s **Tool Response Design** and `skills/api-linter` (`enrichment-*
666
653
  | `ctx.signal` | `AbortSignal` | Always |
667
654
  | `ctx.enrich` | `Enrich` | Always; typed on `HandlerContext<R, E>` when an `enrichment` block is declared |
668
655
  | `ctx.elicit` | `function \| undefined` | Client supports elicitation |
669
- | `ctx.sample` | `function \| undefined` | Client supports sampling |
670
656
  | `ctx.notifyResourceListChanged` | `function \| undefined` | Always in handler ctx; delivery request-scoped (see [§ list-changed notifications](#list-changed-notifications-ctxnotify)) |
671
657
  | `ctx.notifyResourceUpdated` | `function \| undefined` | Always in handler ctx; delivery request-scoped |
672
658
  | `ctx.notifyPromptListChanged` | `function \| undefined` | Always in handler ctx; delivery request-scoped |
@@ -4,7 +4,7 @@ description: >
4
4
  Testing patterns for MCP tool/resource handlers using `createMockContext` and Vitest. Covers mock context options, handler testing, McpError assertions, format testing, Vitest config setup, and test isolation conventions.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.2"
7
+ version: "1.3"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -27,7 +27,6 @@ import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
27
27
  createMockContext() // minimal — ctx.state operations throw without tenantId
28
28
  createMockContext({ tenantId: 'test-tenant' }) // enables ctx.state (tenant-scoped in-memory storage)
29
29
  createMockContext({ errors: myTool.errors }) // attaches typed ctx.fail keyed by the contract reasons
30
- createMockContext({ sample: vi.fn().mockResolvedValue(...) }) // with MCP sampling
31
30
  createMockContext({ elicit: vi.fn().mockResolvedValue(...) }) // with elicitation
32
31
  createMockContext({ progress: true }) // with task progress (ctx.progress populated)
33
32
  createMockContext({ requestId: 'my-id' }) // override request ID (default: 'test-request-id')
@@ -52,7 +51,6 @@ interface MockContextOptions {
52
51
  progress?: boolean;
53
52
  sessionId?: string;
54
53
  requestId?: string;
55
- sample?: (messages: SamplingMessage[], opts?: SamplingOpts) => Promise<CreateMessageResult>;
56
54
  signal?: AbortSignal;
57
55
  tenantId?: string;
58
56
  uri?: URL;
@@ -61,7 +59,7 @@ interface MockContextOptions {
61
59
 
62
60
  | Option | Effect |
63
61
  |:-------|:-------|
64
- | _(none)_ | Minimal context — `ctx.state` operations throw without `tenantId`; `ctx.elicit`/`ctx.sample`/`ctx.progress` are `undefined` |
62
+ | _(none)_ | Minimal context — `ctx.state` operations throw without `tenantId`; `ctx.elicit`/`ctx.progress` are `undefined` |
65
63
  | `auth` | Sets `ctx.auth` for scope-checking tests |
66
64
  | `elicit` | Assigns a function to `ctx.elicit` for testing elicitation calls |
67
65
  | `errors` | Attaches a typed `ctx.fail` against the contract — same wiring the production handler factory uses. Pass `myTool.errors` directly. |
@@ -72,7 +70,6 @@ interface MockContextOptions {
72
70
  | `sessionId` | Sets `ctx.sessionId` for handlers that branch on session ID |
73
71
  | `progress` | Populates `ctx.progress` with real state-tracking implementation (see below) |
74
72
  | `requestId` | Overrides `ctx.requestId` (default: `'test-request-id'`) |
75
- | `sample` | Assigns a function to `ctx.sample` for testing sampling calls |
76
73
  | `signal` | Overrides `ctx.signal` — useful for cancellation testing |
77
74
  | `tenantId` | Sets `ctx.tenantId` and enables `ctx.state` operations with in-memory storage |
78
75
  | `uri` | Sets `ctx.uri` for resource handler testing |
@@ -164,17 +161,6 @@ it('uses elicitation when available', async () => {
164
161
  expect(elicit).toHaveBeenCalledOnce();
165
162
  });
166
163
 
167
- it('uses sampling when available', async () => {
168
- const sample = vi.fn().mockResolvedValue({
169
- role: 'assistant',
170
- content: { type: 'text', text: 'Summary text' },
171
- });
172
- const ctx = createMockContext({ sample });
173
- const input = myTool.input.parse({ query: 'summarize this' });
174
- const result = await myTool.handler(input, ctx);
175
- expect(result.summary).toBeDefined();
176
- });
177
-
178
164
  it('handles missing elicitation gracefully', async () => {
179
165
  // ctx.elicit is undefined — handler must check before calling
180
166
  const ctx = createMockContext();
@@ -4,7 +4,7 @@ description: >
4
4
  Post-session code review and cleanup against a working tree of changes. Analyzes `git diff` to simplify, consolidate, and align changed code with the existing codebase — modernize syntax, remove unnecessary complexity, consolidate duplicated logic, catch efficiency issues. Use after a substantive working session, or when asked to clean up, simplify, reduce slop, consolidate, modernize, tighten up, or de-slop code. For `@cyanheads/mcp-ts-core` projects, includes specific transformations for tool/resource/prompt definitions, the ctx pattern, error factories, and framework idioms.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -62,7 +62,7 @@ Evaluate the changes across these dimensions. Not every dimension applies to eve
62
62
 
63
63
  - **Error throwing patterns** — Prefer framework error factories (`McpError`, `validationError`, `notFound`, `httpErrorFromResponse`) over raw `throw new Error()`. Tool handlers should throw — the framework catches, classifies, and instruments.
64
64
  - **Error codes** — `InvalidParams` only for malformed JSON-RPC params shape. `ValidationError` for domain validation. `NotFound` for missing entities. Don't conflate them.
65
- - **Ctx usage** — Use `ctx.log`, `ctx.state`, `ctx.elicit`, `ctx.sample` — don't reach for global loggers, request-scoped storage, or sampling APIs directly. The `ctx` pattern carries tenant scope and OTel context.
65
+ - **Ctx usage** — Use `ctx.log`, `ctx.state`, `ctx.elicit` — don't reach for global loggers or request-scoped storage directly. The `ctx` pattern carries tenant scope and OTel context.
66
66
  - **Zod schemas** — Every tool input/output field needs `.describe()`. Zod 4 requires `z.record(z.string(), z.string())` not `z.record(z.string())`. Use `.optional()` rather than `.nullish()` unless null is semantically distinct from absent.
67
67
  - **Tool annotations** — `readOnlyHint`, `idempotentHint`, `openWorldHint` should reflect reality. A read-only tool with `readOnlyHint: false` gives clients the wrong picture.
68
68
  - **`exactOptionalPropertyTypes` boundaries** — If a downstream type insists on the field being present-or-not-present (not present-as-undefined), use a mapped widening type at the boundary. The pattern is documented in the framework.
@@ -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: "2.5"
7
+ version: "2.6"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -46,7 +46,7 @@ Compare the structure diagram against the actual directory layout. If it still r
46
46
 
47
47
  ### 4. Update the Context Table
48
48
 
49
- Review the `ctx` feature table. Remove rows for features the server doesn't use (e.g., `ctx.elicit`, `ctx.sample` if no tools call them). Add any custom context usage that's become important. The table should reflect what this server actually uses, not the full framework surface.
49
+ Review the `ctx` feature table. Remove rows for features the server doesn't use (e.g., `ctx.elicit` if no tools call it). Add any custom context usage that's become important. The table should reflect what this server actually uses, not the full framework surface.
50
50
 
51
51
  ### 5. Update Server Config Example
52
52
 
@@ -4,7 +4,7 @@ description: >
4
4
  File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.7"
7
+ version: "1.8"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -148,7 +148,7 @@ Format: `bug(<scope>): concise description`
148
148
  | `tool` | Tool builder, handler, format, annotations |
149
149
  | `resource` | Resource builder, handler, list, params |
150
150
  | `prompt` | Prompt builder, generate, args |
151
- | `context` | Context, logger, state, progress, elicit, sample |
151
+ | `context` | Context, logger, state, progress, elicit |
152
152
  | `config` | AppConfig, parseConfig, env parsing |
153
153
  | `errors` | McpError, error factories, typed contracts (`errors[]` / `ctx.fail`), conformance lint, `httpErrorFromResponse`, auto-classification |
154
154
  | `auth` | Auth modes, scope checking, JWT/OAuth |
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: security-pass
3
3
  description: >
4
- Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / sampling / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
4
+ Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.4"
7
+ version: "1.5"
8
8
  audience: external
9
9
  type: audit
10
10
  ---
@@ -44,7 +44,7 @@ find src/mcp-server/prompts/definitions -name "*.prompt.ts" 2>/dev/null | sort
44
44
  find src/services -maxdepth 1 -mindepth 1 -type d | sort
45
45
  ```
46
46
 
47
- Note: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which handlers use `ctx.sample` or `ctx.elicit`, which services hold module-scope state, whether the server reads `roots`.
47
+ Note: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have `destructiveHint`, which handlers use `ctx.elicit`, which services hold module-scope state, whether the server reads `roots`.
48
48
 
49
49
  **If transport is streamable HTTP or SSE**, also capture:
50
50
 
@@ -162,9 +162,6 @@ grep -rnE "\b(exec|spawn|execSync|spawnSync)\b" src/
162
162
  # Merges — prototype pollution
163
163
  grep -rn "Object.assign\b\|structuredClone" src/
164
164
 
165
- # Sampling — LLM-generated content flowing back into server logic
166
- grep -rn "ctx.sample\|sampling/createMessage" src/
167
-
168
165
  # Roots — client-shared filesystem
169
166
  grep -rn "roots/list\|ctx.roots" src/
170
167
 
@@ -182,9 +179,7 @@ grep -rn "\.passthrough()\|\.catchall(" src/mcp-server/
182
179
  - User-JSON merges reject `__proto__`, `constructor`, `prototype` keys?
183
180
  - **Input schemas `.strict()`** — unknown fields rejected, not silently passed to downstream code that destructures with `...rest`?
184
181
  - **Output schemas without `.passthrough()` / `.catchall()`** — no accidental exfiltration of fields your schema didn't declare?
185
- - Sampling responses (`ctx.sample` result) treated as untrusted input schema-validated before reaching any other sink, never concatenated into prompts, shells, or queries?
186
-
187
- **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization; `await ctx.sample(...)` result interpolated into a shell, SQL, or URL.
182
+ **Smell:** `z.string().url()` with no allowlist; `readFile(input.path)` with no canonicalization.
188
183
 
189
184
  #### Axis 6 — Tenant isolation
190
185
 
@@ -333,14 +328,14 @@ End with:
333
328
  ## Checklist
334
329
 
335
330
  - [ ] Scope confirmed (whole server / module / diff)
336
- - [ ] Map built: tools / resources / prompts, services, upstream APIs, auth mode, sampling / elicit / roots usage
331
+ - [ ] Map built: tools / resources / prompts, services, upstream APIs, auth mode, elicit / roots usage
337
332
  - [ ] Deployment surface reviewed (if HTTP): bind address, Origin allowlist, session ID, unauth routes, auth-spec compliance
338
333
  - [ ] `fuzzTool` started in parallel
339
334
  - [ ] Axis 1 — LLM-facing surfaces (tool / resource / prompt output + descriptions) framed and static
340
335
  - [ ] Axis 2 — scope granularity audited
341
336
  - [ ] Axis 3 — destructive ops verified to elicit, elicit response schema-validated
342
337
  - [ ] Axis 4 — upstream auth + token passthrough reviewed
343
- - [ ] Axis 5 — input sinks (URL / path / roots / shell / proto / sampling / schema strictness / ReDoS) checked
338
+ - [ ] Axis 5 — input sinks (URL / path / roots / shell / proto / schema strictness / ReDoS) checked
344
339
  - [ ] Axis 6 — tenant isolation: module-scope state swept
345
340
  - [ ] Axis 7 — leakage back: errors / outputs / `ctx.log` / `console.*` / telemetry / constant-time comparisons
346
341
  - [ ] Axis 8 — resource bounds on loops / retries / pagination / parse size+depth / per-tenant rate
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
49
49
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
50
50
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
51
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
51
+ - **Check `ctx.elicit`** for presence before calling.
52
52
  - **Secrets in env vars only** — never hardcoded.
53
53
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
54
54
 
@@ -156,9 +156,22 @@ export function getServerConfig() {
156
156
 
157
157
  For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
158
158
 
159
- ### Server instructions
159
+ ### Server identity and instructions
160
160
 
161
- `createApp({ instructions })` optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
161
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
162
+
163
+ ```ts
164
+ await createApp({
165
+ name: 'my-mcp-server',
166
+ title: 'My Server', // human-readable display name
167
+ websiteUrl: 'https://github.com/owner/repo', // canonical homepage URL
168
+ description: 'One-line description.', // wins over MCP_SERVER_DESCRIPTION
169
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
170
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
171
+ });
172
+ ```
173
+
174
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
162
175
 
163
176
  ---
164
177
 
@@ -170,8 +183,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
183
  |:---------|:------------|
171
184
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
172
185
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
173
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
174
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
186
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
175
187
  | `ctx.signal` | `AbortSignal` for cancellation. |
176
188
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
177
189
  | `ctx.requestId` | Unique request ID. |
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
49
49
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
50
50
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
51
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
51
+ - **Check `ctx.elicit`** for presence before calling.
52
52
  - **Secrets in env vars only** — never hardcoded.
53
53
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
54
54
 
@@ -156,9 +156,22 @@ export function getServerConfig() {
156
156
 
157
157
  For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
158
158
 
159
- ### Server instructions
159
+ ### Server identity and instructions
160
160
 
161
- `createApp({ instructions })` optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
161
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
162
+
163
+ ```ts
164
+ await createApp({
165
+ name: 'my-mcp-server',
166
+ title: 'My Server', // human-readable display name
167
+ websiteUrl: 'https://github.com/owner/repo', // canonical homepage URL
168
+ description: 'One-line description.', // wins over MCP_SERVER_DESCRIPTION
169
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
170
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
171
+ });
172
+ ```
173
+
174
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
162
175
 
163
176
  ---
164
177
 
@@ -170,8 +183,7 @@ Handlers receive a unified `ctx` object. Key properties:
170
183
  |:---------|:------------|
171
184
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
172
185
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
173
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
174
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
186
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
175
187
  | `ctx.signal` | `AbortSignal` for cancellation. |
176
188
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
177
189
  | `ctx.requestId` | Unique request ID. |
@@ -1,22 +0,0 @@
1
- /**
2
- * @fileoverview Service for implementing MCP roots capability.
3
- * Roots provide filesystem/workspace context awareness for the server.
4
- *
5
- * MCP Roots Specification:
6
- * @see {@link https://modelcontextprotocol.io/specification/2025-06-18/basic/roots | MCP Roots}
7
- * @module src/mcp-server/roots/roots-registration
8
- */
9
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
- import type { logger as defaultLogger } from '../../utils/internal/logger.js';
11
- export declare class RootsRegistry {
12
- private logger;
13
- constructor(logger: typeof defaultLogger);
14
- /**
15
- * Registers roots handlers on the given MCP server.
16
- * Note: In MCP, roots are typically provided BY THE CLIENT to the server.
17
- * This implementation provides a placeholder for demonstration.
18
- * In production, roots would be received from the client via client.listRoots().
19
- */
20
- registerAll(_server: McpServer): void;
21
- }
22
- //# sourceMappingURL=roots-registration.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"roots-registration.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/roots/roots-registration.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG1E,qBAAa,aAAa;IACZ,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,OAAO,aAAa;IAEhD;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;CActC"}