@corbits/mcp 0.3.0 → 0.4.0
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 +120 -26
- package/dist/auth/callback.d.ts +14 -0
- package/dist/auth/callback.js +79 -0
- package/dist/auth/challenge.d.ts +9 -0
- package/dist/auth/challenge.js +52 -0
- package/dist/auth/contracts.d.ts +18 -0
- package/dist/auth/contracts.js +19 -0
- package/dist/auth/http.d.ts +2 -0
- package/dist/auth/http.js +2 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/login.d.ts +62 -0
- package/dist/auth/login.js +175 -0
- package/dist/auth/oauth-http.d.ts +6 -0
- package/dist/auth/oauth-http.js +32 -0
- package/dist/auth/resource-server.d.ts +25 -0
- package/dist/auth/resource-server.js +94 -0
- package/dist/auth/url-policy.d.ts +10 -0
- package/dist/auth/url-policy.js +35 -0
- package/dist/client/resource-budget.d.ts +1 -0
- package/dist/client/resource-budget.js +1 -0
- package/dist/client/transport.d.ts +10 -0
- package/dist/client/transport.js +1 -0
- package/dist/client.d.ts +208 -26
- package/dist/client.js +1128 -90
- package/dist/extensions/apps.d.ts +58 -0
- package/dist/extensions/apps.js +55 -0
- package/dist/extensions/elicitation.d.ts +18 -0
- package/dist/extensions/elicitation.js +55 -0
- package/dist/extensions/index.d.ts +5 -0
- package/dist/extensions/index.js +5 -0
- package/dist/extensions/maturity.d.ts +44 -0
- package/dist/extensions/maturity.js +24 -0
- package/dist/extensions/skills/contracts.d.ts +53 -0
- package/dist/extensions/skills/contracts.js +1 -0
- package/dist/extensions/skills/index.d.ts +52 -0
- package/dist/extensions/skills/index.js +402 -0
- package/dist/extensions/tasks/contracts.d.ts +39 -0
- package/dist/extensions/tasks/contracts.js +1 -0
- package/dist/extensions/tasks/file-store.d.ts +12 -0
- package/dist/extensions/tasks/file-store.js +152 -0
- package/dist/extensions/tasks/index.d.ts +82 -0
- package/dist/extensions/tasks/index.js +221 -0
- package/dist/extensions/tasks/record.d.ts +3 -0
- package/dist/extensions/tasks/record.js +37 -0
- package/dist/hub/discover.d.ts +5 -1
- package/dist/hub/discover.js +36 -13
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/interchange/client/capabilities.d.ts +4 -0
- package/dist/interchange/client/capabilities.js +53 -0
- package/dist/interchange/client/catalog.d.ts +14 -0
- package/dist/interchange/client/catalog.js +52 -0
- package/dist/interchange/client/contracts.d.ts +47 -0
- package/dist/interchange/client/contracts.js +1 -0
- package/dist/interchange/client/projection.d.ts +6 -0
- package/dist/interchange/client/projection.js +92 -0
- package/dist/interchange/client/skills.d.ts +16 -0
- package/dist/interchange/client/skills.js +87 -0
- package/dist/interchange/client.d.ts +8 -0
- package/dist/interchange/client.js +6 -0
- package/dist/interchange/server/contracts.d.ts +65 -0
- package/dist/interchange/server/contracts.js +1 -0
- package/dist/interchange/server.d.ts +5 -0
- package/dist/interchange/server.js +172 -0
- package/dist/naming.d.ts +2 -0
- package/dist/naming.js +7 -0
- package/dist/oauth-discovery.d.ts +107 -1
- package/dist/oauth-discovery.js +292 -56
- package/dist/protocol/content.d.ts +35 -0
- package/dist/protocol/content.js +64 -0
- package/dist/protocol/envelopes.d.ts +14 -0
- package/dist/protocol/envelopes.js +22 -0
- package/dist/protocol/errors.d.ts +26 -0
- package/dist/protocol/errors.js +31 -0
- package/dist/protocol/extensions.d.ts +34 -0
- package/dist/protocol/extensions.js +86 -0
- package/dist/protocol/headers.d.ts +12 -0
- package/dist/protocol/headers.js +128 -0
- package/dist/protocol/index.d.ts +11 -0
- package/dist/protocol/index.js +11 -0
- package/dist/protocol/json-schema.d.ts +8 -0
- package/dist/protocol/json-schema.js +152 -0
- package/dist/protocol/legacy.d.ts +12 -0
- package/dist/protocol/legacy.js +18 -0
- package/dist/protocol/methods.d.ts +116 -0
- package/dist/protocol/methods.js +78 -0
- package/dist/protocol/resource-budget.d.ts +9 -0
- package/dist/protocol/resource-budget.js +206 -0
- package/dist/protocol/schemas.d.ts +115 -0
- package/dist/protocol/schemas.js +553 -0
- package/dist/protocol/types.d.ts +295 -0
- package/dist/protocol/types.js +3 -0
- package/dist/protocol/ui.d.ts +3 -0
- package/dist/protocol/ui.js +18 -0
- package/dist/server/contracts.d.ts +67 -0
- package/dist/server/contracts.js +1 -0
- package/dist/server/errors.d.ts +6 -0
- package/dist/server/errors.js +21 -0
- package/dist/server/extensions.d.ts +7 -0
- package/dist/server/extensions.js +61 -0
- package/dist/server/fixtures.d.ts +2 -0
- package/dist/server/fixtures.js +34 -0
- package/dist/server/legacy.d.ts +9 -0
- package/dist/server/legacy.js +435 -0
- package/dist/server/tools.d.ts +17 -0
- package/dist/server/tools.js +93 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +406 -0
- package/dist/sidecar-bundle.d.ts +7 -7
- package/dist/sidecar-bundle.js +36 -29
- package/dist/test-server.js +12 -3
- package/dist/tool.d.ts +8 -0
- package/dist/tool.js +35 -19
- package/dist/url.js +8 -3
- package/docs/conformance.md +135 -0
- package/docs/integration-status.md +98 -0
- package/docs/legacy-server.md +58 -0
- package/docs/migration.md +143 -0
- package/docs/public-api.md +106 -0
- package/examples/README.md +28 -0
- package/examples/standalone-client.ts +14 -0
- package/examples/standalone-server.ts +37 -0
- package/package.json +50 -15
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { McpValidationContext, McpExtensionSettings } from "./types.js";
|
|
2
|
+
export declare const MCP_EXT_TASKS = "io.modelcontextprotocol/tasks";
|
|
3
|
+
export declare const MCP_EXT_SKILLS = "io.modelcontextprotocol/skills";
|
|
4
|
+
export declare const MCP_EXT_UI = "io.modelcontextprotocol/ui";
|
|
5
|
+
export declare const MCP_EXTENSIONS: {
|
|
6
|
+
readonly "io.modelcontextprotocol/tasks": {
|
|
7
|
+
readonly id: "io.modelcontextprotocol/tasks";
|
|
8
|
+
readonly coreRevision: "2026-07-28";
|
|
9
|
+
readonly extensionRevision: "2026-07-28";
|
|
10
|
+
readonly sourceCommit: "b3d28aceaa6d49f5f92258e3d06b61ad6f111e84";
|
|
11
|
+
readonly maturity: "stable";
|
|
12
|
+
readonly enabledByDefault: false;
|
|
13
|
+
};
|
|
14
|
+
readonly "io.modelcontextprotocol/skills": {
|
|
15
|
+
readonly id: "io.modelcontextprotocol/skills";
|
|
16
|
+
readonly coreRevision: "2026-07-28";
|
|
17
|
+
readonly extensionRevision: "stable";
|
|
18
|
+
readonly sourceCommit: "c1aa87e4c381c82b99aa37471d0eae4ab50f9985";
|
|
19
|
+
readonly maturity: "stable";
|
|
20
|
+
readonly enabledByDefault: false;
|
|
21
|
+
};
|
|
22
|
+
readonly "io.modelcontextprotocol/ui": {
|
|
23
|
+
readonly id: "io.modelcontextprotocol/ui";
|
|
24
|
+
readonly coreRevision: "2026-07-28";
|
|
25
|
+
readonly extensionRevision: "2026-01-26";
|
|
26
|
+
readonly sourceCommit: "266a9e3b70dbd4a4f0546dda95da2a2c1a8be4b7";
|
|
27
|
+
readonly maturity: "released";
|
|
28
|
+
readonly enabledByDefault: false;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare function extensionEnabled(context: McpValidationContext, id: string): boolean;
|
|
32
|
+
export declare function validateExtensionSettings(settings: McpExtensionSettings): McpExtensionSettings;
|
|
33
|
+
/** Negotiate only explicitly requested, known extensions advertised by the peer. */
|
|
34
|
+
export declare function negotiateExtensions(requested: McpExtensionSettings, peer: McpExtensionSettings): McpExtensionSettings;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { McpError } from "./errors.js";
|
|
2
|
+
export const MCP_EXT_TASKS = "io.modelcontextprotocol/tasks";
|
|
3
|
+
export const MCP_EXT_SKILLS = "io.modelcontextprotocol/skills";
|
|
4
|
+
export const MCP_EXT_UI = "io.modelcontextprotocol/ui";
|
|
5
|
+
export const MCP_EXTENSIONS = {
|
|
6
|
+
[MCP_EXT_TASKS]: {
|
|
7
|
+
id: MCP_EXT_TASKS,
|
|
8
|
+
coreRevision: "2026-07-28",
|
|
9
|
+
extensionRevision: "2026-07-28",
|
|
10
|
+
sourceCommit: "b3d28aceaa6d49f5f92258e3d06b61ad6f111e84",
|
|
11
|
+
maturity: "stable",
|
|
12
|
+
enabledByDefault: false,
|
|
13
|
+
},
|
|
14
|
+
[MCP_EXT_SKILLS]: {
|
|
15
|
+
id: MCP_EXT_SKILLS,
|
|
16
|
+
coreRevision: "2026-07-28",
|
|
17
|
+
extensionRevision: "stable",
|
|
18
|
+
sourceCommit: "c1aa87e4c381c82b99aa37471d0eae4ab50f9985",
|
|
19
|
+
maturity: "stable",
|
|
20
|
+
enabledByDefault: false,
|
|
21
|
+
},
|
|
22
|
+
[MCP_EXT_UI]: {
|
|
23
|
+
id: MCP_EXT_UI,
|
|
24
|
+
coreRevision: "2026-07-28",
|
|
25
|
+
extensionRevision: "2026-01-26",
|
|
26
|
+
sourceCommit: "266a9e3b70dbd4a4f0546dda95da2a2c1a8be4b7",
|
|
27
|
+
maturity: "released",
|
|
28
|
+
enabledByDefault: false,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export function extensionEnabled(context, id) {
|
|
32
|
+
const extensions = context.extensions;
|
|
33
|
+
if (extensions === undefined)
|
|
34
|
+
return false;
|
|
35
|
+
return Array.isArray(extensions)
|
|
36
|
+
? extensions.includes(id)
|
|
37
|
+
: Object.hasOwn(extensions, id);
|
|
38
|
+
}
|
|
39
|
+
export function validateExtensionSettings(settings) {
|
|
40
|
+
for (const [id, value] of Object.entries(settings)) {
|
|
41
|
+
if (!Object.hasOwn(MCP_EXTENSIONS, id))
|
|
42
|
+
throw new McpError(`unsupported extension ${id}`);
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
44
|
+
throw new McpError(`invalid settings for ${id}`);
|
|
45
|
+
const allowed = id === MCP_EXT_SKILLS
|
|
46
|
+
? ["directoryRead"]
|
|
47
|
+
: id === MCP_EXT_UI
|
|
48
|
+
? ["mimeTypes"]
|
|
49
|
+
: [];
|
|
50
|
+
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
51
|
+
throw new McpError(`unsupported settings for ${id}`);
|
|
52
|
+
if (value.directoryRead !== undefined &&
|
|
53
|
+
typeof value.directoryRead !== "boolean")
|
|
54
|
+
throw new McpError("directoryRead must be boolean");
|
|
55
|
+
if (value.mimeTypes !== undefined &&
|
|
56
|
+
(!Array.isArray(value.mimeTypes) ||
|
|
57
|
+
!value.mimeTypes.every((v) => typeof v === "string")))
|
|
58
|
+
throw new McpError("mimeTypes must be strings");
|
|
59
|
+
}
|
|
60
|
+
return settings;
|
|
61
|
+
}
|
|
62
|
+
/** Negotiate only explicitly requested, known extensions advertised by the peer. */
|
|
63
|
+
export function negotiateExtensions(requested, peer) {
|
|
64
|
+
validateExtensionSettings(requested);
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [id, settings] of Object.entries(requested)) {
|
|
67
|
+
if (!Object.hasOwn(peer, id))
|
|
68
|
+
continue;
|
|
69
|
+
const remote = peer[id];
|
|
70
|
+
if (remote === undefined)
|
|
71
|
+
continue;
|
|
72
|
+
validateExtensionSettings({ [id]: remote });
|
|
73
|
+
if (id === MCP_EXT_UI) {
|
|
74
|
+
const localTypes = settings.mimeTypes;
|
|
75
|
+
const remoteTypes = remote.mimeTypes;
|
|
76
|
+
result[id] = {
|
|
77
|
+
mimeTypes: (localTypes ?? ["text/html;profile=mcp-app"]).filter((mime) => (remoteTypes ?? ["text/html;profile=mcp-app"]).includes(mime)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
else if (id === MCP_EXT_SKILLS)
|
|
81
|
+
result[id] = { directoryRead: remote.directoryRead === true };
|
|
82
|
+
else
|
|
83
|
+
result[id] = {};
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type McpValidationContext } from "./types.js";
|
|
2
|
+
export declare function encodeHeaderValue(value: string): string;
|
|
3
|
+
export declare function decodeHeaderValue(value: string): string;
|
|
4
|
+
export type HeaderSlot = {
|
|
5
|
+
path: string[];
|
|
6
|
+
header: string;
|
|
7
|
+
type: "string" | "integer" | "boolean";
|
|
8
|
+
};
|
|
9
|
+
/** Throws for annotations that cannot be statically resolved through properties. */
|
|
10
|
+
export declare function collectHeaderSlots(schema: Record<string, unknown>): HeaderSlot[];
|
|
11
|
+
export declare function paramHeaders(schema: Record<string, unknown> | undefined, args: unknown, _toolName?: string): Record<string, string>;
|
|
12
|
+
export declare function validateRequestHeaders(headers: Headers, method: string, params: Record<string, unknown>, context?: McpValidationContext): void;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { McpError, McpErrorCode } from "./errors.js";
|
|
2
|
+
import { mcpNameFor } from "./methods.js";
|
|
3
|
+
import { MCP_PROTOCOL_VERSION } from "./types.js";
|
|
4
|
+
export function encodeHeaderValue(value) {
|
|
5
|
+
const plain = [...value].every((character) => character === "\t" ||
|
|
6
|
+
(character.charCodeAt(0) >= 0x20 && character.charCodeAt(0) <= 0x7e)) &&
|
|
7
|
+
!/^[ \t]|[ \t]$/.test(value) &&
|
|
8
|
+
!(value.startsWith("=?base64?") && value.endsWith("?="));
|
|
9
|
+
return plain
|
|
10
|
+
? value
|
|
11
|
+
: `=?base64?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
12
|
+
}
|
|
13
|
+
export function decodeHeaderValue(value) {
|
|
14
|
+
if (!(value.startsWith("=?base64?") && value.endsWith("?=")))
|
|
15
|
+
return value;
|
|
16
|
+
const match = /^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/.exec(value);
|
|
17
|
+
if (match?.[1] === undefined)
|
|
18
|
+
throw new McpError("invalid encoded MCP header", 400, McpErrorCode.HeaderMismatch);
|
|
19
|
+
const bytes = Buffer.from(match[1], "base64");
|
|
20
|
+
if (bytes.toString("base64") !== match[1])
|
|
21
|
+
throw new McpError("invalid base64 MCP header", 400, McpErrorCode.HeaderMismatch);
|
|
22
|
+
try {
|
|
23
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new McpError("invalid UTF-8 MCP header", 400, McpErrorCode.HeaderMismatch);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Throws for annotations that cannot be statically resolved through properties. */
|
|
30
|
+
export function collectHeaderSlots(schema) {
|
|
31
|
+
const slots = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
let nodes = 0;
|
|
34
|
+
const walk = (node, path, reachable, depth) => {
|
|
35
|
+
if (++nodes > 10_000 || depth > 64)
|
|
36
|
+
throw new McpError("header schema complexity limit exceeded");
|
|
37
|
+
if (typeof node !== "object" || node === null)
|
|
38
|
+
return;
|
|
39
|
+
if (Array.isArray(node)) {
|
|
40
|
+
for (const item of node)
|
|
41
|
+
walk(item, path, false, depth + 1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const record = node;
|
|
45
|
+
if (Object.hasOwn(record, "x-mcp-header")) {
|
|
46
|
+
const header = record["x-mcp-header"];
|
|
47
|
+
const kind = record.type;
|
|
48
|
+
if (!reachable ||
|
|
49
|
+
path.length === 0 ||
|
|
50
|
+
typeof header !== "string" ||
|
|
51
|
+
!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(header) ||
|
|
52
|
+
(kind !== "string" && kind !== "integer" && kind !== "boolean"))
|
|
53
|
+
throw new McpError("invalid x-mcp-header annotation");
|
|
54
|
+
if (seen.has(header.toLowerCase()))
|
|
55
|
+
throw new McpError("duplicate x-mcp-header annotation");
|
|
56
|
+
seen.add(header.toLowerCase());
|
|
57
|
+
slots.push({ path, header, type: kind });
|
|
58
|
+
}
|
|
59
|
+
for (const [key, child] of Object.entries(record)) {
|
|
60
|
+
if (key === "properties" &&
|
|
61
|
+
typeof child === "object" &&
|
|
62
|
+
child !== null &&
|
|
63
|
+
!Array.isArray(child)) {
|
|
64
|
+
for (const [name, value] of Object.entries(child))
|
|
65
|
+
walk(value, [...path, name], reachable, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
else if (key !== "x-mcp-header")
|
|
68
|
+
walk(child, path, false, depth + 1);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walk(schema, [], true, 0);
|
|
72
|
+
return slots;
|
|
73
|
+
}
|
|
74
|
+
export function paramHeaders(schema, args, _toolName) {
|
|
75
|
+
if (schema === undefined)
|
|
76
|
+
return {};
|
|
77
|
+
const headers = {};
|
|
78
|
+
for (const slot of collectHeaderSlots(schema)) {
|
|
79
|
+
let value = args;
|
|
80
|
+
for (const key of slot.path)
|
|
81
|
+
value =
|
|
82
|
+
typeof value === "object" && value !== null && Object.hasOwn(value, key)
|
|
83
|
+
? value[key]
|
|
84
|
+
: undefined;
|
|
85
|
+
if (value === undefined || value === null)
|
|
86
|
+
continue;
|
|
87
|
+
if (slot.type === "integer"
|
|
88
|
+
? typeof value !== "number" || !Number.isSafeInteger(value)
|
|
89
|
+
: typeof value !== slot.type)
|
|
90
|
+
throw new McpError(`invalid header argument ${slot.path.join(".")}`);
|
|
91
|
+
headers[`Mcp-Param-${slot.header}`] = encodeHeaderValue(String(value));
|
|
92
|
+
}
|
|
93
|
+
return headers;
|
|
94
|
+
}
|
|
95
|
+
export function validateRequestHeaders(headers, method, params, context = {}) {
|
|
96
|
+
if (context.era === "legacy")
|
|
97
|
+
return;
|
|
98
|
+
const meta = params._meta;
|
|
99
|
+
const version = meta?.["io.modelcontextprotocol/protocolVersion"];
|
|
100
|
+
const fail = (header) => {
|
|
101
|
+
throw new McpError(`MCP header mismatch: ${header}`, 400, McpErrorCode.HeaderMismatch);
|
|
102
|
+
};
|
|
103
|
+
if (typeof version !== "string" ||
|
|
104
|
+
headers.get("MCP-Protocol-Version") !== version)
|
|
105
|
+
fail("MCP-Protocol-Version");
|
|
106
|
+
if (version !== (context.protocolVersion ?? MCP_PROTOCOL_VERSION))
|
|
107
|
+
throw new McpError("unsupported protocol version", 400, McpErrorCode.UnsupportedProtocolVersion, { requested: version, supported: [MCP_PROTOCOL_VERSION] });
|
|
108
|
+
if (headers.get("Mcp-Method") !== method)
|
|
109
|
+
fail("Mcp-Method");
|
|
110
|
+
const name = mcpNameFor(method, params);
|
|
111
|
+
const actual = headers.get("Mcp-Name");
|
|
112
|
+
if (name !== undefined &&
|
|
113
|
+
(actual === null || decodeHeaderValue(actual) !== name))
|
|
114
|
+
fail("Mcp-Name");
|
|
115
|
+
if (name === undefined && actual !== null)
|
|
116
|
+
fail("Mcp-Name");
|
|
117
|
+
const expected = paramHeaders(context.inputSchema, params.arguments);
|
|
118
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
119
|
+
const received = headers.get(key);
|
|
120
|
+
if (received === null ||
|
|
121
|
+
decodeHeaderValue(received) !== decodeHeaderValue(value))
|
|
122
|
+
fail(key);
|
|
123
|
+
}
|
|
124
|
+
for (const key of headers.keys())
|
|
125
|
+
if (key.startsWith("mcp-param-") &&
|
|
126
|
+
!Object.keys(expected).some((name) => name.toLowerCase() === key))
|
|
127
|
+
fail(key);
|
|
128
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./errors.js";
|
|
3
|
+
export * from "./extensions.js";
|
|
4
|
+
export * from "./methods.js";
|
|
5
|
+
export * from "./headers.js";
|
|
6
|
+
export * from "./json-schema.js";
|
|
7
|
+
export * from "./schemas.js";
|
|
8
|
+
export * from "./envelopes.js";
|
|
9
|
+
export * from "./ui.js";
|
|
10
|
+
export * from "./content.js";
|
|
11
|
+
export * from "./legacy.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./errors.js";
|
|
3
|
+
export * from "./extensions.js";
|
|
4
|
+
export * from "./methods.js";
|
|
5
|
+
export * from "./headers.js";
|
|
6
|
+
export * from "./json-schema.js";
|
|
7
|
+
export * from "./schemas.js";
|
|
8
|
+
export * from "./envelopes.js";
|
|
9
|
+
export * from "./ui.js";
|
|
10
|
+
export * from "./content.js";
|
|
11
|
+
export * from "./legacy.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JsonSchema } from "./types.js";
|
|
2
|
+
/** Limits apply before AJV compilation and before data validation. No network resolver is installed. */
|
|
3
|
+
export declare function assertJsonBounds(value: unknown, schema?: boolean): void;
|
|
4
|
+
/** Resource payloads alone get an explicit allowance; metadata and JSON structure
|
|
5
|
+
* remain bounded. */
|
|
6
|
+
export declare function assertResourceJsonBounds(value: unknown, maxDecodedBytes: number): void;
|
|
7
|
+
export declare function validateSchema(schema: unknown): JsonSchema;
|
|
8
|
+
export declare function validateInstance(schema: unknown, value: unknown): void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { Ajv } from "ajv";
|
|
3
|
+
import { McpError, McpErrorCode } from "./errors.js";
|
|
4
|
+
const options = {
|
|
5
|
+
strict: false,
|
|
6
|
+
allErrors: false,
|
|
7
|
+
validateFormats: false,
|
|
8
|
+
addUsedSchema: false,
|
|
9
|
+
ownProperties: true,
|
|
10
|
+
code: { optimize: false },
|
|
11
|
+
loopRequired: 100,
|
|
12
|
+
loopEnum: 100,
|
|
13
|
+
};
|
|
14
|
+
const modern = new Ajv2020(options);
|
|
15
|
+
const draft7 = new Ajv(options);
|
|
16
|
+
const cache = new Map();
|
|
17
|
+
/** Limits apply before AJV compilation and before data validation. No network resolver is installed. */
|
|
18
|
+
export function assertJsonBounds(value, schema = false) {
|
|
19
|
+
assertBounds(value, schema);
|
|
20
|
+
}
|
|
21
|
+
/** Resource payloads alone get an explicit allowance; metadata and JSON structure
|
|
22
|
+
* remain bounded. */
|
|
23
|
+
export function assertResourceJsonBounds(value, maxDecodedBytes) {
|
|
24
|
+
if (!Number.isSafeInteger(maxDecodedBytes) || maxDecodedBytes < 0)
|
|
25
|
+
throw new McpError("maxDecodedBytes must be a nonnegative safe integer");
|
|
26
|
+
assertBounds(value, false, Math.min(maxDecodedBytes, 16 * 1024 * 1024));
|
|
27
|
+
}
|
|
28
|
+
function assertBounds(value, schema, resourceBytes) {
|
|
29
|
+
let nodes = 0;
|
|
30
|
+
let bytes = 0;
|
|
31
|
+
let decodedBytes = 0;
|
|
32
|
+
const active = new Set();
|
|
33
|
+
const visit = (item, depth, path) => {
|
|
34
|
+
if (++nodes > (schema ? 10_000 : 100_000) || depth > 64)
|
|
35
|
+
throw new McpError("JSON complexity limit exceeded");
|
|
36
|
+
if (typeof item === "string") {
|
|
37
|
+
const payloadPath = path;
|
|
38
|
+
const payload = resourceBytes !== undefined &&
|
|
39
|
+
payloadPath.length === 3 &&
|
|
40
|
+
payloadPath[0] === "contents" &&
|
|
41
|
+
payloadPath[1] === "*" &&
|
|
42
|
+
(payloadPath[2] === "text" || payloadPath[2] === "blob");
|
|
43
|
+
if (payload) {
|
|
44
|
+
decodedBytes +=
|
|
45
|
+
payloadPath[2] === "text"
|
|
46
|
+
? Buffer.byteLength(item)
|
|
47
|
+
: Math.max(0, Math.floor((item.length * 3) / 4) -
|
|
48
|
+
(item.endsWith("==") ? 2 : item.endsWith("=") ? 1 : 0));
|
|
49
|
+
if (decodedBytes > resourceBytes)
|
|
50
|
+
throw new McpError("Resource decoded byte limit exceeded");
|
|
51
|
+
bytes += 2; // JSON string delimiters, not payload encoding
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
bytes += Buffer.byteLength(item);
|
|
55
|
+
if (resourceBytes !== undefined) {
|
|
56
|
+
if (bytes > 64 * 1024)
|
|
57
|
+
throw new McpError("Resource metadata/envelope byte limit exceeded");
|
|
58
|
+
bytes +=
|
|
59
|
+
Buffer.byteLength(JSON.stringify(item)) - Buffer.byteLength(item);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (typeof item === "number" && !Number.isFinite(item))
|
|
64
|
+
throw new McpError("JSON numbers must be finite");
|
|
65
|
+
else if (typeof item === "object" && item !== null) {
|
|
66
|
+
if (active.has(item))
|
|
67
|
+
throw new McpError("cyclic JSON value");
|
|
68
|
+
active.add(item);
|
|
69
|
+
if (resourceBytes !== undefined)
|
|
70
|
+
bytes += 2; // braces/brackets
|
|
71
|
+
let first = true;
|
|
72
|
+
for (const [key, child] of Object.entries(item)) {
|
|
73
|
+
if (resourceBytes === undefined)
|
|
74
|
+
bytes += Buffer.byteLength(key);
|
|
75
|
+
else {
|
|
76
|
+
if (!first)
|
|
77
|
+
bytes++;
|
|
78
|
+
if (!Array.isArray(item)) {
|
|
79
|
+
if (Buffer.byteLength(key) > 64 * 1024)
|
|
80
|
+
throw new McpError("Resource metadata/envelope byte limit exceeded");
|
|
81
|
+
bytes += Buffer.byteLength(JSON.stringify(key)) + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
first = false;
|
|
85
|
+
visit(child, depth + 1, [...path, Array.isArray(item) ? "*" : key]);
|
|
86
|
+
}
|
|
87
|
+
active.delete(item);
|
|
88
|
+
}
|
|
89
|
+
else if (item !== null &&
|
|
90
|
+
typeof item !== "boolean" &&
|
|
91
|
+
typeof item !== "number")
|
|
92
|
+
throw new McpError("non-JSON value");
|
|
93
|
+
if (resourceBytes !== undefined) {
|
|
94
|
+
if (item === null ||
|
|
95
|
+
typeof item === "boolean" ||
|
|
96
|
+
typeof item === "number")
|
|
97
|
+
bytes += String(item).length;
|
|
98
|
+
if (bytes > 64 * 1024)
|
|
99
|
+
throw new McpError("Resource metadata/envelope byte limit exceeded");
|
|
100
|
+
}
|
|
101
|
+
if (resourceBytes === undefined &&
|
|
102
|
+
bytes > (schema ? 256 * 1024 : 4 * 1024 * 1024))
|
|
103
|
+
throw new McpError("JSON byte limit exceeded");
|
|
104
|
+
};
|
|
105
|
+
visit(value, 0, []);
|
|
106
|
+
}
|
|
107
|
+
function compile(schema) {
|
|
108
|
+
assertJsonBounds(schema, true);
|
|
109
|
+
if (typeof schema !== "boolean" &&
|
|
110
|
+
(typeof schema !== "object" || schema === null || Array.isArray(schema)))
|
|
111
|
+
throw new McpError("JSON Schema must be an object or boolean");
|
|
112
|
+
const key = JSON.stringify(schema);
|
|
113
|
+
const existing = cache.get(key);
|
|
114
|
+
if (existing !== undefined)
|
|
115
|
+
return existing;
|
|
116
|
+
const dialect = typeof schema === "object" && schema !== null
|
|
117
|
+
? schema.$schema
|
|
118
|
+
: undefined;
|
|
119
|
+
const validator = dialect === undefined ||
|
|
120
|
+
dialect === "https://json-schema.org/draft/2020-12/schema"
|
|
121
|
+
? modern
|
|
122
|
+
: dialect === "http://json-schema.org/draft-07/schema#" ||
|
|
123
|
+
dialect === "http://json-schema.org/draft-07/schema"
|
|
124
|
+
? draft7
|
|
125
|
+
: undefined;
|
|
126
|
+
if (validator === undefined)
|
|
127
|
+
throw new McpError(`unsupported JSON Schema dialect ${String(dialect)}`);
|
|
128
|
+
try {
|
|
129
|
+
// Compile an immutable snapshot: caller mutation cannot change a cached validator.
|
|
130
|
+
const result = validator.compile(JSON.parse(key));
|
|
131
|
+
if (cache.size >= 128) {
|
|
132
|
+
const first = cache.keys().next().value;
|
|
133
|
+
if (first !== undefined)
|
|
134
|
+
cache.delete(first);
|
|
135
|
+
}
|
|
136
|
+
cache.set(key, result);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
catch (cause) {
|
|
140
|
+
throw new McpError(`invalid JSON Schema: ${cause instanceof Error ? cause.message : String(cause)}`, undefined, McpErrorCode.InvalidParams);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export function validateSchema(schema) {
|
|
144
|
+
compile(schema);
|
|
145
|
+
return schema;
|
|
146
|
+
}
|
|
147
|
+
export function validateInstance(schema, value) {
|
|
148
|
+
assertJsonBounds(value);
|
|
149
|
+
const validator = compile(schema);
|
|
150
|
+
if (!validator(value))
|
|
151
|
+
throw new McpError(`JSON Schema instance validation failed: ${modern.errorsText(validator.errors)}`, undefined, McpErrorCode.InvalidParams);
|
|
152
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Lifecycle request shared by the three supported Streamable HTTP revisions. */
|
|
2
|
+
declare const Initialize: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
3
|
+
protocolVersion: string;
|
|
4
|
+
capabilities: Record<string, unknown>;
|
|
5
|
+
clientInfo: {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
};
|
|
9
|
+
_meta?: Record<string, unknown>;
|
|
10
|
+
}, {}>;
|
|
11
|
+
export declare function validateLegacyInitialize(value: unknown): typeof Initialize.infer;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type } from "arktype";
|
|
2
|
+
import { McpError, McpErrorCode } from "./errors.js";
|
|
3
|
+
import { assertJsonBounds } from "./json-schema.js";
|
|
4
|
+
/** Lifecycle request shared by the three supported Streamable HTTP revisions. */
|
|
5
|
+
const Initialize = type({
|
|
6
|
+
protocolVersion: "string > 0",
|
|
7
|
+
capabilities: "Record<string, unknown>",
|
|
8
|
+
clientInfo: { name: "string", version: "string" },
|
|
9
|
+
"_meta?": "Record<string, unknown>",
|
|
10
|
+
});
|
|
11
|
+
export function validateLegacyInitialize(value) {
|
|
12
|
+
assertJsonBounds(value);
|
|
13
|
+
const params = Initialize(value);
|
|
14
|
+
if (params instanceof type.errors || Array.isArray(params.capabilities)) {
|
|
15
|
+
throw new McpError("Invalid initialize parameters", 400, McpErrorCode.InvalidParams);
|
|
16
|
+
}
|
|
17
|
+
return params;
|
|
18
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export interface McpMethodDescriptor {
|
|
2
|
+
capability?: string;
|
|
3
|
+
extension?: string;
|
|
4
|
+
cacheable: boolean;
|
|
5
|
+
inputRequired: boolean;
|
|
6
|
+
task: boolean;
|
|
7
|
+
nameSource?: "name" | "uri" | "taskId";
|
|
8
|
+
}
|
|
9
|
+
export declare const MCP_METHODS: {
|
|
10
|
+
readonly "server/discover": {
|
|
11
|
+
readonly cacheable: true;
|
|
12
|
+
readonly inputRequired: false;
|
|
13
|
+
readonly task: false;
|
|
14
|
+
};
|
|
15
|
+
readonly "tools/list": {
|
|
16
|
+
readonly capability: "tools";
|
|
17
|
+
readonly cacheable: true;
|
|
18
|
+
readonly inputRequired: false;
|
|
19
|
+
readonly task: false;
|
|
20
|
+
};
|
|
21
|
+
readonly "tools/call": {
|
|
22
|
+
readonly capability: "tools";
|
|
23
|
+
readonly inputRequired: true;
|
|
24
|
+
readonly task: true;
|
|
25
|
+
readonly nameSource: "name";
|
|
26
|
+
readonly cacheable: false;
|
|
27
|
+
};
|
|
28
|
+
readonly "prompts/list": {
|
|
29
|
+
readonly capability: "prompts";
|
|
30
|
+
readonly cacheable: true;
|
|
31
|
+
readonly inputRequired: false;
|
|
32
|
+
readonly task: false;
|
|
33
|
+
};
|
|
34
|
+
readonly "prompts/get": {
|
|
35
|
+
readonly capability: "prompts";
|
|
36
|
+
readonly inputRequired: true;
|
|
37
|
+
readonly nameSource: "name";
|
|
38
|
+
readonly cacheable: false;
|
|
39
|
+
readonly task: false;
|
|
40
|
+
};
|
|
41
|
+
readonly "resources/list": {
|
|
42
|
+
readonly capability: "resources";
|
|
43
|
+
readonly cacheable: true;
|
|
44
|
+
readonly inputRequired: false;
|
|
45
|
+
readonly task: false;
|
|
46
|
+
};
|
|
47
|
+
readonly "resources/templates/list": {
|
|
48
|
+
readonly capability: "resources";
|
|
49
|
+
readonly cacheable: true;
|
|
50
|
+
readonly inputRequired: false;
|
|
51
|
+
readonly task: false;
|
|
52
|
+
};
|
|
53
|
+
readonly "resources/read": {
|
|
54
|
+
readonly capability: "resources";
|
|
55
|
+
readonly cacheable: true;
|
|
56
|
+
readonly inputRequired: true;
|
|
57
|
+
readonly nameSource: "uri";
|
|
58
|
+
readonly task: false;
|
|
59
|
+
};
|
|
60
|
+
readonly "completion/complete": {
|
|
61
|
+
readonly capability: "completions";
|
|
62
|
+
readonly cacheable: false;
|
|
63
|
+
readonly inputRequired: false;
|
|
64
|
+
readonly task: false;
|
|
65
|
+
};
|
|
66
|
+
readonly "subscriptions/listen": {
|
|
67
|
+
readonly capability: "subscriptions";
|
|
68
|
+
readonly cacheable: false;
|
|
69
|
+
readonly inputRequired: false;
|
|
70
|
+
readonly task: false;
|
|
71
|
+
};
|
|
72
|
+
readonly "tasks/get": {
|
|
73
|
+
readonly extension: "io.modelcontextprotocol/tasks";
|
|
74
|
+
readonly nameSource: "taskId";
|
|
75
|
+
readonly cacheable: false;
|
|
76
|
+
readonly inputRequired: false;
|
|
77
|
+
readonly task: false;
|
|
78
|
+
};
|
|
79
|
+
readonly "tasks/update": {
|
|
80
|
+
readonly extension: "io.modelcontextprotocol/tasks";
|
|
81
|
+
readonly nameSource: "taskId";
|
|
82
|
+
readonly cacheable: false;
|
|
83
|
+
readonly inputRequired: false;
|
|
84
|
+
readonly task: false;
|
|
85
|
+
};
|
|
86
|
+
readonly "tasks/cancel": {
|
|
87
|
+
readonly extension: "io.modelcontextprotocol/tasks";
|
|
88
|
+
readonly nameSource: "taskId";
|
|
89
|
+
readonly cacheable: false;
|
|
90
|
+
readonly inputRequired: false;
|
|
91
|
+
readonly task: false;
|
|
92
|
+
};
|
|
93
|
+
readonly "skills/list": {
|
|
94
|
+
readonly extension: "io.modelcontextprotocol/skills";
|
|
95
|
+
readonly capability: "resources";
|
|
96
|
+
readonly cacheable: true;
|
|
97
|
+
readonly inputRequired: false;
|
|
98
|
+
readonly task: false;
|
|
99
|
+
};
|
|
100
|
+
readonly "skills/get": {
|
|
101
|
+
readonly extension: "io.modelcontextprotocol/skills";
|
|
102
|
+
readonly capability: "resources";
|
|
103
|
+
readonly cacheable: true;
|
|
104
|
+
readonly inputRequired: false;
|
|
105
|
+
readonly task: false;
|
|
106
|
+
};
|
|
107
|
+
readonly "resources/directory/read": {
|
|
108
|
+
readonly extension: "io.modelcontextprotocol/skills";
|
|
109
|
+
readonly capability: "resources";
|
|
110
|
+
readonly cacheable: true;
|
|
111
|
+
readonly inputRequired: false;
|
|
112
|
+
readonly task: false;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
export declare function methodDescriptor(method: string): McpMethodDescriptor | undefined;
|
|
116
|
+
export declare function mcpNameFor(method: string, params?: Record<string, unknown>): string | undefined;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MCP_EXT_SKILLS, MCP_EXT_TASKS } from "./extensions.js";
|
|
2
|
+
const ordinary = {
|
|
3
|
+
cacheable: false,
|
|
4
|
+
inputRequired: false,
|
|
5
|
+
task: false,
|
|
6
|
+
};
|
|
7
|
+
export const MCP_METHODS = {
|
|
8
|
+
"server/discover": { ...ordinary, cacheable: true },
|
|
9
|
+
"tools/list": { ...ordinary, capability: "tools", cacheable: true },
|
|
10
|
+
"tools/call": {
|
|
11
|
+
...ordinary,
|
|
12
|
+
capability: "tools",
|
|
13
|
+
inputRequired: true,
|
|
14
|
+
task: true,
|
|
15
|
+
nameSource: "name",
|
|
16
|
+
},
|
|
17
|
+
"prompts/list": { ...ordinary, capability: "prompts", cacheable: true },
|
|
18
|
+
"prompts/get": {
|
|
19
|
+
...ordinary,
|
|
20
|
+
capability: "prompts",
|
|
21
|
+
inputRequired: true,
|
|
22
|
+
nameSource: "name",
|
|
23
|
+
},
|
|
24
|
+
"resources/list": { ...ordinary, capability: "resources", cacheable: true },
|
|
25
|
+
"resources/templates/list": {
|
|
26
|
+
...ordinary,
|
|
27
|
+
capability: "resources",
|
|
28
|
+
cacheable: true,
|
|
29
|
+
},
|
|
30
|
+
"resources/read": {
|
|
31
|
+
...ordinary,
|
|
32
|
+
capability: "resources",
|
|
33
|
+
cacheable: true,
|
|
34
|
+
inputRequired: true,
|
|
35
|
+
nameSource: "uri",
|
|
36
|
+
},
|
|
37
|
+
"completion/complete": { ...ordinary, capability: "completions" },
|
|
38
|
+
"subscriptions/listen": { ...ordinary, capability: "subscriptions" },
|
|
39
|
+
"tasks/get": { ...ordinary, extension: MCP_EXT_TASKS, nameSource: "taskId" },
|
|
40
|
+
"tasks/update": {
|
|
41
|
+
...ordinary,
|
|
42
|
+
extension: MCP_EXT_TASKS,
|
|
43
|
+
nameSource: "taskId",
|
|
44
|
+
},
|
|
45
|
+
"tasks/cancel": {
|
|
46
|
+
...ordinary,
|
|
47
|
+
extension: MCP_EXT_TASKS,
|
|
48
|
+
nameSource: "taskId",
|
|
49
|
+
},
|
|
50
|
+
"skills/list": {
|
|
51
|
+
...ordinary,
|
|
52
|
+
extension: MCP_EXT_SKILLS,
|
|
53
|
+
capability: "resources",
|
|
54
|
+
cacheable: true,
|
|
55
|
+
},
|
|
56
|
+
"skills/get": {
|
|
57
|
+
...ordinary,
|
|
58
|
+
extension: MCP_EXT_SKILLS,
|
|
59
|
+
capability: "resources",
|
|
60
|
+
cacheable: true,
|
|
61
|
+
},
|
|
62
|
+
"resources/directory/read": {
|
|
63
|
+
...ordinary,
|
|
64
|
+
extension: MCP_EXT_SKILLS,
|
|
65
|
+
capability: "resources",
|
|
66
|
+
cacheable: true,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
export function methodDescriptor(method) {
|
|
70
|
+
return Object.hasOwn(MCP_METHODS, method)
|
|
71
|
+
? MCP_METHODS[method]
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
74
|
+
export function mcpNameFor(method, params) {
|
|
75
|
+
const source = methodDescriptor(method)?.nameSource;
|
|
76
|
+
const value = source === undefined ? undefined : params?.[source];
|
|
77
|
+
return typeof value === "string" ? value : undefined;
|
|
78
|
+
}
|