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 .
Prerequisites
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
- The authenticates once with Arcade.
- When the is invoked, Arcade checks whether if the has a connection to the provider with the required scopes.
- If the does not meet the requirements, Arcade initiates the provider-specific OAuth flow for the requested scopes.
- The is presented with an OAuth challenge, and authorizes Arcade to access a token with the specified scopes.
- The provider issues the token, and Arcade securely injects it into the tool’s .
- 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:
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:
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:
@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:
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:
@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
):
# 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
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:
HTTP transport (default)
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. :
Claude Desktop
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.
# 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:
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
#!/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
HTTP transport (default)
uv run auth_tools.py http
For HTTP transport, view your server’s API docs at http://127.0.0.1:8000/docs .