Skip to content

Commit cbd8f49

Browse files
committed
- reverted and disabled prettier
- added toolrefresh option to be able to enable or disable it - added debounceMs
1 parent e91c862 commit cbd8f49

File tree

1 file changed

+132
-38
lines changed

1 file changed

+132
-38
lines changed

src/client/index.ts

Lines changed: 132 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ export type ClientOptions = ProtocolOptions & {
4747
* Capabilities to advertise as being supported by this client.
4848
*/
4949
capabilities?: ClientCapabilities;
50+
/**
51+
* Configure automatic refresh behavior for tool list changes
52+
*/
53+
toolRefreshOptions?: {
54+
/**
55+
* Whether to automatically refresh the tools list when a change notification is received.
56+
* Default: true
57+
*/
58+
autoRefresh?: boolean;
59+
/**
60+
* Debounce time in milliseconds for tool list refresh operations.
61+
* Multiple notifications received within this timeframe will only trigger one refresh.
62+
* Default: 300
63+
*/
64+
debounceMs?: number;
65+
};
5066
};
5167

5268
/**
@@ -77,7 +93,7 @@ export type ClientOptions = ProtocolOptions & {
7793
export class Client<
7894
RequestT extends Request = Request,
7995
NotificationT extends Notification = Notification,
80-
ResultT extends Result = Result
96+
ResultT extends Result = Result,
8197
> extends Protocol<
8298
ClientRequest | RequestT,
8399
ClientNotification | NotificationT,
@@ -87,6 +103,10 @@ export class Client<
87103
private _serverVersion?: Implementation;
88104
private _capabilities: ClientCapabilities;
89105
private _instructions?: string;
106+
private _toolRefreshOptions: Required<
107+
NonNullable<ClientOptions["toolRefreshOptions"]>
108+
>;
109+
private _toolRefreshDebounceTimer?: ReturnType<typeof setTimeout>;
90110

91111
/**
92112
* Callback for when the server indicates that the tools list has changed.
@@ -97,31 +117,61 @@ export class Client<
97117
/**
98118
* Initializes this client with the given name and version information.
99119
*/
100-
constructor(private _clientInfo: Implementation, options?: ClientOptions) {
120+
constructor(
121+
private _clientInfo: Implementation,
122+
options?: ClientOptions
123+
) {
101124
super(options);
102125
this._capabilities = options?.capabilities ?? {};
126+
this._toolRefreshOptions = {
127+
autoRefresh: options?.toolRefreshOptions?.autoRefresh ?? true,
128+
debounceMs: options?.toolRefreshOptions?.debounceMs ?? 500,
129+
};
103130

104131
// Set up notification handlers
105132
this.setNotificationHandler(
106133
"notifications/tools/list_changed",
107134
async () => {
108-
// Automatically refresh the tools list when the server indicates a change
109-
try {
110-
// Only refresh if the server supports tools
111-
if (this._serverCapabilities?.tools) {
112-
const result = await this.listTools();
113-
// Call the user's callback with the updated tools list
114-
this.onToolListChanged?.(result.tools);
115-
}
116-
} catch (error) {
117-
console.error("Failed to refresh tools list:", error);
118-
// Still call the callback even if refresh failed
135+
// Only proceed with refresh if auto-refresh is enabled
136+
if (!this._toolRefreshOptions.autoRefresh) {
137+
// Still call callback to notify about the change, but without tools data
119138
this.onToolListChanged?.(undefined);
139+
return;
140+
}
141+
142+
// Clear any pending refresh timer
143+
if (this._toolRefreshDebounceTimer) {
144+
clearTimeout(this._toolRefreshDebounceTimer);
120145
}
146+
147+
// Set up debounced refresh
148+
this._toolRefreshDebounceTimer = setTimeout(() => {
149+
this._refreshToolsList().catch((error) => {
150+
console.error("Failed to refresh tools list:", error);
151+
});
152+
}, this._toolRefreshOptions.debounceMs);
121153
}
122154
);
123155
}
124156

157+
/**
158+
* Private method to handle tools list refresh
159+
*/
160+
private async _refreshToolsList(): Promise<void> {
161+
try {
162+
// Only refresh if the server supports tools
163+
if (this._serverCapabilities?.tools) {
164+
const result = await this.listTools();
165+
// Call the user's callback with the updated tools list
166+
this.onToolListChanged?.(result.tools);
167+
}
168+
} catch (error) {
169+
console.error("Failed to refresh tools list:", error);
170+
// Still call the callback even if refresh failed
171+
this.onToolListChanged?.(undefined);
172+
}
173+
}
174+
125175
/**
126176
* Registers new capabilities. This can only be called before connecting to a transport.
127177
*
@@ -130,20 +180,64 @@ export class Client<
130180
public registerCapabilities(capabilities: ClientCapabilities): void {
131181
if (this.transport) {
132182
throw new Error(
133-
"Cannot register capabilities after connecting to transport"
183+
"Cannot register capabilities after connecting to transport",
134184
);
135185
}
136186

137187
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
138188
}
139189

190+
/**
191+
* Updates the tool refresh options
192+
*/
193+
public setToolRefreshOptions(
194+
options: ClientOptions["toolRefreshOptions"]
195+
): void {
196+
if (options) {
197+
if (options.autoRefresh !== undefined) {
198+
this._toolRefreshOptions.autoRefresh = options.autoRefresh;
199+
}
200+
if (options.debounceMs !== undefined) {
201+
this._toolRefreshOptions.debounceMs = options.debounceMs;
202+
}
203+
}
204+
}
205+
206+
/**
207+
* Gets the current tool refresh options
208+
*/
209+
public getToolRefreshOptions(): Required<
210+
NonNullable<ClientOptions["toolRefreshOptions"]>
211+
> {
212+
return { ...this._toolRefreshOptions };
213+
}
214+
215+
/**
216+
* Manually triggers a refresh of the tools list
217+
*/
218+
public async refreshToolsList(): Promise<
219+
ListToolsResult["tools"] | undefined
220+
> {
221+
if (!this._serverCapabilities?.tools) {
222+
return undefined;
223+
}
224+
225+
try {
226+
const result = await this.listTools();
227+
return result.tools;
228+
} catch (error) {
229+
console.error("Failed to manually refresh tools list:", error);
230+
return undefined;
231+
}
232+
}
233+
140234
protected assertCapability(
141235
capability: keyof ServerCapabilities,
142236
method: string
143237
): void {
144238
if (!this._serverCapabilities?.[capability]) {
145239
throw new Error(
146-
`Server does not support ${String(capability)} (required for ${method})`
240+
`Server does not support ${String(capability)} (required for ${method})`,
147241
);
148242
}
149243
}
@@ -161,7 +255,7 @@ export class Client<
161255
clientInfo: this._clientInfo,
162256
},
163257
},
164-
InitializeResultSchema
258+
InitializeResultSchema,
165259
);
166260

167261
if (result === undefined) {
@@ -170,7 +264,7 @@ export class Client<
170264

171265
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
172266
throw new Error(
173-
`Server's protocol version is not supported: ${result.protocolVersion}`
267+
`Server's protocol version is not supported: ${result.protocolVersion}`,
174268
);
175269
}
176270

@@ -215,7 +309,7 @@ export class Client<
215309
case "logging/setLevel":
216310
if (!this._serverCapabilities?.logging) {
217311
throw new Error(
218-
`Server does not support logging (required for ${method})`
312+
`Server does not support logging (required for ${method})`,
219313
);
220314
}
221315
break;
@@ -224,7 +318,7 @@ export class Client<
224318
case "prompts/list":
225319
if (!this._serverCapabilities?.prompts) {
226320
throw new Error(
227-
`Server does not support prompts (required for ${method})`
321+
`Server does not support prompts (required for ${method})`,
228322
);
229323
}
230324
break;
@@ -236,7 +330,7 @@ export class Client<
236330
case "resources/unsubscribe":
237331
if (!this._serverCapabilities?.resources) {
238332
throw new Error(
239-
`Server does not support resources (required for ${method})`
333+
`Server does not support resources (required for ${method})`,
240334
);
241335
}
242336

@@ -245,7 +339,7 @@ export class Client<
245339
!this._serverCapabilities.resources.subscribe
246340
) {
247341
throw new Error(
248-
`Server does not support resource subscriptions (required for ${method})`
342+
`Server does not support resource subscriptions (required for ${method})`,
249343
);
250344
}
251345

@@ -255,15 +349,15 @@ export class Client<
255349
case "tools/list":
256350
if (!this._serverCapabilities?.tools) {
257351
throw new Error(
258-
`Server does not support tools (required for ${method})`
352+
`Server does not support tools (required for ${method})`,
259353
);
260354
}
261355
break;
262356

263357
case "completion/complete":
264358
if (!this._serverCapabilities?.prompts) {
265359
throw new Error(
266-
`Server does not support prompts (required for ${method})`
360+
`Server does not support prompts (required for ${method})`,
267361
);
268362
}
269363
break;
@@ -319,15 +413,15 @@ export class Client<
319413
case "sampling/createMessage":
320414
if (!this._capabilities.sampling) {
321415
throw new Error(
322-
`Client does not support sampling capability (required for ${method})`
416+
`Client does not support sampling capability (required for ${method})`,
323417
);
324418
}
325419
break;
326420

327421
case "roots/list":
328422
if (!this._capabilities.roots) {
329423
throw new Error(
330-
`Client does not support roots capability (required for ${method})`
424+
`Client does not support roots capability (required for ${method})`,
331425
);
332426
}
333427
break;
@@ -346,15 +440,15 @@ export class Client<
346440
return this.request(
347441
{ method: "completion/complete", params },
348442
CompleteResultSchema,
349-
options
443+
options,
350444
);
351445
}
352446

353447
async setLoggingLevel(level: LoggingLevel, options?: RequestOptions) {
354448
return this.request(
355449
{ method: "logging/setLevel", params: { level } },
356450
EmptyResultSchema,
357-
options
451+
options,
358452
);
359453
}
360454

@@ -365,7 +459,7 @@ export class Client<
365459
return this.request(
366460
{ method: "prompts/get", params },
367461
GetPromptResultSchema,
368-
options
462+
options,
369463
);
370464
}
371465

@@ -376,7 +470,7 @@ export class Client<
376470
return this.request(
377471
{ method: "prompts/list", params },
378472
ListPromptsResultSchema,
379-
options
473+
options,
380474
);
381475
}
382476

@@ -387,7 +481,7 @@ export class Client<
387481
return this.request(
388482
{ method: "resources/list", params },
389483
ListResourcesResultSchema,
390-
options
484+
options,
391485
);
392486
}
393487

@@ -398,7 +492,7 @@ export class Client<
398492
return this.request(
399493
{ method: "resources/templates/list", params },
400494
ListResourceTemplatesResultSchema,
401-
options
495+
options,
402496
);
403497
}
404498

@@ -409,7 +503,7 @@ export class Client<
409503
return this.request(
410504
{ method: "resources/read", params },
411505
ReadResourceResultSchema,
412-
options
506+
options,
413507
);
414508
}
415509

@@ -420,7 +514,7 @@ export class Client<
420514
return this.request(
421515
{ method: "resources/subscribe", params },
422516
EmptyResultSchema,
423-
options
517+
options,
424518
);
425519
}
426520

@@ -431,7 +525,7 @@ export class Client<
431525
return this.request(
432526
{ method: "resources/unsubscribe", params },
433527
EmptyResultSchema,
434-
options
528+
options,
435529
);
436530
}
437531

@@ -440,23 +534,23 @@ export class Client<
440534
resultSchema:
441535
| typeof CallToolResultSchema
442536
| typeof CompatibilityCallToolResultSchema = CallToolResultSchema,
443-
options?: RequestOptions
537+
options?: RequestOptions,
444538
) {
445539
return this.request(
446540
{ method: "tools/call", params },
447541
resultSchema,
448-
options
542+
options,
449543
);
450544
}
451545

452546
async listTools(
453547
params?: ListToolsRequest["params"],
454-
options?: RequestOptions
548+
options?: RequestOptions,
455549
) {
456550
return this.request(
457551
{ method: "tools/list", params },
458552
ListToolsResultSchema,
459-
options
553+
options,
460554
);
461555
}
462556

0 commit comments

Comments
 (0)