@mcp-layer/schema 1.0.1 → 1.0.3
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/README.md +46 -0
- package/package.json +12 -7
- package/src/index.js +31 -76
- package/types/index.d.ts +13 -0
package/README.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
`@mcp-layer/schema` extracts MCP server definitions (tools, resources, prompts, templates, and MCP Apps metadata) and normalizes them into a unified Zod-backed schema. The output is designed for downstream generators that need a single contract to build CLIs, REST endpoints, UI renderers, or additional MCP layers without re-parsing the MCP protocol surface.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [What this package does](#what-this-package-does)
|
|
10
|
+
- [Output shape (authoritative)](#output-shape-authoritative)
|
|
11
|
+
- [MCP Apps support](#mcp-apps-support)
|
|
12
|
+
- [Generator guidance](#generator-guidance)
|
|
13
|
+
- [Responsibilities and lifecycle](#responsibilities-and-lifecycle)
|
|
14
|
+
- [Error handling](#error-handling)
|
|
15
|
+
- [JSON Schema vs Zod](#json-schema-vs-zod)
|
|
16
|
+
- [Runtime Error Reference](#runtime-error-reference)
|
|
17
|
+
|
|
5
18
|
## Installation
|
|
6
19
|
|
|
7
20
|
```sh
|
|
@@ -143,3 +156,36 @@ Suggested uses for the unified schema:
|
|
|
143
156
|
## JSON Schema vs Zod
|
|
144
157
|
|
|
145
158
|
Zod validators are produced to allow direct runtime validation, but the original JSON Schema is preserved in `detail.input.json` and `detail.output.json` so generators can emit OpenAPI, JSON Schema, or other schema formats without losing fidelity.
|
|
159
|
+
|
|
160
|
+
## Runtime Error Reference
|
|
161
|
+
|
|
162
|
+
This section is written for high-pressure debugging moments. Each entry maps directly to schema extraction preconditions.
|
|
163
|
+
|
|
164
|
+
<a id="error-49ae61"></a>
|
|
165
|
+
### Expected a Session instance.
|
|
166
|
+
|
|
167
|
+
Thrown from: `extract`
|
|
168
|
+
|
|
169
|
+
This happens when `extract(link)` is called without a connected `Session` object (or with an object that does not expose `.client`). The extractor relies on MCP client capabilities and list calls.
|
|
170
|
+
|
|
171
|
+
Step-by-step resolution:
|
|
172
|
+
1. Verify you are passing the `Session` returned by `connect(...)` or `attach(...)`.
|
|
173
|
+
2. Confirm session setup completed and `session.client` is available.
|
|
174
|
+
3. Avoid passing raw MCP client objects directly; wrap flows around `Session`.
|
|
175
|
+
4. Add an integration test that extracts from a real connected session.
|
|
176
|
+
|
|
177
|
+
<details>
|
|
178
|
+
<summary>Fix Example: extract schemas from a connected Session</summary>
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
const session = await connect(config, 'local-dev');
|
|
182
|
+
const catalog = await extract(session);
|
|
183
|
+
console.log(catalog.items.length);
|
|
184
|
+
await session.close();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
</details>
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-layer/schema",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Extract and normalize MCP schemas into a unified Zod-based format.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
"prompts"
|
|
17
17
|
],
|
|
18
18
|
"main": "src/index.js",
|
|
19
|
+
"types": "./types/index.d.ts",
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
22
|
+
"types": "./types/index.d.ts",
|
|
21
23
|
"import": "./src/index.js",
|
|
22
24
|
"default": "./src/index.js"
|
|
23
25
|
},
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
},
|
|
26
28
|
"files": [
|
|
27
29
|
"src",
|
|
30
|
+
"types",
|
|
28
31
|
"LICENSE"
|
|
29
32
|
],
|
|
30
33
|
"publishConfig": {
|
|
@@ -32,15 +35,17 @@
|
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
37
|
"ajv": "^8.17.1",
|
|
35
|
-
"zod": "^3.25.76"
|
|
38
|
+
"zod": "^3.25.76",
|
|
39
|
+
"@mcp-layer/error": "0.2.1"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
|
-
"@mcp-layer/attach": "1.1.
|
|
39
|
-
"@mcp-layer/config": "1.0.
|
|
40
|
-
"@mcp-layer/connect": "1.
|
|
41
|
-
"@mcp-layer/test-server": "1.0.
|
|
42
|
+
"@mcp-layer/attach": "1.1.2",
|
|
43
|
+
"@mcp-layer/config": "1.0.2",
|
|
44
|
+
"@mcp-layer/connect": "1.2.1",
|
|
45
|
+
"@mcp-layer/test-server": "1.0.4"
|
|
42
46
|
},
|
|
43
47
|
"scripts": {
|
|
44
|
-
"
|
|
48
|
+
"build:types": "tsc -p tsconfig.json",
|
|
49
|
+
"test": "node --test --experimental-test-coverage \"./test/**/*.js\""
|
|
45
50
|
}
|
|
46
51
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { LayerError } from '@mcp-layer/error';
|
|
3
4
|
|
|
4
5
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
5
6
|
|
|
@@ -18,13 +19,9 @@ function isrecord(value) {
|
|
|
18
19
|
* @returns {boolean}
|
|
19
20
|
*/
|
|
20
21
|
function ismissing(error) {
|
|
21
|
-
if (!error || typeof error !== 'object')
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
22
|
+
if (!error || typeof error !== 'object') return false;
|
|
24
23
|
const code = 'code' in error ? error.code : undefined;
|
|
25
|
-
if (code === -32601)
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
24
|
+
if (code === -32601) return true;
|
|
28
25
|
const message = 'message' in error ? String(error.message) : '';
|
|
29
26
|
return message.includes('Method') && message.includes('not found');
|
|
30
27
|
}
|
|
@@ -44,9 +41,7 @@ function refiners(validator, check) {
|
|
|
44
41
|
*/
|
|
45
42
|
function apply(value, ctx) {
|
|
46
43
|
const ok = check(value);
|
|
47
|
-
if (ok)
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
44
|
+
if (ok) return;
|
|
50
45
|
const text = validator.errorsText(check.errors, { dataVar: 'value' });
|
|
51
46
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: text });
|
|
52
47
|
}
|
|
@@ -88,13 +83,9 @@ async function page(call, pull) {
|
|
|
88
83
|
while (true) {
|
|
89
84
|
const result = await call(cursor);
|
|
90
85
|
const batch = pull(result);
|
|
91
|
-
if (Array.isArray(batch))
|
|
92
|
-
list.push(...batch);
|
|
93
|
-
}
|
|
86
|
+
if (Array.isArray(batch)) list.push(...batch);
|
|
94
87
|
cursor = result.nextCursor;
|
|
95
|
-
if (!cursor)
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
88
|
+
if (!cursor) break;
|
|
98
89
|
}
|
|
99
90
|
|
|
100
91
|
return list;
|
|
@@ -190,9 +181,7 @@ async function templates(client) {
|
|
|
190
181
|
try {
|
|
191
182
|
return await page(call, pull);
|
|
192
183
|
} catch (error) {
|
|
193
|
-
if (ismissing(error))
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
184
|
+
if (ismissing(error)) return [];
|
|
196
185
|
throw error;
|
|
197
186
|
}
|
|
198
187
|
}
|
|
@@ -234,12 +223,8 @@ async function prompts(client) {
|
|
|
234
223
|
*/
|
|
235
224
|
function meta(item) {
|
|
236
225
|
const out = {};
|
|
237
|
-
if (Array.isArray(item.icons))
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
if (isrecord(item._meta)) {
|
|
241
|
-
out._meta = item._meta;
|
|
242
|
-
}
|
|
226
|
+
if (Array.isArray(item.icons)) out.icons = item.icons;
|
|
227
|
+
if (isrecord(item._meta)) out._meta = item._meta;
|
|
243
228
|
return out;
|
|
244
229
|
}
|
|
245
230
|
|
|
@@ -250,9 +235,7 @@ function meta(item) {
|
|
|
250
235
|
*/
|
|
251
236
|
function toolmeta(tool) {
|
|
252
237
|
const out = meta(tool);
|
|
253
|
-
if (isrecord(tool.annotations))
|
|
254
|
-
out.annotations = tool.annotations;
|
|
255
|
-
}
|
|
238
|
+
if (isrecord(tool.annotations)) out.annotations = tool.annotations;
|
|
256
239
|
return out;
|
|
257
240
|
}
|
|
258
241
|
|
|
@@ -262,12 +245,8 @@ function toolmeta(tool) {
|
|
|
262
245
|
* @returns {string | undefined}
|
|
263
246
|
*/
|
|
264
247
|
function tooltitle(tool) {
|
|
265
|
-
if (typeof tool.title === 'string')
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
if (isrecord(tool.annotations) && typeof tool.annotations.title === 'string') {
|
|
269
|
-
return tool.annotations.title;
|
|
270
|
-
}
|
|
248
|
+
if (typeof tool.title === 'string') return tool.title;
|
|
249
|
+
if (isrecord(tool.annotations) && typeof tool.annotations.title === 'string') return tool.annotations.title;
|
|
271
250
|
return undefined;
|
|
272
251
|
}
|
|
273
252
|
|
|
@@ -277,26 +256,14 @@ function tooltitle(tool) {
|
|
|
277
256
|
* @returns {Record<string, unknown> | undefined}
|
|
278
257
|
*/
|
|
279
258
|
function uiitem(item) {
|
|
280
|
-
if (!isrecord(item._meta))
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
if (!isrecord(item._meta.ui)) {
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
259
|
+
if (!isrecord(item._meta)) return undefined;
|
|
260
|
+
if (!isrecord(item._meta.ui)) return undefined;
|
|
286
261
|
const ui = /** @type {Record<string, unknown>} */ (item._meta.ui);
|
|
287
262
|
const out = {};
|
|
288
|
-
if (typeof ui.resourceUri === 'string')
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (
|
|
292
|
-
out.csp = ui.csp;
|
|
293
|
-
}
|
|
294
|
-
if (Array.isArray(ui.permissions)) {
|
|
295
|
-
out.permissions = ui.permissions;
|
|
296
|
-
}
|
|
297
|
-
if (Object.keys(out).length === 0) {
|
|
298
|
-
return undefined;
|
|
299
|
-
}
|
|
263
|
+
if (typeof ui.resourceUri === 'string') out.resourceUri = ui.resourceUri;
|
|
264
|
+
if (typeof ui.csp === 'string') out.csp = ui.csp;
|
|
265
|
+
if (Array.isArray(ui.permissions)) out.permissions = ui.permissions;
|
|
266
|
+
if (Object.keys(out).length === 0) return undefined;
|
|
300
267
|
return out;
|
|
301
268
|
}
|
|
302
269
|
|
|
@@ -311,12 +278,8 @@ function toolitem(tool) {
|
|
|
311
278
|
const detail = { input };
|
|
312
279
|
const ui = uiitem(tool);
|
|
313
280
|
|
|
314
|
-
if (isrecord(tool.outputSchema))
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
if (ui) {
|
|
318
|
-
detail.ui = ui;
|
|
319
|
-
}
|
|
281
|
+
if (isrecord(tool.outputSchema)) detail.output = output;
|
|
282
|
+
if (ui) detail.ui = ui;
|
|
320
283
|
|
|
321
284
|
return {
|
|
322
285
|
type: 'tool',
|
|
@@ -375,26 +338,18 @@ function templateitem(template) {
|
|
|
375
338
|
* @returns {{ schema: import('zod').ZodTypeAny, json: Record<string, unknown> | undefined, error?: string }}
|
|
376
339
|
*/
|
|
377
340
|
function promptinput(prompt) {
|
|
378
|
-
if (!Array.isArray(prompt.arguments))
|
|
379
|
-
return wrapschema(undefined);
|
|
380
|
-
}
|
|
341
|
+
if (!Array.isArray(prompt.arguments)) return wrapschema(undefined);
|
|
381
342
|
|
|
382
343
|
const properties = {};
|
|
383
344
|
const required = [];
|
|
384
345
|
|
|
385
346
|
for (const item of prompt.arguments) {
|
|
386
|
-
if (!isrecord(item))
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
if (typeof item.name !== 'string' || item.name.length === 0) {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
347
|
+
if (!isrecord(item)) continue;
|
|
348
|
+
if (typeof item.name !== 'string' || item.name.length === 0) continue;
|
|
392
349
|
properties[item.name] = {
|
|
393
350
|
description: typeof item.description === 'string' ? item.description : undefined
|
|
394
351
|
};
|
|
395
|
-
if (item.required === true)
|
|
396
|
-
required.push(item.name);
|
|
397
|
-
}
|
|
352
|
+
if (item.required === true) required.push(item.name);
|
|
398
353
|
}
|
|
399
354
|
|
|
400
355
|
const json = {
|
|
@@ -468,7 +423,11 @@ function normalize(data) {
|
|
|
468
423
|
*/
|
|
469
424
|
export async function extract(link) {
|
|
470
425
|
if (!link || !link.client) {
|
|
471
|
-
throw new
|
|
426
|
+
throw new LayerError({
|
|
427
|
+
name: 'schema',
|
|
428
|
+
method: 'extract',
|
|
429
|
+
message: 'Expected a Session instance.',
|
|
430
|
+
});
|
|
472
431
|
}
|
|
473
432
|
|
|
474
433
|
const client = link.client;
|
|
@@ -483,18 +442,14 @@ export async function extract(link) {
|
|
|
483
442
|
prompts: undefined
|
|
484
443
|
};
|
|
485
444
|
|
|
486
|
-
if (caps && caps.tools)
|
|
487
|
-
data.tools = await tools(client);
|
|
488
|
-
}
|
|
445
|
+
if (caps && caps.tools) data.tools = await tools(client);
|
|
489
446
|
|
|
490
447
|
if (caps && caps.resources) {
|
|
491
448
|
data.resources = await resources(client);
|
|
492
449
|
data.templates = await templates(client);
|
|
493
450
|
}
|
|
494
451
|
|
|
495
|
-
if (caps && caps.prompts)
|
|
496
|
-
data.prompts = await prompts(client);
|
|
497
|
-
}
|
|
452
|
+
if (caps && caps.prompts) data.prompts = await prompts(client);
|
|
498
453
|
|
|
499
454
|
const items = normalize(data);
|
|
500
455
|
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract MCP tool/resource/prompt schemas into a unified Zod-backed format.
|
|
3
|
+
* @param {import('@mcp-layer/session').Session} link - Active session with a connected MCP server.
|
|
4
|
+
* @returns {Promise<{ server: { info: Record<string, unknown> | undefined, capabilities: Record<string, unknown> | undefined, instructions: string | undefined }, items: Array<Record<string, unknown>> }>}
|
|
5
|
+
*/
|
|
6
|
+
export function extract(link: any): Promise<{
|
|
7
|
+
server: {
|
|
8
|
+
info: Record<string, unknown> | undefined;
|
|
9
|
+
capabilities: Record<string, unknown> | undefined;
|
|
10
|
+
instructions: string | undefined;
|
|
11
|
+
};
|
|
12
|
+
items: Array<Record<string, unknown>>;
|
|
13
|
+
}>;
|