Model Context Protocol Basics
10 examples to get you started connecting an existing MCP server, listing tools, and calling one tool from a thin Python client (then wiring that into an agent loop).
Read What MCP Standardizes for the protocol story. These snippets focus on client-side usage of a prebuilt server, not authoring a server from scratch.
Prerequisites
You need Python 3.10+, Node.js (for common npx reference servers), and comfort with async Python.
python -m venv .venv
source .venv/bin/activate
pip install mcp # official Python SDK; verify version at buildConcept focus: Early examples use the official MCP Python client against a local stdio server. Framework wrappers (OpenAI Agents SDK, LangGraph, and others) wrap the same ideas.
Basic Examples
1. Know the Three Roles Before You Code
- Host - your agent app or IDE
- Client - MCP library session inside the host
- Server - process that exposes tools/resources/prompts
Host (your script)
└─ Client session
└─ Server (e.g. filesystem via npx)- The model never talks to MCP directly in most designs
- Your host maps MCP tools into the model's tool list
- Side effects live in the server, not in the LLM
Related: What MCP Standardizes
2. Pick a Safe Demo Server
Start with a read-scoped local server, not a production write surface.
# Example only: official filesystem server scoped to a demo folder
mkdir -p ./mcp-demo && echo "hello from MCP" > ./mcp-demo/note.txt
npx -y @modelcontextprotocol/server-filesystem "$(pwd)/mcp-demo"- Scope paths tightly (
./mcp-demo, never$HOME) - Prefer read-only tools while learning
- Treat community servers as untrusted code until reviewed
3. Launch a Server Over stdio From Python
The client spawns the server as a subprocess and speaks JSON-RPC on stdin/stdout.
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "./mcp-demo"],
)
async def main() -> None:
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print("MCP session initialized")
asyncio.run(main())initializenegotiates capabilities and protocol version- Keep the async context open for the whole session
- Logs often go to stderr; do not parse stderr as MCP messages
4. List Tools the Server Exposes
Discovery is explicit: call list, then decide what the model may see.
async def list_tools(session: ClientSession) -> None:
result = await session.list_tools()
for tool in result.tools:
print(tool.name, "-", (tool.description or "")[:80])- Names become your host allowlist keys
- Descriptions and input schemas steer model selection later
- Cache lists only when server definitions are stable
Related: MCP's Three Primitives
5. Call One Tool and Print the Result
async def call_list_dir(session: ClientSession) -> None:
# Tool names vary by server version - list first, then call
tools = await session.list_tools()
names = {t.name for t in tools.tools}
print("available:", sorted(names))
# Common filesystem-style tool name patterns (verify against list_tools)
name = next(n for n in names if "list" in n.lower() or "directory" in n.lower())
result = await session.call_tool(name, arguments={"path": "."})
print(result)- Always bind calls to listed tool names
- Pass arguments as a dict matching the tool's input schema
- Structured content may appear as text blocks; normalize in the host
Intermediate Examples
6. Convert MCP Tools Into Model Tool Schemas
Bridge MCP discovery into ordinary function-calling shape.
def mcp_tools_to_openai_shape(tools) -> list[dict]:
out = []
for t in tools:
out.append(
{
"type": "function",
"function": {
"name": t.name,
"description": t.description or t.name,
"parameters": t.inputSchema
or {"type": "object", "properties": {}},
},
}
)
return out- Field names differ slightly by provider; keep one adapter per API
- Prefix names when multiple servers can collide (
fs_read_file) - Filter dangerous tools before they reach the model
7. Host Dispatch: Model Proposes, MCP Executes
async def run_mcp_tool(session: ClientSession, name: str, args: dict) -> dict:
allowed = {t.name for t in (await session.list_tools()).tools}
if name not in allowed:
return {"ok": False, "error": f"unknown_or_disallowed:{name}"}
try:
result = await session.call_tool(name, arguments=args)
return {"ok": True, "content": str(result)}
except Exception as exc: # narrow in production
return {"ok": False, "error": type(exc).__name__, "message": str(exc)[:300]}- Unknown names return soft errors, not process crashes
- Truncate huge payloads before re-entering model context
- Log call name, latency, and redacted args
Related: How Function Calling Actually Works
8. Tiny Agent Loop With Max Turns
import json
async def toy_loop(session: ClientSession, user: str, max_turns: int = 4) -> str:
tools = (await session.list_tools()).tools
schemas = mcp_tools_to_openai_shape(tools)
messages = [
{"role": "system", "content": "Use MCP tools for file facts. Be concise."},
{"role": "user", "content": user},
]
# Replace fake_decide with a real chat.completions call that has tools=schemas
for _ in range(max_turns):
decision = fake_decide(messages, schemas) # your model adapter
if decision["type"] == "final":
return decision["content"]
obs = await run_mcp_tool(session, decision["name"], decision["args"])
messages.append({"role": "assistant", "content": json.dumps(decision)})
messages.append({"role": "tool", "content": json.dumps(obs)})
return "Stopped: max turns reached."- Hard caps belong in host code
- Real models may emit parallel tool calls; start with one
- Frameworks automate this loop; the control points stay the same
9. Prefer Framework MCP Helpers When Available
Example shape using OpenAI Agents SDK-style stdio helper (verify APIs at build):
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main() -> None:
async with MCPServerStdio(
name="fs",
params={
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./mcp-demo"],
},
) as server:
agent = Agent(
name="File assistant",
instructions="Use MCP filesystem tools. Never invent file contents.",
mcp_servers=[server],
)
result = await Runner.run(agent, "List files you can see.")
print(result.final_output)
asyncio.run(main())- Great for production loops once you understand the raw session
- Still apply tool filters and path scoping
- See Native MCP Support in the OpenAI Agents SDK
10. Remote Servers: Streamable HTTP Sketch
# Pattern sketch - verify package imports for your SDK version
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
async def remote_main(url: str) -> None:
async with streamablehttp_client(url) as (read, write, _get_session_id):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])- Use remote transport for shared team/prod services
- Attach auth headers via client options supported by your SDK
- Prefer Streamable HTTP over legacy SSE for new servers
Related: stdio vs HTTP vs SSE
FAQs
Do I need to write a server to learn MCP?
No. Connect to a reference or vendor server first, then build your own when you need custom tools.
Why does my tool call fail after list_tools succeeds?
Wrong argument shape, wrong tool name, path outside server scope, or missing auth on remote servers.
Is stdio required?
No. It is the best local default. Remote agents use Streamable HTTP (or legacy SSE where still required).
Can I connect multiple servers?
Yes. Open multiple sessions and merge or namespace tools in the host.
Where should secrets live?
In host/server environment and secret stores, never in prompts or model-visible tool results.
Related
- What MCP Standardizes and Why It Won the Tool-Connection Wars
- MCP's Three Primitives: Tools, Resources, and Prompts
- stdio vs HTTP vs SSE: MCP's Transport Options
- Model Context Protocol Best Practices
- Building Your First MCP Server in TypeScript or Python
- Native MCP Support in the OpenAI Agents SDK
- Tool Use & Function Calling Basics
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.