@cyanheads/mcp-ts-core 0.2.2 → 0.2.4
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 +25 -6
- package/README.md +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/linter/index.d.ts +1 -1
- package/dist/linter/index.d.ts.map +1 -1
- package/dist/linter/index.js.map +1 -1
- package/dist/linter/rules/index.d.ts +1 -0
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +1 -0
- package/dist/linter/rules/index.js.map +1 -1
- package/dist/linter/rules/server-json-rules.d.ts +18 -0
- package/dist/linter/rules/server-json-rules.d.ts.map +1 -0
- package/dist/linter/rules/server-json-rules.js +290 -0
- package/dist/linter/rules/server-json-rules.js.map +1 -0
- package/dist/linter/types.d.ts +11 -3
- package/dist/linter/types.d.ts.map +1 -1
- package/dist/linter/validate.d.ts.map +1 -1
- package/dist/linter/validate.js +6 -0
- package/dist/linter/validate.js.map +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 +5 -2
- package/scripts/lint-mcp.ts +36 -10
- package/skills/add-service/SKILL.md +35 -0
- package/skills/add-tool/SKILL.md +15 -1
- package/skills/design-mcp-server/SKILL.md +22 -2
- package/skills/polish-docs-meta/references/readme.md +1 -0
- package/templates/AGENTS.md +7 -1
- package/templates/CLAUDE.md +7 -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.4
|
|
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
|
|
|
@@ -251,6 +267,8 @@ export function getMyService(): MyService {
|
|
|
251
267
|
|
|
252
268
|
Usage: `getMyService().doWork(input.query, ctx)`. Convention: `ctx.elicit`/`ctx.sample` only from tool handlers, not services.
|
|
253
269
|
|
|
270
|
+
**API efficiency:** Prefer batch endpoints over N+1 individual requests. Use field selection to minimize payload. Cross-reference batch responses against requested IDs to detect missing items. See `add-service` skill for patterns.
|
|
271
|
+
|
|
254
272
|
---
|
|
255
273
|
|
|
256
274
|
## Context
|
|
@@ -448,6 +466,7 @@ Detailed method signatures, options, and examples live in skill files. Read the
|
|
|
448
466
|
- **JSDoc:** `@fileoverview` + `@module` required on every file
|
|
449
467
|
- **No fabricated signal:** Don't invent synthetic scores or arbitrary "confidence percentages." Surface real signal.
|
|
450
468
|
- **Builders:** `tool()`/`resource()`/`prompt()` with correct fields (`handler`, `input`, `output`, `format`, `auth`, `args`)
|
|
469
|
+
- **`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
470
|
- **Auth:** via `auth: ['scope']` on definitions (not HOF wrapper)
|
|
452
471
|
- **Presence checks:** `ctx.elicit`/`ctx.sample` checked before use
|
|
453
472
|
- **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
|
|
package/dist/core/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { resource } from '../mcp-server/resources/utils/resourceDefinition.js';
|
|
|
17
17
|
export type { AnyToolDef } from '../mcp-server/tools/tool-registration.js';
|
|
18
18
|
export type { AnyToolDefinition, ToolAnnotations, ToolDefinition, } from '../mcp-server/tools/utils/toolDefinition.js';
|
|
19
19
|
export { tool } from '../mcp-server/tools/utils/toolDefinition.js';
|
|
20
|
-
export type { LintDiagnostic, LintInput, LintReport, LintSeverity } from '../linter/types.js';
|
|
20
|
+
export type { LintDefinitionType, LintDiagnostic, LintInput, LintReport, LintSeverity, } from '../linter/types.js';
|
|
21
21
|
export { validateDefinitions } from '../linter/validate.js';
|
|
22
22
|
export type { CallToolResult, ContentBlock, CreateMessageResult, ElicitResult, ModelPreferences, PromptMessage, SamplingMessage, } from '@modelcontextprotocol/sdk/types.js';
|
|
23
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,YAAY,EACV,WAAW,EACX,OAAO,EACP,aAAa,EACb,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,mBAAmB,CAAC;AAM3B,YAAY,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,gDAAgD,CAAC;AACxE,YAAY,EACV,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,oDAAoD,CAAC;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oDAAoD,CAAC;AAC9E,sEAAsE;AACtE,YAAY,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,GACf,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;AAMlE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,YAAY,EACV,WAAW,EACX,OAAO,EACP,aAAa,EACb,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,mBAAmB,CAAC;AAM3B,YAAY,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,gDAAgD,CAAC;AACxE,YAAY,EACV,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,oDAAoD,CAAC;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oDAAoD,CAAC;AAC9E,sEAAsE;AACtE,YAAY,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAC1E,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,GACf,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;AAMlE,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAM3D,YAAY,EACV,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAE9E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,OAAO,EAAE,MAAM,EAAE,MAAM,gDAAgD,CAAC;AAMxE,OAAO,EAAE,QAAQ,EAAE,MAAM,oDAAoD,CAAC;AAQ9E,OAAO,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAE9E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,OAAO,EAAE,MAAM,EAAE,MAAM,gDAAgD,CAAC;AAMxE,OAAO,EAAE,QAAQ,EAAE,MAAM,oDAAoD,CAAC;AAQ9E,OAAO,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;AAalE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/linter/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Exports `validateDefinitions()` and all types needed to consume lint reports.
|
|
4
4
|
* @module src/linter/index
|
|
5
5
|
*/
|
|
6
|
-
export type { LintDiagnostic, LintInput, LintReport, LintSeverity } from './types.js';
|
|
6
|
+
export type { LintDefinitionType, LintDiagnostic, LintInput, LintReport, LintSeverity, } from './types.js';
|
|
7
7
|
export { validateDefinitions } from './validate.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/linter/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/linter/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/linter/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/linter/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/linter/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -6,5 +6,6 @@ export { checkDuplicateNames, checkNameRequired, checkToolNameFormat } from './n
|
|
|
6
6
|
export { lintPromptDefinition } from './prompt-rules.js';
|
|
7
7
|
export { lintResourceDefinition } from './resource-rules.js';
|
|
8
8
|
export { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
9
|
+
export { lintServerJson } from './server-json-rules.js';
|
|
9
10
|
export { lintAuthScopes, lintToolDefinition } from './tool-rules.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -6,5 +6,6 @@ export { checkDuplicateNames, checkNameRequired, checkToolNameFormat } from './n
|
|
|
6
6
|
export { lintPromptDefinition } from './prompt-rules.js';
|
|
7
7
|
export { lintResourceDefinition } from './resource-rules.js';
|
|
8
8
|
export { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
9
|
+
export { lintServerJson } from './server-json-rules.js';
|
|
9
10
|
export { lintAuthScopes, lintToolDefinition } from './tool-rules.js';
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/linter/rules/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/linter/rules/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lint rules for server.json manifest validation.
|
|
3
|
+
* Validates server metadata against the MCP server manifest spec (2025-12-11).
|
|
4
|
+
* @module src/linter/rules/server-json-rules
|
|
5
|
+
*/
|
|
6
|
+
import type { LintDiagnostic } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Validates a parsed server.json manifest against the MCP server manifest spec.
|
|
9
|
+
* Accepts `unknown` to catch structural issues before type narrowing.
|
|
10
|
+
*
|
|
11
|
+
* @param serverJson - Parsed server.json content (or undefined to skip).
|
|
12
|
+
* @param crossCheck - Optional cross-validation data (e.g., package.json version).
|
|
13
|
+
* @returns Array of lint diagnostics (errors and warnings).
|
|
14
|
+
*/
|
|
15
|
+
export declare function lintServerJson(serverJson: unknown, crossCheck?: {
|
|
16
|
+
packageJsonVersion?: string;
|
|
17
|
+
}): LintDiagnostic[];
|
|
18
|
+
//# sourceMappingURL=server-json-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-json-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/server-json-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAiClD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,OAAO,EACnB,UAAU,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,cAAc,EAAE,CA2JlB"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lint rules for server.json manifest validation.
|
|
3
|
+
* Validates server metadata against the MCP server manifest spec (2025-12-11).
|
|
4
|
+
* @module src/linter/rules/server-json-rules
|
|
5
|
+
*/
|
|
6
|
+
const DEF_TYPE = 'server-json';
|
|
7
|
+
const DEF_NAME = 'server.json';
|
|
8
|
+
/** Reverse-DNS name pattern from the spec. */
|
|
9
|
+
const NAME_PATTERN = /^[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/;
|
|
10
|
+
/** Rejects version range specifiers. */
|
|
11
|
+
const VERSION_RANGE_PATTERN = /[~^>=<*x]/;
|
|
12
|
+
/** Loose semver check (major.minor.patch with optional pre-release/build). */
|
|
13
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
14
|
+
/** Valid transport types per spec. */
|
|
15
|
+
const TRANSPORT_TYPES = new Set(['stdio', 'streamable-http', 'sse']);
|
|
16
|
+
/** Transports that require a url field. */
|
|
17
|
+
const URL_TRANSPORTS = new Set(['streamable-http', 'sse']);
|
|
18
|
+
/** URL pattern from the spec. */
|
|
19
|
+
const URL_PATTERN = /^https?:\/\/[^\s]+$/;
|
|
20
|
+
/** Valid argument types. */
|
|
21
|
+
const ARGUMENT_TYPES = new Set(['positional', 'named']);
|
|
22
|
+
/** Valid input format values. */
|
|
23
|
+
const INPUT_FORMATS = new Set(['string', 'number', 'boolean', 'filepath']);
|
|
24
|
+
function diag(rule, severity, message) {
|
|
25
|
+
return { definitionName: DEF_NAME, definitionType: DEF_TYPE, message, rule, severity };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validates a parsed server.json manifest against the MCP server manifest spec.
|
|
29
|
+
* Accepts `unknown` to catch structural issues before type narrowing.
|
|
30
|
+
*
|
|
31
|
+
* @param serverJson - Parsed server.json content (or undefined to skip).
|
|
32
|
+
* @param crossCheck - Optional cross-validation data (e.g., package.json version).
|
|
33
|
+
* @returns Array of lint diagnostics (errors and warnings).
|
|
34
|
+
*/
|
|
35
|
+
export function lintServerJson(serverJson, crossCheck) {
|
|
36
|
+
if (serverJson == null)
|
|
37
|
+
return [];
|
|
38
|
+
const diagnostics = [];
|
|
39
|
+
if (typeof serverJson !== 'object' || Array.isArray(serverJson)) {
|
|
40
|
+
diagnostics.push(diag('server-json-type', 'error', 'server.json must be a JSON object.'));
|
|
41
|
+
return diagnostics;
|
|
42
|
+
}
|
|
43
|
+
const s = serverJson;
|
|
44
|
+
// ── Required fields ──────────────────────────────────────────────────
|
|
45
|
+
// name: required, 3-200 chars, reverse-DNS pattern
|
|
46
|
+
if (typeof s.name !== 'string' || s.name.length === 0) {
|
|
47
|
+
diagnostics.push(diag('server-json-name-required', 'error', 'name is required.'));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (s.name.length < 3 || s.name.length > 200) {
|
|
51
|
+
diagnostics.push(diag('server-json-name-length', 'error', `name must be 3–200 characters, got ${s.name.length}.`));
|
|
52
|
+
}
|
|
53
|
+
if (!NAME_PATTERN.test(s.name)) {
|
|
54
|
+
diagnostics.push(diag('server-json-name-format', 'error', `name '${s.name}' must match reverse-DNS format: owner/project (pattern: ${NAME_PATTERN.source}).`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// description: required, 1-100 chars
|
|
58
|
+
if (typeof s.description !== 'string' || s.description.length === 0) {
|
|
59
|
+
diagnostics.push(diag('server-json-description-required', 'error', 'description is required.'));
|
|
60
|
+
}
|
|
61
|
+
else if (s.description.length > 100) {
|
|
62
|
+
diagnostics.push(diag('server-json-description-length', 'warning', `description exceeds 100 characters (${s.description.length}). Some registries may truncate.`));
|
|
63
|
+
}
|
|
64
|
+
// version: required, no ranges
|
|
65
|
+
if (typeof s.version !== 'string' || s.version.length === 0) {
|
|
66
|
+
diagnostics.push(diag('server-json-version-required', 'error', 'version is required.'));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (s.version.length > 255) {
|
|
70
|
+
diagnostics.push(diag('server-json-version-length', 'error', `version exceeds 255 characters (${s.version.length}).`));
|
|
71
|
+
}
|
|
72
|
+
if (VERSION_RANGE_PATTERN.test(s.version)) {
|
|
73
|
+
diagnostics.push(diag('server-json-version-no-range', 'error', `version '${s.version}' looks like a range. Must be a specific version.`));
|
|
74
|
+
}
|
|
75
|
+
if (!SEMVER_PATTERN.test(s.version)) {
|
|
76
|
+
diagnostics.push(diag('server-json-version-semver', 'warning', `version '${s.version}' is not valid semver. Semver is recommended.`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ── Optional fields with structure constraints ───────────────────────
|
|
80
|
+
// repository
|
|
81
|
+
if (s.repository != null) {
|
|
82
|
+
if (typeof s.repository !== 'object' || Array.isArray(s.repository)) {
|
|
83
|
+
diagnostics.push(diag('server-json-repository-type', 'error', 'repository must be an object.'));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const repo = s.repository;
|
|
87
|
+
if (typeof repo.url !== 'string' || repo.url.length === 0) {
|
|
88
|
+
diagnostics.push(diag('server-json-repository-url', 'error', 'repository.url is required when repository is present.'));
|
|
89
|
+
}
|
|
90
|
+
if (typeof repo.source !== 'string' || repo.source.length === 0) {
|
|
91
|
+
diagnostics.push(diag('server-json-repository-source', 'error', 'repository.source is required when repository is present.'));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// packages
|
|
96
|
+
if (s.packages != null) {
|
|
97
|
+
if (!Array.isArray(s.packages)) {
|
|
98
|
+
diagnostics.push(diag('server-json-packages-type', 'error', 'packages must be an array.'));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
for (let i = 0; i < s.packages.length; i++) {
|
|
102
|
+
diagnostics.push(...lintPackageEntry(s.packages[i], i, s.version));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// remotes
|
|
107
|
+
if (s.remotes != null) {
|
|
108
|
+
if (!Array.isArray(s.remotes)) {
|
|
109
|
+
diagnostics.push(diag('server-json-remotes-type', 'error', 'remotes must be an array.'));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
for (let i = 0; i < s.remotes.length; i++) {
|
|
113
|
+
diagnostics.push(...lintRemoteEntry(s.remotes[i], i));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ── Cross-validation ─────────────────────────────────────────────────
|
|
118
|
+
if (crossCheck?.packageJsonVersion &&
|
|
119
|
+
typeof s.version === 'string' &&
|
|
120
|
+
s.version.length > 0 &&
|
|
121
|
+
s.version !== crossCheck.packageJsonVersion) {
|
|
122
|
+
diagnostics.push(diag('server-json-version-sync', 'warning', `version '${s.version}' does not match package.json version '${crossCheck.packageJsonVersion}'.`));
|
|
123
|
+
}
|
|
124
|
+
return diagnostics;
|
|
125
|
+
}
|
|
126
|
+
/** Validates a single entry in the packages array. */
|
|
127
|
+
function lintPackageEntry(pkg, index, rootVersion) {
|
|
128
|
+
const diagnostics = [];
|
|
129
|
+
const prefix = `packages[${index}]`;
|
|
130
|
+
if (typeof pkg !== 'object' || pkg == null || Array.isArray(pkg)) {
|
|
131
|
+
diagnostics.push(diag('server-json-package-type', 'error', `${prefix} must be an object.`));
|
|
132
|
+
return diagnostics;
|
|
133
|
+
}
|
|
134
|
+
const p = pkg;
|
|
135
|
+
// registryType: required
|
|
136
|
+
if (typeof p.registryType !== 'string' || p.registryType.length === 0) {
|
|
137
|
+
diagnostics.push(diag('server-json-package-registry', 'error', `${prefix}.registryType is required.`));
|
|
138
|
+
}
|
|
139
|
+
// identifier: required
|
|
140
|
+
if (typeof p.identifier !== 'string' || p.identifier.length === 0) {
|
|
141
|
+
diagnostics.push(diag('server-json-package-identifier', 'error', `${prefix}.identifier is required.`));
|
|
142
|
+
}
|
|
143
|
+
// transport: required
|
|
144
|
+
if (p.transport == null) {
|
|
145
|
+
diagnostics.push(diag('server-json-package-transport', 'error', `${prefix}.transport is required.`));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
diagnostics.push(...lintTransport(p.transport, `${prefix}.transport`));
|
|
149
|
+
}
|
|
150
|
+
// version: must not be "latest", should match root
|
|
151
|
+
if (typeof p.version === 'string') {
|
|
152
|
+
if (p.version === 'latest') {
|
|
153
|
+
diagnostics.push(diag('server-json-package-no-latest', 'error', `${prefix}.version must be a specific version, not "latest".`));
|
|
154
|
+
}
|
|
155
|
+
if (rootVersion &&
|
|
156
|
+
p.version.length > 0 &&
|
|
157
|
+
p.version !== 'latest' &&
|
|
158
|
+
p.version !== rootVersion) {
|
|
159
|
+
diagnostics.push(diag('server-json-package-version-sync', 'warning', `${prefix}.version '${p.version}' does not match root version '${rootVersion}'.`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// packageArguments
|
|
163
|
+
if (p.packageArguments != null) {
|
|
164
|
+
if (!Array.isArray(p.packageArguments)) {
|
|
165
|
+
diagnostics.push(diag('server-json-package-args-type', 'error', `${prefix}.packageArguments must be an array.`));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
for (let j = 0; j < p.packageArguments.length; j++) {
|
|
169
|
+
diagnostics.push(...lintArgument(p.packageArguments[j], `${prefix}.packageArguments[${j}]`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// runtimeArguments
|
|
174
|
+
if (p.runtimeArguments != null) {
|
|
175
|
+
if (!Array.isArray(p.runtimeArguments)) {
|
|
176
|
+
diagnostics.push(diag('server-json-runtime-args-type', 'error', `${prefix}.runtimeArguments must be an array.`));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
for (let j = 0; j < p.runtimeArguments.length; j++) {
|
|
180
|
+
diagnostics.push(...lintArgument(p.runtimeArguments[j], `${prefix}.runtimeArguments[${j}]`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// environmentVariables
|
|
185
|
+
if (p.environmentVariables != null) {
|
|
186
|
+
if (!Array.isArray(p.environmentVariables)) {
|
|
187
|
+
diagnostics.push(diag('server-json-env-vars-type', 'error', `${prefix}.environmentVariables must be an array.`));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
for (let j = 0; j < p.environmentVariables.length; j++) {
|
|
191
|
+
diagnostics.push(...lintEnvVar(p.environmentVariables[j], `${prefix}.environmentVariables[${j}]`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return diagnostics;
|
|
196
|
+
}
|
|
197
|
+
/** Validates a single entry in the remotes array. */
|
|
198
|
+
function lintRemoteEntry(remote, index) {
|
|
199
|
+
const diagnostics = [];
|
|
200
|
+
const prefix = `remotes[${index}]`;
|
|
201
|
+
if (typeof remote !== 'object' || remote == null || Array.isArray(remote)) {
|
|
202
|
+
diagnostics.push(diag('server-json-remote-type', 'error', `${prefix} must be an object.`));
|
|
203
|
+
return diagnostics;
|
|
204
|
+
}
|
|
205
|
+
const r = remote;
|
|
206
|
+
// Remote transports must be streamable-http or sse (no stdio)
|
|
207
|
+
if (r.type == null) {
|
|
208
|
+
diagnostics.push(diag('server-json-remote-transport-type', 'error', `${prefix}.type is required.`));
|
|
209
|
+
}
|
|
210
|
+
else if (r.type === 'stdio') {
|
|
211
|
+
diagnostics.push(diag('server-json-remote-no-stdio', 'error', `${prefix}.type is 'stdio' but remotes only support 'streamable-http' or 'sse'.`));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
diagnostics.push(...lintTransport(r, prefix));
|
|
215
|
+
}
|
|
216
|
+
return diagnostics;
|
|
217
|
+
}
|
|
218
|
+
/** Validates a transport object. */
|
|
219
|
+
function lintTransport(transport, path) {
|
|
220
|
+
const diagnostics = [];
|
|
221
|
+
if (typeof transport !== 'object' || transport == null || Array.isArray(transport)) {
|
|
222
|
+
diagnostics.push(diag('server-json-transport-type', 'error', `${path} must be an object.`));
|
|
223
|
+
return diagnostics;
|
|
224
|
+
}
|
|
225
|
+
const t = transport;
|
|
226
|
+
if (typeof t.type !== 'string' || !TRANSPORT_TYPES.has(t.type)) {
|
|
227
|
+
diagnostics.push(diag('server-json-transport-type-value', 'error', `${path}.type must be one of: ${[...TRANSPORT_TYPES].join(', ')}. Got '${String(t.type)}'.`));
|
|
228
|
+
return diagnostics;
|
|
229
|
+
}
|
|
230
|
+
if (URL_TRANSPORTS.has(t.type)) {
|
|
231
|
+
if (typeof t.url !== 'string' || t.url.length === 0) {
|
|
232
|
+
diagnostics.push(diag('server-json-transport-url-required', 'error', `${path}.url is required for transport type '${t.type}'.`));
|
|
233
|
+
}
|
|
234
|
+
else if (!URL_PATTERN.test(t.url)) {
|
|
235
|
+
diagnostics.push(diag('server-json-transport-url-format', 'warning', `${path}.url '${t.url}' should match pattern: ${URL_PATTERN.source}.`));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return diagnostics;
|
|
239
|
+
}
|
|
240
|
+
/** Validates a single argument entry (positional or named). */
|
|
241
|
+
function lintArgument(arg, path) {
|
|
242
|
+
const diagnostics = [];
|
|
243
|
+
if (typeof arg !== 'object' || arg == null || Array.isArray(arg)) {
|
|
244
|
+
diagnostics.push(diag('server-json-argument-type', 'error', `${path} must be an object.`));
|
|
245
|
+
return diagnostics;
|
|
246
|
+
}
|
|
247
|
+
const a = arg;
|
|
248
|
+
if (typeof a.type !== 'string' || !ARGUMENT_TYPES.has(a.type)) {
|
|
249
|
+
diagnostics.push(diag('server-json-argument-type-value', 'error', `${path}.type must be 'positional' or 'named'. Got '${String(a.type)}'.`));
|
|
250
|
+
return diagnostics;
|
|
251
|
+
}
|
|
252
|
+
if (a.type === 'named') {
|
|
253
|
+
if (typeof a.name !== 'string' || a.name.length === 0) {
|
|
254
|
+
diagnostics.push(diag('server-json-argument-name', 'error', `${path}.name is required for named arguments.`));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Positional args need either value or valueHint
|
|
258
|
+
if (a.type === 'positional') {
|
|
259
|
+
const hasValue = typeof a.value === 'string' && a.value.length > 0;
|
|
260
|
+
const hasHint = typeof a.valueHint === 'string' && a.valueHint.length > 0;
|
|
261
|
+
if (!hasValue && !hasHint) {
|
|
262
|
+
diagnostics.push(diag('server-json-argument-value', 'error', `${path} (positional) requires either 'value' or 'valueHint'.`));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// format validation if present
|
|
266
|
+
if (a.format != null && (typeof a.format !== 'string' || !INPUT_FORMATS.has(a.format))) {
|
|
267
|
+
diagnostics.push(diag('server-json-input-format', 'warning', `${path}.format should be one of: ${[...INPUT_FORMATS].join(', ')}. Got '${String(a.format)}'.`));
|
|
268
|
+
}
|
|
269
|
+
return diagnostics;
|
|
270
|
+
}
|
|
271
|
+
/** Validates a single environment variable entry. */
|
|
272
|
+
function lintEnvVar(envVar, path) {
|
|
273
|
+
const diagnostics = [];
|
|
274
|
+
if (typeof envVar !== 'object' || envVar == null || Array.isArray(envVar)) {
|
|
275
|
+
diagnostics.push(diag('server-json-env-var-type', 'error', `${path} must be an object.`));
|
|
276
|
+
return diagnostics;
|
|
277
|
+
}
|
|
278
|
+
const e = envVar;
|
|
279
|
+
if (typeof e.name !== 'string' || e.name.length === 0) {
|
|
280
|
+
diagnostics.push(diag('server-json-env-var-name', 'error', `${path}.name is required.`));
|
|
281
|
+
}
|
|
282
|
+
if (e.description == null || (typeof e.description === 'string' && e.description.length === 0)) {
|
|
283
|
+
diagnostics.push(diag('server-json-env-var-description', 'warning', `${path} '${typeof e.name === 'string' ? e.name : '?'}' has no description.`));
|
|
284
|
+
}
|
|
285
|
+
if (e.format != null && (typeof e.format !== 'string' || !INPUT_FORMATS.has(e.format))) {
|
|
286
|
+
diagnostics.push(diag('server-json-input-format', 'warning', `${path}.format should be one of: ${[...INPUT_FORMATS].join(', ')}. Got '${String(e.format)}'.`));
|
|
287
|
+
}
|
|
288
|
+
return diagnostics;
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=server-json-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-json-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/server-json-rules.ts"],"names":[],"mappings":"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"}
|
package/dist/linter/types.d.ts
CHANGED
|
@@ -6,16 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** Severity of a lint diagnostic. */
|
|
8
8
|
export type LintSeverity = 'error' | 'warning';
|
|
9
|
+
/** Definition type that produced a lint diagnostic. */
|
|
10
|
+
export type LintDefinitionType = 'tool' | 'resource' | 'prompt' | 'server-json';
|
|
9
11
|
/**
|
|
10
12
|
* A single lint diagnostic produced by a validation rule.
|
|
11
13
|
* Errors represent spec violations that will cause runtime failures.
|
|
12
14
|
* Warnings represent SHOULD-level or quality issues.
|
|
13
15
|
*/
|
|
14
16
|
export interface LintDiagnostic {
|
|
15
|
-
/** Name of the specific definition (tool/resource/prompt name). */
|
|
17
|
+
/** Name of the specific definition (tool/resource/prompt name, or 'server.json'). */
|
|
16
18
|
definitionName: string;
|
|
17
19
|
/** Which definition type produced this diagnostic. */
|
|
18
|
-
definitionType:
|
|
20
|
+
definitionType: LintDefinitionType;
|
|
19
21
|
/** Human-readable message describing the issue. */
|
|
20
22
|
message: string;
|
|
21
23
|
/** Rule identifier (e.g., 'name-format', 'describe-on-fields'). */
|
|
@@ -36,11 +38,17 @@ export interface LintReport {
|
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Input to `validateDefinitions()`.
|
|
39
|
-
* Mirrors the definition arrays from `CreateAppOptions
|
|
41
|
+
* Mirrors the definition arrays from `CreateAppOptions`, plus optional server.json.
|
|
40
42
|
*/
|
|
41
43
|
export interface LintInput {
|
|
44
|
+
/** Parsed package.json — used for cross-validation (version sync). */
|
|
45
|
+
packageJson?: {
|
|
46
|
+
version?: string;
|
|
47
|
+
};
|
|
42
48
|
prompts?: unknown[];
|
|
43
49
|
resources?: unknown[];
|
|
50
|
+
/** Parsed server.json content. When provided, validated against the MCP server manifest spec. */
|
|
51
|
+
serverJson?: unknown;
|
|
44
52
|
tools?: unknown[];
|
|
45
53
|
}
|
|
46
54
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/linter/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/linter/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/C,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEhF;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,cAAc,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,cAAc,EAAE,kBAAkB,CAAC;IACnC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,WAAW,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;IACtB,iGAAiG;IACjG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/linter/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/linter/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAkB,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CA0DhE"}
|
package/dist/linter/validate.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { checkDuplicateNames } from './rules/name-rules.js';
|
|
8
8
|
import { lintPromptDefinition } from './rules/prompt-rules.js';
|
|
9
9
|
import { lintResourceDefinition } from './rules/resource-rules.js';
|
|
10
|
+
import { lintServerJson } from './rules/server-json-rules.js';
|
|
10
11
|
import { lintToolDefinition } from './rules/tool-rules.js';
|
|
11
12
|
/**
|
|
12
13
|
* Validates MCP tool, resource, and prompt definitions against the MCP spec
|
|
@@ -46,6 +47,11 @@ export function validateDefinitions(input) {
|
|
|
46
47
|
for (const def of prompts) {
|
|
47
48
|
diagnostics.push(...lintPromptDefinition(def));
|
|
48
49
|
}
|
|
50
|
+
// server.json manifest validation
|
|
51
|
+
if (input.serverJson != null) {
|
|
52
|
+
const pkgVersion = input.packageJson?.version;
|
|
53
|
+
diagnostics.push(...lintServerJson(input.serverJson, pkgVersion ? { packageJsonVersion: pkgVersion } : undefined));
|
|
54
|
+
}
|
|
49
55
|
// Cross-definition duplicate checks
|
|
50
56
|
const extractNames = (defs) => defs
|
|
51
57
|
.map((d) => d?.name)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/linter/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAgB;IAClD,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;IAEpC,4BAA4B;IAC5B,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,WAAW,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,WAAW,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,WAAW,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,oCAAoC;IACpC,MAAM,YAAY,GAAG,CAAC,IAAe,EAAE,EAAE,CACvC,IAAI;SACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAA6B,EAAE,IAAI,CAAC;SAChD,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEvE,WAAW,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtE,MAAM,aAAa,GAAG,SAAS;SAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,CAAC,GAAG,CAA4B,CAAC;QACvC,OAAO,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ;YAChC,CAAC,CAAC,CAAC,CAAC,IAAI;YACR,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,QAAQ;gBAClC,CAAC,CAAC,CAAC,CAAC,WAAW;gBACf,CAAC,CAAC,EAAE,CAAC;IACX,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,WAAW,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;IAEpE,WAAW,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1E,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC;IAErE,OAAO;QACL,MAAM;QACN,QAAQ;QACR,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;KAC5B,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/linter/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -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.4",
|
|
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",
|
|
@@ -145,8 +145,11 @@
|
|
|
145
145
|
"chrono-node": "2.9.0",
|
|
146
146
|
"diff": "8.0.4",
|
|
147
147
|
"dotenv": "17.3.1",
|
|
148
|
+
"brace-expansion": "1.1.13",
|
|
148
149
|
"flatted": "3.4.2",
|
|
150
|
+
"handlebars": "4.7.9",
|
|
149
151
|
"hono": "4.12.9",
|
|
152
|
+
"path-to-regexp": "8.4.0",
|
|
150
153
|
"picomatch": "2.3.2",
|
|
151
154
|
"yaml": "1.10.3",
|
|
152
155
|
"zod": "4.3.6"
|
|
@@ -250,7 +253,7 @@
|
|
|
250
253
|
"dependencies": {
|
|
251
254
|
"@hono/mcp": "^0.2.4",
|
|
252
255
|
"@hono/node-server": "^1.19.11",
|
|
253
|
-
"@modelcontextprotocol/ext-apps": "^1.3.
|
|
256
|
+
"@modelcontextprotocol/ext-apps": "^1.3.2",
|
|
254
257
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
255
258
|
"@opentelemetry/api": "^1.9.1",
|
|
256
259
|
"dotenv": "^17.3.1",
|
package/scripts/lint-mcp.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module scripts/lint-mcp
|
|
18
18
|
*/
|
|
19
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { join, resolve } from 'node:path';
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -102,11 +102,26 @@ function discoverFiles(): string[] {
|
|
|
102
102
|
// Main
|
|
103
103
|
// ---------------------------------------------------------------------------
|
|
104
104
|
|
|
105
|
+
/** Try to read and parse a JSON file. Returns undefined on failure. */
|
|
106
|
+
function tryReadJson(path: string): unknown {
|
|
107
|
+
try {
|
|
108
|
+
if (!existsSync(path)) return;
|
|
109
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn(`Warning: Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
async function main(): Promise<void> {
|
|
106
117
|
const files = discoverFiles();
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
// Discover server.json and package.json at project root
|
|
120
|
+
const serverJson = tryReadJson(resolve('server.json'));
|
|
121
|
+
const packageJson = tryReadJson(resolve('package.json')) as { version?: string } | undefined;
|
|
122
|
+
|
|
123
|
+
if (files.length === 0 && serverJson == null) {
|
|
124
|
+
console.log('No MCP definition files or server.json found. Skipping lint.');
|
|
110
125
|
process.exit(0);
|
|
111
126
|
}
|
|
112
127
|
|
|
@@ -129,17 +144,28 @@ async function main(): Promise<void> {
|
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
147
|
+
const defTotal = tools.length + resources.length + prompts.length;
|
|
148
|
+
if (defTotal === 0 && serverJson == null) {
|
|
134
149
|
console.log(`Scanned ${files.length} files but found no definitions. Skipping lint.`);
|
|
135
150
|
process.exit(0);
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
const parts: string[] = [];
|
|
154
|
+
if (defTotal > 0) {
|
|
155
|
+
parts.push(
|
|
156
|
+
`${tools.length} tool(s), ${resources.length} resource(s), ${prompts.length} prompt(s) from ${files.length} file(s)`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (serverJson != null) parts.push('server.json');
|
|
160
|
+
console.log(`Linting ${parts.join(' + ')}...`);
|
|
161
|
+
|
|
162
|
+
const report = validateDefinitions({
|
|
163
|
+
tools,
|
|
164
|
+
resources,
|
|
165
|
+
prompts,
|
|
166
|
+
serverJson,
|
|
167
|
+
packageJson,
|
|
168
|
+
});
|
|
143
169
|
|
|
144
170
|
for (const w of report.warnings) {
|
|
145
171
|
console.warn(` ⚠ [${w.rule}] ${w.message}`);
|
|
@@ -150,6 +150,40 @@ parseResponse<T>(text: string): T {
|
|
|
150
150
|
}
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
## API Efficiency
|
|
154
|
+
|
|
155
|
+
When a service wraps an external API, design methods to minimize upstream calls. These patterns compound — a tool calling 3 service methods that each make N requests is 3N calls; batching drops it to 3.
|
|
156
|
+
|
|
157
|
+
### Batch over N+1
|
|
158
|
+
|
|
159
|
+
If the API supports filter-by-IDs, bulk GET, or batch query endpoints, expose a batch method instead of (or alongside) the single-item method. One request for 20 items beats 20 sequential requests — it eliminates serial latency, avoids rate-limit accumulation, and simplifies error handling.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
/** Fetch multiple studies in a single request via filter.ids. */
|
|
163
|
+
async getStudiesBatch(nctIds: string[], ctx: Context): Promise<Study[]> {
|
|
164
|
+
const response = await this.searchStudies({
|
|
165
|
+
filterIds: nctIds,
|
|
166
|
+
fields: ['NCTId', 'BriefTitle', 'HasResults', 'ResultsSection'],
|
|
167
|
+
pageSize: nctIds.length,
|
|
168
|
+
}, ctx);
|
|
169
|
+
return response.studies;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Cross-reference the response against the requested IDs to detect missing items — don't assume the API returns everything you asked for.
|
|
174
|
+
|
|
175
|
+
### Field selection
|
|
176
|
+
|
|
177
|
+
If the API supports `fields`, `select`, or `include` parameters, request only what the caller needs. A full record might be 70KB; four fields might be 5KB. Expose field selection as a parameter on the service method, or use sensible defaults per method.
|
|
178
|
+
|
|
179
|
+
### Pagination awareness
|
|
180
|
+
|
|
181
|
+
If a batch request might exceed the API's page size limit, either:
|
|
182
|
+
- Paginate internally (loop until all pages consumed), or
|
|
183
|
+
- Assert/throw when the response indicates truncation (e.g., `nextPageToken` present)
|
|
184
|
+
|
|
185
|
+
Silent truncation is a data integrity bug — the caller thinks it has all results when it doesn't.
|
|
186
|
+
|
|
153
187
|
## Checklist
|
|
154
188
|
|
|
155
189
|
- [ ] Directory created at `src/services/{{domain}}/`
|
|
@@ -159,4 +193,5 @@ parseResponse<T>(text: string): T {
|
|
|
159
193
|
- [ ] `init` function registered in `setup()` callback in `src/index.ts`
|
|
160
194
|
- [ ] Accessor throws `Error` if not initialized
|
|
161
195
|
- [ ] If wrapping external API: retry covers full pipeline (fetch + parse), backoff calibrated
|
|
196
|
+
- [ ] If wrapping external API: batch endpoints used where available, field selection applied, pagination handled
|
|
162
197
|
- [ ] `bun run devcheck` passes
|
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`
|
|
@@ -33,7 +33,7 @@ If the domain has a public API, read its docs before designing. Don't design fro
|
|
|
33
33
|
|
|
34
34
|
### 1. Research External Dependencies
|
|
35
35
|
|
|
36
|
-
Before designing, verify the APIs and services the server will wrap.
|
|
36
|
+
Before designing, verify the APIs and services the server will wrap. Read the docs, then **hit the API** — real requests reveal what docs omit.
|
|
37
37
|
|
|
38
38
|
If the Agent tool is available, spawn background agents to research in parallel while you proceed with domain mapping:
|
|
39
39
|
|
|
@@ -43,6 +43,16 @@ If the Agent tool is available, spawn background agents to research in parallel
|
|
|
43
43
|
|
|
44
44
|
If the Agent tool is not available, do this research inline — fetch docs, read SDK readmes, confirm assumptions before committing them to the design.
|
|
45
45
|
|
|
46
|
+
**Live API probing.** After reading docs, make real requests against the API to verify assumptions:
|
|
47
|
+
|
|
48
|
+
- **Response shapes** — confirm actual field names, nesting, and types. Docs frequently lag or omit fields.
|
|
49
|
+
- **Batch/filter endpoints** — look for `filter.ids`, bulk GET, or query-by-multiple-IDs patterns. A single batch request replaces N individual fetches and eliminates serial-request bottlenecks and rate-limit accumulation.
|
|
50
|
+
- **Field selection** — check if the API supports `fields` or `select` parameters to request only the data you need. This reduces payload size dramatically for large objects.
|
|
51
|
+
- **Pagination behavior** — verify token format, page size limits, and what happens when results exceed one page.
|
|
52
|
+
- **Error shapes** — trigger real 400/404/429 responses to see the actual error format, not just what docs claim.
|
|
53
|
+
|
|
54
|
+
This step prevents building a service layer against assumed response shapes that don't match reality.
|
|
55
|
+
|
|
46
56
|
### 2. Map the Domain
|
|
47
57
|
|
|
48
58
|
List the concrete operations the underlying system supports. Group by domain noun.
|
|
@@ -193,7 +203,7 @@ output: z.object({
|
|
|
193
203
|
```
|
|
194
204
|
|
|
195
205
|
- **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
|
-
-
|
|
206
|
+
- **`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
207
|
|
|
198
208
|
#### Convenience shortcuts for complex inputs
|
|
199
209
|
|
|
@@ -279,6 +289,15 @@ For services wrapping external APIs, plan the resilience layer. See `docs/servic
|
|
|
279
289
|
| **Parse failure classification** | Response handler detects HTML error pages and throws transient errors, not `SerializationError`. |
|
|
280
290
|
| **Exhausted retry messaging** | `withRetry` enriches the final error with attempt count automatically. |
|
|
281
291
|
|
|
292
|
+
For API efficiency, design the service methods to minimize upstream calls:
|
|
293
|
+
|
|
294
|
+
| Concern | Decision |
|
|
295
|
+
|:--------|:---------|
|
|
296
|
+
| **Batch over N+1** | If the API supports filter-by-IDs or bulk-GET endpoints, use a single batch request instead of N individual fetches. Cross-reference the response against requested IDs to detect missing items. |
|
|
297
|
+
| **Field selection** | If the API supports `fields`/`select` parameters, request only the fields the tool needs. A full study record might be 70KB; selecting 4 fields might be 5KB. |
|
|
298
|
+
| **Request consolidation** | When a tool needs data from multiple related endpoints, check if a single endpoint with broader field selection can serve the same data in one round trip. |
|
|
299
|
+
| **Pagination awareness** | If a batch request might exceed the API's page size, either paginate internally or assert/throw when results are truncated so callers aren't silently missing data. |
|
|
300
|
+
|
|
282
301
|
**Config** — list env vars (API keys, base URLs). Goes in `src/config/server-config.ts` as a separate Zod schema.
|
|
283
302
|
|
|
284
303
|
### 8. Write the Design Doc
|
|
@@ -364,6 +383,7 @@ Execute the plan using the scaffolding skills:
|
|
|
364
383
|
- [ ] Parameter `.describe()` text explains what the value is, what it affects, and tradeoffs
|
|
365
384
|
- [ ] Input schemas use constrained types (enums, literals, regex) over free strings
|
|
366
385
|
- [ ] Output schemas designed for LLM's next action — chaining IDs, post-write state, filtering communicated
|
|
386
|
+
- [ ] `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
387
|
- [ ] Error messages guide recovery — name what went wrong and what to do next
|
|
368
388
|
- [ ] Annotations set correctly (`readOnlyHint`, `destructiveHint`, etc.)
|
|
369
389
|
- [ ] 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/AGENTS.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
|
|
|
@@ -279,6 +284,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
279
284
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
280
285
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
281
286
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
|
287
|
+
- [ ] `format()` renders all data the LLM needs — `content[]` is the only field most clients forward to the model
|
|
282
288
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
283
289
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
284
290
|
- [ ] `npm run devcheck` passes
|
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
|
|
|
@@ -279,6 +284,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
279
284
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
280
285
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
281
286
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
|
287
|
+
- [ ] `format()` renders all data the LLM needs — `content[]` is the only field most clients forward to the model
|
|
282
288
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
283
289
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
284
290
|
- [ ] `npm run devcheck` passes
|
|
@@ -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
|
});
|