К документации
Руководство 07

MCP Marketplace

MCP Marketplace позволяет устанавливать, включать и ограничивать внешние MCP server из курируемого каталога. MCP server предоставляют tool, которые агент ReAct может вызывать во время чатов, расширяя возможности без кода.

4 мин чтения

MCP Marketplace позволяет устанавливать, включать и ограничивать внешние MCP server из курируемого каталога. MCP server предоставляют tool, которые агент ReAct может вызывать во время чатов, расширяя возможности без кода.

Система охватывает: модели Prisma (McpCatalogEntry, MCPConnection, ChatMCPPin), route (marketplace.ts, mcp.ts), реестр skill агента (skills.ts), сервисы (health-check scheduler, DLP filter, audit logging).

Концепции

  • `McpCatalogEntry` - шаблон каталога (27 seed-записей: 12 placeholder + 15 curated). Поля: 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` - workspace-scoped подключение. source (manual/marketplace), catalogEntryId (FK если из marketplace), enabled, headers (JSON string), workspaceId/projectId.
  • `ChatMCPPin` - scoping на chat. @@unique([chatId, connectionId]). Hard-deleted при unpin.

Каталог (browse + install)

browse (список и детали) требует только JWT-аутентификацию; операции записи требуют authMiddleware + requireAdmin. Frontend фильтрует search/category на клиенте.

МетодPathAuthПримечание
GET/api/mcp-marketplaceJWTСписок; query ?workspaceId (добавляет isInstalled)
GET/api/mcp-marketplace/:entryIdJWTДетали записи
POST/api/mcp-marketplaceJWT + adminСоздать запись каталога
POST/api/mcp-marketplace/:entryId/installJWT + adminУстановить в workspace
POST/api/mcp-marketplace/:entryId/uninstallJWT + adminУдалить

Install flow (marketplace.ts:165)

  1. Validate installMcpServerSchema ({ workspaceId: uuid, name?, headers? }); override headers валидируются на стороне write с mcpHeadersSchema (D-12: hop-by-hop blocklist, name regex, size limits).
  2. Check запись существует (404), не установлена (409, фильтр source: "marketplace" - ручное подключение с тем же catalogEntryId не блокирует).
  3. Создаёт MCPConnection: source="marketplace", catalogEntryId, url/transportType копируются из каталога, enabled=true, name = override || catalog.name.
  4. logEvent("mcp_connection", ..., "mcp.installed", ...).
  5. Fire-and-forget connectMCPServer(connection.id) - сбой логируется, не пробрасывается (201 всё равно возвращается).

Uninstall flow (marketplace.ts:291)

  1. Находит MCPConnection по { catalogEntryId, workspaceId, source: "marketplace" } (404 если нет).
  2. disconnectMCPServer + unregisterSkillsForConnection (удаляет skill с префиксом mcp_<conn>_).
  3. Hard delete MCPConnection (без soft delete).
  4. logEvent("mcp.uninstalled").

Re-install = заново прогнать install flow (новая MCPConnection с новым ID).

Ручные подключения (CRUD)

/api/mcp-connections (mcp.ts, authMiddleware + requireAdmin): GET, GET /statuses (live из Map activeConnections, не из DB), POST создаёт, PUT /:id (auto-disconnect/reconnect если enabled), DELETE /:id, POST /:id/toggle, POST /:id/test (timeout 10s, возвращает { success, toolCount?, error? }).

Подключения, установленные из marketplace, неотличимы от ручных на уровне CRUD - поле source различает происхождение.

MCP client (agent/mcpClient.ts)

  • connectMCPServer(connectionId) (:233): ensureConnected cache-hit → read-side header validation (mcpHeadersSchema D-12), transport fallback D-09 (streamable-http первичный → 4xx → SSE fallback; объявленный sse = только SSE; 5xx не триггерит fallback), Client.listTools() → регистрирует каждый tool как skill mcp_<connectionId>_<toolName> (UUID-префикс без коллизий).
  • Per-connection mutex withConnectionLock.
  • activeConnections: Map<connectionId, MCPConnectionState> (не персистится, rebuild при restart; scope workspace/project захватывается в connect-time для фильтра D-14).
  • disconnectMCPServer (:363): delete-first + unregister + client.close().
  • shutdownMCPConnections для graceful shutdown.
  • resolveMcpSourceName(toolName) → human-readable имя для mcpSources SSE.

Per-chat pinning

Pin позволяют ограничить, какие MCP-подключения предоставляют tool конкретному chat.

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

Разрешение (agent/skills.ts:136 resolveSkillsForChat):

  • Нет pin → все enabled MCP-подключения workspace предоставляют skill.
  • Есть pin → фильтрует подключения pinned AND enabled AND тот же workspace (глобальные оба-null включены; project-scoped исключены).
  • Все pinned отключены/out-of-scope → fallback workspace defaults + warn log.
  • Union MCP-02 (strict, D-04): builtin + pinned MCP skill + union с активными workspace-scoped tool (pin никогда не удаляют tool - только добавляют).

Health check

services/mcpHealthCheckJob.ts - pg-boss cron 30-min (initMCPHealthCheckScheduler() из src/index.ts; */30 * * * *, без fallback timer когда pg-boss недоступен - D-02). Циклится по установленным + enabled подключениям с catalogEntryId: пингует url с экспоненциальным retry (1s/2s/4s, timeout 10s), обновляет healthStatus (healthy / stale на 1-2 сбое / down с 3-го), lastHealthCheck, lastHealthError, consecutiveFailures. Не блокирует: stale/down записи остаются видимыми с warning badge; reset начинается с успешного пинга.

Trust layer (verification tier)

verificationTier - только seed-data (не пишется в runtime):

  • official - поддерживается проектом Simmetric Chat.
  • verified_community - одобрен мейнтейнерами.
  • unverified - сообщество, не проверено.

lastCommitDate = индикатор свежести. Tier информативны (не блокируют установку), но рендерятся как badge. Установка записей unverified/down - на риск admin.

Audit logging

Все операции логируются через eventLogService.logEvent с entityType = "mcp_connection":

ДействиеEvent action
Install из marketplacemcp.installed
Uninstall из marketplacemcp.uninstalled
Enable connectionmcp.enabled
Disable connectionmcp.disabled
Delete connectionmcp.deleted

В Community события проходят через logEvent (no-op записи без Enterprise-плагина, dispatch webhook всё равно активен); с audit_log_immutable (Enterprise) события пишутся в неизменяемый audit log. Фильтруй по entityType = "mcp_connection" для полного audit trail.

mcpSources в SSE

SSE done включает mcpSources: string[] - имена MCP-подключений, чьи tool были вызваны. Выводится из префикса tool call (mcp_<conn>_<tool>) через resolveMcpSourceName. Frontend показывает chip'ы источников на сообщении; widget proxy ретранслирует событие byte-verbatim, поэтому данные остаются доступны на клиенте.

Как использовать (admin)

  1. Settings → MCP Connections (подраздел Advanced) или /mcp-marketplace (admin/superuser).
  2. Просмотри каталог (search, category filter, badge tier/health - клиентские фильтры).
  3. Установи в workspace → badge "Installed" + контекстное меню Uninstall.
  4. (Опционально) Pin конкретных tool к chat через McpPinnerPopover в header чата.
  5. Тест подключения (POST /:id/test) для проверки live toolCount.

Как расширить

  • Новая catalog entry: POST /api/mcp-marketplace (admin) или прямой seed в Prisma (seedCatalogEntries, идемпотентный upsert - runtime-поля вроде installCount никогда не перезаписываются при re-seed).
  • Новый transport: расширь buildTransport (mcpClient.ts:136) + TransportKind.
  • Custom scope: getMCPToolsForWorkspace(workspaceId) фильтрует по scope.workspaceId (global включены).