Skip to Content
HomeBuild toolsTools and Context

Understanding Context and tools

Context is a class in Arcade that manages authorization and user information for requiring authentication. Context provides access to the following runtime features:

  • Logging via context.log
  • Secrets via context.get_secret
  • User via context.user_id—this is the user ID of the user invoking the
  • Progress reporting via context.progress.report
  • Decorator options

Some authorization providers may also include additional structured information in the ToolContext.

How Context Works

When you invoke a that requires authorization, Arcade’s Tool SDK automatically:

  1. Creates a Context object
  2. Passes this object to your

You can then use the Context object to make authenticated requests to external APIs.

Let’s walk through an example (or skip to the full code).

Context Features

Context provides access to runtime features:

Logging

Send log messages at different levels using the log attribute of the Context object:

Python
await context.log.debug("Debug message") await context.log.info("Information message") await context.log.warning("Warning message") await context.log.error("Error message")

Secrets Management

Access environment variables securely using the get_secret method:

Python
try: api_key = context.get_secret("API_KEY") except ValueError: # Handle missing secret

User Context

Access information about the current using the user_id attribute:

Python
user_id = context.user_id or "anonymous"

Progress Reporting

Report progress for long-running operations using the progress attribute:

Python
await context.progress.report(current, total, "Processing...")

Tool Decorator Options

Specify required secrets using the requires_secrets decorator:

Python
@tool(requires_secrets=["DATABASE_URL", "API_KEY"]) async def my_tool(context: Context, ...):

Example Code

The following is an example shows how can access runtime features through Context, including logging, secrets, and progress reporting.

Environment Variables

ENV
API_KEY="your-secret-key" DATABASE_URL="postgresql://localhost/mydb"

For the code to work, you must define your environment variables in a .env file.

Python
server.py
#!/usr/bin/env python3 import sys from typing import Annotated, Any from arcade_mcp_server import Context, MCPApp # Create the MCP application app = MCPApp( name="context_example", version="1.0.0", instructions="Example server demonstrating Context usage", ) @app.tool 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 or aiohttp) 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" # Log at different levels if db_url == "Not configured": await context.log.warning("DATABASE_URL not set") else: await context.log.debug(f"Checking database: {db_url.split('@')[-1]}") # Get user info user_info = f"User: {context.user_id or 'anonymous'}" if table_name: return f"{user_info}\nDatabase: {db_url}\nChecking table: {table_name}" else: return f"{user_info}\nDatabase: {db_url}" @app.tool async def debug_context( context: Context, show_secrets: Annotated[bool, "Whether to show secret keys (not values)"] = False, ) -> Annotated[dict, "Current context information"]: """Debug tool to inspect the current context.""" info: dict[str, Any] = { "user_id": context.user_id, } if show_secrets: # Only show keys, not values for security info["secret_keys"] = [s.key for s in (context.secrets or [])] # Log that debug info was accessed await context.log.info(f"Debug context accessed by {context.user_id or 'unknown'}") return info @app.tool async def process_with_progress( context: Context, items: Annotated[list[str], "Items to process"], delay_seconds: Annotated[float, "Delay between items"] = 0.1, ) -> Annotated[dict, "Processing results"]: """Process items with progress notifications.""" results: dict[str, list] = {"processed": [], "errors": []} # Log start await context.log.info(f"Starting to process {len(items)} items") for i, item in enumerate(items): try: # Simulate processing import asyncio await asyncio.sleep(delay_seconds) # Report progress (current, total, message) await context.progress.report(i + 1, len(items), f"Processing: {item}") await context.log.debug(f"Processing item {i + 1}/{len(items)}: {item}") results["processed"].append(item.upper()) except Exception as e: await context.log.error(f"Failed to process {item}: {e}") results["errors"].append({"item": item, "error": str(e)}) # Log completion await context.log.info( f"Processing complete: {len(results['processed'])} succeeded, " f"{len(results['errors'])} failed" ) return results # The Context provides at runtime (via TDK wrapper): # - context.user_id: ID of the user making the request # - context.get_secret(key): Retrieve a secret value (raises if missing) # - context.log.<level>(msg): Send log messages to the client (debug/info/warning/error) # - context.progress.report(progress, total=None, message=None): Progress updates if __name__ == "__main__": # Check if stdio transport was requested transport = "stdio" if len(sys.argv) > 1 and sys.argv[1] == "stdio" else "http" print(f"Starting {app.name} v{app.version}") print(f"Transport: {transport}") # Run the server app.run(transport=transport, host="127.0.0.1", port=8000)

Run your MCP server

Terminal
uv run server.py

For HTTP transport, view your server’s API docs at http://127.0.0.1:8000/docs .

Key Concepts

  • Parameter receive a Context as their first parameter
  • Async Functions Use async def for tools that use features
  • Secure Secrets Secrets are accessed through , not hardcoded
  • Structured Logging Log at appropriate levels for debugging
  • Progress Updates Keep informed during long operations

Next Steps

Last updated on