Voltar para a documentação
Guia 07

MCP Marketplace

O MCP Marketplace permite instalar, habilitar e scopear MCP server externos a partir de um catálogo curado. Os MCP server expõem tools que o agente ReAct pode invocar durante os chats, estendendo as capacidades sem código.

5 min de leitura

O MCP Marketplace permite instalar, habilitar e scopear MCP server externos a partir de um catálogo curado. Os MCP server expõem tools que o agente ReAct pode invocar durante os chats, estendendo as capacidades sem código.

O sistema abrange: modelos Prisma (McpCatalogEntry, MCPConnection, ChatMCPPin), routes (marketplace.ts, mcp.ts), registro de skills do agente (skills.ts), serviços (health-check scheduler, DLP filter, audit logging).

Conceitos

  • `McpCatalogEntry` - template de catálogo (27 entradas seedadas: 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` - conexão workspace-scoped. source (manual/marketplace), catalogEntryId (FK se vier do marketplace), enabled, headers (JSON string), workspaceId/projectId.
  • `ChatMCPPin` - scoping por chat. @@unique([chatId, connectionId]). Hard-deleted no unpin.

Catálogo (browse + install)

O browse (lista e detalhe) exige apenas autenticação JWT; as operações de escrita exigem authMiddleware + requireAdmin. O frontend filtra search/category do lado cliente.

MétodoPathAuthNota
GET/api/mcp-marketplaceJWTLista; query ?workspaceId (adiciona isInstalled)
GET/api/mcp-marketplace/:entryIdJWTDetalhe da entrada
POST/api/mcp-marketplaceJWT + adminCria entrada de catálogo
POST/api/mcp-marketplace/:entryId/installJWT + adminInstala no workspace
POST/api/mcp-marketplace/:entryId/uninstallJWT + adminDesinstala

Install flow (marketplace.ts:165)

  1. Validate installMcpServerSchema ({ workspaceId: uuid, name?, headers? }); headers override validados do lado write com mcpHeadersSchema (D-12: hop-by-hop blocklist, name regex, size limits).
  2. Check se a entrada existe (404), não está já instalada (409, filtro source: "marketplace" - uma conexão manual com o mesmo catalogEntryId não bloqueia).
  3. Cria MCPConnection: source="marketplace", catalogEntryId, url/transportType copiados do catálogo, enabled=true, name = override || catalog.name.
  4. logEvent("mcp_connection", ..., "mcp.installed", ...).
  5. Fire-and-forget connectMCPServer(connection.id) - falha logada, não propagada (201 devolvido na mesma).

Uninstall flow (marketplace.ts:291)

  1. Encontra MCPConnection por { catalogEntryId, workspaceId, source: "marketplace" } (404 se não).
  2. disconnectMCPServer + unregisterSkillsForConnection (remove skills com prefixo mcp_<conn>_).
  3. Hard delete MCPConnection (sem soft delete).
  4. logEvent("mcp.uninstalled").

Re-install = refazer o install flow (nova MCPConnection com novo ID).

Conexões manuais (CRUD)

/api/mcp-connections (mcp.ts, authMiddleware + requireAdmin): GET, GET /statuses (live do activeConnections Map, não DB), POST cria, PUT /:id (auto-disconnect/reconnect se enabled), DELETE /:id, POST /:id/toggle, POST /:id/test (timeout 10s, devolve { success, toolCount?, error? }).

As conexões instaladas a partir do marketplace são indistinguíveis das manuais a nível CRUD - o campo source diferencia a origem.

MCP client (agent/mcpClient.ts)

  • connectMCPServer(connectionId) (:233): ensureConnected cache-hit → read-side header validation (mcpHeadersSchema D-12), transport fallback D-09 (streamable-http primário → 4xx → SSE fallback; sse declarado = só SSE; 5xx não dispara fallback), Client.listTools() → registra cada tool como skill mcp_<connectionId>_<toolName> (UUID prefix collision-free).
  • Mutex per-connection withConnectionLock.
  • activeConnections: Map<connectionId, MCPConnectionState> (não persistida, rebuild no restart; o scope workspace/project é capturado no connect-time para o filtro D-14).
  • disconnectMCPServer (:363): delete-first + unregister + client.close().
  • shutdownMCPConnections para o graceful shutdown.
  • resolveMcpSourceName(toolName) → nome human-readable para mcpSources SSE.

Pinning per-chat

Os pin permitem limitar quais conexões MCP fornecem tools a uma chat específica.

/api/chats/:chatId/pins (mcpPins.ts): GET, POST (createMcpPinSchema), DELETE /:pinId.

Resolução (agent/skills.ts:136 resolveSkillsForChat):

  • Sem pin → todas as conexões MCP enabled do workspace fornecem skills.
  • Pins presentes → filtra conexões pinned AND enabled AND mesmo workspace (globais ambos-null incluídas; project-scoped excluídas).
  • Todos os pinned desabilitados/out-of-scope → fallback workspace defaults + warn log.
  • Union MCP-02 (strict, D-04): builtin + pinned MCP skills + union com os tools workspace-scoped ativos (os pin nunca removem tools - só adicionam).

Health check

services/mcpHealthCheckJob.ts - pg-boss cron 30-min (initMCPHealthCheckScheduler() de src/index.ts; */30 * * * *, sem fallback timer quando pg-boss está unavailable - D-02). Cicla pelas conexões instaladas + enabled com catalogEntryId: ping url com retry exponencial (1s/2s/4s, timeout 10s), atualiza healthStatus (healthy / stale no 1º-2º falhanço / down a partir do 3º), lastHealthCheck, lastHealthError, consecutiveFailures. Não bloqueante: entradas stale/down permanecem visíveis com badge warning; o reset parte de um ping bem-sucedido.

Trust layer (verification tier)

verificationTier é seed-data-only (não escrito em runtime):

  • official - mantido pelo projeto Simmetric Chat.
  • verified_community - aprovado pelos maintainers.
  • unverified - comunidade, não revisto.

lastCommitDate = indicador de recência. Os tiers são informativos (não bloqueiam a install) mas renderizados como badges. A instalação de entradas unverified/down é por risco do admin.

Audit logging

Todas as operações logam via eventLogService.logEvent com entityType = "mcp_connection":

AçãoEvent action
Install do marketplacemcp.installed
Uninstall do marketplacemcp.uninstalled
Enable connectionmcp.enabled
Disable connectionmcp.disabled
Delete connectionmcp.deleted

No Community os eventos passam por logEvent (no-op de escrita sem o plugin Enterprise, dispatch de webhook igualmente ativo); com audit_log_immutable (Enterprise) os eventos são escritos no audit log imutável. Filtra com entityType = "mcp_connection" para o audit trail completo.

mcpSources no SSE

O SSE done inclui mcpSources: string[] - nomes das conexões MCP cujos tools foram invocados. Derivado do prefix do tool call (mcp_<conn>_<tool>) via resolveMcpSourceName. O frontend mostra os chips das fontes na mensagem; o proxy widget relayeia o evento byte-verbatim, portanto o dado permanece disponível do lado cliente.

Como se usa (admin)

  1. Settings → MCP Connections (subsecção Advanced) ou /mcp-marketplace (admin/superuser).
  2. Navega o catálogo (search, category filter, badge tier/health - filtros do lado cliente).
  3. Instala num workspace → badge "Installed" + menu contextual Uninstall.
  4. (Opcional) Pin de tools específicos a uma chat via McpPinnerPopover no header chat.
  5. Teste de conexão (POST /:id/test) para verificar o toolCount live.

Como se estende

  • Nova catalog entry: POST /api/mcp-marketplace (admin) ou seed direto no Prisma (seedCatalogEntries, upsert idempotente - os campos runtime tipo installCount nunca são sobrescritos pelo re-seed).
  • Novo transport: estenda buildTransport (mcpClient.ts:136) + TransportKind.
  • Scope custom: getMCPToolsForWorkspace(workspaceId) filtra por scope.workspaceId (global incluídos).