Volver a la documentación
Guía 07

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.

5 min de lectura

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étodoPathAuthNota
GET/api/mcp-marketplaceJWTLista; query ?workspaceId (añade isInstalled)
GET/api/mcp-marketplace/:entryIdJWTDetalle de la entrada
POST/api/mcp-marketplaceJWT + adminCrea entrada de catálogo
POST/api/mcp-marketplace/:entryId/installJWT + adminInstala en el workspace
POST/api/mcp-marketplace/:entryId/uninstallJWT + adminDesinstala

Install flow (marketplace.ts:165)

  1. Validate installMcpServerSchema ({ workspaceId: uuid, name?, headers? }); headers override validados del lado write con mcpHeadersSchema (D-12: hop-by-hop blocklist, name regex, size limits).
  2. Check de que la entrada existe (404), no está ya instalada (409, filtro source: "marketplace" - una conexión manual con el mismo catalogEntryId no bloquea).
  3. Crea MCPConnection: source="marketplace", catalogEntryId, url/transportType copiados del catálogo, enabled=true, name = override || catalog.name.
  4. logEvent("mcp_connection", ..., "mcp.installed", ...).
  5. Fire-and-forget connectMCPServer(connection.id) - fallo logueado, no propagado (201 devuelto igualmente).

Uninstall flow (marketplace.ts:291)

  1. Encuentra MCPConnection por { catalogEntryId, workspaceId, source: "marketplace" } (404 si no).
  2. disconnectMCPServer + unregisterSkillsForConnection (elimina skills con prefijo mcp_<conn>_).
  3. Hard delete MCPConnection (sin soft delete).
  4. 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 (mcpHeadersSchema D-12), transport fallback D-09 (streamable-http primario → 4xx → SSE fallback; sse declarado = solo SSE; 5xx no dispara fallback), Client.listTools() → registra cada tool como skill mcp_<connectionId>_<toolName> (UUID prefix collision-free).
  • Mutex per-connection withConnectionLock.
  • activeConnections: Map<connectionId, MCPConnectionState> (no persistida, rebuild al restart; el scope workspace/project se captura en connect-time para el filtro D-14).
  • disconnectMCPServer (:363): delete-first + unregister + client.close().
  • shutdownMCPConnections para el graceful shutdown.
  • resolveMcpSourceName(toolName) → nombre human-readable para mcpSources SSE.

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ónEvent action
Install desde marketplacemcp.installed
Uninstall desde marketplacemcp.uninstalled
Enable connectionmcp.enabled
Disable connectionmcp.disabled
Delete connectionmcp.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)

  1. Settings → MCP Connections (subsección Advanced) o /mcp-marketplace (admin/superuser).
  2. Navega el catálogo (search, category filter, badge tier/health - filtros del lado cliente).
  3. Instala en un workspace → badge "Installed" + menú contextual Uninstall.
  4. (Opcional) Pin de tools específicos a una chat vía McpPinnerPopover en el header chat.
  5. 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 tipo installCount nunca se sobrescriben por el re-seed).
  • Nuevo transport: extiende buildTransport (mcpClient.ts:136) + TransportKind.
  • Scope custom: getMCPToolsForWorkspace(workspaceId) filtra por scope.workspaceId (global incluidos).