@cyanheads/mcp-ts-core 0.10.3 → 0.10.5
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 +11 -3
- package/CLAUDE.md +11 -3
- package/README.md +1 -1
- package/changelog/0.10.x/0.10.4.md +41 -0
- package/changelog/0.10.x/0.10.5.md +60 -0
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +5 -8
- package/dist/core/app.js.map +1 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +0 -2
- package/dist/core/worker.js.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +1 -1
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- 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/CanvasRegistry.d.ts +6 -0
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +11 -0
- package/dist/services/canvas/core/CanvasRegistry.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/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +6 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/services/mirror/types.d.ts +4 -2
- package/dist/services/mirror/types.d.ts.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.d.ts +2 -2
- package/dist/services/speech/providers/elevenlabs.provider.js +3 -3
- package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
- package/dist/services/speech/providers/whisper.provider.d.ts +1 -1
- package/dist/services/speech/providers/whisper.provider.js +3 -3
- package/dist/services/speech/providers/whisper.provider.js.map +1 -1
- package/dist/services/speech/types.d.ts +4 -4
- package/dist/services/speech/types.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.js +9 -2
- package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
- package/dist/testing/vitest.d.ts +76 -0
- package/dist/testing/vitest.d.ts.map +1 -0
- package/dist/testing/vitest.js +70 -0
- package/dist/testing/vitest.js.map +1 -0
- package/dist/utils/internal/encoding.d.ts.map +1 -1
- package/dist/utils/internal/encoding.js +6 -17
- package/dist/utils/internal/encoding.js.map +1 -1
- package/dist/utils/internal/performance.d.ts +17 -27
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +23 -45
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +6 -13
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/retry.js +11 -9
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/rateLimiter.d.ts +5 -0
- package/dist/utils/security/rateLimiter.d.ts.map +1 -1
- package/dist/utils/security/rateLimiter.js +7 -0
- package/dist/utils/security/rateLimiter.js.map +1 -1
- package/package.json +64 -33
- package/skills/api-canvas/SKILL.md +12 -6
- package/skills/api-testing/SKILL.md +63 -1
- package/skills/api-workers/SKILL.md +97 -1
- package/templates/Dockerfile +3 -0
- package/templates/package.json +5 -5
- package/templates/tests/tools/echo.tool.test.ts +28 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
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",
|
|
@@ -35,75 +35,98 @@
|
|
|
35
35
|
"exports": {
|
|
36
36
|
".": {
|
|
37
37
|
"types": "./dist/core/index.d.ts",
|
|
38
|
-
"import": "./dist/core/index.js"
|
|
38
|
+
"import": "./dist/core/index.js",
|
|
39
|
+
"default": "./dist/core/index.js"
|
|
39
40
|
},
|
|
40
41
|
"./worker": {
|
|
41
42
|
"types": "./dist/core/worker.d.ts",
|
|
42
|
-
"import": "./dist/core/worker.js"
|
|
43
|
+
"import": "./dist/core/worker.js",
|
|
44
|
+
"default": "./dist/core/worker.js"
|
|
43
45
|
},
|
|
44
46
|
"./tools": {
|
|
45
47
|
"types": "./dist/mcp-server/tools/utils/toolDefinition.d.ts",
|
|
46
|
-
"import": "./dist/mcp-server/tools/utils/toolDefinition.js"
|
|
48
|
+
"import": "./dist/mcp-server/tools/utils/toolDefinition.js",
|
|
49
|
+
"default": "./dist/mcp-server/tools/utils/toolDefinition.js"
|
|
47
50
|
},
|
|
48
51
|
"./resources": {
|
|
49
52
|
"types": "./dist/mcp-server/resources/utils/resourceDefinition.d.ts",
|
|
50
|
-
"import": "./dist/mcp-server/resources/utils/resourceDefinition.js"
|
|
53
|
+
"import": "./dist/mcp-server/resources/utils/resourceDefinition.js",
|
|
54
|
+
"default": "./dist/mcp-server/resources/utils/resourceDefinition.js"
|
|
51
55
|
},
|
|
52
56
|
"./prompts": {
|
|
53
57
|
"types": "./dist/mcp-server/prompts/utils/promptDefinition.d.ts",
|
|
54
|
-
"import": "./dist/mcp-server/prompts/utils/promptDefinition.js"
|
|
58
|
+
"import": "./dist/mcp-server/prompts/utils/promptDefinition.js",
|
|
59
|
+
"default": "./dist/mcp-server/prompts/utils/promptDefinition.js"
|
|
55
60
|
},
|
|
56
61
|
"./tasks": {
|
|
57
62
|
"types": "./dist/mcp-server/tasks/utils/taskToolDefinition.d.ts",
|
|
58
|
-
"import": "./dist/mcp-server/tasks/utils/taskToolDefinition.js"
|
|
63
|
+
"import": "./dist/mcp-server/tasks/utils/taskToolDefinition.js",
|
|
64
|
+
"default": "./dist/mcp-server/tasks/utils/taskToolDefinition.js"
|
|
59
65
|
},
|
|
60
66
|
"./errors": {
|
|
61
67
|
"types": "./dist/types-global/errors.d.ts",
|
|
62
|
-
"import": "./dist/types-global/errors.js"
|
|
68
|
+
"import": "./dist/types-global/errors.js",
|
|
69
|
+
"default": "./dist/types-global/errors.js"
|
|
63
70
|
},
|
|
64
71
|
"./config": {
|
|
65
72
|
"types": "./dist/config/index.d.ts",
|
|
66
|
-
"import": "./dist/config/index.js"
|
|
73
|
+
"import": "./dist/config/index.js",
|
|
74
|
+
"default": "./dist/config/index.js"
|
|
67
75
|
},
|
|
68
76
|
"./auth": {
|
|
69
77
|
"types": "./dist/mcp-server/transports/auth/lib/checkScopes.d.ts",
|
|
70
|
-
"import": "./dist/mcp-server/transports/auth/lib/checkScopes.js"
|
|
78
|
+
"import": "./dist/mcp-server/transports/auth/lib/checkScopes.js",
|
|
79
|
+
"default": "./dist/mcp-server/transports/auth/lib/checkScopes.js"
|
|
71
80
|
},
|
|
72
81
|
"./storage": {
|
|
73
82
|
"types": "./dist/storage/core/StorageService.d.ts",
|
|
74
|
-
"import": "./dist/storage/core/StorageService.js"
|
|
83
|
+
"import": "./dist/storage/core/StorageService.js",
|
|
84
|
+
"default": "./dist/storage/core/StorageService.js"
|
|
75
85
|
},
|
|
76
86
|
"./storage/types": {
|
|
77
87
|
"types": "./dist/storage/core/IStorageProvider.d.ts",
|
|
78
|
-
"import": "./dist/storage/core/IStorageProvider.js"
|
|
88
|
+
"import": "./dist/storage/core/IStorageProvider.js",
|
|
89
|
+
"default": "./dist/storage/core/IStorageProvider.js"
|
|
79
90
|
},
|
|
80
91
|
"./canvas": {
|
|
81
92
|
"types": "./dist/services/canvas/index.d.ts",
|
|
82
|
-
"import": "./dist/services/canvas/index.js"
|
|
93
|
+
"import": "./dist/services/canvas/index.js",
|
|
94
|
+
"default": "./dist/services/canvas/index.js"
|
|
83
95
|
},
|
|
84
96
|
"./mirror": {
|
|
85
97
|
"types": "./dist/services/mirror/index.d.ts",
|
|
86
|
-
"import": "./dist/services/mirror/index.js"
|
|
98
|
+
"import": "./dist/services/mirror/index.js",
|
|
99
|
+
"default": "./dist/services/mirror/index.js"
|
|
87
100
|
},
|
|
88
101
|
"./utils": {
|
|
89
102
|
"types": "./dist/utils/index.d.ts",
|
|
90
|
-
"import": "./dist/utils/index.js"
|
|
103
|
+
"import": "./dist/utils/index.js",
|
|
104
|
+
"default": "./dist/utils/index.js"
|
|
91
105
|
},
|
|
92
106
|
"./services": {
|
|
93
107
|
"types": "./dist/services/index.d.ts",
|
|
94
|
-
"import": "./dist/services/index.js"
|
|
108
|
+
"import": "./dist/services/index.js",
|
|
109
|
+
"default": "./dist/services/index.js"
|
|
95
110
|
},
|
|
96
111
|
"./linter": {
|
|
97
112
|
"types": "./dist/linter/index.d.ts",
|
|
98
|
-
"import": "./dist/linter/index.js"
|
|
113
|
+
"import": "./dist/linter/index.js",
|
|
114
|
+
"default": "./dist/linter/index.js"
|
|
99
115
|
},
|
|
100
116
|
"./testing": {
|
|
101
117
|
"types": "./dist/testing/index.d.ts",
|
|
102
|
-
"import": "./dist/testing/index.js"
|
|
118
|
+
"import": "./dist/testing/index.js",
|
|
119
|
+
"default": "./dist/testing/index.js"
|
|
103
120
|
},
|
|
104
121
|
"./testing/fuzz": {
|
|
105
122
|
"types": "./dist/testing/fuzz.d.ts",
|
|
106
|
-
"import": "./dist/testing/fuzz.js"
|
|
123
|
+
"import": "./dist/testing/fuzz.js",
|
|
124
|
+
"default": "./dist/testing/fuzz.js"
|
|
125
|
+
},
|
|
126
|
+
"./testing/vitest": {
|
|
127
|
+
"types": "./dist/testing/vitest.d.ts",
|
|
128
|
+
"import": "./dist/testing/vitest.js",
|
|
129
|
+
"default": "./dist/testing/vitest.js"
|
|
107
130
|
},
|
|
108
131
|
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
109
132
|
"./vitest.config": "./vitest.config.base.mjs",
|
|
@@ -148,11 +171,14 @@
|
|
|
148
171
|
"tree": "bun run scripts/tree.ts",
|
|
149
172
|
"fetch-spec": "bun run scripts/fetch-openapi-spec.ts",
|
|
150
173
|
"test": "bunx vitest run",
|
|
151
|
-
"test:all": "bun run test && bun run test:worker && bun run test:integration",
|
|
174
|
+
"test:all": "bun run test:coverage && bun run test:node && bun run test:worker && bun run test:integration",
|
|
152
175
|
"test:integration": "bunx vitest run --config vitest.integration.ts",
|
|
176
|
+
"test:leaks": "bunx vitest run --detect-async-leaks",
|
|
177
|
+
"test:node": "sh -c 'NODE_BIN=$(which -a node | grep -v /bun-node- | head -n 1); \"$NODE_BIN\" ./node_modules/vitest/vitest.mjs run && \"$NODE_BIN\" ./node_modules/vitest/vitest.mjs run --config vitest.integration.ts'",
|
|
153
178
|
"test:unit": "bunx vitest run --project unit",
|
|
154
179
|
"test:smoke": "bunx vitest run --project smoke",
|
|
155
180
|
"test:fuzz": "bunx vitest run --project fuzz",
|
|
181
|
+
"test:typecheck": "bunx vitest run --project typecheck",
|
|
156
182
|
"test:compliance": "bunx vitest run --project compliance",
|
|
157
183
|
"test:worker": "sh -c 'NODE_BIN=$(which -a node | grep -v /bun-node- | head -n 1); \"$NODE_BIN\" ./node_modules/vitest/vitest.mjs run --config vitest.worker.ts'",
|
|
158
184
|
"test:ui": "bunx vitest --ui",
|
|
@@ -177,18 +203,18 @@
|
|
|
177
203
|
},
|
|
178
204
|
"devDependencies": {
|
|
179
205
|
"@biomejs/biome": "2.4.16",
|
|
180
|
-
"@cloudflare/vitest-pool-workers": "^0.16.
|
|
181
|
-
"@cloudflare/workers-types": "4.
|
|
206
|
+
"@cloudflare/vitest-pool-workers": "^0.16.15",
|
|
207
|
+
"@cloudflare/workers-types": "4.20260611.1",
|
|
182
208
|
"@duckdb/node-api": "^1.5.3-r.3",
|
|
183
209
|
"@hono/otel": "^1.1.2",
|
|
184
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.
|
|
185
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
186
|
-
"@opentelemetry/instrumentation-http": "^0.
|
|
187
|
-
"@opentelemetry/instrumentation-pino": "^0.
|
|
188
|
-
"@opentelemetry/resources": "^2.
|
|
189
|
-
"@opentelemetry/sdk-metrics": "^2.
|
|
190
|
-
"@opentelemetry/sdk-node": "^0.
|
|
191
|
-
"@opentelemetry/sdk-trace-node": "^2.
|
|
210
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
|
|
211
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
212
|
+
"@opentelemetry/instrumentation-http": "^0.219.0",
|
|
213
|
+
"@opentelemetry/instrumentation-pino": "^0.65.0",
|
|
214
|
+
"@opentelemetry/resources": "^2.8.0",
|
|
215
|
+
"@opentelemetry/sdk-metrics": "^2.8.0",
|
|
216
|
+
"@opentelemetry/sdk-node": "^0.219.0",
|
|
217
|
+
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
192
218
|
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
193
219
|
"@supabase/supabase-js": "^2.108.1",
|
|
194
220
|
"@types/bun": "^1.3.14",
|
|
@@ -207,6 +233,7 @@
|
|
|
207
233
|
"depcheck": "^1.4.7",
|
|
208
234
|
"diff": "^9.0.0",
|
|
209
235
|
"execa": "^9.6.1",
|
|
236
|
+
"fast-check": "^4.8.0",
|
|
210
237
|
"fast-xml-parser": "^5.8.0",
|
|
211
238
|
"ignore": "^7.0.5",
|
|
212
239
|
"js-yaml": "^4.2.0",
|
|
@@ -257,7 +284,7 @@
|
|
|
257
284
|
"url": "https://www.buymeacoffee.com/cyanheads"
|
|
258
285
|
}
|
|
259
286
|
],
|
|
260
|
-
"packageManager": "bun@1.3.
|
|
287
|
+
"packageManager": "bun@1.3.11",
|
|
261
288
|
"engines": {
|
|
262
289
|
"bun": ">=1.3.0",
|
|
263
290
|
"node": ">=24.0.0"
|
|
@@ -303,8 +330,9 @@
|
|
|
303
330
|
"better-sqlite3": "^12.0.0",
|
|
304
331
|
"chrono-node": "^2.9.0",
|
|
305
332
|
"defuddle": "^0.18.1",
|
|
306
|
-
"diff": "
|
|
307
|
-
"fast-check": "
|
|
333
|
+
"diff": "^9.0.0",
|
|
334
|
+
"fast-check": "^4.0.0",
|
|
335
|
+
"vitest": ">=4.0.0",
|
|
308
336
|
"fast-xml-parser": "^5.8.0",
|
|
309
337
|
"js-yaml": "^4.1.1",
|
|
310
338
|
"linkedom": "^0.18.12",
|
|
@@ -402,6 +430,9 @@
|
|
|
402
430
|
},
|
|
403
431
|
"validator": {
|
|
404
432
|
"optional": true
|
|
433
|
+
},
|
|
434
|
+
"vitest": {
|
|
435
|
+
"optional": true
|
|
405
436
|
}
|
|
406
437
|
}
|
|
407
438
|
}
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
DataCanvas primitive reference — a Tier 3 SQL/analytical workspace for tabular MCP servers, backed by DuckDB. Use when registering tables from upstream APIs, running ad-hoc SQL across them, and exporting results. Covers the acquire → register → query → export flow, per-table TTL, the token-sharing pattern for multi-agent collaboration, env config, and Cloudflare Workers fail-closed behavior.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.6"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -135,7 +135,7 @@ await instance.registerTable('big_dataset', asyncRows, {
|
|
|
135
135
|
await instance.registerTable('recent_fetch', rows, { ttlMs: 30 * 60 * 1000 });
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
**Schema inference** when `schema` is omitted: sniffer materializes the first 100 rows, unions JS-side types per column, and maps to DuckDB types. Fall-backs to `VARCHAR` for ambiguous unions (string mixed with numerics). Numeric widening: `INTEGER + DOUBLE → DOUBLE`, `INTEGER + BIGINT → BIGINT`. Column ordering follows first-appearance.
|
|
138
|
+
**Schema inference** when `schema` is omitted: sniffer materializes the first 100 rows, unions JS-side types per column, and maps to DuckDB types. All inferred columns are **always nullable** — a sample can prove a column is nullable, but can never prove NOT NULL (a null may appear past the sniff window). Pass an explicit `schema` when `NOT NULL` enforcement is required. Fall-backs to `VARCHAR` for ambiguous unions (string mixed with numerics). Numeric widening: `INTEGER + DOUBLE → DOUBLE`, `INTEGER + BIGINT → BIGINT`. Column ordering follows first-appearance.
|
|
139
139
|
|
|
140
140
|
**Per-table TTL (`ttlMs`)** — optional sliding TTL for this table specifically. When set:
|
|
141
141
|
- The sweep loop drops the table (and clears its bookkeeping) when its window expires.
|
|
@@ -146,7 +146,9 @@ await instance.registerTable('recent_fetch', rows, { ttlMs: 30 * 60 * 1000 });
|
|
|
146
146
|
|
|
147
147
|
### `instance.query(sql, options?)`
|
|
148
148
|
|
|
149
|
-
Run SQL across registered tables. Returns at most `rowLimit` rows (default 10 000). For full result sets, pass `registerAs` — the result is materialized as a new canvas table; the response carries a `preview` slice
|
|
149
|
+
Run SQL across registered tables. Returns at most `rowLimit` rows (default 10 000). When the result exceeds `rowLimit`, the response carries `truncated: true` and `rowCount` reflects the number of materialized rows (not the full result set). For full result sets and exact counts, pass `registerAs` — the result is materialized as a new canvas table; the response carries a `preview` slice and the exact `rowCount`.
|
|
150
|
+
|
|
151
|
+
Querying a table that does not exist throws `NotFound` (`data.reason: 'missing_table'`) with a recovery hint to re-stage the table or call `describe()`. This happens when a table has expired (per-table TTL), been dropped, or the name is mistyped. The error is `NotFound`, not `ValidationError` — agents should re-stage, not fix the SQL shape.
|
|
150
152
|
|
|
151
153
|
```ts
|
|
152
154
|
const result = await instance.query(`
|
|
@@ -172,7 +174,9 @@ const chained = await instance.query(
|
|
|
172
174
|
|
|
173
175
|
`ttlMs` on `query({ registerAs })` assigns a per-table TTL to the materialized table — the same sliding semantics as `registerTable({ ttlMs })`. The SQL text is also scanned for referenced table names; any tracked per-table TTL entry found is slid on each `query()` call.
|
|
174
176
|
|
|
175
|
-
|
|
177
|
+
`denySystemCatalogs?: boolean` (default `false`) — when `true`, the gate rejects any reference to system catalog namespaces (`information_schema`, `pg_catalog`, `sqlite_master`, `duckdb_<name>()` calls) at the text-scan layer before the query executes. Use on shared canvases where handle possession is the access boundary — catalog namespaces let callers enumerate every staged handle. Rejection throws `ValidationError` with `data.reason: 'system_catalog_access'`. Canvas-token servers that explicitly expose `describe()` to agents do not need this; only servers that intentionally hide the full catalog should opt in.
|
|
178
|
+
|
|
179
|
+
**Read-only enforcement** (four layers + optional catalog layer):
|
|
176
180
|
1. Text-level deny-list — pre-parse scan for file/HTTP-reading table functions (`read_csv*`, `read_json*`, `read_parquet*`, `read_text`, `read_blob`, `glob`, `iceberg_scan`, `delta_scan`, `postgres_scan`, `mysql_scan`, `sqlite_scan`, plus pre-staged spatial ones).
|
|
177
181
|
2. Statement count (must be 1) via `extractStatements`.
|
|
178
182
|
3. Statement type (must be `SELECT`) via `prepared.statementType`.
|
|
@@ -182,7 +186,7 @@ Any layer's rejection throws `ValidationError` with a structured `data.reason`.
|
|
|
182
186
|
|
|
183
187
|
### `instance.registerView(name, selectSql, options?)`
|
|
184
188
|
|
|
185
|
-
Register a SQL view on the canvas. The `SELECT` runs through the same
|
|
189
|
+
Register a SQL view on the canvas. The `SELECT` runs through the same gate `query()` enforces (four layers), so a malicious definition fails at registration time, not later when the view is referenced. Pass `{ denySystemCatalogs: true }` to also block catalog namespace references in the view definition — same semantics as the `query()` flag.
|
|
186
190
|
|
|
187
191
|
```ts
|
|
188
192
|
await instance.registerView(
|
|
@@ -224,7 +228,7 @@ await instance.export('g_with_obs', { format: 'csv', stream: writableStream });
|
|
|
224
228
|
|
|
225
229
|
```ts
|
|
226
230
|
const tables = await instance.describe();
|
|
227
|
-
// [{ name: 'germplasm', kind: 'table', rowCount: 200, columns: [...] }, ...]
|
|
231
|
+
// [{ name: 'germplasm', kind: 'table', rowCount: 200, approxSizeBytes: 8192, columns: [...] }, ...]
|
|
228
232
|
|
|
229
233
|
// Filter by kind ('table' | 'view').
|
|
230
234
|
const onlyViews = await instance.describe({ kind: 'view' });
|
|
@@ -235,6 +239,8 @@ await instance.clear(); // returns count dropped (drops views b
|
|
|
235
239
|
|
|
236
240
|
`TableInfo.kind` discriminates `'table'` vs `'view'`. For views, `rowCount` is materialized at describe time via `COUNT(*)` — not free; treat as an approximation if the view is expensive.
|
|
237
241
|
|
|
242
|
+
`TableInfo.approxSizeBytes` is set for base tables (DuckDB's `estimated_size` from `duckdb_tables()`). It is `undefined` for views — views have no entry in `duckdb_tables()`. Use it to decide what to drop when a canvas approaches its memory limit.
|
|
243
|
+
|
|
238
244
|
### Cancellation
|
|
239
245
|
|
|
240
246
|
`registerTable`, `query`, and `export` accept `options.signal: AbortSignal`. The provider opens a fresh DuckDB connection per query/export so `connection.interrupt()` cancels exactly the in-flight work without disturbing other ops on the same canvas.
|
|
@@ -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.5"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -19,6 +19,52 @@ Tests target handler behavior directly — call `handler(input, ctx)`, assert on
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
## `mcpTest` — fixture-based Vitest test
|
|
23
|
+
|
|
24
|
+
`mcpTest` is a `test.extend`-based Vitest test that provides `ctx` and `storage` as **per-test fixtures** — fresh instances for every test, eliminating the `createMockContext()` boilerplate and enforcing the fresh-context-per-test convention automatically.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { mcpTest } from '@cyanheads/mcp-ts-core/testing/vitest';
|
|
28
|
+
|
|
29
|
+
mcpTest('echoes the message', async ({ ctx }) => {
|
|
30
|
+
const result = await echoTool.handler(echoTool.input.parse({ message: 'hi' }), ctx);
|
|
31
|
+
expect(result.message).toBe('hi');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
mcpTest('uses storage fixture', async ({ ctx, storage }) => {
|
|
35
|
+
const svc = new MyService(config, storage);
|
|
36
|
+
const result = await svc.doWork(ctx);
|
|
37
|
+
expect(result).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Fixtures
|
|
42
|
+
|
|
43
|
+
| Fixture | Type | Per-test? | Notes |
|
|
44
|
+
|:--------|:-----|:----------|:------|
|
|
45
|
+
| `ctx` | `Context` | Yes | Fresh `createMockContext()` each test |
|
|
46
|
+
| `storage` | `StorageService` | Yes | Fresh `createInMemoryStorage()` each test |
|
|
47
|
+
|
|
48
|
+
### Extending with the function form
|
|
49
|
+
|
|
50
|
+
Override fixtures using the **function form** (`async ({}, use) => { ... }`) to preserve per-test freshness. A bare-value override shares one mutable instance across the entire file — defeating the fixture's isolation guarantee.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createMockContext } from '@cyanheads/mcp-ts-core/testing/vitest';
|
|
54
|
+
|
|
55
|
+
// Correct — function form gives each test a fresh context:
|
|
56
|
+
const tenantTest = mcpTest.extend({
|
|
57
|
+
ctx: async ({}, use) => { await use(createMockContext({ tenantId: 'test-tenant' })); },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Wrong — bare value shares one ctx across every test in the file:
|
|
61
|
+
// const tenantTest = mcpTest.extend({ ctx: createMockContext({ tenantId: 'test-tenant' }) });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`createMockContext` and `createInMemoryStorage` are re-exported from `@cyanheads/mcp-ts-core/testing/vitest` so overrides don't need a second import.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
22
68
|
## `createMockContext` options
|
|
23
69
|
|
|
24
70
|
```ts
|
|
@@ -311,6 +357,22 @@ Use `.rejects.toThrow(McpError)` to assert type only. Use `.rejects.toMatchObjec
|
|
|
311
357
|
|
|
312
358
|
---
|
|
313
359
|
|
|
360
|
+
## Output schema assertions
|
|
361
|
+
|
|
362
|
+
`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:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
it('output conforms to the declared output schema', async () => {
|
|
366
|
+
const ctx = createMockContext();
|
|
367
|
+
const result = await myTool.handler(myTool.input.parse({ query: 'x' }), ctx);
|
|
368
|
+
expect(result).toEqual(expect.schemaMatching(myTool.output));
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
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.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
314
376
|
## Testing handlers with `errors[]` (typed contract)
|
|
315
377
|
|
|
316
378
|
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:
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.5"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -198,3 +198,99 @@ export function getServerConfig() {
|
|
|
198
198
|
> `DuckDB canvas requires Node.js or Bun. Set CANVAS_PROVIDER_TYPE=none or omit it for Cloudflare Workers deployment.`
|
|
199
199
|
|
|
200
200
|
Leave the env unset (or set to `none`) for Worker deployments. Tools that conditionally use canvas should check the module-level accessor (`if (!getCanvas()) { ... }`) and surface a clear "feature unavailable on this deployment" message. See `api-canvas` for the full DataCanvas reference and setup wiring pattern.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Testing Workers with miniflare
|
|
205
|
+
|
|
206
|
+
`bun run test:worker` runs the worker suite under `vitest.worker.ts` (using `@cloudflare/vitest-pool-workers` + miniflare). Each test **file** gets its own fresh V8 isolate — module scope (including `createWorkerHandler`'s `appPromise` singleton) is reset between files.
|
|
207
|
+
|
|
208
|
+
### vitest.worker.ts miniflare bindings
|
|
209
|
+
|
|
210
|
+
Declare all storage bindings used in the suite:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
cloudflareTest({
|
|
214
|
+
main: './tests/fixtures/worker-runtime.fixture.ts',
|
|
215
|
+
miniflare: {
|
|
216
|
+
bindings: { STORAGE_PROVIDER_TYPE: 'cloudflare-kv', ... },
|
|
217
|
+
kvNamespaces: ['KV_NAMESPACE', 'CUSTOM_KV'],
|
|
218
|
+
r2Buckets: ['R2_BUCKET'],
|
|
219
|
+
d1Databases: ['DB'],
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Binding names must match the hardcoded names in `storageFactory.ts` (`KV_NAMESPACE`, `R2_BUCKET`, `DB`).
|
|
225
|
+
|
|
226
|
+
### Per-provider test isolation
|
|
227
|
+
|
|
228
|
+
`bindings.STORAGE_PROVIDER_TYPE` is a global default. Per-provider test files override it by passing a modified env to `worker.fetch()`:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { env } from 'cloudflare:workers';
|
|
232
|
+
|
|
233
|
+
// Fresh isolate per file — appPromise starts null.
|
|
234
|
+
// First fetch initialises the singleton with the overridden provider type.
|
|
235
|
+
const r2Env = { ...env, STORAGE_PROVIDER_TYPE: 'cloudflare-r2' };
|
|
236
|
+
await worker.fetch(request, r2Env, ctx);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Use `reset()` from `cloudflare:test` in `afterEach` to clear binding state between tests. For D1, re-apply migrations immediately after each `reset()` (reset wipes the schema):
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { applyD1Migrations, reset } from 'cloudflare:test';
|
|
243
|
+
|
|
244
|
+
afterEach(async () => {
|
|
245
|
+
await reset();
|
|
246
|
+
await applyD1Migrations(env.DB, [{ name: '0001_schema', queries: [CREATE_TABLE_SQL] }]);
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### D1 schema setup
|
|
251
|
+
|
|
252
|
+
The `cloudflare-d1` provider requires the `kv_store` table before any operations. Apply it in `beforeAll` via `applyD1Migrations`:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const KV_STORE_MIGRATION = `
|
|
256
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
257
|
+
tenant_id TEXT NOT NULL,
|
|
258
|
+
key TEXT NOT NULL,
|
|
259
|
+
value TEXT NOT NULL,
|
|
260
|
+
expires_at INTEGER,
|
|
261
|
+
PRIMARY KEY (tenant_id, key)
|
|
262
|
+
)`;
|
|
263
|
+
|
|
264
|
+
beforeAll(async () => {
|
|
265
|
+
await applyD1Migrations(env.DB, [
|
|
266
|
+
{ name: '0001_create_kv_store', queries: [KV_STORE_MIGRATION] },
|
|
267
|
+
]);
|
|
268
|
+
sessionId = await openSession(d1Env);
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### R2 list limit
|
|
273
|
+
|
|
274
|
+
The R2 provider fetches `limit + 1` objects to detect further pages. Miniflare caps R2 `MaxKeys` at 1000, so the default `limit=1000` → request 1001 → error. Pass `limit: 100` (or any value ≤ 999) to `ctx.state.list()` in test fixtures to stay under the cap:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
const result = await ctx.state.list(prefix, { limit: 100 });
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Storage probe pattern
|
|
281
|
+
|
|
282
|
+
Expose storage operations as MCP tools in the fixture to test them through the real HTTP surface:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
const storageSetTool = tool('storage_set', {
|
|
286
|
+
input: z.object({ key: z.string().describe('...'), value: z.string().describe('...') }),
|
|
287
|
+
output: z.object({ ok: z.boolean().describe('Always true on success') }),
|
|
288
|
+
async handler(input, ctx) {
|
|
289
|
+
await ctx.state.set(input.key, input.value);
|
|
290
|
+
return { ok: true };
|
|
291
|
+
},
|
|
292
|
+
format: (r) => [{ type: 'text', text: `ok=${r.ok}` }],
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Call via `tools/call` over MCP JSON-RPC and assert on `structuredContent`. See `tests/worker/storage-r2.worker.test.ts` and `tests/worker/storage-d1.worker.test.ts` for the full pattern.
|
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
|
}
|
|
@@ -5,8 +5,29 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
7
7
|
import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
8
|
+
import { mcpTest } from '@cyanheads/mcp-ts-core/testing/vitest';
|
|
8
9
|
import { echoTool } from '@/mcp-server/tools/definitions/echo.tool.js';
|
|
9
10
|
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Fixture-based tests (mcpTest) — fresh ctx per test, no manual construction
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
mcpTest('echoTool: echoes the message back (fixture)', async ({ ctx }) => {
|
|
16
|
+
const input = echoTool.input.parse({ message: 'hello world' });
|
|
17
|
+
const result = await echoTool.handler(input, ctx);
|
|
18
|
+
expect(result).toEqual({ message: 'hello world' });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
mcpTest('echoTool: output conforms to declared schema (fixture)', async ({ ctx }) => {
|
|
22
|
+
const input = echoTool.input.parse({ message: 'hello world' });
|
|
23
|
+
const result = await echoTool.handler(input, ctx);
|
|
24
|
+
expect(result).toEqual(expect.schemaMatching(echoTool.output));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Classic describe/it tests (createMockContext) — shown for comparison
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
10
31
|
describe('echoTool', () => {
|
|
11
32
|
it('echoes the message back', async () => {
|
|
12
33
|
const ctx = createMockContext();
|
|
@@ -15,6 +36,13 @@ describe('echoTool', () => {
|
|
|
15
36
|
expect(result).toEqual({ message: 'hello world' });
|
|
16
37
|
});
|
|
17
38
|
|
|
39
|
+
it('output conforms to the declared output schema', async () => {
|
|
40
|
+
const ctx = createMockContext();
|
|
41
|
+
const input = echoTool.input.parse({ message: 'hello world' });
|
|
42
|
+
const result = await echoTool.handler(input, ctx);
|
|
43
|
+
expect(result).toEqual(expect.schemaMatching(echoTool.output));
|
|
44
|
+
});
|
|
45
|
+
|
|
18
46
|
it('formats output as text content', () => {
|
|
19
47
|
const blocks = echoTool.format!({ message: 'hello world' });
|
|
20
48
|
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
|