Descope Connection Hub Architecture (Refined v1.1)
Status: SUPERSEDED AUTH SNAPSHOT. Descope is not the canonical user or
workspace identity root. Clerk identity is verified at Gateway; connector
authorization follows the current provider-management/connection contract.
See
docs/architecture/IMPLEMENTATION_CONTRACT_v1.1.md.
Original Status: CANONICAL — supersedes DESCOPE_CONNECTION_HUB.md and DESCOPE_ARCHITECTURE_SUMMARY.md
Date: July 17, 2026
Version: v1.1 (Provider-Abstracted with Refined Ownership)
Canonical Path: docs/architecture/CONNECTION_ARCHITECTURE.md
Canonical Ownership Model
Clear separation of concerns:
| Layer | Owner | Responsibility |
|---|---|---|
| User authentication | Descope | Login, SSO, MFA, session management |
| OAuth authorization | Descope | User consent, authorization flow |
| Access tokens | Descope | Token generation, signing, validation |
| Refresh tokens | Descope | Token refresh, rotation, expiry |
| Credential refresh | Descope | Auto-refresh expired credentials |
| Authorization references | Descope | descope:github:auth_123 generation |
| Connection metadata | IntegrateWise | Status, labels, policies, last used |
| Continuity | IntegrateWise | Memory, audit trail, context |
| Capability routing | MCP Pool | Route selection, provider dispatch |
| Governance | IntegrateWise | Authority checks, approval workflows |
| Memory / Spine | IntegrateWise | Persistent state, knowledge base |
What Lives in the Spine
Provider-neutral Connection records (no vendor lock-in):
{
"connectionId": "github-primary",
"provider": "github",
"credentialProvider": "descope",
"authorizationRef": "descope:github:auth_123",
"status": "connected",
"capabilities": ["repo.read", "repo.write"],
"lastValidatedAt": "2026-07-17T14:32:00Z",
"health": "healthy",
"owner": "user_123",
"tenantId": "tenant_456",
"createdAt": "2026-07-17T10:00:00Z",
"lastUsedAt": "2026-07-17T14:30:00Z"
}
Key constraint: authorizationRef is the only bridge to Descope. IW never stores credentials directly.
Runtime Execution Pattern
Every capability execution follows the same flow:
Hermes (orchestrator)
│
▼
Capability Selection (authorizationRef required)
│
▼
Provider Management MCP ◄─── ALL PROVIDER LOGIC CONFINED HERE
│
├─ Descope Adapter (resolve authorizationRef → credentials)
│ │
│ ▼
│ Descope (returns fresh credentials)
│ │
│ ▼
│ Temporary Credential (in-memory, never persisted)
│
├─ Provider API (GitHub, Salesforce, Slack, etc.)
│ │
│ ▼
│ Operation Result
│
▼
Credential Destroyed (immediately after use)
│
▼
Result returned to Hermes
│
▼
Continuity updated (operation record + outcome)
Principle: No access token persists beyond execution scope.
Connection Abstraction
Business logic never couples to Descope concepts:
interface Connection {
id: string; // UUID
provider: string; // "github", "salesforce", "slack"
authorizationRef: string; // "descope:github:auth_123" or provider-agnostic
status: "connected" | "disconnected" | "error" | "refreshing";
scopes: string[]; // ["repo.read", "repo.write"]
owner: string; // user_id
tenantId: string;
createdAt: Date;
lastUsedAt: Date;
}
Future-proofing: Replace Descope with Auth0, Okta, or internal service without touching Hermes, Capability Fabric, or providers.
Architecture Layers
Users
│
▼
Descope Auth (inbound: login, SSO, OAuth)
│
▼
Gateway (mapDescopePrincipal, context envelope)
│
▼
Continuity Bridge (tenant_id, user_id, roles, permissions)
│
▼
MCP Pool (routing dispatcher)
│
▼
Provider Management MCP ◄─── ABSTRACTION LAYER (single source of provider truth)
│
├─ Descope Adapter (for V0; future: Auth0 Adapter, Okta Adapter, etc.)
│
├─ Provider Implementations (GitHub, Salesforce, Slack, HubSpot, Linear, etc.)
│
└─ Connection Health Checks
│
▼
External APIs (GitHub, Salesforce, Slack, etc.)
Connection Flow (OAuth to Execution)
1. User Initiates Connection
User clicks "Connect GitHub"
↓
Governance checks: does user have INTEGRATION_CONNECT authority?
↓
If yes: Provider Management MCP routes to Descope OAuth
If no: Error (no permission)
2. OAuth Authorization
User redirected to Descope OAuth consent
↓
User grants permissions (scopes)
↓
Descope callback to /api/v1/provider-management/callback
↓
Provider Management MCP receives authorizationRef: "descope:github:auth_123"
3. Connection Stored in Spine
Provider Management MCP returns to Gateway
↓
Gateway creates Connection record:
{
id: "github-primary",
provider: "github",
authorizationRef: "descope:github:auth_123",
status: "connected",
...
}
↓
Spine stores Connection (NO credentials, only authorizationRef)
4. Later: Capability Execution
User triggers "Merge PR" (capability)
↓
Capability Fabric selects route: provider=github, authorizationRef=descope:github:auth_123
↓
Continuity Bridge routes to Provider Management MCP with authorizationRef
↓
Provider Management MCP → Descope Adapter: resolve(authorizationRef)
↓
Descope returns fresh credentials (auto-refreshed if expired)
↓
GitHub provider executes with credentials
↓
Result returned
↓
Credentials destroyed (NOT stored)
Components
1. Provider Management MCP (New Core)
File: services/mcp-connector/src/providers/provider-management/index.ts
Responsibilities:
- Route OAuth flows for all providers
- Abstract provider-specific logic
- Resolve authorizationRef to fresh credentials
- Health checks for all connections
- Credential lifecycle management
export interface ProviderManagementMCP {
// OAuth flows
initiateConnection(provider: string, user: User): Promise<OAuthUrl>;
handleCallback(provider: string, code: string, state: string): Promise<Connection>;
// Credential resolution
resolveCredentials(authorizationRef: string): Promise<Credentials>;
// Connection management
listConnections(user: User): Promise<Connection[]>;
updateConnectionStatus(connectionId: string, status: string): Promise<void>;
deleteConnection(connectionId: string): Promise<void>;
// Health checks
checkHealth(connectionId: string): Promise<HealthStatus>;
}
2. Descope Adapter (V0 Implementation)
File: services/mcp-connector/src/providers/descope-adapter/index.ts
Implements Provider Management MCP interface for Descope:
export class DescopeAdapter implements ProviderAdapter {
async resolveCredentials(authorizationRef: string): Promise<Credentials> {
// Call Descope Management API: GET /v1/auth/connection/{authorizationRef}
const connection = await descopeClient.getConnection(authorizationRef);
// Refresh token if expired
if (connection.expiresAt < Date.now()) {
connection = await descopeClient.refreshToken(authorizationRef);
}
return {
accessToken: connection.accessToken,
refreshToken: connection.refreshToken,
expiresAt: connection.expiresAt,
provider: connection.provider,
};
}
async initiateOAuth(provider: string): Promise<string> {
const state = crypto.randomUUID();
const url = `https://descope.com/oauth/${provider}?state=${state}`;
await kv.put(`oauth_state_${state}`, provider, { expirationTtl: 600 });
return url;
}
async handleCallback(code: string, state: string): Promise<Connection> {
const provider = await kv.get(`oauth_state_${state}`);
if (!provider) throw new Error("Invalid state");
const authResult = await descopeClient.exchangeCode({
provider,
code,
state,
});
return {
provider,
authorizationRef: authResult.authorizationRef,
status: "connected",
scopes: authResult.scopes,
};
}
}
3. Spine Connection Model
File: apps/web/src/data/knowledge.ts
export interface Connection {
id: string;
provider: string;
authorizationRef: string;
status: "connected" | "disconnected" | "error" | "refreshing";
capabilities: string[];
lastValidatedAt: Date;
health: "healthy" | "unhealthy" | "unknown";
owner: string;
tenantId: string;
createdAt: Date;
lastUsedAt: Date;
metadata?: Record<string, any>;
}
Implementation Priority
| Priority | Task | Timeline | Blocker |
|---|---|---|---|
| 1 | Create Provider Management MCP interface & contract | 2 days | None |
| 2 | Wire /api/v1/provider-management/callback into Gateway | 1 day | Priority 1 |
| 3 | Implement Descope Adapter (V0) | 3 days | Priority 1, 2 |
| 4 | Create Spine Connection model + migrations | 2 days | Priority 3 |
| 5 | Update all providers to use Provider Management MCP | 5 days | Priority 3, 4 |
| 6 | Test end-to-end: connect → execute → health check | 3 days | Priority 5 |
Total: 2-3 weeks
V0 Ready: After Priority 4 (Connection model in Spine)
Production Ready: After Priority 6
Benefits of This Architecture
✅ Zero vendor lock-in: Descope is an implementation detail, not architecture
✅ Provider-neutral: Replace Descope without changing business logic
✅ Clear ownership: Identity = Descope, Operations = IntegrateWise
✅ Secure: No credentials in IW state, temporary in-memory credentials only
✅ Auditable: All operations recorded in Spine + Descope audit trail
✅ Scalable: Add providers by implementing ProviderAdapter interface
✅ Governance enforced: Authority checks at every connection point
✅ Future-proof: Architecture supports Auth0, Okta, internal SSO, etc.
Comparison: V1.0 vs V1.1
| Aspect | V1.0 | V1.1 |
|---|---|---|
| Descope role | Single source of truth | Identity + credential provider |
| IntegrateWise role | State storage only | Operations + governance + continuity |
| Provider abstraction | None (Descope-specific) | Provider Management MCP |
| Extensibility | Add more Descope apps | Add ProviderAdapter implementations |
| Vendor lock-in | High (tightly coupled) | Low (pluggable adapters) |
| Future migration | Difficult | Straightforward (new adapter) |
References
- Descope Outbound Connections: https://docs.descope.com/build/outbound-connections/
- MCP Descope Adapter:
services/mcp-connector/src/providers/descope/ - Continuity Bridge:
services/mcp-connector/src/continuity-bridge.ts - Integration Manager:
apps/web/src/components/connectivity/integrations-hub.tsx