{"slug":"connectors/MCP_MARKETPLACE_FLOW","title":"MCP Marketplace Integration Flow: Technical Architecture","section":"connectors","sectionLabel":"Connectors","status":"unclassified","type":"document","path":"connectors/MCP_MARKETPLACE_FLOW.mdx","bytes":28804,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n\n> **Tier C — Historical.** This document is a point-in-time snapshot preserved for reference. For current architecture, see [CANONICAL_PLATFORM_ARCHITECTURE.md](../architecture/CANONICAL_PLATFORM_ARCHITECTURE.md).\n\n## Overview\n\nThis document details how IntegrateWise integrates with MCP-aware AI clients (Claude Desktop, ChatGPT, Perplexity, Replit, Lovable) through the MCP protocol, OAuth authentication, and Spine data synchronization.\n\n---\n\n## 1. MCP Server Architecture\n\n### Location\n```\nservices/mcp-server/\n├── src/\n│   ├── index.ts                  # Main server entry\n│   ├── marketplace-setup.ts      # Marketplace app configs\n│   ├── oauth-redirect.ts         # OAuth callback handler\n│   ├── mcp-handlers.ts           # MCP protocol handlers\n│   ├── tools/\n│   │   ├── spine-read.ts         # Read Spine data\n│   │   ├── spine-write.ts        # Write decisions, tasks\n│   │   ├── memory-query.ts       # Query shared memory\n│   │   ├── agent-lee.ts          # Invoke Twin orchestrator\n│   │   └── sync-tools.ts         # Sync with Jira, Asana, etc.\n│   ├── resources/\n│   │   ├── entities.ts           # Spine entities (accounts, opportunities)\n│   │   ├── metrics.ts            # Live metrics from Spine\n│   │   └── memory.ts             # Shared memory resources\n│   └── types.ts                  # MCP types & interfaces\n├── package.json\n├── wrangler.toml\n└── tsconfig.json\n```\n\n### 1.1 MCP Server Initialization\n\n```typescript\n// services/mcp-server/src/index.ts\n\n\nconst server = new Server({\n  name: \"integratewise\",\n  version: \"1.0.0\",\n  description: \"Wired business intelligence for AI agents\"\n});\n\n// Register tools\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n  tools: [\n    {\n      name: \"read_spine\",\n      description: \"Query live entity data from Spine (Salesforce, HubSpot, Stripe, etc.)\",\n      inputSchema: {\n        type: \"object\",\n        properties: {\n          entity_type: { type: \"string\", enum: [\"account\", \"opportunity\", \"contact\", \"invoice\", \"ticket\"] },\n          entity_id: { type: \"string\" },\n          scope: { type: \"string\", enum: [\"all\", \"sales\", \"finance\", \"csm\"] }\n        },\n        required: [\"entity_type\", \"entity_id\"]\n      }\n    },\n    {\n      name: \"query_memory\",\n      description: \"Search shared memory for past decisions and outcomes\",\n      inputSchema: {\n        type: \"object\",\n        properties: {\n          query: { type: \"string\" },\n          scope: { type: \"string\", enum: [\"global\", \"tenant\", \"user\"] },\n          limit: { type: \"number\", default: 10 }\n        },\n        required: [\"query\"]\n      }\n    },\n    {\n      name: \"invoke_agent_lee\",\n      description: \"Get Agent Lee's recommendation on a business decision\",\n      inputSchema: {\n        type: \"object\",\n        properties: {\n          context: { type: \"object\" },\n          request: { type: \"string\" }\n        },\n        required: [\"request\"]\n      }\n    },\n    {\n      name: \"write_decision\",\n      description: \"Log a business decision to the Spine with evidence\",\n      inputSchema: {\n        type: \"object\",\n        properties: {\n          entity_id: { type: \"string\" },\n          decision_type: { type: \"string\" },\n          reasoning: { type: \"string\" },\n          evidence: { type: \"array\" }\n        },\n        required: [\"entity_id\", \"decision_type\", \"reasoning\"]\n      }\n    },\n    {\n      name: \"create_task\",\n      description: \"Create a task in Spine (syncs to Jira, Asana, etc.)\",\n      inputSchema: {\n        type: \"object\",\n        properties: {\n          title: { type: \"string\" },\n          description: { type: \"string\" },\n          assigned_to: { type: \"string\" },\n          due_date: { type: \"string\" },\n          related_entity: { type: \"object\" }\n        },\n        required: [\"title\"]\n      }\n    }\n  ]\n}));\n\n// Register resources\nserver.setRequestHandler(ListResourcesRequestSchema, async () => ({\n  resources: [\n    {\n      uri: \"spine://accounts\",\n      name: \"Accounts\",\n      description: \"All accounts in Spine (from Salesforce, HubSpot, etc.)\"\n    },\n    {\n      uri: \"spine://metrics\",\n      name: \"Live Metrics\",\n      description: \"Active metrics (CSM health, pipeline, etc.)\"\n    },\n    {\n      uri: \"memory://decisions\",\n      name: \"Past Decisions\",\n      description: \"Historical decisions and outcomes\"\n    }\n  ]\n}));\n\n```\n\n---\n\n## 2. OAuth + Continuity Bridge\n\n### Location\n```\nservices/continuity-bridge/\n├── src/\n│   ├── index.ts                 # Main handler\n│   ├── oauth-flow.ts            # OAuth 2.0 PKCE flow\n│   ├── tenant-resolver.ts       # Identify tenant from OAuth context\n│   ├── token-issuer.ts          # JWT token creation\n│   ├── token-validator.ts       # Validate MCP client tokens\n│   └── types.ts                 # OAuth types\n├── wrangler.toml\n└── tsconfig.json\n```\n\n### 2.1 OAuth Flow Sequence\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 1. MCP Client Initialization                               │\n│    (Claude Desktop starts up)                              │\n│                                                             │\n│    Claude MCP Handler                                       │\n│      ├─ Connects to: https://api.integratewise.ai/mcp     │\n│      └─ Receives MCP tools list                            │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 2. Authorization Challenge                                 │\n│    (MCP client makes first request → 401 Unauthorized)    │\n│                                                             │\n│    MCP Tool Call: read_spine(account_123)                  │\n│      │                                                      │\n│      └─ MCP Server checks: missing X-MCP-Token header      │\n│         ↓                                                   │\n│         Returns: {                                          │\n│           error: \"unauthorized\",                           │\n│           auth_url: \"https://integratewise.ai/auth/init\",  │\n│           state: \"xyz123\"                                  │\n│         }                                                   │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 3. User Opens Authorization URL                            │\n│    (Claude opens browser to IntegrateWise OAuth)          │\n│                                                             │\n│    GET /auth/init?state=xyz123                             │\n│      └─ Continuity Bridge returns OAuth authorization page │\n│                                                             │\n│    User sees:                                              │\n│      \"Claude is requesting access to your IntegrateWise    │\n│       business data (Salesforce, HubSpot, Stripe, etc.)\"   │\n│                                                             │\n│      [AUTHORIZE]  [CANCEL]                                 │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 4. User Authorizes                                          │\n│    (User clicks AUTHORIZE)                                 │\n│                                                             │\n│    POST /auth/authorize                                    │\n│      ├─ Email: user@company.com                            │\n│      ├─ Org: Acme Corp                                     │\n│      └─ Capabilities: [\"read_spine\", \"write_spine\"]        │\n│         ↓                                                   │\n│      Continuity Bridge:                                    │\n│        ├─ Resolves tenant (email domain → tenant_abc)     │\n│        ├─ Checks: Is user@company.com in tenant_abc?      │\n│        ├─ If no: Create user in tenant                     │\n│        ├─ Apply capabilities based on role                 │\n│        └─ Generate authorization code                      │\n│           ↓                                                 │\n│      Redirect: /auth/callback?code=AUTH_CODE&state=xyz123 │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 5. Token Exchange                                           │\n│    (Claude exchanges auth code for token)                  │\n│                                                             │\n│    Claude browser redirects: /auth/callback?code=...       │\n│      └─ Claude intercepts and sends to:                    │\n│         POST /auth/token                                   │\n│           ├─ code: AUTH_CODE                               │\n│           ├─ client_id: claude_desktop                     │\n│           └─ code_verifier: PKCE_VERIFIER                 │\n│            ↓                                                │\n│      Continuity Bridge validates PKCE                      │\n│      Issues JWT token:                                     │\n│      {                                                     │\n│        \"sub\": \"user_123\",                                  │\n│        \"tenant\": \"tenant_abc\",                             │\n│        \"email\": \"user@company.com\",                        │\n│        \"capabilities\": [\"read_spine\", \"write_spine\"],      │\n│        \"iat\": 1702700000,                                  │\n│        \"exp\": 1702786400,                                  │\n│        \"iss\": \"integratewise.continuity\"                   │\n│      }                                                     │\n│      ↓                                                      │\n│      Returns: { access_token, expires_in, token_type }   │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 6. MCP Client Stores Token                                 │\n│    (Claude saves token in local config)                    │\n│                                                             │\n│    ~/.config/Claude/integratewise.json                     │\n│      ├─ mcp_server_url: https://api.integratewise.ai/mcp  │\n│      ├─ access_token: JWT_TOKEN                            │\n│      ├─ tenant_id: tenant_abc                              │\n│      ├─ user_id: user_123                                  │\n│      └─ expires_at: 1702786400                             │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│ 7. Authenticated MCP Calls                                 │\n│    (Claude can now call MCP tools)                         │\n│                                                             │\n│    Claude MCP Client:                                      │\n│      POST /mcp/call                                        │\n│        ├─ Authorization: Bearer JWT_TOKEN                  │\n│        ├─ tool: \"read_spine\"                               │\n│        ├─ params: { entity_type: \"account\", id: \"123\" }   │\n│        └─ ↓                                                 │\n│                                                             │\n│      MCP Server validates token:                           │\n│        ├─ Extract tenant from JWT                          │\n│        ├─ Extract user from JWT                            │\n│        ├─ Check capabilities (has \"read_spine\"?)           │\n│        ├─ Scope Spine query to tenant                      │\n│        └─ ↓                                                 │\n│                                                             │\n│      Query Spine for account (with RLS):                   │\n│        WHERE tenant_id = 'tenant_abc'                      │\n│        AND id = '123'                                      │\n│        ↓                                                    │\n│                                                             │\n│      Returns: {                                            │\n│        id: \"acc_123\",                                      │\n│        name: \"Acme Corp\",                                  │\n│        revenue: 5000000,                                   │\n│        industry: \"Technology\",                             │\n│        crm: \"Salesforce\",                                  │\n│        billing: \"Stripe\",                                  │\n│        support: \"Zendesk\"                                  │\n│      }                                                     │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### 2.2 Continuity Bridge Implementation\n\n```typescript\n// services/continuity-bridge/src/index.ts\n\n\nconst router = Router();\n\n// 1. OAuth init endpoint\nrouter.get(\"/auth/init\", async (req: Request, env: Env) => {\n  const state = generateState();\n  const codeVerifier = generateCodeVerifier();\n  const codeChallenge = base64UrlEncode(sha256(codeVerifier));\n\n  // Store in KV (short TTL)\n  await env.KV.put(`oauth_state:${state}`, codeVerifier, { expirationTtl: 600 });\n\n  return json({\n    oauth_url: `https://integratewise.ai/auth/authorize?state=${state}&code_challenge=${codeChallenge}`,\n    state\n  });\n});\n\n// 2. Authorization endpoint\nrouter.post(\"/auth/authorize\", async (req: Request, env: Env) => {\n  const { email, org, state } = await req.json();\n\n  // Verify state\n  const codeVerifier = await env.KV.get(`oauth_state:${state}`);\n  if (!codeVerifier) throw new Error(\"Invalid state\");\n\n  // Resolve tenant\n  const emailDomain = email.split(\"@\")[1];\n  let tenant = await env.DB.prepare(\n    \"SELECT id FROM tenants WHERE domain = ?\"\n  ).bind(emailDomain).first();\n\n  if (!tenant) {\n    // Create tenant\n    tenant = await env.DB.prepare(\n      \"INSERT INTO tenants (domain, name) VALUES (?, ?) RETURNING id\"\n    ).bind(emailDomain, org).first();\n  }\n\n  // Ensure user exists\n  let user = await env.DB.prepare(\n    \"SELECT id FROM users WHERE email = ? AND tenant_id = ?\"\n  ).bind(email, tenant.id).first();\n\n  if (!user) {\n    user = await env.DB.prepare(\n      \"INSERT INTO users (email, tenant_id, role) VALUES (?, ?, ?) RETURNING id\"\n    ).bind(email, tenant.id, \"member\").first();\n  }\n\n  // Generate auth code\n  const authCode = generateAuthCode();\n  await env.KV.put(\n    `auth_code:${authCode}`,\n    JSON.stringify({ user_id: user.id, tenant_id: tenant.id }),\n    { expirationTtl: 300 }\n  );\n\n  return json({\n    code: authCode,\n    state,\n    redirect_uri: `https://claude-desktop.integratewise.ai/callback?code=${authCode}&state=${state}`\n  });\n});\n\n// 3. Token exchange endpoint\nrouter.post(\"/auth/token\", async (req: Request, env: Env) => {\n  const { code, client_id, code_verifier } = await req.json();\n\n  // Validate auth code\n  const authData = await env.KV.get(`auth_code:${code}`);\n  if (!authData) throw new Error(\"Invalid code\");\n\n  const { user_id, tenant_id } = JSON.parse(authData);\n\n  // Get user details for JWT\n  const user = await env.DB.prepare(\n    \"SELECT id, email, role FROM users WHERE id = ? AND tenant_id = ?\"\n  ).bind(user_id, tenant_id).first();\n\n  // Determine capabilities based on role\n  const capabilities = getCapabilitiesForRole(user.role);\n\n  // Create JWT\n  const token = await sign(\n    {\n      sub: user.id,\n      tenant: tenant_id,\n      email: user.email,\n      capabilities,\n      client_id\n    },\n    env.JWT_SECRET,\n    { algorithm: \"HS256\", expirationTime: \"24h\" }\n  );\n\n  return json({\n    access_token: token,\n    token_type: \"Bearer\",\n    expires_in: 86400\n  });\n});\n\n// 4. Token validation middleware\nrouter.use(\"*\", async (req: Request, env: Env) => {\n  const authHeader = req.headers.get(\"Authorization\");\n  if (!authHeader?.startsWith(\"Bearer \")) {\n    return new Response(json({ error: \"Unauthorized\" }), { status: 401 });\n  }\n\n  const token = authHeader.slice(7);\n  try {\n    const decoded = await verify(token, env.JWT_SECRET);\n    (req as any).user = decoded;\n    (req as any).tenant = decoded.tenant;\n  } catch (e) {\n    return new Response(json({ error: \"Invalid token\" }), { status: 401 });\n  }\n});\n\n```\n\n---\n\n## 3. Spine → MCP Tool Adapter\n\n### 3.1 read_spine Tool\n\n```typescript\n// services/mcp-server/src/tools/spine-read.ts\n\nexport async function read_spine(\n  params: { entity_type: string; entity_id: string; scope?: string },\n  env: Env,\n  user: any\n) {\n  const { entity_type, entity_id, scope = \"all\" } = params;\n\n  // Scope query to tenant\n  const tenant_id = user.tenant;\n\n  // Get entity from Spine\n  const entity = await env.DB.prepare(\n    `SELECT * FROM ${entity_type}s WHERE id = ? AND tenant_id = ?`\n  ).bind(entity_id, tenant_id).first();\n\n  if (!entity) {\n    throw new Error(`${entity_type} not found`);\n  }\n\n  // Apply scope filtering\n  if (scope === \"sales\") {\n    return filterBySalesScope(entity);\n  } else if (scope === \"finance\") {\n    return filterByFinanceScope(entity);\n  } else if (scope === \"csm\") {\n    return filterByCSMScope(entity);\n  }\n\n  return entity;\n}\n\n// Scope helpers\nfunction filterBySalesScope(entity: any) {\n  return {\n    id: entity.id,\n    name: entity.name,\n    revenue: entity.revenue,\n    deal_count: entity.deal_count,\n    arr_current: entity.arr_current,\n    logo_status: entity.logo_status,\n    csm_assigned: entity.csm_assigned,\n    renewal_date: entity.renewal_date,\n    expansion_opportunity: entity.expansion_opportunity\n  };\n}\n\nfunction filterByFinanceScope(entity: any) {\n  return {\n    id: entity.id,\n    name: entity.name,\n    revenue: entity.revenue,\n    arr_current: entity.arr_current,\n    arr_forecast: entity.arr_forecast,\n    mrr: entity.mrr,\n    churn_rate: entity.churn_rate,\n    payment_terms: entity.payment_terms,\n    overdue_invoices: entity.overdue_invoices\n  };\n}\n\nfunction filterByCSMScope(entity: any) {\n  return {\n    id: entity.id,\n    name: entity.name,\n    csm_assigned: entity.csm_assigned,\n    health_score: entity.health_score,\n    nps_score: entity.nps_score,\n    support_tickets_open: entity.support_tickets_open,\n    renewal_date: entity.renewal_date,\n    churn_risk: entity.churn_risk\n  };\n}\n```\n\n### 3.2 write_decision Tool\n\n```typescript\n// services/mcp-server/src/tools/spine-write.ts\n\nexport async function write_decision(\n  params: {\n    entity_id: string;\n    decision_type: string;\n    reasoning: string;\n    evidence?: any[];\n  },\n  env: Env,\n  user: any\n) {\n  const { entity_id, decision_type, reasoning, evidence = [] } = params;\n  const tenant_id = user.tenant;\n  const user_id = user.sub;\n\n  // Create decision event\n  const decision_id = generateId();\n  const now = new Date().toISOString();\n\n  // Write to Spine decisions table\n  await env.DB.prepare(\n    `INSERT INTO decisions (id, tenant_id, user_id, entity_id, decision_type, reasoning, evidence, created_at)\n     VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n  ).bind(\n    decision_id,\n    tenant_id,\n    user_id,\n    entity_id,\n    decision_type,\n    reasoning,\n    JSON.stringify(evidence),\n    now\n  ).run();\n\n  // Emit event to Hermes (event stream)\n  await env.HERMES.emit(\"decision.created\", {\n    id: decision_id,\n    tenant_id,\n    user_id,\n    entity_id,\n    decision_type,\n    reasoning,\n    evidence,\n    timestamp: now\n  });\n\n  // Update metrics\n  await updateMetricsFromDecision(env, tenant_id, decision_id);\n\n  // Update memory\n  await updateMemoryFromDecision(env, tenant_id, decision_id, reasoning);\n\n  return {\n    success: true,\n    decision_id,\n    timestamp: now\n  };\n}\n\nasync function updateMetricsFromDecision(env: Env, tenant_id: string, decision_id: string) {\n  // Example: If decision_type is \"pursue\", update \"deals_pursued\" metric\n  await env.DB.prepare(\n    `UPDATE metrics SET value = value + 1, last_updated_at = NOW()\n     WHERE tenant_id = ? AND name = 'deals_pursued'`\n  ).bind(tenant_id).run();\n}\n\nasync function updateMemoryFromDecision(env: Env, tenant_id: string, decision_id: string, reasoning: string) {\n  // Save to shared memory (searchable for future conversations)\n  const memory_id = generateId();\n  await env.SHARED_MEMORY_DO.put(\n    `memory:${memory_id}`,\n    {\n      decision_id,\n      reasoning,\n      timestamp: Date.now(),\n      tenant_id\n    },\n    { expirationTtl: 7776000 } // 90 days\n  );\n}\n```\n\n---\n\n## 4. Multi-Platform Integrations\n\n### 4.1 Claude Desktop\n\n**Integration Point:** MCP server URL in Claude config\n\n```json\n{\n  \"mcpServers\": {\n    \"integratewise\": {\n      \"command\": \"node\",\n      \"args\": [\"./dist/mcp-server.js\"],\n      \"env\": {\n        \"MCP_SERVER_URL\": \"https://api.integratewise.ai/mcp\",\n        \"CLIENT_ID\": \"claude_desktop\",\n        \"AUTH_CALLBACK\": \"https://claude-desktop.integratewise.ai/callback\"\n      }\n    }\n  }\n}\n```\n\n### 4.2 ChatGPT / OpenAI\n\n**Integration Point:** Custom action (schema-based)\n\n```json\n{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"title\": \"IntegrateWise\",\n    \"description\": \"Query live business data from Salesforce, HubSpot, Stripe, etc.\",\n    \"version\": \"1.0.0\"\n  },\n  \"servers\": [\n    {\n      \"url\": \"https://api.integratewise.ai\",\n      \"description\": \"IntegrateWise API\"\n    }\n  ],\n  \"paths\": {\n    \"/mcp/read_spine\": {\n      \"post\": {\n        \"summary\": \"Query Spine entity\",\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"type\": \"object\",\n                \"properties\": {\n                  \"entity_type\": { \"type\": \"string\" },\n                  \"entity_id\": { \"type\": \"string\" }\n                }\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Entity data\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": { \"type\": \"object\" }\n              }\n            }\n          }\n        },\n        \"security\": [\n          { \"oauth2\": [\"read_spine\"] }\n        ]\n      }\n    }\n  }\n}\n```\n\n### 4.3 Perplexity, Replit, Lovable\n\n**Integration Point:** Shared MCP endpoint\n\nAll clients connect to: `https://api.integratewise.ai/mcp` with OAuth token\n\n---\n\n## 5. Data Flow: Decision Logging\n\n### Complete End-to-End Flow\n\n```\nUser (Claude): \"Should we pursue Acme Corp?\"\n    │\n    ▼\nClaude MCP Client (calls tools):\n    ├─ read_spine(account_id, scope=sales)\n    │  └─ Returns: { name, revenue, csm_health, renewal_date }\n    │\n    ├─ query_memory(\"similar_companies_similar_revenue\")\n    │  └─ Returns: [ { outcome: \"pursued\", result: \"won\" }, ... ]\n    │\n    ├─ invoke_agent_lee({ context, request })\n    │  └─ Returns: { proposal: \"PURSUE\", confidence: 0.88, reasoning: \"...\" }\n    │\n    └─ write_decision({\n         entity_id: \"acme_corp\",\n         decision_type: \"pursue\",\n         reasoning: \"Strong health, good renewal timing, expansion opportunity\",\n         evidence: [\n           { source: \"HubSpot\", field: \"csm_health\", value: 0.9 }\n         ]\n       })\n       │\n       ▼\n    MCP Server (in Cloudflare):\n       ├─ Validate token (JWT)\n       ├─ Scope to tenant\n       ├─ Insert into decisions table\n       │\n       ▼\n    Spine (D1 Database):\n       ├─ INSERT decisions row\n       ├─ INSERT events row\n       └─ Emit event to Hermes\n       │\n       ▼\n    Hermes Event Stream (broadcast):\n       ├─ → Twin Orchestrator (Agent Lee learns)\n       ├─ → ChatGPT MCP Client (sees same decision)\n       ├─ → Perplexity MCP Client (sees same decision)\n       ├─ → Memory indexer (searchable)\n       └─ → Metrics updater (trends)\n       │\n       ▼\n    Connected Systems (real sync):\n       ├─ → Salesforce (log activity)\n       ├─ → Jira (create follow-up task)\n       ├─ → Slack (post to #sales)\n       └─ → Dashboard (real-time update)\n```\n\n---\n\n## 6. Security & Multi-Tenancy\n\n### Token Security\n- JWT tokens signed with HS256\n- 24-hour expiration\n- User + tenant scoped\n- Capabilities-based access control\n\n### Row-Level Security (RLS)\n- Every Spine query scoped to `tenant_id = ?`\n- MCP server extracts tenant from JWT\n- No cross-tenant data leakage\n\n### Audit Trail\n- All MCP tool calls logged (tool name, params, result)\n- All Spine writes tracked (user, timestamp, decision)\n- Events immutable in Hermes stream\n\n---\n\n## 7. Implementation Checklist\n\n- [ ] Continuity Bridge deployed (OAuth working)\n- [ ] MCP server handles concurrent requests\n- [ ] Spine write paths tested (no data loss)\n- [ ] Claude Desktop app approved\n- [ ] ChatGPT action created and tested\n- [ ] Perplexity integration working\n- [ ] Replit integration working\n- [ ] Token refresh logic implemented\n- [ ] Error handling for auth failures\n- [ ] Monitoring dashboards live\n\n"}