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.
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éthode | Path | Auth | Note |
|---|---|---|---|
| GET | /api/mcp-marketplace | JWT | Liste ; query ?workspaceId (ajoute isInstalled) |
| GET | /api/mcp-marketplace/:entryId | JWT | Détail de l'entrée |
| POST | /api/mcp-marketplace | JWT + admin | Crée une entrée de catalogue |
| POST | /api/mcp-marketplace/:entryId/install | JWT + admin | Installe dans le workspace |
| POST | /api/mcp-marketplace/:entryId/uninstall | JWT + admin | Désinstalle |
Install flow (marketplace.ts:165)
- Validate
installMcpServerSchema({ workspaceId: uuid, name?, headers? }) ; override headers validés côté write avecmcpHeadersSchema(D-12 : hop-by-hop blocklist, name regex, size limits). - Check entry existe (404), pas déjà installé (409, filtre
source: "marketplace"- une connexion manuelle avec le mêmecatalogEntryIdne bloque pas). - Crée
MCPConnection:source="marketplace",catalogEntryId,url/transportTypecopiés du catalogue,enabled=true,name = override || catalog.name. logEvent("mcp_connection", ..., "mcp.installed", ...).- Fire-and-forget
connectMCPServer(connection.id)- échec loggé, non propagé (201 renvoyé quand même).
Uninstall flow (marketplace.ts:291)
- Trouve
MCPConnectionpar{ catalogEntryId, workspaceId, source: "marketplace" }(404 sinon). disconnectMCPServer+unregisterSkillsForConnection(retire les skills préfixéesmcp_<conn>_).- Hard delete
MCPConnection(pas de soft delete). 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 (mcpHeadersSchemaD-12), transport fallback D-09 (streamable-httpprimaire → 4xx → SSE fallback ;ssedéclaré = SSE seulement ; 5xx ne déclenche pas le fallback),Client.listTools()→ enregistre chaque tool comme skillmcp_<connectionId>_<toolName>(UUID prefix collision-free).- Mutex per-connection
withConnectionLock. activeConnections: Map<connectionId, MCPConnectionState>(non persistée, rebuild au restart ; lescopeworkspace/project est capturé à la connexion pour le filtre D-14).disconnectMCPServer(:363) : delete-first + unregister +client.close().shutdownMCPConnectionspour le graceful shutdown.resolveMcpSourceName(toolName)→ nom human-readable pourmcpSourcesSSE.
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" :
| Action | Event action |
|---|---|
| Install depuis marketplace | mcp.installed |
| Uninstall depuis marketplace | mcp.uninstalled |
| Enable connection | mcp.enabled |
| Disable connection | mcp.disabled |
| Delete connection | mcp.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)
- Settings → MCP Connections (sous-section Avancé) ou /mcp-marketplace (admin/superuser).
- Parcourez le catalogue (search, category filter, badge tier/health - filtres côté client).
- Installez dans un workspace → badge "Installed" + menu contextuel Uninstall.
- (Optionnel) Pin des tools spécifiques à une chat via
McpPinnerPopoverdans le header chat. - 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 typeinstallCountne sont jamais écrasés par le re-seed). - Nouveau transport : étends
buildTransport(mcpClient.ts:136) +TransportKind. - Scope custom :
getMCPToolsForWorkspace(workspaceId)filtre parscope.workspaceId(global inclus).
Cross-link
- Endpoints : 04 - API Reference
- Agent loop : 05 - Feature Guide
- License/audit flag : 03 - Configuration / 08 - Administration