Lesson 3 of 6·10 min read

MCP Client Integration

An MCP server is only half the equation. The other half is the client that connects to the server, discovers available tools, and makes them available to the AI model.

Building a Client Connection

TypeScript Client

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

const transport = new StdioClientTransport({
  command: "node",
  args: ["./my-mcp-server/build/index.js"]
});

const client = new Client({
  name: "my-app",
  version: "1.0.0"
});

await client.connect(transport);

Python Client

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

server_params = StdioServerParameters(
    command="python",
    args=["my_mcp_server.py"]
)

async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await session.list_tools()

Tool Discovery

After connecting, the client automatically discovers available tools:

// List all available tools
const { tools } = await client.listTools();

for (const tool of tools) {
  console.log(`Tool: ${tool.name}`);
  console.log(`Description: ${tool.description}`);
  console.log(`Schema: ${JSON.stringify(tool.inputSchema)}`);
}

Tool Discovery Flow

Client                          Server
  │                               │
  │── initialize ────────────────▶│
  │◀── capabilities ─────────────│
  │                               │
  │── tools/list ────────────────▶│
  │◀── tool definitions ─────────│
  │                               │
  │── tools/call (get_weather) ──▶│
  │◀── result ───────────────────│

Capability Negotiation

During connection setup, client and server exchange their capabilities:

const capabilities = await client.getServerCapabilities();

// Check which features the server supports
if (capabilities.tools) {
  // Server offers tools
  const tools = await client.listTools();
}

if (capabilities.resources) {
  // Server offers resources
  const resources = await client.listResources();
}

if (capabilities.prompts) {
  // Server offers prompts
  const prompts = await client.listPrompts();
}

Connecting Tools with AI Model

The real value comes when MCP tools are available to the AI model:

import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

// Convert MCP tools to Anthropic format
const mcpTools = await client.listTools();
const anthropicTools = mcpTools.tools.map(tool => ({
  name: tool.name,
  description: tool.description,
  input_schema: tool.inputSchema
}));

// Call AI model with tools
const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  messages: [{ role: "user", content: "What's the weather in Berlin?" }],
  tools: anthropicTools
});

// Execute tool calls via MCP
for (const block of response.content) {
  if (block.type === "tool_use") {
    const result = await client.callTool({
      name: block.name,
      arguments: block.input
    });
  }
}

Error Handling

Connection Errors

try {
  await client.connect(transport);
} catch (error) {
  if (error.code === "CONNECTION_REFUSED") {
    console.error("MCP server unreachable");
  }
  // Fallback without MCP tools
}

Tool Call Errors

try {
  const result = await client.callTool({ name: "get_weather", arguments: { city: "Berlin" } });
} catch (error) {
  // Return error message to the LLM
  return { content: [{ type: "text", text: `Error: ${error.message}` }] };
}

Practical tip: Always implement fallback logic for when an MCP server is unreachable. Your application should degrade gracefully without MCP tools — not crash completely.