@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.
Files changed (33) hide show
  1. package/AGENTS.md +7 -3
  2. package/CLAUDE.md +7 -3
  3. package/changelog/0.10.x/0.10.4.md +41 -0
  4. package/dist/mcp-server/server.d.ts +2 -2
  5. package/dist/mcp-server/server.js +3 -3
  6. package/dist/mcp-server/server.js.map +1 -1
  7. package/dist/services/canvas/core/schemaSniffer.d.ts +6 -4
  8. package/dist/services/canvas/core/schemaSniffer.d.ts.map +1 -1
  9. package/dist/services/canvas/core/schemaSniffer.js +7 -10
  10. package/dist/services/canvas/core/schemaSniffer.js.map +1 -1
  11. package/dist/services/canvas/core/sqlGate.d.ts +15 -0
  12. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  13. package/dist/services/canvas/core/sqlGate.js +40 -0
  14. package/dist/services/canvas/core/sqlGate.js.map +1 -1
  15. package/dist/services/canvas/index.d.ts +1 -1
  16. package/dist/services/canvas/index.d.ts.map +1 -1
  17. package/dist/services/canvas/index.js +1 -1
  18. package/dist/services/canvas/index.js.map +1 -1
  19. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  20. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +76 -18
  21. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  22. package/dist/services/canvas/types.d.ts +30 -1
  23. package/dist/services/canvas/types.d.ts.map +1 -1
  24. package/package.json +44 -23
  25. package/skills/api-canvas/SKILL.md +12 -6
  26. package/skills/api-testing/SKILL.md +17 -1
  27. package/templates/Dockerfile +3 -0
  28. package/templates/package.json +5 -5
  29. package/templates/tests/tools/echo.tool.test.ts +7 -0
  30. package/tsconfig.base.json +0 -1
  31. package/dist/logs/combined.log +0 -4
  32. package/dist/logs/error.log +0 -2
  33. 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.3"
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:
@@ -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"]
@@ -62,12 +62,12 @@
62
62
  "zod": "{{ZOD_VERSION}}"
63
63
  },
64
64
  "devDependencies": {
65
- "@biomejs/biome": "^2.4.7",
66
- "@types/node": "^25.6.0",
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.16",
70
- "typescript": "^5.9.3",
71
- "vitest": "^4.1.0"
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' }]);
@@ -20,7 +20,6 @@
20
20
  "declarationMap": true,
21
21
  "sourceMap": true,
22
22
 
23
- "importHelpers": true,
24
23
  "incremental": true,
25
24
  "skipLibCheck": true,
26
25
  "forceConsistentCasingInFileNames": true,
@@ -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."}
@@ -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