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 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 the handler result, returns it as `CallToolResult.structuredContent`, and also includes a JSON text backstop in `content[]` for older clients.
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. Tools whose natural result is prose, images, audio, files, or other content blocks should omit `outputSchema` and keep returning content.
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 sent during MCP initialization.
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, } from "./jsonrpc.js";
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 && !jsonSchemaValidator.validate(tool.inputSchema, toolArgs)) {
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: "Invalid tool arguments",
129
- },
129
+ message: `Invalid tool arguments: ${jsonSchemaValidator.errorsText(errors)}`,
130
+ data: errors
131
+ }
130
132
  };
131
133
  }
132
134
  try {
133
- const handlerResult = await tool.handler(toolArgs);
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 (tool.outputSchema !== undefined && !jsonSchemaValidator.validate(tool.outputSchema, result.structuredContent)) {
136
- throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Invalid structured tool result");
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
- && findReadableResource(uri, resources, resourceTemplates) === undefined) {
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
- assertSupportedOutputSchema(outputSchema);
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
- assertSupportedOutputSchema(definition.outputSchema);
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 && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
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 && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
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 && [...messageLifecycles].some((lifecycle) => lifecycle.notificationReady)) {
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
- && value.structuredContent !== undefined
622
- && !isJsonObject(value.structuredContent)) {
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
- && value.isError !== undefined
627
- && typeof value.isError !== "boolean");
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
- const structuredContent = isCallToolResult(handlerResult)
643
- ? handlerResult.structuredContent
644
- : handlerResult;
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: [{ type: "text", text: JSON.stringify(structuredContent) }],
650
- ...(isCallToolResult(handlerResult) && handlerResult.isError !== undefined
651
- ? { isError: handlerResult.isError }
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") || value.description === undefined || typeof value.description === "string")
746
- && Array.isArray(value.messages)
747
- && value.messages.every((message) => typeof message === "object"
748
- && message !== null
749
- && hasOwnProperty(message, "role")
750
- && (message.role === "user" || message.role === "assistant")
751
- && hasOwnProperty(message, "content")
752
- && isPromptContentItem(message.content));
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" && value !== null && hasOwnProperty(value, "content")
763
- && Array.isArray(value.content);
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
- && typeof block.data === "string"
779
- && isBase64(block.data)
780
- && hasOwnProperty(block, "mimeType")
781
- && typeof block.mimeType === "string";
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
- && typeof block.uri === "string"
786
- && isValidUri(block.uri)
787
- && hasOwnProperty(block, "name")
788
- && typeof block.name === "string"
789
- && (!hasOwnProperty(block, "title") || block.title === undefined || typeof block.title === "string")
790
- && (!hasOwnProperty(block, "description") || block.description === undefined || typeof block.description === "string")
791
- && (!hasOwnProperty(block, "mimeType") || block.mimeType === undefined || typeof block.mimeType === "string")
792
- && (!hasOwnProperty(block, "size") || block.size === undefined || typeof block.size === "number");
793
- }
794
- if (block.type !== "resource"
795
- || !hasOwnProperty(block, "resource")
796
- || typeof block.resource !== "object"
797
- || block.resource === null) {
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
- || value === null
805
- || !hasOwnProperty(value, "uri")
806
- || typeof value.uri !== "string"
807
- || !isValidUri(value.uri)) {
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") && value.mimeType !== undefined && typeof value.mimeType !== "string") {
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
- || (hasOwnProperty(value, "blob") && typeof value.blob === "string" && isBase64(value.blob));
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 || (Array.isArray(audience) && audience.every((item) => item === "user" || item === "assistant")))
825
- && (priority === undefined || typeof priority === "number")
826
- && (lastModified === undefined || typeof lastModified === "string");
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" && value !== null && hasOwnProperty(value, "type") && value.type === "resource_link");
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-stdio-mcp-server",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "bugs": {
5
5
  "url": "https://github.com/poe-platform/poe-code/issues"
6
6
  },