@wrongstack/mcp 0.283.1 → 0.284.1

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/dist/index.d.ts CHANGED
@@ -102,7 +102,9 @@ declare class MCPClient {
102
102
  private connectStdio;
103
103
  private connectSSE;
104
104
  private connectStreamableHTTP;
105
- callTool(name: string, input: unknown): Promise<ToolCallResult>;
105
+ callTool(name: string, input: unknown, opts?: {
106
+ signal?: AbortSignal | undefined;
107
+ }): Promise<ToolCallResult>;
106
108
  close(): Promise<void>;
107
109
  private request;
108
110
  /**
@@ -633,7 +635,9 @@ declare class SSETransport extends BaseHTTPTransport {
633
635
  private readSSEBody;
634
636
  private buildSSEUrl;
635
637
  private httpPost;
636
- callTool(name: string, input: unknown): Promise<ToolCallResult>;
638
+ callTool(name: string, input: unknown, opts?: {
639
+ signal?: AbortSignal | undefined;
640
+ }): Promise<ToolCallResult>;
637
641
  /** Generic JSON-RPC request — used by MCPClient.request() for SSE transports. */
638
642
  request(method: string, params: unknown, timeoutMs?: number): Promise<JsonRpcResponse>;
639
643
  close(): Promise<void>;
@@ -652,7 +656,9 @@ declare class StreamableHTTPTransport extends BaseHTTPTransport {
652
656
  private postRaw;
653
657
  /** Generic JSON-RPC request — used by MCPClient.request() for SSE/streamable-http transports. */
654
658
  request(method: string, params: unknown, timeoutMs?: number): Promise<JsonRpcResponse>;
655
- callTool(name: string, input: unknown): Promise<ToolCallResult>;
659
+ callTool(name: string, input: unknown, opts?: {
660
+ signal?: AbortSignal | undefined;
661
+ }): Promise<ToolCallResult>;
656
662
  close(): Promise<void>;
657
663
  }
658
664
 
package/dist/index.js CHANGED
@@ -305,6 +305,11 @@ function assertMatchingJsonRpcResult(data, expectedId, method) {
305
305
  }
306
306
  return data;
307
307
  }
308
+ function makeAbortError(method) {
309
+ const err = new Error(`MCP request "${method}" aborted by client`);
310
+ err.name = "AbortError";
311
+ return err;
312
+ }
308
313
  function createTimeoutSignal(parent, timeoutMs) {
309
314
  const ctrl = new AbortController();
310
315
  const onAbort = () => ctrl.abort(parent?.reason);
@@ -543,10 +548,12 @@ var SSETransport = class extends BaseHTTPTransport {
543
548
  return this.url;
544
549
  }
545
550
  }
546
- async httpPost(method, params) {
551
+ async httpPost(method, params, opts) {
547
552
  const id = this.genId();
548
553
  const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
549
- const timeoutSignal = createTimeoutSignal(this.abortController?.signal, this.requestTimeout);
554
+ const external = opts?.signal;
555
+ const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
556
+ const timeoutSignal = createTimeoutSignal(parent, this.requestTimeout);
550
557
  const fetchOpts = {
551
558
  method: "POST",
552
559
  headers: {
@@ -557,8 +564,8 @@ var SSETransport = class extends BaseHTTPTransport {
557
564
  signal: timeoutSignal.signal
558
565
  };
559
566
  this.applyTlsAgent(fetchOpts);
560
- const res = await fetch(this.url, fetchOpts);
561
567
  try {
568
+ const res = await fetch(this.url, fetchOpts);
562
569
  if (!res.ok) {
563
570
  const body2 = await res.text();
564
571
  const cap = MCP_CONSTANTS.REQUEST_LOG_CAP;
@@ -583,11 +590,21 @@ var SSETransport = class extends BaseHTTPTransport {
583
590
  });
584
591
  }
585
592
  return assertMatchingJsonRpcResult(data, id, method);
593
+ } catch (err) {
594
+ if (external?.aborted && !method.startsWith("notifications/")) {
595
+ void this.httpPost("notifications/cancelled", {
596
+ requestId: id,
597
+ reason: "client aborted"
598
+ }).catch(() => {
599
+ });
600
+ throw makeAbortError(method);
601
+ }
602
+ throw err;
586
603
  } finally {
587
604
  timeoutSignal.dispose();
588
605
  }
589
606
  }
590
- async callTool(name, input) {
607
+ async callTool(name, input, opts) {
591
608
  if (this.state !== "connected") {
592
609
  throw new ToolError({
593
610
  message: `SSE transport not connected (state=${this.state})`,
@@ -596,7 +613,7 @@ var SSETransport = class extends BaseHTTPTransport {
596
613
  context: { transport: "sse", state: this.state }
597
614
  });
598
615
  }
599
- const res = await this.httpPost("tools/call", { name, arguments: input });
616
+ const res = await this.httpPost("tools/call", { name, arguments: input }, opts);
600
617
  if (res.error) {
601
618
  return { content: res.error.message, isError: true };
602
619
  }
@@ -746,10 +763,12 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
746
763
  throw err;
747
764
  }
748
765
  }
749
- async postRaw(method, params) {
766
+ async postRaw(method, params, opts) {
750
767
  const id = this.genId();
751
768
  const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
752
- const timeoutSignal = createTimeoutSignal(this.abortController?.signal, this.requestTimeout);
769
+ const external = opts?.signal;
770
+ const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
771
+ const timeoutSignal = createTimeoutSignal(parent, this.requestTimeout);
753
772
  const fetchOpts = {
754
773
  method: "POST",
755
774
  headers: {
@@ -762,8 +781,8 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
762
781
  signal: timeoutSignal.signal
763
782
  };
764
783
  this.applyTlsAgent(fetchOpts);
765
- const res = await fetch(this.url, fetchOpts);
766
784
  try {
785
+ const res = await fetch(this.url, fetchOpts);
767
786
  if (!res.ok) {
768
787
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
769
788
  }
@@ -776,6 +795,16 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
776
795
  return assertMatchingJsonRpcResult(match, id, method);
777
796
  }
778
797
  throw new Error("Could not parse response as JSON-RPC");
798
+ } catch (err) {
799
+ if (external?.aborted && !method.startsWith("notifications/")) {
800
+ void this.postRaw("notifications/cancelled", {
801
+ requestId: id,
802
+ reason: "client aborted"
803
+ }).catch(() => {
804
+ });
805
+ throw makeAbortError(method);
806
+ }
807
+ throw err;
779
808
  } finally {
780
809
  timeoutSignal.dispose();
781
810
  }
@@ -823,11 +852,11 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
823
852
  timeoutSignal.dispose();
824
853
  }
825
854
  }
826
- async callTool(name, input) {
855
+ async callTool(name, input, opts) {
827
856
  if (this.state !== "connected") {
828
857
  throw new Error(`streamable-http transport not connected (state=${this.state})`);
829
858
  }
830
- const res = await this.postRaw("tools/call", { name, arguments: input });
859
+ const res = await this.postRaw("tools/call", { name, arguments: input }, opts);
831
860
  if (res.error) {
832
861
  return { content: res.error.message, isError: true };
833
862
  }
@@ -1087,17 +1116,17 @@ var MCPClient = class {
1087
1116
  this._toolsCache = this._tools;
1088
1117
  this.state = "connected";
1089
1118
  }
1090
- async callTool(name, input) {
1119
+ async callTool(name, input, opts) {
1091
1120
  if (this.state !== "connected") {
1092
1121
  throw new Error(`MCP client "${this.opts.name}" not connected (state=${this.state})`);
1093
1122
  }
1094
1123
  if (this.sseTransport) {
1095
- return this.sseTransport.callTool(name, input);
1124
+ return this.sseTransport.callTool(name, input, opts);
1096
1125
  }
1097
1126
  if (this.httpTransport) {
1098
- return this.httpTransport.callTool(name, input);
1127
+ return this.httpTransport.callTool(name, input, opts);
1099
1128
  }
1100
- const res = await this.request("tools/call", { name, arguments: input });
1129
+ const res = await this.request("tools/call", { name, arguments: input }, void 0, opts);
1101
1130
  if (res.error) {
1102
1131
  return { content: res.error.message, isError: true };
1103
1132
  }
@@ -1140,14 +1169,38 @@ var MCPClient = class {
1140
1169
  this.httpTransport?.close();
1141
1170
  this.state = "disconnected";
1142
1171
  }
1143
- request(method, params, timeoutMs = this.opts.requestTimeoutMs ?? 6e4) {
1172
+ request(method, params, timeoutMs = this.opts.requestTimeoutMs ?? 6e4, opts) {
1144
1173
  if (this.sseTransport) return this.sseTransport.request(method, params, timeoutMs);
1145
1174
  if (this.httpTransport) return this.httpTransport.request(method, params, timeoutMs);
1175
+ const signal = opts?.signal;
1176
+ if (signal?.aborted) {
1177
+ const err = new Error(`MCP "${this.opts.name}" request "${method}" aborted before send`);
1178
+ err.name = "AbortError";
1179
+ return Promise.reject(err);
1180
+ }
1146
1181
  const id = this.nextId++;
1147
1182
  const req = { jsonrpc: "2.0", id, method, params };
1148
1183
  return new Promise((resolve, reject) => {
1184
+ const onAbort = signal ? () => {
1185
+ const pending = this.pending.get(id);
1186
+ this.pending.delete(id);
1187
+ if (pending) clearTimeout(pending.timer);
1188
+ void this.notify("notifications/cancelled", {
1189
+ requestId: id,
1190
+ reason: "client aborted"
1191
+ }).catch(() => {
1192
+ });
1193
+ const err = new Error(`MCP "${this.opts.name}" request "${method}" aborted by client`);
1194
+ err.name = "AbortError";
1195
+ reject(err);
1196
+ } : void 0;
1197
+ if (signal && onAbort) signal.addEventListener("abort", onAbort, { once: true });
1198
+ const detach = () => {
1199
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
1200
+ };
1149
1201
  const timer = setTimeout(() => {
1150
1202
  this.pending.delete(id);
1203
+ detach();
1151
1204
  reject(
1152
1205
  new Error(`MCP "${this.opts.name}" request "${method}" timed out after ${timeoutMs}ms`)
1153
1206
  );
@@ -1155,10 +1208,12 @@ var MCPClient = class {
1155
1208
  this.pending.set(id, {
1156
1209
  resolve: (res) => {
1157
1210
  clearTimeout(timer);
1211
+ detach();
1158
1212
  resolve(res);
1159
1213
  },
1160
1214
  reject: (err) => {
1161
1215
  clearTimeout(timer);
1216
+ detach();
1162
1217
  reject(err);
1163
1218
  },
1164
1219
  timer
@@ -1168,6 +1223,7 @@ var MCPClient = class {
1168
1223
  const pending = this.pending.get(id);
1169
1224
  this.pending.delete(id);
1170
1225
  if (pending) clearTimeout(pending.timer);
1226
+ detach();
1171
1227
  reject(new Error(`MCP "${this.opts.name}" request "${method}": stdin not writable`));
1172
1228
  return;
1173
1229
  }
@@ -1177,6 +1233,7 @@ var MCPClient = class {
1177
1233
  const pending = this.pending.get(id);
1178
1234
  this.pending.delete(id);
1179
1235
  if (pending) clearTimeout(pending.timer);
1236
+ detach();
1180
1237
  reject(err);
1181
1238
  }
1182
1239
  });
@@ -1333,9 +1390,9 @@ function wrapMCPTool(serverName, mcpTool, client, permission = "confirm") {
1333
1390
  mutating: isMutatingTool(mcpTool),
1334
1391
  capabilities: [ToolCapabilities.MCP_PROXY],
1335
1392
  inputSchema: mcpTool.inputSchema ?? { type: "object", properties: {} },
1336
- async execute(input, _ctx, _opts) {
1393
+ async execute(input, _ctx, opts) {
1337
1394
  const live = typeof client === "function" ? await client() : client;
1338
- const res = await live.callTool(mcpTool.name, input);
1395
+ const res = await live.callTool(mcpTool.name, input, { signal: opts.signal });
1339
1396
  if (res.isError) {
1340
1397
  throw new Error(stringify(res.content));
1341
1398
  }