.do

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

flowchart TD REQUEST[Incoming Request] --> CHECK{Authorization<br/>Header?} CHECK --> |No Header| ANON[Anonymous Mode] CHECK --> |Bearer oauth_*| OAUTH[OAuth Mode] CHECK --> |Bearer sk_*| APIKEY[API Key Mode] ANON --> ANON_LIMIT[Rate Limit: 10 req/min<br/>Timeout: 10s<br/>Readonly Only] OAUTH --> OAUTH_VALIDATE[Validate with<br/>OAuth Server] APIKEY --> APIKEY_VALIDATE[Validate API Key] OAUTH_VALIDATE --> |Valid| OAUTH_LIMIT[Rate Limit: 100 req/min<br/>Timeout: 30s<br/>Full Access] OAUTH_VALIDATE --> |Invalid| ERROR401[401 Unauthorized] APIKEY_VALIDATE --> |Valid| APIKEY_LIMIT[Rate Limit: 100 req/min<br/>Timeout: 30s<br/>Full Access] APIKEY_VALIDATE --> |Invalid| ERROR401 ANON_LIMIT --> AST[AST Analysis<br/>Block Write Operations] OAUTH_LIMIT --> RBAC[Role-Based<br/>Access Control] APIKEY_LIMIT --> FULL[Full Platform<br/>Access] AST --> EXECUTE[Execute Script] RBAC --> EXECUTE FULL --> EXECUTE EXECUTE --> RESPONSE[Return Result] ERROR401 --> WWW[WWW-Authenticate<br/>Header]

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 allowed

Use 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:

  1. Extract token from Authorization: Bearer oauth_xxx
  2. Call OAuth server: GET /oauth/token/info
  3. Verify token is valid and not expired
  4. Extract user ID and permissions
  5. 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) or sk_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/min

Readonly 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 entities
  • db.update() - Updating entities
  • db.delete() - Deleting entities
  • send() - Publishing events
  • every() - Scheduling workflows
  • ai.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 elicit tool
  • 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 do and elicit tools
  • 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_key

3. 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 access

Next Steps