MCP Marketplace
El MCP Marketplace permite instalar, habilitar y scopear MCP server externos desde un catálogo curado. Los MCP server exponen tools que el agente ReAct puede invocar durante los chats, extendiendo las capacidades sin código.
El MCP Marketplace permite instalar, habilitar y scopear MCP server externos desde un catálogo curado. Los MCP server exponen tools que el agente ReAct puede invocar durante los chats, extendiendo las capacidades sin código.
El sistema abarca: modelos Prisma (McpCatalogEntry, MCPConnection, ChatMCPPin), routes (marketplace.ts, mcp.ts), registro de skills del agente (skills.ts), servicios (health-check scheduler, DLP filter, audit logging).
Conceptos
- `McpCatalogEntry` - template de catálogo (27 entradas seedeadas: 12 placeholder + 15 curadas). Campos:
name,url,transportType(sse/streamable-http),headers(JSON string),description,category,version,author,verificationTier(official/verified_community/unverified),healthStatus(healthy/stale/down),lastHealthCheck,lastHealthError,lastCommitDate,consecutiveFailures. - `MCPConnection` - conexión workspace-scoped.
source(manual/marketplace),catalogEntryId(FK si viene del marketplace),enabled,headers(JSON string),workspaceId/projectId. - `ChatMCPPin` - scoping por chat.
@@unique([chatId, connectionId]). Hard-deleted al unpin.
Catálogo (browse + install)
El browse (lista y detalle) requiere solo autenticación JWT; las operaciones de escritura requieren authMiddleware + requireAdmin. El frontend filtra search/category del lado cliente.
| Método | Path | Auth | Nota |
|---|---|---|---|
| GET | /api/mcp-marketplace | JWT | Lista; query ?workspaceId (añade isInstalled) |
| GET | /api/mcp-marketplace/:entryId | JWT | Detalle de la entrada |
| POST | /api/mcp-marketplace | JWT + admin | Crea entrada de catálogo |
| POST | /api/mcp-marketplace/:entryId/install | JWT + admin | Instala en el workspace |
| POST | /api/mcp-marketplace/:entryId/uninstall | JWT + admin | Desinstala |
Install flow (marketplace.ts:165)
- Validate
installMcpServerSchema({ workspaceId: uuid, name?, headers? }); headers override validados del lado write conmcpHeadersSchema(D-12: hop-by-hop blocklist, name regex, size limits). - Check de que la entrada existe (404), no está ya instalada (409, filtro
source: "marketplace"- una conexión manual con el mismocatalogEntryIdno bloquea). - Crea
MCPConnection:source="marketplace",catalogEntryId,url/transportTypecopiados del catálogo,enabled=true,name = override || catalog.name. logEvent("mcp_connection", ..., "mcp.installed", ...).- Fire-and-forget
connectMCPServer(connection.id)- fallo logueado, no propagado (201 devuelto igualmente).
Uninstall flow (marketplace.ts:291)
- Encuentra
MCPConnectionpor{ catalogEntryId, workspaceId, source: "marketplace" }(404 si no). disconnectMCPServer+unregisterSkillsForConnection(elimina skills con prefijomcp_<conn>_).- Hard delete
MCPConnection(sin soft delete). logEvent("mcp.uninstalled").
Re-install = rehacer el install flow (nuevo MCPConnection con nuevo ID).
Conexiones manuales (CRUD)
/api/mcp-connections (mcp.ts, authMiddleware + requireAdmin): GET, GET /statuses (live desde activeConnections Map, no DB), POST crea, PUT /:id (auto-disconnect/reconnect si enabled), DELETE /:id, POST /:id/toggle, POST /:id/test (timeout 10s, devuelve { success, toolCount?, error? }).
Las conexiones instaladas desde el marketplace son indistinguibles de las manuales a nivel CRUD - el campo source diferencia el origen.
MCP client (agent/mcpClient.ts)
connectMCPServer(connectionId)(:233): ensureConnected cache-hit → read-side header validation (mcpHeadersSchemaD-12), transport fallback D-09 (streamable-httpprimario → 4xx → SSE fallback;ssedeclarado = solo SSE; 5xx no dispara fallback),Client.listTools()→ registra cada tool como skillmcp_<connectionId>_<toolName>(UUID prefix collision-free).- Mutex per-connection
withConnectionLock. activeConnections: Map<connectionId, MCPConnectionState>(no persistida, rebuild al restart; elscopeworkspace/project se captura en connect-time para el filtro D-14).disconnectMCPServer(:363): delete-first + unregister +client.close().shutdownMCPConnectionspara el graceful shutdown.resolveMcpSourceName(toolName)→ nombre human-readable paramcpSourcesSSE.
Pinning per-chat
Los pin permiten limitar qué conexiones MCP proporcionan tools a una chat específica.
/api/chats/:chatId/pins (mcpPins.ts): GET, POST (createMcpPinSchema), DELETE /:pinId.
Resolución (agent/skills.ts:136 resolveSkillsForChat):
- Sin pin → todas las conexiones MCP enabled del workspace proporcionan skills.
- Pins presentes → filtra conexiones pinned AND enabled AND mismo workspace (globales ambos-null incluidas; project-scoped excluidas).
- Todos los pinned deshabilitados/out-of-scope → fallback workspace defaults + warn log.
- Union MCP-02 (strict, D-04): builtin + pinned MCP skills + union con los tools workspace-scoped activos (los pin nunca eliminan tools - solo añaden).
Health check
services/mcpHealthCheckJob.ts - pg-boss cron 30-min (initMCPHealthCheckScheduler() desde src/index.ts; */30 * * * *, sin fallback timer cuando pg-boss no está disponible - D-02). Recorre las conexiones instaladas + enabled con catalogEntryId: ping url con retry exponencial (1s/2s/4s, timeout 10s), actualiza healthStatus (healthy / stale al 1º-2º fallo / down desde el 3º), lastHealthCheck, lastHealthError, consecutiveFailures. No bloqueante: las entradas stale/down permanecen visibles con badge warning; el reset parte de un ping exitoso.
Trust layer (verification tier)
verificationTier es seed-data-only (no se escribe en runtime):
- official - mantenido por el proyecto Simmetric Chat.
- verified_community - aprobado por los maintainers.
- unverified - comunidad, no revisado.
lastCommitDate = indicador de recencia. Los tiers son informativos (no bloquean la install) pero se renderizan como badges. La instalación de entradas unverified/down es a riesgo del admin.
Audit logging
Todas las operaciones loguean via eventLogService.logEvent con entityType = "mcp_connection":
| Acción | Event action |
|---|---|
| Install desde marketplace | mcp.installed |
| Uninstall desde marketplace | mcp.uninstalled |
| Enable connection | mcp.enabled |
| Disable connection | mcp.disabled |
| Delete connection | mcp.deleted |
En Community los eventos pasan por logEvent (no-op de escritura sin el plugin Enterprise, dispatch de webhook igualmente activo); con audit_log_immutable (Enterprise) los eventos se escriben en el audit log inmutable. Filtra con entityType = "mcp_connection" para el audit trail completo.
mcpSources en SSE
El SSE done incluye mcpSources: string[] - nombres de las conexiones MCP cuyos tools fueron invocados. Derivado del prefix del tool call (mcp_<conn>_<tool>) vía resolveMcpSourceName. El frontend muestra los chips de las fuentes en el mensaje; el proxy widget relayea el evento byte-verbatim, así que el dato permanece disponible del lado cliente.
Cómo se usa (admin)
- Settings → MCP Connections (subsección Advanced) o /mcp-marketplace (admin/superuser).
- Navega el catálogo (search, category filter, badge tier/health - filtros del lado cliente).
- Instala en un workspace → badge "Installed" + menú contextual Uninstall.
- (Opcional) Pin de tools específicos a una chat vía
McpPinnerPopoveren el header chat. - Test de conexión (
POST /:id/test) para verificar el toolCount live.
Cómo se extiende
- Nueva catalog entry:
POST /api/mcp-marketplace(admin) o seed directo en Prisma (seedCatalogEntries, upsert idempotente - los campos runtime tipoinstallCountnunca se sobrescriben por el re-seed). - Nuevo transport: extiende
buildTransport(mcpClient.ts:136) +TransportKind. - Scope custom:
getMCPToolsForWorkspace(workspaceId)filtra porscope.workspaceId(global incluidos).
Cross-link
- Endpoints: 04 - API Reference
- Agent loop: 05 - Feature Guide
- License/audit flag: 03 - Configuration / 08 - Administration