> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agentkit`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# MCP Servers - Additional Reading

Explore advanced topics for MCP servers, including OAuth 2.1 flows, scope design, dynamic client registration, and security best practices.
MCP Clients that want to get authorized to access your MCP Server need to follow either of the below OAuth 2.1 Flows Supported by Scalekit.

## OAuth 2.1 Flows Supported

### Authorization Code Flow

Ideal when an AI agent or MCP Client acts on behalf of a human user:

```javascript
// Step 1: Redirect user to authorization server
const authURL = new URL('https://your-org.scalekit.com/oauth/authorize');
authURL.searchParams.set('response_type', 'code');
authURL.searchParams.set('client_id', 'your-client-id');
authURL.searchParams.set('redirect_uri', 'https://your-app.com/callback');
authURL.searchParams.set('scope', 'mcp:tools:calendar:read mcp:tools:email:send');
authURL.searchParams.set('state', generateSecureRandomString());
authURL.searchParams.set('code_challenge', generatePKCEChallenge());
authURL.searchParams.set('code_challenge_method', 'S256');

// Step 2: Handle callback and exchange code for token
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state parameter to prevent CSRF
  if (!isValidState(state)) {
    return res.status(400).json({ error: 'Invalid state parameter' });
  }

  const tokenResponse = await fetch('https://your-org.scalekit.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      client_id: 'your-client-id',
      redirect_uri: 'https://your-app.com/callback',
      code_verifier: getPKCEVerifier() // From PKCE challenge generation
    })
  });

  const tokens = await tokenResponse.json();
  // Store tokens securely and proceed with MCP calls
});
```

### Client Credentials Flow

Perfect for automated agents that don't represent a specific user but want to access your MCP Server on their own behalf. This is typically used for Machine-to-Machine (M2M) authentication.

```javascript
const getMachineToken = async () => {
  const response = await fetch('https://your-org.scalekit.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: 'your-service-client-id',
      client_secret: 'your-service-client-secret',
      scope: 'mcp:tools:inventory:check mcp:resources:store-data',
      audience: 'https://your-mcp-server.com',
    })
  });

  return await response.json();
};
```

## Scope Design Best Practices

Design OAuth scopes that reflect your MCP server's actual capabilities and security requirements:

### Hierarchical Scopes

```javascript
// Resource-based scopes
'mcp:resources:customer-data:read'    // Read customer data
'mcp:resources:customer-data:write'   // Modify customer data
'mcp:resources:*'                     // All resources (admin-level)

// Tool-based scopes
'mcp:tools:weather'                   // Weather API access
'mcp:tools:calendar:read'             // Read calendar events
'mcp:tools:calendar:write'            // Create/modify calendar events
'mcp:tools:email:send'                // Send emails
'mcp:tools:*'                         // All tools access

// Action-based scopes
'mcp:exec:workflows:risk-assessment'  // Execute risk assessment workflow
'mcp:exec:functions:data-analysis'    // Run data analysis functions
```

### Scope Validation Helpers

```javascript
const ScopeValidator = {
  hasScope: (userScopes, requiredScope) => {
    return userScopes.includes(requiredScope) ||
           userScopes.includes(requiredScope.split(':').slice(0, -1).join(':') + ':*');
  },

  hasAnyScope: (userScopes, allowedScopes) => {
    return allowedScopes.some(scope => ScopeValidator.hasScope(userScopes, scope));
  },

  validateToolAccess: (userScopes, toolName) => {
    const toolScope = `mcp:tools:${toolName}`;
    const wildcardScope = 'mcp:tools:*';
    return userScopes.includes(toolScope) || userScopes.includes(wildcardScope);
  }
};

// Usage in MCP tool handlers
app.post('/mcp/tools/:toolName', (req, res) => {
  const { toolName } = req.params;
  const userScopes = req.auth.scopes;

  if (!ScopeValidator.validateToolAccess(userScopes, toolName)) {
    return res.status(403).json({
      error: 'insufficient_scope',
      error_description: `Access to tool '${toolName}' requires appropriate scope`
    });
  }

  // Process tool request
});
```

## Dynamic Client Registration

Scalekit supports Dynamic Client Registration (DCR) to enable seamless integration for new MCP clients that want to connect to your MCP Server. MCP clients can auto-register using DCR:

```javascript
// MCP clients can auto-register using DCR
const registerClient = async (clientMetadata) => {
  const response = await fetch('https://your-org.scalekit.com/resource-server/oauth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_name: 'AI Sales Assistant',
      client_uri: 'https://sales-ai.company.com',
      redirect_uris: ['https://sales-ai.company.com/oauth/callback'],
      grant_types: ['authorization_code', 'refresh_token'],
      response_types: ['code'],
      scope: 'mcp:tools:crm:read mcp:tools:email:send',
      audience: 'https://your-mcp-server.com',
      token_endpoint_auth_method: 'client_secret_basic',
      ...clientMetadata
    })
  });

  return await response.json();
  // Returns: { client_id, client_secret, client_id_issued_at, ... }
};
```

## Security Implementation

### Rate Limiting by Client

Implement client-specific rate limits:

```javascript

const createClientRateLimit = () => {
  return rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    limit: (req) => {
      // Different limits based on client type or scopes
      const scopes = req.auth?.scopes || [];
      if (scopes.includes('mcp:tools:*')) return 1000; // Premium client
      if (scopes.includes('mcp:tools:basic')) return 100; // Basic client
      return 50; // Default limit
    },
    keyGenerator: (req) => req.auth?.clientId || req.ip,
    message: {
      error: 'rate_limit_exceeded',
      error_description: 'Too many requests from this client'
    }
  });
};

app.use('/mcp', createClientRateLimit());
```

### Comprehensive Logging

Track all OAuth and MCP interactions:

```javascript
const auditLogger = {
  logTokenRequest: (clientId, grantType, scopes, success) => {
    console.log(JSON.stringify({
      event: 'oauth_token_request',
      timestamp: new Date().toISOString(),
      client_id: clientId,
      grant_type: grantType,
      requested_scopes: scopes,
      success
    }));
  },

  logMCPAccess: (req, toolName, success, error = null) => {
    console.log(JSON.stringify({
      event: 'mcp_tool_access',
      timestamp: new Date().toISOString(),
      user_id: req.auth?.userId,
      client_id: req.auth?.clientId,
      tool_name: toolName,
      scopes: req.auth?.scopes,
      success,
      error: error?.message,
      ip_address: req.ip,
      user_agent: req.get('User-Agent')
    }));
  }
};

// Use in your MCP handlers
app.post('/mcp/tools/:toolName', async (req, res) => {
  const { toolName } = req.params;

  try {
    // Process tool request
    const result = await processToolRequest(toolName, req.body);

    auditLogger.logMCPAccess(req, toolName, true);
    res.json(result);
  } catch (error) {
    auditLogger.logMCPAccess(req, toolName, false, error);
    res.status(500).json({ error: 'Tool execution failed' });
  }
});
```

### Health Check Endpoints

Monitor your MCP server and authorization integration:

```javascript
app.get('/health', async (req, res) => {
  const health = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    services: {
      mcp_server: 'healthy',
      oauth_server: 'unknown'
    }
  };

  try {
    // Test OAuth server connectivity
    const oauthTest = await fetch('https://your-org.scalekit.com/.well-known/oauth-authorization-server');
    health.services.oauth_server = oauthTest.ok ? 'healthy' : 'degraded';
  } catch (error) {
    health.services.oauth_server = 'unhealthy';
    health.status = 'degraded';
  }

  const statusCode = health.status === 'healthy' ? 200 : 503;
  res.status(statusCode).json(health);
});
```

## Troubleshooting

### Common Issues and Solutions

**Token Validation Failures**

```javascript
// Debug token validation issues
const debugTokenValidation = async (token) => {
  try {
    // Check token structure
    const [header, payload, signature] = token.split('.');
    console.log('Token Header:', JSON.parse(atob(header)));
    console.log('Token Payload:', JSON.parse(atob(payload)));

    // Validate with detailed error info
    await jwtVerify(token, JWKS, {
      issuer: 'https://your-org.scalekit.com',
      audience: 'https://your-mcp-server.com'
    });
  } catch (error) {
    console.error('Token validation error:', {
      name: error.name,
      message: error.message,
      code: error.code
    });
  }
};
```

**CORS Issues with Authorization Server**

```javascript
// Configure CORS for OAuth endpoints
app.use('/oauth', cors({
  origin: 'https://your-org.scalekit.com',
  credentials: true,
  methods: ['GET', 'POST', 'OPTIONS'],
  allowedHeaders: ['Authorization', 'Content-Type', 'MCP-Protocol-Version']
}));
```

**Scope Permission Debugging**

```javascript
const debugScopes = (req, res, next) => {
  console.log('Request Scopes:', {
    user_scopes: req.auth?.scopes,
    required_scope: req.requiredScope,
    has_permission: req.auth?.scopes?.includes(req.requiredScope)
  });
  next();
};
```

### Error Response Standards

Follow OAuth 2.1 and MCP error response formats:

```javascript
const sendOAuthError = (res, error, description, statusCode = 400) => {
  res.status(statusCode).json({
    error,
    error_description: description,
    error_uri: 'https://your-mcp-server.com/docs/errors'
  });
};

// Usage examples
app.use((error, req, res, next) => {
  if (error.name === 'TokenExpiredError') {
    return sendOAuthError(res, 'invalid_token', 'Access token has expired', 401);
  }

  if (error.name === 'InsufficientScopeError') {
    return sendOAuthError(res, 'insufficient_scope', `Required scope: ${error.requiredScope}`, 403);
  }

  // Default error
  sendOAuthError(res, 'server_error', 'An unexpected error occurred', 500);
});
```

## Advanced Configuration

### Custom Scope Mapping

Map OAuth scopes to internal permissions:

```javascript
const scopePermissionMap = {
  'mcp:tools:weather': ['weather:read'],
  'mcp:tools:calendar:read': ['calendar:events:read'],
  'mcp:tools:calendar:write': ['calendar:events:read', 'calendar:events:write'],
  'mcp:tools:email:send': ['email:send', 'contacts:read'],
  'mcp:resources:customer-data': ['customers:read', 'customers:write']
};

const getPermissionsFromScopes = (scopes) => {
  const permissions = new Set();
  scopes.forEach(scope => {
    const scopePermissions = scopePermissionMap[scope] || [];
    scopePermissions.forEach(permission => permissions.add(permission));
  });
  return Array.from(permissions);
};
```

### Refresh Token Management

Handle token refresh for long-running agents:

```javascript
const TokenManager = {
  async refreshToken(refreshToken) {
    const response = await fetch('https://your-org.scalekit.com/oauth2/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: 'your-client-id',
        client_secret: 'your-client-secret'
      })
    });

    return await response.json();
  },

  async autoRefreshWrapper(tokenStore, makeRequest) {
    try {
      return await makeRequest(tokenStore.accessToken);
    } catch (error) {
      if (error.status === 401) {
        // Token expired, try refresh
        const newTokens = await this.refreshToken(tokenStore.refreshToken);
        tokenStore.accessToken = newTokens.access_token;
        tokenStore.refreshToken = newTokens.refresh_token;

        // Retry original request
        return await makeRequest(tokenStore.accessToken);
      }
      throw error;
    }
  }
};
```


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
