Model Context Protocol (MCP): Connect AI to Your Tools and DataConnecting hosts and building clients · Lesson 10 of 18
Building MCP clients and wiring them into your own agent
Video lecture
Building MCP clients and wiring them into your own agent
The narrated lecture is in production
Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.
Chapters
Transcript of the narration, chapter by chapter.
0:00 Building MCP clients
Hosts like Claude and IDEs cover chat and coding. But when MCP tools need to power your own product, like a reporting agent or a support backend, you need your own client. In this lesson you'll learn what a client is responsible for, use the Python SDK's client, and build a complete bridge that lets a model use tools from several MCP servers.
0:27 Why build a client?
Why build your own client when so many hosts exist? Because hosts are designed for people chatting. Your business processes, like nightly reports, ticket triage or onboarding pipelines, run without anyone typing. Think of the difference between using a taxi app and running a delivery fleet. The app is perfect for one person getting a ride. A fleet needs its own dispatch system, rules about which drivers take which jobs, and tracking. A custom MCP client is your dispatch system.
1:02 Seven client jobs
A client has seven jobs. Manage connections, local or remote, with timeouts. Discover tools, resources and prompts, caching lists sensibly. Translate MCP tool definitions into your model provider's format and results back again. Enforce policy: which servers and tools each agent may use, and approvals for writes. Namespace tools per server to avoid collisions. Treat results as untrusted and never forward tokens between servers. And trace every call.
1:32 Python v2 Client
The Python SDK version two makes connecting easy. Pass a URL to the client and it speaks Streamable HTTP. Pass standard I O server parameters, a command and arguments, and it launches a local server. Then list tools and call them, reading structured content and the error flag from each result. For authenticated HTTP, build the transport with your own HTTP client and headers, or use the SDK's OAuth support. TypeScript has the same concepts in its client package.
2:06 Bridge step 1: gather tools
Now the bridge. First, connect to every server and list its tools. Keep only the tools on your allowlist for that server, because policy belongs in your code, not in the server's annotations. Rename each tool with a server prefix, like crm double underscore find contacts, and remember which server it came from. Convert each definition into the model's tool format, which for Claude is a name, a description and an input schema.
2:38 Simple example: morning digest
A simple example. A small agency wants a morning digest: yesterday's new leads from the CRM server and yesterday's ad spend from the analytics server, summarized in three bullets and posted to their team chat. The client connects to both servers, exposes just two read tools to the model, runs one short loop, and posts the result. No chat window, no human typing, and the same two servers keep working in Claude for ad hoc questions during the day.
3:12 Bridge step 2: the loop
Then run the familiar agent loop. Send the goal and the combined tool list to the model. When it asks for a tool, look up which server owns it, call that server with the original tool name, and turn the result into a tool result: structured content as JSON if present, otherwise the text, capped in size, with the error flag preserved. Append all results in one message and loop, with a step budget.
3:44 Alternatives
The same bridge works with OpenAI or Gemini; only the tool format conversion changes. And you don't always need to write it. The Claude API's MCP connector calls remote servers for you. The OpenAI Agents SDK has MCP server classes with approval policies. The Claude Agent SDK takes MCP servers in its options, including in process ones. And Anthropic's SDK has helpers that convert MCP tools for its tool runner, though you should check compatibility with your MCP SDK version.
4:19 Example: nightly report pipeline
A worked example. An agency in Riyadh produces weekly Arabic and English reports for twenty five clients. A Python worker connects to its analytics and CRM servers, runs the bridged agent for each client with read only tools, stores each report, and posts a link to Slack. The same MCP servers also power analysts' ad hoc questions in Claude and developers' IDE workflows. Build once, use everywhere.
4:48 Pitfalls and metrics
Avoid four pitfalls. Exposing every tool without an allowlist. Name collisions across servers. Forwarding a token you received for one server to another, which the spec forbids. And ignoring the error flag, so failures get treated as data. Measure per server success rates, latency per tool, how many tokens tool definitions add, and task success on your eval set.
5:14 Client performance
What about performance when your client connects to several servers? List tools once and cache them, respecting the cache hints servers send. Run independent tool calls in parallel. Set timeouts per server, so one slow server doesn't stall the whole agent. And if a server is down, remove its tools from the list for that run and tell the model, rather than letting every call fail. Your agent stays useful even when one integration has a bad day.
5:48 Deeper: Sunday-night reports (illustrative)
Let's deepen the Riyadh reporting pipeline with illustrative numbers. Twenty five clients, Arabic and English reports every Sunday night. The worker runs the bridged agent once per client with four read only tools. The first version sent every tool from both servers to the model and averaged many more calls per report; with the allowlist, calls dropped and the reports became more consistent. When the CRM server was down for maintenance one night, the client removed its tools and the reports went out with a note, CRM data unavailable, instead of failing entirely. Account managers preferred a slightly incomplete report on time over no report at all.
6:35 Watch me do it: the bridge
Watch me do it. Let's step through the bridge. The allowed dictionary lists the read only tools I permit from each server. In run, I create two clients: one from the CRM URL, one launching the analytics server as a subprocess, and open both. For each server I list tools, skip anything not in the allowlist, build a qualified name with a double underscore, record the route back to the server and tool, and convert the definition into Claude's format with name, description and input schema. Then the loop: call the model with the combined tools. If it doesn't want a tool, return the text. For each tool use block, look up the route, call the owning server with the original tool name, and turn the result into text: structured content as JSON if present, otherwise the text parts, capped at eight thousand characters, keeping the error flag. All results go back in one user message. The loop stops after ten steps.
7:45 Try this now
Try this now. Take the bridge example from the lesson and connect it to one of your own servers. Then extend it in one direction. Either add a second model provider by writing the conversion for its tool format, or add an approval step for one write tool that pauses and asks you in the terminal before calling the server. Add a log line for every call with the server name, tool name, duration and error flag. You now have the core of a production client.
8:22 Recap
To recap: build a client when MCP must power your own software. Handle connections, discovery, translation, policy, namespacing, security and tracing, and use the bridge pattern to give any model tools from many servers. Your next step: extend the bridge to a second model provider, or add an approval step for one write tool, and trace each call with server and tool names.
Why build a client?
Hosts cover chat and IDE use. You need your own client when MCP tools must power your product or automation: a reporting agent, a support backend, a batch pipeline. A client connects to servers, lists capabilities, calls tools, reads resources and fetches prompts, and bridges them to whichever model API you use.
Client responsibilities
- Connection management: stdio subprocesses or HTTP endpoints; timeouts; reconnects.
- Discovery: list tools (and resources/prompts); cache lists using
ttlMshints where available; refresh on change notifications. - Translation: convert MCP tool definitions into your model provider's tool format, and MCP results back into tool results.
- Policy: allowlist which servers and tools each agent may use; require approval for writes; validate arguments.
- Naming: namespace tools per server to avoid collisions (
crm__find_contacts). - Security: treat tool results as untrusted input; never forward tokens between servers.
- Observability: trace every call with server, tool, duration and errors; propagate trace context in
_metawhere supported.
The Python v2 client
from mcp import Client, StdioServerParameters
# Remote (Streamable HTTP): pass a URL. Local (stdio): pass StdioServerParameters.
async with Client("https://mcp.example.com/mcp") as crm, \
Client(StdioServerParameters(command="python", args=["analytics_server.py"])) as analytics:
tools = (await crm.list_tools()).tools
result = await crm.call_tool("crm_find_contacts", {"query": "Acme"})
print(result.structured_content, result.is_error)For authenticated HTTP, build the transport with your own HTTP client and headers (the SDK provides streamable_http_client(url, http_client=...)), or use the SDK's OAuth client support for interactive flows. TypeScript offers the equivalent with Client, StreamableHTTPClientTransport and StdioClientTransport from @modelcontextprotocol/client.
Bridging MCP tools to an LLM: a complete example
The pattern: gather tools from all connected servers, convert them to your provider's schema with namespaced names, run the agent loop, and dispatch each call to the right server.
import asyncio, json, os
import anthropic
from mcp import Client, StdioServerParameters
llm = anthropic.AsyncAnthropic()
MODEL = os.environ.get("MODEL", "claude-sonnet-5")
ALLOWED = {"crm": {"crm_find_contacts", "crm_contact_brief"}, # policy: read-only tools
"analytics": {"find_campaigns", "get_performance"}}
async def run(goal: str):
servers = {
"crm": Client("https://mcp.example.com/mcp"),
"analytics": Client(StdioServerParameters(command="python", args=["analytics_server.py"])),
}
async with servers["crm"] as crm, servers["analytics"] as analytics:
clients = {"crm": crm, "analytics": analytics}
tools, route = [], {}
for sname, c in clients.items():
for t in (await c.list_tools()).tools:
if t.name not in ALLOWED[sname]:
continue
qualified = f"{sname}__{t.name}"
route[qualified] = (sname, t.name)
tools.append({"name": qualified, "description": t.description or "",
"input_schema": t.input_schema})
messages = [{"role": "user", "content": goal}]
for _ in range(10):
resp = await llm.messages.create(model=MODEL, max_tokens=4000, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
return "".join(b.text for b in resp.content if b.type == "text")
results = []
for b in resp.content:
if b.type != "tool_use":
continue
sname, tname = route[b.name]
r = await clients[sname].call_tool(tname, b.input)
text = json.dumps(r.structured_content) if r.structured_content is not None else \
"\n".join(getattr(x, "text", "") for x in r.content)
results.append({"type": "tool_result", "tool_use_id": b.id,
"content": text[:8000], "is_error": bool(r.is_error)})
messages.append({"role": "user", "content": results})
return "Stopped: step budget exhausted"
print(asyncio.run(run("Brief me on Acme and how their UAE campaigns performed last week.")))Key choices: an explicit allowlist per server (policy lives in your client, not in the server's annotations), namespaced names, capped result sizes, and errors passed through as is_error. The same bridge works for OpenAI or Gemini by changing the tool-format conversion (lesson 5 of the platform-integration course compares formats).
Alternatives to writing the bridge
- Claude API MCP connector for remote servers (the API calls the server for you).
- OpenAI Agents SDK
MCPServerStdio/MCPServerStreamableHttpwithrequire_approval. - Claude Agent SDK
mcp_serversoption (stdio, HTTP, or in-process SDK servers). - Anthropic SDK MCP helpers (
anthropic[mcp]) that convert MCP tools for the tool runner; check compatibility with your installed MCP SDK major version.
Worked example: a nightly client-report pipeline in Riyadh
An agency generates Arabic and English weekly reports for 25 clients. A Python worker connects to the analytics and CRM servers, runs the bridged agent per client with read-only tools, writes the report to storage, and posts a link to Slack. Because the tools come from MCP servers, the same servers also power analysts' ad-hoc questions in Claude and developers' IDE workflows.
Pitfalls
- Exposing every server tool to the model without an allowlist.
- Name collisions across servers (two
searchtools). - Forwarding an access token received for one server to another (forbidden token passthrough; module 5).
- Ignoring
is_errorand treating failures as data.
Measuring success
Per-server call success rate, latency per tool, tokens contributed by tool definitions, and agent task success on your eval set.
Key takeaways
- Build your own client when MCP tools must power your product or automation.
- Clients handle connections, discovery, translation to model tool formats, policy, namespacing, security and tracing.
- The Python v2 Client accepts a URL for HTTP servers or StdioServerParameters for local ones.
- Enforce an allowlist in the client and namespace tools per server.
- Hosted options exist: Claude API MCP connector, OpenAI Agents SDK, Claude Agent SDK.
Check your understanding
Quick questions to lock in the lesson. They don’t count towards your certificate.
Put it into practice
Extend the bridge example to a second model provider or add an approval step for one write tool, and trace each call with server and tool names.
Enrol for free to save your progress
Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.