Context: what a tool can reach while it runs
A tool's arguments come from the model. Everything else, like the server's own resources or a channel back to the client, comes from the Context.
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Shop support")
@mcp.resource("policy://refunds")
def refund_policy() -> str:
"""The shop's refund policy."""
return "Full refund within 30 days of delivery."
@mcp.tool()
async def refund_rules(ctx: Context) -> str:
"""Tell the model the refund rules before it promises anything."""
[policy] = await ctx.read_resource("policy://refunds")
return f"Policy: {policy.content}"A parameter annotated Context is filled in by the SDK on every request. ctx.read_resource reads one of the server's own resources through the same path a client uses, so the policy text lives in one place and both the application and the tool read it.
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
print(tools.tools[0].input_schema)
result = await client.call_tool("refund_rules", {})
print(result.content[0].text)
asyncio.run(main())The model never sees ctx. The input schema has no properties at all, and the tool is called with {}. The Context is between your function and the SDK.
The tool is async def because read_resource is awaited. A plain def tool is fine for work that does not wait on anything; the SDK runs it in a separate thread so it never blocks the server.
What else is on it
ctx.report_progress(...): tell the client how far a slow tool has got. Lesson 13.ctx.request_context.lifespan_context: objects the server built at startup, such as a database. Lesson 12.ctx.headers: the HTTP headers of the request, orNonewhen the server is not reached over HTTP (lesson 16).ctx.session: the channel back to this client, for notifications like a changed tool list.
- Name the parameter
contextinstead ofctx. Does anything change? - Print
ctx.request_idfrom inside the tool. - Give
refund_rulesanorder_id: strargument and include it in the answer.
Little by little, you're building something great.