Skip to content
Open
8 changes: 8 additions & 0 deletions mcp-client-typescript/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# An LLM-Powered Chatbot MCP Client written in TypeScript

See the [Build an MCP client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial for more information.

## Structured content

The SDK validates every result against the tool's declared `outputSchema`, so the spec's client-side SHOULD needs no code here.

The two channels go to different readers: `content` is forwarded to the model, while `structuredContent` is used as data — when a tool returns an array, the client counts its items rather than re-reading the prose. See [Structured Content](https://modelcontextprotocol.io/specification/draft/server/tools#structured-content).

`versionNegotiation: { mode: 'auto' }` probes `server/discover` and falls back to the `2025-11-25` handshake; the SDK's default is `'legacy'`. See the [SDK documentation](https://ts.sdk.modelcontextprotocol.io/v2/).
25 changes: 20 additions & 5 deletions mcp-client-typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
ToolUseBlock,
} from "@anthropic-ai/sdk/resources/messages/messages.mjs";

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import readline from "readline/promises";

import dotenv from "dotenv";

dotenv.config(); // load environment variables from .env

const ANTHROPIC_MODEL = "claude-sonnet-4-5";
const ANTHROPIC_MODEL = "claude-sonnet-5";
const MAX_TOOL_TURNS = 10;

class MCPClient {
Expand All @@ -25,8 +25,12 @@ class MCPClient {
private tools: Tool[] = [];

constructor() {
// Initialize MCP client
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
// 'auto' probes server/discover, falling back to the 2025-11-25 handshake.
// The SDK's default is 'legacy'.
this.mcp = new Client(
{ name: "mcp-client-cli", version: "1.0.0" },
{ versionNegotiation: { mode: "auto" } },
);
}

private get anthropic(): Anthropic {
Expand Down Expand Up @@ -123,14 +127,25 @@ class MCPClient {
finalText.push(
`[Calling tool ${toolUse.name} with args ${JSON.stringify(toolArgs)}]`,
);
// callTool validates the result against the declared schema.
const result = await this.mcp.callTool({
name: toolUse.name,
arguments: toolArgs,
});

// structuredContent is data the application can use directly.
if (Array.isArray(result.structuredContent)) {
finalText.push(
`[${toolUse.name} returned ${result.structuredContent.length} items]`,
);
}

// content is what the model reads.
toolResults.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: result.content as ToolResultBlockParam["content"],
is_error: result.isError === true,
});
}

Expand Down
Loading
Loading