@cyanheads/mcp-ts-core 0.7.1 → 0.7.2
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 +1 -1
- package/README.md +1 -1
- package/changelog/0.7.x/0.7.2.md +16 -0
- package/dist/logs/combined.log +4 -4
- package/dist/logs/error.log +4 -4
- package/package.json +4 -3
- package/scripts/check-framework-antipatterns.ts +117 -0
- package/scripts/devcheck.ts +8 -0
- package/{vitest.config.base.ts → vitest.config.base.mjs} +3 -0
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
|
-
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.7.
|
|
3
|
+
**Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.7.2
|
|
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.
|
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
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: Ship vitest.config subpath export as .mjs (fixes Node 22.7+ type-strip failure under node_modules); new devcheck step guards against SDK-coupling antipatterns
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.7.2 — 2026-04-24
|
|
7
|
+
|
|
8
|
+
Two small, unrelated preventive changes. The vitest config fix unblocks consumer test runs on Node 22.7+ without `NODE_OPTIONS=--no-experimental-strip-types`. The new devcheck rule pins architectural decisions about how the framework must not work around SDK internals.
|
|
9
|
+
|
|
10
|
+
## Fixed
|
|
11
|
+
|
|
12
|
+
- **`./vitest.config` subpath export is now `.mjs`, not `.ts` ([#64](https://github.com/cyanheads/mcp-ts-core/issues/64))** — shipping raw TypeScript under `node_modules/` triggers `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` on Node ≥22.7 when the module loader reaches the file before Vite's config handler. Node's type stripper refuses to operate inside `node_modules/` on purpose (supply-chain hygiene), so the failure was correct and the export was wrong. The source file moves from `vitest.config.base.ts` to `vitest.config.base.mjs`; consumers importing via `@cyanheads/mcp-ts-core/vitest.config` see no API change. Removes the need for `NODE_OPTIONS=--no-experimental-strip-types` in consumer test scripts.
|
|
13
|
+
|
|
14
|
+
## Added
|
|
15
|
+
|
|
16
|
+
- **`Framework Antipatterns` devcheck step ([#68](https://github.com/cyanheads/mcp-ts-core/issues/68))** — new source-hygiene guard at `scripts/check-framework-antipatterns.ts`, wired into devcheck alongside `MCP Definitions` and `Docs Sync`. Three rules scan `src/` for SDK-coupling shortcuts tempting to reach for when working around MCP SDK error shapes: (1) downgraded `inputSchema` (`z.unknown()`, `z.any()`, `.passthrough()`) passed to `server.registerTool()` in framework-side tool registration; (2) post-register mutation of `RegisteredTool.inputSchema`; (3) transport-layer regex/string matching on the SDK's `"Input validation error"` wording. Each breaks `tools/list` schema advertising or couples the framework to SDK internals. Rules land with zero current violations — pre-emptive hygiene ahead of [#66](https://github.com/cyanheads/mcp-ts-core/issues/66) implementation.
|
package/dist/logs/combined.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"level":50,"time":
|
|
2
|
-
{"level":50,"time":
|
|
3
|
-
{"level":50,"time":
|
|
4
|
-
{"level":50,"time":
|
|
1
|
+
{"level":50,"time":1777068909278,"env":"testing","version":"0.0.0-test","pid":51484,"requestId":"31H8Z-66JNN","timestamp":"2026-04-24T22:15:09.277Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d52f5c4c46a4e6f84b3af32b0c91399366a38fb240fc5d7e9994b1cb11d17878","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"d52f5c4c46a4e6f84b3af32b0c91399366a38fb240fc5d7e9994b1cb11d17878","toolName":"scoped_echo","requestId":"31H8Z-66JNN","timestamp":"2026-04-24T22:15:09.277Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:105:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
|
|
2
|
+
{"level":50,"time":1777068910260,"env":"testing","version":"0.7.2","pid":51491,"requestId":"K4C6A-92U2J","timestamp":"2026-04-24T22:15:10.260Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"K4C6A-92U2J","timestamp":"2026-04-24T22:15:10.260Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
|
|
3
|
+
{"level":50,"time":1777068910274,"env":"testing","version":"0.7.2","pid":51491,"requestId":"BV9TG-ATSIH","timestamp":"2026-04-24T22:15:10.274Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"BV9TG-ATSIH","timestamp":"2026-04-24T22:15:10.274Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
|
|
4
|
+
{"level":50,"time":1777068910278,"env":"testing","version":"0.7.2","pid":51491,"requestId":"OKRWX-57WIU","timestamp":"2026-04-24T22:15:10.278Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"OKRWX-57WIU","timestamp":"2026-04-24T22:15:10.278Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
|
package/dist/logs/error.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"level":50,"time":
|
|
2
|
-
{"level":50,"time":
|
|
3
|
-
{"level":50,"time":
|
|
4
|
-
{"level":50,"time":
|
|
1
|
+
{"level":50,"time":1777068909278,"env":"testing","version":"0.0.0-test","pid":51484,"requestId":"31H8Z-66JNN","timestamp":"2026-04-24T22:15:09.277Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d52f5c4c46a4e6f84b3af32b0c91399366a38fb240fc5d7e9994b1cb11d17878","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"d52f5c4c46a4e6f84b3af32b0c91399366a38fb240fc5d7e9994b1cb11d17878","toolName":"scoped_echo","requestId":"31H8Z-66JNN","timestamp":"2026-04-24T22:15:09.277Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:105:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
|
|
2
|
+
{"level":50,"time":1777068910260,"env":"testing","version":"0.7.2","pid":51491,"requestId":"K4C6A-92U2J","timestamp":"2026-04-24T22:15:10.260Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"K4C6A-92U2J","timestamp":"2026-04-24T22:15:10.260Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
|
|
3
|
+
{"level":50,"time":1777068910274,"env":"testing","version":"0.7.2","pid":51491,"requestId":"BV9TG-ATSIH","timestamp":"2026-04-24T22:15:10.274Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"BV9TG-ATSIH","timestamp":"2026-04-24T22:15:10.274Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
|
|
4
|
+
{"level":50,"time":1777068910278,"env":"testing","version":"0.7.2","pid":51491,"requestId":"OKRWX-57WIU","timestamp":"2026-04-24T22:15:10.278Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"OKRWX-57WIU","timestamp":"2026-04-24T22:15:10.278Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts/build-changelog.ts",
|
|
12
12
|
"scripts/build.ts",
|
|
13
13
|
"scripts/check-docs-sync.ts",
|
|
14
|
+
"scripts/check-framework-antipatterns.ts",
|
|
14
15
|
"scripts/check-skills-sync.ts",
|
|
15
16
|
"scripts/clean.ts",
|
|
16
17
|
"scripts/devcheck.ts",
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
"templates/",
|
|
21
22
|
"CLAUDE.md",
|
|
22
23
|
"tsconfig.base.json",
|
|
23
|
-
"vitest.config.base.
|
|
24
|
+
"vitest.config.base.mjs",
|
|
24
25
|
"biome.json"
|
|
25
26
|
],
|
|
26
27
|
"bin": {
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
"import": "./dist/testing/fuzz.js"
|
|
93
94
|
},
|
|
94
95
|
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
95
|
-
"./vitest.config": "./vitest.config.base.
|
|
96
|
+
"./vitest.config": "./vitest.config.base.mjs",
|
|
96
97
|
"./biome": "./biome.json",
|
|
97
98
|
"./package.json": "./package.json"
|
|
98
99
|
},
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Guards framework source against three SDK-coupling antipatterns
|
|
4
|
+
* tempting to reach for when fixing tool input-validation error shape (#66) or
|
|
5
|
+
* similar SDK-adjacent bugs. Each rule is narrow and mechanical — the goal is to
|
|
6
|
+
* pin an architectural decision so it does not have to be re-litigated per PR.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* 1. Framework must not downgrade the Zod `inputSchema` passed to
|
|
10
|
+
* `server.registerTool()` — the SDK rederives JSON Schema from it for
|
|
11
|
+
* `tools/list`, so `z.unknown()` / `z.any()` / `.passthrough()` breaks
|
|
12
|
+
* schema advertising. Consumer-side `.passthrough()` on output schemas
|
|
13
|
+
* is a documented escape hatch and stays legal (rule is scoped to
|
|
14
|
+
* `src/mcp-server/tools/`).
|
|
15
|
+
* 2. Mutating `RegisteredTool.inputSchema` after register breaks
|
|
16
|
+
* `tools/list` the same way — the SDK reads the stored Zod object at list
|
|
17
|
+
* time.
|
|
18
|
+
* 3. Matching the MCP SDK's error text at the transport layer (e.g. regex on
|
|
19
|
+
* `"Input validation error"`) is brittle across SDK versions. Any fix for
|
|
20
|
+
* #66 that intervenes at transport should use a structural signal, not a
|
|
21
|
+
* string match.
|
|
22
|
+
*
|
|
23
|
+
* Runs standalone (`bun run scripts/check-framework-antipatterns.ts`) and as
|
|
24
|
+
* a devcheck step.
|
|
25
|
+
*
|
|
26
|
+
* @module scripts/check-framework-antipatterns
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawnSync } from 'node:child_process';
|
|
30
|
+
import process from 'node:process';
|
|
31
|
+
|
|
32
|
+
interface Rule {
|
|
33
|
+
id: string;
|
|
34
|
+
/** Human-readable message printed when the rule fires. */
|
|
35
|
+
message: string;
|
|
36
|
+
/** Pathspecs to scan. Exclusions use `:!` prefix. */
|
|
37
|
+
pathspec: string[];
|
|
38
|
+
/** Extended regex (POSIX ERE) passed to `git grep -E`. */
|
|
39
|
+
pattern: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const RULES: Rule[] = [
|
|
43
|
+
{
|
|
44
|
+
id: 'inputSchema-downgrade',
|
|
45
|
+
pattern: 'inputSchema:\\s*z\\.(unknown|any)\\(\\)|inputSchema:[^,]*\\.passthrough\\(\\)',
|
|
46
|
+
pathspec: ['src/mcp-server/tools/', ':!**/*.test.ts'],
|
|
47
|
+
message: 'Framework must not downgrade tool inputSchema — breaks tools/list advertising',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'inputSchema-mutation',
|
|
51
|
+
pattern: '\\.inputSchema\\s*=([^=]|$)',
|
|
52
|
+
pathspec: ['src/', ':!src/linter/', ':!**/*.test.ts'],
|
|
53
|
+
message: 'Post-register inputSchema mutation breaks tools/list advertising',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'transport-text-match',
|
|
57
|
+
pattern: '[\'"`]Input validation error',
|
|
58
|
+
pathspec: ['src/mcp-server/transports/'],
|
|
59
|
+
message: 'Matching SDK error text in transport layer is brittle across SDK versions',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
interface Finding {
|
|
64
|
+
file: string;
|
|
65
|
+
line: string;
|
|
66
|
+
lineNo: number;
|
|
67
|
+
ruleId: string;
|
|
68
|
+
ruleMessage: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function runRule(rule: Rule): Finding[] {
|
|
72
|
+
const result = spawnSync('git', ['grep', '-nE', rule.pattern, '--', ...rule.pathspec], {
|
|
73
|
+
encoding: 'utf-8',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// git grep: exit 0 = matches, exit 1 = no matches, exit >=2 = error
|
|
77
|
+
if (result.status === 1) return [];
|
|
78
|
+
if (result.status !== 0) {
|
|
79
|
+
console.error(`git grep failed for rule '${rule.id}':`);
|
|
80
|
+
console.error(result.stderr);
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result.stdout
|
|
85
|
+
.trim()
|
|
86
|
+
.split('\n')
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.map((raw) => {
|
|
89
|
+
// format: `path:line:content`
|
|
90
|
+
const firstColon = raw.indexOf(':');
|
|
91
|
+
const secondColon = raw.indexOf(':', firstColon + 1);
|
|
92
|
+
const file = raw.slice(0, firstColon);
|
|
93
|
+
const lineNo = Number(raw.slice(firstColon + 1, secondColon));
|
|
94
|
+
const line = raw.slice(secondColon + 1);
|
|
95
|
+
return { file, lineNo, line, ruleId: rule.id, ruleMessage: rule.message };
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const findings = RULES.flatMap(runRule);
|
|
100
|
+
|
|
101
|
+
if (findings.length === 0) {
|
|
102
|
+
console.log(`No framework antipatterns found (${RULES.length} rule(s) scanned).`);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.error(`Found ${findings.length} framework antipattern violation(s):`);
|
|
107
|
+
console.error('');
|
|
108
|
+
for (const f of findings) {
|
|
109
|
+
console.error(` ${f.file}:${f.lineNo} [${f.ruleId}]`);
|
|
110
|
+
console.error(` ${f.ruleMessage}`);
|
|
111
|
+
console.error(` ${f.line.trim()}`);
|
|
112
|
+
console.error('');
|
|
113
|
+
}
|
|
114
|
+
console.error(
|
|
115
|
+
'See skills/api-linter/SKILL.md or scripts/check-framework-antipatterns.ts for rule rationale.',
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
package/scripts/devcheck.ts
CHANGED
|
@@ -416,6 +416,14 @@ const ALL_CHECKS: Check[] = [
|
|
|
416
416
|
tip: (c) =>
|
|
417
417
|
`Fix definition errors above — each diagnostic links to its rule in ${c.bold('skills/api-linter/SKILL.md')}.`,
|
|
418
418
|
},
|
|
419
|
+
{
|
|
420
|
+
name: 'Framework Antipatterns',
|
|
421
|
+
flag: '--no-framework-antipatterns',
|
|
422
|
+
canFix: false,
|
|
423
|
+
getCommand: () => ['bun', 'run', 'scripts/check-framework-antipatterns.ts'],
|
|
424
|
+
tip: (c) =>
|
|
425
|
+
`Remove the flagged SDK-coupling shortcut. See ${c.bold('scripts/check-framework-antipatterns.ts')} for rule rationale.`,
|
|
426
|
+
},
|
|
419
427
|
{
|
|
420
428
|
name: 'Docs Sync',
|
|
421
429
|
flag: '--no-docs-sync',
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Base Vitest configuration for consumer servers.
|
|
3
|
+
* Shipped as `.mjs` (not `.ts`) so Node ≥22.7 does not attempt to strip
|
|
4
|
+
* types under `node_modules/` — `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`.
|
|
5
|
+
*
|
|
3
6
|
* Extend this in your server's `vitest.config.ts`:
|
|
4
7
|
*
|
|
5
8
|
* ```ts
|