@cyanheads/mcp-ts-core 0.8.2 → 0.8.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.
- package/CLAUDE.md +8 -2
- package/README.md +1 -1
- package/changelog/0.8.x/0.8.3.md +43 -0
- package/dist/logs/combined.log +12 -4
- package/dist/logs/error.log +12 -4
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +2 -3
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +3 -3
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +8 -12
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +2 -3
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +28 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +64 -23
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/types-global/errors.d.ts +3 -22
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +0 -19
- package/dist/types-global/errors.js.map +1 -1
- package/package.json +1 -1
- package/skills/add-resource/SKILL.md +1 -1
- package/skills/add-tool/SKILL.md +87 -20
- package/skills/api-errors/SKILL.md +21 -7
- package/skills/api-linter/SKILL.md +1 -1
- package/skills/design-mcp-server/SKILL.md +1 -1
- package/skills/field-test/SKILL.md +15 -6
- package/templates/AGENTS.md +3 -2
- package/templates/CLAUDE.md +3 -2
- package/templates/src/mcp-server/tools/definitions/echo.tool.ts +18 -1
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Exercise tools, resources, and prompts against a live HTTP server via MCP JSON-RPC over curl. Starts the server, surfaces the catalog, runs real and adversarial inputs, and produces a tight report with concrete findings and numbered follow-up options. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.2"
|
|
8
8
|
audience: external
|
|
9
9
|
type: debug
|
|
10
10
|
---
|
|
@@ -15,6 +15,15 @@ Unit tests (`add-test` skill) verify handler logic with mocked context. Field te
|
|
|
15
15
|
|
|
16
16
|
**Actively call the tools. Don't read code and guess.**
|
|
17
17
|
|
|
18
|
+
### Transport coverage
|
|
19
|
+
|
|
20
|
+
This skill drives an HTTP server because curl + JSON-RPC is the most reliable harness for shell-based agents. Most servers ship both transports (`bun run dev:http` and `dev:stdio`), so HTTP coverage is sufficient: the same handler runs on both, only the framing differs. If the server is **stdio-only** (no HTTP transport in `package.json` / no `MCP_TRANSPORT_TYPE=http` path), drive it through one of:
|
|
21
|
+
|
|
22
|
+
- **MCP Inspector** (`npx @modelcontextprotocol/inspector bun run dev:stdio`) — interactive UI for catalog browsing and tool calls; best for hands-on exploration
|
|
23
|
+
- **mcp-cli** (`uvx mcp-cli --stdio bun run dev:stdio`) — scriptable JSON-RPC client; best for batch/agentic testing
|
|
24
|
+
|
|
25
|
+
Adapt the test plan below the same way — universal battery on every definition, situational categories only when triggered, same error-contract verification — but call the tools through the inspector / mcp-cli rather than `mcp_call`. Pino startup + handler logs land on stderr in stdio mode (stdout is reserved for JSON-RPC), so tail with `2>/tmp/mcp-server.log` if you start the server yourself.
|
|
26
|
+
|
|
18
27
|
---
|
|
19
28
|
|
|
20
29
|
## Steps
|
|
@@ -190,8 +199,8 @@ Runs `initialize`, captures the session id, sends `notifications/initialized`.
|
|
|
190
199
|
|
|
191
200
|
```bash
|
|
192
201
|
. /tmp/mcp-field-test.sh
|
|
193
|
-
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema
|
|
194
|
-
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType
|
|
202
|
+
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
|
|
203
|
+
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType}'
|
|
195
204
|
mcp_call prompts/list | jq '.result.prompts[] | {name, description, arguments}'
|
|
196
205
|
```
|
|
197
206
|
|
|
@@ -229,7 +238,7 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
|
|
|
229
238
|
| Hits external API / live upstream | One call that exercises upstream; note rate-limit / timeout / transient-failure behavior |
|
|
230
239
|
| Chained with other tools (search → detail → act) | Run one representative chain end-to-end; does each step return the IDs/cursors the next needs? |
|
|
231
240
|
| `cursor` / `offset` / `limit` params | Pagination: second page, end-of-list |
|
|
232
|
-
| Tool declared an `errors: [...]` contract | Error contract (tool): trigger ≥1 declared failure mode. Verify `result.
|
|
241
|
+
| Tool declared an `errors: [...]` contract | Error contract (tool): trigger ≥1 declared failure mode. Verify `result.structuredContent.error.code` matches the contract entry, `result.structuredContent.error.data.reason` is the declared reason (only present when the handler threw an `McpError` — `ctx.fail` always does, plain `throw new Error(...)` does not), and `content[0].text` is actionable. Reasons declared but unreachable from any input are dead contract entries. |
|
|
233
242
|
| Resource declared an `errors: [...]` contract | Error contract (resource): trigger ≥1 declared failure mode by reading a URI that exercises it. Resources re-throw errors at the JSON-RPC level — verify `error.code` matches the contract entry and `error.data.reason` is the declared reason. (Resources don't use the `result.isError` envelope — they fail the request itself.) |
|
|
234
243
|
|
|
235
244
|
**Resources.** Happy path, not-found URI, `list` if defined, pagination if used.
|
|
@@ -253,7 +262,7 @@ When a call surprises you — slow, hangs, returns terse output, surfaces an unh
|
|
|
253
262
|
**Interpreting responses**
|
|
254
263
|
|
|
255
264
|
- Tool domain errors return `{result: {content: [...], isError: true}}` — they live in `result`, not `error`. Check `isError`, not the JSON-RPC error field.
|
|
256
|
-
- **Tool error code/reason** rides on `result.
|
|
265
|
+
- **Tool error code/reason** rides on `result.structuredContent.error.{code, message, data?.reason}` — inspect that, not just the text. `data` is only spread when the handler threw an `McpError` (or `ZodError`); plain `throw new Error(...)` won't populate `data.reason`. Use `ctx.fail`-thrown errors when the contract reason matters. The text in `result.content[0].text` mirrors the message and includes `Recovery: <hint>` when `data.recovery.hint` is present.
|
|
257
266
|
- **Resource errors** are JSON-RPC-level — they appear in the top-level `error.{code, data.reason}` field, not inside `result`. Resource handlers re-throw rather than producing an `isError` envelope.
|
|
258
267
|
- JSON-RPC `error` only appears for protocol issues (bad session, malformed envelope, unknown method).
|
|
259
268
|
- `mcp_call` already strips SSE framing. Pipe to `jq` for readability.
|
|
@@ -317,7 +326,7 @@ End with:
|
|
|
317
326
|
- [ ] Catalog surfaced and presented; descriptions audited for leaks (implementation details, meta-coaching, consumer-aware phrasing)
|
|
318
327
|
- [ ] Universal battery run on every definition
|
|
319
328
|
- [ ] Situational categories applied only when triggered
|
|
320
|
-
- [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result.
|
|
329
|
+
- [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result.structuredContent.error.code` and `data.reason` verified against the contract entry
|
|
321
330
|
- [ ] **If a resource declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; top-level JSON-RPC `error.code` and `error.data.reason` verified against the contract entry
|
|
322
331
|
- [ ] External-state / auth-gated tools handled explicitly (run, skip, or confirm)
|
|
323
332
|
- [ ] Server stopped; state file removed
|
package/templates/AGENTS.md
CHANGED
|
@@ -90,6 +90,7 @@ export const searchItems = tool('search_items', {
|
|
|
90
90
|
|
|
91
91
|
```ts
|
|
92
92
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
93
|
+
import { notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
93
94
|
|
|
94
95
|
export const itemData = resource('inventory://{itemId}', {
|
|
95
96
|
description: 'Fetch an inventory item by ID.',
|
|
@@ -97,7 +98,7 @@ export const itemData = resource('inventory://{itemId}', {
|
|
|
97
98
|
auth: ['inventory:read'],
|
|
98
99
|
async handler(params, ctx) {
|
|
99
100
|
const item = await ctx.state.get(`item:${params.itemId}`);
|
|
100
|
-
if (!item) throw
|
|
101
|
+
if (!item) throw notFound(`Item ${params.itemId} not found`, { itemId: params.itemId });
|
|
101
102
|
return item;
|
|
102
103
|
},
|
|
103
104
|
});
|
|
@@ -167,7 +168,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
167
168
|
|
|
168
169
|
Handlers throw — the framework catches, classifies, and formats.
|
|
169
170
|
|
|
170
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
171
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
171
172
|
|
|
172
173
|
```ts
|
|
173
174
|
errors: [
|
package/templates/CLAUDE.md
CHANGED
|
@@ -90,6 +90,7 @@ export const searchItems = tool('search_items', {
|
|
|
90
90
|
|
|
91
91
|
```ts
|
|
92
92
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
93
|
+
import { notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
93
94
|
|
|
94
95
|
export const itemData = resource('inventory://{itemId}', {
|
|
95
96
|
description: 'Fetch an inventory item by ID.',
|
|
@@ -97,7 +98,7 @@ export const itemData = resource('inventory://{itemId}', {
|
|
|
97
98
|
auth: ['inventory:read'],
|
|
98
99
|
async handler(params, ctx) {
|
|
99
100
|
const item = await ctx.state.get(`item:${params.itemId}`);
|
|
100
|
-
if (!item) throw
|
|
101
|
+
if (!item) throw notFound(`Item ${params.itemId} not found`, { itemId: params.itemId });
|
|
101
102
|
return item;
|
|
102
103
|
},
|
|
103
104
|
});
|
|
@@ -167,7 +168,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
167
168
|
|
|
168
169
|
Handlers throw — the framework catches, classifies, and formats.
|
|
169
170
|
|
|
170
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
171
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
171
172
|
|
|
172
173
|
```ts
|
|
173
174
|
errors: [
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
8
|
|
|
8
9
|
// Tool names are snake_case, prefixed with your server name to avoid collisions across servers.
|
|
9
10
|
// e.g. for a "tasks" server: tasks_fetch_list, tasks_create_item.
|
|
@@ -17,7 +18,23 @@ export const echoTool = tool('template_echo_message', {
|
|
|
17
18
|
message: z.string().describe('The echoed message.'),
|
|
18
19
|
}),
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
// Declare each domain failure mode the agent should plan around. The framework
|
|
22
|
+
// types `ctx.fail(reason, …)` against the declared union. Baseline codes
|
|
23
|
+
// (InternalError, ServiceUnavailable, Timeout, ValidationError,
|
|
24
|
+
// SerializationError) bubble freely — only declare domain-specific reasons.
|
|
25
|
+
// Delete this block if no domain-specific failures apply to your tool.
|
|
26
|
+
errors: [
|
|
27
|
+
{
|
|
28
|
+
reason: 'empty_message',
|
|
29
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
30
|
+
when: 'Message contained only whitespace.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
handler(input, ctx) {
|
|
35
|
+
if (input.message.trim().length === 0) {
|
|
36
|
+
throw ctx.fail('empty_message', 'Message must contain at least one non-whitespace character.');
|
|
37
|
+
}
|
|
21
38
|
return { message: input.message };
|
|
22
39
|
},
|
|
23
40
|
|