Authentication & Security
Authentication modes and security features in MCP.do
MCP.do implements a three-tier authentication system with automatic mode detection, comprehensive security features, and OAuth 2.1 compliance.
Authentication Modes
Authentication is automatically detected from the Authorization header - no separate endpoints needed.
Authentication Flow Diagram
1. Anonymous Access
No Authorization header provided.
Configuration:
# No header
curl https://mcp.do \
-H "Content-Type: application/json" \
-d '{"method": "tools/call", ...}'Limits:
- Rate Limit: 10 requests/minute
- Timeout: 10 seconds per request
- Permissions: Read-only operations only
- Rate Limit Window: 60 seconds sliding window
Allowed Operations:
// ✅ Read operations
await db.Businesses.list()
await db.Orders.get('ord_123')
await ai.embed('text')
await api.fetch('https://api.example.com/data')
// ❌ Write operations (blocked via AST analysis)
await db.Orders.create({...}) // Error: Write operation not allowed
await db.Orders.update(id, {...}) // Error: Write operation not allowed
await send.Email({...}) // Error: Write operation not allowedUse Cases:
- Public demos
- Documentation examples
- Quick testing
- Read-only integrations
2. OAuth Authentication
User-specific access with Bearer oauth_ prefix.
Configuration:
# OAuth token
curl https://mcp.do \
-H "Authorization: Bearer oauth_abc123" \
-H "Content-Type: application/json" \
-d '{"method": "tools/call", ...}'Limits:
- Rate Limit: 100 requests/minute
- Timeout: 30 seconds per request
- Permissions: Full access based on user's roles
- Rate Limit Window: 60 seconds sliding window
Token Format:
- Prefix:
oauth_ - Validation: Via OAuth server at
env.OAUTH_SERVER_URL - Scopes:
mcp:tools,mcp:resources,mcp:prompts
Use Cases:
- User-facing applications
- Claude Desktop integration
- Personal automation
- Multi-tenant SaaS
Token Validation Flow:
- Extract token from
Authorization: Bearer oauth_xxx - Call OAuth server:
GET /oauth/token/info - Verify token is valid and not expired
- Extract user ID and permissions
- Check user roles (admin, user, etc.)
3. API Key Authentication
Service-to-service access with Bearer sk_ prefix.
Configuration:
# API key
curl https://mcp.do \
-H "Authorization: Bearer sk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{"method": "tools/call", ...}'Limits:
- Rate Limit: 100 requests/minute (configurable)
- Timeout: 30 seconds per request
- Permissions: Full access (no user context)
- Rate Limit Window: 60 seconds sliding window
Key Format:
- Prefix:
sk_live_(production) orsk_test_(testing) - Validation: Via API key service
- Scopes: Full platform access
Use Cases:
- Server-to-server integration
- Backend automation
- CI/CD pipelines
- Scheduled jobs
OAuth 2.1 Compliance
MCP.do implements RFC 9728 (OAuth 2.0 Protected Resource Metadata):
Discovery Endpoint
Response:
{
"resource": "https://mcp.do",
"authorization_servers": ["https://oauth.do"],
"scopes_supported": ["mcp:tools", "mcp:resources", "mcp:prompts"],
"bearer_methods_supported": ["header"],
"resource_signing_alg_values_supported": ["RS256"],
"resource_documentation": "https://mcp.do/docs",
"resource_policy_uri": "https://mcp.do/policy"
}WWW-Authenticate Header
On authentication failure:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="https://mcp.do",
resource="https://mcp.do/.well-known/oauth-protected-resource",
authorization_server="https://oauth.do"
Content-Type: application/json
{
"error": "invalid_token",
"error_description": "Token validation failed",
"resource": "https://mcp.do/.well-known/oauth-protected-resource"
}Scopes
mcp:tools- Execute tools (do,elicit, etc.)mcp:resources- Access resources (files, data, etc.)mcp:prompts- Use prompt templates
Security Features
Rate Limiting
Sliding window rate limiter using Durable Objects:
Implementation:
// Rate limiter tracks requests per user/session
const rateLimitId = env.rateLimiter.idFromName(auth.id)
const rateLimiter = env.rateLimiter.get(rateLimitId)
// Check limit
const response = await rateLimiter.fetch(
new Request('https://internal/check', {
method: 'POST',
body: JSON.stringify({ limit: 100 }),
})
)
if (!response.ok) {
return new Response('Rate limit exceeded', {
status: 429,
headers: {
'X-RateLimit-Limit': '100',
'X-RateLimit-Window': '60',
},
})
}Response Headers:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Window: 60
WWW-Authenticate: Bearer realm="https://mcp.do"Configuration:
# Environment variables
ANON_RATE_LIMIT=10 # Anonymous: 10 req/min
AUTH_RATE_LIMIT=100 # Authenticated: 100 req/minReadonly Enforcement
Anonymous users are restricted to read-only operations via AST (Abstract Syntax Tree) analysis:
Analysis:
// Parse TypeScript to AST
const ast = parseTypeScript(script)
// Check for write operations
const writeOps = ['db.create', 'db.update', 'db.delete', 'send', 'every', 'ai.generate']
for (const node of ast.nodes) {
if (isCallExpression(node) && writeOps.includes(node.callee)) {
throw new Error('Write operation not allowed in readonly mode')
}
}Blocked Operations:
db.create()- Creating entitiesdb.update()- Updating entitiesdb.delete()- Deleting entitiessend()- Publishing eventsevery()- Scheduling workflowsai.generate()- AI generation (has side effects)
Execution Sandboxing
Each request executes in an isolated Durable Object:
Sandbox Features:
- Isolated Environment: Separate process per request
- Timeout Enforcement: 10s (anon) or 30s (auth)
- Resource Limits: Memory and CPU constraints
- No State Persistence: Clean slate for each request
Implementation:
// Create sandbox for this request
const sandboxId = env.sandbox.newUniqueId()
const sandbox = env.sandbox.get(sandboxId)
// Execute with timeout
const result = await sandbox.fetch(
new Request('https://internal/execute', {
method: 'POST',
body: JSON.stringify({
script,
timeout: auth.type === 'anon' ? 10000 : 30000,
}),
})
)Audit Logging
All operations are logged with authentication context:
Log Format:
{
"timestamp": "2024-01-15T10:30:00Z",
"authType": "oauth",
"userId": "usr_123",
"tool": "do",
"script": "await db.list('Business')",
"duration": 245,
"success": true
}Anonymous Tracking:
{
"timestamp": "2024-01-15T10:30:00Z",
"authType": "anon",
"sessionId": "anon:192.168.1.1",
"tool": "do",
"script": "await db.list('Business')",
"readonly": true,
"success": true
}Input Validation
All inputs are validated before execution:
Script Validation:
- Maximum length: 10,000 characters
- Syntax checking via TypeScript parser
- AST analysis for security concerns
Schema Validation:
- JSON Schema validation for
elicittool - Type checking for all inputs
- Enum validation for constrained fields
Role-Based Access Control
User Roles
- Admin: Full access to all tools including admin-only operations
- User: Standard access to
doandelicittools - Readonly: Anonymous users with read-only access
Admin Tools
Admin users get additional tools:
// Only available to admin users
{
"name": "admin_deploy_worker",
"description": "Deploy a Cloudflare Worker",
"inputSchema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"script": { "type": "string" }
}
}
}Admin Check:
// In tool handler
if (!auth.isAdmin) {
return {
content: [
{
type: 'text',
text: 'Error: Admin access required',
},
],
isError: true,
}
}Events Tools
Authenticated users (OAuth or API key) get event management tools:
{
"name": "events_list",
"description": "List recent events",
"inputSchema": {
"type": "object",
"properties": {
"type": { "type": "string" },
"limit": { "type": "number" }
}
}
}Auth Check:
if (!auth || auth.type === 'anon') {
return {
content: [
{
type: 'text',
text: 'Error: Authentication required',
},
],
isError: true,
}
}Best Practices
1. Use Appropriate Auth Mode
// ❌ API key for user-facing app
const client = new MCPClient({
serverUrl: 'https://mcp.do',
apiKey: 'sk_live_xxx', // Exposes key to users
})
// ✅ OAuth for user-facing app
const client = new MCPClient({
serverUrl: 'https://mcp.do',
oauth: {
authorizationServer: 'https://oauth.do',
clientId: 'client_id',
},
})2. Rotate API Keys Regularly
# Generate new key
do auth keys create --name "production-v2"
# Update configuration
export DO_API_KEY="sk_live_new_key"
# Revoke old key
do auth keys revoke sk_live_old_key3. Implement Token Refresh
// Refresh OAuth tokens before expiry
if (tokenExpiresIn < 300) {
// 5 minutes
const newToken = await refreshToken(currentToken)
client.setToken(newToken)
}4. Handle Rate Limits Gracefully
try {
const result = await client.useTool('do', { script })
} catch (error) {
if (error.status === 429) {
// Wait and retry
await delay(60000) // Wait 1 minute
return retry()
}
throw error
}Environment Variables
# OAuth Server
OAUTH_SERVER_URL=https://oauth.do
# Rate Limits
ANON_RATE_LIMIT=10 # Anonymous requests/minute
AUTH_RATE_LIMIT=100 # Authenticated requests/minute
# Timeouts
ANON_TIMEOUT_MS=10000 # Anonymous timeout (10s)
AUTH_TIMEOUT_MS=30000 # Authenticated timeout (30s)
# Admin Access
ADMIN_ROLE=admin # Role name for admin accessNext Steps
- Configuration - Server setup and deployment
- Integration Guide - Client integration patterns
- Debugging - Monitoring and troubleshooting