Torna alla documentazione
Guida 07

MCP Marketplace

Il MCP Marketplace permette di installare, abilitare e scoping MCP server esterni da un catalogo curato. Gli MCP server espongono tool che l'agente ReAct può invocare durante le chat, estendendo le capacità senza codice.

5 min di lettura

Il MCP Marketplace permette di installare, abilitare e scoping MCP server esterni da un catalogo curato. Gli MCP server espongono tool che l'agente ReAct può invocare durante le chat, estendendo le capacità senza codice.

MCP bidirezionale (F-MCP-1): oltre alla direzione connect (MCP esterni connesi come skill), la direzione expose è built-in: l'MCP server interno espone la ricerca RAG a client esterni.

Il sistema spanna: modelli Prisma (McpCatalogEntry, MCPConnection, ChatMCPPin), route (marketplace.ts, mcp.ts), agent skill registry (skills.ts), servizi (health-check scheduler, DLP filter, audit logging).

Lato expose (built-in)

packages/server/src/agent/mcpServer.ts espone la RAG via Model Context Protocol a client esterni (Cursor, VS Code e IDE): tool rag_query (ricerca documenti workspace) e list_workspaces. Transport: Streamable HTTP stateless primario (POST /api/mcp/mcp, core MCP 2026-07-28, risposte JSON-only) + SSE legacy (GET /api/mcp/sse + POST /api/mcp/message, deprecato in v2 con grace di un anno). Auth: con MCP_API_KEY impostata → Authorization: Bearer <MCP_API_KEY> (401 su miss/wrong); unset → fallback loopback-only (127.0.0.1/::1/^::ffff:127.0.0.1) - il remoto → 401.

Concetti

  • `McpCatalogEntry` - template di catalogo (27 entry seedate: 12 placeholder + 15 curate). Campi: 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` - connessione workspace-scoped. source (manual/marketplace), catalogEntryId (FK se da marketplace), enabled, headers (JSON string), workspaceId/projectId.
  • `ChatMCPPin` - scoping per-chat. @@unique([chatId, connectionId]). Hard-deleted on unpin.

Catalogo (browse + install)

La browse (lista e dettaglio) richiede solo autenticazione JWT; le operazioni di scrittura richiedono authMiddleware + requireAdmin. Il frontend filtra search/category lato client.

MetodoPathAuthNote
GET/api/mcp-marketplaceJWTLista; query ?workspaceId (aggiunge isInstalled)
GET/api/mcp-marketplace/:entryIdJWTDettaglio entry
POST/api/mcp-marketplaceJWT + adminCrea entry di catalogo
POST/api/mcp-marketplace/:entryId/installJWT + adminInstalla nel workspace
POST/api/mcp-marketplace/:entryId/uninstallJWT + adminDisinstalla

Install flow (marketplace.ts:165)

  1. Validate installMcpServerSchema ({ workspaceId: uuid, name?, headers? }); override headers validati lato write con mcpHeadersSchema (D-12: hop-by-hop blocklist, name regex, size limits).
  2. Check entry esiste (404), non già installato (409, filtro source: "marketplace" - una connessione manuale con lo stesso catalogEntryId non blocca).
  3. Crea MCPConnection: source="marketplace", catalogEntryId, url/transportType copiati dal catalogo, enabled=true, name = override || catalog.name.
  4. logEvent("mcp_connection", ..., "mcp.installed", ...).
  5. Fire-and-forget connectMCPServer(connection.id) - fallimento logged, non propagato (201 restituito comunque).

Uninstall flow (marketplace.ts:291)

  1. Trova MCPConnection per { catalogEntryId, workspaceId, source: "marketplace" } (404 se no).
  2. disconnectMCPServer + unregisterSkillsForConnection (rimuove skill prefisso mcp_<conn>_).
  3. Hard delete MCPConnection (no soft delete).
  4. logEvent("mcp.uninstalled").

Re-install = rifai l'install flow (nuovo MCPConnection con nuovo ID).

Connessioni manuali (CRUD)

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

Le connessioni marketplace-installate sono indistinguibili da quelle manuali a livello CRUD - il campo source differenzia l'origine.

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 dichiarato = solo SSE; 5xx non triggera fallback), Client.listTools() → registra ogni tool come skill mcp_<connectionId>_<toolName> (UUID prefix collision-free).
  • Per-connection mutex withConnectionLock.
  • activeConnections: Map<connectionId, MCPConnectionState> (non persistito, rebuild al restart; lo scope workspace/project è catturato a connect-time per il filtro D-14).
  • disconnectMCPServer (:363): delete-first + unregister + client.close().
  • shutdownMCPConnections per graceful shutdown.
  • resolveMcpSourceName(toolName) → nome human-readable per mcpSources SSE.

Pinning per-chat

Le pin permettono di limitare quali connessioni MCP forniscono tool a una specifica chat.

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

Risoluzione (agent/skills.ts:136 resolveSkillsForChat):

  • No pin → tutti i connection MCP enabled del workspace forniscono skill.
  • Pin presenti → filtra connection pinned AND enabled AND stesso workspace (globali entrambi-null incluse; project-scoped escluse).
  • Tutti pinned disabilitati/out-of-scope → fallback workspace defaults + warn log.
  • Union MCP-02 (strict, D-04): builtin + pinned MCP skill + union con i tool workspace-scoped attivi (le pin non rimuovono mai tool - solo aggiungono).

Health check

services/mcpHealthCheckJob.ts - pg-boss cron 30-min (initMCPHealthCheckScheduler() da src/index.ts; */30 * * * *, no fallback timer quando pg-boss è unavailable - D-02). Cicla sulle connessioni installate + enabled con catalogEntryId: ping url con retry esponenziale (1s/2s/4s, timeout 10s), aggiorna healthStatus (healthy / stale al 1°-2° fail / down dal 3°), lastHealthCheck, lastHealthError, consecutiveFailures. Non bloccante: entry stale/down restano visibili con badge warning; il reset parte da un ping riuscito.

Trust layer (verification tier)

verificationTier è seed-data-only (non scritto a runtime):

  • official - mantenuto dal progetto Simmetric Chat.
  • verified_community - approvato dai maintainer.
  • unverified - community, non revisionato.

lastCommitDate = indicatore di recenza. I tier sono informativi (non bloccano install) ma renderizzati come badge. L'installazione di entry unverified/down è a rischio dell'admin.

Audit logging

Tutte le operazioni loggano via eventLogService.logEvent con entityType = "mcp_connection":

AzioneEvent action
Install da marketplacemcp.installed
Uninstall da marketplacemcp.uninstalled
Enable connectionmcp.enabled
Disable connectionmcp.disabled
Delete connectionmcp.deleted

In Community gli eventi passano da logEvent (no-op di scrittura senza il plugin Enterprise, dispatch webhook comunque attivo); con audit_log_immutable (Enterprise) gli eventi sono scritti nell'audit log immutabile. Filtra con entityType = "mcp_connection" per l'audit trail completo.

mcpSources in SSE

L'SSE done include mcpSources: string[] - nomi delle connessioni MCP i cui tool sono stati invocati. Derivato dal prefix del tool call (mcp_<conn>_<tool>) via resolveMcpSourceName. Il frontend mostra i chip delle fonti sul messaggio; il proxy widget rigola l'evento byte-verbatim, così il dato resta disponibile lato client.

Come si usa (admin)

  1. Settings → MCP Connections (sotto-sezione Avanzate) o /mcp-marketplace (admin/superuser).
  2. Sfoglia catalogo (search, category filter, badge tier/health - filtri lato client).
  3. Installa in un workspace → badge "Installed" + menu contestuale Uninstall.
  4. (Opzionale) Pin tool specifici a una chat via McpPinnerPopover nell'header chat.
  5. Test connessione (POST /:id/test) per verificare toolCount live.

Come si estende

  • Nuovo catalog entry: POST /api/mcp-marketplace (admin) o seed diretto in Prisma (seedCatalogEntries, upsert idempotente - i campi runtime installCount-like non vengono mai sovrascritti dal re-seed).
  • Nuovo transport: estendi buildTransport (mcpClient.ts:136) + TransportKind.
  • Scope custom: getMCPToolsForWorkspace(workspaceId) filtra per scope.workspaceId (global inclusi).

Provenienza v0.28.1

AffermazioneFonte
La direzione expose (tool rag_query/list_workspaces, transport streamable stateless + SSE legacy, gate MCP_API_KEY/loopback)packages/server/src/agent/mcpServer.ts:1-100 (row-time probe 35-02) + README.md:19 - F-MCP-1
Il marketplace = ruoli admindocs/MCP_MARKETPLACE.md:3 (row-time)

Riferimento operativo - Simmetric Chat v0.28.1 (docs aggiornate 2026-10-02, census SHA `aca5b8ff`).