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.
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étodo | Path | Auth | Nota |
|---|---|---|---|
| GET | /api/mcp-marketplace | JWT | Lista; query ?workspaceId (adiciona isInstalled) |
| GET | /api/mcp-marketplace/:entryId | JWT | Detalhe da entrada |
| POST | /api/mcp-marketplace | JWT + admin | Cria entrada de catálogo |
| POST | /api/mcp-marketplace/:entryId/install | JWT + admin | Instala no workspace |
| POST | /api/mcp-marketplace/:entryId/uninstall | JWT + admin | Desinstala |
Install flow (marketplace.ts:165)
- Validate
installMcpServerSchema({ workspaceId: uuid, name?, headers? }); headers override validados do lado write commcpHeadersSchema(D-12: hop-by-hop blocklist, name regex, size limits). - Check se a entrada existe (404), não está já instalada (409, filtro
source: "marketplace"- uma conexão manual com o mesmocatalogEntryIdnão bloqueia). - Cria
MCPConnection:source="marketplace",catalogEntryId,url/transportTypecopiados do catálogo,enabled=true,name = override || catalog.name. logEvent("mcp_connection", ..., "mcp.installed", ...).- Fire-and-forget
connectMCPServer(connection.id)- falha logada, não propagada (201 devolvido na mesma).
Uninstall flow (marketplace.ts:291)
- Encontra
MCPConnectionpor{ catalogEntryId, workspaceId, source: "marketplace" }(404 se não). disconnectMCPServer+unregisterSkillsForConnection(remove skills com prefixomcp_<conn>_).- Hard delete
MCPConnection(sem soft delete). 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 (mcpHeadersSchemaD-12), transport fallback D-09 (streamable-httpprimário → 4xx → SSE fallback;ssedeclarado = só SSE; 5xx não dispara fallback),Client.listTools()→ registra cada tool como skillmcp_<connectionId>_<toolName>(UUID prefix collision-free).- Mutex per-connection
withConnectionLock. activeConnections: Map<connectionId, MCPConnectionState>(não persistida, rebuild no restart; oscopeworkspace/project é capturado no connect-time para o filtro D-14).disconnectMCPServer(:363): delete-first + unregister +client.close().shutdownMCPConnectionspara o graceful shutdown.resolveMcpSourceName(toolName)→ nome human-readable paramcpSourcesSSE.
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ção | Event action |
|---|---|
| Install do marketplace | mcp.installed |
| Uninstall do marketplace | mcp.uninstalled |
| Enable connection | mcp.enabled |
| Disable connection | mcp.disabled |
| Delete connection | mcp.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)
- Settings → MCP Connections (subsecção Advanced) ou /mcp-marketplace (admin/superuser).
- Navega o catálogo (search, category filter, badge tier/health - filtros do lado cliente).
- Instala num workspace → badge "Installed" + menu contextual Uninstall.
- (Opcional) Pin de tools específicos a uma chat via
McpPinnerPopoverno header chat. - 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 tipoinstallCountnunca são sobrescritos pelo re-seed). - Novo transport: estenda
buildTransport(mcpClient.ts:136) +TransportKind. - Scope custom:
getMCPToolsForWorkspace(workspaceId)filtra porscope.workspaceId(global incluídos).
Cross-link
- Endpoints: 04 - API Reference
- Agent loop: 05 - Feature Guide
- License/audit flag: 03 - Configuration / 08 - Administration