tiny-stdio-mcp-server 0.1.15 → 0.1.17
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 +7 -3
- package/dist/server.js +184 -206
- package/dist/types.d.ts +2 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ server.tool("search", "Search for things", schema, async ({ query, limit }) => {
|
|
|
58
58
|
});
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
For structured-data tools, pass a root-object output schema. The server advertises it as MCP `Tool.outputSchema`, validates
|
|
61
|
+
For structured-data tools, pass a root-object output schema. The server advertises it as MCP `Tool.outputSchema`, validates successful handler results, and returns them as `CallToolResult.structuredContent`. Plain object returns receive a JSON text backstop in `content[]` for older clients. Explicit `CallToolResult` values keep their handler-supplied content blocks; the JSON text backstop is added only when `content` is empty.
|
|
62
62
|
|
|
63
63
|
```ts
|
|
64
64
|
const input = defineSchema({
|
|
@@ -89,7 +89,11 @@ server.tool(
|
|
|
89
89
|
);
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Output schemas must have `type: "object"` at the root
|
|
92
|
+
Output schemas must have `type: "object"` at the root because MCP structured content is an object. Otherwise, any schema accepted by Ajv is supported, including composition keywords, nullable type unions, and local `$defs`/`$ref` references. Input and output schemas compile synchronously during `.tool()` or `.registerTool()` registration, so malformed schemas throw immediately instead of failing on the first tool call.
|
|
93
|
+
|
|
94
|
+
Successful structured results must satisfy `outputSchema`. Validation failures use JSON-RPC `-32602` for inputs and `-32603` for outputs, with Ajv's formatted error text in the message and the raw Ajv error array in `error.data`. Explicit handler results with `isError: true` are passed through unchanged and are exempt from structured-content and output-schema validation.
|
|
95
|
+
|
|
96
|
+
Tools whose natural result is prose, images, audio, files, or other content blocks should omit `outputSchema` and keep returning content.
|
|
93
97
|
|
|
94
98
|
### `.listen()`
|
|
95
99
|
|
|
@@ -121,7 +125,7 @@ Dynamically add/remove tools at runtime and notify connected clients.
|
|
|
121
125
|
|
|
122
126
|
## Configuration Options
|
|
123
127
|
|
|
124
|
-
- `createServer({ name, version })`: server identity
|
|
128
|
+
- `createServer({ name, version, toolCallTimeoutMs? })`: server identity plus an optional positive-integer tool timeout in milliseconds. Timed-out calls return JSON-RPC error `-32603`; handlers continue running in the background.
|
|
125
129
|
- `.tool(name, description, schema, handler, outputSchema?)`: tool metadata, input schema, handler, and optional structured output schema.
|
|
126
130
|
- `.connect({ readable, writable })`: custom stream transport for tests or embedded hosts.
|
|
127
131
|
|
package/dist/server.js
CHANGED
|
@@ -3,15 +3,15 @@ import AjvModule from "ajv";
|
|
|
3
3
|
import uriTemplateParser from "uri-template";
|
|
4
4
|
import UriTemplate from "uri-template-lite";
|
|
5
5
|
import { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
6
|
-
import { parseMessage, formatSuccessResponse, formatErrorResponse
|
|
6
|
+
import { parseMessage, formatSuccessResponse, formatErrorResponse } from "./jsonrpc.js";
|
|
7
7
|
import { toContentBlocks } from "./content/convert.js";
|
|
8
8
|
const PROTOCOL_VERSION = "2025-11-25";
|
|
9
|
-
const SUPPORTED_PROTOCOL_VERSIONS = new Set([
|
|
10
|
-
"2025-03-26",
|
|
11
|
-
"2025-06-18",
|
|
12
|
-
PROTOCOL_VERSION,
|
|
13
|
-
]);
|
|
9
|
+
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-03-26", "2025-06-18", PROTOCOL_VERSION]);
|
|
14
10
|
export function createServer(options) {
|
|
11
|
+
if (options.toolCallTimeoutMs !== undefined &&
|
|
12
|
+
(!Number.isInteger(options.toolCallTimeoutMs) || options.toolCallTimeoutMs <= 0)) {
|
|
13
|
+
throw new Error("toolCallTimeoutMs must be a positive integer.");
|
|
14
|
+
}
|
|
15
15
|
const Ajv = "default" in AjvModule ? AjvModule.default : AjvModule;
|
|
16
16
|
const jsonSchemaValidator = new Ajv({ strict: false });
|
|
17
17
|
const supportNotifications = options.supportNotifications !== false;
|
|
@@ -26,7 +26,7 @@ export function createServer(options) {
|
|
|
26
26
|
initialized: false,
|
|
27
27
|
initializeAccepted: false,
|
|
28
28
|
notificationReady: false,
|
|
29
|
-
resourceSubscriptions: new Set()
|
|
29
|
+
resourceSubscriptions: new Set()
|
|
30
30
|
};
|
|
31
31
|
const messageLifecycles = new Set([defaultLifecycle]);
|
|
32
32
|
const handleMessageWithLifecycle = async (method, lifecycle, params) => {
|
|
@@ -43,29 +43,27 @@ export function createServer(options) {
|
|
|
43
43
|
lifecycle.initializeAccepted = true;
|
|
44
44
|
lifecycle.initialized = true;
|
|
45
45
|
lifecycle.notificationReady = false;
|
|
46
|
-
const requestedProtocol = typeof params?.protocolVersion === "string"
|
|
47
|
-
? params.protocolVersion
|
|
48
|
-
: undefined;
|
|
46
|
+
const requestedProtocol = typeof params?.protocolVersion === "string" ? params.protocolVersion : undefined;
|
|
49
47
|
const result = {
|
|
50
48
|
protocolVersion: requestedProtocol !== undefined && SUPPORTED_PROTOCOL_VERSIONS.has(requestedProtocol)
|
|
51
49
|
? requestedProtocol
|
|
52
50
|
: PROTOCOL_VERSION,
|
|
53
51
|
capabilities: {
|
|
54
52
|
tools: {
|
|
55
|
-
...(supportNotifications ? { listChanged: true } : {})
|
|
53
|
+
...(supportNotifications ? { listChanged: true } : {})
|
|
56
54
|
},
|
|
57
55
|
prompts: {
|
|
58
|
-
...(supportNotifications ? { listChanged: true } : {})
|
|
56
|
+
...(supportNotifications ? { listChanged: true } : {})
|
|
59
57
|
},
|
|
60
58
|
resources: {
|
|
61
59
|
...(supportNotifications ? { listChanged: true } : {}),
|
|
62
|
-
...(supportResourceSubscriptions ? { subscribe: true } : {})
|
|
63
|
-
}
|
|
60
|
+
...(supportResourceSubscriptions ? { subscribe: true } : {})
|
|
61
|
+
}
|
|
64
62
|
},
|
|
65
63
|
serverInfo: {
|
|
66
64
|
name: options.name,
|
|
67
|
-
version: options.version
|
|
68
|
-
}
|
|
65
|
+
version: options.version
|
|
66
|
+
}
|
|
69
67
|
};
|
|
70
68
|
return { result };
|
|
71
69
|
}
|
|
@@ -74,8 +72,8 @@ export function createServer(options) {
|
|
|
74
72
|
return {
|
|
75
73
|
error: {
|
|
76
74
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
77
|
-
message: "Server not initialized"
|
|
78
|
-
}
|
|
75
|
+
message: "Server not initialized"
|
|
76
|
+
}
|
|
79
77
|
};
|
|
80
78
|
}
|
|
81
79
|
lifecycle.notificationReady = true;
|
|
@@ -86,8 +84,8 @@ export function createServer(options) {
|
|
|
86
84
|
return {
|
|
87
85
|
error: {
|
|
88
86
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
89
|
-
message: "Server not initialized"
|
|
90
|
-
}
|
|
87
|
+
message: "Server not initialized"
|
|
88
|
+
}
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
if (method === "tools/list") {
|
|
@@ -95,8 +93,10 @@ export function createServer(options) {
|
|
|
95
93
|
for (const tool of tools.values()) {
|
|
96
94
|
const descriptor = { ...tool };
|
|
97
95
|
delete descriptor.handler;
|
|
96
|
+
delete descriptor.inputValidator;
|
|
97
|
+
delete descriptor.outputValidator;
|
|
98
98
|
toolList.push({
|
|
99
|
-
...descriptor
|
|
99
|
+
...descriptor
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
return { result: { tools: toolList } };
|
|
@@ -107,8 +107,8 @@ export function createServer(options) {
|
|
|
107
107
|
return {
|
|
108
108
|
error: {
|
|
109
109
|
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
110
|
-
message: "Tool name required"
|
|
111
|
-
}
|
|
110
|
+
message: "Tool name required"
|
|
111
|
+
}
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
const tool = tools.get(toolName);
|
|
@@ -116,24 +116,48 @@ export function createServer(options) {
|
|
|
116
116
|
return {
|
|
117
117
|
error: {
|
|
118
118
|
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
119
|
-
message: `Tool not found: ${toolName}
|
|
120
|
-
}
|
|
119
|
+
message: `Tool not found: ${toolName}`
|
|
120
|
+
}
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
const toolArgs = (params?.arguments ?? {});
|
|
124
|
-
if (options.validateToolArguments !== false && !
|
|
124
|
+
if (options.validateToolArguments !== false && !tool.inputValidator(toolArgs)) {
|
|
125
|
+
const errors = tool.inputValidator.errors ?? [];
|
|
125
126
|
return {
|
|
126
127
|
error: {
|
|
127
128
|
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
128
|
-
message:
|
|
129
|
-
|
|
129
|
+
message: `Invalid tool arguments: ${jsonSchemaValidator.errorsText(errors)}`,
|
|
130
|
+
data: errors
|
|
131
|
+
}
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
try {
|
|
133
|
-
|
|
135
|
+
let handlerResult;
|
|
136
|
+
if (options.toolCallTimeoutMs === undefined) {
|
|
137
|
+
handlerResult = await tool.handler(toolArgs);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
let timeout;
|
|
141
|
+
const handlerPromise = Promise.resolve().then(() => tool.handler(toolArgs));
|
|
142
|
+
handlerResult = await Promise.race([
|
|
143
|
+
handlerPromise,
|
|
144
|
+
new Promise((_resolve, reject) => {
|
|
145
|
+
timeout = setTimeout(() => {
|
|
146
|
+
reject(new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Tool call timed out: ${toolName}`));
|
|
147
|
+
}, options.toolCallTimeoutMs);
|
|
148
|
+
})
|
|
149
|
+
]).finally(() => {
|
|
150
|
+
if (timeout !== undefined) {
|
|
151
|
+
clearTimeout(timeout);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
134
155
|
const result = normalizeToolResult(handlerResult, tool.outputSchema);
|
|
135
|
-
if (
|
|
136
|
-
|
|
156
|
+
if (result.isError !== true &&
|
|
157
|
+
tool.outputValidator !== undefined &&
|
|
158
|
+
!tool.outputValidator(result.structuredContent)) {
|
|
159
|
+
const errors = tool.outputValidator.errors ?? [];
|
|
160
|
+
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Invalid structured tool result: ${jsonSchemaValidator.errorsText(errors)}`, errors);
|
|
137
161
|
}
|
|
138
162
|
return { result };
|
|
139
163
|
}
|
|
@@ -143,14 +167,14 @@ export function createServer(options) {
|
|
|
143
167
|
error: {
|
|
144
168
|
code: err.code,
|
|
145
169
|
message: err.message,
|
|
146
|
-
...(err.data === undefined ? {} : { data: err.data })
|
|
147
|
-
}
|
|
170
|
+
...(err.data === undefined ? {} : { data: err.data })
|
|
171
|
+
}
|
|
148
172
|
};
|
|
149
173
|
}
|
|
150
174
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
151
175
|
const result = {
|
|
152
176
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
153
|
-
isError: true
|
|
177
|
+
isError: true
|
|
154
178
|
};
|
|
155
179
|
return { result };
|
|
156
180
|
}
|
|
@@ -158,8 +182,8 @@ export function createServer(options) {
|
|
|
158
182
|
if (method === "prompts/list") {
|
|
159
183
|
return {
|
|
160
184
|
result: {
|
|
161
|
-
prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt)
|
|
162
|
-
}
|
|
185
|
+
prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt)
|
|
186
|
+
}
|
|
163
187
|
};
|
|
164
188
|
}
|
|
165
189
|
if (method === "prompts/get") {
|
|
@@ -189,15 +213,15 @@ export function createServer(options) {
|
|
|
189
213
|
if (method === "resources/list") {
|
|
190
214
|
return {
|
|
191
215
|
result: {
|
|
192
|
-
resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource)
|
|
193
|
-
}
|
|
216
|
+
resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource)
|
|
217
|
+
}
|
|
194
218
|
};
|
|
195
219
|
}
|
|
196
220
|
if (method === "resources/templates/list") {
|
|
197
221
|
return {
|
|
198
222
|
result: {
|
|
199
|
-
resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate)
|
|
200
|
-
}
|
|
223
|
+
resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate)
|
|
224
|
+
}
|
|
201
225
|
};
|
|
202
226
|
}
|
|
203
227
|
if (method === "resources/read") {
|
|
@@ -225,16 +249,16 @@ export function createServer(options) {
|
|
|
225
249
|
return {
|
|
226
250
|
error: {
|
|
227
251
|
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
228
|
-
message: "Method not found"
|
|
229
|
-
}
|
|
252
|
+
message: "Method not found"
|
|
253
|
+
}
|
|
230
254
|
};
|
|
231
255
|
}
|
|
232
256
|
const uri = typeof params?.uri === "string" ? params.uri : undefined;
|
|
233
257
|
if (uri === undefined || !isValidUri(uri)) {
|
|
234
258
|
return invalidParams("Resource URI required");
|
|
235
259
|
}
|
|
236
|
-
if (method === "resources/subscribe"
|
|
237
|
-
|
|
260
|
+
if (method === "resources/subscribe" &&
|
|
261
|
+
findReadableResource(uri, resources, resourceTemplates) === undefined) {
|
|
238
262
|
return resourceNotFound(uri);
|
|
239
263
|
}
|
|
240
264
|
if (method === "resources/subscribe") {
|
|
@@ -248,8 +272,8 @@ export function createServer(options) {
|
|
|
248
272
|
return {
|
|
249
273
|
error: {
|
|
250
274
|
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
251
|
-
message: "Method not found"
|
|
252
|
-
}
|
|
275
|
+
message: "Method not found"
|
|
276
|
+
}
|
|
253
277
|
};
|
|
254
278
|
};
|
|
255
279
|
const createMessageSession = (listener) => {
|
|
@@ -257,7 +281,7 @@ export function createServer(options) {
|
|
|
257
281
|
initialized: false,
|
|
258
282
|
initializeAccepted: false,
|
|
259
283
|
notificationReady: false,
|
|
260
|
-
resourceSubscriptions: new Set()
|
|
284
|
+
resourceSubscriptions: new Set()
|
|
261
285
|
};
|
|
262
286
|
messageLifecycles.add(lifecycle);
|
|
263
287
|
if (listener !== undefined) {
|
|
@@ -270,7 +294,7 @@ export function createServer(options) {
|
|
|
270
294
|
connectionNotificationListeners.delete(listener);
|
|
271
295
|
}
|
|
272
296
|
messageLifecycles.delete(lifecycle);
|
|
273
|
-
}
|
|
297
|
+
}
|
|
274
298
|
};
|
|
275
299
|
};
|
|
276
300
|
const handleMessage = (method, params) => handleMessageWithLifecycle(method, defaultLifecycle, params);
|
|
@@ -288,7 +312,7 @@ export function createServer(options) {
|
|
|
288
312
|
const requestWithId = request;
|
|
289
313
|
write(formatErrorResponse(requestWithId.id, {
|
|
290
314
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
291
|
-
message: "Invalid Request"
|
|
315
|
+
message: "Invalid Request"
|
|
292
316
|
}) + "\n");
|
|
293
317
|
return;
|
|
294
318
|
}
|
|
@@ -301,7 +325,7 @@ export function createServer(options) {
|
|
|
301
325
|
const requestWithId = request;
|
|
302
326
|
write(formatErrorResponse(requestWithId.id, {
|
|
303
327
|
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
304
|
-
message: "Internal error"
|
|
328
|
+
message: "Internal error"
|
|
305
329
|
}) + "\n");
|
|
306
330
|
}
|
|
307
331
|
return;
|
|
@@ -322,7 +346,7 @@ export function createServer(options) {
|
|
|
322
346
|
const notification = {
|
|
323
347
|
jsonrpc: "2.0",
|
|
324
348
|
method,
|
|
325
|
-
...(params === undefined ? {} : { params })
|
|
349
|
+
...(params === undefined ? {} : { params })
|
|
326
350
|
};
|
|
327
351
|
for (const listener of notificationListeners) {
|
|
328
352
|
listener(notification);
|
|
@@ -336,8 +360,11 @@ export function createServer(options) {
|
|
|
336
360
|
const server = {
|
|
337
361
|
tool(name, description, inputSchema, handler, outputSchema) {
|
|
338
362
|
assertNonEmptyName(name, "Tool name required");
|
|
363
|
+
const inputValidator = jsonSchemaValidator.compile(inputSchema);
|
|
364
|
+
let outputValidator;
|
|
339
365
|
if (outputSchema !== undefined) {
|
|
340
|
-
|
|
366
|
+
assertObjectRootSchema(outputSchema, "outputSchema");
|
|
367
|
+
outputValidator = jsonSchemaValidator.compile(outputSchema);
|
|
341
368
|
}
|
|
342
369
|
tools.set(name, {
|
|
343
370
|
name,
|
|
@@ -345,17 +372,24 @@ export function createServer(options) {
|
|
|
345
372
|
inputSchema: inputSchema,
|
|
346
373
|
...(outputSchema === undefined ? {} : { outputSchema: outputSchema }),
|
|
347
374
|
handler: handler,
|
|
375
|
+
inputValidator,
|
|
376
|
+
...(outputValidator === undefined ? {} : { outputValidator })
|
|
348
377
|
});
|
|
349
378
|
return server;
|
|
350
379
|
},
|
|
351
380
|
registerTool(definition, handler) {
|
|
352
381
|
assertNonEmptyName(definition.name, "Tool name required");
|
|
382
|
+
const inputValidator = jsonSchemaValidator.compile(definition.inputSchema);
|
|
383
|
+
let outputValidator;
|
|
353
384
|
if (definition.outputSchema !== undefined) {
|
|
354
|
-
|
|
385
|
+
assertObjectRootSchema(definition.outputSchema, "outputSchema");
|
|
386
|
+
outputValidator = jsonSchemaValidator.compile(definition.outputSchema);
|
|
355
387
|
}
|
|
356
388
|
tools.set(definition.name, {
|
|
357
389
|
...definition,
|
|
358
390
|
handler: handler,
|
|
391
|
+
inputValidator,
|
|
392
|
+
...(outputValidator === undefined ? {} : { outputValidator })
|
|
359
393
|
});
|
|
360
394
|
return server;
|
|
361
395
|
},
|
|
@@ -396,17 +430,20 @@ export function createServer(options) {
|
|
|
396
430
|
return resourceTemplates.delete(uriTemplate);
|
|
397
431
|
},
|
|
398
432
|
async notifyToolsChanged() {
|
|
399
|
-
if (supportNotifications &&
|
|
433
|
+
if (supportNotifications &&
|
|
434
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
400
435
|
await broadcastNotification("notifications/tools/list_changed");
|
|
401
436
|
}
|
|
402
437
|
},
|
|
403
438
|
async notifyPromptsChanged() {
|
|
404
|
-
if (supportNotifications &&
|
|
439
|
+
if (supportNotifications &&
|
|
440
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
405
441
|
await broadcastNotification("notifications/prompts/list_changed");
|
|
406
442
|
}
|
|
407
443
|
},
|
|
408
444
|
async notifyResourcesChanged() {
|
|
409
|
-
if (supportNotifications &&
|
|
445
|
+
if (supportNotifications &&
|
|
446
|
+
[...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
|
|
410
447
|
await broadcastNotification("notifications/resources/list_changed");
|
|
411
448
|
}
|
|
412
449
|
},
|
|
@@ -421,7 +458,7 @@ export function createServer(options) {
|
|
|
421
458
|
async listen() {
|
|
422
459
|
return server.connect({
|
|
423
460
|
readable: process.stdin,
|
|
424
|
-
writable: process.stdout
|
|
461
|
+
writable: process.stdout
|
|
425
462
|
});
|
|
426
463
|
},
|
|
427
464
|
async connect(transport) {
|
|
@@ -432,7 +469,7 @@ export function createServer(options) {
|
|
|
432
469
|
const session = server.createMessageSession(listener);
|
|
433
470
|
const rl = readline.createInterface({
|
|
434
471
|
input: transport.readable,
|
|
435
|
-
crlfDelay: Infinity
|
|
472
|
+
crlfDelay: Infinity
|
|
436
473
|
});
|
|
437
474
|
const pendingMessages = new Set();
|
|
438
475
|
rl.on("line", (line) => {
|
|
@@ -477,8 +514,8 @@ export function createServer(options) {
|
|
|
477
514
|
id: message.id,
|
|
478
515
|
error: {
|
|
479
516
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
480
|
-
message: "Invalid Request"
|
|
481
|
-
}
|
|
517
|
+
message: "Invalid Request"
|
|
518
|
+
}
|
|
482
519
|
});
|
|
483
520
|
return;
|
|
484
521
|
}
|
|
@@ -493,8 +530,8 @@ export function createServer(options) {
|
|
|
493
530
|
id: request.id,
|
|
494
531
|
error: {
|
|
495
532
|
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
496
|
-
message: "Internal error"
|
|
497
|
-
}
|
|
533
|
+
message: "Internal error"
|
|
534
|
+
}
|
|
498
535
|
});
|
|
499
536
|
return;
|
|
500
537
|
}
|
|
@@ -503,7 +540,7 @@ export function createServer(options) {
|
|
|
503
540
|
const response = {
|
|
504
541
|
jsonrpc: "2.0",
|
|
505
542
|
id: request.id,
|
|
506
|
-
error
|
|
543
|
+
error
|
|
507
544
|
};
|
|
508
545
|
await transport.send(response);
|
|
509
546
|
}
|
|
@@ -511,7 +548,7 @@ export function createServer(options) {
|
|
|
511
548
|
const response = {
|
|
512
549
|
jsonrpc: "2.0",
|
|
513
550
|
id: request.id,
|
|
514
|
-
result
|
|
551
|
+
result
|
|
515
552
|
};
|
|
516
553
|
await transport.send(response);
|
|
517
554
|
}
|
|
@@ -525,7 +562,7 @@ export function createServer(options) {
|
|
|
525
562
|
reject(error);
|
|
526
563
|
});
|
|
527
564
|
});
|
|
528
|
-
}
|
|
565
|
+
}
|
|
529
566
|
};
|
|
530
567
|
return server;
|
|
531
568
|
}
|
|
@@ -533,24 +570,24 @@ function invalidParams(message) {
|
|
|
533
570
|
return {
|
|
534
571
|
error: {
|
|
535
572
|
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
536
|
-
message
|
|
537
|
-
}
|
|
573
|
+
message
|
|
574
|
+
}
|
|
538
575
|
};
|
|
539
576
|
}
|
|
540
577
|
function internalError(message) {
|
|
541
578
|
return {
|
|
542
579
|
error: {
|
|
543
580
|
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
544
|
-
message
|
|
545
|
-
}
|
|
581
|
+
message
|
|
582
|
+
}
|
|
546
583
|
};
|
|
547
584
|
}
|
|
548
585
|
function resourceNotFound(uri) {
|
|
549
586
|
return {
|
|
550
587
|
error: {
|
|
551
588
|
code: JSON_RPC_ERROR_CODES.RESOURCE_NOT_FOUND,
|
|
552
|
-
message: `Resource not found: ${uri}
|
|
553
|
-
}
|
|
589
|
+
message: `Resource not found: ${uri}`
|
|
590
|
+
}
|
|
554
591
|
};
|
|
555
592
|
}
|
|
556
593
|
function toErrorMessage(error) {
|
|
@@ -573,7 +610,7 @@ function assertNonEmptyName(name, message) {
|
|
|
573
610
|
function assertReadableUriTemplate(uriTemplate) {
|
|
574
611
|
const parsed = uriTemplateParser.parse(uriTemplate);
|
|
575
612
|
const expanded = parsed.expand(new Proxy({}, {
|
|
576
|
-
get: (_target, property) => typeof property === "string" ? "value" : undefined
|
|
613
|
+
get: (_target, property) => (typeof property === "string" ? "value" : undefined)
|
|
577
614
|
}));
|
|
578
615
|
if (typeof expanded !== "string" || !isValidUri(expanded)) {
|
|
579
616
|
throw new Error(`Invalid resource URI template: ${uriTemplate}`);
|
|
@@ -617,14 +654,14 @@ function isCallToolResult(value) {
|
|
|
617
654
|
if (!hasContentArray(value) || !value.content.every(isContentItem)) {
|
|
618
655
|
return false;
|
|
619
656
|
}
|
|
620
|
-
if (hasOwnProperty(value, "structuredContent")
|
|
621
|
-
|
|
622
|
-
|
|
657
|
+
if (hasOwnProperty(value, "structuredContent") &&
|
|
658
|
+
value.structuredContent !== undefined &&
|
|
659
|
+
!isJsonObject(value.structuredContent)) {
|
|
623
660
|
return false;
|
|
624
661
|
}
|
|
625
|
-
return !(hasOwnProperty(value, "isError")
|
|
626
|
-
|
|
627
|
-
|
|
662
|
+
return !(hasOwnProperty(value, "isError") &&
|
|
663
|
+
value.isError !== undefined &&
|
|
664
|
+
typeof value.isError !== "boolean");
|
|
628
665
|
}
|
|
629
666
|
function normalizeToolResult(handlerResult, outputSchema) {
|
|
630
667
|
if (hasContentArray(handlerResult) && !isCallToolResult(handlerResult)) {
|
|
@@ -639,102 +676,27 @@ function normalizeToolResult(handlerResult, outputSchema) {
|
|
|
639
676
|
}
|
|
640
677
|
return result;
|
|
641
678
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
679
|
+
if (isCallToolResult(handlerResult) && handlerResult.isError === true) {
|
|
680
|
+
return handlerResult;
|
|
681
|
+
}
|
|
682
|
+
const callToolResult = isCallToolResult(handlerResult) ? handlerResult : undefined;
|
|
683
|
+
const structuredContent = callToolResult ? callToolResult.structuredContent : handlerResult;
|
|
645
684
|
if (!isJsonObject(structuredContent)) {
|
|
646
685
|
throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Structured tool result must be an object");
|
|
647
686
|
}
|
|
648
687
|
return {
|
|
649
|
-
content:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
structuredContent
|
|
688
|
+
content: callToolResult !== undefined && callToolResult.content.length > 0
|
|
689
|
+
? callToolResult.content
|
|
690
|
+
: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
691
|
+
...(callToolResult?.isError !== undefined ? { isError: callToolResult.isError } : {}),
|
|
692
|
+
structuredContent
|
|
654
693
|
};
|
|
655
694
|
}
|
|
656
|
-
function assertSupportedOutputSchema(schema) {
|
|
657
|
-
assertObjectRootSchema(schema, "outputSchema");
|
|
658
|
-
assertSupportedJsonSchema(schema, "outputSchema");
|
|
659
|
-
}
|
|
660
695
|
function assertObjectRootSchema(schema, path) {
|
|
661
696
|
if (schema.type !== "object") {
|
|
662
697
|
throw new Error(`${path} root type must be "object"`);
|
|
663
698
|
}
|
|
664
699
|
}
|
|
665
|
-
function assertSupportedJsonSchema(schema, path) {
|
|
666
|
-
for (const keyword of ["anyOf", "allOf", "not", "if", "then", "else", "contains", "prefixItems"]) {
|
|
667
|
-
if (schema[keyword] !== undefined) {
|
|
668
|
-
throw new Error(`${path} uses unsupported keyword "${keyword}"`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
const type = schema.type;
|
|
672
|
-
if (Array.isArray(type)) {
|
|
673
|
-
const supported = new Set(["string", "number", "integer", "boolean", "object", "array", "null"]);
|
|
674
|
-
for (const item of type) {
|
|
675
|
-
if (!supported.has(item)) {
|
|
676
|
-
throw new Error(`${path} uses unsupported type "${item}"`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
else if (type !== undefined &&
|
|
681
|
-
type !== "string" &&
|
|
682
|
-
type !== "number" &&
|
|
683
|
-
type !== "integer" &&
|
|
684
|
-
type !== "boolean" &&
|
|
685
|
-
type !== "object" &&
|
|
686
|
-
type !== "array") {
|
|
687
|
-
throw new Error(`${path} uses unsupported type "${type}"`);
|
|
688
|
-
}
|
|
689
|
-
if (isJsonObject(schema.properties)) {
|
|
690
|
-
for (const [key, child] of Object.entries(schema.properties)) {
|
|
691
|
-
if (isJsonObject(child)) {
|
|
692
|
-
assertSupportedJsonSchema(child, `${path}.properties.${key}`);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
throw new Error(`${path}.properties.${key} must be an object schema`);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
else if (schema.properties !== undefined) {
|
|
700
|
-
throw new Error(`${path}.properties must be an object`);
|
|
701
|
-
}
|
|
702
|
-
const additionalProperties = schema.additionalProperties;
|
|
703
|
-
if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
704
|
-
if (Array.isArray(additionalProperties)) {
|
|
705
|
-
throw new Error(`${path}.additionalProperties must be an object schema or boolean`);
|
|
706
|
-
}
|
|
707
|
-
assertSupportedJsonSchema(additionalProperties, `${path}.additionalProperties`);
|
|
708
|
-
}
|
|
709
|
-
else if (additionalProperties !== undefined
|
|
710
|
-
&& typeof additionalProperties !== "boolean") {
|
|
711
|
-
throw new Error(`${path}.additionalProperties must be an object schema or boolean`);
|
|
712
|
-
}
|
|
713
|
-
const items = schema.items;
|
|
714
|
-
if (Array.isArray(items)) {
|
|
715
|
-
throw new Error(`${path}.items uses unsupported tuple array schemas`);
|
|
716
|
-
}
|
|
717
|
-
if (typeof items === "object" && items !== null && !Array.isArray(items)) {
|
|
718
|
-
assertSupportedJsonSchema(items, `${path}.items`);
|
|
719
|
-
}
|
|
720
|
-
else if (items !== undefined && typeof items !== "boolean") {
|
|
721
|
-
throw new Error(`${path}.items must be an object schema or boolean`);
|
|
722
|
-
}
|
|
723
|
-
const oneOf = schema.oneOf;
|
|
724
|
-
if (Array.isArray(oneOf)) {
|
|
725
|
-
for (const [index, child] of oneOf.entries()) {
|
|
726
|
-
if (typeof child === "object" && child !== null && !Array.isArray(child)) {
|
|
727
|
-
assertSupportedJsonSchema(child, `${path}.oneOf[${index}]`);
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
throw new Error(`${path}.oneOf[${index}] must be an object schema`);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
else if (oneOf !== undefined) {
|
|
735
|
-
throw new Error(`${path}.oneOf must be an array`);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
700
|
function isJsonObject(value) {
|
|
739
701
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
740
702
|
}
|
|
@@ -742,25 +704,28 @@ function isGetPromptResult(value) {
|
|
|
742
704
|
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "messages")) {
|
|
743
705
|
return false;
|
|
744
706
|
}
|
|
745
|
-
return (!hasOwnProperty(value, "description") ||
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
707
|
+
return ((!hasOwnProperty(value, "description") ||
|
|
708
|
+
value.description === undefined ||
|
|
709
|
+
typeof value.description === "string") &&
|
|
710
|
+
Array.isArray(value.messages) &&
|
|
711
|
+
value.messages.every((message) => typeof message === "object" &&
|
|
712
|
+
message !== null &&
|
|
713
|
+
hasOwnProperty(message, "role") &&
|
|
714
|
+
(message.role === "user" || message.role === "assistant") &&
|
|
715
|
+
hasOwnProperty(message, "content") &&
|
|
716
|
+
isPromptContentItem(message.content)));
|
|
753
717
|
}
|
|
754
718
|
function isReadResourceResult(value) {
|
|
755
719
|
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "contents")) {
|
|
756
720
|
return false;
|
|
757
721
|
}
|
|
758
|
-
return Array.isArray(value.contents)
|
|
759
|
-
&& value.contents.every(isResourceContents);
|
|
722
|
+
return Array.isArray(value.contents) && value.contents.every(isResourceContents);
|
|
760
723
|
}
|
|
761
724
|
function hasContentArray(value) {
|
|
762
|
-
return typeof value === "object" &&
|
|
763
|
-
&&
|
|
725
|
+
return (typeof value === "object" &&
|
|
726
|
+
value !== null &&
|
|
727
|
+
hasOwnProperty(value, "content") &&
|
|
728
|
+
Array.isArray(value.content));
|
|
764
729
|
}
|
|
765
730
|
function isContentItem(value) {
|
|
766
731
|
if (typeof value !== "object" || value === null || !hasOwnProperty(value, "type")) {
|
|
@@ -774,44 +739,52 @@ function isContentItem(value) {
|
|
|
774
739
|
return hasOwnProperty(block, "text") && typeof block.text === "string";
|
|
775
740
|
}
|
|
776
741
|
if (block.type === "image" || block.type === "audio") {
|
|
777
|
-
return hasOwnProperty(block, "data")
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
742
|
+
return (hasOwnProperty(block, "data") &&
|
|
743
|
+
typeof block.data === "string" &&
|
|
744
|
+
isBase64(block.data) &&
|
|
745
|
+
hasOwnProperty(block, "mimeType") &&
|
|
746
|
+
typeof block.mimeType === "string");
|
|
782
747
|
}
|
|
783
748
|
if (block.type === "resource_link") {
|
|
784
|
-
return hasOwnProperty(block, "uri")
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
749
|
+
return (hasOwnProperty(block, "uri") &&
|
|
750
|
+
typeof block.uri === "string" &&
|
|
751
|
+
isValidUri(block.uri) &&
|
|
752
|
+
hasOwnProperty(block, "name") &&
|
|
753
|
+
typeof block.name === "string" &&
|
|
754
|
+
(!hasOwnProperty(block, "title") ||
|
|
755
|
+
block.title === undefined ||
|
|
756
|
+
typeof block.title === "string") &&
|
|
757
|
+
(!hasOwnProperty(block, "description") ||
|
|
758
|
+
block.description === undefined ||
|
|
759
|
+
typeof block.description === "string") &&
|
|
760
|
+
(!hasOwnProperty(block, "mimeType") ||
|
|
761
|
+
block.mimeType === undefined ||
|
|
762
|
+
typeof block.mimeType === "string") &&
|
|
763
|
+
(!hasOwnProperty(block, "size") || block.size === undefined || typeof block.size === "number"));
|
|
764
|
+
}
|
|
765
|
+
if (block.type !== "resource" ||
|
|
766
|
+
!hasOwnProperty(block, "resource") ||
|
|
767
|
+
typeof block.resource !== "object" ||
|
|
768
|
+
block.resource === null) {
|
|
798
769
|
return false;
|
|
799
770
|
}
|
|
800
771
|
return isResourceContents(block.resource);
|
|
801
772
|
}
|
|
802
773
|
function isResourceContents(value) {
|
|
803
|
-
if (typeof value !== "object"
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
774
|
+
if (typeof value !== "object" ||
|
|
775
|
+
value === null ||
|
|
776
|
+
!hasOwnProperty(value, "uri") ||
|
|
777
|
+
typeof value.uri !== "string" ||
|
|
778
|
+
!isValidUri(value.uri)) {
|
|
808
779
|
return false;
|
|
809
780
|
}
|
|
810
|
-
if (hasOwnProperty(value, "mimeType") &&
|
|
781
|
+
if (hasOwnProperty(value, "mimeType") &&
|
|
782
|
+
value.mimeType !== undefined &&
|
|
783
|
+
typeof value.mimeType !== "string") {
|
|
811
784
|
return false;
|
|
812
785
|
}
|
|
813
|
-
return (hasOwnProperty(value, "text") && typeof value.text === "string")
|
|
814
|
-
|
|
786
|
+
return ((hasOwnProperty(value, "text") && typeof value.text === "string") ||
|
|
787
|
+
(hasOwnProperty(value, "blob") && typeof value.blob === "string" && isBase64(value.blob)));
|
|
815
788
|
}
|
|
816
789
|
function hasValidContentAnnotations(value) {
|
|
817
790
|
if (!hasOwnProperty(value, "annotations") || value.annotations === undefined) {
|
|
@@ -821,9 +794,11 @@ function hasValidContentAnnotations(value) {
|
|
|
821
794
|
return false;
|
|
822
795
|
}
|
|
823
796
|
const { audience, priority, lastModified } = value.annotations;
|
|
824
|
-
return (audience === undefined ||
|
|
825
|
-
|
|
826
|
-
|
|
797
|
+
return ((audience === undefined ||
|
|
798
|
+
(Array.isArray(audience) &&
|
|
799
|
+
audience.every((item) => item === "user" || item === "assistant"))) &&
|
|
800
|
+
(priority === undefined || typeof priority === "number") &&
|
|
801
|
+
(lastModified === undefined || typeof lastModified === "string"));
|
|
827
802
|
}
|
|
828
803
|
function isBase64(value) {
|
|
829
804
|
if (value.length === 0) {
|
|
@@ -848,7 +823,10 @@ function isPromptContentItem(value) {
|
|
|
848
823
|
if (!isContentItem(value)) {
|
|
849
824
|
return false;
|
|
850
825
|
}
|
|
851
|
-
return !(typeof value === "object" &&
|
|
826
|
+
return !(typeof value === "object" &&
|
|
827
|
+
value !== null &&
|
|
828
|
+
hasOwnProperty(value, "type") &&
|
|
829
|
+
value.type === "resource_link");
|
|
852
830
|
}
|
|
853
831
|
function hasOwnProperty(value, name) {
|
|
854
832
|
return Object.prototype.hasOwnProperty.call(value, name);
|
package/dist/types.d.ts
CHANGED
|
@@ -164,10 +164,7 @@ export interface ResourceTemplateDefinition extends ResourceTemplate {
|
|
|
164
164
|
}
|
|
165
165
|
export interface HandleResult {
|
|
166
166
|
result?: unknown;
|
|
167
|
-
error?:
|
|
168
|
-
code: number;
|
|
169
|
-
message: string;
|
|
170
|
-
};
|
|
167
|
+
error?: JSONRPCError;
|
|
171
168
|
}
|
|
172
169
|
export type PromptContentItem = {
|
|
173
170
|
type: "text";
|
|
@@ -211,6 +208,7 @@ export interface JSONSchemaProperty extends Record<string, unknown> {
|
|
|
211
208
|
export interface ServerOptions {
|
|
212
209
|
name: string;
|
|
213
210
|
version: string;
|
|
211
|
+
toolCallTimeoutMs?: number;
|
|
214
212
|
validateToolArguments?: boolean;
|
|
215
213
|
supportNotifications?: boolean;
|
|
216
214
|
supportResourceSubscriptions?: boolean;
|