Skip to Content
HomeBuild toolsCreate a tool with auth

Add user authorization to your MCP tools

Outcomes

Create and use an that requires OAuth to access Reddit, prompting to authorize the action when called. Jump to Example Code to see the complete code.

You will Learn

  • How work.
  • How to add user authorization to your custom with Arcade.
  • How to use the to make authenticated requests to external APIs.
  • How to use the Reddit to authorize your .

An is the service that issues and manages the OAuth token your uses. It is the identity “source of truth” your tool integrates with to request permissions and obtain OAuth tokens.

When you create a with requires_auth, you specify which provider to use. In this example, arcade_mcp_server.auth.Reddit specifies the Reddit .

How auth providers work during execution

  1. The authenticates once with Arcade.
  2. When the is invoked, Arcade checks whether if the has a connection to the provider with the required scopes.
  3. If the does not meet the requirements, Arcade initiates the provider-specific OAuth flow for the requested scopes.
  4. The is presented with an OAuth challenge, and authorizes Arcade to access a token with the specified scopes.
  5. The provider issues the token, and Arcade securely injects it into the tool’s .
  6. Your is executed, and uses that token to call the provider’s API (e.g., https://oauth.reddit.com), without the LLM or client ever seeing the token.

The defines where the identity lives, what permissions are available (scopes), and how tokens are issued and refreshed. In code, it’s the class you pass to requires_auth (e.g., Reddit(scopes=“read”])) that encodes the OAuth details for that service.

Add user authorization to your MCP tools

Import the necessary modules

Create a new Python file, e.g., auth_tools.py, and import the necessary modules:

Python
auth_tools.py
import sys from typing import Annotated import httpx from arcade_mcp_server import Context, MCPApp from arcade_mcp_server.auth import Reddit

Create the MCP Server

Create an instance of the MCPApp class:

Python
auth_tools.py
app = MCPApp(name="auth_example", version="1.0.0", log_level="DEBUG")

Define your MCP tool

Now, define your using the @app.tool decorator and specify the required authorization, in this case, by using Arcade’s Reddit auth provider.

Specifying the requires_auth parameter in the @app.tool decorator indicates that the needs authorization. In this example, we’re using the Reddit with the read scope:

Python
auth_tools.py
@app.tool( requires_auth=Reddit( scopes=["read"] ) ) async def get_posts_in_subreddit( context: Context, subreddit: Annotated[str, "The name of the subreddit"] ) -> dict: """Get posts from a specific subreddit""" # Normalize the subreddit name subreddit = subreddit.lower().replace("r/", "").replace(" ", "") # Prepare the httpx request # OAuth token is injected into the context at runtime. # LLMs and MCP clients cannot see or access your OAuth tokens. oauth_token = context.get_auth_token_or_empty() headers = { "Authorization": f"Bearer {oauth_token}", "User-Agent": "mcp_server-mcp-server", } params = {"limit": 5} url = f"https://oauth.reddit.com/r/{subreddit}/hot" # Make the request async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers, params=params) response.raise_for_status() # Return the response return response.json()

To use this , you need to install the Arcade CLI and run ‘arcade login’ to authenticate:

Terminal
uv pip install arcade-mcp arcade login

Or add your ARCADE_API_KEY and ARCADE_USER_ID as environment variables or an .env file at the root of your .

Arcade offers a number of built-in auth providers, including Slack, Google, and GitHub. You can also require authorization with a custom , using the OAuth2 class, a subclass of the ToolAuthorization class:

Python
@app.tool( requires_auth=OAuth2( id="your-oauth-provider-id", scopes=["scope1", "scope2"], ) )

The OAuth2 class requires an id parameter to identify the in the . For built-in providers like Slack, you can skip the id. The Arcade Engine will find the right provider using your credentials. While you can specify an id for built-in providers, only do this for private that won’t be shared.

Specify OAuth scopes

Specify the OAuth scopes you need for your . In this example, you already are using the read scope, but you can specify multiple scopes for more permissions (like identity):

Python
auth_tools.py
# Multiple scopes for more permissions @app.tool(requires_auth=Reddit(scopes=["read", "identity"])) async def identity_tool(context: Context) -> dict: """Tool that accesses user identity.""" pass

Run the MCP Server

Python
auth_tools.py
if __name__ == "__main__": # Get transport from command line argument, default to "http" transport = sys.argv[1] if len(sys.argv) > 1 else "http" print(f"Starting auth example server with {transport} transport") print("Prerequisites:") print(" 1. Install: uv pip install arcade-mcp") print(" 2. Login: arcade login") print("") # Run the server # - "http" (default): HTTP streaming for Cursor, VS Code, etc. # - "stdio": Standard I/O for Claude Desktop, CLI tools, etc. app.run(transport=transport, host="127.0.0.1", port=8000)

Run your Server using one of the with the following commands in your terminal:

Terminal
uv run auth_tools.py http

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

Configure your MCP Client(s)

Now you can connect your server to apps that support MCP Clients, like AI assistants and IDEs. :

Terminal
arcade configure claude --from-local

Try calling your inside your assistant.

Handle authorization

Since your requires authorization, the first time you use it, the (identified by user_id) needs to authorize access.

Arcade handles the authorization flow, prompting the to visit a URL to grant permissions.

Your application should guide the through this process.

How it works

Arcade manages the OAuth flow, and provides the token via context.get_auth_token_or_empty(). Arcade also remembers the ’s authorization tokens, so they won’t have to go through the authorization process again until the auth expires or is revoked.

Accessing OAuth tokens

To get the authorization token, use the context.get_auth_token_or_empty() method.

Python
auth_tools.py
# Get the token (returns empty string if not authenticated) oauth_token = context.get_auth_token_or_empty() # Use token in API requests headers = { "Authorization": f"Bearer {oauth_token}", "User-Agent": "my-app", }

Making Authenticated API Requests

Use the OAuth token with httpx or other HTTP clients:

Python
auth_tools.py
import httpx async with httpx.AsyncClient() as client: response = await client.get( "https://oauth.reddit.com/api/endpoint", headers={"Authorization": f"Bearer {oauth_token}"} ) response.raise_for_status() return response.json()

Security Best Practices

  • Never log tokens: OAuth tokens should never be logged or exposed
  • Use appropriate scopes: Request only the scopes your actually needs

Key takeaways

  • OAuth Integration Arcade handles OAuth flows and token management
  • Secure Token Injection Tokens are injected into at runtime
  • Scope Management Specify exactly which permissions your needs
  • Provider Support Multiple OAuth providers available out of the box
  • Privacy LLMs and clients never see OAuth tokens

Next steps

  • Try adding more authorized
  • Explore how to handle different authorization providers and scopes
  • Learn how to build a tool with secrets

Example Code

Python
auth_tools.py
#!/usr/bin/env python3 import sys from typing import Annotated import httpx from arcade_mcp_server import Context, MCPApp from arcade_mcp_server.auth import Reddit # Create the app app = MCPApp(name="auth_example", version="1.0.0", log_level="DEBUG") # To use this tool, you need to use the Arcade CLI (uv pip install arcade-mcp) # and run 'arcade login' to authenticate. @app.tool(requires_auth=Reddit(scopes=["read"])) async def get_posts_in_subreddit( context: Context, subreddit: Annotated[str, "The name of the subreddit"] ) -> dict: """Get posts from a specific subreddit""" # Normalize the subreddit name subreddit = subreddit.lower().replace("r/", "").replace(" ", "") # Prepare the httpx request # OAuth token is injected into the context at runtime. # LLMs and MCP clients cannot see or access your OAuth tokens. oauth_token = context.get_auth_token_or_empty() headers = { "Authorization": f"Bearer {oauth_token}", "User-Agent": "mcp_server-mcp-server", } params = {"limit": 5} url = f"https://oauth.reddit.com/r/{subreddit}/hot" # Make the request async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers, params=params) response.raise_for_status() # Return the response return response.json() # Run with specific transport if __name__ == "__main__": # Get transport from command line argument, default to "http" transport = sys.argv[1] if len(sys.argv) > 1 else "http" print(f"Starting auth example server with {transport} transport") print("Prerequisites:") print(" 1. Install: uv pip install arcade-mcp") print(" 2. Login: arcade login") print("") # Run the server # - "http" (default): HTTP streaming for Cursor, VS Code, etc. # - "stdio": Standard I/O for Claude Desktop, CLI tools, etc. app.run(transport=transport, host="127.0.0.1", port=8000)

Run your MCP server

Terminal
uv run auth_tools.py http

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

Last updated on