Recall Module 9’s own closing promise — you built a real, working server. Every capability it exposes is genuinely useless until something actually connects to it. This module builds that other half.
Connecting to a real, existing server
Let’s connect directly to Module 9’s own, final server file.
We’ll open a real, live connection to the server, using the exact same FastMCP framework, this time from the client side.
import asyncio
from fastmcp import Client
async def main():
async with Client("support_server.py") as client: # launches and connects to the real server
print("Connected.") # the real, live connection is now open
asyncio.run(main())
Notice async with — recall your own async coursework’s real discipline; this genuinely ensures the connection is properly, safely closed once the block finishes, even if something inside it fails.
It’s worth being precise about what Client(...) genuinely accepts, since this course will use more than one real form. A local .py file path, like the one above, triggers a real stdio connection — launching the file as a genuine, local subprocess. A real URL, like Client("https://api.example.com/mcp"), connects over Streamable HTTP instead — recall Module 8’s own real transport distinction, inferred automatically from what you actually pass in.
Discovering what the server actually exposes
Recall Module 5’s own real tools/list — let’s call it for real this time, not just inspect its raw message shape.
async def main():
async with Client("support_server.py") as client:
tools = await client.list_tools() # a real, live discovery call
for tool in tools:
print(f"{tool.name}: {tool.description}")
asyncio.run(main())
Run this against Module 9’s own server, and you’ll see the real, actual tool — get_order_status — along with its genuine, real description, discovered live, not hardcoded anywhere in this client’s own code.
Calling a real tool
Let’s actually use the discovered capability, not just list it.
We’ll call the real, connected server’s tool directly, and inspect its genuine, real result.
async def main():
async with Client("support_server.py") as client:
result = await client.call_tool("get_order_status", {"order_id": "O1"}) # a real, live tool call
print(result)
asyncio.run(main())
Notice this genuinely triggers real, actual execution on the Server — the same ORDERS.get(...) lookup from Module 9’s own code, running live, with its real result sent back over the real connection.
Reading a real resource
Recall Module 6’s own precise distinction — let’s read real, existing context, not perform an action.
async def main():
async with Client("support_server.py") as client:
policy = await client.read_resource("policy://returns") # a real, live resource read
print(policy)
asyncio.run(main())
Using a real prompt
Recall Module 7’s own reusable templates — let’s actually request one.
async def main():
async with Client("support_server.py") as client:
prompt = await client.get_prompt("draft_response", {"order_id": "O1", "status": "Shipped"})
print(prompt) # the real, tuned prompt text, ready to send to an LLM
asyncio.run(main())
A complete, real client, tying everything together
Let’s build the genuine, full picture — a client that discovers, then uses, every real capability the server exposes.
flowchart TD
A[Connect] --> B[list_tools / list_resources / list_prompts]
B --> C{What's actually available?}
C --> D[call_tool]
C --> E[read_resource]
C --> F[get_prompt]
import asyncio
from fastmcp import Client
async def main():
async with Client("support_server.py") as client:
# discover everything real this server actually exposes
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
print(f"Found {len(tools)} tools, {len(resources)} resources, {len(prompts)} prompts.")
# actually use each real capability type
status = await client.call_tool("get_order_status", {"order_id": "O1"})
policy = await client.read_resource("policy://returns")
response_prompt = await client.get_prompt("draft_response", {"order_id": "O1", "status": "Shipped"})
print(status)
print(policy)
print(response_prompt)
asyncio.run(main())
This is genuinely the complete, real shape every Host application builds on — discover, then use, exactly the same real pattern regardless of how many capabilities a given Server actually exposes.
Common mistakes worth avoiding
Hardcoding tool names without ever calling list_tools() first. Recall this module’s own real discovery example — a genuinely robust client checks what’s actually available before assuming a specific capability exists, especially against a Server you don’t control.
Forgetting await on every real client call. Recall your LlamaIndex course’s own async-first warning — every one of these real calls is a genuine coroutine; omitting await hands you an unresolved object, not an actual result.
Not using async with, and leaving a real connection open. Recall this module’s own real example — the context manager genuinely guarantees cleanup, even when something inside the block fails; a bare, un-managed connection risks leaking real, open resources.
What you should take away from this module
- A real MCP client connects, then genuinely discovers what a server exposes, before actually using any of it.
list_tools(),list_resources(), andlist_prompts()are the real, live discovery calls;call_tool(),read_resource(), andget_prompt()are how you actually use what was discovered.async with Client(...)is the real, correct pattern — guaranteeing a connection closes properly, even on failure.
Where this goes next
The next module connects everything to what you already know deeply: Connecting Agents to MCP — wiring a real, live MCP connection directly into an agent from your LangChain, LangGraph, or LlamaIndex coursework.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed