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).
Search across all documentation pages
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.
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.
Host (your script)
└─ Client session
└─ Server (e.g. filesystem via npx)Related: What MCP Standardizes
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"./mcp-demo, never $HOME)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())initialize negotiates capabilities and protocol versionDiscovery 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])Related: MCP's Three Primitives
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)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 outfs_read_file)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]}Related: How Function Calling Actually Works
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."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())# 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])Related: stdio vs HTTP vs SSE
No. Connect to a reference or vendor server first, then build your own when you need custom tools.
Wrong argument shape, wrong tool name, path outside server scope, or missing auth on remote servers.
No. It is the best local default. Remote agents use Streamable HTTP (or legacy SSE where still required).
Yes. Open multiple sessions and merge or namespace tools in the host.
In host/server environment and secret stores, never in prompts or model-visible tool results.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026