@cyanheads/mcp-ts-core 0.2.2 → 0.2.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 +23 -6
- package/README.md +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +24 -4
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js +14 -2
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/package.json +2 -2
- package/skills/add-tool/SKILL.md +15 -1
- package/skills/design-mcp-server/SKILL.md +2 -1
- package/skills/polish-docs-meta/references/readme.md +1 -0
- package/templates/CLAUDE.md +6 -1
- package/templates/src/mcp-server/tools/definitions/echo.tool.ts +3 -0
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
|
-
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.2.
|
|
3
|
+
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.2.3
|
|
4
4
|
**npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) · **Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
|
|
5
5
|
|
|
6
6
|
> **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never edit a file before reading it.
|
|
@@ -159,16 +159,32 @@ export const myTool = tool('my_tool', {
|
|
|
159
159
|
description: 'Does something useful.',
|
|
160
160
|
annotations: { readOnlyHint: true },
|
|
161
161
|
input: z.object({ query: z.string().describe('Search query') }),
|
|
162
|
-
output: z.object({
|
|
162
|
+
output: z.object({
|
|
163
|
+
items: z.array(z.object({
|
|
164
|
+
id: z.string().describe('Item ID'),
|
|
165
|
+
name: z.string().describe('Item name'),
|
|
166
|
+
status: z.string().describe('Current status'),
|
|
167
|
+
description: z.string().optional().describe('Item description'),
|
|
168
|
+
})).describe('Matching items'),
|
|
169
|
+
totalCount: z.number().describe('Total matches before pagination'),
|
|
170
|
+
}),
|
|
163
171
|
auth: ['tool:my_tool:read'],
|
|
164
172
|
|
|
165
173
|
async handler(input, ctx) {
|
|
166
174
|
const data = await fetchFromApi(input.query);
|
|
167
|
-
ctx.log.info('Query resolved', { query: input.query, resultCount: data.length });
|
|
168
|
-
return
|
|
175
|
+
ctx.log.info('Query resolved', { query: input.query, resultCount: data.items.length });
|
|
176
|
+
return data;
|
|
169
177
|
},
|
|
170
178
|
|
|
171
|
-
format: (result) =>
|
|
179
|
+
format: (result) => {
|
|
180
|
+
const lines = [`**${result.totalCount} results**\n`];
|
|
181
|
+
for (const item of result.items) {
|
|
182
|
+
lines.push(`### ${item.name}`);
|
|
183
|
+
lines.push(`**ID:** ${item.id} | **Status:** ${item.status}`);
|
|
184
|
+
if (item.description) lines.push(item.description);
|
|
185
|
+
}
|
|
186
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
187
|
+
},
|
|
172
188
|
});
|
|
173
189
|
```
|
|
174
190
|
|
|
@@ -176,7 +192,7 @@ export const myTool = tool('my_tool', {
|
|
|
176
192
|
|
|
177
193
|
**Schema constraint:** Input/output schemas must use JSON-Schema-serializable Zod types only. The MCP SDK converts schemas to JSON Schema for `tools/list` — non-serializable types (`z.custom()`, `z.date()`, `z.transform()`, etc.) cause a hard runtime failure. Use structural equivalents instead (e.g., `z.string()` with `.describe('ISO 8601 date')` instead of `z.date()`). The linter validates this at startup.
|
|
178
194
|
|
|
179
|
-
**`format`**: Maps output to `
|
|
195
|
+
**`format`**: Maps output to MCP `content[]` — the only field most LLM clients (Claude Code, VS Code Copilot, Cursor, Windsurf) forward to the model. `structuredContent` (from `output`) is for programmatic use and is not reliably shown to the LLM. **Make `format()` content-complete** — render all data the model needs to reason about the result, not just a count or title. Omit for JSON stringify fallback. Additional formatters: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter` from `/utils`.
|
|
180
196
|
|
|
181
197
|
**Task tools:** Add `task: true` for long-running async operations. Framework manages lifecycle: creates task → returns ID immediately → runs handler in background with `ctx.progress` → stores result/error → `ctx.signal` for cancellation. See `add-tool` skill for full example.
|
|
182
198
|
|
|
@@ -448,6 +464,7 @@ Detailed method signatures, options, and examples live in skill files. Read the
|
|
|
448
464
|
- **JSDoc:** `@fileoverview` + `@module` required on every file
|
|
449
465
|
- **No fabricated signal:** Don't invent synthetic scores or arbitrary "confidence percentages." Surface real signal.
|
|
450
466
|
- **Builders:** `tool()`/`resource()`/`prompt()` with correct fields (`handler`, `input`, `output`, `format`, `auth`, `args`)
|
|
467
|
+
- **`format()` completeness:** `content[]` is the only field most LLM clients forward to the model — `format()` must render all data the LLM needs, not just a count or title
|
|
451
468
|
- **Auth:** via `auth: ['scope']` on definitions (not HOF wrapper)
|
|
452
469
|
- **Presence checks:** `ctx.elicit`/`ctx.sample` checked before use
|
|
453
470
|
- **Task tools:** use `task: true` flag
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
7
7
|
|
|
8
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE)
|
|
9
9
|
|
|
10
10
|
[](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
@@ -55,8 +55,16 @@ export interface ToolDefinition<TInput extends ZodObject<ZodRawShape> = ZodObjec
|
|
|
55
55
|
/** LLM-facing description. */
|
|
56
56
|
description: string;
|
|
57
57
|
/**
|
|
58
|
-
* Optional formatter mapping output to
|
|
59
|
-
*
|
|
58
|
+
* Optional formatter mapping output to MCP `content[]` — the field LLM clients
|
|
59
|
+
* inject into the model's context. `structuredContent` (from `output`) is for
|
|
60
|
+
* programmatic/machine use and is NOT reliably forwarded to the model by most
|
|
61
|
+
* clients (Claude Code, VS Code Copilot, Cursor, Windsurf all read `content[]`).
|
|
62
|
+
*
|
|
63
|
+
* **Make `format()` content-complete.** If the LLM needs data to reason about
|
|
64
|
+
* the result, it must appear here — not just in the output schema. A thin
|
|
65
|
+
* one-liner (e.g., a count or title) leaves the model blind to the actual data.
|
|
66
|
+
*
|
|
67
|
+
* If omitted, the handler factory JSON-stringifies the output as a fallback.
|
|
60
68
|
*/
|
|
61
69
|
format?: (result: z.infer<TOutput>) => ContentBlock[];
|
|
62
70
|
/**
|
|
@@ -85,13 +93,25 @@ export type AnyToolDefinition = ToolDefinition<ZodObject<ZodRawShape>, ZodObject
|
|
|
85
93
|
* const myTool = tool('my_tool', {
|
|
86
94
|
* description: 'Does something useful.',
|
|
87
95
|
* input: z.object({ query: z.string().describe('Search query') }),
|
|
88
|
-
* output: z.object({
|
|
96
|
+
* output: z.object({
|
|
97
|
+
* items: z.array(z.object({
|
|
98
|
+
* id: z.string().describe('Item ID'),
|
|
99
|
+
* name: z.string().describe('Item name'),
|
|
100
|
+
* status: z.string().describe('Current status'),
|
|
101
|
+
* })).describe('Matching items'),
|
|
102
|
+
* }),
|
|
89
103
|
* auth: ['tool:my_tool:read'],
|
|
90
104
|
* annotations: { readOnlyHint: true },
|
|
91
105
|
* async handler(input, ctx) {
|
|
92
106
|
* ctx.log.info('Processing', { query: input.query });
|
|
93
|
-
* return {
|
|
107
|
+
* return { items: await search(input.query) };
|
|
94
108
|
* },
|
|
109
|
+
* // format() populates content[] — the only field most LLM clients read.
|
|
110
|
+
* // Render all data the model needs; structuredContent is not forwarded.
|
|
111
|
+
* format: (result) => [{
|
|
112
|
+
* type: 'text',
|
|
113
|
+
* text: result.items.map(i => `**${i.id}**: ${i.name} (${i.status})`).join('\n'),
|
|
114
|
+
* }],
|
|
95
115
|
* });
|
|
96
116
|
* ```
|
|
97
117
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolDefinition.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/toolDefinition.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAC7B,MAAM,SAAS,SAAS,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,EAC9D,OAAO,SAAS,SAAS,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC;IAE/D,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,qCAAqC;IACrC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB
|
|
1
|
+
{"version":3,"file":"toolDefinition.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/toolDefinition.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAC7B,MAAM,SAAS,SAAS,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,EAC9D,OAAO,SAAS,SAAS,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC;IAE/D,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,qCAAqC;IACrC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,YAAY,EAAE,CAAC;IACtD;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChG,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;AAM/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,IAAI,CAAC,MAAM,SAAS,SAAS,CAAC,WAAW,CAAC,EAAE,OAAO,SAAS,SAAS,CAAC,WAAW,CAAC,EAChG,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,GACrD,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjC"}
|
|
@@ -15,13 +15,25 @@
|
|
|
15
15
|
* const myTool = tool('my_tool', {
|
|
16
16
|
* description: 'Does something useful.',
|
|
17
17
|
* input: z.object({ query: z.string().describe('Search query') }),
|
|
18
|
-
* output: z.object({
|
|
18
|
+
* output: z.object({
|
|
19
|
+
* items: z.array(z.object({
|
|
20
|
+
* id: z.string().describe('Item ID'),
|
|
21
|
+
* name: z.string().describe('Item name'),
|
|
22
|
+
* status: z.string().describe('Current status'),
|
|
23
|
+
* })).describe('Matching items'),
|
|
24
|
+
* }),
|
|
19
25
|
* auth: ['tool:my_tool:read'],
|
|
20
26
|
* annotations: { readOnlyHint: true },
|
|
21
27
|
* async handler(input, ctx) {
|
|
22
28
|
* ctx.log.info('Processing', { query: input.query });
|
|
23
|
-
* return {
|
|
29
|
+
* return { items: await search(input.query) };
|
|
24
30
|
* },
|
|
31
|
+
* // format() populates content[] — the only field most LLM clients read.
|
|
32
|
+
* // Render all data the model needs; structuredContent is not forwarded.
|
|
33
|
+
* format: (result) => [{
|
|
34
|
+
* type: 'text',
|
|
35
|
+
* text: result.items.map(i => `**${i.id}**: ${i.name} (${i.status})`).join('\n'),
|
|
36
|
+
* }],
|
|
25
37
|
* });
|
|
26
38
|
* ```
|
|
27
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolDefinition.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/toolDefinition.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"toolDefinition.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/toolDefinition.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2FH,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,IAAI,CAClB,IAAY,EACZ,OAAsD;IAEtD,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;AAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for building MCP servers. Build tools, not infrastructure. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Node.js and Cloudflare Workers.",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
"dependencies": {
|
|
251
251
|
"@hono/mcp": "^0.2.4",
|
|
252
252
|
"@hono/node-server": "^1.19.11",
|
|
253
|
-
"@modelcontextprotocol/ext-apps": "^1.3.
|
|
253
|
+
"@modelcontextprotocol/ext-apps": "^1.3.2",
|
|
254
254
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
255
255
|
"@opentelemetry/api": "^1.9.1",
|
|
256
256
|
"dotenv": "^17.3.1",
|
package/skills/add-tool/SKILL.md
CHANGED
|
@@ -55,7 +55,20 @@ export const {{TOOL_EXPORT}} = tool('{{tool_name}}', {
|
|
|
55
55
|
return { /* output */ };
|
|
56
56
|
},
|
|
57
57
|
|
|
58
|
-
format
|
|
58
|
+
// format() populates MCP content[] — the only field most LLM clients forward
|
|
59
|
+
// to the model. structuredContent (from output) is for programmatic use only.
|
|
60
|
+
// Render ALL data the LLM needs to reason about the result.
|
|
61
|
+
format: (result) => {
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
// Render each item with all relevant fields — not just a count or title.
|
|
64
|
+
// A thin one-liner (e.g., "Found 5 items") leaves the model blind to the data.
|
|
65
|
+
for (const item of result.items) {
|
|
66
|
+
lines.push(`## ${item.name}`);
|
|
67
|
+
lines.push(`**ID:** ${item.id} | **Status:** ${item.status}`);
|
|
68
|
+
if (item.description) lines.push(item.description);
|
|
69
|
+
}
|
|
70
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
71
|
+
},
|
|
59
72
|
});
|
|
60
73
|
```
|
|
61
74
|
|
|
@@ -192,6 +205,7 @@ Large payloads burn the agent's context window. Default to curated summaries; of
|
|
|
192
205
|
- [ ] Schemas use only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`)
|
|
193
206
|
- [ ] JSDoc `@fileoverview` and `@module` header present
|
|
194
207
|
- [ ] `handler(input, ctx)` is pure — throws on failure, no try/catch
|
|
208
|
+
- [ ] `format()` renders all data the LLM needs (not just a count or title) — `content[]` is the only field most clients forward to the model
|
|
195
209
|
- [ ] `auth` scopes declared if the tool needs authorization
|
|
196
210
|
- [ ] `task: true` added if the tool is long-running
|
|
197
211
|
- [ ] Registered in `definitions/index.ts` barrel and `allToolDefinitions`
|
|
@@ -193,7 +193,7 @@ output: z.object({
|
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
- **Truncate large output with counts.** When a list exceeds a reasonable display size, show the top N and append "...and X more". Don't silently drop results.
|
|
196
|
-
-
|
|
196
|
+
- **`format()` is the model-facing output — make it content-complete.** MCP `content[]` (populated by `format()`) is the only field most LLM clients forward to the model. `structuredContent` (from `output`) is for programmatic/machine use and is not reliably shown to the LLM. A thin `format()` that returns only a count or title leaves the model blind to the actual data. Render all fields the LLM needs to reason about or act on the result. Use structured markdown (headers, bold labels, lists) for readability.
|
|
197
197
|
|
|
198
198
|
#### Convenience shortcuts for complex inputs
|
|
199
199
|
|
|
@@ -364,6 +364,7 @@ Execute the plan using the scaffolding skills:
|
|
|
364
364
|
- [ ] Parameter `.describe()` text explains what the value is, what it affects, and tradeoffs
|
|
365
365
|
- [ ] Input schemas use constrained types (enums, literals, regex) over free strings
|
|
366
366
|
- [ ] Output schemas designed for LLM's next action — chaining IDs, post-write state, filtering communicated
|
|
367
|
+
- [ ] `format()` renders all data the LLM needs — `content[]` is the only field most clients forward to the model (not just a count or title)
|
|
367
368
|
- [ ] Error messages guide recovery — name what went wrong and what to do next
|
|
368
369
|
- [ ] Annotations set correctly (`readOnlyHint`, `destructiveHint`, etc.)
|
|
369
370
|
- [ ] Resource URIs use `{param}` templates, pagination planned for large lists
|
|
@@ -242,6 +242,7 @@ Table of environment variables. Include framework vars only if the server uses n
|
|
|
242
242
|
| `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
|
|
243
243
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
|
|
244
244
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
|
|
245
|
+
| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
|
|
245
246
|
| `STORAGE_PROVIDER_TYPE` | Storage backend. | `in-memory` |
|
|
246
247
|
| `OTEL_ENABLED` | Enable OpenTelemetry. | `false` |
|
|
247
248
|
```
|
package/templates/CLAUDE.md
CHANGED
|
@@ -75,7 +75,12 @@ export const searchItems = tool('search_items', {
|
|
|
75
75
|
return { items };
|
|
76
76
|
},
|
|
77
77
|
|
|
78
|
-
format
|
|
78
|
+
// format() populates content[] — the only field most LLM clients forward to
|
|
79
|
+
// the model. Render all data the LLM needs, not just a count or title.
|
|
80
|
+
format: (result) => [{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: result.items.map(i => `**${i.id}**: ${i.name}`).join('\n'),
|
|
83
|
+
}],
|
|
79
84
|
});
|
|
80
85
|
```
|
|
81
86
|
|
|
@@ -21,5 +21,8 @@ export const echoTool = tool('template_echo_message', {
|
|
|
21
21
|
return { message: input.message };
|
|
22
22
|
},
|
|
23
23
|
|
|
24
|
+
// format() populates MCP content[] — the only field most LLM clients forward to
|
|
25
|
+
// the model. Render ALL data the LLM needs here, not just a summary or count.
|
|
26
|
+
// This echo tool is trivial; real tools should render every relevant field.
|
|
24
27
|
format: (result) => [{ type: 'text', text: result.message }],
|
|
25
28
|
});
|