The Context class is used by tools for both runtime capabilities and -specific data access. Tools receive a populated Context instance as a parameter. Tools should not create Context instances directly.
Outcomes
Understand how to use the Context object to access runtime capabilities and -specific data.
You will Learn
-specific data that can be accessed using the Context object:
Access OAuth token via context.get_auth_token_or_empty
Access secrets via context.get_secret
Access user_id via context.user_id
Runtime capabilities that can be accessed using the Context object:
Log messages via context.log
Request LLM sampling via context.sampling
Request elicitation via context.ui.elicit
Report progress via context.progress.report
How Context Works
When a tool that requires authorization is invoked, the server automatically:
Creates an instance of Context and populates it with the runtime capabilities and -specific data
Passes this object to your tool’s first parameter named “”
You can then use the Context object to make requests to external APIs that require an OAuth token.
Let’s walk through an example (or skip to the full code).
Context Features
Tool-Specific Data
Tool secrets
Specify required secrets using the requires_secrets argument of the @tool decorator and access them securely using the get_secret method:
Specify required authorization using the requires_auth argument of the @tool decorator and access the OAuth token securely using the get_auth_token_or_empty method:
Python
@tool(requires_auth=ClickUp())async def my_tool(context: Context) -> str: oauth_token = context.get_auth_token_or_empty() return "Do something interesting with the OAuth token"
User Info
Access information about the user that authorized the using the authorization.user_info attribute. Note that this is only available if the tool’s supports it (e.g., the LinkedIn auth provider provides the ID):
Python
user_info = context.authorization.user_info
Runtime Capabilities
Logging
Logging in MCP provides a standardized channel for servers to emit structured, leveled messages to clients. Logging is useful for observability, debugging, and user feedback during .
You can send log messages at different levels using the log attribute of the Context object like this:
Sampling in MCP is a way for servers to request LLM sampling (“completions” or “generations”) from language models via clients. This flow allows clients to maintain control over model access, selection, and permissions while enabling servers to leverage AI capabilities—with no server API keys necessary.
Elicitation in MCP is a way for servers to request additional information from users through the client during interactions. This flow allows clients to maintain control over user interactions and data sharing while enabling servers to gather necessary information dynamically.
Python
elicitation_schema = {"type": "object", "properties": {"nickname": {"type": "string"}}}await context.ui.elicit("What is your nickname?", elicitation_schema)
Progress Reporting
Progress reporting in MCP is a way for servers to report progress for long-running operations to clients.
For the code to work, you must define your environment variables in a .env file at the same directory as your entrypoint file.
Python
server.py
#!/usr/bin/env python3import sysfrom typing import Annotatedfrom arcade_mcp_server import Context, MCPApp# Create the MCP applicationapp = MCPApp( name="context_example", version="1.0.0", instructions="Example server demonstrating Context usage",)@app.tool(requires_secrets=["API_KEY"])async def secure_api_call( context: Context, endpoint: Annotated[str, "API endpoint to call"], method: Annotated[str, "HTTP method (GET, POST, etc.)"] = "GET",) -> Annotated[str, "API response or error message"]: """Make a secure API call using secrets from context.""" # Access secrets from environment via Context helper try: api_key = context.get_secret("API_KEY") except ValueError: await context.log.error("API_KEY not found in environment") return "Error: API_KEY not configured" # Log the API call await context.log.info(f"Making {method} request to {endpoint}") # Simulate API call (in real code, use httpx) return f"Successfully called {endpoint} with API key: {api_key[:4]}..."# Don't forget to add the secret to the .env file or export it as an environment variable@app.tool(requires_secrets=["DATABASE_URL"])async def database_info( context: Context, table_name: Annotated[str | None, "Specific table to check"] = None) -> Annotated[str, "Database connection info"]: """Get database connection information from context.""" # Get database URL from secrets try: db_url = context.get_secret("DATABASE_URL") except ValueError: db_url = "Not configured" if table_name: return f"Database: {db_url}\nChecking table: {table_name}" return f"Database: {db_url}"@app.toolasync def logging_tool(context: Context, message: Annotated[str, "The message to log"]) -> str: """Log a message at varying levels.""" await context.log.log("debug", f"Debug via log.log: {message}") await context.log.debug(f"Debug via log.debug: {message}") await context.log.info(f"Info via log.info: {message}") await context.log.warning(f"Warning via log.warning: {message}") await context.log.error(f"Error via log.error: {message}") return "Logged!"@app.toolasync def reporting_progress(context: Context) -> str: """Report progress back to the client""" total = 5 for i in range(total): await context.progress.report(i + 1, total=total, message=f"Step {i + 1} of {total}") return "All progress reported successfully"@app.toolasync def sampling( context: Context, text: Annotated[str, "The text to be summarized by the client's model"]) -> str: """Summarize the text using the client's model.""" result = await context.sampling.create_message( messages=text, system_prompt=( "You are a helpful assistant that summarizes text. " "Given a text, you should summarize it in a few sentences." ), ) return result.text@app.toolasync def elicit_nickname(context: Context) -> str: """Ask the end user for their nickname, and then use it to greet them.""" elicitation_schema = {"type": "object", "properties": {"nickname": {"type": "string"}}} result = await context.ui.elicit( "What is your nickname?", schema=elicitation_schema, ) if result.action == "accept": return f"Hello, {result.content['nickname']}!" elif result.action == "decline": return "User declined to provide a nickname." elif result.action == "cancel": return "User cancelled the elicitation." return "Unknown response from client"if __name__ == "__main__": # Check if stdio transport was requested transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" # Run the server app.run(transport=transport, host="127.0.0.1", port=8000)