mcp-client-development

verified

af3b8e89-8a86-4a7f-a7fc-6998b4869e0a

Build an MCP client — connect to stdio and streamable-HTTP servers, run the initialize handshake, list tools/resources, call tools, handle errors, and wire tools into an LLM agent loop.

Metadata

Skill ID
af3b8e89-8a86-4a7f-a7fc-6998b4869e0a
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
mcpmodel-context-protocolclientpythonsdkllmtool-useagents
Signature
verified
Integrity
OK
Content hash
b77e7375e9fefbd765a8d4ccc5f43ddf383a5f6f14b1002e0fc355b7cd426ada
Created
2026-08-13T03:21:49Z

Skill file

Raw skill file (markdown source)
# Building an MCP Client

Use when you want *your* program or LLM agent to connect to an **MCP server** and
use its tools/resources. Server-side development is covered by the
`fastmcp-mcp-server` and `mcp-server-development` skills — this one is the flip
side: how to connect, discover, and invoke.

## MCP client fundamentals

The MCP protocol is JSON-RPC 2.0 over a transport. The client:

1. **Connects** over a transport (stdio child-process for local, or
   streamable-HTTP for remote).
2. **Initializes** — an `initialize` handshake where client and server exchange
   protocol versions and capabilities, then the client sends `initialized`.
3. **Discovers** — `tools/list`, `resources/list`, `prompts/list`.
4. **Invokes** — `tools/call` with a name and arguments; returns content blocks.
5. Keeps a session for the lifetime of the connection.

The canonical request/response:

```json
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"New York"}}}
```

### Bring your own tools into the loop
- Once you know the schema (OpenAI-style `input_schema`), you map each MCP tool to
  that API, letting the agent "call" MCP tools exactly like native tools.
- When the model emits a tool call, forward it to the MCP server with
  `session.call_tool(name=..., arguments=...)`, read `result.content`, and feed
  the returned text back to the model as the tool result. Cache the content:
  most clients keep earlier tool results and provide them to the model on
  subsequent turns instead of re-reading from disk.

## Handling tool call cycles and errors
- **Error handling:** wrap the tool-call execution and surface structured errors
  to the model so it can react or retry; don't let a malformed tool blow up the
  whole loop.
- **Cancellation/timeouts:** if a model wants to stop a long-running call, send a
  `notifications/cancelled` notification for that request id. Set your own
  client-side timeout too; `CallToolResult` supports `isError` to mark a call
  that "succeeded" as an error.
- A common pattern is treating embedded content and text content as separate
  result subtypes. Check the result's `content` list for `TextContent` /
  `ImageContent` blocks and render/handle each appropriately.

## Building a minimal client in Python
The `mcp` Python SDK's `ClientSession` + `stdio_client` handles the protocol
mechanics for you:

```python
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command="python", args=["server.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()          # discover
            print([t.name for t in tools.tools])
            result = await session.call_tool("add_todo", {"title": "hi"})  # invoke
            print(result.content)

asyncio.run(main())
```

For **streamable-HTTP** remote servers use `mcp.client.streamable_http` /
`streamable_http_client` with the endpoint URL, plus an authorization header when
the server requires it.

## Capability-aware design
- Handle the case where a server declares a subset of capabilities (tools only,
  no resources) — don't assume all are present.
- Respect **pagination**: `tools/list` supports a `cursor` for large lists; loop
  until `nextCursor` is absent.
- Some servers support tool **result caching** / resource caching — take
  advantage of it to cut round-trips.
- Not all servers support prompts; feature-detect before offering.

## Pitfalls
- **Skipping initialize** — you must complete the handshake before `tools/list`;
  calling early gets protocol errors.
- **Ignoring the session lifecycle** — reuse one session for the connection
  instead of re-initializing per call.
- **Not mapping `inputSchema` correctly** when bridging to OpenAI-style tool
  params (rename key, keep `type`, `properties`, `required`).
- **Treating every server as supporting every capability.**
- **No timeout / no cancellation** on long tool calls, so a hung tool stalls the
  whole agent loop.
- **Trusting server output blindly** — validate arguments before sending and
  treat returned content as untrusted data (see `mcp-server-security`).

## Verify
- Connect to a known-good server and confirm you can list and call at least one
  tool end-to-end.
- Test a tool that errors (bad arguments) and confirm you get a structured,
  non-crashing result back that your agent can act on.
- Confirm the initialize handshake works over both stdio and streamable-HTTP with
  the servers you'll actually use.
- Test pagination/caching if the server returns a large tool list.

Attached files