tiny-stdio-mcp-server 0.1.16 → 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
@@ -125,7 +125,7 @@ Dynamically add/remove tools at runtime and notify connected clients.
125
125
 
126
126
  ## Configuration Options
127
127
 
128
- - `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.
129
129
  - `.tool(name, description, schema, handler, outputSchema?)`: tool metadata, input schema, handler, and optional structured output schema.
130
130
  - `.connect({ readable, writable })`: custom stream transport for tests or embedded hosts.
131
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,30 +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
- protocolVersion: requestedProtocol !== undefined &&
51
- SUPPORTED_PROTOCOL_VERSIONS.has(requestedProtocol)
48
+ protocolVersion: requestedProtocol !== undefined && SUPPORTED_PROTOCOL_VERSIONS.has(requestedProtocol)
52
49
  ? requestedProtocol
53
50
  : PROTOCOL_VERSION,
54
51
  capabilities: {
55
52
  tools: {
56
- ...(supportNotifications ? { listChanged: true } : {}),
53
+ ...(supportNotifications ? { listChanged: true } : {})
57
54
  },
58
55
  prompts: {
59
- ...(supportNotifications ? { listChanged: true } : {}),
56
+ ...(supportNotifications ? { listChanged: true } : {})
60
57
  },
61
58
  resources: {
62
59
  ...(supportNotifications ? { listChanged: true } : {}),
63
- ...(supportResourceSubscriptions ? { subscribe: true } : {}),
64
- },
60
+ ...(supportResourceSubscriptions ? { subscribe: true } : {})
61
+ }
65
62
  },
66
63
  serverInfo: {
67
64
  name: options.name,
68
- version: options.version,
69
- },
65
+ version: options.version
66
+ }
70
67
  };
71
68
  return { result };
72
69
  }
@@ -75,8 +72,8 @@ export function createServer(options) {
75
72
  return {
76
73
  error: {
77
74
  code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
78
- message: "Server not initialized",
79
- },
75
+ message: "Server not initialized"
76
+ }
80
77
  };
81
78
  }
82
79
  lifecycle.notificationReady = true;
@@ -87,8 +84,8 @@ export function createServer(options) {
87
84
  return {
88
85
  error: {
89
86
  code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
90
- message: "Server not initialized",
91
- },
87
+ message: "Server not initialized"
88
+ }
92
89
  };
93
90
  }
94
91
  if (method === "tools/list") {
@@ -97,10 +94,9 @@ export function createServer(options) {
97
94
  const descriptor = { ...tool };
98
95
  delete descriptor.handler;
99
96
  delete descriptor.inputValidator;
100
- delete descriptor
101
- .outputValidator;
97
+ delete descriptor.outputValidator;
102
98
  toolList.push({
103
- ...descriptor,
99
+ ...descriptor
104
100
  });
105
101
  }
106
102
  return { result: { tools: toolList } };
@@ -111,8 +107,8 @@ export function createServer(options) {
111
107
  return {
112
108
  error: {
113
109
  code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
114
- message: "Tool name required",
115
- },
110
+ message: "Tool name required"
111
+ }
116
112
  };
117
113
  }
118
114
  const tool = tools.get(toolName);
@@ -120,24 +116,42 @@ export function createServer(options) {
120
116
  return {
121
117
  error: {
122
118
  code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
123
- message: `Tool not found: ${toolName}`,
124
- },
119
+ message: `Tool not found: ${toolName}`
120
+ }
125
121
  };
126
122
  }
127
123
  const toolArgs = (params?.arguments ?? {});
128
- if (options.validateToolArguments !== false &&
129
- !tool.inputValidator(toolArgs)) {
124
+ if (options.validateToolArguments !== false && !tool.inputValidator(toolArgs)) {
130
125
  const errors = tool.inputValidator.errors ?? [];
131
126
  return {
132
127
  error: {
133
128
  code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
134
129
  message: `Invalid tool arguments: ${jsonSchemaValidator.errorsText(errors)}`,
135
- data: errors,
136
- },
130
+ data: errors
131
+ }
137
132
  };
138
133
  }
139
134
  try {
140
- 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
+ }
141
155
  const result = normalizeToolResult(handlerResult, tool.outputSchema);
142
156
  if (result.isError !== true &&
143
157
  tool.outputValidator !== undefined &&
@@ -153,14 +167,14 @@ export function createServer(options) {
153
167
  error: {
154
168
  code: err.code,
155
169
  message: err.message,
156
- ...(err.data === undefined ? {} : { data: err.data }),
157
- },
170
+ ...(err.data === undefined ? {} : { data: err.data })
171
+ }
158
172
  };
159
173
  }
160
174
  const errorMessage = err instanceof Error ? err.message : String(err);
161
175
  const result = {
162
176
  content: [{ type: "text", text: `Error: ${errorMessage}` }],
163
- isError: true,
177
+ isError: true
164
178
  };
165
179
  return { result };
166
180
  }
@@ -168,8 +182,8 @@ export function createServer(options) {
168
182
  if (method === "prompts/list") {
169
183
  return {
170
184
  result: {
171
- prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt),
172
- },
185
+ prompts: [...prompts.values()].map(({ handler: _handler, ...prompt }) => prompt)
186
+ }
173
187
  };
174
188
  }
175
189
  if (method === "prompts/get") {
@@ -199,15 +213,15 @@ export function createServer(options) {
199
213
  if (method === "resources/list") {
200
214
  return {
201
215
  result: {
202
- resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource),
203
- },
216
+ resources: [...resources.values()].map(({ handler: _handler, ...resource }) => resource)
217
+ }
204
218
  };
205
219
  }
206
220
  if (method === "resources/templates/list") {
207
221
  return {
208
222
  result: {
209
- resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate),
210
- },
223
+ resourceTemplates: [...resourceTemplates.values()].map(({ handler: _handler, ...resourceTemplate }) => resourceTemplate)
224
+ }
211
225
  };
212
226
  }
213
227
  if (method === "resources/read") {
@@ -230,14 +244,13 @@ export function createServer(options) {
230
244
  return internalError(toErrorMessage(error));
231
245
  }
232
246
  }
233
- if (method === "resources/subscribe" ||
234
- method === "resources/unsubscribe") {
247
+ if (method === "resources/subscribe" || method === "resources/unsubscribe") {
235
248
  if (!supportResourceSubscriptions) {
236
249
  return {
237
250
  error: {
238
251
  code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
239
- message: "Method not found",
240
- },
252
+ message: "Method not found"
253
+ }
241
254
  };
242
255
  }
243
256
  const uri = typeof params?.uri === "string" ? params.uri : undefined;
@@ -259,8 +272,8 @@ export function createServer(options) {
259
272
  return {
260
273
  error: {
261
274
  code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
262
- message: "Method not found",
263
- },
275
+ message: "Method not found"
276
+ }
264
277
  };
265
278
  };
266
279
  const createMessageSession = (listener) => {
@@ -268,7 +281,7 @@ export function createServer(options) {
268
281
  initialized: false,
269
282
  initializeAccepted: false,
270
283
  notificationReady: false,
271
- resourceSubscriptions: new Set(),
284
+ resourceSubscriptions: new Set()
272
285
  };
273
286
  messageLifecycles.add(lifecycle);
274
287
  if (listener !== undefined) {
@@ -281,7 +294,7 @@ export function createServer(options) {
281
294
  connectionNotificationListeners.delete(listener);
282
295
  }
283
296
  messageLifecycles.delete(lifecycle);
284
- },
297
+ }
285
298
  };
286
299
  };
287
300
  const handleMessage = (method, params) => handleMessageWithLifecycle(method, defaultLifecycle, params);
@@ -299,7 +312,7 @@ export function createServer(options) {
299
312
  const requestWithId = request;
300
313
  write(formatErrorResponse(requestWithId.id, {
301
314
  code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
302
- message: "Invalid Request",
315
+ message: "Invalid Request"
303
316
  }) + "\n");
304
317
  return;
305
318
  }
@@ -312,7 +325,7 @@ export function createServer(options) {
312
325
  const requestWithId = request;
313
326
  write(formatErrorResponse(requestWithId.id, {
314
327
  code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
315
- message: "Internal error",
328
+ message: "Internal error"
316
329
  }) + "\n");
317
330
  }
318
331
  return;
@@ -333,7 +346,7 @@ export function createServer(options) {
333
346
  const notification = {
334
347
  jsonrpc: "2.0",
335
348
  method,
336
- ...(params === undefined ? {} : { params }),
349
+ ...(params === undefined ? {} : { params })
337
350
  };
338
351
  for (const listener of notificationListeners) {
339
352
  listener(notification);
@@ -357,12 +370,10 @@ export function createServer(options) {
357
370
  name,
358
371
  description,
359
372
  inputSchema: inputSchema,
360
- ...(outputSchema === undefined
361
- ? {}
362
- : { outputSchema: outputSchema }),
373
+ ...(outputSchema === undefined ? {} : { outputSchema: outputSchema }),
363
374
  handler: handler,
364
375
  inputValidator,
365
- ...(outputValidator === undefined ? {} : { outputValidator }),
376
+ ...(outputValidator === undefined ? {} : { outputValidator })
366
377
  });
367
378
  return server;
368
379
  },
@@ -378,7 +389,7 @@ export function createServer(options) {
378
389
  ...definition,
379
390
  handler: handler,
380
391
  inputValidator,
381
- ...(outputValidator === undefined ? {} : { outputValidator }),
392
+ ...(outputValidator === undefined ? {} : { outputValidator })
382
393
  });
383
394
  return server;
384
395
  },
@@ -447,7 +458,7 @@ export function createServer(options) {
447
458
  async listen() {
448
459
  return server.connect({
449
460
  readable: process.stdin,
450
- writable: process.stdout,
461
+ writable: process.stdout
451
462
  });
452
463
  },
453
464
  async connect(transport) {
@@ -458,7 +469,7 @@ export function createServer(options) {
458
469
  const session = server.createMessageSession(listener);
459
470
  const rl = readline.createInterface({
460
471
  input: transport.readable,
461
- crlfDelay: Infinity,
472
+ crlfDelay: Infinity
462
473
  });
463
474
  const pendingMessages = new Set();
464
475
  rl.on("line", (line) => {
@@ -503,8 +514,8 @@ export function createServer(options) {
503
514
  id: message.id,
504
515
  error: {
505
516
  code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
506
- message: "Invalid Request",
507
- },
517
+ message: "Invalid Request"
518
+ }
508
519
  });
509
520
  return;
510
521
  }
@@ -519,8 +530,8 @@ export function createServer(options) {
519
530
  id: request.id,
520
531
  error: {
521
532
  code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
522
- message: "Internal error",
523
- },
533
+ message: "Internal error"
534
+ }
524
535
  });
525
536
  return;
526
537
  }
@@ -529,7 +540,7 @@ export function createServer(options) {
529
540
  const response = {
530
541
  jsonrpc: "2.0",
531
542
  id: request.id,
532
- error,
543
+ error
533
544
  };
534
545
  await transport.send(response);
535
546
  }
@@ -537,7 +548,7 @@ export function createServer(options) {
537
548
  const response = {
538
549
  jsonrpc: "2.0",
539
550
  id: request.id,
540
- result,
551
+ result
541
552
  };
542
553
  await transport.send(response);
543
554
  }
@@ -551,7 +562,7 @@ export function createServer(options) {
551
562
  reject(error);
552
563
  });
553
564
  });
554
- },
565
+ }
555
566
  };
556
567
  return server;
557
568
  }
@@ -559,24 +570,24 @@ function invalidParams(message) {
559
570
  return {
560
571
  error: {
561
572
  code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
562
- message,
563
- },
573
+ message
574
+ }
564
575
  };
565
576
  }
566
577
  function internalError(message) {
567
578
  return {
568
579
  error: {
569
580
  code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
570
- message,
571
- },
581
+ message
582
+ }
572
583
  };
573
584
  }
574
585
  function resourceNotFound(uri) {
575
586
  return {
576
587
  error: {
577
588
  code: JSON_RPC_ERROR_CODES.RESOURCE_NOT_FOUND,
578
- message: `Resource not found: ${uri}`,
579
- },
589
+ message: `Resource not found: ${uri}`
590
+ }
580
591
  };
581
592
  }
582
593
  function toErrorMessage(error) {
@@ -599,7 +610,7 @@ function assertNonEmptyName(name, message) {
599
610
  function assertReadableUriTemplate(uriTemplate) {
600
611
  const parsed = uriTemplateParser.parse(uriTemplate);
601
612
  const expanded = parsed.expand(new Proxy({}, {
602
- get: (_target, property) => typeof property === "string" ? "value" : undefined,
613
+ get: (_target, property) => (typeof property === "string" ? "value" : undefined)
603
614
  }));
604
615
  if (typeof expanded !== "string" || !isValidUri(expanded)) {
605
616
  throw new Error(`Invalid resource URI template: ${uriTemplate}`);
@@ -668,12 +679,8 @@ function normalizeToolResult(handlerResult, outputSchema) {
668
679
  if (isCallToolResult(handlerResult) && handlerResult.isError === true) {
669
680
  return handlerResult;
670
681
  }
671
- const callToolResult = isCallToolResult(handlerResult)
672
- ? handlerResult
673
- : undefined;
674
- const structuredContent = callToolResult
675
- ? callToolResult.structuredContent
676
- : handlerResult;
682
+ const callToolResult = isCallToolResult(handlerResult) ? handlerResult : undefined;
683
+ const structuredContent = callToolResult ? callToolResult.structuredContent : handlerResult;
677
684
  if (!isJsonObject(structuredContent)) {
678
685
  throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, "Structured tool result must be an object");
679
686
  }
@@ -681,10 +688,8 @@ function normalizeToolResult(handlerResult, outputSchema) {
681
688
  content: callToolResult !== undefined && callToolResult.content.length > 0
682
689
  ? callToolResult.content
683
690
  : [{ type: "text", text: JSON.stringify(structuredContent) }],
684
- ...(callToolResult?.isError !== undefined
685
- ? { isError: callToolResult.isError }
686
- : {}),
687
- structuredContent,
691
+ ...(callToolResult?.isError !== undefined ? { isError: callToolResult.isError } : {}),
692
+ structuredContent
688
693
  };
689
694
  }
690
695
  function assertObjectRootSchema(schema, path) {
@@ -696,9 +701,7 @@ function isJsonObject(value) {
696
701
  return typeof value === "object" && value !== null && !Array.isArray(value);
697
702
  }
698
703
  function isGetPromptResult(value) {
699
- if (typeof value !== "object" ||
700
- value === null ||
701
- !hasOwnProperty(value, "messages")) {
704
+ if (typeof value !== "object" || value === null || !hasOwnProperty(value, "messages")) {
702
705
  return false;
703
706
  }
704
707
  return ((!hasOwnProperty(value, "description") ||
@@ -713,12 +716,10 @@ function isGetPromptResult(value) {
713
716
  isPromptContentItem(message.content)));
714
717
  }
715
718
  function isReadResourceResult(value) {
716
- if (typeof value !== "object" ||
717
- value === null ||
718
- !hasOwnProperty(value, "contents")) {
719
+ if (typeof value !== "object" || value === null || !hasOwnProperty(value, "contents")) {
719
720
  return false;
720
721
  }
721
- return (Array.isArray(value.contents) && value.contents.every(isResourceContents));
722
+ return Array.isArray(value.contents) && value.contents.every(isResourceContents);
722
723
  }
723
724
  function hasContentArray(value) {
724
725
  return (typeof value === "object" &&
@@ -727,9 +728,7 @@ function hasContentArray(value) {
727
728
  Array.isArray(value.content));
728
729
  }
729
730
  function isContentItem(value) {
730
- if (typeof value !== "object" ||
731
- value === null ||
732
- !hasOwnProperty(value, "type")) {
731
+ if (typeof value !== "object" || value === null || !hasOwnProperty(value, "type")) {
733
732
  return false;
734
733
  }
735
734
  const block = value;
@@ -761,9 +760,7 @@ function isContentItem(value) {
761
760
  (!hasOwnProperty(block, "mimeType") ||
762
761
  block.mimeType === undefined ||
763
762
  typeof block.mimeType === "string") &&
764
- (!hasOwnProperty(block, "size") ||
765
- block.size === undefined ||
766
- typeof block.size === "number"));
763
+ (!hasOwnProperty(block, "size") || block.size === undefined || typeof block.size === "number"));
767
764
  }
768
765
  if (block.type !== "resource" ||
769
766
  !hasOwnProperty(block, "resource") ||
@@ -787,13 +784,10 @@ function isResourceContents(value) {
787
784
  return false;
788
785
  }
789
786
  return ((hasOwnProperty(value, "text") && typeof value.text === "string") ||
790
- (hasOwnProperty(value, "blob") &&
791
- typeof value.blob === "string" &&
792
- isBase64(value.blob)));
787
+ (hasOwnProperty(value, "blob") && typeof value.blob === "string" && isBase64(value.blob)));
793
788
  }
794
789
  function hasValidContentAnnotations(value) {
795
- if (!hasOwnProperty(value, "annotations") ||
796
- value.annotations === undefined) {
790
+ if (!hasOwnProperty(value, "annotations") || value.annotations === undefined) {
797
791
  return true;
798
792
  }
799
793
  if (!isJsonObject(value.annotations)) {
@@ -817,8 +811,7 @@ function isBase64(value) {
817
811
  const paddingStart = value.indexOf("=");
818
812
  const encoded = paddingStart === -1 ? value : value.slice(0, paddingStart);
819
813
  const padding = paddingStart === -1 ? "" : value.slice(paddingStart);
820
- if (padding.length > 2 ||
821
- [...padding].some((character) => character !== "=")) {
814
+ if (padding.length > 2 || [...padding].some((character) => character !== "=")) {
822
815
  return false;
823
816
  }
824
817
  if ([...encoded].some((character) => !alphabet.includes(character))) {
package/dist/types.d.ts CHANGED
@@ -208,6 +208,7 @@ export interface JSONSchemaProperty extends Record<string, unknown> {
208
208
  export interface ServerOptions {
209
209
  name: string;
210
210
  version: string;
211
+ toolCallTimeoutMs?: number;
211
212
  validateToolArguments?: boolean;
212
213
  supportNotifications?: boolean;
213
214
  supportResourceSubscriptions?: boolean;
package/dist/types.js CHANGED
@@ -5,7 +5,7 @@ export const JSON_RPC_ERROR_CODES = Object.freeze({
5
5
  METHOD_NOT_FOUND: -32601,
6
6
  INVALID_PARAMS: -32602,
7
7
  INTERNAL_ERROR: -32603,
8
- RESOURCE_NOT_FOUND: -32002,
8
+ RESOURCE_NOT_FOUND: -32002
9
9
  });
10
10
  export class ToolError extends Error {
11
11
  code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-stdio-mcp-server",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "bugs": {
5
5
  "url": "https://github.com/poe-platform/poe-code/issues"
6
6
  },