diff --git a/app/en/home/build-tools/_meta.tsx b/app/en/home/build-tools/_meta.tsx index 88373013..74349d63 100644 --- a/app/en/home/build-tools/_meta.tsx +++ b/app/en/home/build-tools/_meta.tsx @@ -7,5 +7,7 @@ export default { "providing-useful-tool-errors": "Providing useful tool errors", "retry-tools-with-improved-prompt": "Retry tools with improved prompt", "call-tools-from-mcp-clients": "Call tools from MCP clients", + "secure-your-mcp-server": "Secure Your MCP Server with OAuth", + "server-level-vs-tool-level-auth": "Server-Level vs Tool-Level Authorization", "migrate-from-toolkits": "Migrate from toolkits", }; diff --git a/app/en/home/build-tools/secure-your-mcp-server/page.mdx b/app/en/home/build-tools/secure-your-mcp-server/page.mdx new file mode 100644 index 00000000..41d1cf8c --- /dev/null +++ b/app/en/home/build-tools/secure-your-mcp-server/page.mdx @@ -0,0 +1,480 @@ +--- +title: "Adding Resource Server Authentication" +description: "Secure your HTTP MCP server with OAuth 2.1 Resource Server authentication" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +# Adding Resource Server Authentication to Your MCP Server + +Resource Server authentication enables your HTTP MCP server to act as an OAuth 2.1 Protected Resource (compliant with [MCP's specification for Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)), validating Bearer tokens on every request. This unlocks support for tool-level authorization and secrets on HTTP servers, allowing you to host secure MCP servers anywhere (local, on-premise, or third-party hosted). + + + + +Add [MCP compliant OAuth 2.1 front-door authentication](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) to your HTTP MCP server to enable tool-level authorization and secrets. + + + + + +- An existing MCP server created with `arcade new` (see [Create an MCP Server](/home/build-tools/create-a-mcp-server)) +- Understanding of [MCP Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- An OAuth 2.1 compliant authorization server (e.g., WorkOS AuthKit, Auth0, Descope, etc.) +- Authorization server's JWKS endpoint URL + + + + + +- What Resource Server authentication is and why it's needed +- How to configure your MCP server to validate OAuth tokens +- How to support multiple authorization servers +- How to use environment variables for production deployments + + + + +## Understanding Resource Server Authentication + +### What is it? + +Resource Server authentication turns your MCP server into an [OAuth 2.1 Protected Resource](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles) that validates Bearer tokens on every HTTP request. Your MCP server trusts one or more [Authorization Servers](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles) to issue valid tokens for accessing your MCP server. + +### Why is it needed? + +By default, HTTP MCP servers cannot use tools that require authorization or secrets for security reasons. + +Resource Server authentication solves this by: + +1. **Authenticating every request** - Validates the Bearer token before processing any MCP messages +2. **Extracting user identity** - The token's `sub` claim becomes the `context.user_id` for tool execution +3. **Enabling secure tools** - Tools requiring authorization or secrets can now safely execute over HTTP +4. **Supporting OAuth discovery** - MCP clients can automatically discover your authentication requirements + + +**Resource Server auth vs Tool-level auth**: Resource Server authentication secures _access to your MCP server_, while tool-level authorization secures _access to third-party APIs that your tools use_. They work together: Resource Server auth identifies _who_ is calling your server, and tool-level auth enables tools to act _on behalf of that user_. + + + + +## Choose Your Configuration Approach + +The `arcade_mcp_server.resource_server` module provides two validators: + + + + +**`ResourceServer`** - Full-featured OAuth 2.1 Resource Server with: +- Support for multiple authorization servers (multi-IdP, regional endpoints) +- OAuth discovery metadata endpoint +- Environment variable configuration +- Best for production deployments + + + + +**`JWKSTokenValidator`** - Direct JWKS-based validation with: +- Simple setup for single authorization server +- No OAuth discovery endpoint +- Requires explicit configuration +- Best for development or simple use cases + + + + +## Configure Your Authorization Server + +First, gather these details from your authorization server: + +- **Authorization Server URL** - The base URL of your authorization server (e.g., `https://your-app.authkit.app`) +- **Issuer** - The expected `iss` claim in tokens (usually same as authorization server URL) +- **JWKS URI** - Where to fetch public keys for token verification (e.g., `https://your-app.authkit.app/oauth2/jwks`) +- **Canonical URL** - Your MCP server's public URL (e.g., `http://127.0.0.1:8000/mcp` if running locally) + + +The **Canonical URL** must match the `aud` (audience) claim in tokens issued by your authorization server. Configure your authorization server to issue tokens with your MCP server's URL as the audience. + + +## Add Authentication to Your Server + +Update your `server.py` to add the `auth` parameter to `MCPApp`: + + + + +```python filename="server.py" {5-9,11-26,32} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ( + AccessTokenValidationOptions, + AuthorizationServerEntry, + ResourceServer, +) + +# Setup your resource server that trusts a single Authkit authorization server +resource_server = ResourceServer( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-workos.authkit.app", + issuer="https://your-workos.authkit.app", + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + algorithm="RS256", + # Authkit doesn't set the aud claim as the MCP server's canonical URL + validation_options=AccessTokenValidationOptions( + verify_aud=False, + ), + ) + ], +) + +# Pass the resource_server to MCPApp +app = MCPApp( + name="my_server", + version="1.0.0", + auth=resource_server # Enable Resource Server authentication +) + +# Your tools here... +@app.tool +def greet(name: Annotated[str, "The name of the person to greet"]) -> str: + """Greet a person by name.""" + return f"Hello, {name}!" + +if __name__ == "__main__": + app.run(transport="http", host="127.0.0.1", port=8000) +``` + + + + +```python filename="server.py" {10-29} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ( + ResourceServer, + AuthorizationServerEntry, +) + +# Support multiple authorization servers (multi-IdP) +resource_server = ResourceServer( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-workos.authkit.app", + issuer="https://your-workos.authkit.app", + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + # Authkit doesn't set the aud claim as the MCP server's canonical URL + validation_options=AccessTokenValidationOptions( + verify_aud=False, + ), + ), + AuthorizationServerEntry( + authorization_server_url="https://your-github.com/login/oauth", + issuer="https://github.com", + jwks_uri="https://your-github.com/token.actions.githubusercontent.com/.well-known/jwks", + ), + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server) +``` + + +Multiple authorization servers enable scenarios like: +- **Multi-IdP**: Accept tokens from WorkOS _and_ GitHub +- **Regional endpoints**: Multiple authorization server URLs with shared keys +- **Migration**: Smoothly transition between authorization servers + + + + + +```python filename="server.py" {5,8-9} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ResourceServer + +# Configuration loaded from environment variables +# No parameters needed! +resource_server = ResourceServer() + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server) +``` + +Create a `.env` file: + +```bash filename=".env" +MCP_SERVER_AUTH_CANONICAL_URL=http://127.0.0.1:8000/mcp +MCP_SERVER_AUTH_AUTHORIZATION_SERVERS='[ + { + "authorization_server_url": "https://your-workos.authkit.app", + "issuer": "https://your-workos.authkit.app", + "jwks_uri": "https://your-workos.authkit.app/oauth2/jwks", + "algorithm": "RS256", + "verify_options": { + "verify_aud": false + } + } +]' +``` + + +Environment variable configuration is **recommended for production** as it separates auth configuration from your code and allows deployment-time configuration. + + + + + +```python filename="server.py" {5,7-12,15} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import JWKSTokenValidator + +# Configure JWKS token validation +validator = JWKSTokenValidator( + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + issuer="https://your-workos.authkit.app", + audience="http://127.0.0.1:8000/mcp", +) + +app = MCPApp( + name="my_server", + version="1.0.0", + auth=validator +) + +# Your tools here... +``` + + + + +## Run Your Authenticated Server + +Start your server with HTTP transport: + +```bash +uv run server.py +``` + +Your server now requires valid Bearer tokens for all requests. You should see output like: + +```bash +INFO | 14:23:45 | Starting my_server v1.0.0 with 3 tools +INFO | 14:23:45 | Resource Server authentication enabled: True +INFO | 14:23:45 | Accepted authorization server(s): https://your-app.authkit.app +``` + +## OAuth Discovery +Now that your server is protected, you can see that your server exposes an OAuth discovery endpoint at `http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource`. This endpoint is used by MCP clients to discover the authorization servers that are trusted by your server. + +```bash +curl http://127.0.0.1:8000/.well-known/oauth-protected-resource +``` + +You should see a response like: + +```http +{ + "resource":"http://127.0.0.1:8000/mcp", + "authorization_servers":["https://your-workos.authkit.app"] +} +``` + + +MCP clients can use this endpoint to automatically discover which authorization servers issue valid tokens for your server. + + +## Verify Your Server is Protected + +Try calling your server without a token: + +```bash +curl -i http://127.0.0.1:8000/mcp/ +``` + +You should receive a 401 response with `WWW-Authenticate` header: + +```http {4} +HTTP/1.1 401 Unauthorized +date: Tue, 02 Dec 2025 01:00:54 GMT +server: uvicorn +www-authenticate: Bearer, resource_metadata="http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource" +access-control-allow-origin: * +access-control-allow-methods: GET, POST, DELETE +access-control-allow-headers: Content-Type, Authorization, Mcp-Session-Id +access-control-expose-headers: WWW-Authenticate, Mcp-Session-Id +content-length: 12 + +Unauthorized +``` + +### Test Your Server with a Valid Token + +The easiest way to test your secure server by using the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) as your client & connecting to your server from it. + +
+ +
+ + +
+ +## Advanced Configuration + +### Custom Token Validation Options + +Disable specific validations when needed: + +```python +from arcade_mcp_server.resource_server import ( + ResourceServer, + AuthorizationServerEntry, + AccessTokenValidationOptions, +) + +resource_server = ResourceServer( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-app.authkit.app", + issuer="https://your-app.authkit.app", + jwks_uri="https://your-app.authkit.app/oauth2/jwks", + validation_options=AccessTokenValidationOptions( + verify_aud=False, # Disable audience validation + verify_exp=True, # Still verify expiration (default) + verify_iat=True, # Still verify issued-at (default) + verify_iss=True, # Still verify issuer (default) + ), + ) + ], +) +``` + + +**Security Note**: Token signature verification is always enabled and cannot be disabled. Additionally, the `sub` claim must always be present. Only disable other validations if your authorization server doesn't comply with MCP and you accept the risk of not validating all claims in the token. + + +### Different JWT Algorithms + +```python +AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + algorithm="ES256", # Use ECDSA instead of RSA +) +``` + +Supported algorithms: `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512` + +### Regional Authorization Servers with Shared Keys + +```python +resource_server = ResourceServer( + canonical_url="https://mcp.example.com", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth-us.example.com", + issuer="https://auth.example.com", # Same issuer + jwks_uri="https://auth.example.com/jwks", # Shared JWKS + ), + AuthorizationServerEntry( + authorization_server_url="https://auth-eu.example.com", + issuer="https://auth.example.com", # Same issuer + jwks_uri="https://auth.example.com/jwks", # Shared JWKS + ), + ], +) +``` + +## How It Works + +1. **Client makes request** with `Authorization: Bearer ` header +2. **Middleware intercepts** every HTTP request before MCP processing +3. **Token validation** occurs: + - Fetches JWKS from authorization server + - Verifies token signature + - Checks expiration, issuer, and audience + - Extracts `sub` claim as user ID +4. **Resource owner stored** in request context +5. **MCP processing continues** with authenticated user +6. **Tools execute** with `context.user_id` set from token's `sub` claim + +### Security Features + +- ✅ **No token caching** - Every request validates the token fresh (per MCP spec) +- ✅ **JWKS caching** - Public keys cached for performance (default 1 hour) +- ✅ **Algorithm enforcement** - Prevents algorithm confusion attacks +- ✅ **Signature verification** - Always enabled, cannot be disabled +- ✅ **RFC 6750 compliant** - Standard OAuth 2.0 Bearer token usage +- ✅ **RFC 9728 compliant** - OAuth 2.0 Protected Resource Metadata + +## Common Authorization Server Configurations + +### WorkOS AuthKit + +```python +AuthorizationServerEntry( + authorization_server_url="https://your-app.authkit.app", + issuer="https://your-app.authkit.app", + jwks_uri="https://your-app.authkit.app/oauth2/jwks", + validation_options=AccessTokenValidationOptions( + # Authkit doesn't set the aud claim as the MCP server's canonical URL + verify_aud=False, + ), +) +``` + +### Auth0 + +```python +AuthorizationServerEntry( + authorization_server_url="https://your-tenant.auth0.com", + issuer="https://your-tenant.auth0.com/", + jwks_uri="https://your-tenant.auth0.com/.well-known/jwks.json", +) +``` + +### Descope + +```python +AuthorizationServerEntry( + authorization_server_url="https://api.descope.com", + issuer="https://api.descope.com/{project-id}", + jwks_uri="https://api.descope.com/{project-id}/.well-known/jwks.json", +) +``` + +### GitHub Actions + +```python +AuthorizationServerEntry( + authorization_server_url="https://github.com/login/oauth", + issuer="https://token.actions.githubusercontent.com", + jwks_uri="https://token.actions.githubusercontent.com/.well-known/jwks", +) +``` + +## Next Steps + +- **Let Arcade secure your server instead**: [Learn about `arcade deploy`](/home/serve-tools/arcade-deploy) +- **Build tools with authorization**: [Create tools that use OAuth](/home/build-tools/create-a-tool-with-auth) +- **Use secrets securely**: [Create tools with secrets](/home/build-tools/create-a-tool-with-secrets) diff --git a/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx b/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx new file mode 100644 index 00000000..eb613ff0 --- /dev/null +++ b/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx @@ -0,0 +1,219 @@ +--- +title: "Server-Level vs Tool-Level Authorization" +description: "Understanding the difference between Resource Server authentication and tool-level authorization in Arcade MCP servers" +--- + +import { Callout } from "nextra/components"; + +# Server-Level vs Tool-Level Authorization + +Arcade MCP servers support two distinct layers of authorization that work together to provide comprehensive security. Understanding the difference is crucial for building secure, production-ready MCP servers. + +## Quick Comparison + +| Aspect | [Resource Server Auth (Front-Door)](/home/build-tools/secure-your-mcp-server) | [Tool-Level Authorization](/home/build-tools/create-a-tool-with-auth) | +|--------|-----------------------------------|--------------------------| +| **What it secures** | Access to your MCP server | Access to third-party APIs | +| **Who authenticates** | The user calling your server | The user's access to external services | +| **When it happens** | Every HTTP request to your server | When a tool calls an external API | +| **Token source** | Authorization Server (e.g., WorkOS, Auth0) | Arcade authorization platform | +| **Required for** | HTTP servers in production | Tools that access user data from APIs | +| **Configuration** | `MCPApp(auth=resource_server)` | `@app.tool(requires_auth=GitHub(...))` | + +## Resource Server Authentication (Server-Level) + +Resource Server authentication (also called "front-door auth") validates Bearer tokens on **every HTTP request** to your MCP server. The end user only needs to go through the OAuth flow once. Afterwards, the MCP client will send the token in the Authorization header for every request to your MCP server. Your MCP server acts as an OAuth 2.1 Protected Resource. + +Resource Server authentication ensures every request identifies the caller. It blocks unauthenticated requests at the door, so only users with valid tokens can access your MCP server. This security lets you run tools that require authorization or secrets over HTTP. + +### When You Need It + +✅ **You need Resource Server auth if:** +- You've determined that [arcade deploy](/home/serve-tools/arcade-deploy) is not a good fit for your use case +- You're running an HTTP MCP server in production +- Your server has tools that require authorization or secrets +- You need to identify which user is calling your server +- You want to control who can access your MCP server + +❌ **You don't need it if:** +- You're using [arcade deploy](/home/serve-tools/arcade-deploy) to secure your server +- You're using stdio transport +- Your server only has public tools (no auth/secrets required) +- You're doing local development only + +### Example + +```python filename="server.py" showLineNumbers +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ResourceServer, AuthorizationServerEntry + +# Configure who can access your MCP server +resource_server = ResourceServer( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + ) + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server) +``` + + +**Result**: Only users with valid tokens from `https://auth.example.com` can call ANY tools on your server. + + +## Tool-Level Authorization + +Tool-level authorization enables individual tools to access third-party APIs on behalf of the authenticated user. Arcade manages the OAuth flow and token storage. + +Tool-level authorization lets your tools authenticate to external APIs using OAuth tokens for services like Gmail or GitHub. Each tool acts on behalf of the user by using their connected accounts, and requests only the scopes it needs. Arcade manages the entire OAuth flow, including token refresh and secure storage, so you don't have to handle these details yourself. + +### When You Need It + +✅ **You need tool-level auth if:** +- Your tool calls external APIs (Gmail, GitHub, Slack, etc.) that require user-specific OAuth tokens +- You want to access user data from third-party services +- The tool needs to act on behalf of the user + +❌ **You don't need it if:** +- Your tool doesn't call external APIs +- The API uses API keys instead of OAuth +- The tool accesses public data only + +### Example + +```python filename="server.py" showLineNumbers +from typing import Annotated + +from arcade_mcp_server import Context, MCPApp +from arcade_mcp_server.auth import GitHub +import httpx + +app = MCPApp(name="my_server", version="1.0.0") + +# This tool requires GitHub auth +@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) +async def create_github_issue( + context: Context, + repo: Annotated[str, "The repository to create the issue in"], + title: Annotated[str, "The title of the issue"], + body: Annotated[str, "The body of the issue"], +) -> Annotated[dict, "The created issue"]: + """Create a GitHub issue""" + # Arcade provides the OAuth token for this user in the context + token = context.get_auth_token_or_empty() + + headers = {"Authorization": f"Bearer {token}"} + url = f"https://api.github.com/repos/{repo}/issues" + + async with httpx.AsyncClient() as client: + response = await client.post( + url, + headers=headers, + json={"title": title, "body": body} + ) + return response.json() + +if __name__ == "__main__": + app.run(transport="stdio") +``` + + +**stdio transport doesn't need Resource Server auth** because the connection is local and doesn't go over the network. + + +## How They Work Together + +The two authorization layers complement each other. Below is an example of a +protected HTTP server with both server-level and tool-level authorization. + +```python filename="server.py" {9-18, 20, 24, 33, 48} showLineNumbers +from typing import Annotated + +import httpx +from arcade_mcp_server import Context, MCPApp +from arcade_mcp_server.auth import GitHub +from arcade_mcp_server.resource_server import AuthorizationServerEntry, ResourceServer + +# Configure who can access your MCP server +resource_server = ResourceServer( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + ) + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server) + + +# This tool requires GitHub auth +@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) +async def create_github_issue( + context: Context, + repo: Annotated[str, "The repository to create the issue in"], + title: Annotated[str, "The title of the issue"], + body: Annotated[str, "The body of the issue"], +) -> Annotated[dict, "The created issue"]: + """Create a GitHub issue""" + # Arcade provides the OAuth token for this user in the context + token = context.get_auth_token_or_empty() + + headers = {"Authorization": f"Bearer {token}"} + url = f"https://api.github.com/repos/{repo}/issues" + + async with httpx.AsyncClient() as client: + response = await client.post( + url, + headers=headers, + json={"title": title, "body": body}, + ) + return response.json() + + +if __name__ == "__main__": + app.run(transport="http") + +``` + +**Flow:** +1. MCP client sends request with Bearer token to `http://127.0.0.1:8000/mcp` +2. Resource Server middleware validates token → extracts `user_id` from `sub` claim +3. MCP processes tool call with authenticated user context +4. Tool requests GitHub token from Arcade for this `user_id` +5. Tool uses GitHub token to call GitHub API +6. Response returns to MCP client + +## Common Questions + +### Q: Can I use tool-level auth without Resource Server auth? + +**A:** Yes, but only for stdio transport or when using [arcade deploy](/home/serve-tools/arcade-deploy) (Arcade will protect your MCP server for you). + +### Q: Do I need Resource Server auth for local development? + +**A:** No, you can use stdio transport for local development. Resource Server auth is primarily for production HTTP servers. + +### Q: Does Resource Server auth replace tool-level auth? + +**A:** No, they serve different purposes. Resource Server auth secures _your server_, tool-level auth secures _external APIs_. + +### Q: Can I have different authorization servers for different tools in the same server? + +**A:** No. Resource Server auth applies to the entire server. However, you can accept tokens from multiple authorization servers (multi-IdP). + +## Key Takeaways + +- **Resource Server auth secures your MCP server** - Controls who can call your tools +- **Tool-level auth secures external APIs** - Controls what your tools can access +- **They work together** - Resource Server provides user identity, tool-level provides API access +- **HTTP requires Resource Server auth** - For tools with auth/secrets in production +- **stdio doesn't need Resource Server auth** - Local connections are already secure +- **Choose based on transport and requirements** - Different scenarios need different combinations diff --git a/app/en/home/serve-tools/securing-arcade-mcp/page.mdx b/app/en/home/serve-tools/securing-arcade-mcp/page.mdx index 4b3a8a28..86b6dc91 100644 --- a/app/en/home/serve-tools/securing-arcade-mcp/page.mdx +++ b/app/en/home/serve-tools/securing-arcade-mcp/page.mdx @@ -2,7 +2,7 @@ You may have noticed that when you connected to the MCP serer you created with `arcade-mcp`, you could immediately call your tools from local MCP Clients and agents, like Claude and Cursor. This is because the `arcade-mcp` server is *not* secured by any mechanism by default. Most use-cases for MCP servers today are local development or local to a single machine, and we optimize for that use-case. -However, you can secure your MCP server by deploying it to Arcade (available today) or using OAuth (coming soon). +However, you can secure your MCP server in two ways: deploying it to Arcade or adding front-door OAuth. ## Arcade Deploy When you `arcade deploy` your MCP server, it will be secured behind the Arcade platform. @@ -11,5 +11,18 @@ Under the hood, we disable the MCP routes provided by `arcade-mcp`, and use the Learn more about how to deploy your MCP server to Arcade [here](/home/serve-tools/arcade-deploy). -## OAuth (Coming soon) -Coming soon, you will be able to secure your MCP server's `/mcp` endpoints with a OAuth Authorization Server (AS) - either using Dynamic Client Registration (DCR) or Client ID Metadata Documents (CIMD). Learn more about how MCP integrates with OAuth [here](https://blog.modelcontextprotocol.io/posts/client_registration/). +## OAuth Resource Server Authentication + +You can secure your MCP server's `/mcp` endpoints with OAuth 2.1 Resource Server authentication. This turns your MCP server into a protected resource, enabling you to use tools with authorization and secrets over HTTP. + +This approach is ideal when: +- You want to host your MCP server yourself (local, on-premise, or third-party hosting) +- You already have an OAuth 2.1 compliant Authorization Server (e.g., WorkOS AuthKit, Auth0, Descope) + +Resource Server authentication works alongside tool-level authorization. Resource Server auth secures access to your MCP server itself, while tool-level auth enables your tools to access third-party APIs on behalf of the authenticated user. + +Learn more about adding front-door OAuth to your MCP server [here](/home/build-tools/secure-your-mcp-server). + +### Client ID Metadata Documents (Coming soon) + +Coming soon, you will be able to secure your MCP server using Client ID Metadata Documents (CIMD) for authorization. Learn more about how MCP integrates with OAuth [here](https://blog.modelcontextprotocol.io/posts/client_registration/).