From e91c862550b70e70fed3eeb6b5dd707636396a51 Mon Sep 17 00:00:00 2001 From: John Jung Date: Fri, 28 Mar 2025 19:28:39 -0400 Subject: [PATCH 1/6] Added auto-refreshing tool list notification handler to client Added handler for server's notifications/tools/list_changed notification Implemented auto-refresh for tools list when notification is received Added onToolListChanged callback property to provide updated tools to clients Added setToolListChangedCallback convenience method for callback registration Added capability validation to ensure tools support before refreshing Fixed type definitions for proper TypeScript compliance Added error handling for failed refreshes --- src/client/index.ts | 130 ++++++++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 40 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index bcad952c..00355cab 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -28,6 +28,7 @@ import { ListResourceTemplatesRequest, ListResourceTemplatesResultSchema, ListToolsRequest, + ListToolsResult, ListToolsResultSchema, LoggingLevel, Notification, @@ -76,7 +77,7 @@ export type ClientOptions = ProtocolOptions & { export class Client< RequestT extends Request = Request, NotificationT extends Notification = Notification, - ResultT extends Result = Result, + ResultT extends Result = Result > extends Protocol< ClientRequest | RequestT, ClientNotification | NotificationT, @@ -87,15 +88,38 @@ export class Client< private _capabilities: ClientCapabilities; private _instructions?: string; + /** + * Callback for when the server indicates that the tools list has changed. + * Client should typically refresh its list of tools in response. + */ + onToolListChanged?: (tools?: ListToolsResult["tools"]) => void; + /** * Initializes this client with the given name and version information. */ - constructor( - private _clientInfo: Implementation, - options?: ClientOptions, - ) { + constructor(private _clientInfo: Implementation, options?: ClientOptions) { super(options); this._capabilities = options?.capabilities ?? {}; + + // Set up notification handlers + this.setNotificationHandler( + "notifications/tools/list_changed", + async () => { + // Automatically refresh the tools list when the server indicates a change + try { + // Only refresh if the server supports tools + if (this._serverCapabilities?.tools) { + const result = await this.listTools(); + // Call the user's callback with the updated tools list + this.onToolListChanged?.(result.tools); + } + } catch (error) { + console.error("Failed to refresh tools list:", error); + // Still call the callback even if refresh failed + this.onToolListChanged?.(undefined); + } + } + ); } /** @@ -106,7 +130,7 @@ export class Client< public registerCapabilities(capabilities: ClientCapabilities): void { if (this.transport) { throw new Error( - "Cannot register capabilities after connecting to transport", + "Cannot register capabilities after connecting to transport" ); } @@ -115,11 +139,11 @@ export class Client< protected assertCapability( capability: keyof ServerCapabilities, - method: string, + method: string ): void { if (!this._serverCapabilities?.[capability]) { throw new Error( - `Server does not support ${capability} (required for ${method})`, + `Server does not support ${String(capability)} (required for ${method})` ); } } @@ -137,7 +161,7 @@ export class Client< clientInfo: this._clientInfo, }, }, - InitializeResultSchema, + InitializeResultSchema ); if (result === undefined) { @@ -146,7 +170,7 @@ export class Client< if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { throw new Error( - `Server's protocol version is not supported: ${result.protocolVersion}`, + `Server's protocol version is not supported: ${result.protocolVersion}` ); } @@ -191,7 +215,7 @@ export class Client< case "logging/setLevel": if (!this._serverCapabilities?.logging) { throw new Error( - `Server does not support logging (required for ${method})`, + `Server does not support logging (required for ${method})` ); } break; @@ -200,7 +224,7 @@ export class Client< case "prompts/list": if (!this._serverCapabilities?.prompts) { throw new Error( - `Server does not support prompts (required for ${method})`, + `Server does not support prompts (required for ${method})` ); } break; @@ -212,7 +236,7 @@ export class Client< case "resources/unsubscribe": if (!this._serverCapabilities?.resources) { throw new Error( - `Server does not support resources (required for ${method})`, + `Server does not support resources (required for ${method})` ); } @@ -221,7 +245,7 @@ export class Client< !this._serverCapabilities.resources.subscribe ) { throw new Error( - `Server does not support resource subscriptions (required for ${method})`, + `Server does not support resource subscriptions (required for ${method})` ); } @@ -231,7 +255,7 @@ export class Client< case "tools/list": if (!this._serverCapabilities?.tools) { throw new Error( - `Server does not support tools (required for ${method})`, + `Server does not support tools (required for ${method})` ); } break; @@ -239,7 +263,7 @@ export class Client< case "completion/complete": if (!this._serverCapabilities?.prompts) { throw new Error( - `Server does not support prompts (required for ${method})`, + `Server does not support prompts (required for ${method})` ); } break; @@ -255,13 +279,23 @@ export class Client< } protected assertNotificationCapability( - method: NotificationT["method"], + method: NotificationT["method"] ): void { switch (method as ClientNotification["method"]) { case "notifications/roots/list_changed": if (!this._capabilities.roots?.listChanged) { throw new Error( - `Client does not support roots list changed notifications (required for ${method})`, + `Client does not support roots list changed notifications (required for ${method})` + ); + } + break; + + case "notifications/tools/list_changed": + if (!this._capabilities.tools?.listChanged) { + throw new Error( + `Client does not support tools capability (required for ${String( + method + )})` ); } break; @@ -285,7 +319,7 @@ export class Client< case "sampling/createMessage": if (!this._capabilities.sampling) { throw new Error( - `Client does not support sampling capability (required for ${method})`, + `Client does not support sampling capability (required for ${method})` ); } break; @@ -293,7 +327,7 @@ export class Client< case "roots/list": if (!this._capabilities.roots) { throw new Error( - `Client does not support roots capability (required for ${method})`, + `Client does not support roots capability (required for ${method})` ); } break; @@ -312,7 +346,7 @@ export class Client< return this.request( { method: "completion/complete", params }, CompleteResultSchema, - options, + options ); } @@ -320,84 +354,84 @@ export class Client< return this.request( { method: "logging/setLevel", params: { level } }, EmptyResultSchema, - options, + options ); } async getPrompt( params: GetPromptRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "prompts/get", params }, GetPromptResultSchema, - options, + options ); } async listPrompts( params?: ListPromptsRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "prompts/list", params }, ListPromptsResultSchema, - options, + options ); } async listResources( params?: ListResourcesRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "resources/list", params }, ListResourcesResultSchema, - options, + options ); } async listResourceTemplates( params?: ListResourceTemplatesRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "resources/templates/list", params }, ListResourceTemplatesResultSchema, - options, + options ); } async readResource( params: ReadResourceRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "resources/read", params }, ReadResourceResultSchema, - options, + options ); } async subscribeResource( params: SubscribeRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "resources/subscribe", params }, EmptyResultSchema, - options, + options ); } async unsubscribeResource( params: UnsubscribeRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "resources/unsubscribe", params }, EmptyResultSchema, - options, + options ); } @@ -406,27 +440,43 @@ export class Client< resultSchema: | typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema = CallToolResultSchema, - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "tools/call", params }, resultSchema, - options, + options ); } async listTools( params?: ListToolsRequest["params"], - options?: RequestOptions, + options?: RequestOptions ) { return this.request( { method: "tools/list", params }, ListToolsResultSchema, - options, + options ); } + /** + * Registers a callback to be called when the server indicates that + * the tools list has changed. The callback should typically refresh the tools list. + * + * @param callback Function to call when tools list changes + */ + setToolListChangedCallback( + callback: (tools?: ListToolsResult["tools"]) => void + ): void { + this.onToolListChanged = callback; + } + async sendRootsListChanged() { return this.notification({ method: "notifications/roots/list_changed" }); } + + async sendToolListChanged() { + return this.notification({ method: "notifications/tools/list_changed" }); + } } From cbd8f49e2848332e3b9148bb0a9d33d470b7b206 Mon Sep 17 00:00:00 2001 From: John Jung Date: Wed, 9 Apr 2025 17:08:01 -0400 Subject: [PATCH 2/6] - reverted and disabled prettier - added toolrefresh option to be able to enable or disable it - added debounceMs --- src/client/index.ts | 170 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 132 insertions(+), 38 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index 00355cab..36fe854d 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -47,6 +47,22 @@ export type ClientOptions = ProtocolOptions & { * Capabilities to advertise as being supported by this client. */ capabilities?: ClientCapabilities; + /** + * Configure automatic refresh behavior for tool list changes + */ + toolRefreshOptions?: { + /** + * Whether to automatically refresh the tools list when a change notification is received. + * Default: true + */ + autoRefresh?: boolean; + /** + * Debounce time in milliseconds for tool list refresh operations. + * Multiple notifications received within this timeframe will only trigger one refresh. + * Default: 300 + */ + debounceMs?: number; + }; }; /** @@ -77,7 +93,7 @@ export type ClientOptions = ProtocolOptions & { export class Client< RequestT extends Request = Request, NotificationT extends Notification = Notification, - ResultT extends Result = Result + ResultT extends Result = Result, > extends Protocol< ClientRequest | RequestT, ClientNotification | NotificationT, @@ -87,6 +103,10 @@ export class Client< private _serverVersion?: Implementation; private _capabilities: ClientCapabilities; private _instructions?: string; + private _toolRefreshOptions: Required< + NonNullable + >; + private _toolRefreshDebounceTimer?: ReturnType; /** * Callback for when the server indicates that the tools list has changed. @@ -97,31 +117,61 @@ export class Client< /** * Initializes this client with the given name and version information. */ - constructor(private _clientInfo: Implementation, options?: ClientOptions) { + constructor( + private _clientInfo: Implementation, + options?: ClientOptions + ) { super(options); this._capabilities = options?.capabilities ?? {}; + this._toolRefreshOptions = { + autoRefresh: options?.toolRefreshOptions?.autoRefresh ?? true, + debounceMs: options?.toolRefreshOptions?.debounceMs ?? 500, + }; // Set up notification handlers this.setNotificationHandler( "notifications/tools/list_changed", async () => { - // Automatically refresh the tools list when the server indicates a change - try { - // Only refresh if the server supports tools - if (this._serverCapabilities?.tools) { - const result = await this.listTools(); - // Call the user's callback with the updated tools list - this.onToolListChanged?.(result.tools); - } - } catch (error) { - console.error("Failed to refresh tools list:", error); - // Still call the callback even if refresh failed + // Only proceed with refresh if auto-refresh is enabled + if (!this._toolRefreshOptions.autoRefresh) { + // Still call callback to notify about the change, but without tools data this.onToolListChanged?.(undefined); + return; + } + + // Clear any pending refresh timer + if (this._toolRefreshDebounceTimer) { + clearTimeout(this._toolRefreshDebounceTimer); } + + // Set up debounced refresh + this._toolRefreshDebounceTimer = setTimeout(() => { + this._refreshToolsList().catch((error) => { + console.error("Failed to refresh tools list:", error); + }); + }, this._toolRefreshOptions.debounceMs); } ); } + /** + * Private method to handle tools list refresh + */ + private async _refreshToolsList(): Promise { + try { + // Only refresh if the server supports tools + if (this._serverCapabilities?.tools) { + const result = await this.listTools(); + // Call the user's callback with the updated tools list + this.onToolListChanged?.(result.tools); + } + } catch (error) { + console.error("Failed to refresh tools list:", error); + // Still call the callback even if refresh failed + this.onToolListChanged?.(undefined); + } + } + /** * Registers new capabilities. This can only be called before connecting to a transport. * @@ -130,20 +180,64 @@ export class Client< public registerCapabilities(capabilities: ClientCapabilities): void { if (this.transport) { throw new Error( - "Cannot register capabilities after connecting to transport" + "Cannot register capabilities after connecting to transport", ); } this._capabilities = mergeCapabilities(this._capabilities, capabilities); } + /** + * Updates the tool refresh options + */ + public setToolRefreshOptions( + options: ClientOptions["toolRefreshOptions"] + ): void { + if (options) { + if (options.autoRefresh !== undefined) { + this._toolRefreshOptions.autoRefresh = options.autoRefresh; + } + if (options.debounceMs !== undefined) { + this._toolRefreshOptions.debounceMs = options.debounceMs; + } + } + } + + /** + * Gets the current tool refresh options + */ + public getToolRefreshOptions(): Required< + NonNullable + > { + return { ...this._toolRefreshOptions }; + } + + /** + * Manually triggers a refresh of the tools list + */ + public async refreshToolsList(): Promise< + ListToolsResult["tools"] | undefined + > { + if (!this._serverCapabilities?.tools) { + return undefined; + } + + try { + const result = await this.listTools(); + return result.tools; + } catch (error) { + console.error("Failed to manually refresh tools list:", error); + return undefined; + } + } + protected assertCapability( capability: keyof ServerCapabilities, method: string ): void { if (!this._serverCapabilities?.[capability]) { throw new Error( - `Server does not support ${String(capability)} (required for ${method})` + `Server does not support ${String(capability)} (required for ${method})`, ); } } @@ -161,7 +255,7 @@ export class Client< clientInfo: this._clientInfo, }, }, - InitializeResultSchema + InitializeResultSchema, ); if (result === undefined) { @@ -170,7 +264,7 @@ export class Client< if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { throw new Error( - `Server's protocol version is not supported: ${result.protocolVersion}` + `Server's protocol version is not supported: ${result.protocolVersion}`, ); } @@ -215,7 +309,7 @@ export class Client< case "logging/setLevel": if (!this._serverCapabilities?.logging) { throw new Error( - `Server does not support logging (required for ${method})` + `Server does not support logging (required for ${method})`, ); } break; @@ -224,7 +318,7 @@ export class Client< case "prompts/list": if (!this._serverCapabilities?.prompts) { throw new Error( - `Server does not support prompts (required for ${method})` + `Server does not support prompts (required for ${method})`, ); } break; @@ -236,7 +330,7 @@ export class Client< case "resources/unsubscribe": if (!this._serverCapabilities?.resources) { throw new Error( - `Server does not support resources (required for ${method})` + `Server does not support resources (required for ${method})`, ); } @@ -245,7 +339,7 @@ export class Client< !this._serverCapabilities.resources.subscribe ) { throw new Error( - `Server does not support resource subscriptions (required for ${method})` + `Server does not support resource subscriptions (required for ${method})`, ); } @@ -255,7 +349,7 @@ export class Client< case "tools/list": if (!this._serverCapabilities?.tools) { throw new Error( - `Server does not support tools (required for ${method})` + `Server does not support tools (required for ${method})`, ); } break; @@ -263,7 +357,7 @@ export class Client< case "completion/complete": if (!this._serverCapabilities?.prompts) { throw new Error( - `Server does not support prompts (required for ${method})` + `Server does not support prompts (required for ${method})`, ); } break; @@ -319,7 +413,7 @@ export class Client< case "sampling/createMessage": if (!this._capabilities.sampling) { throw new Error( - `Client does not support sampling capability (required for ${method})` + `Client does not support sampling capability (required for ${method})`, ); } break; @@ -327,7 +421,7 @@ export class Client< case "roots/list": if (!this._capabilities.roots) { throw new Error( - `Client does not support roots capability (required for ${method})` + `Client does not support roots capability (required for ${method})`, ); } break; @@ -346,7 +440,7 @@ export class Client< return this.request( { method: "completion/complete", params }, CompleteResultSchema, - options + options, ); } @@ -354,7 +448,7 @@ export class Client< return this.request( { method: "logging/setLevel", params: { level } }, EmptyResultSchema, - options + options, ); } @@ -365,7 +459,7 @@ export class Client< return this.request( { method: "prompts/get", params }, GetPromptResultSchema, - options + options, ); } @@ -376,7 +470,7 @@ export class Client< return this.request( { method: "prompts/list", params }, ListPromptsResultSchema, - options + options, ); } @@ -387,7 +481,7 @@ export class Client< return this.request( { method: "resources/list", params }, ListResourcesResultSchema, - options + options, ); } @@ -398,7 +492,7 @@ export class Client< return this.request( { method: "resources/templates/list", params }, ListResourceTemplatesResultSchema, - options + options, ); } @@ -409,7 +503,7 @@ export class Client< return this.request( { method: "resources/read", params }, ReadResourceResultSchema, - options + options, ); } @@ -420,7 +514,7 @@ export class Client< return this.request( { method: "resources/subscribe", params }, EmptyResultSchema, - options + options, ); } @@ -431,7 +525,7 @@ export class Client< return this.request( { method: "resources/unsubscribe", params }, EmptyResultSchema, - options + options, ); } @@ -440,23 +534,23 @@ export class Client< resultSchema: | typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema = CallToolResultSchema, - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "tools/call", params }, resultSchema, - options + options, ); } async listTools( params?: ListToolsRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "tools/list", params }, ListToolsResultSchema, - options + options, ); } From 57f59e6d65977a4670f0d3c070efa93b399e2445 Mon Sep 17 00:00:00 2001 From: John Jung Date: Wed, 9 Apr 2025 17:11:38 -0400 Subject: [PATCH 3/6] reverse prettier formatting --- src/client/index.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index 36fe854d..860739bf 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -233,7 +233,7 @@ export class Client< protected assertCapability( capability: keyof ServerCapabilities, - method: string + method: string, ): void { if (!this._serverCapabilities?.[capability]) { throw new Error( @@ -373,7 +373,7 @@ export class Client< } protected assertNotificationCapability( - method: NotificationT["method"] + method: NotificationT["method"], ): void { switch (method as ClientNotification["method"]) { case "notifications/roots/list_changed": @@ -454,7 +454,7 @@ export class Client< async getPrompt( params: GetPromptRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "prompts/get", params }, @@ -465,7 +465,7 @@ export class Client< async listPrompts( params?: ListPromptsRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "prompts/list", params }, @@ -476,7 +476,7 @@ export class Client< async listResources( params?: ListResourcesRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "resources/list", params }, @@ -487,7 +487,7 @@ export class Client< async listResourceTemplates( params?: ListResourceTemplatesRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "resources/templates/list", params }, @@ -498,7 +498,7 @@ export class Client< async readResource( params: ReadResourceRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "resources/read", params }, @@ -509,7 +509,7 @@ export class Client< async subscribeResource( params: SubscribeRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "resources/subscribe", params }, @@ -520,7 +520,7 @@ export class Client< async unsubscribeResource( params: UnsubscribeRequest["params"], - options?: RequestOptions + options?: RequestOptions, ) { return this.request( { method: "resources/unsubscribe", params }, From bc6d37a82a9b072ae1686b6fe774c5f38be7d23c Mon Sep 17 00:00:00 2001 From: John Jung Date: Wed, 9 Apr 2025 17:14:38 -0400 Subject: [PATCH 4/6] reverse prettier formatting for easier diffs --- src/client/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/index.ts b/src/client/index.ts index 860739bf..7aee3ae2 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -119,7 +119,7 @@ export class Client< */ constructor( private _clientInfo: Implementation, - options?: ClientOptions + options?: ClientOptions, ) { super(options); this._capabilities = options?.capabilities ?? {}; From f3d12d6a36a41126c0e7c70311df5ab3a5b891b9 Mon Sep 17 00:00:00 2001 From: John Jung Date: Wed, 9 Apr 2025 17:28:53 -0400 Subject: [PATCH 5/6] Added optional cleaner error handling mechanisms --- src/client/index.ts | 51 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index 7aee3ae2..78867a89 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -62,6 +62,11 @@ export type ClientOptions = ProtocolOptions & { * Default: 300 */ debounceMs?: number; + /** + * Optional callback for handling tool list refresh errors. + * When provided, this will be called instead of logging to console. + */ + onError?: (error: Error) => void; }; }; @@ -103,9 +108,11 @@ export class Client< private _serverVersion?: Implementation; private _capabilities: ClientCapabilities; private _instructions?: string; - private _toolRefreshOptions: Required< - NonNullable - >; + private _toolRefreshOptions: { + autoRefresh: boolean; + debounceMs: number; + onError?: (error: Error) => void; + }; private _toolRefreshDebounceTimer?: ReturnType; /** @@ -126,6 +133,7 @@ export class Client< this._toolRefreshOptions = { autoRefresh: options?.toolRefreshOptions?.autoRefresh ?? true, debounceMs: options?.toolRefreshOptions?.debounceMs ?? 500, + onError: options?.toolRefreshOptions?.onError, }; // Set up notification handlers @@ -147,7 +155,12 @@ export class Client< // Set up debounced refresh this._toolRefreshDebounceTimer = setTimeout(() => { this._refreshToolsList().catch((error) => { - console.error("Failed to refresh tools list:", error); + // Use error callback if provided, otherwise log to console + if (this._toolRefreshOptions.onError) { + this._toolRefreshOptions.onError(error instanceof Error ? error : new Error(String(error))); + } else { + console.error("Failed to refresh tools list:", error); + } }); }, this._toolRefreshOptions.debounceMs); } @@ -166,7 +179,12 @@ export class Client< this.onToolListChanged?.(result.tools); } } catch (error) { - console.error("Failed to refresh tools list:", error); + // Use error callback if provided, otherwise log to console + if (this._toolRefreshOptions.onError) { + this._toolRefreshOptions.onError(error instanceof Error ? error : new Error(String(error))); + } else { + console.error("Failed to refresh tools list:", error); + } // Still call the callback even if refresh failed this.onToolListChanged?.(undefined); } @@ -200,18 +218,28 @@ export class Client< if (options.debounceMs !== undefined) { this._toolRefreshOptions.debounceMs = options.debounceMs; } + if (options.onError !== undefined) { + this._toolRefreshOptions.onError = options.onError; + } } } /** * Gets the current tool refresh options */ - public getToolRefreshOptions(): Required< - NonNullable - > { + public getToolRefreshOptions(): typeof this._toolRefreshOptions { return { ...this._toolRefreshOptions }; } + /** + * Sets an error handler for tool list refresh errors + * + * @param handler Function to call when a tool list refresh error occurs + */ + public setToolRefreshErrorHandler(handler: (error: Error) => void): void { + this._toolRefreshOptions.onError = handler; + } + /** * Manually triggers a refresh of the tools list */ @@ -226,7 +254,12 @@ export class Client< const result = await this.listTools(); return result.tools; } catch (error) { - console.error("Failed to manually refresh tools list:", error); + // Use error callback if provided, otherwise log to console + if (this._toolRefreshOptions.onError) { + this._toolRefreshOptions.onError(error instanceof Error ? error : new Error(String(error))); + } else { + console.error("Failed to manually refresh tools list:", error); + } return undefined; } } From 367133416f4634cf9fab45a40aa04d81a613e90e Mon Sep 17 00:00:00 2001 From: John Jung Date: Sun, 27 Apr 2025 18:01:38 -0400 Subject: [PATCH 6/6] clean up and write notes for now --- src/client/index.ts | 53 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index 78867a89..59c834e0 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -240,30 +240,6 @@ export class Client< this._toolRefreshOptions.onError = handler; } - /** - * Manually triggers a refresh of the tools list - */ - public async refreshToolsList(): Promise< - ListToolsResult["tools"] | undefined - > { - if (!this._serverCapabilities?.tools) { - return undefined; - } - - try { - const result = await this.listTools(); - return result.tools; - } catch (error) { - // Use error callback if provided, otherwise log to console - if (this._toolRefreshOptions.onError) { - this._toolRefreshOptions.onError(error instanceof Error ? error : new Error(String(error))); - } else { - console.error("Failed to manually refresh tools list:", error); - } - return undefined; - } - } - protected assertCapability( capability: keyof ServerCapabilities, method: string, @@ -576,6 +552,35 @@ export class Client< ); } + /** + * Retrieves the list of available tools from the server. + * + * This method is called automatically when a tools list changed notification + * is received (if auto-refresh is enabled and after debouncing). + * + * To manually refresh the tools list: + * ```typescript + * try { + * const result = await client.listTools(); + * // Use result.tools + * } catch (error) { + * // Handle error + * } + * ``` + * + * Alternatively, register an error handler: + * ```typescript + * client.setToolRefreshErrorHandler((error) => { + * // Handle error + * }); + * + * const result = await client.listTools(); + * ``` + * + * @param params Optional parameters for the list tools request + * @param options Optional request options + * @returns The list tools result containing available tools + */ async listTools( params?: ListToolsRequest["params"], options?: RequestOptions,