@cyanheads/mcp-ts-core 0.10.3 → 0.10.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/AGENTS.md +7 -3
- package/CLAUDE.md +7 -3
- package/changelog/0.10.x/0.10.4.md +41 -0
- package/dist/mcp-server/server.d.ts +2 -2
- package/dist/mcp-server/server.js +3 -3
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/services/canvas/core/schemaSniffer.d.ts +6 -4
- package/dist/services/canvas/core/schemaSniffer.d.ts.map +1 -1
- package/dist/services/canvas/core/schemaSniffer.js +7 -10
- package/dist/services/canvas/core/schemaSniffer.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts +15 -0
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/core/sqlGate.js +40 -0
- package/dist/services/canvas/core/sqlGate.js.map +1 -1
- package/dist/services/canvas/index.d.ts +1 -1
- package/dist/services/canvas/index.d.ts.map +1 -1
- package/dist/services/canvas/index.js +1 -1
- package/dist/services/canvas/index.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +76 -18
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/types.d.ts +30 -1
- package/dist/services/canvas/types.d.ts.map +1 -1
- package/package.json +44 -23
- package/skills/api-canvas/SKILL.md +12 -6
- package/skills/api-testing/SKILL.md +17 -1
- package/templates/Dockerfile +3 -0
- package/templates/package.json +5 -5
- package/templates/tests/tools/echo.tool.test.ts +7 -0
- package/tsconfig.base.json +0 -1
- package/dist/logs/combined.log +0 -4
- package/dist/logs/error.log +0 -2
- package/dist/logs/interactions.log +0 -0
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Testing patterns for MCP tool/resource handlers using `createMockContext` and Vitest. Covers mock context options, handler testing, McpError assertions, format testing, Vitest config setup, and test isolation conventions.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.4"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -311,6 +311,22 @@ Use `.rejects.toThrow(McpError)` to assert type only. Use `.rejects.toMatchObjec
|
|
|
311
311
|
|
|
312
312
|
---
|
|
313
313
|
|
|
314
|
+
## Output schema assertions
|
|
315
|
+
|
|
316
|
+
`expect.schemaMatching` (Vitest 4, Standard Schema) validates a value against any Zod schema — including the definition's own `output`. Use it to assert schema conformance without duplicating the shape in the test:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
it('output conforms to the declared output schema', async () => {
|
|
320
|
+
const ctx = createMockContext();
|
|
321
|
+
const result = await myTool.handler(myTool.input.parse({ query: 'x' }), ctx);
|
|
322
|
+
expect(result).toEqual(expect.schemaMatching(myTool.output));
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
It composes as an asymmetric matcher anywhere a value is expected — e.g. `toHaveBeenCalledWith(expect.schemaMatching(schema))`. Prefer exact-value assertions when the expected output is fully known; reach for `schemaMatching` when the output is dynamic (timestamps, generated IDs) or the schema itself is the contract under test.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
314
330
|
## Testing handlers with `errors[]` (typed contract)
|
|
315
331
|
|
|
316
332
|
Tools and resources that declare an `errors[]` contract receive a typed `ctx.fail` helper at runtime. Pass the definition's own `errors` to `createMockContext` and the mock wires `fail` the same way the production handler factory does:
|
package/templates/Dockerfile
CHANGED
|
@@ -114,5 +114,8 @@ ENV MCP_FORCE_CONSOLE_LOGGING="true"
|
|
|
114
114
|
# Expose the port the server listens on
|
|
115
115
|
EXPOSE ${MCP_HTTP_PORT}
|
|
116
116
|
|
|
117
|
+
# Health check using a bun-native fetch (slim image ships no curl/wget)
|
|
118
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD bun -e "fetch('http://localhost:'+(process.env.MCP_HTTP_PORT??'3010')+'/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
119
|
+
|
|
117
120
|
# The command to start the server
|
|
118
121
|
CMD ["bun", "run", "dist/index.js"]
|
package/templates/package.json
CHANGED
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"zod": "{{ZOD_VERSION}}"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@biomejs/biome": "
|
|
66
|
-
"@types/node": "
|
|
65
|
+
"@biomejs/biome": "2.4.16",
|
|
66
|
+
"@types/node": "25.9.3",
|
|
67
67
|
"depcheck": "^1.4.7",
|
|
68
68
|
"ignore": "^7.0.5",
|
|
69
|
-
"tsc-alias": "^1.8.
|
|
70
|
-
"typescript": "^
|
|
71
|
-
"vitest": "^4.1.
|
|
69
|
+
"tsc-alias": "^1.8.17",
|
|
70
|
+
"typescript": "^6.0.3",
|
|
71
|
+
"vitest": "^4.1.8"
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -15,6 +15,13 @@ describe('echoTool', () => {
|
|
|
15
15
|
expect(result).toEqual({ message: 'hello world' });
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
it('output conforms to the declared output schema', async () => {
|
|
19
|
+
const ctx = createMockContext();
|
|
20
|
+
const input = echoTool.input.parse({ message: 'hello world' });
|
|
21
|
+
const result = await echoTool.handler(input, ctx);
|
|
22
|
+
expect(result).toEqual(expect.schemaMatching(echoTool.output));
|
|
23
|
+
});
|
|
24
|
+
|
|
18
25
|
it('formats output as text content', () => {
|
|
19
26
|
const blocks = echoTool.format!({ message: 'hello world' });
|
|
20
27
|
expect(blocks).toEqual([{ type: 'text', text: 'hello world' }]);
|
package/tsconfig.base.json
CHANGED
package/dist/logs/combined.log
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
{"level":40,"time":1781133231939,"env":"testing","version":"0.10.3","pid":80401,"transport":"http","requestId":"PAZLD-YA82Z","timestamp":"2026-06-10T23:13:51.938Z","operation":"TransportManager.start","component":"HttpTransportSetup","msg":"MCP_ALLOWED_ORIGINS is not set — CORS is wildcard for CLI clients; browser Origin headers are restricted to loopback. Set MCP_ALLOWED_ORIGINS for production deployments accepting remote browser origins."}
|
|
2
|
-
{"level":40,"time":1781133233685,"env":"testing","version":"0.10.3","pid":80401,"component":"HttpTransport","requestId":"2ISX1-LS2V4","timestamp":"2026-06-10T23:13:53.685Z","operation":"HttpRpcRequest","sessionId":"not-a-real-session-1781133233685","msg":"Session validation failed - invalid or hijacked session"}
|
|
3
|
-
{"level":50,"time":1781133237889,"env":"testing","version":"0.0.0-test","pid":80550,"requestId":"RD4D8-H3QV1","timestamp":"2026-06-10T23:13:57.888Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b6c3787ad9d5f6d3b11827959b7c06d08b23e57c3b1f8aee6dc7dbdb5d6ffa55","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"b6c3787ad9d5f6d3b11827959b7c06d08b23e57c3b1f8aee6dc7dbdb5d6ffa55","toolName":"scoped_echo","requestId":"RD4D8-H3QV1","timestamp":"2026-06-10T23:13:57.888Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"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:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283: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:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:324:26)\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."}
|
|
4
|
-
{"level":50,"time":1781133237899,"env":"testing","version":"0.0.0-test","pid":80550,"requestId":"GBWGR-X2TSW","timestamp":"2026-06-10T23:13:57.899Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d540d7df1cdbaf04f12a646d555d508ac0db2e191d0a5282980a2327f57f727f","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"d540d7df1cdbaf04f12a646d555d508ac0db2e191d0a5282980a2327f57f727f","toolName":"scoped_echo","requestId":"GBWGR-X2TSW","timestamp":"2026-06-10T23:13:57.899Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"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:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283: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:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:324:26)\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."}
|
package/dist/logs/error.log
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
{"level":50,"time":1781133237889,"env":"testing","version":"0.0.0-test","pid":80550,"requestId":"RD4D8-H3QV1","timestamp":"2026-06-10T23:13:57.888Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"b6c3787ad9d5f6d3b11827959b7c06d08b23e57c3b1f8aee6dc7dbdb5d6ffa55","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"b6c3787ad9d5f6d3b11827959b7c06d08b23e57c3b1f8aee6dc7dbdb5d6ffa55","toolName":"scoped_echo","requestId":"RD4D8-H3QV1","timestamp":"2026-06-10T23:13:57.888Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"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:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283: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:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:324:26)\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":1781133237899,"env":"testing","version":"0.0.0-test","pid":80550,"requestId":"GBWGR-X2TSW","timestamp":"2026-06-10T23:13:57.899Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d540d7df1cdbaf04f12a646d555d508ac0db2e191d0a5282980a2327f57f727f","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"d540d7df1cdbaf04f12a646d555d508ac0db2e191d0a5282980a2327f57f727f","toolName":"scoped_echo","requestId":"GBWGR-X2TSW","timestamp":"2026-06-10T23:13:57.899Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"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:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283: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:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:324:26)\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."}
|
|
File without changes
|