mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
feat: add built-in MCP service support
This commit is contained in:
144
docs/BUILTIN_MCP_SERVICES.md
Normal file
144
docs/BUILTIN_MCP_SERVICES.md
Normal file
@@ -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 服务将恢复为普通服务,可以被编辑和删除。
|
||||
@@ -24,6 +24,7 @@ export interface MCPService {
|
||||
args: string[] // Command arguments array
|
||||
}
|
||||
env_vars?: Record<string, string> // Environment variables for stdio transport
|
||||
is_builtin?: boolean // Whether this is a builtin MCP service
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -2291,6 +2291,7 @@ export default {
|
||||
},
|
||||
deleteConfirmBody: 'MCP 서비스 "{name}"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.',
|
||||
unnamed: "이름 없음",
|
||||
builtin: "내장",
|
||||
},
|
||||
// 모델 설정
|
||||
modelSettings: {
|
||||
|
||||
@@ -1602,7 +1602,8 @@ export default {
|
||||
deleteFailed: 'Не удалось удалить сервис MCP'
|
||||
},
|
||||
deleteConfirmBody: 'Удалить сервис MCP «{name}»? Действие необратимо.',
|
||||
unnamed: 'Без названия'
|
||||
unnamed: 'Без названия',
|
||||
builtin: 'Встроенный'
|
||||
},
|
||||
modelSettings: {
|
||||
title: 'Настройки моделей',
|
||||
|
||||
@@ -2245,6 +2245,7 @@ export default {
|
||||
},
|
||||
deleteConfirmBody: '确定要删除 MCP 服务"{name}"吗?此操作无法撤销。',
|
||||
unnamed: "未命名",
|
||||
builtin: "内置",
|
||||
},
|
||||
|
||||
// 新增:模型设置
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
<div class="service-header">
|
||||
<div class="service-name">
|
||||
{{ service.name }}
|
||||
<t-tag
|
||||
v-if="service.is_builtin"
|
||||
theme="warning"
|
||||
size="small"
|
||||
variant="light"
|
||||
>
|
||||
{{ $t('mcpSettings.builtin') }}
|
||||
</t-tag>
|
||||
<t-tag
|
||||
:theme="getTransportTypeTheme(service.transport_type)"
|
||||
size="small"
|
||||
@@ -48,8 +56,10 @@
|
||||
v-model="service.enabled"
|
||||
@change="() => handleToggleEnabled(service)"
|
||||
size="large"
|
||||
:disabled="service.is_builtin"
|
||||
/>
|
||||
<t-dropdown
|
||||
v-if="!service.is_builtin"
|
||||
:options="getServiceOptions(service)"
|
||||
@click="(data: any) => handleMenuAction(data, service)"
|
||||
placement="bottom-right"
|
||||
@@ -59,6 +69,17 @@
|
||||
<t-icon name="more" />
|
||||
</t-button>
|
||||
</t-dropdown>
|
||||
<t-dropdown
|
||||
v-else
|
||||
:options="getBuiltinServiceOptions(service)"
|
||||
@click="(data: any) => handleMenuAction(data, service)"
|
||||
placement="bottom-right"
|
||||
:disabled="testing"
|
||||
>
|
||||
<t-button variant="text" shape="square" size="small" class="more-btn" :disabled="testing">
|
||||
<t-icon name="more" />
|
||||
</t-button>
|
||||
</t-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="service.description" class="service-description">
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
14
migrations/versioned/000017_mcp_builtin.down.sql
Normal file
14
migrations/versioned/000017_mcp_builtin.down.sql
Normal file
@@ -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 $;
|
||||
11
migrations/versioned/000017_mcp_builtin.up.sql
Normal file
11
migrations/versioned/000017_mcp_builtin.up.sql
Normal file
@@ -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 $;
|
||||
Reference in New Issue
Block a user