All posts
mcpsdk

MCP Client SDK: A Practical Guide for Full-Stack Developers

A practical guide to building your own MCP client using the SDK — connecting to servers, discovering capabilities, and driving tool-use loops.

SR

Suhail Roushan

August 6, 2026

·
5 min read
·
0 views

Most MCP content focuses on building servers, but if you're building your own AI application — not just using Claude Desktop or Cursor — you need the client side too: the code that connects to MCP servers, discovers their tools, and drives the actual tool-use loop with your model of choice.

The MCP client SDK provides the building blocks for connecting to MCP servers, discovering their available tools/resources/prompts, and invoking them — the piece your own application needs if you're building a custom AI assistant or agent that should support the same standardized MCP servers Claude Desktop, Cursor, and other clients support.

Why the MCP Client SDK Matters (and When You Don't Need It)

If you're building a custom application that lets users connect their own MCP servers — an internal AI assistant, a specialized agent product — the client SDK gives you standardized connection handling, capability discovery, and invocation, so you're not reimplementing the protocol yourself and your application can support the same broad ecosystem of existing MCP servers.

Skip building a custom MCP client if you're using an existing client application (Claude Desktop, Claude Code, Cursor) that already implements this — most developers connecting MCP servers to their workflow don't need the client SDK at all, since they're using an existing client, not building one.

Getting Started with the MCP Client SDK

Connecting to a server and listing available tools:

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

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

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

const { tools } = await client.listTools();
console.log(tools);

Invoking a tool and using the result:

const result = await client.callTool({
  name: "get_order",
  arguments: { orderId: "123" },
});

Core MCP Client SDK Concepts Every Developer Should Know

Capability discovery (listTools, listResources, listPrompts) happens after connection, letting your application dynamically learn what a server offers rather than needing hardcoded knowledge of a specific server's capabilities in advance — this is what makes MCP genuinely pluggable, since your client code can work with any conforming server without server-specific integration code.

Your application still needs to drive the actual model tool-use loop — the client SDK handles the MCP protocol side (discovering and invoking tools), but you're responsible for feeding discovered tool definitions to your model (Claude, or whichever model you're using), handling the model's decision to call a tool, invoking it via the client, and feeding the result back into the conversation.

Transport choice (stdio, Streamable HTTP) depends on where the server runs. Stdio transport is for local servers you launch as a subprocess; Streamable HTTP (or the older SSE transport, being phased out) is for remote servers accessible over the network — your client code needs the right transport for each server it connects to.

Error handling for tool calls needs to account for both protocol-level and application-level failures — a tool call might fail because the server is unreachable (protocol/transport error) or because the tool itself returned an error result (application-level failure reported within a successful protocol response) — these need different handling in your application logic.

Common Mistakes Building an MCP Client and How to Fix Them

Mistake 1: hardcoding assumptions about a specific server's tools instead of using capability discovery, defeating MCP's pluggability benefit and coupling your client to one specific server's implementation. Fix: use listTools/listResources/listPrompts to discover capabilities dynamically rather than assuming a fixed set.

Mistake 2: not distinguishing protocol-level connection failures from application-level tool errors, handling both the same way and giving users confusing or unhelpful error messages. Fix: handle transport/connection errors and tool-execution errors distinctly, with appropriate recovery or messaging for each.

Mistake 3: not properly closing connections/transports when done, leaking resources (especially relevant for stdio transport, which launches a subprocess that needs to be properly terminated). Fix: explicitly close client connections when your application no longer needs them.

When Should You Build a Custom MCP Client Instead of Using an Existing One?

Build a custom MCP client when you're building your own AI application or agent product that needs to support user-connected MCP servers, and no existing client application fits your product's specific requirements. Use an existing client (Claude Desktop, Claude Code, Cursor) when you're an end user or team wanting to connect MCP servers to your own daily workflow, where building custom client code would be pure, unnecessary overhead.

MCP Client SDK in Production

Use capability discovery rather than hardcoded assumptions about connected servers, preserving the pluggability that's MCP's core value. Also handle protocol-level and tool-level errors distinctly, and make sure transports are properly closed when connections are no longer needed, particularly for stdio-based local server subprocesses.

If you're building a custom AI application that should support the broader MCP ecosystem rather than a fixed set of hand-integrated tools, the client SDK is the right foundation — most other MCP work (as a user) doesn't need it at all.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch