Skip to content

Latest commit

 

History

History
150 lines (103 loc) · 4.75 KB

File metadata and controls

150 lines (103 loc) · 4.75 KB

Session Ownership System

Overview

The MCP server implements multi-user session isolation to ensure users can only access their own MCP sessions. This is accomplished using user identifiers and session ownership tracking.

Note: While this document describes Redis-backed storage, the server also supports in-memory storage for development. When Redis is not configured, session data is stored in memory and lost on restart.

How It Works

Each MCP session is owned by a specific user. Session ownership is stored in Redis and validated on each request.

User ID Assignment

Production: OAuth providers assign unique user identifiers via the sub (subject) claim - a standard OAuth/OpenID Connect field.

User ID sources:

  • Provider's user database: Users created in Auth0/Okta (example: auth0|507f1f77bcf86cd799439011)
  • Social/federated identities: Provider links to Google, Facebook (example: google-oauth2|112233445566778899)
  • Enterprise connections: SAML, LDAP, Active Directory (example: samlp|ad|[email protected])

Demo mode: The demo auth server simulates user authentication by generating unique user IDs. This allows testing multi-user scenarios without a real identity provider.

Token Validation

When the MCP server validates an access token:

  1. MCP server calls the auth server's /introspect endpoint with the token
  2. Auth server validates the token and returns user information including the sub field (user ID)
  3. MCP server populates AuthInfo.extra.userId with this user identifier

This separation allows the auth server to be replaced with any OAuth provider that supports token introspection.

Session Creation

const userId = req.auth?.extra?.userId;
const sessionId = randomUUID();
await redisClient.set(`session:${sessionId}:owner`, userId);

Session Access Validation

const owner = await redisClient.get(`session:${sessionId}:owner`);
if (owner !== userId) {
  throw new Error('Session not owned by user');
}

Redis Key Structure

Session Ownership

session:{sessionId}:owner → userId

Example: session:550e8400-...:ownerauth0|507f1f77bcf86cd799439011

Session Liveness

Sessions are considered "live" when an MCP server process is actively handling them. This is tracked via Redis pub/sub subscription counts:

mcp:shttp:toserver:{sessionId} → pub/sub channel

Check if live: PUBSUB NUMSUB mcp:shttp:toserver:{sessionId} returns > 0

When an MCP server starts handling a session, it subscribes to the channel. When it shuts down (gracefully or via crash), Redis automatically removes the subscription.

Security

  • Session isolation: Users can only access sessions they own
  • Ownership persistence: Survives across requests and server restarts
  • Token-based validation: User ID extracted from validated OAuth token
  • Access control: All operations (GET, POST, DELETE) validate ownership

Implementation

Core Functions (src/modules/mcp/services/redisTransport.ts)

export async function setSessionOwner(sessionId: string, userId: string): Promise<void>
export async function isSessionOwnedBy(sessionId: string, userId: string): Promise<boolean>
export async function isLive(sessionId: string): Promise<boolean>
export async function shutdownSession(sessionId: string): Promise<void>

Request Flow

sequenceDiagram
    participant Client
    participant MCP as MCP Server
    participant Redis

    Client->>MCP: POST /mcp (initialize)
    MCP->>MCP: Extract userId from OAuth token
    MCP->>MCP: Generate sessionId
    MCP->>Redis: SET session:{sessionId}:owner = userId
    MCP->>Client: Return sessionId

    Client->>MCP: POST /mcp (with sessionId)
    MCP->>MCP: Extract userId from OAuth token
    MCP->>Redis: GET session:{sessionId}:owner
    Redis-->>MCP: userId
    MCP->>MCP: Verify userId matches token
    MCP->>Client: Process request
Loading

Configuration

Environment Variables

REDIS_URL=redis://localhost:6379
BASE_URI=http://localhost:3232

Redis Monitoring

# List all session owners
redis-cli KEYS "session:*:owner"

# Check specific session ownership
redis-cli GET "session:{sessionId}:owner"

# Check if session is live (actively being handled)
redis-cli PUBSUB NUMSUB "mcp:shttp:toserver:{sessionId}"

# Monitor session operations
redis-cli MONITOR | grep "session:"

Testing

# Test session isolation
npm test -- --testNamePattern="User Session Isolation"

# Test session ownership
npm test -- --testNamePattern="Session Ownership"

References