diff --git a/docs/BUILTIN_MCP_SERVICES.md b/docs/BUILTIN_MCP_SERVICES.md new file mode 100644 index 00000000..5f79823e --- /dev/null +++ b/docs/BUILTIN_MCP_SERVICES.md @@ -0,0 +1,144 @@ +# 内置 MCP 服务管理指南 + +## 概述 + +内置 MCP 服务是系统级别的 MCP(Model Context Protocol)服务配置,对所有租户可见,但敏感信息会被隐藏,且不可编辑或删除。内置 MCP 服务通常用于提供系统默认的外部工具和资源接入,确保所有租户都能使用统一的 MCP 服务。 + +## 内置 MCP 服务特性 + +- **所有租户可见**:内置 MCP 服务对所有租户都可见,无需单独配置 +- **安全保护**:内置 MCP 服务的敏感信息(URL、认证配置、Headers、环境变量)会被隐藏,无法查看详情 +- **只读保护**:内置 MCP 服务不能被编辑或删除,仅支持测试连接 +- **统一管理**:由系统管理员统一维护,确保配置一致性和安全性 + +## 与内置模型的对比 + +| 特性 | 内置模型 | 内置 MCP 服务 | +|------|---------|--------------| +| 标识字段 | `is_builtin` | `is_builtin` | +| 可见范围 | 所有租户 | 所有租户 | +| 隐藏信息 | API Key、Base URL | URL、认证配置、Headers、环境变量 | +| 编辑保护 | 不可编辑/删除 | 不可编辑/删除 | +| 前端标签 | 显示"内置"标签 | 显示"内置"标签 | +| 启停控制 | — | 禁用开关(始终启用) | + +## 如何添加内置 MCP 服务 + +内置 MCP 服务需要通过数据库直接插入。以下是添加内置 MCP 服务的步骤: + +### 1. 准备服务数据 + +首先,确保你已经有了要设置为内置 MCP 服务的配置信息,包括: +- 服务名称(name) +- 服务描述(description) +- 传输方式(transport_type):`sse` 或 `http-streamable` +- 服务地址(url):SSE / HTTP Streamable 必填 +- 认证配置(auth_config):可选,包括 api_key、token 等 +- 高级配置(advanced_config):可选,包括超时、重试策略等 +- 租户ID(tenant_id):建议使用小于 10000 的租户ID,避免冲突 + +**支持的传输方式**: +- `sse`:Server-Sent Events,推荐用于流式体验 +- `http-streamable`:HTTP Streamable,标准 HTTP 兼容 + +> 注意:出于安全考虑,`stdio` 传输方式在服务端已被禁用。 + +### 2. 执行 SQL 插入语句 + +使用以下 SQL 语句插入内置 MCP 服务: + +```sql +-- 示例:插入一个 SSE 传输方式的内置 MCP 服务 +INSERT INTO mcp_services ( + id, + tenant_id, + name, + description, + enabled, + transport_type, + url, + auth_config, + advanced_config, + is_builtin +) VALUES ( + 'builtin-mcp-001', -- 使用固定ID,建议使用 builtin-mcp- 前缀 + 10000, -- 租户ID(使用第一个租户) + 'Web Search', -- 服务名称 + '内置 Web 搜索 MCP 服务', -- 描述 + true, -- 启用状态 + 'sse', -- 传输方式 + 'https://mcp.example.com/sse', -- 服务地址 + '{"api_key": "your-api-key"}'::jsonb, -- 认证配置 + '{"timeout": 30, "retry_count": 3, "retry_delay": 1}'::jsonb, -- 高级配置 + true -- 标记为内置服务 +) ON CONFLICT (id) DO NOTHING; + +-- 示例:插入一个 HTTP Streamable 传输方式的内置 MCP 服务 +INSERT INTO mcp_services ( + id, + tenant_id, + name, + description, + enabled, + transport_type, + url, + headers, + auth_config, + advanced_config, + is_builtin +) VALUES ( + 'builtin-mcp-002', + 10000, + 'Code Interpreter', + '内置代码解释器 MCP 服务', + true, + 'http-streamable', + 'https://mcp.example.com/stream', + '{"X-Custom-Header": "value"}'::jsonb, + '{"token": "your-bearer-token"}'::jsonb, + '{"timeout": 60, "retry_count": 2, "retry_delay": 2}'::jsonb, + true +) ON CONFLICT (id) DO NOTHING; +``` + +### 3. 验证插入结果 + +执行以下 SQL 查询验证内置 MCP 服务是否成功插入: + +```sql +SELECT id, name, transport_type, enabled, is_builtin +FROM mcp_services +WHERE is_builtin = true +ORDER BY created_at; +``` + +## 注意事项 + +1. **ID 命名规范**:建议使用 `builtin-mcp-{序号}` 的格式,例如 `builtin-mcp-001`、`builtin-mcp-002` +2. **租户ID**:内置 MCP 服务可以属于任意租户,但建议使用第一个租户ID(通常是 10000) +3. **JSON 格式**:`auth_config`、`advanced_config`、`headers` 等字段必须是有效的 JSON 格式 +4. **幂等性**:使用 `ON CONFLICT (id) DO NOTHING` 确保重复执行不会报错 +5. **安全性**:内置 MCP 服务的 URL、认证信息在前端会被自动隐藏,但数据库中的原始数据仍然存在,请妥善保管数据库访问权限 +6. **传输方式限制**:仅支持 `sse` 和 `http-streamable`,`stdio` 已被禁用 + +## 将现有 MCP 服务设置为内置服务 + +如果你已经有一个 MCP 服务,想将其设置为内置服务,可以使用 UPDATE 语句: + +```sql +UPDATE mcp_services +SET is_builtin = true +WHERE id = '服务ID' AND name = '服务名称'; +``` + +## 移除内置 MCP 服务 + +如果需要移除内置标记(恢复为普通 MCP 服务),执行: + +```sql +UPDATE mcp_services +SET is_builtin = false +WHERE id = '服务ID'; +``` + +注意:移除内置标记后,该 MCP 服务将恢复为普通服务,可以被编辑和删除。 diff --git a/frontend/src/api/mcp-service.ts b/frontend/src/api/mcp-service.ts index 0691f487..0cb9538e 100644 --- a/frontend/src/api/mcp-service.ts +++ b/frontend/src/api/mcp-service.ts @@ -24,6 +24,7 @@ export interface MCPService { args: string[] // Command arguments array } env_vars?: Record // Environment variables for stdio transport + is_builtin?: boolean // Whether this is a builtin MCP service created_at?: string updated_at?: string } diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 30125bd9..5abc9f71 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -2241,7 +2241,8 @@ export default { deleteFailed: 'Failed to delete MCP service' }, deleteConfirmBody: 'Delete MCP service "{name}"? This action cannot be undone.', - unnamed: 'Unnamed' + unnamed: 'Unnamed', + builtin: 'Built-in' }, // New: Model Settings modelSettings: { diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index e45fb1f0..c537c5fa 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -2291,6 +2291,7 @@ export default { }, deleteConfirmBody: 'MCP 서비스 "{name}"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.', unnamed: "이름 없음", + builtin: "내장", }, // 모델 설정 modelSettings: { diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 01905cf2..1e761fe9 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -1602,7 +1602,8 @@ export default { deleteFailed: 'Не удалось удалить сервис MCP' }, deleteConfirmBody: 'Удалить сервис MCP «{name}»? Действие необратимо.', - unnamed: 'Без названия' + unnamed: 'Без названия', + builtin: 'Встроенный' }, modelSettings: { title: 'Настройки моделей', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index da18aeb6..123f0755 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -2245,6 +2245,7 @@ export default { }, deleteConfirmBody: '确定要删除 MCP 服务"{name}"吗?此操作无法撤销。', unnamed: "未命名", + builtin: "内置", }, // 新增:模型设置 diff --git a/frontend/src/views/settings/McpSettings.vue b/frontend/src/views/settings/McpSettings.vue index b7e2f019..26706841 100644 --- a/frontend/src/views/settings/McpSettings.vue +++ b/frontend/src/views/settings/McpSettings.vue @@ -35,6 +35,14 @@
{{ service.name }} + + {{ $t('mcpSettings.builtin') }} + + + + + +
@@ -264,6 +285,16 @@ const getServiceOptions = (service: MCPService) => { ] } +// Get service options for builtin services (test only) +const getBuiltinServiceOptions = (service: MCPService) => { + return [ + { + content: t('mcpSettings.actions.test'), + value: `test-${service.id}` + } + ] +} + // Handle menu action const handleMenuAction = (data: { value: string }, service: MCPService) => { const value = data.value diff --git a/internal/application/repository/mcp_service.go b/internal/application/repository/mcp_service.go index 9ee1a942..21f12225 100644 --- a/internal/application/repository/mcp_service.go +++ b/internal/application/repository/mcp_service.go @@ -25,10 +25,12 @@ func (r *mcpServiceRepository) Create(ctx context.Context, service *types.MCPSer } // GetByID retrieves an MCP service by ID and tenant ID +// Builtin MCP services are visible to all tenants func (r *mcpServiceRepository) GetByID(ctx context.Context, tenantID uint64, id string) (*types.MCPService, error) { var service types.MCPService err := r.db.WithContext(ctx). - Where("id = ? AND tenant_id = ?", id, tenantID). + Where("id = ?", id). + Where("tenant_id = ? OR is_builtin = true", tenantID). First(&service).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -41,10 +43,11 @@ func (r *mcpServiceRepository) GetByID(ctx context.Context, tenantID uint64, id } // List retrieves all MCP services for a tenant +// Includes builtin MCP services visible to all tenants func (r *mcpServiceRepository) List(ctx context.Context, tenantID uint64) ([]*types.MCPService, error) { var services []*types.MCPService err := r.db.WithContext(ctx). - Where("tenant_id = ?", tenantID). + Where("tenant_id = ? OR is_builtin = true", tenantID). Order("created_at DESC"). Find(&services).Error if err != nil { @@ -55,10 +58,11 @@ func (r *mcpServiceRepository) List(ctx context.Context, tenantID uint64) ([]*ty } // ListEnabled retrieves all enabled MCP services for a tenant +// Includes enabled builtin MCP services visible to all tenants func (r *mcpServiceRepository) ListEnabled(ctx context.Context, tenantID uint64) ([]*types.MCPService, error) { var services []*types.MCPService err := r.db.WithContext(ctx). - Where("tenant_id = ? AND enabled = ?", tenantID, true). + Where("(tenant_id = ? OR is_builtin = true) AND enabled = ?", tenantID, true). Order("created_at DESC"). Find(&services).Error if err != nil { @@ -69,6 +73,7 @@ func (r *mcpServiceRepository) ListEnabled(ctx context.Context, tenantID uint64) } // ListByIDs retrieves MCP services by multiple IDs for a tenant +// Includes builtin MCP services visible to all tenants func (r *mcpServiceRepository) ListByIDs( ctx context.Context, tenantID uint64, @@ -80,7 +85,7 @@ func (r *mcpServiceRepository) ListByIDs( var services []*types.MCPService err := r.db.WithContext(ctx). - Where("tenant_id = ? AND id IN ?", tenantID, ids). + Where("(tenant_id = ? OR is_builtin = true) AND id IN ?", tenantID, ids). Find(&services).Error if err != nil { return nil, err diff --git a/internal/application/service/mcp_service.go b/internal/application/service/mcp_service.go index a78abdb4..a7c5b18d 100644 --- a/internal/application/service/mcp_service.go +++ b/internal/application/service/mcp_service.go @@ -81,8 +81,12 @@ func (s *mcpServiceService) ListMCPServices(ctx context.Context, tenantID uint64 } // Mask sensitive data for list view - for _, service := range services { - service.MaskSensitiveData() + for i, service := range services { + if service.IsBuiltin { + services[i] = service.HideSensitiveInfo() + } else { + service.MaskSensitiveData() + } } return services, nil @@ -118,6 +122,11 @@ func (s *mcpServiceService) UpdateMCPService(ctx context.Context, service *types return fmt.Errorf("MCP service not found") } + // Builtin MCP services cannot be updated + if existing.IsBuiltin { + return fmt.Errorf("builtin MCP services cannot be updated") + } + // Determine the final transport type after merge finalTransportType := existing.TransportType if service.TransportType != "" { @@ -232,6 +241,11 @@ func (s *mcpServiceService) DeleteMCPService(ctx context.Context, tenantID uint6 return fmt.Errorf("MCP service not found") } + // Builtin MCP services cannot be deleted + if existing.IsBuiltin { + return fmt.Errorf("builtin MCP services cannot be deleted") + } + // Close client connection s.mcpManager.CloseClient(id) diff --git a/internal/handler/mcp_service.go b/internal/handler/mcp_service.go index 36795b62..70e0f80c 100644 --- a/internal/handler/mcp_service.go +++ b/internal/handler/mcp_service.go @@ -129,9 +129,15 @@ func (h *MCPServiceHandler) GetMCPService(c *gin.Context) { return } + // Hide sensitive information for builtin MCP services + responseService := service + if service.IsBuiltin { + responseService = service.HideSensitiveInfo() + } + c.JSON(http.StatusOK, gin.H{ "success": true, - "data": service, + "data": responseService, }) } diff --git a/internal/types/mcp.go b/internal/types/mcp.go index 393ee22b..069c9eae 100644 --- a/internal/types/mcp.go +++ b/internal/types/mcp.go @@ -32,6 +32,7 @@ type MCPService struct { AdvancedConfig *MCPAdvancedConfig `json:"advanced_config" gorm:"type:json"` StdioConfig *MCPStdioConfig `json:"stdio_config,omitempty" gorm:"type:json"` // Required for stdio transport EnvVars MCPEnvVars `json:"env_vars,omitempty" gorm:"type:json"` // Environment variables for stdio + IsBuiltin bool `json:"is_builtin" gorm:"default:false"` // Whether this is a builtin MCP service (visible to all tenants) CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` @@ -217,6 +218,21 @@ func (m *MCPService) MaskSensitiveData() { } } +// HideSensitiveInfo returns a copy of the MCP service with sensitive fields cleared for builtin services +func (m *MCPService) HideSensitiveInfo() *MCPService { + if !m.IsBuiltin { + return m + } + + copy := *m + copy.URL = nil + copy.AuthConfig = nil + copy.Headers = nil + copy.EnvVars = nil + copy.StdioConfig = nil + return © +} + // maskString masks a string, showing only first 4 and last 4 characters func maskString(s string) string { if len(s) <= 8 { diff --git a/migrations/sqlite/000000_init.up.sql b/migrations/sqlite/000000_init.up.sql index a61a12a9..19b22973 100644 --- a/migrations/sqlite/000000_init.up.sql +++ b/migrations/sqlite/000000_init.up.sql @@ -250,6 +250,7 @@ CREATE TABLE IF NOT EXISTS mcp_services ( advanced_config TEXT, stdio_config TEXT, env_vars TEXT, + is_builtin BOOLEAN NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, deleted_at DATETIME @@ -257,6 +258,7 @@ CREATE TABLE IF NOT EXISTS mcp_services ( CREATE INDEX IF NOT EXISTS idx_mcp_services_tenant_id ON mcp_services(tenant_id); CREATE INDEX IF NOT EXISTS idx_mcp_services_enabled ON mcp_services(enabled); +CREATE INDEX IF NOT EXISTS idx_mcp_services_is_builtin ON mcp_services(is_builtin); CREATE INDEX IF NOT EXISTS idx_mcp_services_deleted_at ON mcp_services(deleted_at); CREATE TABLE IF NOT EXISTS custom_agents ( diff --git a/migrations/versioned/000017_mcp_builtin.down.sql b/migrations/versioned/000017_mcp_builtin.down.sql new file mode 100644 index 00000000..7024007d --- /dev/null +++ b/migrations/versioned/000017_mcp_builtin.down.sql @@ -0,0 +1,14 @@ +-- ============================================================================ +-- Migration 000017 DOWN: Remove is_builtin from mcp_services +-- ============================================================================ + +DO $ BEGIN RAISE NOTICE '[Migration 000017 DOWN] Removing is_builtin column from mcp_services...'; END $; + +-- Drop index +DROP INDEX IF EXISTS idx_mcp_services_is_builtin; + +-- Remove is_builtin column +ALTER TABLE mcp_services +DROP COLUMN IF EXISTS is_builtin; + +DO $ BEGIN RAISE NOTICE '[Migration 000017 DOWN] is_builtin column removed from mcp_services'; END $; diff --git a/migrations/versioned/000017_mcp_builtin.up.sql b/migrations/versioned/000017_mcp_builtin.up.sql new file mode 100644 index 00000000..e1ea0d56 --- /dev/null +++ b/migrations/versioned/000017_mcp_builtin.up.sql @@ -0,0 +1,11 @@ +-- ============================================================================ +-- Migration 000017: Add is_builtin support for MCP services +-- ============================================================================ + +DO $ BEGIN RAISE NOTICE '[Migration 000017] Adding is_builtin column to mcp_services...'; END $; + +-- Add is_builtin column to mcp_services +ALTER TABLE mcp_services ADD COLUMN IF NOT EXISTS is_builtin BOOLEAN NOT NULL DEFAULT false; +CREATE INDEX IF NOT EXISTS idx_mcp_services_is_builtin ON mcp_services(is_builtin); + +DO $ BEGIN RAISE NOTICE '[Migration 000017] is_builtin column added to mcp_services'; END $;