Unclassified

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:

LayerOwnerResponsibility
User authenticationDescopeLogin, SSO, MFA, session management
OAuth authorizationDescopeUser consent, authorization flow
Access tokensDescopeToken generation, signing, validation
Refresh tokensDescopeToken refresh, rotation, expiry
Credential refreshDescopeAuto-refresh expired credentials
Authorization referencesDescopedescope:github:auth_123 generation
Connection metadataIntegrateWiseStatus, labels, policies, last used
ContinuityIntegrateWiseMemory, audit trail, context
Capability routingMCP PoolRoute selection, provider dispatch
GovernanceIntegrateWiseAuthority checks, approval workflows
Memory / SpineIntegrateWisePersistent 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

PriorityTaskTimelineBlocker
1Create Provider Management MCP interface & contract2 daysNone
2Wire /api/v1/provider-management/callback into Gateway1 dayPriority 1
3Implement Descope Adapter (V0)3 daysPriority 1, 2
4Create Spine Connection model + migrations2 daysPriority 3
5Update all providers to use Provider Management MCP5 daysPriority 3, 4
6Test end-to-end: connect → execute → health check3 daysPriority 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

AspectV1.0V1.1
Descope roleSingle source of truthIdentity + credential provider
IntegrateWise roleState storage onlyOperations + governance + continuity
Provider abstractionNone (Descope-specific)Provider Management MCP
ExtensibilityAdd more Descope appsAdd ProviderAdapter implementations
Vendor lock-inHigh (tightly coupled)Low (pluggable adapters)
Future migrationDifficultStraightforward (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