typescript-sdk/examples/guides/clientGuide.examples.ts

923 lines
33 KiB
TypeScript

/**
* Type-checked examples for docs/client.md.
*
* Regions are synced into markdown code fences via `pnpm sync:snippets`.
* Each function wraps a single region. The function name matches the region name.
*
* @module
*/
//#region imports
import type {
AuthProvider,
CallToolResult,
InputRequiredResult,
OAuthClientInformationContext,
OAuthClientInformationMixed,
OAuthClientMetadata,
OAuthClientProvider,
OAuthDiscoveryState,
OAuthTokens
} from '@modelcontextprotocol/client';
import {
applyMiddlewares,
checkResourceAllowed,
Client,
ClientCredentialsProvider,
createMiddleware,
CrossAppAccessProvider,
discoverAndRequestJwtAuthGrant,
isInputRequiredResult,
IssuerMismatchError,
LOG_LEVEL_META_KEY,
PrivateKeyJwtProvider,
ProtocolError,
resourceUrlFromServerUrl,
SdkError,
SdkErrorCode,
SdkHttpError,
SSEClientTransport,
StreamableHTTPClientTransport,
TRACEPARENT_META_KEY,
TRACESTATE_META_KEY,
UnauthorizedError
} from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
//#endregion imports
// ---------------------------------------------------------------------------
// Connecting to a server
// ---------------------------------------------------------------------------
/** Example: Streamable HTTP transport. */
async function connect_streamableHttp() {
//#region connect_streamableHttp
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
await client.connect(transport);
//#endregion connect_streamableHttp
}
/** Example: stdio transport for local process-spawned servers. */
async function connect_stdio() {
//#region connect_stdio
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StdioClientTransport({
command: 'node',
args: ['server.js']
});
await client.connect(transport);
//#endregion connect_stdio
}
/** Example: Try Streamable HTTP, fall back to legacy SSE. */
async function connect_sseFallback(url: string) {
//#region 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 connect_sseFallback
}
/** Example: Opt into 2026-07-28 protocol version negotiation. */
async function Client_versionNegotiation(transport: StreamableHTTPClientTransport) {
//#region Client_versionNegotiation
// Auto-negotiate: probe with server/discover, fall back to the 2025 handshake
// against a 2025-only server.
const client = new Client({ name: 'my-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await client.connect(transport);
client.getProtocolEra(); // 'modern' or 'legacy'
client.getNegotiatedProtocolVersion(); // '2026-07-28' or '2025-11-25'
//#endregion Client_versionNegotiation
}
/** Example: zero-round-trip connect from a persisted DiscoverResult. */
async function Client_connect_prior(url: URL) {
//#region Client_connect_prior
// Probe once (here via the 'auto'-mode connect), persist the result …
const bootstrap = new Client({ name: 'gateway', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await bootstrap.connect(new StreamableHTTPClientTransport(url));
const persisted = JSON.stringify(bootstrap.getDiscoverResult());
// … then every worker connects with zero round trips.
const worker = new Client({ name: 'worker', version: '1.0.0' });
await worker.connect(new StreamableHTTPClientTransport(url), { prior: JSON.parse(persisted) });
//#endregion Client_connect_prior
}
// ---------------------------------------------------------------------------
// Disconnecting
// ---------------------------------------------------------------------------
/** Example: Graceful disconnect for Streamable HTTP. */
async function disconnect_streamableHttp(client: Client, transport: StreamableHTTPClientTransport) {
//#region disconnect_streamableHttp
await transport.terminateSession(); // notify the server (recommended)
await client.close();
//#endregion disconnect_streamableHttp
}
// ---------------------------------------------------------------------------
// Server instructions
// ---------------------------------------------------------------------------
/** Example: Access server instructions after connecting. */
async function serverInstructions_basic(client: Client) {
//#region serverInstructions_basic
const instructions = client.getInstructions();
const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boolean).join('\n\n');
console.log(systemPrompt);
//#endregion serverInstructions_basic
}
// ---------------------------------------------------------------------------
// Extension capabilities
// ---------------------------------------------------------------------------
/** Example: Read the negotiated extension capabilities after connecting. */
function extensionCapabilities_read(client: Client) {
//#region extensionCapabilities_read
const extensions = client.getServerCapabilities()?.extensions ?? {};
if ('com.example/feature-flags' in extensions) {
// Advertised on this connection; the entry's value is its settings object.
}
//#endregion extensionCapabilities_read
}
// ---------------------------------------------------------------------------
// Authentication
// ---------------------------------------------------------------------------
/** Example: Minimal AuthProvider for bearer auth with externally-managed tokens. */
async function auth_tokenProvider(getStoredToken: () => Promise<string>) {
//#region auth_tokenProvider
const authProvider: AuthProvider = { token: async () => getStoredToken() };
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
//#endregion auth_tokenProvider
return transport;
}
/** Example: Client credentials auth for service-to-service communication. */
async function auth_clientCredentials() {
//#region auth_clientCredentials
const authProvider = new ClientCredentialsProvider({
clientId: 'my-service',
clientSecret: 'my-secret'
});
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
await client.connect(transport);
//#endregion auth_clientCredentials
}
/** Example: Private key JWT auth. */
async function auth_privateKeyJwt(pemEncodedKey: string) {
//#region auth_privateKeyJwt
const authProvider = new PrivateKeyJwtProvider({
clientId: 'my-service',
privateKey: pemEncodedKey,
algorithm: 'RS256'
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
//#endregion auth_privateKeyJwt
return transport;
}
/** Example: Cross-App Access (SEP-990 Enterprise Managed Authorization). */
async function auth_crossAppAccess(getIdToken: () => Promise<string>) {
//#region auth_crossAppAccess
const authProvider = new CrossAppAccessProvider({
assertion: async ctx => {
// ctx provides: authorizationServerUrl, resourceUrl, scope, fetchFn
const result = await discoverAndRequestJwtAuthGrant({
idpUrl: 'https://idp.example.com',
audience: ctx.authorizationServerUrl,
resource: ctx.resourceUrl,
idToken: await getIdToken(),
clientId: 'my-idp-client',
clientSecret: 'my-idp-secret',
scope: ctx.scope,
fetchFn: ctx.fetchFn
});
return result.jwtAuthGrant;
},
clientId: 'my-mcp-client',
clientSecret: 'my-mcp-secret'
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
//#endregion auth_crossAppAccess
return transport;
}
/**
* Example: Minimal `OAuthClientProvider` for the authorization-code flow.
* Client credentials are stored per authorization-server `issuer` (SEP-2352).
*/
function auth_oauthClientProvider(onRedirect: (url: URL) => void) {
//#region auth_oauthClientProvider
class MyOAuthProvider implements OAuthClientProvider {
// Key DCR-obtained credentials by issuer so a client_id registered with one
// authorization server is never returned for another (SEP-2352).
private creds = new Map<string, OAuthClientInformationMixed>();
private storedTokens?: OAuthTokens;
private verifier?: string;
private discovery?: OAuthDiscoveryState;
lastState?: string;
readonly redirectUrl = 'http://localhost:8090/callback';
readonly clientMetadata: OAuthClientMetadata = {
client_name: 'My MCP Client',
redirect_uris: ['http://localhost:8090/callback'],
// Loopback redirect → the SDK would default this to 'native'; set
// explicitly when the heuristic is wrong for your deployment (SEP-837).
application_type: 'native'
};
clientInformation(ctx?: OAuthClientInformationContext) {
return ctx ? this.creds.get(ctx.issuer) : undefined;
}
saveClientInformation(info: OAuthClientInformationMixed, ctx?: OAuthClientInformationContext) {
if (ctx) this.creds.set(ctx.issuer, info);
}
tokens() {
return this.storedTokens;
}
saveTokens(tokens: OAuthTokens) {
// In production, persist to OS keychain / secure storage — never plain files.
this.storedTokens = tokens;
}
// CSRF binding for the redirect — the SDK puts this on the authorize URL;
// your callback handler compares it before calling `finishAuth`.
state() {
this.lastState = crypto.randomUUID();
return this.lastState;
}
// Callback-leg AS-binding (SEP-2352): record what discovery resolved before
// the redirect so the SDK can verify the code is exchanged at the same AS.
saveDiscoveryState(state: OAuthDiscoveryState) {
this.discovery = state;
}
discoveryState() {
return this.discovery;
}
redirectToAuthorization(url: URL) {
onRedirect(url);
}
saveCodeVerifier(v: string) {
this.verifier = v;
}
codeVerifier() {
if (!this.verifier) throw new Error('no code verifier');
return this.verifier;
}
}
const provider = new MyOAuthProvider();
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
authProvider: provider
});
//#endregion auth_oauthClientProvider
//#region auth_validateResourceURL
class PinnedResourceProvider extends MyOAuthProvider {
async validateResourceURL(serverUrl: string | URL, resource?: string): Promise<URL | undefined> {
const expected = resourceUrlFromServerUrl(serverUrl); // strips the fragment (RFC 8707 §2)
if (resource && !checkResourceAllowed({ requestedResource: expected, configuredResource: resource })) {
throw new Error(`Refusing resource ${resource} for server ${expected.href}`);
}
return expected;
}
}
//#endregion auth_validateResourceURL
void PinnedResourceProvider;
return { provider, transport };
}
/** Example: Handling the OAuth callback — extract `iss` for RFC 9207 validation. */
async function auth_finishAuth(url: URL, provider: OAuthClientProvider & { lastState?: string }, waitForCallback: () => Promise<string>) {
//#region auth_finishAuth
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(url, { authProvider: provider });
try {
await client.connect(transport);
return client;
} catch (error) {
// With version negotiation, the connect-time 401 may surface wrapped as
// SdkError(EraNegotiationFailed) whose .data.cause is the UnauthorizedError.
const root = error instanceof UnauthorizedError ? error : (error as { data?: { cause?: unknown } }).data?.cause;
if (!(root instanceof UnauthorizedError)) throw error;
// The transport called redirectToAuthorization(); fall through to the browser callback.
}
const callbackUrl = await waitForCallback();
const params = new URL(callbackUrl).searchParams;
// The SDK does not validate `state` — compare it to the value your provider generated.
if (params.get('state') !== provider.lastState) throw new Error('state mismatch');
try {
// Preferred: hand over the whole query — the SDK extracts `code` and
// `iss`, validates `iss` (RFC 9207), and never surfaces callback-derived
// `error`/`error_description` text on mismatch.
await transport.finishAuth(params);
} catch (error) {
if (error instanceof IssuerMismatchError) {
// Mix-up attack: do NOT render params.get('error_description') to the user.
throw new Error('Authorization failed: issuer mismatch');
}
throw error;
}
// Reconnect on a FRESH transport — a started transport cannot be restarted;
// OAuth state (tokens, verifier, discovery) lives on the provider, not the transport.
await client.connect(new StreamableHTTPClientTransport(url, { authProvider: provider }));
return client;
//#endregion auth_finishAuth
}
// ---------------------------------------------------------------------------
// Using server features
// ---------------------------------------------------------------------------
/** Example: List and call tools. */
async function callTool_basic(client: Client) {
//#region callTool_basic
const { tools } = await client.listTools();
console.log(
'Available tools:',
tools.map(t => t.name)
);
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
console.log(result.content);
//#endregion callTool_basic
}
/** Example: Structured tool output. */
async function callTool_structuredOutput(client: Client) {
//#region callTool_structuredOutput
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
// Machine-readable output for the client application. SEP-2106: structuredContent is
// `unknown` (any JSON value). Check for presence with `!== undefined` and narrow before use.
if (result.structuredContent !== undefined) {
const sc: unknown = result.structuredContent; // e.g. { bmi: 22.86 }
if (typeof sc === 'object' && sc !== null && 'bmi' in sc) {
console.log(sc.bmi);
}
}
//#endregion callTool_structuredOutput
}
/** Example: Track progress of a long-running tool call. */
async function callTool_progress(client: Client) {
//#region callTool_progress
const result = await client.callTool(
{ name: 'long-operation', arguments: {} },
{
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
console.log(`Progress: ${progress}/${total ?? '?'}`);
},
resetTimeoutOnProgress: true,
maxTotalTimeout: 600_000
}
);
console.log(result.content);
//#endregion callTool_progress
}
/** Example: List and read resources. */
async function readResource_basic(client: Client) {
//#region readResource_basic
const { resources } = await client.listResources();
console.log(
'Available resources:',
resources.map(r => r.name)
);
const { contents } = await client.readResource({ uri: 'config://app' });
for (const item of contents) {
console.log(item);
}
//#endregion readResource_basic
}
/** Example: Subscribe to resource changes. */
async function subscribeResource_basic(client: Client) {
//#region subscribeResource_basic
await client.subscribeResource({ uri: 'config://app' });
client.setNotificationHandler('notifications/resources/updated', async notification => {
if (notification.params.uri === 'config://app') {
const { contents } = await client.readResource({ uri: 'config://app' });
console.log('Config updated:', contents);
}
});
// Later: stop receiving updates
await client.unsubscribeResource({ uri: 'config://app' });
//#endregion subscribeResource_basic
}
/** Example: List and get prompts. */
async function getPrompt_basic(client: Client) {
//#region getPrompt_basic
const { prompts } = await client.listPrompts();
console.log(
'Available prompts:',
prompts.map(p => p.name)
);
const { messages } = await client.getPrompt({
name: 'review-code',
arguments: { code: 'console.log("hello")' }
});
console.log(messages);
//#endregion getPrompt_basic
}
/** Example: Request argument completions. */
async function complete_basic(client: Client) {
//#region complete_basic
const { completion } = await client.complete({
ref: {
type: 'ref/prompt',
name: 'review-code'
},
argument: {
name: 'language',
value: 'type'
}
});
console.log(completion.values); // e.g. ['typescript']
//#endregion complete_basic
}
// ---------------------------------------------------------------------------
// Response caching
// ---------------------------------------------------------------------------
/** Example: Per-call cache disposition via cacheMode. */
async function responseCache_basic(client: Client) {
//#region responseCache_basic
const tools = await client.listTools(); // network, then cached for the server's ttlMs
const again = await client.listTools(); // served from cache while still fresh
await client.listTools(undefined, { cacheMode: 'refresh' }); // always refetch and re-store
await client.readResource({ uri: 'config://app' }, { cacheMode: 'bypass' }); // no cache read or write
//#endregion responseCache_basic
void tools;
void again;
}
// ---------------------------------------------------------------------------
// Notifications
// ---------------------------------------------------------------------------
/** Example: Handle log messages and list-change notifications. */
function notificationHandler_basic(client: Client) {
//#region notificationHandler_basic
// Server log messages (sent by the server during request processing)
client.setNotificationHandler('notifications/message', notification => {
const { level, data } = notification.params;
console.log(`[${level}]`, data);
});
// Server's resource list changed — re-fetch the list
client.setNotificationHandler('notifications/resources/list_changed', async () => {
const { resources } = await client.listResources();
console.log('Resources changed:', resources.length);
});
//#endregion notificationHandler_basic
}
/** Example: Control server log level. */
async function setLoggingLevel_basic(client: Client) {
//#region setLoggingLevel_basic
await client.setLoggingLevel('warning');
//#endregion setLoggingLevel_basic
}
/** Example: Per-request log-level opt-in on a 2026-07-28 connection. */
async function logLevelMeta_modern(client: Client) {
//#region logLevelMeta_modern
const result = await client.callTool({
name: 'fetch-data',
arguments: { url: 'https://example.com' },
_meta: { [LOG_LEVEL_META_KEY]: 'debug' }
});
//#endregion logLevelMeta_modern
void result;
}
/** Example: Automatic list-change tracking via the listChanged option. */
async function listChanged_basic() {
//#region listChanged_basic
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
listChanged: {
tools: {
onChanged: (error, tools) => {
if (error) {
console.error('Failed to refresh tools:', error);
return;
}
console.log('Tools updated:', tools);
}
},
prompts: {
onChanged: (error, prompts) => console.log('Prompts updated:', prompts)
}
}
}
);
//#endregion listChanged_basic
return client;
}
/** Example: Open a subscriptions/listen stream explicitly (2026-07-28). */
async function listen_basic(client: Client) {
//#region listen_basic
client.setNotificationHandler('notifications/tools/list_changed', async () => {
const { tools } = await client.listTools();
console.log('Tools changed:', tools.length);
});
client.setNotificationHandler('notifications/resources/updated', async notification => {
console.log('Resource updated:', notification.params.uri);
});
const subscription = await client.listen({
toolsListChanged: true,
resourceSubscriptions: ['config://app']
});
console.log('Server honored:', subscription.honoredFilter);
// Later: tear the stream down
await subscription.close();
//#endregion listen_basic
}
/** Example: Watch loop that re-listens on unexpected closes. */
async function listen_watchLoop(client: Client, watching: boolean) {
//#region listen_watchLoop
while (watching) {
const sub = await client.listen({ resourceSubscriptions: ['config://app'] });
const reason = await sub.closed;
if (reason !== 'remote') break; // 'local' or 'graceful': done
await new Promise(resolve => setTimeout(resolve, 1000)); // back off, then re-listen
}
//#endregion listen_watchLoop
}
// ---------------------------------------------------------------------------
// Handling server-initiated requests
// ---------------------------------------------------------------------------
/** Example: Declare client capabilities for sampling, elicitation, and roots. */
function capabilities_declaration() {
//#region capabilities_declaration
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
capabilities: {
sampling: {},
elicitation: { form: {} },
roots: { listChanged: true }
}
}
);
//#endregion capabilities_declaration
return client;
}
/** Example: Handle a sampling request from the server. */
function sampling_handler(client: Client) {
//#region sampling_handler
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 sampling_handler
}
/** Example: Handle an elicitation request from the server. */
function elicitation_handler(client: Client) {
//#region elicitation_handler
client.setRequestHandler('elicitation/create', async request => {
console.log('Server asks:', request.params.message);
if (request.params.mode === 'form') {
// Present the schema-driven form to the user
console.log('Schema:', request.params.requestedSchema);
return { action: 'accept', content: { confirm: true } };
}
return { action: 'decline' };
});
//#endregion elicitation_handler
}
/** Example: Expose filesystem roots to the server. */
function roots_handler(client: Client) {
//#region roots_handler
client.setRequestHandler('roots/list', async () => {
return {
roots: [
{ uri: 'file:///home/user/projects/my-app', name: 'My App' },
{ uri: 'file:///home/user/data', name: 'Data' }
]
};
});
//#endregion roots_handler
}
/** Example: Manual multi-round-trip handling with autoFulfill disabled (2026-07-28). */
async function inputRequired_manual(transport: StreamableHTTPClientTransport) {
//#region inputRequired_manual
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
capabilities: { elicitation: { form: {} } },
versionNegotiation: { mode: 'auto' },
inputRequired: { autoFulfill: false }
}
);
await client.connect(transport);
const value = (await client.request(
{ method: 'tools/call', params: { name: 'deploy', arguments: { env: 'prod' } } },
{ allowInputRequired: true }
)) as CallToolResult | InputRequiredResult;
if (isInputRequiredResult(value)) {
// Collect responses for value.inputRequests from your UI, then retry:
await client.request(
{
method: 'tools/call',
params: {
name: 'deploy',
arguments: { env: 'prod' },
inputResponses: { confirm: { action: 'accept', content: { confirm: true } } },
requestState: value.requestState // echo byte-exact
}
},
{ allowInputRequired: true }
);
}
//#endregion inputRequired_manual
}
// ---------------------------------------------------------------------------
// Error handling
// ---------------------------------------------------------------------------
/** Example: Tool errors vs protocol errors. */
async function errorHandling_toolErrors(client: Client) {
//#region errorHandling_toolErrors
try {
const result = await client.callTool({
name: 'fetch-data',
arguments: { url: 'https://example.com' }
});
// Tool-level error: the tool ran but reported a problem
if (result.isError) {
console.error('Tool error:', result.content);
return;
}
console.log('Success:', result.content);
} catch (error) {
// Protocol-level error: the request itself failed
if (error instanceof ProtocolError) {
console.error(`Protocol error ${error.code}: ${error.message}`);
} else if (error instanceof SdkError) {
console.error(`SDK error [${error.code}]: ${error.message}`);
} else {
throw error;
}
}
//#endregion errorHandling_toolErrors
}
/** Example: Connection lifecycle callbacks. */
function errorHandling_lifecycle(client: Client) {
//#region errorHandling_lifecycle
// Out-of-band errors (SSE disconnects, parse errors)
client.onerror = error => {
console.error('Transport error:', error.message);
};
// Connection closed (pending requests are rejected with CONNECTION_CLOSED)
client.onclose = () => {
console.log('Connection closed');
};
//#endregion errorHandling_lifecycle
}
/** Example: Custom timeouts. */
async function errorHandling_timeout(client: Client) {
//#region errorHandling_timeout
try {
const result = await client.callTool(
{ name: 'slow-operation', arguments: {} },
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
);
console.log(result.content);
} catch (error) {
if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) {
console.error('Request timed out');
}
}
//#endregion errorHandling_timeout
}
/** Example: Typed HTTP transport errors. */
async function errorHandling_http(client: Client, transport: StreamableHTTPClientTransport) {
//#region errorHandling_http
try {
await client.connect(transport);
} catch (error) {
if (error instanceof SdkHttpError) {
console.error(`HTTP ${error.status} (${error.statusText ?? ''}) [${error.code}]`);
} else {
throw error;
}
}
//#endregion errorHandling_http
}
// ---------------------------------------------------------------------------
// Advanced patterns
// ---------------------------------------------------------------------------
/** Example: Client middleware that adds a custom header. */
async function middleware_basic() {
//#region middleware_basic
const authMiddleware = createMiddleware(async (next, input, init) => {
const headers = new Headers(init?.headers);
headers.set('X-Custom-Header', 'my-value');
return next(input, { ...init, headers });
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
fetch: applyMiddlewares(authMiddleware)(fetch)
});
//#endregion middleware_basic
return transport;
}
/** Example: Attach W3C Trace Context to a single request via `_meta`. */
async function traceContext_perRequest(client: Client) {
//#region traceContext_perRequest
// Values would normally come from your tracer's active span context.
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 },
_meta: {
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
[TRACESTATE_META_KEY]: 'vendor1=opaqueValue1'
}
});
console.log(result.content);
//#endregion traceContext_perRequest
}
/** Example: Client middleware that injects trace context into every outgoing request. */
async function traceContext_middleware() {
//#region traceContext_middleware
const traceContextMiddleware = createMiddleware(async (next, input, init) => {
if (typeof init?.body !== 'string') {
return next(input, init);
}
const message = JSON.parse(init.body) as {
method?: string;
params?: { _meta?: Record<string, unknown>; [key: string]: unknown };
};
// Only requests and notifications carry params._meta; skip responses.
if (message.method === undefined) {
return next(input, init);
}
message.params = {
...message.params,
_meta: {
...message.params?._meta,
// Replace with values from your tracer's active span context.
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
}
};
return next(input, { ...init, body: JSON.stringify(message) });
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
fetch: applyMiddlewares(traceContextMiddleware)(fetch)
});
//#endregion traceContext_middleware
return transport;
}
/** Example: Track resumption tokens for SSE reconnection. */
async function resumptionToken_basic(client: Client) {
//#region resumptionToken_basic
let lastToken: string | undefined;
const result = await client.request(
{
method: 'tools/call',
params: { name: 'long-running-operation', arguments: {} }
},
{
resumptionToken: lastToken,
onresumptiontoken: (token: string) => {
lastToken = token;
// Persist token to survive restarts
}
}
);
console.log(result);
//#endregion resumptionToken_basic
}
// Suppress unused-function warnings (functions exist solely for type-checking)
void connect_streamableHttp;
void connect_stdio;
void connect_sseFallback;
void Client_versionNegotiation;
void disconnect_streamableHttp;
void serverInstructions_basic;
void extensionCapabilities_read;
void auth_tokenProvider;
void auth_clientCredentials;
void auth_privateKeyJwt;
void auth_crossAppAccess;
void callTool_basic;
void callTool_structuredOutput;
void callTool_progress;
void readResource_basic;
void subscribeResource_basic;
void getPrompt_basic;
void complete_basic;
void responseCache_basic;
void notificationHandler_basic;
void setLoggingLevel_basic;
void logLevelMeta_modern;
void listChanged_basic;
void listen_basic;
void listen_watchLoop;
void capabilities_declaration;
void sampling_handler;
void elicitation_handler;
void roots_handler;
void inputRequired_manual;
void errorHandling_toolErrors;
void errorHandling_lifecycle;
void errorHandling_timeout;
void errorHandling_http;
void middleware_basic;
void traceContext_perRequest;
void traceContext_middleware;
void resumptionToken_basic;