toolcraft 0.0.410 → 0.0.412
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/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/README.md +51 -0
- package/node_modules/tiny-http-mcp-server/dist/composition.json +11 -1
- package/node_modules/tiny-http-mcp-server/dist/fetch-body.d.ts +6 -0
- package/node_modules/tiny-http-mcp-server/dist/fetch-body.js +47 -0
- package/node_modules/tiny-http-mcp-server/dist/fetch.d.ts +23 -0
- package/node_modules/tiny-http-mcp-server/dist/fetch.js +130 -0
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +1 -2
- package/node_modules/tiny-http-mcp-server/package.json +5 -1
- package/node_modules/tiny-stdio-mcp-server/README.md +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/core.d.ts +8 -0
- package/node_modules/tiny-stdio-mcp-server/dist/core.js +4 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-server.d.ts +48 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-server.js +1156 -0
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +3 -39
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +4 -1144
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +2 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +4 -0
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
import { compileJsonSchema, formatIssues, normalizeLegacyNullability } from "toolcraft-schema";
|
|
2
|
+
import { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
3
|
+
import { parseMessage, formatSuccessResponse, formatErrorResponse, isRequestId } from "./jsonrpc.js";
|
|
4
|
+
import { parseUriTemplate } from "./uri-template.js";
|
|
5
|
+
import { toContentBlocks } from "./content/convert.js";
|
|
6
|
+
import { ToolCallAdmission } from "./tool-call-admission.js";
|
|
7
|
+
import { isBase64 } from "./base64.js";
|
|
8
|
+
import { selectRequestProtocol, decorateModernResult, MODERN_PROTOCOL_VERSION } from "./protocol.js";
|
|
9
|
+
import { SubscriptionRegistry } from "./subscriptions.js";
|
|
10
|
+
import { waitForRequest } from "./request-cancellation.js";
|
|
11
|
+
import { isJsonValue } from "toolcraft-schema";
|
|
12
|
+
import { isValidUri } from "./uri.js";
|
|
13
|
+
import { getParameterHeaders, validateParameterHeaders } from "./headers.js";
|
|
14
|
+
const PROTOCOL_VERSION = "2025-11-25";
|
|
15
|
+
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-03-26", "2025-06-18", PROTOCOL_VERSION, MODERN_PROTOCOL_VERSION]);
|
|
16
|
+
function createLifecycleState(listener, supportNotifications, supportResourceSubscriptions) {
|
|
17
|
+
const abortController = new AbortController();
|
|
18
|
+
const admissions = new Set();
|
|
19
|
+
const requests = new Map();
|
|
20
|
+
abortController.signal.addEventListener("abort", () => {
|
|
21
|
+
for (const admission of admissions)
|
|
22
|
+
admission.abort(abortController.signal.reason);
|
|
23
|
+
admissions.clear();
|
|
24
|
+
for (const request of requests.values())
|
|
25
|
+
request.abort(abortController.signal.reason);
|
|
26
|
+
requests.clear();
|
|
27
|
+
}, { once: true });
|
|
28
|
+
return {
|
|
29
|
+
initialized: false,
|
|
30
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
31
|
+
initializeAccepted: false,
|
|
32
|
+
notificationReady: false,
|
|
33
|
+
resourceSubscriptions: new Set(),
|
|
34
|
+
abortController,
|
|
35
|
+
admissions,
|
|
36
|
+
requests,
|
|
37
|
+
subscriptions: new SubscriptionRegistry(listener ?? (() => { }), supportNotifications, supportResourceSubscriptions),
|
|
38
|
+
listener
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function compileToolSchema(schema) {
|
|
42
|
+
try {
|
|
43
|
+
return compileJsonSchema(schema);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
throw new Error(`schema is invalid: ${message}`, { cause: error });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function createProtocolServer(options, stdio) {
|
|
51
|
+
if (options.toolCallTimeoutMs !== undefined &&
|
|
52
|
+
(!Number.isInteger(options.toolCallTimeoutMs) || options.toolCallTimeoutMs <= 0)) {
|
|
53
|
+
throw new Error("toolCallTimeoutMs must be a positive integer.");
|
|
54
|
+
}
|
|
55
|
+
const maxConcurrentToolCalls = options.maxConcurrentToolCalls ?? 4;
|
|
56
|
+
const maxQueuedToolCalls = options.maxQueuedToolCalls ?? 64;
|
|
57
|
+
const maxStdioOutputBytes = options.maxStdioOutputBytes ?? 1024 * 1024;
|
|
58
|
+
const maxPendingStdioMessages = options.maxPendingStdioMessages ?? 128;
|
|
59
|
+
const maxActiveRequests = options.maxActiveRequests ?? 128;
|
|
60
|
+
const maxStdioLineBytes = options.maxStdioLineBytes ?? 1024 * 1024;
|
|
61
|
+
for (const [name, value, minimum] of [
|
|
62
|
+
["maxConcurrentToolCalls", maxConcurrentToolCalls, 1],
|
|
63
|
+
["maxQueuedToolCalls", maxQueuedToolCalls, 0],
|
|
64
|
+
["maxStdioOutputBytes", maxStdioOutputBytes, 1],
|
|
65
|
+
["maxPendingStdioMessages", maxPendingStdioMessages, 1],
|
|
66
|
+
["maxActiveRequests", maxActiveRequests, 1],
|
|
67
|
+
["maxStdioLineBytes", maxStdioLineBytes, 1]
|
|
68
|
+
]) {
|
|
69
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
70
|
+
throw new Error(`${name} must be a safe integer greater than or equal to ${minimum}.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const toolAdmission = new ToolCallAdmission(maxConcurrentToolCalls, maxQueuedToolCalls);
|
|
74
|
+
const supportNotifications = options.supportNotifications !== false;
|
|
75
|
+
const supportResourceSubscriptions = options.supportResourceSubscriptions !== false;
|
|
76
|
+
const tools = new Map();
|
|
77
|
+
const prompts = new Map();
|
|
78
|
+
const resources = new Map();
|
|
79
|
+
const resourceTemplates = new Map();
|
|
80
|
+
const methods = new Map();
|
|
81
|
+
const notificationListeners = new Set();
|
|
82
|
+
const connectionNotificationListeners = new Map();
|
|
83
|
+
// Workers permit AbortController only inside a request, but tool registration
|
|
84
|
+
// belongs at module scope. Allocate the direct-call lifecycle on first use.
|
|
85
|
+
let defaultLifecycle;
|
|
86
|
+
const messageLifecycles = new Set();
|
|
87
|
+
let activeRequests = 0;
|
|
88
|
+
const describeCapabilities = () => ({
|
|
89
|
+
tools: {
|
|
90
|
+
...(supportNotifications ? { listChanged: true } : {})
|
|
91
|
+
},
|
|
92
|
+
prompts: {
|
|
93
|
+
...(supportNotifications ? { listChanged: true } : {})
|
|
94
|
+
},
|
|
95
|
+
resources: {
|
|
96
|
+
...(supportNotifications ? { listChanged: true } : {}),
|
|
97
|
+
...(supportResourceSubscriptions ? { subscribe: true } : {})
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const dispatchMessage = async (method, lifecycle, params, modern = false, requestSignal = lifecycle.abortController.signal, parameterHeaders, localContext) => {
|
|
101
|
+
const handlerContext = {
|
|
102
|
+
signal: requestSignal,
|
|
103
|
+
localContext,
|
|
104
|
+
...(modern && params?.requestState !== undefined
|
|
105
|
+
? { requestState: params.requestState }
|
|
106
|
+
: {}),
|
|
107
|
+
...(modern && params?.inputResponses !== undefined
|
|
108
|
+
? { inputResponses: params.inputResponses }
|
|
109
|
+
: {}),
|
|
110
|
+
clientCapabilities: modern
|
|
111
|
+
? (params?._meta)["io.modelcontextprotocol/clientCapabilities"]
|
|
112
|
+
: {}
|
|
113
|
+
};
|
|
114
|
+
if (method === "server/discover") {
|
|
115
|
+
const result = {
|
|
116
|
+
resultType: "complete",
|
|
117
|
+
supportedVersions: [...SUPPORTED_PROTOCOL_VERSIONS].reverse(),
|
|
118
|
+
capabilities: describeCapabilities(),
|
|
119
|
+
_meta: {
|
|
120
|
+
"io.modelcontextprotocol/serverInfo": { name: options.name, version: options.version }
|
|
121
|
+
},
|
|
122
|
+
ttlMs: 0,
|
|
123
|
+
cacheScope: "private"
|
|
124
|
+
};
|
|
125
|
+
return { result };
|
|
126
|
+
}
|
|
127
|
+
// Allow ping and initialize before initialization
|
|
128
|
+
if (method === "ping") {
|
|
129
|
+
return { result: {} };
|
|
130
|
+
}
|
|
131
|
+
if (method === "initialize") {
|
|
132
|
+
// Re-initialize on the same connection is idempotent: real MCP clients
|
|
133
|
+
// (e.g. kimi-cli via fastmcp) re-send `initialize` on a persistent
|
|
134
|
+
// connection per tool call, and the official MCP SDK server re-responds
|
|
135
|
+
// with InitializeResult instead of erroring. Per-connection isolation is
|
|
136
|
+
// still enforced by the separate lifecycle object given to each connection.
|
|
137
|
+
lifecycle.initializeAccepted = true;
|
|
138
|
+
lifecycle.initialized = true;
|
|
139
|
+
lifecycle.notificationReady = false;
|
|
140
|
+
const requestedProtocol = typeof params?.protocolVersion === "string" ? params.protocolVersion : undefined;
|
|
141
|
+
const result = {
|
|
142
|
+
protocolVersion: requestedProtocol !== undefined && requestedProtocol !== MODERN_PROTOCOL_VERSION &&
|
|
143
|
+
SUPPORTED_PROTOCOL_VERSIONS.has(requestedProtocol)
|
|
144
|
+
? requestedProtocol
|
|
145
|
+
: PROTOCOL_VERSION,
|
|
146
|
+
capabilities: describeCapabilities(),
|
|
147
|
+
serverInfo: {
|
|
148
|
+
name: options.name,
|
|
149
|
+
version: options.version
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
lifecycle.protocolVersion = result.protocolVersion;
|
|
153
|
+
return { result };
|
|
154
|
+
}
|
|
155
|
+
if (method === "notifications/initialized") {
|
|
156
|
+
if (!lifecycle.initializeAccepted) {
|
|
157
|
+
return {
|
|
158
|
+
error: {
|
|
159
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
160
|
+
message: "Server not initialized"
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
lifecycle.notificationReady = true;
|
|
165
|
+
return { result: undefined };
|
|
166
|
+
}
|
|
167
|
+
// All other methods require initialization
|
|
168
|
+
if (!modern && !lifecycle.initialized) {
|
|
169
|
+
return {
|
|
170
|
+
error: {
|
|
171
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
172
|
+
message: "Server not initialized"
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (method === "tools/list") {
|
|
177
|
+
const toolList = [];
|
|
178
|
+
for (const tool of tools.values()) {
|
|
179
|
+
const descriptor = { ...tool };
|
|
180
|
+
delete descriptor.handler;
|
|
181
|
+
delete descriptor.inputValidator;
|
|
182
|
+
delete descriptor.outputValidator;
|
|
183
|
+
delete descriptor.parameterHeaders;
|
|
184
|
+
if (!modern && descriptor.outputSchema?.type !== "object")
|
|
185
|
+
delete descriptor.outputSchema;
|
|
186
|
+
toolList.push(structuredClone(descriptor));
|
|
187
|
+
}
|
|
188
|
+
return { result: { tools: toolList } };
|
|
189
|
+
}
|
|
190
|
+
if (method === "tools/call") {
|
|
191
|
+
const toolName = params?.name;
|
|
192
|
+
if (!toolName) {
|
|
193
|
+
return {
|
|
194
|
+
error: {
|
|
195
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
196
|
+
message: "Tool name required"
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const tool = tools.get(toolName);
|
|
201
|
+
if (!tool) {
|
|
202
|
+
const availableTools = [...tools.keys()].slice(0, 20);
|
|
203
|
+
return {
|
|
204
|
+
error: {
|
|
205
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
206
|
+
message: `Tool not found: ${toolName}${availableTools.length === 0 ? "" : `. Available: ${availableTools.join(", ")}`}`
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (params?.arguments !== undefined && !isJsonObject(params.arguments))
|
|
211
|
+
return invalidParams("Tool arguments must be an object");
|
|
212
|
+
const toolArgs = (params?.arguments ?? {});
|
|
213
|
+
if (modern && parameterHeaders !== undefined) {
|
|
214
|
+
const headerError = validateParameterHeaders(tool.parameterHeaders, toolArgs, parameterHeaders);
|
|
215
|
+
if (headerError !== undefined)
|
|
216
|
+
return { error: { code: -32020, message: headerError } };
|
|
217
|
+
}
|
|
218
|
+
const inputValidation = tool.inputValidator.validate(toolArgs);
|
|
219
|
+
if (options.validateToolArguments !== false && !inputValidation.ok) {
|
|
220
|
+
return {
|
|
221
|
+
error: {
|
|
222
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
223
|
+
message: `Invalid tool arguments: ${formatIssues(inputValidation.issues)}`,
|
|
224
|
+
data: inputValidation.issues
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
let handlerResult;
|
|
230
|
+
const admissionController = new AbortController();
|
|
231
|
+
const sessionSignal = requestSignal;
|
|
232
|
+
const admissionSignal = admissionController.signal;
|
|
233
|
+
const cancelAdmission = () => admissionController.abort(sessionSignal.reason);
|
|
234
|
+
if (sessionSignal.aborted)
|
|
235
|
+
admissionController.abort(sessionSignal.reason);
|
|
236
|
+
else {
|
|
237
|
+
lifecycle.admissions.add(admissionController);
|
|
238
|
+
if (sessionSignal !== lifecycle.abortController.signal)
|
|
239
|
+
sessionSignal.addEventListener("abort", cancelAdmission, { once: true });
|
|
240
|
+
}
|
|
241
|
+
const handlerPromise = (async () => {
|
|
242
|
+
const release = await toolAdmission.acquire(admissionSignal);
|
|
243
|
+
try {
|
|
244
|
+
admissionSignal.throwIfAborted();
|
|
245
|
+
return await tool.handler(toolArgs, { ...handlerContext, signal: admissionSignal });
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
release();
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
try {
|
|
252
|
+
if (options.toolCallTimeoutMs === undefined) {
|
|
253
|
+
handlerResult = await handlerPromise;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
let timeout;
|
|
257
|
+
handlerResult = await Promise.race([
|
|
258
|
+
handlerPromise,
|
|
259
|
+
new Promise((_resolve, reject) => {
|
|
260
|
+
timeout = setTimeout(() => {
|
|
261
|
+
const error = new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Tool call timed out: ${toolName}`);
|
|
262
|
+
admissionController.abort(error);
|
|
263
|
+
reject(error);
|
|
264
|
+
}, options.toolCallTimeoutMs);
|
|
265
|
+
})
|
|
266
|
+
]).finally(() => {
|
|
267
|
+
if (timeout !== undefined) {
|
|
268
|
+
clearTimeout(timeout);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
lifecycle.admissions.delete(admissionController);
|
|
275
|
+
sessionSignal.removeEventListener("abort", cancelAdmission);
|
|
276
|
+
}
|
|
277
|
+
if (modern && isInputRequiredResult(handlerResult))
|
|
278
|
+
return { result: handlerResult };
|
|
279
|
+
const outputSchema = modern || tool.outputSchema?.type === "object" ? tool.outputSchema : undefined;
|
|
280
|
+
const result = normalizeToolResult(handlerResult, tool.outputSchema, modern);
|
|
281
|
+
const outputValidation = outputSchema === undefined ? undefined : tool.outputValidator?.validate(result.structuredContent);
|
|
282
|
+
if (result.isError !== true && outputValidation !== undefined && !outputValidation.ok) {
|
|
283
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Invalid structured tool result: ${formatIssues(outputValidation.issues)}`, outputValidation.issues);
|
|
284
|
+
}
|
|
285
|
+
return { result };
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
if (err instanceof ToolError) {
|
|
289
|
+
return {
|
|
290
|
+
error: {
|
|
291
|
+
code: err.code,
|
|
292
|
+
message: err.message,
|
|
293
|
+
...(err.data === undefined ? {} : { data: err.data })
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
298
|
+
const result = {
|
|
299
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
300
|
+
isError: true
|
|
301
|
+
};
|
|
302
|
+
return { result };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (method === "prompts/list") {
|
|
306
|
+
return {
|
|
307
|
+
result: {
|
|
308
|
+
prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => structuredClone(prompt))
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (method === "prompts/get") {
|
|
313
|
+
const promptName = typeof params?.name === "string" ? params.name : undefined;
|
|
314
|
+
if (promptName === undefined) {
|
|
315
|
+
return invalidParams("Prompt name required");
|
|
316
|
+
}
|
|
317
|
+
const prompt = prompts.get(promptName);
|
|
318
|
+
if (prompt === undefined) {
|
|
319
|
+
return invalidParams(`Prompt not found: ${promptName}`);
|
|
320
|
+
}
|
|
321
|
+
const args = toStringArguments(params?.arguments);
|
|
322
|
+
if (args === undefined || !hasRequiredPromptArguments(prompt, args)) {
|
|
323
|
+
return invalidParams("Invalid prompt arguments");
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const result = await prompt.handler(args, handlerContext);
|
|
327
|
+
if (modern && isInputRequiredResult(result))
|
|
328
|
+
return { result };
|
|
329
|
+
if (!isGetPromptResult(result, modern || lifecycle.protocolVersion === PROTOCOL_VERSION)) {
|
|
330
|
+
return internalError("Invalid prompt result");
|
|
331
|
+
}
|
|
332
|
+
return { result };
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return internalError(toErrorMessage(error));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (method === "resources/list") {
|
|
339
|
+
return {
|
|
340
|
+
result: {
|
|
341
|
+
resources: [...resources.values()].map(({ handler: _handler, ...resource }) => structuredClone(resource))
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (method === "resources/templates/list") {
|
|
346
|
+
return {
|
|
347
|
+
result: {
|
|
348
|
+
resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, template: _template, ...resourceTemplate }) => structuredClone(resourceTemplate))
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (method === "resources/read") {
|
|
353
|
+
const uri = typeof params?.uri === "string" ? params.uri : undefined;
|
|
354
|
+
if (uri === undefined || !isValidUri(uri)) {
|
|
355
|
+
return invalidParams("Resource URI required");
|
|
356
|
+
}
|
|
357
|
+
const resource = findReadableResource(uri, resources, resourceTemplates);
|
|
358
|
+
if (resource === undefined) {
|
|
359
|
+
return resourceNotFound(uri);
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const result = await resource.handler(uri, handlerContext);
|
|
363
|
+
if (modern && isInputRequiredResult(result))
|
|
364
|
+
return { result };
|
|
365
|
+
if (!isReadResourceResult(result)) {
|
|
366
|
+
return internalError("Invalid resource result");
|
|
367
|
+
}
|
|
368
|
+
return { result };
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
return internalError(toErrorMessage(error));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (method === "resources/subscribe" || method === "resources/unsubscribe") {
|
|
375
|
+
if (!supportResourceSubscriptions) {
|
|
376
|
+
return {
|
|
377
|
+
error: {
|
|
378
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
379
|
+
message: "Method not found"
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const uri = typeof params?.uri === "string" ? params.uri : undefined;
|
|
384
|
+
if (uri === undefined || !isValidUri(uri)) {
|
|
385
|
+
return invalidParams("Resource URI required");
|
|
386
|
+
}
|
|
387
|
+
if (method === "resources/subscribe" &&
|
|
388
|
+
findReadableResource(uri, resources, resourceTemplates) === undefined) {
|
|
389
|
+
return resourceNotFound(uri);
|
|
390
|
+
}
|
|
391
|
+
if (method === "resources/subscribe") {
|
|
392
|
+
lifecycle.resourceSubscriptions.add(uri);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
lifecycle.resourceSubscriptions.delete(uri);
|
|
396
|
+
}
|
|
397
|
+
return { result: {} };
|
|
398
|
+
}
|
|
399
|
+
const customMethod = methods.get(method);
|
|
400
|
+
if (customMethod !== undefined) {
|
|
401
|
+
try {
|
|
402
|
+
const result = await customMethod(params, {
|
|
403
|
+
signal: requestSignal,
|
|
404
|
+
async notify(notificationMethod, notificationParams) {
|
|
405
|
+
if (requestSignal.aborted ||
|
|
406
|
+
(!modern && !lifecycle.notificationReady) ||
|
|
407
|
+
lifecycle.listener === undefined) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
await lifecycle.listener({
|
|
411
|
+
jsonrpc: "2.0",
|
|
412
|
+
method: notificationMethod,
|
|
413
|
+
...(notificationParams === undefined ? {} : { params: notificationParams })
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
return { result };
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (error instanceof ToolError) {
|
|
421
|
+
return {
|
|
422
|
+
error: {
|
|
423
|
+
code: error.code,
|
|
424
|
+
message: error.message,
|
|
425
|
+
...(error.data === undefined ? {} : { data: error.data })
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return internalError(toErrorMessage(error));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
error: {
|
|
434
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
435
|
+
message: "Method not found"
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
const handleMessageWithLifecycle = async (method, lifecycle, params, context) => {
|
|
440
|
+
if (method === "notifications/cancelled") {
|
|
441
|
+
const id = params?.requestId;
|
|
442
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
443
|
+
lifecycle.requests.get(id)?.abort(new Error("Request cancelled"));
|
|
444
|
+
}
|
|
445
|
+
return { result: undefined };
|
|
446
|
+
}
|
|
447
|
+
const protocol = selectRequestProtocol(method, params, SUPPORTED_PROTOCOL_VERSIONS);
|
|
448
|
+
if (protocol.error !== undefined)
|
|
449
|
+
return { error: protocol.error };
|
|
450
|
+
const directLegacy = !protocol.modern && context === undefined;
|
|
451
|
+
if (protocol.modern &&
|
|
452
|
+
context !== undefined &&
|
|
453
|
+
context.requestId !== undefined &&
|
|
454
|
+
!isRequestId(context.requestId)) {
|
|
455
|
+
return {
|
|
456
|
+
error: { code: JSON_RPC_ERROR_CODES.INVALID_REQUEST, message: "Invalid Request ID" }
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (activeRequests >= maxActiveRequests) {
|
|
460
|
+
return { error: { code: -32000, message: "Too many active requests" } };
|
|
461
|
+
}
|
|
462
|
+
const key = context?.requestId ?? Symbol("request");
|
|
463
|
+
if (lifecycle.requests.has(key)) {
|
|
464
|
+
return {
|
|
465
|
+
error: {
|
|
466
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
467
|
+
message: "Request ID is already active"
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const controller = new AbortController();
|
|
472
|
+
const parents = context?.signal === undefined ? [] : [context.signal];
|
|
473
|
+
const signal = controller.signal;
|
|
474
|
+
if (lifecycle.abortController.signal.aborted)
|
|
475
|
+
controller.abort(lifecycle.abortController.signal.reason);
|
|
476
|
+
const abort = (event) => controller.abort(event.target.reason);
|
|
477
|
+
for (const parent of parents) {
|
|
478
|
+
if (parent.aborted)
|
|
479
|
+
controller.abort(parent.reason);
|
|
480
|
+
else
|
|
481
|
+
parent.addEventListener("abort", abort, { once: true });
|
|
482
|
+
}
|
|
483
|
+
const cleanup = () => {
|
|
484
|
+
for (const parent of parents)
|
|
485
|
+
parent.removeEventListener("abort", abort);
|
|
486
|
+
};
|
|
487
|
+
if (signal.aborted) {
|
|
488
|
+
cleanup();
|
|
489
|
+
return { result: undefined };
|
|
490
|
+
}
|
|
491
|
+
lifecycle.requests.set(key, controller);
|
|
492
|
+
activeRequests += 1;
|
|
493
|
+
const operation = protocol.modern && method === "subscriptions/listen"
|
|
494
|
+
? lifecycle.subscriptions.listen(context?.requestId, params?.notifications, signal)
|
|
495
|
+
: dispatchMessage(method, lifecycle, params, protocol.modern, directLegacy ? lifecycle.abortController.signal : signal, context?.parameterHeaders, context?.localContext);
|
|
496
|
+
const release = () => {
|
|
497
|
+
activeRequests -= 1;
|
|
498
|
+
if (lifecycle.requests.get(key) === controller)
|
|
499
|
+
lifecycle.requests.delete(key);
|
|
500
|
+
};
|
|
501
|
+
void operation.then(release, release);
|
|
502
|
+
try {
|
|
503
|
+
const handled = directLegacy ? await operation : await waitForRequest(operation, signal);
|
|
504
|
+
if (handled === undefined || (!directLegacy && signal.aborted))
|
|
505
|
+
return { result: undefined };
|
|
506
|
+
return protocol.modern
|
|
507
|
+
? decorateModernResult(method, handled, { name: options.name, version: options.version }, (params?._meta)["io.modelcontextprotocol/clientCapabilities"])
|
|
508
|
+
: handled;
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
cleanup();
|
|
512
|
+
controller.abort();
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const createMessageSession = (listener) => {
|
|
516
|
+
const lifecycle = createLifecycleState(listener, supportNotifications, supportResourceSubscriptions);
|
|
517
|
+
messageLifecycles.add(lifecycle);
|
|
518
|
+
if (listener !== undefined) {
|
|
519
|
+
connectionNotificationListeners.set(listener, lifecycle);
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
handleMessage: (method, params, context) => handleMessageWithLifecycle(method, lifecycle, params, context),
|
|
523
|
+
close: () => {
|
|
524
|
+
lifecycle.abortController.abort();
|
|
525
|
+
if (listener !== undefined) {
|
|
526
|
+
connectionNotificationListeners.delete(listener);
|
|
527
|
+
}
|
|
528
|
+
messageLifecycles.delete(lifecycle);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
};
|
|
532
|
+
const handleMessage = (method, params, context) => {
|
|
533
|
+
if (defaultLifecycle === undefined) {
|
|
534
|
+
defaultLifecycle = createLifecycleState((notification) => {
|
|
535
|
+
for (const listener of notificationListeners)
|
|
536
|
+
listener(notification);
|
|
537
|
+
}, supportNotifications, supportResourceSubscriptions);
|
|
538
|
+
messageLifecycles.add(defaultLifecycle);
|
|
539
|
+
}
|
|
540
|
+
return handleMessageWithLifecycle(method, defaultLifecycle, params, context);
|
|
541
|
+
};
|
|
542
|
+
const processLine = async (line, write, messageHandler, subscriptionSignal) => {
|
|
543
|
+
const parsed = parseMessage(line);
|
|
544
|
+
if (!parsed.success) {
|
|
545
|
+
await write(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const { request, isNotification } = parsed;
|
|
549
|
+
if (isNotification &&
|
|
550
|
+
!request.method.startsWith("notifications/") &&
|
|
551
|
+
selectRequestProtocol(request.method, request.params, SUPPORTED_PROTOCOL_VERSIONS).modern)
|
|
552
|
+
return;
|
|
553
|
+
if (isNotification && request.method === "initialize") {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (!isNotification && request.method === "notifications/initialized") {
|
|
557
|
+
const requestWithId = request;
|
|
558
|
+
await write(formatErrorResponse(requestWithId.id, {
|
|
559
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
560
|
+
message: "Invalid Request"
|
|
561
|
+
}) + "\n");
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
let handled;
|
|
565
|
+
try {
|
|
566
|
+
handled = await messageHandler(request.method, request.params, !isNotification && request.id !== null
|
|
567
|
+
? {
|
|
568
|
+
requestId: request.id,
|
|
569
|
+
...(request.method === "subscriptions/listen" ? { signal: subscriptionSignal } : {})
|
|
570
|
+
}
|
|
571
|
+
: undefined);
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
if (!isNotification) {
|
|
575
|
+
const requestWithId = request;
|
|
576
|
+
await write(formatErrorResponse(requestWithId.id, {
|
|
577
|
+
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
578
|
+
message: "Internal error"
|
|
579
|
+
}) + "\n");
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const { result, error } = handled;
|
|
584
|
+
if (isNotification) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const requestWithId = request;
|
|
588
|
+
if (error) {
|
|
589
|
+
await write(formatErrorResponse(requestWithId.id, error) + "\n");
|
|
590
|
+
}
|
|
591
|
+
else if (result !== undefined) {
|
|
592
|
+
await write(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
const broadcastNotification = async (method, params, canSend = () => true) => {
|
|
596
|
+
const notification = {
|
|
597
|
+
jsonrpc: "2.0",
|
|
598
|
+
method,
|
|
599
|
+
...(params === undefined ? {} : { params })
|
|
600
|
+
};
|
|
601
|
+
for (const listener of notificationListeners) {
|
|
602
|
+
listener(notification);
|
|
603
|
+
}
|
|
604
|
+
await Promise.all([...messageLifecycles].map((lifecycle) => lifecycle.subscriptions.emit(method, params)));
|
|
605
|
+
await Promise.all([...connectionNotificationListeners].map(async ([listener, lifecycle]) => {
|
|
606
|
+
if (lifecycle.notificationReady && canSend(lifecycle)) {
|
|
607
|
+
await listener(notification);
|
|
608
|
+
}
|
|
609
|
+
}));
|
|
610
|
+
};
|
|
611
|
+
const server = {
|
|
612
|
+
tool(name, description, inputSchema, handler, outputSchema) {
|
|
613
|
+
assertNonEmptyName(name, "Tool name required");
|
|
614
|
+
if (tools.has(name)) {
|
|
615
|
+
throw new Error(`Tool already registered: ${name}`);
|
|
616
|
+
}
|
|
617
|
+
const inputSchemaSnapshot = normalizeLegacyNullability(inputSchema);
|
|
618
|
+
const outputSchemaSnapshot = outputSchema === undefined ? undefined : normalizeLegacyNullability(outputSchema);
|
|
619
|
+
const inputValidator = compileToolSchema(inputSchemaSnapshot);
|
|
620
|
+
assertObjectRootSchema(inputSchemaSnapshot, "inputSchema");
|
|
621
|
+
let outputValidator;
|
|
622
|
+
if (outputSchemaSnapshot !== undefined) {
|
|
623
|
+
assertOutputSchema(outputSchemaSnapshot);
|
|
624
|
+
outputValidator = compileToolSchema(outputSchemaSnapshot);
|
|
625
|
+
}
|
|
626
|
+
tools.set(name, {
|
|
627
|
+
name,
|
|
628
|
+
description,
|
|
629
|
+
inputSchema: inputSchemaSnapshot,
|
|
630
|
+
...(outputSchemaSnapshot === undefined ? {} : { outputSchema: outputSchemaSnapshot }),
|
|
631
|
+
handler: handler,
|
|
632
|
+
inputValidator,
|
|
633
|
+
parameterHeaders: getParameterHeaders(inputSchemaSnapshot),
|
|
634
|
+
...(outputValidator === undefined ? {} : { outputValidator })
|
|
635
|
+
});
|
|
636
|
+
return server;
|
|
637
|
+
},
|
|
638
|
+
registerTool(definition, handler) {
|
|
639
|
+
assertNonEmptyName(definition.name, "Tool name required");
|
|
640
|
+
if (tools.has(definition.name)) {
|
|
641
|
+
throw new Error(`Tool already registered: ${definition.name}`);
|
|
642
|
+
}
|
|
643
|
+
const descriptor = structuredClone(definition);
|
|
644
|
+
const inputSchema = normalizeLegacyNullability(descriptor.inputSchema);
|
|
645
|
+
const inputValidator = compileToolSchema(inputSchema);
|
|
646
|
+
assertObjectRootSchema(inputSchema, "inputSchema");
|
|
647
|
+
let outputValidator;
|
|
648
|
+
if (descriptor.outputSchema !== undefined) {
|
|
649
|
+
descriptor.outputSchema = normalizeLegacyNullability(descriptor.outputSchema);
|
|
650
|
+
assertOutputSchema(descriptor.outputSchema);
|
|
651
|
+
outputValidator = compileToolSchema(descriptor.outputSchema);
|
|
652
|
+
}
|
|
653
|
+
tools.set(definition.name, {
|
|
654
|
+
...descriptor,
|
|
655
|
+
inputSchema,
|
|
656
|
+
handler: handler,
|
|
657
|
+
inputValidator,
|
|
658
|
+
parameterHeaders: getParameterHeaders(inputSchema),
|
|
659
|
+
...(outputValidator === undefined ? {} : { outputValidator })
|
|
660
|
+
});
|
|
661
|
+
return server;
|
|
662
|
+
},
|
|
663
|
+
prompt(definition, handler) {
|
|
664
|
+
assertNonEmptyName(definition.name, "Prompt name required");
|
|
665
|
+
if (prompts.has(definition.name)) {
|
|
666
|
+
throw new Error(`Prompt already registered: ${definition.name}`);
|
|
667
|
+
}
|
|
668
|
+
prompts.set(definition.name, { ...structuredClone(definition), handler });
|
|
669
|
+
return server;
|
|
670
|
+
},
|
|
671
|
+
resource(definition, handler) {
|
|
672
|
+
if (!isValidUri(definition.uri)) {
|
|
673
|
+
throw new Error(`Invalid resource URI: ${definition.uri}`);
|
|
674
|
+
}
|
|
675
|
+
if (resources.has(definition.uri)) {
|
|
676
|
+
throw new Error(`Resource already registered: ${definition.uri}`);
|
|
677
|
+
}
|
|
678
|
+
resources.set(definition.uri, { ...structuredClone(definition), handler });
|
|
679
|
+
return server;
|
|
680
|
+
},
|
|
681
|
+
resourceTemplate(definition, handler) {
|
|
682
|
+
const template = parseReadableUriTemplate(definition.uriTemplate);
|
|
683
|
+
if (resourceTemplates.has(definition.uriTemplate)) {
|
|
684
|
+
throw new Error(`Resource template already registered: ${definition.uriTemplate}`);
|
|
685
|
+
}
|
|
686
|
+
resourceTemplates.set(definition.uriTemplate, { ...structuredClone(definition), handler, template });
|
|
687
|
+
return server;
|
|
688
|
+
},
|
|
689
|
+
method(name, handler) {
|
|
690
|
+
assertNonEmptyName(name, "Method name required");
|
|
691
|
+
methods.set(name, handler);
|
|
692
|
+
return server;
|
|
693
|
+
},
|
|
694
|
+
onNotification(listener) {
|
|
695
|
+
notificationListeners.add(listener);
|
|
696
|
+
return () => {
|
|
697
|
+
notificationListeners.delete(listener);
|
|
698
|
+
};
|
|
699
|
+
},
|
|
700
|
+
removeTool(name) {
|
|
701
|
+
return tools.delete(name);
|
|
702
|
+
},
|
|
703
|
+
removePrompt(name) {
|
|
704
|
+
return prompts.delete(name);
|
|
705
|
+
},
|
|
706
|
+
removeResource(uri) {
|
|
707
|
+
return resources.delete(uri);
|
|
708
|
+
},
|
|
709
|
+
removeResourceTemplate(uriTemplate) {
|
|
710
|
+
return resourceTemplates.delete(uriTemplate);
|
|
711
|
+
},
|
|
712
|
+
async notifyToolsChanged() {
|
|
713
|
+
if (supportNotifications &&
|
|
714
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady || lifecycle.subscriptions.size > 0)) {
|
|
715
|
+
await broadcastNotification("notifications/tools/list_changed");
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
async notifyPromptsChanged() {
|
|
719
|
+
if (supportNotifications &&
|
|
720
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady || lifecycle.subscriptions.size > 0)) {
|
|
721
|
+
await broadcastNotification("notifications/prompts/list_changed");
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async notifyResourcesChanged() {
|
|
725
|
+
if (supportNotifications &&
|
|
726
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady || lifecycle.subscriptions.size > 0)) {
|
|
727
|
+
await broadcastNotification("notifications/resources/list_changed");
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
async notifyResourceUpdated(uri) {
|
|
731
|
+
if (!supportResourceSubscriptions) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
await broadcastNotification("notifications/resources/updated", { uri }, (lifecycle) => lifecycle.resourceSubscriptions.has(uri));
|
|
735
|
+
},
|
|
736
|
+
createMessageSession,
|
|
737
|
+
handleMessage,
|
|
738
|
+
async listen() {
|
|
739
|
+
return server.connect({
|
|
740
|
+
readable: process.stdin,
|
|
741
|
+
writable: process.stdout
|
|
742
|
+
});
|
|
743
|
+
},
|
|
744
|
+
async connect(transport) {
|
|
745
|
+
if (stdio === undefined)
|
|
746
|
+
throw new Error("Stdio is unavailable in the protocol-only entry point");
|
|
747
|
+
const { readline, StdioInput, StdioOutput } = stdio;
|
|
748
|
+
return new Promise((resolve, reject) => {
|
|
749
|
+
let inputClosed = false;
|
|
750
|
+
let settled = false;
|
|
751
|
+
const pendingMessages = new Set();
|
|
752
|
+
const subscriptions = new AbortController();
|
|
753
|
+
const output = new StdioOutput(transport.writable, maxStdioOutputBytes, fail, finish);
|
|
754
|
+
const listener = (notification) => output.write(`${JSON.stringify(notification)}\n`);
|
|
755
|
+
const session = server.createMessageSession(listener);
|
|
756
|
+
const input = new StdioInput(maxStdioLineBytes);
|
|
757
|
+
const rl = readline.createInterface({
|
|
758
|
+
input,
|
|
759
|
+
crlfDelay: Infinity
|
|
760
|
+
});
|
|
761
|
+
function fail(error) {
|
|
762
|
+
if (settled)
|
|
763
|
+
return;
|
|
764
|
+
settled = true;
|
|
765
|
+
session.close();
|
|
766
|
+
rl.close();
|
|
767
|
+
transport.readable.unpipe(input);
|
|
768
|
+
input.destroy();
|
|
769
|
+
transport.readable.pause();
|
|
770
|
+
transport.readable.off("error", fail);
|
|
771
|
+
output.abort(error);
|
|
772
|
+
pendingMessages.clear();
|
|
773
|
+
reject(error);
|
|
774
|
+
}
|
|
775
|
+
function finish() {
|
|
776
|
+
if (settled || !inputClosed || pendingMessages.size > 0 || output.pending > 0)
|
|
777
|
+
return;
|
|
778
|
+
settled = true;
|
|
779
|
+
session.close();
|
|
780
|
+
transport.readable.unpipe(input);
|
|
781
|
+
input.destroy();
|
|
782
|
+
transport.readable.off("error", fail);
|
|
783
|
+
output.close();
|
|
784
|
+
resolve();
|
|
785
|
+
}
|
|
786
|
+
transport.readable.on("error", fail);
|
|
787
|
+
input.on("error", fail);
|
|
788
|
+
rl.on("error", fail);
|
|
789
|
+
rl.on("line", (line) => {
|
|
790
|
+
if (settled)
|
|
791
|
+
return;
|
|
792
|
+
if (pendingMessages.size >= maxPendingStdioMessages) {
|
|
793
|
+
fail(new Error("Stdio pending message limit exceeded"));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const message = processLine(line, (data) => output.write(data), session.handleMessage, subscriptions.signal);
|
|
797
|
+
if (!settled)
|
|
798
|
+
pendingMessages.add(message);
|
|
799
|
+
void message.then(() => {
|
|
800
|
+
pendingMessages.delete(message);
|
|
801
|
+
finish();
|
|
802
|
+
}, fail);
|
|
803
|
+
});
|
|
804
|
+
rl.on("close", () => {
|
|
805
|
+
inputClosed = true;
|
|
806
|
+
subscriptions.abort();
|
|
807
|
+
finish();
|
|
808
|
+
});
|
|
809
|
+
transport.readable.pipe(input);
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
async connectSDK(transport) {
|
|
813
|
+
return new Promise((resolve, reject) => {
|
|
814
|
+
const listener = (notification) => transport.send(notification);
|
|
815
|
+
const session = server.createMessageSession(listener);
|
|
816
|
+
transport.onmessage = async (message) => {
|
|
817
|
+
// Ignore responses (we only handle requests/notifications)
|
|
818
|
+
if (!("method" in message)) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
// Handle notifications (no id) - don't respond
|
|
822
|
+
if (!("id" in message) || message.id === undefined) {
|
|
823
|
+
if (!message.method.startsWith("notifications/") &&
|
|
824
|
+
selectRequestProtocol(message.method, message.params, SUPPORTED_PROTOCOL_VERSIONS)
|
|
825
|
+
.modern)
|
|
826
|
+
return;
|
|
827
|
+
if (message.method === "initialize") {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
await session.handleMessage(message.method, message.params);
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (message.method === "notifications/initialized") {
|
|
839
|
+
await transport.send({
|
|
840
|
+
jsonrpc: "2.0",
|
|
841
|
+
id: message.id,
|
|
842
|
+
error: {
|
|
843
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
844
|
+
message: "Invalid Request"
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const request = message;
|
|
850
|
+
if (selectRequestProtocol(request.method, request.params, SUPPORTED_PROTOCOL_VERSIONS)
|
|
851
|
+
.modern &&
|
|
852
|
+
!isRequestId(request.id)) {
|
|
853
|
+
await transport.send({
|
|
854
|
+
jsonrpc: "2.0",
|
|
855
|
+
id: null,
|
|
856
|
+
error: { code: JSON_RPC_ERROR_CODES.INVALID_REQUEST, message: "Invalid Request ID" }
|
|
857
|
+
});
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
let handled;
|
|
861
|
+
try {
|
|
862
|
+
handled = await session.handleMessage(request.method, request.params, request.id === null ? undefined : { requestId: request.id });
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
await transport.send({
|
|
866
|
+
jsonrpc: "2.0",
|
|
867
|
+
id: request.id,
|
|
868
|
+
error: {
|
|
869
|
+
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
870
|
+
message: "Internal error"
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const { result, error } = handled;
|
|
876
|
+
if (error) {
|
|
877
|
+
const response = {
|
|
878
|
+
jsonrpc: "2.0",
|
|
879
|
+
id: request.id,
|
|
880
|
+
error
|
|
881
|
+
};
|
|
882
|
+
await transport.send(response);
|
|
883
|
+
}
|
|
884
|
+
else if (result !== undefined) {
|
|
885
|
+
const response = {
|
|
886
|
+
jsonrpc: "2.0",
|
|
887
|
+
id: request.id,
|
|
888
|
+
result
|
|
889
|
+
};
|
|
890
|
+
await transport.send(response);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
transport.onclose = () => {
|
|
894
|
+
session.close();
|
|
895
|
+
resolve();
|
|
896
|
+
};
|
|
897
|
+
void transport.start().catch((error) => {
|
|
898
|
+
session.close();
|
|
899
|
+
reject(error);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
return server;
|
|
905
|
+
}
|
|
906
|
+
function invalidParams(message) {
|
|
907
|
+
return {
|
|
908
|
+
error: {
|
|
909
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
910
|
+
message
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function internalError(message) {
|
|
915
|
+
return {
|
|
916
|
+
error: {
|
|
917
|
+
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
918
|
+
message
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function resourceNotFound(uri) {
|
|
923
|
+
return {
|
|
924
|
+
error: {
|
|
925
|
+
code: JSON_RPC_ERROR_CODES.RESOURCE_NOT_FOUND,
|
|
926
|
+
message: `Resource not found: ${uri}`
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
function toErrorMessage(error) {
|
|
931
|
+
return error instanceof Error ? error.message : String(error);
|
|
932
|
+
}
|
|
933
|
+
function assertNonEmptyName(name, message) {
|
|
934
|
+
if (name.length === 0) {
|
|
935
|
+
throw new Error(message);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function parseReadableUriTemplate(uriTemplate) {
|
|
939
|
+
const template = parseUriTemplate(uriTemplate);
|
|
940
|
+
if (!isValidUri(template.expand({}))) {
|
|
941
|
+
throw new Error(`Invalid resource URI template: ${uriTemplate}`);
|
|
942
|
+
}
|
|
943
|
+
return template;
|
|
944
|
+
}
|
|
945
|
+
function toStringArguments(value) {
|
|
946
|
+
if (value === undefined) {
|
|
947
|
+
return {};
|
|
948
|
+
}
|
|
949
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
const args = {};
|
|
953
|
+
for (const [name, argument] of Object.entries(value)) {
|
|
954
|
+
if (typeof argument !== "string") {
|
|
955
|
+
return undefined;
|
|
956
|
+
}
|
|
957
|
+
args[name] = argument;
|
|
958
|
+
}
|
|
959
|
+
return args;
|
|
960
|
+
}
|
|
961
|
+
function hasRequiredPromptArguments(prompt, args) {
|
|
962
|
+
return (prompt.arguments ?? []).every((argument) => argument.required !== true || args[argument.name] !== undefined);
|
|
963
|
+
}
|
|
964
|
+
function findReadableResource(uri, resources, resourceTemplates) {
|
|
965
|
+
const resource = resources.get(uri);
|
|
966
|
+
if (resource !== undefined) {
|
|
967
|
+
return resource;
|
|
968
|
+
}
|
|
969
|
+
return [...resourceTemplates.values()].find((template) => template.template.match(uri) !== null);
|
|
970
|
+
}
|
|
971
|
+
function isInputRequiredResult(value) {
|
|
972
|
+
return (typeof value === "object" &&
|
|
973
|
+
value !== null &&
|
|
974
|
+
!Array.isArray(value) &&
|
|
975
|
+
value.resultType === "input_required");
|
|
976
|
+
}
|
|
977
|
+
function isCallToolResult(value, modern) {
|
|
978
|
+
if (!hasContentArray(value) || !value.content.every(isContentItem)) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
if (hasOwnProperty(value, "structuredContent") &&
|
|
982
|
+
value.structuredContent !== undefined &&
|
|
983
|
+
!(isJsonValue(value.structuredContent) && (modern || isJsonObject(value.structuredContent)))) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
return !(hasOwnProperty(value, "isError") &&
|
|
987
|
+
value.isError !== undefined &&
|
|
988
|
+
typeof value.isError !== "boolean");
|
|
989
|
+
}
|
|
990
|
+
function normalizeToolResult(handlerResult, outputSchema, modern) {
|
|
991
|
+
if (!modern && outputSchema !== undefined && outputSchema.type !== "object") {
|
|
992
|
+
const result = normalizeToolResult(handlerResult, outputSchema, true);
|
|
993
|
+
const { structuredContent, ...legacyResult } = result;
|
|
994
|
+
return {
|
|
995
|
+
...legacyResult,
|
|
996
|
+
content: typeof handlerResult === "string"
|
|
997
|
+
? [{ type: "text", text: handlerResult }]
|
|
998
|
+
: legacyResult.content.length > 0 || structuredContent === undefined
|
|
999
|
+
? legacyResult.content
|
|
1000
|
+
: [{ type: "text", text: JSON.stringify(structuredContent) }]
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
if (hasContentArray(handlerResult) && !isCallToolResult(handlerResult, modern)) {
|
|
1004
|
+
if (outputSchema !== undefined)
|
|
1005
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Invalid tool result");
|
|
1006
|
+
throw new Error("Invalid tool result");
|
|
1007
|
+
}
|
|
1008
|
+
if (outputSchema === undefined) {
|
|
1009
|
+
const result = isCallToolResult(handlerResult, modern)
|
|
1010
|
+
? handlerResult
|
|
1011
|
+
: { content: toContentBlocks(handlerResult) };
|
|
1012
|
+
if (!isCallToolResult(result, modern)) {
|
|
1013
|
+
throw new Error("Invalid tool result");
|
|
1014
|
+
}
|
|
1015
|
+
return result;
|
|
1016
|
+
}
|
|
1017
|
+
if (isCallToolResult(handlerResult, modern) && handlerResult.isError === true) {
|
|
1018
|
+
return handlerResult;
|
|
1019
|
+
}
|
|
1020
|
+
const callToolResult = isCallToolResult(handlerResult, modern) ? handlerResult : undefined;
|
|
1021
|
+
const structuredContent = callToolResult ? callToolResult.structuredContent : handlerResult;
|
|
1022
|
+
if (!(isJsonValue(structuredContent) && (modern || isJsonObject(structuredContent)))) {
|
|
1023
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, modern ? "Structured tool result must be JSON" : "Structured tool result must be a JSON object");
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
...callToolResult,
|
|
1027
|
+
content: callToolResult !== undefined && callToolResult.content.length > 0
|
|
1028
|
+
? callToolResult.content
|
|
1029
|
+
: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
1030
|
+
structuredContent
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function assertObjectRootSchema(schema, path) {
|
|
1034
|
+
if (typeof schema !== "object" || schema === null || !("type" in schema) || schema.type !== "object") {
|
|
1035
|
+
throw new Error(`${path} root type must be "object"`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function assertOutputSchema(schema) {
|
|
1039
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema))
|
|
1040
|
+
throw new Error("outputSchema must be a JSON Schema object");
|
|
1041
|
+
}
|
|
1042
|
+
function isJsonObject(value) {
|
|
1043
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1044
|
+
}
|
|
1045
|
+
function isGetPromptResult(value, modern = false) {
|
|
1046
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "messages")) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
return ((!hasOwnProperty(value, "description") ||
|
|
1050
|
+
value.description === undefined ||
|
|
1051
|
+
typeof value.description === "string") &&
|
|
1052
|
+
Array.isArray(value.messages) &&
|
|
1053
|
+
value.messages.every((message) => typeof message === "object" &&
|
|
1054
|
+
message !== null &&
|
|
1055
|
+
hasOwnProperty(message, "role") &&
|
|
1056
|
+
(message.role === "user" || message.role === "assistant") &&
|
|
1057
|
+
hasOwnProperty(message, "content") &&
|
|
1058
|
+
(modern ? isContentItem(message.content) : isPromptContentItem(message.content))));
|
|
1059
|
+
}
|
|
1060
|
+
function isReadResourceResult(value) {
|
|
1061
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "contents")) {
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
return Array.isArray(value.contents) && value.contents.every(isResourceContents);
|
|
1065
|
+
}
|
|
1066
|
+
function hasContentArray(value) {
|
|
1067
|
+
return (typeof value === "object" &&
|
|
1068
|
+
value !== null &&
|
|
1069
|
+
hasOwnProperty(value, "content") &&
|
|
1070
|
+
Array.isArray(value.content));
|
|
1071
|
+
}
|
|
1072
|
+
function isContentItem(value) {
|
|
1073
|
+
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "type")) {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
const block = value;
|
|
1077
|
+
if (!hasValidContentAnnotations(block)) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
if (block.type === "text") {
|
|
1081
|
+
return hasOwnProperty(block, "text") && typeof block.text === "string";
|
|
1082
|
+
}
|
|
1083
|
+
if (block.type === "image" || block.type === "audio") {
|
|
1084
|
+
return (hasOwnProperty(block, "data") &&
|
|
1085
|
+
typeof block.data === "string" &&
|
|
1086
|
+
isBase64(block.data) &&
|
|
1087
|
+
hasOwnProperty(block, "mimeType") &&
|
|
1088
|
+
typeof block.mimeType === "string");
|
|
1089
|
+
}
|
|
1090
|
+
if (block.type === "resource_link") {
|
|
1091
|
+
return (hasOwnProperty(block, "uri") &&
|
|
1092
|
+
typeof block.uri === "string" &&
|
|
1093
|
+
isValidUri(block.uri) &&
|
|
1094
|
+
hasOwnProperty(block, "name") &&
|
|
1095
|
+
typeof block.name === "string" &&
|
|
1096
|
+
(!hasOwnProperty(block, "title") ||
|
|
1097
|
+
block.title === undefined ||
|
|
1098
|
+
typeof block.title === "string") &&
|
|
1099
|
+
(!hasOwnProperty(block, "description") ||
|
|
1100
|
+
block.description === undefined ||
|
|
1101
|
+
typeof block.description === "string") &&
|
|
1102
|
+
(!hasOwnProperty(block, "mimeType") ||
|
|
1103
|
+
block.mimeType === undefined ||
|
|
1104
|
+
typeof block.mimeType === "string") &&
|
|
1105
|
+
(!hasOwnProperty(block, "size") || block.size === undefined || typeof block.size === "number"));
|
|
1106
|
+
}
|
|
1107
|
+
if (block.type !== "resource" ||
|
|
1108
|
+
!hasOwnProperty(block, "resource") ||
|
|
1109
|
+
typeof block.resource !== "object" ||
|
|
1110
|
+
block.resource === null) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
return isResourceContents(block.resource);
|
|
1114
|
+
}
|
|
1115
|
+
function isResourceContents(value) {
|
|
1116
|
+
if (typeof value !== "object" ||
|
|
1117
|
+
value === null ||
|
|
1118
|
+
!hasOwnProperty(value, "uri") ||
|
|
1119
|
+
typeof value.uri !== "string" ||
|
|
1120
|
+
!isValidUri(value.uri)) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
if (hasOwnProperty(value, "mimeType") &&
|
|
1124
|
+
value.mimeType !== undefined &&
|
|
1125
|
+
typeof value.mimeType !== "string") {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
return ((hasOwnProperty(value, "text") && typeof value.text === "string") ||
|
|
1129
|
+
(hasOwnProperty(value, "blob") && typeof value.blob === "string" && isBase64(value.blob)));
|
|
1130
|
+
}
|
|
1131
|
+
function hasValidContentAnnotations(value) {
|
|
1132
|
+
if (!hasOwnProperty(value, "annotations") || value.annotations === undefined) {
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
if (!isJsonObject(value.annotations)) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
const { audience, priority, lastModified } = value.annotations;
|
|
1139
|
+
return ((audience === undefined ||
|
|
1140
|
+
(Array.isArray(audience) &&
|
|
1141
|
+
audience.every((item) => item === "user" || item === "assistant"))) &&
|
|
1142
|
+
(priority === undefined || typeof priority === "number") &&
|
|
1143
|
+
(lastModified === undefined || typeof lastModified === "string"));
|
|
1144
|
+
}
|
|
1145
|
+
function isPromptContentItem(value) {
|
|
1146
|
+
if (!isContentItem(value)) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
return !(typeof value === "object" &&
|
|
1150
|
+
value !== null &&
|
|
1151
|
+
hasOwnProperty(value, "type") &&
|
|
1152
|
+
value.type === "resource_link");
|
|
1153
|
+
}
|
|
1154
|
+
function hasOwnProperty(value, name) {
|
|
1155
|
+
return Object.prototype.hasOwnProperty.call(value, name);
|
|
1156
|
+
}
|