MCP Marketplace
MCP Marketplace позволяет устанавливать, включать и ограничивать внешние MCP server из курируемого каталога. MCP server предоставляют tool, которые агент ReAct может вызывать во время чатов, расширяя возможности без кода.
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 на клиенте.
| Метод | Path | Auth | Примечание |
|---|---|---|---|
| GET | /api/mcp-marketplace | JWT | Список; query ?workspaceId (добавляет isInstalled) |
| GET | /api/mcp-marketplace/:entryId | JWT | Детали записи |
| POST | /api/mcp-marketplace | JWT + admin | Создать запись каталога |
| POST | /api/mcp-marketplace/:entryId/install | JWT + admin | Установить в workspace |
| POST | /api/mcp-marketplace/:entryId/uninstall | JWT + admin | Удалить |
Install flow (marketplace.ts:165)
- Validate
installMcpServerSchema({ workspaceId: uuid, name?, headers? }); override headers валидируются на стороне write сmcpHeadersSchema(D-12: hop-by-hop blocklist, name regex, size limits). - Check запись существует (404), не установлена (409, фильтр
source: "marketplace"- ручное подключение с тем жеcatalogEntryIdне блокирует). - Создаёт
MCPConnection:source="marketplace",catalogEntryId,url/transportTypeкопируются из каталога,enabled=true,name = override || catalog.name. logEvent("mcp_connection", ..., "mcp.installed", ...).- Fire-and-forget
connectMCPServer(connection.id)- сбой логируется, не пробрасывается (201 всё равно возвращается).
Uninstall flow (marketplace.ts:291)
- Находит
MCPConnectionпо{ catalogEntryId, workspaceId, source: "marketplace" }(404 если нет). disconnectMCPServer+unregisterSkillsForConnection(удаляет skill с префиксомmcp_<conn>_).- Hard delete
MCPConnection(без soft delete). 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 (mcpHeadersSchemaD-12), transport fallback D-09 (streamable-httpпервичный → 4xx → SSE fallback; объявленныйsse= только SSE; 5xx не триггерит fallback),Client.listTools()→ регистрирует каждый tool как skillmcp_<connectionId>_<toolName>(UUID-префикс без коллизий).- Per-connection mutex
withConnectionLock. activeConnections: Map<connectionId, MCPConnectionState>(не персистится, rebuild при restart;scopeworkspace/project захватывается в connect-time для фильтра D-14).disconnectMCPServer(:363): delete-first + unregister +client.close().shutdownMCPConnectionsдля graceful shutdown.resolveMcpSourceName(toolName)→ human-readable имя дляmcpSourcesSSE.
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 из marketplace | mcp.installed |
| Uninstall из marketplace | mcp.uninstalled |
| Enable connection | mcp.enabled |
| Disable connection | mcp.disabled |
| Delete connection | mcp.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)
- Settings → MCP Connections (подраздел Advanced) или /mcp-marketplace (admin/superuser).
- Просмотри каталог (search, category filter, badge tier/health - клиентские фильтры).
- Установи в workspace → badge "Installed" + контекстное меню Uninstall.
- (Опционально) Pin конкретных tool к chat через
McpPinnerPopoverв header чата. - Тест подключения (
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 включены).
Cross-link
- Endpoints: 04 - API Reference
- Agent loop: 05 - Feature Guide
- License/audit flag: 03 - Configuration / 08 - Administration