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