Add JSDoc descriptions and @example blocks to Client public methods (#1556)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-02-19 20:32:44 -06:00 • committed by GitHub
commit 1c4c77ae2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 321 additions and 2 deletions

View file

@ -7,7 +7,12 @@
* @module
*/
import type { Prompt, Resource, Tool } from '@modelcontextprotocol/core';
import { Client } from './client.js';
import { SSEClientTransport } from './sse.js';
import { StdioClientTransport } from './stdio.js';
import { StreamableHTTPClientTransport } from './streamableHttp.js';
/**
* Example: Using listChanged to automatically track tool and prompt updates.
@ -36,3 +41,154 @@ function ClientOptions_listChanged() {
//#endregion ClientOptions_listChanged
return client;
}
/**
* Example: Connect to a local server process over stdio.
*/
async function Client_connect_stdio() {
//#region Client_connect_stdio
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StdioClientTransport({ command: 'my-mcp-server' });
await client.connect(transport);
//#endregion Client_connect_stdio
return client;
}
/**
* Example: Connect with Streamable HTTP, falling back to legacy SSE.
*/
async function Client_connect_sseFallback(url: string) {
//#region Client_connect_sseFallback
const baseUrl = new URL(url);
try {
// Try modern Streamable HTTP transport first
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(baseUrl);
await client.connect(transport);
return { client, transport };
} catch {
// Fall back to legacy SSE transport
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new SSEClientTransport(baseUrl);
await client.connect(transport);
return { client, transport };
}
//#endregion Client_connect_sseFallback
}
/**
* Example: Call a tool on the connected server.
*/
async function Client_callTool_basic(client: Client) {
//#region Client_callTool_basic
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
// Tool-level errors are returned in the result, not thrown
if (result.isError) {
console.error('Tool error:', result.content);
return;
}
console.log(result.content);
//#endregion Client_callTool_basic
}
/**
* Example: Access machine-readable structured output from a tool call.
*/
async function Client_callTool_structuredOutput(client: Client) {
//#region Client_callTool_structuredOutput
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
// Machine-readable output for the client application
if (result.structuredContent) {
console.log(result.structuredContent); // e.g. { bmi: 22.86 }
}
//#endregion Client_callTool_structuredOutput
}
/**
* Example: Handle a sampling request from the server.
*/
function Client_setRequestHandler_sampling(client: Client) {
//#region Client_setRequestHandler_sampling
client.setRequestHandler('sampling/createMessage', async request => {
const lastMessage = request.params.messages.at(-1);
console.log('Sampling request:', lastMessage);
// In production, send messages to your LLM here
return {
model: 'my-model',
role: 'assistant' as const,
content: {
type: 'text' as const,
text: 'Response from the model'
}
};
});
//#endregion Client_setRequestHandler_sampling
}
/**
* Example: List tools with cursor-based pagination.
*/
async function Client_listTools_pagination(client: Client) {
//#region Client_listTools_pagination
const allTools: Tool[] = [];
let cursor: string | undefined;
do {
const { tools, nextCursor } = await client.listTools({ cursor });
allTools.push(...tools);
cursor = nextCursor;
} while (cursor);
console.log(
'Available tools:',
allTools.map(t => t.name)
);
//#endregion Client_listTools_pagination
}
/**
* Example: List prompts with cursor-based pagination.
*/
async function Client_listPrompts_pagination(client: Client) {
//#region Client_listPrompts_pagination
const allPrompts: Prompt[] = [];
let cursor: string | undefined;
do {
const { prompts, nextCursor } = await client.listPrompts({ cursor });
allPrompts.push(...prompts);
cursor = nextCursor;
} while (cursor);
console.log(
'Available prompts:',
allPrompts.map(p => p.name)
);
//#endregion Client_listPrompts_pagination
}
/**
* Example: List resources with cursor-based pagination.
*/
async function Client_listResources_pagination(client: Client) {
//#region Client_listResources_pagination
const allResources: Resource[] = [];
let cursor: string | undefined;
do {
const { resources, nextCursor } = await client.listResources({ cursor });
allResources.push(...resources);
cursor = nextCursor;
} while (cursor);
console.log(
'Available resources:',
allResources.map(r => r.name)
);
//#endregion Client_listResources_pagination
}

View file

@ -287,7 +287,30 @@ export class Client extends Protocol<ClientContext> {
}
/**
* Override request handler registration to enforce client-side validation for elicitation.
* Registers a handler for server-initiated requests (sampling, elicitation, roots).
* The client must declare the corresponding capability for the handler to be accepted.
* Replaces any previously registered handler for the same method.
*
* For `sampling/createMessage` and `elicitation/create`, the handler is automatically
* wrapped with schema validation for both the incoming request and the returned result.
*
* @example Handling a sampling request
* ```ts source="./client.examples.ts#Client_setRequestHandler_sampling"
* client.setRequestHandler('sampling/createMessage', async request => {
* const lastMessage = request.params.messages.at(-1);
* console.log('Sampling request:', lastMessage);
*
* // In production, send messages to your LLM here
* return {
* model: 'my-model',
* role: 'assistant' as const,
* content: {
* type: 'text' as const,
* text: 'Response from the model'
* }
* };
* });
* ```
*/
public override setRequestHandler<M extends RequestMethod>(
method: M,
@ -416,6 +439,35 @@ export class Client extends Protocol<ClientContext> {
}
}
/**
* Connects to a server via the given transport and performs the MCP initialization handshake.
*
* @example Basic usage (stdio)
* ```ts source="./client.examples.ts#Client_connect_stdio"
* const client = new Client({ name: 'my-client', version: '1.0.0' });
* const transport = new StdioClientTransport({ command: 'my-mcp-server' });
* await client.connect(transport);
* ```
*
* @example Streamable HTTP with SSE fallback
* ```ts source="./client.examples.ts#Client_connect_sseFallback"
* const baseUrl = new URL(url);
*
* try {
* // Try modern Streamable HTTP transport first
* const client = new Client({ name: 'my-client', version: '1.0.0' });
* const transport = new StreamableHTTPClientTransport(baseUrl);
* await client.connect(transport);
* return { client, transport };
* } catch {
* // Fall back to legacy SSE transport
* const client = new Client({ name: 'my-client', version: '1.0.0' });
* const transport = new SSEClientTransport(baseUrl);
* await client.connect(transport);
* return { client, transport };
* }
* ```
*/
override async connect(transport: Transport, options?: RequestOptions): Promise<void> {
await super.connect(transport);
// When transport sessionId is already set this means we are trying to reconnect.
@ -655,22 +707,47 @@ export class Client extends Protocol<ClientContext> {
assertClientRequestTaskCapability(this._capabilities.tasks?.requests, method, 'Client');
}
/** Sends a ping to the server to check connectivity. */
async ping(options?: RequestOptions) {
return this.request({ method: 'ping' }, EmptyResultSchema, options);
}
/** Requests argument autocompletion suggestions from the server for a prompt or resource. */
async complete(params: CompleteRequest['params'], options?: RequestOptions) {
return this.request({ method: 'completion/complete', params }, CompleteResultSchema, options);
}
/** Sets the minimum severity level for log messages sent by the server. */
async setLoggingLevel(level: LoggingLevel, options?: RequestOptions) {
return this.request({ method: 'logging/setLevel', params: { level } }, EmptyResultSchema, options);
}
/** Retrieves a prompt by name from the server, passing the given arguments for template substitution. */
async getPrompt(params: GetPromptRequest['params'], options?: RequestOptions) {
return this.request({ method: 'prompts/get', params }, GetPromptResultSchema, options);
}
/**
* Lists available prompts. Results may be paginated — loop on `nextCursor` to collect all pages.
*
* Returns an empty list if the server does not advertise prompts capability
* (or throws if {@linkcode ClientOptions.enforceStrictCapabilities} is enabled).
*
* @example
* ```ts source="./client.examples.ts#Client_listPrompts_pagination"
* const allPrompts: Prompt[] = [];
* let cursor: string | undefined;
* do {
* const { prompts, nextCursor } = await client.listPrompts({ cursor });
* allPrompts.push(...prompts);
* cursor = nextCursor;
* } while (cursor);
* console.log(
* 'Available prompts:',
* allPrompts.map(p => p.name)
* );
* ```
*/
async listPrompts(params?: ListPromptsRequest['params'], options?: RequestOptions) {
if (!this._serverCapabilities?.prompts && !this._enforceStrictCapabilities) {
// Respect capability negotiation: server does not support prompts
@ -680,6 +757,27 @@ export class Client extends Protocol<ClientContext> {
return this.request({ method: 'prompts/list', params }, ListPromptsResultSchema, options);
}
/**
* Lists available resources. Results may be paginated — loop on `nextCursor` to collect all pages.
*
* Returns an empty list if the server does not advertise resources capability
* (or throws if {@linkcode ClientOptions.enforceStrictCapabilities} is enabled).
*
* @example
* ```ts source="./client.examples.ts#Client_listResources_pagination"
* const allResources: Resource[] = [];
* let cursor: string | undefined;
* do {
* const { resources, nextCursor } = await client.listResources({ cursor });
* allResources.push(...resources);
* cursor = nextCursor;
* } while (cursor);
* console.log(
* 'Available resources:',
* allResources.map(r => r.name)
* );
* ```
*/
async listResources(params?: ListResourcesRequest['params'], options?: RequestOptions) {
if (!this._serverCapabilities?.resources && !this._enforceStrictCapabilities) {
// Respect capability negotiation: server does not support resources
@ -689,6 +787,12 @@ export class Client extends Protocol<ClientContext> {
return this.request({ method: 'resources/list', params }, ListResourcesResultSchema, options);
}
/**
* Lists available resource URI templates for dynamic resources. Results may be paginated — see {@linkcode listResources | listResources()} for the cursor pattern.
*
* Returns an empty list if the server does not advertise resources capability
* (or throws if {@linkcode ClientOptions.enforceStrictCapabilities} is enabled).
*/
async listResourceTemplates(params?: ListResourceTemplatesRequest['params'], options?: RequestOptions) {
if (!this._serverCapabilities?.resources && !this._enforceStrictCapabilities) {
// Respect capability negotiation: server does not support resources
@ -700,22 +804,59 @@ export class Client extends Protocol<ClientContext> {
return this.request({ method: 'resources/templates/list', params }, ListResourceTemplatesResultSchema, options);
}
/** Reads the contents of a resource by URI. */
async readResource(params: ReadResourceRequest['params'], options?: RequestOptions) {
return this.request({ method: 'resources/read', params }, ReadResourceResultSchema, options);
}
/** Subscribes to change notifications for a resource. The server must support resource subscriptions. */
async subscribeResource(params: SubscribeRequest['params'], options?: RequestOptions) {
return this.request({ method: 'resources/subscribe', params }, EmptyResultSchema, options);
}
/** Unsubscribes from change notifications for a resource. */
async unsubscribeResource(params: UnsubscribeRequest['params'], options?: RequestOptions) {
return this.request({ method: 'resources/unsubscribe', params }, EmptyResultSchema, options);
}
/**
* Calls a tool and waits for the result. Automatically validates structured output if the tool has an `outputSchema`.
* Calls a tool on the connected server and returns the result. Automatically validates structured output
* if the tool has an `outputSchema`.
*
* Tool results have two error surfaces: `result.isError` for tool-level failures (the tool ran but reported
* a problem), and thrown {@linkcode ProtocolError} for protocol-level failures or {@linkcode SdkError} for
* SDK-level issues (timeouts, missing capabilities).
*
* For task-based execution with streaming behavior, use {@linkcode ExperimentalClientTasks.callToolStream | client.experimental.tasks.callToolStream()} instead.
*
* @example Basic usage
* ```ts source="./client.examples.ts#Client_callTool_basic"
* const result = await client.callTool({
* name: 'calculate-bmi',
* arguments: { weightKg: 70, heightM: 1.75 }
* });
*
* // Tool-level errors are returned in the result, not thrown
* if (result.isError) {
* console.error('Tool error:', result.content);
* return;
* }
*
* console.log(result.content);
* ```
*
* @example Structured output
* ```ts source="./client.examples.ts#Client_callTool_structuredOutput"
* const result = await client.callTool({
* name: 'calculate-bmi',
* arguments: { weightKg: 70, heightM: 1.75 }
* });
*
* // Machine-readable output for the client application
* if (result.structuredContent) {
* console.log(result.structuredContent); // e.g. { bmi: 22.86 }
* }
* ```
*/
async callTool(
params: CallToolRequest['params'],
@ -820,6 +961,27 @@ export class Client extends Protocol<ClientContext> {
return this._cachedToolOutputValidators.get(toolName);
}
/**
* Lists available tools. Results may be paginated — loop on `nextCursor` to collect all pages.
*
* Returns an empty list if the server does not advertise tools capability
* (or throws if {@linkcode ClientOptions.enforceStrictCapabilities} is enabled).
*
* @example
* ```ts source="./client.examples.ts#Client_listTools_pagination"
* const allTools: Tool[] = [];
* let cursor: string | undefined;
* do {
* const { tools, nextCursor } = await client.listTools({ cursor });
* allTools.push(...tools);
* cursor = nextCursor;
* } while (cursor);
* console.log(
* 'Available tools:',
* allTools.map(t => t.name)
* );
* ```
*/
async listTools(params?: ListToolsRequest['params'], options?: RequestOptions) {
if (!this._serverCapabilities?.tools && !this._enforceStrictCapabilities) {
// Respect capability negotiation: server does not support tools
@ -894,6 +1056,7 @@ export class Client extends Protocol<ClientContext> {
this.setNotificationHandler(notificationMethod, handler);
}
/** Notifies the server that the client's root list has changed. Requires the `roots.listChanged` capability. */
async sendRootsListChanged() {
return this.notification({ method: 'notifications/roots/list_changed' });
}