Skip to content

Commit 54d146b

Browse files
committed
Support passing an AbortSignal to request()
1 parent 347e890 commit 54d146b

File tree

3 files changed

+64
-29
lines changed

3 files changed

+64
-29
lines changed

src/client/index.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2-
ProgressCallback,
32
Protocol,
43
ProtocolOptions,
4+
RequestOptions,
55
} from "../shared/protocol.js";
66
import { Transport } from "../shared/transport.js";
77
import {
@@ -278,14 +278,11 @@ export class Client<
278278
return this.request({ method: "ping" }, EmptyResultSchema);
279279
}
280280

281-
async complete(
282-
params: CompleteRequest["params"],
283-
onprogress?: ProgressCallback,
284-
) {
281+
async complete(params: CompleteRequest["params"], options?: RequestOptions) {
285282
return this.request(
286283
{ method: "completion/complete", params },
287284
CompleteResultSchema,
288-
onprogress,
285+
options,
289286
);
290287
}
291288

@@ -298,56 +295,56 @@ export class Client<
298295

299296
async getPrompt(
300297
params: GetPromptRequest["params"],
301-
onprogress?: ProgressCallback,
298+
options?: RequestOptions,
302299
) {
303300
return this.request(
304301
{ method: "prompts/get", params },
305302
GetPromptResultSchema,
306-
onprogress,
303+
options,
307304
);
308305
}
309306

310307
async listPrompts(
311308
params?: ListPromptsRequest["params"],
312-
onprogress?: ProgressCallback,
309+
options?: RequestOptions,
313310
) {
314311
return this.request(
315312
{ method: "prompts/list", params },
316313
ListPromptsResultSchema,
317-
onprogress,
314+
options,
318315
);
319316
}
320317

321318
async listResources(
322319
params?: ListResourcesRequest["params"],
323-
onprogress?: ProgressCallback,
320+
options?: RequestOptions,
324321
) {
325322
return this.request(
326323
{ method: "resources/list", params },
327324
ListResourcesResultSchema,
328-
onprogress,
325+
options,
329326
);
330327
}
331328

332329
async listResourceTemplates(
333330
params?: ListResourceTemplatesRequest["params"],
334-
onprogress?: ProgressCallback,
331+
options?: RequestOptions,
335332
) {
336333
return this.request(
337334
{ method: "resources/templates/list", params },
338335
ListResourceTemplatesResultSchema,
339-
onprogress,
336+
options,
340337
);
341338
}
342339

343340
async readResource(
344341
params: ReadResourceRequest["params"],
345-
onprogress?: ProgressCallback,
342+
options?: RequestOptions,
346343
) {
347344
return this.request(
348345
{ method: "resources/read", params },
349346
ReadResourceResultSchema,
350-
onprogress,
347+
options,
351348
);
352349
}
353350

@@ -370,23 +367,23 @@ export class Client<
370367
resultSchema:
371368
| typeof CallToolResultSchema
372369
| typeof CompatibilityCallToolResultSchema = CallToolResultSchema,
373-
onprogress?: ProgressCallback,
370+
options?: RequestOptions,
374371
) {
375372
return this.request(
376373
{ method: "tools/call", params },
377374
resultSchema,
378-
onprogress,
375+
options,
379376
);
380377
}
381378

382379
async listTools(
383380
params?: ListToolsRequest["params"],
384-
onprogress?: ProgressCallback,
381+
options?: RequestOptions,
385382
) {
386383
return this.request(
387384
{ method: "tools/list", params },
388385
ListToolsResultSchema,
389-
onprogress,
386+
options,
390387
);
391388
}
392389

src/server/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2-
ProgressCallback,
32
Protocol,
43
ProtocolOptions,
4+
RequestOptions,
55
} from "../shared/protocol.js";
66
import {
77
ClientCapabilities,
@@ -257,23 +257,23 @@ export class Server<
257257

258258
async createMessage(
259259
params: CreateMessageRequest["params"],
260-
onprogress?: ProgressCallback,
260+
options?: RequestOptions,
261261
) {
262262
return this.request(
263263
{ method: "sampling/createMessage", params },
264264
CreateMessageResultSchema,
265-
onprogress,
265+
options,
266266
);
267267
}
268268

269269
async listRoots(
270270
params?: ListRootsRequest["params"],
271-
onprogress?: ProgressCallback,
271+
options?: RequestOptions,
272272
) {
273273
return this.request(
274274
{ method: "roots/list", params },
275275
ListRootsResultSchema,
276-
onprogress,
276+
options,
277277
);
278278
}
279279

src/shared/protocol.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ export type ProtocolOptions = {
3535
enforceStrictCapabilities?: boolean;
3636
};
3737

38+
/**
39+
* Options that can be given per request.
40+
*/
41+
export type RequestOptions = {
42+
/**
43+
* If set, requests progress notifications from the remote end (if supported). When progress notifications are received, this callback will be invoked.
44+
*/
45+
onprogress?: ProgressCallback;
46+
47+
/**
48+
* Can be used to cancel an in-flight request. This will cause an AbortError to be raised from request().
49+
*/
50+
signal?: AbortSignal;
51+
};
52+
3853
/**
3954
* Implements MCP protocol framing on top of a pluggable transport, including
4055
* features like request/response linking, notifications, and progress.
@@ -285,14 +300,14 @@ export abstract class Protocol<
285300
protected abstract assertRequestHandlerCapability(method: string): void;
286301

287302
/**
288-
* Sends a request and wait for a response, with optional progress notifications in the meantime (if supported by the server).
303+
* Sends a request and wait for a response.
289304
*
290305
* Do not use this method to emit notifications! Use notification() instead.
291306
*/
292307
request<T extends ZodType<object>>(
293308
request: SendRequestT,
294309
resultSchema: T,
295-
onprogress?: ProgressCallback,
310+
options?: RequestOptions,
296311
): Promise<z.infer<T>> {
297312
return new Promise((resolve, reject) => {
298313
if (!this._transport) {
@@ -304,22 +319,28 @@ export abstract class Protocol<
304319
this.assertCapabilityForMethod(request.method);
305320
}
306321

322+
options?.signal?.throwIfAborted();
323+
307324
const messageId = this._requestMessageId++;
308325
const jsonrpcRequest: JSONRPCRequest = {
309326
...request,
310327
jsonrpc: "2.0",
311328
id: messageId,
312329
};
313330

314-
if (onprogress) {
315-
this._progressHandlers.set(messageId, onprogress);
331+
if (options?.onprogress) {
332+
this._progressHandlers.set(messageId, options.onprogress);
316333
jsonrpcRequest.params = {
317334
...request.params,
318335
_meta: { progressToken: messageId },
319336
};
320337
}
321338

322339
this._responseHandlers.set(messageId, (response) => {
340+
if (options?.signal?.aborted) {
341+
return;
342+
}
343+
323344
if (response instanceof Error) {
324345
return reject(response);
325346
}
@@ -332,6 +353,23 @@ export abstract class Protocol<
332353
}
333354
});
334355

356+
options?.signal?.addEventListener("abort", () => {
357+
const reason = options?.signal?.reason;
358+
this._responseHandlers.delete(messageId);
359+
this._progressHandlers.delete(messageId);
360+
361+
this._transport?.send({
362+
jsonrpc: "2.0",
363+
method: "cancelled",
364+
params: {
365+
requestId: messageId,
366+
reason: String(reason),
367+
},
368+
});
369+
370+
reject(reason);
371+
});
372+
335373
this._transport.send(jsonrpcRequest).catch(reject);
336374
});
337375
}

0 commit comments

Comments
 (0)