MCP Integration
Model Context Protocol (MCP) integration for the Oktivo platform. Connects AI agents to external tools (Google Calendar, HubSpot CRM, custom MCP servers) during live calls.
How It Works
- Superadmin enables
mcp_integrationsfeature for an Organization - Admin assigns the feature to a Customer
- Customer connects MCP integrations (global or per-agent) via the UI
- On call dispatch, backend resolves active integrations → decrypts credentials → embeds
mcp_servers[]in LiveKit metadata - Agent Server creates
MCPServerHTTPinstances → passes toAgentSession→ LLM can invoke MCP tools during conversation
Step-by-Step UI Testing
1. Enable Feature (Superadmin)
- Login as superadmin
- Go to Organizations page
- Click edit on the target org
- In the Features grid, check mcp integrations
- Save
What happens: mcp_integrations is added to org.plan.features[] in MongoDB.
2. Assign Feature to Customer (Admin)
- Login as admin of that org
- Go to Customers page
- Click edit on the target customer
- In the Features list, check mcp integrations
- Save
What happens: mcp_integrations is added to user.features[] in MongoDB. The customer can now access MCP endpoints.
3. Connect an Integration (Customer)
- Login as the customer
- Click MCP Integrations in the sidebar (plug icon)
- You see the Available Integrations catalog:
- Google Calendar
- HubSpot CRM
- Generic MCP Server
- Click Connect on any card
- Fill in credentials (e.g., Composio API key)
- Select scope: Global (all agents) or Agent-specific (one agent)
- Click Test Connection backend resolves the URL and attempts an MCP handshake (10s timeout)
- If test passes, click Connect
What happens:
POST /api/v2/mcp/integrationsis called- Backend validates catalogId, encrypts credentials (AES-256-GCM), stores
McpIntegrationdocument - Integration appears in Active Integrations section with masked credentials
4. Verify in a Call
- Go to Agents → open any agent → click Test Call
- The call dispatches to LiveKit
What happens behind the scenes:
calling.service.ts/agent.controller.tscallsresolveMcpServersForDispatch(customerUID, agentId)- Queries all active global + agent-scoped integrations
- Decrypts credentials, resolves
serverUrltemplates (e.g.,{{api_key}}→ actual key) - Embeds
mcp_servers[]in dispatch metadata - Agent Server parses metadata → creates
mcp.MCPServerHTTPinstances → passes toAgentSession - LLM can now invoke tools exposed by the MCP server during the conversation
Quick Local Test
- Run a test MCP server:bash
npx -y supergateway --stdio "npx -y @modelcontextprotocol/server-everything" --port 8888 - In MCP Integrations page, create Generic MCP Server:
- Server URL:
http://host.docker.internal:8888/sse(usehost.docker.internalfor Docker) - Auth Token: leave empty
- Server URL:
- Click Test Connection → green success
- Click Connect
- Make a test call → check Agent Server logs for
Created MCP server: Generic MCP Server - During the call, say "use the echo tool to repeat hello"
- Check supergateway terminal for incoming connections
5. Delete an Integration
- In MCP Integrations page, click the trash icon on any active integration
- Confirm deletion
What happens: Soft-delete sets deletedAt timestamp. Integration no longer appears in lists or dispatch resolution.
6. Agent Deletion Cascade
When you delete an agent, all its agent-scoped MCP integrations are automatically soft-deleted.
7. Agent Cloning
When you clone an agent, MCP integrations are NOT cloned (credentials are sensitive). You must re-configure them on the cloned agent.
Architecture
Outbound/Test Call Flow
Inbound Call Flow
Feature Gate
Same two-layer pattern as dynamic_tools:
Superadmin → adds "mcp_integrations" to org.plan.features[]
Admin → assigns "mcp_integrations" to customer.features[]
Customer → can access /api/v2/mcp/* endpointsBackend enforcement: requireFeatureAccess(...FEATURE_MAP.manageMcpIntegrations) middleware on all public MCP routes. Returns 403 if feature not assigned.
Superadmins bypass all feature checks. Admins can access if org has the feature.
Data Model
McpIntegration Collection
| Field | Type | Description |
|---|---|---|
integrationId | String | Unique, prefix mcpi_ |
catalogId | String | References catalog entry (e.g., google-calendar) |
customerUID | String | Tenant isolation |
orgUID | String | Organization |
scope | Enum | "global" or "agent" |
agentId | String/null | Agent ID when scope=agent, null when global |
credentials | Object | AES-256-GCM encrypted key-value pairs |
isActive | Boolean | Default true |
deletedAt | Date/null | Soft-delete timestamp |
Indexes
{ customerUID: 1, deletedAt: 1 }list by customer{ customerUID: 1, scope: 1, agentId: 1, deletedAt: 1 }list by customer+agent{ catalogId: 1, customerUID: 1, scope: 1, agentId: 1 }unique partial (deletedAt: null), prevents duplicates{ agentId: 1, deletedAt: 1 }cascade delete on agent deletion
Credential Handling
Create/Update → encryptSecret(value) per field → stored encrypted in MongoDB
Dispatch/Internal API → decryptSecret(value) per field → used to resolve serverUrl
Public API response → maskCredentials(value) → "sk-a****" (first 4 chars + ****)
Decryption failure → skip that integration, log error, continue (graceful degradation)API Endpoints
Public (JWT + mcp_integrations feature gate)
| Method | Path | Description |
|---|---|---|
| GET | /api/v2/mcp/catalog | List catalog entries |
| POST | /api/v2/mcp/integrations | Create integration |
| GET | /api/v2/mcp/integrations | List active (optional ?agentId=X for global+agent) |
| GET | /api/v2/mcp/integrations/:id | Get single |
| PUT | /api/v2/mcp/integrations/:id | Update (re-encrypts credentials) |
| DELETE | /api/v2/mcp/integrations/:id | Soft-delete |
| POST | /api/v2/mcp/integrations/verify | Test connection (10s timeout) |
Internal (x-api-secret auth)
| Method | Path | Description |
|---|---|---|
| GET | /internal/mcp-servers/:agentId/:customerUID | Resolved servers with decrypted credentials |
File Structure
Client Backend
client_backend/src/
├── types/mcp-integration.types.ts # Interfaces
├── config/mcp-catalog.ts # Static catalog (Google Calendar, HubSpot, Generic)
├── models/McpIntegration.ts # Mongoose schema + indexes
├── repositories/mcp-integration.repository.ts
├── services/mcp-integration.service.ts # Business logic
├── controllers/mcp-integration.controller.ts
├── routes/mcp-integration.routes.ts # Public routes
└── routes/mcp-integration-internal.routes.ts # Internal routesAgent Server
Agent/src/
├── schemas/metadata_schema.py # McpServerDef + MetadataSchema.mcp_servers
└── core/
├── mcp_servers.py # MCPServerHTTP creation + fetch from API
├── session_builder.py # Passes mcp_servers to AgentSession
└── voice_assistant.py # Loads MCP servers in __init__Frontend
Integrations are configured from the unified Tools page. The standalone /mcp-integrations page was deleted in 2026-08 and its route now redirects to /tools see TOOLS-AND-LOGS.md.
app/src/
├── lib/features.ts # MCP_INTEGRATIONS feature constant
├── services/mcpIntegration.service.ts # API client + tool call logs
├── components/features/tools/IntegrationToolForm.tsx # Setup form (registered in TOOL_TYPE_CONFIGS)
├── components/features/tools/tool-registry.ts # list / get / remove / setActive adapter
├── components/features/tools/logs/
│ └── IntegrationLogsSection.tsx # /logs → Integration Logs tab
├── components/features/McpTabContent.tsx # Agent Editor → MCP sub-tab (overlaps /tools)
└── routes.tsx # /tools route; /mcp-integrations redirectsTool Call Logging
MCP tool calls go straight from the Agent Server to the external MCP server, so the backend never sees the HTTP exchange and there is no status code or header detail to record. Agent/src/core/mcp_tool_logging.py instead listens for LiveKit's function_tools_executed event and reports the tool name, the arguments the LLM supplied, the result returned and the latency to POST /internal/mcp-tool-call-logs.
Only MCP-owned tools are reported toolset ids are set to the McpIntegration id in mcp_servers.create_mcp_toolsets, which is how the backend resolves the catalog entry for display. Logging is fire-and-forget so a logging failure can never affect the conversation.
| Field | Notes |
|---|---|
integrationId / catalogId | catalogId resolved server-side for display |
toolName | As exposed by the MCP server (e.g. create_event) |
requestArguments / responseBody | JSON-stringified, truncated to 8KB |
status | success or failed no queued state exists |
callRoomId | LiveKit room, correlates with the Contact and recording |
Stored in McpToolCallLog with a 30-day TTL. Read via GET /api/v2/mcp/tool-call-logs (JWT + mcp_integrations), surfaced at /logs → Integration Logs.
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| No "MCP Integrations" in sidebar | Feature not assigned to customer | Superadmin → enable for org, Admin → assign to customer |
| 403 on MCP endpoints | Missing mcp_integrations feature | Same as above |
| 409 on create | Duplicate catalogId+scope+agentId | Delete existing integration first |
| Test Connection fails | Bad credentials or server URL | Double-check API key / server URL |
| Test Connection timeout | MCP server unreachable | Verify server is running and accessible |
| MCP tools not in call | Integration inactive or decryption failed | Check isActive flag, check server logs for decrypt errors |
| Agent Server can't fetch | Internal API unreachable | Verify BACKEND_URL and WEBHOOK_SECRET env vars |
| Credentials corrupted | Encryption key rotated | Re-create the integration with fresh credentials |