Retour à la documentation
Guide 07

MCP Marketplace

Le MCP Marketplace permet d'installer, d'activer et de scoper des MCP server externes depuis un catalogue curé. Les MCP server exposent des tools que l'agent ReAct peut invoquer pendant les chats, étendant les capacités sans code.

5 min de lecture

Le MCP Marketplace permet d'installer, d'activer et de scoper des MCP server externes depuis un catalogue curé. Les MCP server exposent des tools que l'agent ReAct peut invoquer pendant les chats, étendant les capacités sans code.

Le système couvre : les modèles Prisma (McpCatalogEntry, MCPConnection, ChatMCPPin), les routes (marketplace.ts, mcp.ts), le registre de skills de l'agent (skills.ts), les services (health-check scheduler, DLP filter, audit logging).

Concepts

  • `McpCatalogEntry` - template de catalogue (27 entrées seedées : 12 placeholder + 15 curées). Champs : 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` - connexion workspace-scoped. source (manual/marketplace), catalogEntryId (FK si depuis le marketplace), enabled, headers (JSON string), workspaceId/projectId.
  • `ChatMCPPin` - scoping par chat. @@unique([chatId, connectionId]). Hard-deleted au unpin.

Catalogue (browse + install)

Le browse (liste et détail) exige uniquement l'authentification JWT ; les opérations d'écriture exigent authMiddleware + requireAdmin. Le frontend filtre search/category côté client.

MéthodePathAuthNote
GET/api/mcp-marketplaceJWTListe ; query ?workspaceId (ajoute isInstalled)
GET/api/mcp-marketplace/:entryIdJWTDétail de l'entrée
POST/api/mcp-marketplaceJWT + adminCrée une entrée de catalogue
POST/api/mcp-marketplace/:entryId/installJWT + adminInstalle dans le workspace
POST/api/mcp-marketplace/:entryId/uninstallJWT + adminDésinstalle

Install flow (marketplace.ts:165)

  1. Validate installMcpServerSchema ({ workspaceId: uuid, name?, headers? }) ; override headers validés côté write avec mcpHeadersSchema (D-12 : hop-by-hop blocklist, name regex, size limits).
  2. Check entry existe (404), pas déjà installé (409, filtre source: "marketplace" - une connexion manuelle avec le même catalogEntryId ne bloque pas).
  3. Crée MCPConnection : source="marketplace", catalogEntryId, url/transportType copiés du catalogue, enabled=true, name = override || catalog.name.
  4. logEvent("mcp_connection", ..., "mcp.installed", ...).
  5. Fire-and-forget connectMCPServer(connection.id) - échec loggé, non propagé (201 renvoyé quand même).

Uninstall flow (marketplace.ts:291)

  1. Trouve MCPConnection par { catalogEntryId, workspaceId, source: "marketplace" } (404 sinon).
  2. disconnectMCPServer + unregisterSkillsForConnection (retire les skills préfixées mcp_<conn>_).
  3. Hard delete MCPConnection (pas de soft delete).
  4. logEvent("mcp.uninstalled").

Re-install = refaire l'install flow (nouveau MCPConnection avec nouvel ID).

Connexions manuelles (CRUD)

/api/mcp-connections (mcp.ts, authMiddleware + requireAdmin) : GET, GET /statuses (live depuis activeConnections Map, pas la DB), POST crée, PUT /:id (auto-disconnect/ reconnect si enabled), DELETE /:id, POST /:id/toggle, POST /:id/test (timeout 10s, retourne { success, toolCount?, error? }).

Les connexions installées depuis le marketplace sont indistinguables des manuelles au niveau CRUD - le champ source différencie 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 primaire → 4xx → SSE fallback ; sse déclaré = SSE seulement ; 5xx ne déclenche pas le fallback), Client.listTools() → enregistre chaque tool comme skill mcp_<connectionId>_<toolName> (UUID prefix collision-free).
  • Mutex per-connection withConnectionLock.
  • activeConnections: Map<connectionId, MCPConnectionState> (non persistée, rebuild au restart ; le scope workspace/project est capturé à la connexion pour le filtre D-14).
  • disconnectMCPServer (:363) : delete-first + unregister + client.close().
  • shutdownMCPConnections pour le graceful shutdown.
  • resolveMcpSourceName(toolName) → nom human-readable pour mcpSources SSE.

Pinning per-chat

Les pin permettent de limiter les connexions MCP qui fournissent des tools à une chat précise.

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

Résolution (agent/skills.ts:136 resolveSkillsForChat) :

  • Pas de pin → toutes les connexions MCP enabled du workspace fournissent des skills.
  • Pin présents → filtre connexions pinned AND enabled AND même workspace (globales les deux-null incluses ; project-scoped exclues).
  • Tous les pinned désactivés/out-of-scope → fallback workspace defaults + warn log.
  • Union MCP-02 (strict, D-04) : builtin + pinned MCP skills + union avec les tools workspace-scoped actifs (les pin ne retirent jamais de tool - elles ne font qu'ajouter).

Health check

services/mcpHealthCheckJob.ts - pg-boss cron 30-min (initMCPHealthCheckScheduler() depuis src/index.ts ; */30 * * * *, pas de fallback timer quand pg-boss est indisponible - D-02). Boucle sur les connexions installées + enabled avec catalogEntryId : ping url avec retry exponentiel (1s/2s/4s, timeout 10s), met à jour healthStatus (healthy / stale au 1er-2e échec / down dès le 3e), lastHealthCheck, lastHealthError, consecutiveFailures. Non bloquant : les entrées stale/down restent visibles avec un badge warning ; le reset part d'un ping réussi.

Trust layer (verification tier)

verificationTier est seed-data-only (non écrit à la runtime) :

  • official - maintenu par le projet Simmetric Chat.
  • verified_community - approuvé par les maintainers.
  • unverified - communauté, non révisé.

lastCommitDate = indicateur de récence. Les tiers sont informatifs (ils ne bloquent pas l'install) mais rendus comme badges. L'installation d'entrées unverified/down est au risque de l'admin.

Audit logging

Toutes les opérations loggent via eventLogService.logEvent avec entityType = "mcp_connection" :

ActionEvent action
Install depuis marketplacemcp.installed
Uninstall depuis marketplacemcp.uninstalled
Enable connectionmcp.enabled
Disable connectionmcp.disabled
Delete connectionmcp.deleted

En Community les événements passent par logEvent (no-op d'écriture sans le plugin Enterprise, dispatch webhook toujours actif) ; avec audit_log_immutable (Enterprise) les événements sont écrits dans l'audit log immuable. Filtre avec entityType = "mcp_connection" pour l'audit trail complet.

mcpSources dans SSE

Le SSE done inclut mcpSources: string[] - noms des connexions MCP dont les tools ont été invoqués. Dérivé du prefix du tool call (mcp_<conn>_<tool>) via resolveMcpSourceName. Le frontend affiche les chips des sources sur le message ; le proxy widget relaye l'événement byte-verbatim, donc la donnée reste disponible côté client.

Comment l'utiliser (admin)

  1. Settings → MCP Connections (sous-section Avancé) ou /mcp-marketplace (admin/superuser).
  2. Parcourez le catalogue (search, category filter, badge tier/health - filtres côté client).
  3. Installez dans un workspace → badge "Installed" + menu contextuel Uninstall.
  4. (Optionnel) Pin des tools spécifiques à une chat via McpPinnerPopover dans le header chat.
  5. Test de connexion (POST /:id/test) pour vérifier le toolCount live.

Comment l'étendre

  • Nouvelle catalog entry : POST /api/mcp-marketplace (admin) ou seed direct en Prisma (seedCatalogEntries, upsert idempotent - les champs runtime type installCount ne sont jamais écrasés par le re-seed).
  • Nouveau transport : étends buildTransport (mcpClient.ts:136) + TransportKind.
  • Scope custom : getMCPToolsForWorkspace(workspaceId) filtre par scope.workspaceId (global inclus).