@cyanheads/mcp-ts-core 0.1.20 → 0.1.22
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 +4 -2
- package/README.md +2 -2
- package/dist/linter/rules/index.d.ts +2 -2
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +2 -2
- package/dist/linter/rules/index.js.map +1 -1
- package/dist/linter/rules/name-rules.js +2 -2
- package/dist/linter/rules/name-rules.js.map +1 -1
- package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
- package/dist/linter/rules/prompt-rules.js +4 -1
- package/dist/linter/rules/prompt-rules.js.map +1 -1
- package/dist/linter/rules/resource-rules.d.ts.map +1 -1
- package/dist/linter/rules/resource-rules.js +60 -2
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +9 -1
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +28 -1
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts +2 -0
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +64 -3
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/package.json +3 -2
- package/scripts/lint-mcp.ts +3 -1
- package/skills/add-tool/SKILL.md +3 -2
- package/skills/design-mcp-server/SKILL.md +1 -0
- package/skills/field-test/SKILL.md +1 -1
- package/skills/polish-docs-meta/references/readme.md +6 -4
- package/templates/AGENTS.md +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/_.dockerignore +197 -0
- package/templates/package.json +2 -2
- package/templates/tests/prompts/echo.prompt.test.ts +19 -0
- package/templates/tests/resources/echo.resource.test.ts +26 -0
- package/templates/tests/tools/echo.tool.test.ts +22 -0
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
|
-
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.1.
|
|
3
|
+
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.1.22
|
|
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.
|
|
@@ -173,6 +173,8 @@ export const myTool = tool('my_tool', {
|
|
|
173
173
|
|
|
174
174
|
**Steps:** Create `src/mcp-server/tools/definitions/[name].tool.ts` (kebab-case) → use `tool('snake_case', {...})` with Zod `.describe()` on all fields → implement `handler(input, ctx)` (pure, throws on failure) → add `auth`/`format` if needed → register in `definitions/index.ts` → `bun run devcheck` → smoke-test with `dev:stdio`/`dev:http`.
|
|
175
175
|
|
|
176
|
+
**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.
|
|
177
|
+
|
|
176
178
|
**`format`**: Maps output to `ContentBlock[]`. Omit for JSON stringify default. Additional formatters: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter` from `/utils`.
|
|
177
179
|
|
|
178
180
|
**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.
|
|
@@ -418,7 +420,7 @@ Detailed method signatures, options, and examples live in skill files. Read the
|
|
|
418
420
|
|
|
419
421
|
## Code Style & Checklist
|
|
420
422
|
|
|
421
|
-
- **Validation:** Zod schemas, all fields need `.describe()`
|
|
423
|
+
- **Validation:** Zod schemas, all fields need `.describe()`. Schemas must be JSON-Schema-serializable — avoid `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()` (the linter catches these at startup)
|
|
422
424
|
- **Logging:** Framework auto-instruments all handler calls. `ctx.log` for domain-specific logging in handlers, global `logger` for lifecycle/background
|
|
423
425
|
- **Errors:** handlers throw — error factories (`notFound()`, `validationError()`, etc.) when the code matters, plain `Error` for don't-care cases. Framework catches and classifies. `ErrorHandler.tryCatch` for services only.
|
|
424
426
|
- **Secrets:** server config only — no hardcoded credentials
|
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
|
|
|
@@ -39,7 +39,7 @@ That's a complete MCP server. Every tool call is automatically logged with durat
|
|
|
39
39
|
- **Unified Context** — handlers receive a single `ctx` object with `ctx.log` (request-scoped logging), `ctx.state` (tenant-scoped storage), `ctx.elicit` (user prompting), `ctx.sample` (LLM completion), and `ctx.signal` (cancellation).
|
|
40
40
|
- **Inline auth** — `auth: ['scope']` on definitions. No wrapper functions. Framework checks scopes before calling your handler.
|
|
41
41
|
- **Task tools** — `task: true` flag for long-running operations. Framework manages the full lifecycle (create, poll, progress, complete/fail/cancel).
|
|
42
|
-
- **Definition linter** — `validateDefinitions()` checks tools, resources, and prompts against MCP spec at startup. Name format, schema structure, `.describe()` presence, URI template
|
|
42
|
+
- **Definition linter** — `validateDefinitions()` checks tools, resources, and prompts against MCP spec at startup. Name format, schema structure, `.describe()` presence, JSON Schema serializability, auth scope validity, annotation coherence, and URI template–params alignment. Also available as a standalone CLI (`lint:mcp`) and devcheck step.
|
|
43
43
|
- **Structured error handling** — Handlers throw freely; the framework catches, classifies, and formats. Error factories (`notFound()`, `validationError()`, `serviceUnavailable()`, etc.) for precise control when the code matters. Auto-classification from plain `Error` messages when it doesn't.
|
|
44
44
|
- **Multi-backend storage** — `in-memory`, `filesystem`, `Supabase`, `Cloudflare D1/KV/R2`. Swap providers via env var without changing tool logic. Cursor pagination, batch ops, TTL, tenant isolation.
|
|
45
45
|
- **Pluggable auth** — `none`, `jwt`, or `oauth` modes. JWT with local secret or OAuth with JWKS verification.
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
export { checkDuplicateNames, checkNameRequired, checkToolNameFormat } from './name-rules.js';
|
|
6
6
|
export { lintPromptDefinition } from './prompt-rules.js';
|
|
7
7
|
export { lintResourceDefinition } from './resource-rules.js';
|
|
8
|
-
export { checkFieldDescriptions, checkIsZodObject } from './schema-rules.js';
|
|
9
|
-
export { lintToolDefinition } from './tool-rules.js';
|
|
8
|
+
export { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
9
|
+
export { lintAuthScopes, lintToolDefinition } from './tool-rules.js';
|
|
10
10
|
//# 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,
|
|
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"}
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
export { checkDuplicateNames, checkNameRequired, checkToolNameFormat } from './name-rules.js';
|
|
6
6
|
export { lintPromptDefinition } from './prompt-rules.js';
|
|
7
7
|
export { lintResourceDefinition } from './resource-rules.js';
|
|
8
|
-
export { checkFieldDescriptions, checkIsZodObject } from './schema-rules.js';
|
|
9
|
-
export { lintToolDefinition } from './tool-rules.js';
|
|
8
|
+
export { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
9
|
+
export { lintAuthScopes, lintToolDefinition } from './tool-rules.js';
|
|
10
10
|
//# 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,
|
|
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"}
|
|
@@ -29,9 +29,9 @@ export function checkToolNameFormat(name) {
|
|
|
29
29
|
if (!TOOL_NAME_RE.test(name)) {
|
|
30
30
|
return {
|
|
31
31
|
rule: 'name-format',
|
|
32
|
-
severity: '
|
|
32
|
+
severity: 'error',
|
|
33
33
|
message: `Tool name '${name}' does not match MCP spec format [A-Za-z0-9._-]{1,128}. ` +
|
|
34
|
-
'
|
|
34
|
+
'The spec requires (MUST) this format. Non-conforming names may break clients.',
|
|
35
35
|
definitionType: 'tool',
|
|
36
36
|
definitionName: name,
|
|
37
37
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"name-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/name-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,6EAA6E;AAC7E,MAAM,YAAY,GAAG,yBAAyB,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAa,EACb,cAAgD,EAChD,cAAsB;IAEtB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,GAAG,cAAc,mDAAmD;YAC7E,cAAc;YACd,cAAc,EAAE,cAAc,IAAI,WAAW;SAC9C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"name-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/name-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,6EAA6E;AAC7E,MAAM,YAAY,GAAG,yBAAyB,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAa,EACb,cAAgD,EAChD,cAAsB;IAEtB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,GAAG,cAAc,mDAAmD;YAC7E,cAAc;YACd,cAAc,EAAE,cAAc,IAAI,WAAW;SAC9C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,OAAO;YACjB,OAAO,EACL,cAAc,IAAI,0DAA0D;gBAC5E,+EAA+E;YACjF,cAAc,EAAE,MAAM;YACtB,cAAc,EAAE,IAAI;SACrB,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAe,EACf,cAAgD;IAEhD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,WAAW,GAAqB,EAAE,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,aAAa;gBACnB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,aAAa,cAAc,UAAU,IAAI,WAAW,cAAc,2BAA2B;gBACtG,cAAc;gBACd,cAAc,EAAE,IAAI;aACrB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/prompt-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"prompt-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/prompt-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,EAAE,CA6CnE"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module src/linter/rules/prompt-rules
|
|
5
5
|
*/
|
|
6
6
|
import { checkNameRequired } from './name-rules.js';
|
|
7
|
-
import { checkFieldDescriptions, checkIsZodObject } from './schema-rules.js';
|
|
7
|
+
import { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
8
8
|
/**
|
|
9
9
|
* Runs all lint rules against a single prompt definition.
|
|
10
10
|
*/
|
|
@@ -45,6 +45,9 @@ export function lintPromptDefinition(def) {
|
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
47
|
diagnostics.push(...checkFieldDescriptions(d.args, 'args', 'prompt', displayName));
|
|
48
|
+
const argsSerial = checkSchemaSerializable(d.args, 'args', 'prompt', displayName);
|
|
49
|
+
if (argsSerial)
|
|
50
|
+
diagnostics.push(argsSerial);
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
return diagnostics;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/prompt-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,
|
|
1
|
+
{"version":3,"file":"prompt-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/prompt-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,MAAM,WAAW,GAAG,IAAI,IAAI,WAAW,CAAC;IAExC,kBAAkB;IAClB,MAAM,OAAO,GAAG,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3D,IAAI,OAAO;QAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEvC,cAAc;IACd,IAAI,OAAO,CAAC,EAAE,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrE,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,WAAW,WAAW,uBAAuB;YACtD,cAAc,EAAE,QAAQ;YACxB,cAAc,EAAE,WAAW;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,oBAAoB;IACpB,IAAI,OAAO,CAAC,EAAE,QAAQ,KAAK,UAAU,EAAE,CAAC;QACtC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,mBAAmB;YACzB,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,WAAW,WAAW,mCAAmC;YAClE,cAAc,EAAE,QAAQ;YACxB,cAAc,EAAE,WAAW;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,6DAA6D;IAC7D,IAAI,CAAC,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC1E,IAAI,SAAS,EAAE,CAAC;YACd,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;YACnF,MAAM,UAAU,GAAG,uBAAuB,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAClF,IAAI,UAAU;gBAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/resource-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"resource-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/resource-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AASlD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,EAAE,CAiGrE"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @module src/linter/rules/resource-rules
|
|
5
5
|
*/
|
|
6
6
|
import { checkNameRequired } from './name-rules.js';
|
|
7
|
-
import { checkFieldDescriptions, checkIsZodObject } from './schema-rules.js';
|
|
7
|
+
import { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
8
|
+
import { lintAuthScopes } from './tool-rules.js';
|
|
8
9
|
/**
|
|
9
10
|
* Runs all lint rules against a single resource definition.
|
|
10
11
|
*/
|
|
@@ -75,13 +76,70 @@ export function lintResourceDefinition(def) {
|
|
|
75
76
|
}
|
|
76
77
|
else {
|
|
77
78
|
diagnostics.push(...checkFieldDescriptions(d.params, 'params', 'resource', displayName));
|
|
79
|
+
const paramsSerial = checkSchemaSerializable(d.params, 'params', 'resource', displayName);
|
|
80
|
+
if (paramsSerial)
|
|
81
|
+
diagnostics.push(paramsSerial);
|
|
82
|
+
// Cross-reference: template variables must match params schema keys
|
|
83
|
+
if (uriTemplate) {
|
|
84
|
+
diagnostics.push(...checkTemplateParamsAlignment(uriTemplate, d.params, displayName));
|
|
85
|
+
}
|
|
78
86
|
}
|
|
79
87
|
}
|
|
88
|
+
// Auth scopes validation
|
|
89
|
+
if (d?.auth !== undefined) {
|
|
90
|
+
diagnostics.push(...lintAuthScopes(d.auth, 'resource', displayName));
|
|
91
|
+
}
|
|
80
92
|
// Output schema (optional, but must be ZodObject when present)
|
|
81
93
|
if (d?.output !== undefined) {
|
|
82
94
|
const outputCheck = checkIsZodObject(d.output, 'output', 'resource', displayName);
|
|
83
|
-
if (outputCheck)
|
|
95
|
+
if (outputCheck) {
|
|
84
96
|
diagnostics.push(outputCheck);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const outputSerial = checkSchemaSerializable(d.output, 'output', 'resource', displayName);
|
|
100
|
+
if (outputSerial)
|
|
101
|
+
diagnostics.push(outputSerial);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return diagnostics;
|
|
105
|
+
}
|
|
106
|
+
/** Extracts variable names from an RFC 6570 URI template (strips operators like +, #, ?, &, etc.). */
|
|
107
|
+
function extractTemplateVariables(template) {
|
|
108
|
+
const vars = [];
|
|
109
|
+
for (const match of template.matchAll(/\{(?:[+#./;?&]?)([^}]+)\}/g)) {
|
|
110
|
+
// match[1] contains comma-separated variable names, each optionally with :maxLength or *
|
|
111
|
+
const varList = match[1] ?? '';
|
|
112
|
+
for (const part of varList.split(',')) {
|
|
113
|
+
const name = part.replace(/:[0-9]+$|\*$/g, '').trim();
|
|
114
|
+
if (name)
|
|
115
|
+
vars.push(name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return vars;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Checks that URI template variables align with params schema keys.
|
|
122
|
+
* A mismatch causes hard failures: the SDK extracts variables from the URI
|
|
123
|
+
* and passes them to the params schema for validation.
|
|
124
|
+
*/
|
|
125
|
+
function checkTemplateParamsAlignment(template, params, resourceName) {
|
|
126
|
+
const templateVars = extractTemplateVariables(template);
|
|
127
|
+
if (templateVars.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
const shape = params.shape;
|
|
130
|
+
const schemaKeys = new Set(Object.keys(shape));
|
|
131
|
+
const diagnostics = [];
|
|
132
|
+
for (const varName of templateVars) {
|
|
133
|
+
if (!schemaKeys.has(varName)) {
|
|
134
|
+
diagnostics.push({
|
|
135
|
+
rule: 'template-params-align',
|
|
136
|
+
severity: 'error',
|
|
137
|
+
message: `Resource '${resourceName}' URI template variable '{${varName}}' has no matching key in params schema. ` +
|
|
138
|
+
`Params schema has: [${[...schemaKeys].join(', ')}]. This will cause every resource read to fail.`,
|
|
139
|
+
definitionType: 'resource',
|
|
140
|
+
definitionName: resourceName,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
85
143
|
}
|
|
86
144
|
return diagnostics;
|
|
87
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/resource-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"resource-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/resource-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Lint rules for Zod schema validation: type checking, `.describe()` presence
|
|
2
|
+
* @fileoverview Lint rules for Zod schema validation: type checking, `.describe()` presence,
|
|
3
|
+
* and JSON Schema serializability.
|
|
3
4
|
* Covers MCP spec rules T3-T5 and framework convention for field descriptions.
|
|
4
5
|
* @module src/linter/rules/schema-rules
|
|
5
6
|
*/
|
|
@@ -14,4 +15,11 @@ export declare function checkIsZodObject(schema: unknown, fieldName: string, def
|
|
|
14
15
|
* Framework convention: all fields need `.describe()` for LLM discoverability.
|
|
15
16
|
*/
|
|
16
17
|
export declare function checkFieldDescriptions(schema: unknown, fieldName: string, definitionType: LintDiagnostic['definitionType'], definitionName: string): LintDiagnostic[];
|
|
18
|
+
/**
|
|
19
|
+
* Checks that a Zod schema can be converted to JSON Schema.
|
|
20
|
+
* The MCP SDK serializes schemas via `toJSONSchema()` when handling `tools/list`.
|
|
21
|
+
* Types like `z.custom()`, `z.date()`, `z.transform()`, etc. throw at serialization
|
|
22
|
+
* time, causing a hard runtime failure for any client that enumerates tools.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkSchemaSerializable(schema: unknown, fieldName: string, definitionType: LintDiagnostic['definitionType'], definitionName: string): LintDiagnostic | null;
|
|
17
25
|
//# sourceMappingURL=schema-rules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/schema-rules.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"schema-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/schema-rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAChD,cAAc,EAAE,MAAM,GACrB,cAAc,GAAG,IAAI,CAavB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAChD,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAwBlB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAChD,cAAc,EAAE,MAAM,GACrB,cAAc,GAAG,IAAI,CAkBvB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Lint rules for Zod schema validation: type checking, `.describe()` presence
|
|
2
|
+
* @fileoverview Lint rules for Zod schema validation: type checking, `.describe()` presence,
|
|
3
|
+
* and JSON Schema serializability.
|
|
3
4
|
* Covers MCP spec rules T3-T5 and framework convention for field descriptions.
|
|
4
5
|
* @module src/linter/rules/schema-rules
|
|
5
6
|
*/
|
|
7
|
+
import { toJSONSchema } from 'zod/v4/core';
|
|
6
8
|
/**
|
|
7
9
|
* Checks that a schema is a ZodObject (required for tool inputSchema).
|
|
8
10
|
* Spec: T3-T4 — inputSchema MUST be a JSON Schema object with type: "object".
|
|
@@ -45,6 +47,31 @@ export function checkFieldDescriptions(schema, fieldName, definitionType, defini
|
|
|
45
47
|
}
|
|
46
48
|
return diagnostics;
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks that a Zod schema can be converted to JSON Schema.
|
|
52
|
+
* The MCP SDK serializes schemas via `toJSONSchema()` when handling `tools/list`.
|
|
53
|
+
* Types like `z.custom()`, `z.date()`, `z.transform()`, etc. throw at serialization
|
|
54
|
+
* time, causing a hard runtime failure for any client that enumerates tools.
|
|
55
|
+
*/
|
|
56
|
+
export function checkSchemaSerializable(schema, fieldName, definitionType, definitionName) {
|
|
57
|
+
if (!isZodObject(schema))
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
toJSONSchema(schema);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const message = err instanceof Error ? err.message : 'Schema contains non-serializable types';
|
|
65
|
+
return {
|
|
66
|
+
rule: 'schema-serializable',
|
|
67
|
+
severity: 'error',
|
|
68
|
+
message: `${definitionType} '${definitionName}' ${fieldName} cannot be converted to JSON Schema: ${message}. ` +
|
|
69
|
+
'Replace non-serializable types (z.custom(), z.date(), z.transform(), z.bigint(), etc.) with structural Zod types.',
|
|
70
|
+
definitionType,
|
|
71
|
+
definitionName,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
48
75
|
/** Runtime check for ZodObject via Zod 4's `_zod.def.type`. */
|
|
49
76
|
function isZodObject(value) {
|
|
50
77
|
if (!value || typeof value !== 'object')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/schema-rules.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"schema-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/schema-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,4 +9,6 @@ import type { LintDiagnostic } from '../types.js';
|
|
|
9
9
|
* Accepts `unknown` to catch structural issues before type narrowing.
|
|
10
10
|
*/
|
|
11
11
|
export declare function lintToolDefinition(def: unknown): LintDiagnostic[];
|
|
12
|
+
/** Validates that auth scopes are well-formed (array of non-empty strings). */
|
|
13
|
+
export declare function lintAuthScopes(auth: unknown, definitionType: LintDiagnostic['definitionType'], definitionName: string): LintDiagnostic[];
|
|
12
14
|
//# sourceMappingURL=tool-rules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/tool-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/tool-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,EAAE,CAoEjE;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAChD,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA8BlB"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module src/linter/rules/tool-rules
|
|
5
5
|
*/
|
|
6
6
|
import { checkNameRequired, checkToolNameFormat } from './name-rules.js';
|
|
7
|
-
import { checkFieldDescriptions, checkIsZodObject } from './schema-rules.js';
|
|
7
|
+
import { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
|
|
8
8
|
/**
|
|
9
9
|
* Runs all lint rules against a single tool definition.
|
|
10
10
|
* Accepts `unknown` to catch structural issues before type narrowing.
|
|
@@ -43,21 +43,31 @@ export function lintToolDefinition(def) {
|
|
|
43
43
|
definitionName: displayName,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
// Input schema: must be ZodObject
|
|
46
|
+
// Input schema: must be ZodObject, serializable to JSON Schema
|
|
47
47
|
const inputCheck = checkIsZodObject(d?.input, 'input', 'tool', displayName);
|
|
48
48
|
if (inputCheck) {
|
|
49
49
|
diagnostics.push(inputCheck);
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
diagnostics.push(...checkFieldDescriptions(d?.input, 'input', 'tool', displayName));
|
|
53
|
+
const inputSerial = checkSchemaSerializable(d?.input, 'input', 'tool', displayName);
|
|
54
|
+
if (inputSerial)
|
|
55
|
+
diagnostics.push(inputSerial);
|
|
53
56
|
}
|
|
54
|
-
// Output schema: must be ZodObject
|
|
57
|
+
// Output schema: must be ZodObject, serializable to JSON Schema
|
|
55
58
|
const outputCheck = checkIsZodObject(d?.output, 'output', 'tool', displayName);
|
|
56
59
|
if (outputCheck) {
|
|
57
60
|
diagnostics.push(outputCheck);
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
63
|
diagnostics.push(...checkFieldDescriptions(d?.output, 'output', 'tool', displayName));
|
|
64
|
+
const outputSerial = checkSchemaSerializable(d?.output, 'output', 'tool', displayName);
|
|
65
|
+
if (outputSerial)
|
|
66
|
+
diagnostics.push(outputSerial);
|
|
67
|
+
}
|
|
68
|
+
// Auth scopes validation
|
|
69
|
+
if (d?.auth !== undefined) {
|
|
70
|
+
diagnostics.push(...lintAuthScopes(d.auth, 'tool', displayName));
|
|
61
71
|
}
|
|
62
72
|
// Annotations validation
|
|
63
73
|
if (d?.annotations && typeof d.annotations === 'object') {
|
|
@@ -65,6 +75,34 @@ export function lintToolDefinition(def) {
|
|
|
65
75
|
}
|
|
66
76
|
return diagnostics;
|
|
67
77
|
}
|
|
78
|
+
/** Validates that auth scopes are well-formed (array of non-empty strings). */
|
|
79
|
+
export function lintAuthScopes(auth, definitionType, definitionName) {
|
|
80
|
+
const diagnostics = [];
|
|
81
|
+
if (!Array.isArray(auth)) {
|
|
82
|
+
diagnostics.push({
|
|
83
|
+
rule: 'auth-type',
|
|
84
|
+
severity: 'error',
|
|
85
|
+
message: `${definitionType} '${definitionName}' auth must be an array of scope strings.`,
|
|
86
|
+
definitionType,
|
|
87
|
+
definitionName,
|
|
88
|
+
});
|
|
89
|
+
return diagnostics;
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < auth.length; i++) {
|
|
92
|
+
const scope = auth[i];
|
|
93
|
+
if (typeof scope !== 'string' || scope.trim().length === 0) {
|
|
94
|
+
diagnostics.push({
|
|
95
|
+
rule: 'auth-scope-format',
|
|
96
|
+
severity: 'error',
|
|
97
|
+
message: `${definitionType} '${definitionName}' auth[${i}] must be a non-empty string, ` +
|
|
98
|
+
`got ${typeof scope === 'string' ? 'empty string' : typeof scope}.`,
|
|
99
|
+
definitionType,
|
|
100
|
+
definitionName,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return diagnostics;
|
|
105
|
+
}
|
|
68
106
|
/** Validates that annotation hint values are booleans where expected. */
|
|
69
107
|
function lintToolAnnotations(annotations, toolName) {
|
|
70
108
|
const diagnostics = [];
|
|
@@ -85,6 +123,29 @@ function lintToolAnnotations(annotations, toolName) {
|
|
|
85
123
|
});
|
|
86
124
|
}
|
|
87
125
|
}
|
|
126
|
+
// Semantic coherence: destructiveHint and idempotentHint are meaningless when readOnlyHint is true
|
|
127
|
+
if (annotations.readOnlyHint === true) {
|
|
128
|
+
if ('destructiveHint' in annotations) {
|
|
129
|
+
diagnostics.push({
|
|
130
|
+
rule: 'annotation-coherence',
|
|
131
|
+
severity: 'warning',
|
|
132
|
+
message: `Tool '${toolName}' sets destructiveHint while readOnlyHint is true. ` +
|
|
133
|
+
'destructiveHint is meaningless for read-only tools.',
|
|
134
|
+
definitionType: 'tool',
|
|
135
|
+
definitionName: toolName,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if ('idempotentHint' in annotations) {
|
|
139
|
+
diagnostics.push({
|
|
140
|
+
rule: 'annotation-coherence',
|
|
141
|
+
severity: 'warning',
|
|
142
|
+
message: `Tool '${toolName}' sets idempotentHint while readOnlyHint is true. ` +
|
|
143
|
+
'Read-only tools are inherently idempotent — this hint is redundant.',
|
|
144
|
+
definitionType: 'tool',
|
|
145
|
+
definitionName: toolName,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
88
149
|
return diagnostics;
|
|
89
150
|
}
|
|
90
151
|
//# sourceMappingURL=tool-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/tool-rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,
|
|
1
|
+
{"version":3,"file":"tool-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/tool-rules.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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",
|
|
@@ -175,6 +175,7 @@
|
|
|
175
175
|
"depcheck": "^1.4.7",
|
|
176
176
|
"execa": "^9.6.1",
|
|
177
177
|
"fast-check": "^4.6.0",
|
|
178
|
+
"js-yaml": "^4.1.0",
|
|
178
179
|
"ignore": "^7.0.5",
|
|
179
180
|
"msw": "^2.12.14",
|
|
180
181
|
"node-cron": "^4.2.1",
|
|
@@ -268,7 +269,7 @@
|
|
|
268
269
|
"chrono-node": "^2.9.0",
|
|
269
270
|
"diff": "^8.0.3",
|
|
270
271
|
"fast-xml-parser": "^5.5.8",
|
|
271
|
-
"js-yaml": "^
|
|
272
|
+
"js-yaml": "^4.1.0",
|
|
272
273
|
"node-cron": "^4.2.1",
|
|
273
274
|
"openai": "^6.32.0",
|
|
274
275
|
"papaparse": "^5.5.3",
|
package/scripts/lint-mcp.ts
CHANGED
|
@@ -42,7 +42,9 @@ try {
|
|
|
42
42
|
function isToolLike(v: unknown): boolean {
|
|
43
43
|
if (!v || typeof v !== 'object') return false;
|
|
44
44
|
const o = v as Record<string, unknown>;
|
|
45
|
-
|
|
45
|
+
const hasHandler = typeof o.handler === 'function';
|
|
46
|
+
const hasTaskHandlers = o.taskHandlers != null && typeof o.taskHandlers === 'object';
|
|
47
|
+
return (hasHandler || hasTaskHandlers) && o.input != null && o.output != null;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function isResourceLike(v: unknown): boolean {
|
package/skills/add-tool/SKILL.md
CHANGED
|
@@ -42,10 +42,10 @@ export const {{TOOL_EXPORT}} = tool('{{tool_name}}', {
|
|
|
42
42
|
description: '{{TOOL_DESCRIPTION}}',
|
|
43
43
|
annotations: { readOnlyHint: true },
|
|
44
44
|
input: z.object({
|
|
45
|
-
// All fields need .describe()
|
|
45
|
+
// All fields need .describe(). Only JSON-Schema-serializable Zod types allowed.
|
|
46
46
|
}),
|
|
47
47
|
output: z.object({
|
|
48
|
-
// All fields need .describe()
|
|
48
|
+
// All fields need .describe(). Only JSON-Schema-serializable Zod types allowed.
|
|
49
49
|
}),
|
|
50
50
|
// auth: ['tool:{{tool_name}}:read'],
|
|
51
51
|
|
|
@@ -100,6 +100,7 @@ export const allToolDefinitions = [
|
|
|
100
100
|
|
|
101
101
|
- [ ] File created at `src/mcp-server/tools/definitions/{{tool-name}}.tool.ts`
|
|
102
102
|
- [ ] All Zod schema fields have `.describe()` annotations
|
|
103
|
+
- [ ] Schemas use only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`)
|
|
103
104
|
- [ ] JSDoc `@fileoverview` and `@module` header present
|
|
104
105
|
- [ ] `handler(input, ctx)` is pure — throws on failure, no try/catch
|
|
105
106
|
- [ ] `auth` scopes declared if the tool needs authorization
|
|
@@ -147,6 +147,7 @@ Context-dependent: a simple read-only tool needs a one-line description. A tool
|
|
|
147
147
|
Every `.describe()` is prompt text the LLM reads. Parameters should convey: what the value is, what it affects, and (where non-obvious) how to use it well.
|
|
148
148
|
|
|
149
149
|
- **Constrain the type.** Enums and literals over free strings. Regex validation for formatted IDs. Ranges for numeric bounds.
|
|
150
|
+
- **Use JSON-Schema-serializable types only.** The MCP SDK serializes schemas to JSON Schema for `tools/list`. Types like `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()` throw at runtime. Use structural equivalents (e.g., `z.string().describe('ISO 8601 date')` instead of `z.date()`).
|
|
150
151
|
- **Explain costs and tradeoffs** when a parameter choice has meaningful consequences.
|
|
151
152
|
- **Name alternative approaches** when a simpler path exists.
|
|
152
153
|
- **Include format patterns** for structured values, but don't pad descriptions with redundant examples.
|
|
@@ -89,7 +89,7 @@ Cross-cutting observations that aren't tied to a single definition:
|
|
|
89
89
|
- Inconsistent error message patterns across tools
|
|
90
90
|
- Missing format functions (raw JSON returned to user)
|
|
91
91
|
- Description quality issues (vague, missing, or misleading)
|
|
92
|
-
- Schema design issues (required fields that should be optional, missing defaults, overly broad types)
|
|
92
|
+
- Schema design issues (required fields that should be optional, missing defaults, overly broad types, non-JSON-Schema-serializable types like `z.custom()` or `z.date()`)
|
|
93
93
|
- Performance observations (unexpectedly slow responses)
|
|
94
94
|
|
|
95
95
|
---
|
|
@@ -27,17 +27,18 @@ Badges row ← npm, Docker, Version, MCP Spec, SDK,
|
|
|
27
27
|
|
|
28
28
|
### Title Block
|
|
29
29
|
|
|
30
|
-
Centered HTML. The `<h1>` is the server name
|
|
30
|
+
Centered HTML. The `<h1>` is the server name — use the scoped package name if published under a scope (e.g., `@cyanheads/my-mcp-server`). The `<p>` is a bold one-liner: what the server wraps, key capabilities, transport/deployment options. Follow with a count line summarizing the MCP surface (tools, resources, prompts) separated by ` · `, then a badge row.
|
|
31
31
|
|
|
32
32
|
```html
|
|
33
33
|
<div align="center">
|
|
34
|
-
<h1
|
|
35
|
-
<p><b>MCP server for the Acme API. Search projects, manage tasks, track teams.
|
|
34
|
+
<h1>@cyanheads/my-mcp-server</h1>
|
|
35
|
+
<p><b>MCP server for the Acme API. Search projects, manage tasks, track teams. STDIO & Streamable HTTP</b></p>
|
|
36
|
+
<p><b>7 Tools · 2 Resources · 1 Prompt</b></p>
|
|
36
37
|
</div>
|
|
37
38
|
|
|
38
39
|
<div align="center">
|
|
39
40
|
|
|
40
|
-
[](https://www.npmjs.com/package/my-mcp-server) [](./CHANGELOG.md) [](https://modelcontextprotocol.io/) [](./LICENSE) [](https://www.typescriptlang.org/)
|
|
41
|
+
[](https://www.npmjs.com/package/my-mcp-server) [](./CHANGELOG.md) [](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) [](https://modelcontextprotocol.io/) [](./LICENSE) [](https://www.typescriptlang.org/)
|
|
41
42
|
|
|
42
43
|
</div>
|
|
43
44
|
```
|
|
@@ -49,6 +50,7 @@ Centered HTML. The `<h1>` is the server name (package name). The `<p>` is a bold
|
|
|
49
50
|
| npm | Published to npm |
|
|
50
51
|
| Docker | Published to ghcr.io or Docker Hub |
|
|
51
52
|
| Version | Always — link to CHANGELOG.md |
|
|
53
|
+
| Framework | Always — links to `@cyanheads/mcp-ts-core` on npm |
|
|
52
54
|
| MCP Spec | Always — link to the spec version implemented |
|
|
53
55
|
| MCP SDK | Always — show the `@modelcontextprotocol/sdk` version |
|
|
54
56
|
| License | Always |
|
package/templates/AGENTS.md
CHANGED
|
@@ -273,7 +273,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
273
273
|
|
|
274
274
|
## Checklist
|
|
275
275
|
|
|
276
|
-
- [ ] Zod schemas: all fields have `.describe()`
|
|
276
|
+
- [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, etc.)
|
|
277
277
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
278
278
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
279
279
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
package/templates/CLAUDE.md
CHANGED
|
@@ -273,7 +273,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
273
273
|
|
|
274
274
|
## Checklist
|
|
275
275
|
|
|
276
|
-
- [ ] Zod schemas: all fields have `.describe()`
|
|
276
|
+
- [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, etc.)
|
|
277
277
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
278
278
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
279
279
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# OPERATING SYSTEM FILES
|
|
3
|
+
# =============================================================================
|
|
4
|
+
.DS_Store
|
|
5
|
+
.DS_Store?
|
|
6
|
+
._*
|
|
7
|
+
.Spotlight-V100
|
|
8
|
+
.Trashes
|
|
9
|
+
ehthumbs.db
|
|
10
|
+
Thumbs.db
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# VERSION CONTROL
|
|
14
|
+
# =============================================================================
|
|
15
|
+
.git/
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# IDE AND EDITOR FILES
|
|
19
|
+
# =============================================================================
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
*~
|
|
25
|
+
*.sublime-workspace
|
|
26
|
+
*.sublime-project
|
|
27
|
+
.history/
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# NODE.JS & PACKAGE MANAGERS
|
|
31
|
+
# =============================================================================
|
|
32
|
+
node_modules/
|
|
33
|
+
npm-debug.log*
|
|
34
|
+
yarn-debug.log*
|
|
35
|
+
yarn-error.log*
|
|
36
|
+
.pnpm-debug.log*
|
|
37
|
+
.npm
|
|
38
|
+
.pnp.js
|
|
39
|
+
.pnp.cjs
|
|
40
|
+
.pnp.mjs
|
|
41
|
+
.pnp.json
|
|
42
|
+
.pnp.ts
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# TYPESCRIPT & JAVASCRIPT
|
|
46
|
+
# =============================================================================
|
|
47
|
+
*.tsbuildinfo
|
|
48
|
+
.tscache/
|
|
49
|
+
*.js.map
|
|
50
|
+
*.mjs.map
|
|
51
|
+
*.cjs.map
|
|
52
|
+
*.d.ts.map
|
|
53
|
+
*.d.ts
|
|
54
|
+
!*.d.ts.template
|
|
55
|
+
*.tgz
|
|
56
|
+
.eslintcache
|
|
57
|
+
.rollup.cache
|
|
58
|
+
|
|
59
|
+
# =============================================================================
|
|
60
|
+
# PYTHON
|
|
61
|
+
# =============================================================================
|
|
62
|
+
__pycache__/
|
|
63
|
+
*.py[cod]
|
|
64
|
+
*$py.class
|
|
65
|
+
*.so
|
|
66
|
+
.Python
|
|
67
|
+
develop-eggs/
|
|
68
|
+
eggs/
|
|
69
|
+
.eggs/
|
|
70
|
+
lib/
|
|
71
|
+
lib64/
|
|
72
|
+
parts/
|
|
73
|
+
sdist/
|
|
74
|
+
var/
|
|
75
|
+
wheels/
|
|
76
|
+
*.egg-info/
|
|
77
|
+
.installed.cfg
|
|
78
|
+
*.egg
|
|
79
|
+
.pytest_cache/
|
|
80
|
+
.coverage
|
|
81
|
+
htmlcov/
|
|
82
|
+
.tox/
|
|
83
|
+
.venv
|
|
84
|
+
venv/
|
|
85
|
+
ENV/
|
|
86
|
+
|
|
87
|
+
# =============================================================================
|
|
88
|
+
# JAVA
|
|
89
|
+
# =============================================================================
|
|
90
|
+
*.class
|
|
91
|
+
*.jar
|
|
92
|
+
*.war
|
|
93
|
+
*.nar
|
|
94
|
+
*.ear
|
|
95
|
+
hs_err_pid*
|
|
96
|
+
target/
|
|
97
|
+
.gradle/
|
|
98
|
+
|
|
99
|
+
# =============================================================================
|
|
100
|
+
# RUBY
|
|
101
|
+
# =============================================================================
|
|
102
|
+
*.gem
|
|
103
|
+
*.rbc
|
|
104
|
+
/.config
|
|
105
|
+
/coverage/
|
|
106
|
+
/InstalledFiles
|
|
107
|
+
/pkg/
|
|
108
|
+
/spec/reports/
|
|
109
|
+
/spec/examples.txt
|
|
110
|
+
/test/tmp/
|
|
111
|
+
/test/version_tmp/
|
|
112
|
+
/tmp/
|
|
113
|
+
.byebug_history
|
|
114
|
+
|
|
115
|
+
# =============================================================================
|
|
116
|
+
# BUILD & DISTRIBUTION
|
|
117
|
+
# =============================================================================
|
|
118
|
+
build/
|
|
119
|
+
dist/
|
|
120
|
+
out/
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# COMPILED FILES
|
|
124
|
+
# =============================================================================
|
|
125
|
+
*.com
|
|
126
|
+
*.dll
|
|
127
|
+
*.exe
|
|
128
|
+
*.o
|
|
129
|
+
|
|
130
|
+
# =============================================================================
|
|
131
|
+
# PACKAGE & ARCHIVE FILES
|
|
132
|
+
# =============================================================================
|
|
133
|
+
*.7z
|
|
134
|
+
*.dmg
|
|
135
|
+
*.gz
|
|
136
|
+
*.iso
|
|
137
|
+
*.rar
|
|
138
|
+
*.tar
|
|
139
|
+
*.tar.gz
|
|
140
|
+
*.zip
|
|
141
|
+
|
|
142
|
+
# =============================================================================
|
|
143
|
+
# LOGS & DATABASES
|
|
144
|
+
# =============================================================================
|
|
145
|
+
*.log
|
|
146
|
+
*.sql
|
|
147
|
+
*.sqlite
|
|
148
|
+
*.sqlite3
|
|
149
|
+
logs/
|
|
150
|
+
|
|
151
|
+
# =============================================================================
|
|
152
|
+
# TESTING & COVERAGE
|
|
153
|
+
# =============================================================================
|
|
154
|
+
coverage/
|
|
155
|
+
.nyc_output/
|
|
156
|
+
|
|
157
|
+
# =============================================================================
|
|
158
|
+
# CACHE & TEMPORARY FILES
|
|
159
|
+
# =============================================================================
|
|
160
|
+
.cache/
|
|
161
|
+
.parcel-cache/
|
|
162
|
+
*.bak
|
|
163
|
+
|
|
164
|
+
# =============================================================================
|
|
165
|
+
# ENVIRONMENT & CONFIGURATION
|
|
166
|
+
# =============================================================================
|
|
167
|
+
.env
|
|
168
|
+
.env.local
|
|
169
|
+
.env.development.local
|
|
170
|
+
.env.test.local
|
|
171
|
+
.env.production.local
|
|
172
|
+
.sample-env
|
|
173
|
+
sample.*
|
|
174
|
+
!sample.template.*
|
|
175
|
+
mcp-servers.json
|
|
176
|
+
mcp-config.json
|
|
177
|
+
|
|
178
|
+
# =============================================================================
|
|
179
|
+
# DEMO & EXAMPLE DIRECTORIES
|
|
180
|
+
# =============================================================================
|
|
181
|
+
demo/
|
|
182
|
+
demos/
|
|
183
|
+
example/
|
|
184
|
+
examples/
|
|
185
|
+
samples/
|
|
186
|
+
|
|
187
|
+
# =============================================================================
|
|
188
|
+
# GENERATED DOCUMENTATION
|
|
189
|
+
# =============================================================================
|
|
190
|
+
docs/api/
|
|
191
|
+
|
|
192
|
+
# =============================================================================
|
|
193
|
+
# APPLICATION SPECIFIC
|
|
194
|
+
# =============================================================================
|
|
195
|
+
repomix-output*
|
|
196
|
+
duckdata/
|
|
197
|
+
.claude
|
package/templates/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"format": "biome check --write --unsafe .",
|
|
27
27
|
"lint:mcp": "tsx scripts/lint-mcp.ts",
|
|
28
28
|
"test": "vitest run",
|
|
29
|
-
"dev:stdio": "tsx --watch src/index.ts",
|
|
29
|
+
"dev:stdio": "MCP_TRANSPORT_TYPE=stdio tsx --watch src/index.ts",
|
|
30
30
|
"dev:http": "MCP_TRANSPORT_TYPE=http tsx --watch src/index.ts",
|
|
31
|
-
"start:stdio": "node dist/index.js",
|
|
31
|
+
"start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
|
|
32
32
|
"start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the echo prompt.
|
|
3
|
+
* @module tests/prompts/echo.prompt.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { echoPrompt } from '@/mcp-server/prompts/definitions/echo.prompt.js';
|
|
8
|
+
|
|
9
|
+
describe('echoPrompt', () => {
|
|
10
|
+
it('generates a user message with the echoed text', () => {
|
|
11
|
+
const args = echoPrompt.args.parse({ message: 'hello world' });
|
|
12
|
+
const messages = echoPrompt.generate(args);
|
|
13
|
+
expect(messages).toHaveLength(1);
|
|
14
|
+
expect(messages[0]).toMatchObject({
|
|
15
|
+
role: 'user',
|
|
16
|
+
content: { type: 'text', text: 'Echo: hello world' },
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the echo resource.
|
|
3
|
+
* @module tests/resources/echo.resource.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
8
|
+
import { echoResource } from '@/mcp-server/resources/definitions/echo.resource.js';
|
|
9
|
+
|
|
10
|
+
describe('echoResource', () => {
|
|
11
|
+
it('echoes the message from params', async () => {
|
|
12
|
+
const ctx = createMockContext();
|
|
13
|
+
const params = echoResource.params.parse({ message: 'hello world' });
|
|
14
|
+
const result = await echoResource.handler(params, ctx);
|
|
15
|
+
expect(result).toEqual({ message: 'hello world' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('lists available resources', () => {
|
|
19
|
+
const listing = echoResource.list!();
|
|
20
|
+
expect(listing.resources).toHaveLength(1);
|
|
21
|
+
expect(listing.resources[0]).toMatchObject({
|
|
22
|
+
uri: 'echo://hello',
|
|
23
|
+
name: 'Echo Hello',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the echo tool.
|
|
3
|
+
* @module tests/tools/echo.tool.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
8
|
+
import { echoTool } from '@/mcp-server/tools/definitions/echo.tool.js';
|
|
9
|
+
|
|
10
|
+
describe('echoTool', () => {
|
|
11
|
+
it('echoes the message back', async () => {
|
|
12
|
+
const ctx = createMockContext();
|
|
13
|
+
const input = echoTool.input.parse({ message: 'hello world' });
|
|
14
|
+
const result = await echoTool.handler(input, ctx);
|
|
15
|
+
expect(result).toEqual({ message: 'hello world' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('formats output as text content', () => {
|
|
19
|
+
const blocks = echoTool.format!({ message: 'hello world' });
|
|
20
|
+
expect(blocks).toEqual([{ type: 'text', text: 'hello world' }]);
|
|
21
|
+
});
|
|
22
|
+
});
|