Model Context ProtocolMCP Python SDK 2.2 · LangChain 1.4 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →

Context

The Context is an object the SDK gives a tool so it can reach the server's own resources, report progress and talk back to the client, without any of that showing up in the model's arguments.

Last updated: 29 Sep, 2026 · MCP 2.2

The Context, Lifespan, Progress and Elicitation lessons each show one feature on a small server of its own, saved in its own file: context_demo.py here. Keep shop.py as it is; the stdio lesson goes back to it.

Examplecontext_demo.py
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.

Example
import asyncio

from mcp import Client
from context_demo 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 input schema is empty. The tool was called with {} and its schema has no properties, so the model never sees ctx.
  • The policy came from the resource. ctx.read_resource read policy://refunds through the same path a client uses, so the text lives in one place.

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. See the Progress lesson.
  • ctx.request_context.lifespan_context: objects the server built at startup, such as a database. See the Lifespan lesson.
  • ctx.headers: the HTTP headers of the request, or None when the server is not reached over HTTP (the Streamable HTTP lesson).
  • ctx.session: the channel back to this client, for notifications like a changed tool list.

Context vs a normal argument

A normal argumentThe Context
In the input schemaYes, the model fills itNo, the SDK fills it
Comes fromThe model's tool callThe server and the request
Good forData the tool acts onResources, progress, headers, the session

When to reach for the Context

  • Reading one of the server's own resources inside a tool, so a value lives in one place.
  • Reporting progress on a slow tool (the Progress lesson) or reading request headers (the Streamable HTTP lesson).
  • Sending notifications back to the client through the session.
Watch out
Naming the parameter context instead of ctx works, but dropping its type annotation does not. Without Context in the annotation the SDK cannot inject it, and the value stays unfilled.
Try it yourself
  • Name the parameter context instead of ctx. Does anything change?
  • Print ctx.request_id from inside the tool.
  • Give refund_rules an order_id: str argument and include it in the answer.
PreviousPrompts

Slow is fine. Stopping is the only problem.