Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions development/satellite/hierarchical-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ Instead of exposing all tools directly, the satellite exposes only 2 meta-tools:
- 37.5% of 200k context consumed

**After (Hierarchical):**
- 2 meta-tools × 175 tokens = 350 tokens
- 0.175% of 200k context consumed
- **99.5% reduction**
- 2 tools × 686 tokens = 1372 tokens consumed
- Result: 0.686% of context window used
- **Token Reduction: 98.3%**

## Meta-Tool Specifications

Expand Down Expand Up @@ -527,8 +527,8 @@ Next time the client calls `discover_mcp_tools`, the new tools are automatically
| Metric | Traditional | Hierarchical | Reduction |
|--------|-------------|--------------|-----------|
| Tools Exposed | 150 | 2 | 98.7% |
| Tokens Consumed | 75,000 | 350 | 99.5% |
| Context Available | 62.5% | 99.8% | +37.3% |
| Tokens Consumed | 75,000 | 1372 | 98.2% |
| Context Available | 62.5% | 99.3% | +36.8% |

### Search Performance

Expand All @@ -541,8 +541,8 @@ Next time the client calls `discover_mcp_tools`, the new tools are automatically

**Claude Code Example:**
- Before: 82,000 tokens (41%) consumed by MCP tools
- After: 350 tokens (0.175%) consumed by meta-tools
- Result: **81,650 tokens freed for actual work**
- After: 1372 tokens (0.686%) consumed by meta-tools
- Result: **80,628 tokens freed for actual work**

## Implementation Status

Expand Down
88 changes: 88 additions & 0 deletions development/satellite/oauth-authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,94 @@ const response = await fetch(`${backendUrl}/api/oauth2/introspect`, {
- `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
// 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
Expand Down
97 changes: 53 additions & 44 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
"dependencies": {},
"devDependencies": {
"@semantic-release/github": "^12.0.2",
"@types/node": "24.9.1",
"@types/node": "24.10.1",
"markdownlint-cli": "^0.45.0",
"markdownlint-cli2": "^0.18.1",
"markdownlint-cli2": "^0.19.0",
"typescript": "5.9.3"
}
}
Loading