Build MCP tools with OAuth, deploy to the cloud, and connect them to AI agents.
This repo demonstrates how to build a full-stack AI agent workflow with Arcade:
reminder-app/— A FastAPI app with OAuth 2.0 + PKCE that manages remindersreminder_tools/— An Arcade MCP server that wraps the Reminder App API as agent-callable toolsreminder-agent/— A LangChain agent that uses the tools via Arcade Cloud, with a Flask user verifier
┌──────────────┐ ┌──────────────────┐ ┌───────────────┐
│ LangChain │────▶│ Arcade Cloud │────▶│ Reminder App │
│ Agent │ │ (MCP runtime) │ │ (FastAPI) │
└──────────────┘ └──────────────────┘ └───────────────┘
│
┌──────┴───────┐
│ OAuth + PKCE │
│ (per-user) │
└──────────────┘
When the agent calls a tool that requires OAuth, Arcade orchestrates a multi-step authorization flow across the agent, the user's browser, the custom verifier, and the reminder app:
sequenceDiagram
participant Agent as LangChain Agent
participant Arcade as Arcade Cloud
participant Browser as User's Browser
participant App as Reminder App<br/>(via ngrok)
participant Verifier as Flask Verifier<br/>(localhost:4000)
Note over Agent,Verifier: 1. Agent requests authorization
Agent->>Arcade: tools.authorize(tool_name, user_id)
Arcade-->>Agent: {status: "pending", url, auth_id}
Note over Agent: Agent prints the URL for the user<br/>and starts polling
Agent->>Arcade: auth.wait_for_completion(auth_id)
Note over Browser,App: 2. User opens the URL → OAuth consent
Browser->>App: GET /oauth/authorize (via ngrok URL)
App-->>Browser: Login form (if not logged in)
Browser->>App: POST /oauth/login
App-->>Browser: Session cookie + redirect back
App-->>Browser: Consent page
Browser->>App: POST /oauth/authorize (approve)
App-->>Browser: 302 → Arcade callback?code=xyz
Note over Arcade,App: 3. Arcade exchanges the code for tokens
Browser->>Arcade: GET /callback?code=xyz
Arcade->>App: POST /oauth/token (code + PKCE verifier)
App-->>Arcade: {access_token, refresh_token}
Note over Browser,Verifier: 4. Arcade redirects to the custom verifier
Arcade-->>Browser: 303 → /verify?flow_id=abc123
Browser->>Verifier: GET /verify?flow_id=abc123
Note over Verifier,Arcade: 5. Verifier confirms the user's identity
Verifier->>Arcade: auth.confirm_user(flow_id, user_id=email)
Arcade-->>Verifier: {auth_id}
Verifier->>Arcade: auth.wait_for_completion(auth_id)
Arcade-->>Verifier: status: "completed"
Verifier-->>Browser: Redirect → /success
Note over Agent,Verifier: 6. Agent's polling completes
Arcade-->>Agent: wait_for_completion → "completed"
Note over Agent,App: 7. Agent executes the tool
Agent->>Arcade: tools.execute(tool_name, input, user_id)
Arcade->>App: API call with Bearer token
App-->>Arcade: Response data
Arcade-->>Agent: Tool result
- Python 3.11+
- uv — fast Python package manager
- ngrok — tunnel your local server to the internet
- Arcade account — for deploying MCP tools and managing OAuth
- OpenAI API key — for the LangChain agent's LLM
Install uv if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | shInstall the Arcade CLI:
uv tool install arcade-mcp
arcade logincd reminder-app
uv syncCreate a .env file (or export the variables):
# Optional — random defaults are generated if not set
JWT_SECRET=your-jwt-secret
SESSION_SECRET=your-session-secret
PUBLIC_URL=https://your-subdomain.ngrok-free.appStart the app:
uv run uvicorn reminder_app.main:app --reload --port 8000Start the ngrok tunnel (in a separate terminal):
./tunnel.shSeed the database with demo users and reminders:
uv run seed.pyThis creates 3 users (alice, bob, charlie — password: asdf) with 10 reminders each. Or register manually via the web UI at http://localhost:8000.
- Go to the Arcade Dashboard > Auth > Providers
- Add a Custom OAuth2 provider with ID
reminder-app - Get the endpoints and client credentials from:
http://localhost:8000/api/clients/arcade-config - Copy the authorization endpoint, token endpoint, client ID, client secret, and scopes into the Arcade form
cd reminder_tools
uv syncSet the Reminder App URL as an Arcade secret:
arcade secret set REMINDER_APP_URL="https://your-subdomain.ngrok-free.app"Deploy to Arcade Cloud:
arcade deploy -e src/reminder_tools/server.py| Tool | Description | Scopes |
|---|---|---|
get_profile |
Get the authenticated user's profile | profile:read |
list_reminders |
List reminders with optional filters (priority, status, date range) | reminders:read |
search_reminders |
Search reminders by title with fuzzy matching | reminders:read |
get_reminder |
Get a single reminder by ID | reminders:read |
create_reminder |
Create a new reminder | reminders:write |
update_reminder |
Update an existing reminder | reminders:write |
mark_reminder_done |
Mark a reminder as done | reminders:write |
delete_reminder |
Delete a reminder | reminders:write |
cd reminder-agent
uv syncCreate a .env file:
ARCADE_API_KEY=your-arcade-api-key
OPENAI_API_KEY=your-openai-api-key
ARCADE_USER_ID=your-email@example.comStart the Flask verifier (in a separate terminal):
uv run verifier.pyConfigure the verifier URL in the Arcade Dashboard > Auth > Settings as:
http://localhost:4000/verify
Run the agent:
uv run main.pyYou: add a reminder to buy groceries tomorrow, high priority
You: show me all my reminders
You: delete the groceries reminder
You: what's my profile?
tool-building/
├── reminder-app/ # FastAPI app with OAuth 2.0 provider
│ ├── src/reminder_app/
│ │ ├── main.py # App entrypoint
│ │ ├── models.py # SQLAlchemy models
│ │ ├── auth/ # Session + JWT auth
│ │ ├── api/ # REST endpoints (reminders, clients)
│ │ └── oauth/ # OAuth 2.0 + PKCE provider
│ ├── tunnel.sh # ngrok tunnel script
│ └── pyproject.toml
│
├── reminder_tools/ # Arcade MCP server
│ ├── src/reminder_tools/
│ │ ├── server.py # MCPApp entrypoint
│ │ ├── client.py # HTTP client wrapper
│ │ ├── tools/ # Tool definitions (queries + commands)
│ │ └── models/ # Enums and TypedDict outputs
│ └── pyproject.toml
│
├── reminder-agent/ # LangChain agent
│ ├── main.py # Agent with Arcade tool integration
│ ├── verifier.py # Flask user verifier for Arcade auth
│ ├── globals.py # Shared config
│ └── pyproject.toml
│
└── .gitignore
