> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Authentication Implementation

> Technical implementation of multi-team OAuth 2.1 Resource Server functionality in DeployStack Satellite for MCP client authentication.

<Note>
  **OAuth System Clarification**: This document covers **MCP Client → Satellite authentication** (how VS Code/Cursor/Claude.ai authenticate to DeployStack).

  For **User → MCP Server authentication** (how users connect to Notion/Box/Linear), see:

  * Backend Implementation: [MCP Server OAuth](/development/backend/mcp-server-oauth)
  * Satellite Integration: [OAuth Token Injection](/development/satellite/mcp-server-token-injection)
</Note>

DeployStack Satellite implements OAuth 2.1 Resource Server functionality to authenticate MCP clients with team-aware access control. This document covers the technical implementation, integration patterns, and development setup for the OAuth authentication layer.

## Technical Overview

### OAuth 2.1 Resource Server Architecture

The satellite operates as a multi-team OAuth 2.1 Resource Server that validates Bearer tokens via Backend introspection. The backend now uses database-backed storage for dynamic client registration, enabling persistent MCP client authentication:

```
MCP Client                    Satellite                    Backend
    │                           │                             │
    │──── GET /sse ─────────────▶│                             │
    │                           │                             │
    │◀─── 401 + WWW-Auth ──────│                             │
    │                           │                             │
    │──── Dynamic Registration ─────────────────────────────▶│
    │◀─── Client ID ───────────────────────────────────────│
    │                           │                             │
    │──── OAuth Flow ──────────────────────────────────────▶│
    │◀─── Bearer Token ────────────────────────────────────│
    │                           │                             │
    │──── GET /sse + Token ────▶│                             │
    │                           │──── POST /introspect ─────▶│
    │                           │◀─── Team Context ─────────│
    │                           │                             │
    │◀─── SSE Stream ──────────│                             │
```

### Core Components

**Token Introspection Service:**

* Validates Bearer tokens via Backend introspection endpoint
* Implements 5-minute token caching for performance
* Supports multi-team authentication (any valid team)
* Extracts team context from token validation response
* Handles both static and dynamic client tokens

**Authentication Middleware:**

* `requireAuthentication()` - Validates Bearer tokens for any team
* `requireScope()` - Enforces OAuth scope requirements
* Proper WWW-Authenticate headers with OAuth 2.1 compliance
* JSON-RPC 2.0 compliant error responses
* Dynamic client registration guidance in error responses

**Team-Aware MCP Handler:**

* Filters tools based on team's MCP server installations
* Team-aware `tools/list` - only shows tools from team's allowed servers
* Team-aware `tools/call` - validates team access before execution
* Integrates with existing tool discovery and configuration systems

For detailed team isolation implementation, see [Team Isolation Implementation](/development/satellite/team-isolation).

**RFC 9728 Protected Resource Metadata:**

* Exposes `/.well-known/oauth-protected-resource` endpoint for MCP client discovery
* Returns resource identifier: `${DEPLOYSTACK_SATELLITE_URL}/mcp` (fixes Gemini CLI audience mismatch)
* Advertises supported scopes: `["mcp:read", "mcp:tools:execute"]`
* Specifies bearer token methods: `["header"]`
* Provides documentation link for MCP clients
* Enables zero-configuration OAuth for compliant MCP clients

**RFC 8707 Audience Validation:**

* Validates token `aud` (audience) claim matches satellite URL
* Prevents token reuse across different satellites
* Audience format: `https://satellite.deploystack.io/mcp`
* Backend generates `aud` claim from `resource` parameter
* Satellite validates via token introspection response

**Dynamic Client Support:**

* Supports RFC 7591 dynamically registered clients
* Handles VS Code MCP extension client caching
* Supports Cursor, Claude.ai, and other MCP clients
* Persistent client storage survives backend restarts

## Implementation Files

### Core OAuth Services

**Token Introspection Service:**

* File: `services/satellite/src/services/token-introspection-service.ts`
* Purpose: Backend token validation with 5-minute caching
* Dependencies: BackendClient for introspection calls

**Authentication Middleware:**

* File: `services/satellite/src/middleware/auth-middleware.ts`
* Purpose: Bearer token validation and scope enforcement
* Integration: Fastify preValidation hooks
* **RFC 9728 Enhancements:**
  * WWW-Authenticate headers use `resource_metadata` instead of direct URIs
  * Includes OAuth error codes (`invalid_token`, `insufficient_scope`)
  * Adds Link header for proactive discovery: `Link: </.well-known/oauth-protected-resource>; rel="oauth-protected-resource"`
  * Error responses include `resource_metadata_uri` for discovery starting point

**Team-Aware MCP Handler:**

* File: `services/satellite/src/services/team-aware-mcp-handler.ts`
* Purpose: Team-filtered tool discovery and execution
* Dependencies: DynamicConfigManager, RemoteToolDiscoveryManager

**OAuth Discovery Routes:**

* File: `services/satellite/src/routes/oauth-discovery.ts`
* Purpose: RFC 9728 Protected Resource Metadata and RFC 8414 Authorization Server Metadata
* Endpoints:
  * `GET /.well-known/oauth-protected-resource` - Resource metadata with scopes and capabilities
  * `GET /.well-known/oauth-authorization-server` - Proxy to backend authorization server metadata
  * `GET /.well-known/openid-configuration` - VS Code compatibility endpoint
* **Resource URL Format:** `${DEPLOYSTACK_SATELLITE_URL}/mcp` (fixes Gemini CLI audience mismatch)
* **Environment Variables:**
  * `DEPLOYSTACK_SATELLITE_URL` - Base satellite URL (e.g., `https://satellite.deploystack.io`)
  * `DEPLOYSTACK_RESOURCE_DOCUMENTATION` - Documentation URL (defaults to `https://docs.deploystack.io/`)

### Route Integration

**Updated MCP Routes:**

* Files: `services/satellite/src/routes/mcp.ts`, `services/satellite/src/routes/sse.ts`
* Authentication: Bearer token required for all MCP endpoints
* Scopes: `mcp:read` for discovery, `mcp:tools:execute` for execution
* CORS: OPTIONS endpoints remain unauthenticated

**Server Configuration:**

* File: `services/satellite/src/server.ts`
* Integration: OAuth services initialized after satellite registration
* Swagger: Updated with Bearer authentication security scheme

## OAuth Scopes and Permissions

### Supported OAuth Scopes

**mcp:read:**

* Required for tool discovery (`tools/list`)
* Required for SSE connection establishment
* Required for MCP transport initialization

**mcp:tools:execute:**

* Required for tool execution (`tools/call`)
* Required for MCP JSON-RPC message sending
* Includes read permissions implicitly

**offline\_access:**

* Required for refresh token issuance
* Enables long-lived sessions for MCP clients

<Info>
  **Default Scopes**: The `scope` parameter is optional in authorization requests. When not provided, the Backend defaults to `mcp:read mcp:tools:execute offline_access`. This allows MCP clients that don't send scope (like Claude Code) to authenticate without errors.
</Info>

### Team-Based Access Control

**Team Resolution:**

* Team context extracted from validated OAuth token
* No hardcoded team configuration in satellite
* Dynamic team filtering based on token validation response
* Supports multiple teams per user

**Tool Filtering:**

* Tools filtered based on team's MCP server installations
* Team-MCP server mappings from Backend database (`mcpServerInstallations` table)
* Access control enforced before tool execution
* Complete team isolation maintained

## MCP Client Integration

### Dynamic Client Registration Support

The satellite now supports MCP clients that use RFC 7591 Dynamic Client Registration:

**VS Code MCP Extension:**

* Automatic client registration via Backend `/api/oauth2/register`
* Client ID caching for improved user experience
* Persistent storage survives VS Code restarts
* Long-lived tokens (1-week access, 30-day refresh)

**Cursor MCP Client:**

* Dynamic registration with `cursor://` redirect URIs
* Team-scoped tool access
* Automatic token refresh handling

**Claude.ai Custom Connector:**

* Registration with `https://claude.ai/mcp/auth/callback`
* OAuth 2.1 compliant authentication flow
* Team-aware tool discovery

**Cline MCP Client:**

* VS Code extension integration
* Shared client registration with VS Code patterns
* Consistent authentication experience

### Client Authentication Flow

**First-Time Authentication:**

1. MCP client attempts to connect to satellite
2. Satellite returns 401 with registration guidance
3. Client registers via Backend `/api/oauth2/register`
4. Client receives unique client\_id (e.g., `dyn_1757880447836_uvze3d0yc`)
5. Client initiates OAuth flow with Backend
6. User authorizes in browser with team selection
7. Client receives Bearer token
8. Client connects to satellite with token
9. Satellite validates token and establishes SSE connection

**Subsequent Authentications:**

1. MCP client uses cached client\_id
2. Client uses stored refresh token if access token expired
3. Client connects directly to satellite with valid token
4. Satellite validates token via introspection (with caching)
5. SSE connection established immediately

## Development Setup

### Environment Configuration

**Required Environment Variables:**

```bash theme={null}
# Satellite identity
DEPLOYSTACK_SATELLITE_NAME=dev-satellite-001
DEPLOYSTACK_BACKEND_URL=http://localhost:3000

# Optional configuration
PORT=3001
HOST=0.0.0.0
LOG_LEVEL=debug
```

**Removed Environment Variables:**

* `DEPLOYSTACK_TEAM_ID` - Team context comes from OAuth tokens
* `DEPLOYSTACK_TEAM_NAME` - Team context comes from OAuth tokens

### Local Development Setup

**Clone and Setup:**

```bash theme={null}
git clone https://github.com/deploystackio/deploystack.git
cd deploystack/services/satellite
npm install
cp .env.example .env
# Edit DEPLOYSTACK_SATELLITE_NAME and DEPLOYSTACK_BACKEND_URL
npm run dev
```

**Backend Dependency:**

```bash theme={null}
# Start backend first (required for satellite operation)
cd services/backend
npm run dev
# Backend runs on http://localhost:3000
```

**Satellite Startup:**

```bash theme={null}
cd services/satellite
npm run dev
# Satellite runs on http://localhost:3001
# API docs: http://localhost:3001/documentation
```

## Token Validation Implementation

### Token Introspection Flow

**Cache-First Validation:**

```typescript theme={null}
// 1. Check 5-minute cache first
const cacheKey = this.hashToken(token);
const cached = this.tokenCache.get(cacheKey);

// 2. Call Backend introspection if cache miss
const introspectionResponse = await this.callIntrospectionEndpoint(token);

// 3. Validate token is active and extract team context
if (introspectionResponse.active) {
  const result = {
    valid: true,
    user: { id: introspectionResponse.sub, username: introspectionResponse.username },
    team: { 
      id: introspectionResponse.team_id,
      name: introspectionResponse.team_name,
      role: introspectionResponse.team_role,
      permissions: introspectionResponse.team_permissions
    },
    scopes: introspectionResponse.scope.split(' ')
  };
}
```

### Backend Introspection Integration

**Introspection Request:**

```typescript theme={null}
const response = await fetch(`${backendUrl}/api/oauth2/introspect`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${satelliteApiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ token: token }),
  signal: AbortSignal.timeout(10000)
});
```

**Response Processing:**

* `active: true` - Token is valid, extract team context
* `active: false` - Token invalid, return authentication error
* Team context includes: team\_id, team\_name, team\_role, team\_permissions

## Session Management and Security Model

### MCP Sessions vs OAuth Authentication

The satellite implements a two-layer security model that separates authentication from session management:

**Authentication Layer (OAuth Bearer Token):**

* Primary security mechanism for all requests
* Validates user identity, team membership, and permissions
* Enforced by authentication middleware before session handling
* Team isolation enforced at this layer via token introspection

**Session Layer (MCP Session ID):**

* Transport-level identifier for HTTP/SSE connection routing
* NOT a security credential - purely for protocol state management
* Can be safely reused because security comes from Bearer token
* Managed by StreamableHTTPServerTransport from MCP SDK

### Session Resurrection After Satellite Restart

When a satellite restarts (deployments, updates, crashes), MCP sessions are lost because they live in memory. The satellite implements transparent session resurrection to avoid forcing users to manually reconnect:

**How Session Resurrection Works:**

1. Client sends request with old session ID (from before restart)
2. Satellite validates Bearer token FIRST (authentication layer)
3. If session ID is stale, satellite creates new Server + Transport with same session ID
4. Bootstrap transport with synthetic `initialize` request
5. Process actual client request normally
6. Client continues without reconnection

**Implementation Details:**

```typescript theme={null}
// Authentication happens FIRST (line 558 in mcp-server-wrapper.ts)
const authHeader = request.headers['authorization'];
const token = authHeader?.replace(/^Bearer\s+/i, '');

if (!token) {
  return reply.status(401).send({
    jsonrpc: '2.0',
    error: { code: -32001, message: 'Authentication required' },
    id: null
  });
}

// Validate token via introspection BEFORE session handling
const introspectionResult = await this.tokenIntrospectionService.validateToken(token);

if (!introspectionResult.valid) {
  return reply.status(401).send({
    jsonrpc: '2.0',
    error: { code: -32002, message: 'Invalid token' },
    id: null
  });
}

// NOW handle session resurrection (lines 616-722)
const sessionId = request.headers['mcp-session-id'];
const existingTransport = this.transports.get(sessionId);

if (!existingTransport && sessionId) {
  // Create new Server + Transport with same session ID
  server = new Server({ name: 'deploystack-satellite', version: '1.0.0' });

  transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => sessionId, // Reuse old session ID
    onsessioninitialized: (restoredSessionId) => {
      this.transports.set(restoredSessionId, { transport, server });
    }
  });

  await server.connect(transport);

  // Bootstrap transport with synthetic initialize request
  const syntheticInitRequest = {
    jsonrpc: '2.0',
    id: 0,
    method: 'initialize',
    params: {
      protocolVersion: '2024-11-05',
      capabilities: {},
      clientInfo: { name: 'resurrected-session', version: '1.0.0' }
    }
  };

  await transport.handleRequest(request.raw, mockRes, syntheticInitRequest);
}
```

## Team-Aware Tool Discovery

### Tool Filtering Implementation

**Team Server Access:**

```typescript theme={null}
private getTeamAllowedServers(teamId: string): string[] {
  const currentConfig = this.configManager.getCurrentConfiguration();
  const allowedServers: string[] = [];
  
  for (const [serverName, serverConfig] of Object.entries(currentConfig.servers)) {
    if (serverConfig.enabled === false) continue;
    
    // TODO: Filter based on team-MCP server mappings from backend
    // Currently allows all enabled servers for all teams
    allowedServers.push(serverName);
  }
  
  return allowedServers;
}
```

**Tool List Filtering:**

```typescript theme={null}
async handleTeamAwareToolsList(teamId?: string): Promise<any> {
  const allCachedTools = this.toolDiscoveryManager.getCachedTools();
  const teamAllowedServers = this.getTeamAllowedServers(teamId);
  
  const teamFilteredTools = allCachedTools.filter(tool => 
    teamAllowedServers.includes(tool.serverName)
  );
  
  return { tools: teamFilteredTools.map(tool => ({
    name: tool.namespacedName,
    description: tool.description,
    inputSchema: tool.inputSchema
  }))};
}
```

### Tool Execution Validation

**Access Control Check:**

```typescript theme={null}
async handleTeamAwareToolsCall(params: any, requestId: any, teamId?: string): Promise<any> {
  const namespacedToolName = params.name;
  const serverName = namespacedToolName.substring(0, namespacedToolName.indexOf('-'));
  
  const teamAllowedServers = this.getTeamAllowedServers(teamId);
  
  if (!teamAllowedServers.includes(serverName)) {
    throw new Error(`Access denied: Team does not have permission to use server '${serverName}'`);
  }
  
  // Delegate to base handler for execution
  return await this.baseHandler.handleMcpRequest(baseRequest);
}
```

## Authentication Middleware Integration

### Fastify Route Protection

**MCP Route Authentication:**

```typescript theme={null}
server.get('/sse', {
  preValidation: [
    requireAuthentication(tokenIntrospectionService),
    requireScope('mcp:read')
  ],
  // ... route handler
});

server.post('/mcp', {
  preValidation: [
    requireAuthentication(tokenIntrospectionService),
    requireScope('mcp:tools:execute')
  ],
  // ... route handler
});
```

### Authentication Context

**Request Context Extension:**

```typescript theme={null}
declare module 'fastify' {
  interface FastifyRequest {
    auth?: {
      user: { id: string; username: string };
      team: { id: string; name: string; role: string; permissions: string[] };
      scopes: string[];
      client_id?: string;
    };
  }
}
```

**Context Usage in Routes:**

```typescript theme={null}
server.log.info({
  operation: 'mcp_request',
  userId: request.auth?.user.id,
  teamId: request.auth?.team.id,
  clientId: request.auth?.client_id,
  method: message?.method
}, 'Authenticated MCP request');
```

## Error Handling Implementation

### Authentication Errors

**401 Unauthorized Response:**

```typescript theme={null}
function sendAuthenticationRequired(reply: FastifyReply) {
  const backendUrl = process.env.DEPLOYSTACK_BACKEND_URL;
  
  const wwwAuthenticate = `Bearer realm="DeployStack MCP Satellite", ` +
    `authorizationUri="${backendUrl}/api/oauth2/auth", ` +
    `tokenUri="${backendUrl}/api/oauth2/token", ` +
    `registrationUri="${backendUrl}/api/oauth2/register"`;

  const errorResponse = {
    jsonrpc: '2.0',
    error: {
      code: -32001,
      message: 'Authentication required',
      data: {
        message: 'Bearer token required for MCP access',
        authorization_uri: `${backendUrl}/api/oauth2/auth`,
        token_uri: `${backendUrl}/api/oauth2/token`,
        registration_uri: `${backendUrl}/api/oauth2/register`,
        flow: 'Dynamic client registration available for MCP clients'
      }
    },
    id: null
  };

  return reply
    .status(401)
    .header('WWW-Authenticate', wwwAuthenticate)
    .type('application/json')
    .send(JSON.stringify(errorResponse));
}
```

### Scope Validation Errors

**403 Insufficient Scope Response:**

```typescript theme={null}
function sendInsufficientScopeError(reply: FastifyReply, requiredScope: string) {
  const errorResponse = {
    jsonrpc: '2.0',
    error: {
      code: -32004,
      message: 'Insufficient scope',
      data: {
        message: `Token missing required scope: ${requiredScope}`,
        required_scope: requiredScope,
        available_scopes: ['mcp:read', 'mcp:tools:execute', 'offline_access']
      }
    },
    id: null
  };

  return reply.status(403).type('application/json').send(JSON.stringify(errorResponse));
}
```

## Performance Characteristics

### Token Validation Caching

**Cache Configuration:**

* Cache TTL: 5 minutes
* Cache key: Hashed token (security)
* Memory usage: \~1KB per cached token
* Cleanup: Automatic expired token removal every 5 minutes

**Cache Implementation:**

```typescript theme={null}
private tokenCache: Map<string, { result: TokenValidationResult; expires: number }>;

// Cache hit
if (cached && cached.expires > Date.now()) {
  return cached.result;
}

// Cache miss - call backend
const introspectionResponse = await this.callIntrospectionEndpoint(token);

// Cache result
this.tokenCache.set(cacheKey, {
  result,
  expires: Date.now() + (5 * 60 * 1000)
});
```

### Multi-Team Scalability

**Team Limits:**

* No hard limit on concurrent teams (memory-bound)
* Supports 100+ teams simultaneously
* Tool filtering: O(n) where n = team's MCP servers
* Memory efficiency: Shared tool cache across all teams

**Performance Optimization:**

* Connection pooling to Backend for introspection
* Async token validation pipeline
* Efficient team-server mapping lookups

## Integration with Backend Systems

### Backend Communication

**Introspection Endpoint:**

* URL: `${DEPLOYSTACK_BACKEND_URL}/api/oauth2/introspect`
* Authentication: Satellite API key (Bearer token)
* Timeout: 10 seconds
* Retry: Handled by existing backend client

**Team-MCP Server Mappings:**

* Source: Backend database `mcpServerInstallations` table
* Delivery: Via existing backend polling system
* Update: Dynamic configuration sync
* Storage: In-memory via DynamicConfigManager

### Configuration Integration

**Dynamic Configuration:**

```typescript theme={null}
// Team-MCP server mappings come via existing polling system
const currentConfig = this.configManager.getCurrentConfiguration();

// Filter servers based on team access (future implementation)
for (const [serverName, serverConfig] of Object.entries(currentConfig.servers)) {
  if (serverConfig.enabled && teamHasAccess(teamId, serverName)) {
    allowedServers.push(serverName);
  }
}
```

## Development Patterns

### Service Initialization

**Server Startup Integration:**

```typescript theme={null}
// Initialize after satellite registration
if (registrationResult.success && registrationResult.satellite) {
  backendClient.setApiKey(registrationResult.satellite.api_key);

  // Initialize OAuth services
  const tokenIntrospectionService = new TokenIntrospectionService(backendClient, server.log);
  const teamAwareMcpHandler = new TeamAwareMcpHandler(
    mcpProtocolHandler,
    dynamicConfigManager,
    toolDiscoveryManager,
    server.log
  );

  // Store for route access
  server.decorate('tokenIntrospectionService', tokenIntrospectionService);
  server.decorate('teamAwareMcpHandler', teamAwareMcpHandler);
}
```

### Logging Patterns

**Authentication Events:**

```typescript theme={null}
// Successful authentication
request.log.debug({
  operation: 'authentication_success',
  userId: request.auth.user.id,
  teamId: request.auth.team.id,
  clientId: request.auth.client_id,
  scopes: request.auth.scopes
}, 'Authentication successful');

// Team tool access
this.logger.info({
  operation: 'team_tool_access_granted',
  team_id: teamId,
  server_name: serverName,
  namespaced_tool_name: namespacedToolName
}, `Team ${teamId} has access to server ${serverName}`);
```

### Error Handling Patterns

**Service Error Handling:**

```typescript theme={null}
try {
  const validationResult = await introspectionService.validateToken(token);
  if (!validationResult.valid) {
    return sendInvalidTokenError(reply, request, validationResult);
  }
} catch (error) {
  request.log.error({
    operation: 'authentication_middleware_error',
    error: error instanceof Error ? error.message : String(error)
  }, 'Authentication middleware error');
  return sendServerError(reply, request);
}
```

## Testing and Validation

### Local Testing Setup

**Backend OAuth Token Generation:**

```bash theme={null}
# Method 1: Client Credentials Flow (simplest for testing)
curl -X POST http://localhost:3000/api/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=test_client&client_secret=test_secret&scope=mcp:read mcp:tools:execute&team=<TEAM_ID>"

# Method 2: Authorization Code Flow with PKCE (production flow)
# Step 1: Generate PKCE parameters
node -e "
const crypto = require('crypto');
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
console.log('Verifier:', verifier);
console.log('Challenge:', challenge);
"

# Step 2: Authorization request (browser)
# Note: scope is optional - defaults to "mcp:read mcp:tools:execute offline_access"
http://localhost:3000/api/oauth2/auth?response_type=code&client_id=test_client&redirect_uri=http://localhost:3000/callback&scope=mcp:read%20mcp:tools:execute&team=<TEAM_ID>&state=abc123&code_challenge=<CHALLENGE>&code_challenge_method=S256

# Step 3: Token exchange
curl -X POST http://localhost:3000/api/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=<AUTH_CODE>&client_id=test_client&redirect_uri=http://localhost:3000/callback&code_verifier=<VERIFIER>"
```

### Authentication Testing

**Test Unauthenticated Access:**

```bash theme={null}
curl -X GET "http://localhost:3001/sse"
# Expected: 401 with WWW-Authenticate header
```

**Test Authenticated Access:**

```bash theme={null}
curl -X GET "http://localhost:3001/sse" \
  -H "Authorization: Bearer <valid_token>"
# Expected: SSE stream establishment
```

**Test Team-Filtered Tool Discovery:**

```bash theme={null}
curl -X POST "http://localhost:3001/mcp" \
  -H "Authorization: Bearer <team_token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
# Expected: Tools filtered by team's MCP server access
```

### Multi-Team Validation

**Test Different Team Tokens:**

```bash theme={null}
# Team A token
curl -X POST "http://localhost:3001/mcp" \
  -H "Authorization: Bearer <team_a_token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'

# Team B token
curl -X POST "http://localhost:3001/mcp" \
  -H "Authorization: Bearer <team_b_token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'

# Expected: Different tool lists based on each team's MCP server installations
```

## Security Implementation

### Token Security

**Token Handling:**

* Never log actual token values
* Use hashed tokens for cache keys
* Clear tokens from memory after use
* 10-second timeout for introspection requests

**Cache Security:**

```typescript theme={null}
private hashToken(token: string): string {
  let hash = 0;
  for (let i = 0; i < token.length; i++) {
    const char = token.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }
  return hash.toString();
}
```

### Team Isolation

**Complete Separation:**

* Teams only see tools from their MCP server installations
* Access control enforced before tool execution
* Audit logging with team context
* No cross-team access possible

**Access Validation:**

```typescript theme={null}
// Validate team has access to MCP server before tool execution
const teamAllowedServers = this.getTeamAllowedServers(teamId);

if (!teamAllowedServers.includes(serverName)) {
  throw new Error(`Access denied: Team does not have permission to use server '${serverName}'`);
}
```

## MCP Client Configuration

### Claude.ai Custom Connector

**Configuration Example:**

```json theme={null}
{
  "name": "DeployStack Team MCP",
  "description": "Team-scoped MCP access via DeployStack Satellite",
  "url": "http://localhost:3001/sse",
  "auth": {
    "type": "oauth2",
    "authorization_url": "http://localhost:3000/api/oauth2/auth",
    "token_url": "http://localhost:3000/api/oauth2/token",
    "client_id": "claude_ai_mcp_client",
    "scopes": ["mcp:read", "mcp:tools:execute"],
    "additional_parameters": {
      "team": "your_team_id"
    }
  }
}
```

### VS Code MCP Extension

**Configuration Example:**

```json theme={null}
{
  "mcpServers": {
    "deploystack-team": {
      "command": "mcp-client",
      "args": ["--transport", "sse"],
      "env": {
        "MCP_SERVER_URL": "http://localhost:3001/sse",
        "OAUTH_AUTHORIZATION_URL": "http://localhost:3000/api/oauth2/auth",
        "OAUTH_TOKEN_URL": "http://localhost:3000/api/oauth2/token",
        "OAUTH_CLIENT_ID": "vscode_mcp_client",
        "OAUTH_SCOPES": "mcp:read mcp:tools:execute",
        "OAUTH_TEAM": "your_team_id"
      }
    }
  }
}
```

## Troubleshooting

### Common Issues

**"Token introspection failed: HTTP 401":**

* Check satellite API key is set correctly
* Verify backend is running and accessible
* Ensure satellite is registered with backend

**"Authentication failed - token not active":**

* Check token format and expiry
* Verify token was issued by correct backend
* Ensure team exists in backend database

**"Access denied: Team does not have permission":**

* Verify team has MCP server installations in backend
* Check team-MCP server mappings in database
* Ensure user is member of the team

**"Token validation cache not working":**

* Check token hashing function
* Verify cache TTL settings (5 minutes)
* Monitor cache cleanup logs

### Debug Logging

**Enable Debug Logging:**

```bash theme={null}
LOG_LEVEL=debug npm run dev
```

**Key Log Operations:**

* `token_validation_cache_hit` - Cache performance
* `authentication_success` - Successful token validation
* `team_tool_access_granted` - Team access validation
* `token_cache_cleanup` - Cache maintenance

## Integration Status

### Current Implementation

**Completed Features:**

* Multi-team token introspection with 5-minute caching
* Team-aware tool discovery and filtering
* OAuth 2.1 Resource Server with scope validation
* Authentication middleware with proper error handling
* Integration with existing backend polling system
* Swagger documentation with Bearer authentication
* RFC 7591 Dynamic Client Registration support
* Database-backed persistent client storage
* VS Code MCP extension authentication (tested and working)
* Support for Cursor, Claude.ai, and Cline MCP clients

**Backend Integration:**

* Uses existing satellite registration system
* Leverages existing backend polling for team-MCP server mappings
* Integrates with existing tool discovery and configuration systems
* Maintains all existing MCP transport functionality
* Database-backed client storage survives backend restarts
* Supports both static and dynamic OAuth clients

**Verified MCP Client Support:**

* VS Code MCP Extension: Full OAuth flow tested and working
* Dynamic client registration: RFC 7591 compliant implementation
* Client ID caching: Persistent across client restarts
* Token refresh: Long-lived access for MCP clients
* Team isolation: Complete separation of team resources

The OAuth authentication implementation provides enterprise-grade security with complete team isolation while maintaining the existing satellite architecture and performance characteristics. The database-backed storage ensures MCP clients can cache credentials and maintain persistent authentication across sessions.

<Info>
  **Implementation Status**: OAuth authentication is fully implemented and operational with database-backed dynamic client registration. The system successfully authenticates MCP clients (including VS Code, Cursor, Claude.ai, and Cline) with team-aware access control, filters tools based on team permissions, and maintains complete team isolation while preserving all existing satellite functionality. Dynamic client registration enables seamless MCP client integration with persistent authentication.
</Info>

## Related Documentation

* [OAuth2 Server](/development/backend/oauth2-server) - Backend OAuth server implementation
* [MCP Server Token Injection](/development/satellite/mcp-server-token-injection) - External MCP server OAuth
* [MCP Server OAuth](/development/backend/mcp-server-oauth) - Backend OAuth for MCP servers
* [OAuth Providers](/development/backend/oauth-providers) - Social login
