Skip to content

Commit 295a806

Browse files
authored
feat: support add custom mcp server (#1731)
* feat: support add custom mcp server * chore: upgrade robot version to fix autoscroll * fix: review
1 parent b323e9a commit 295a806

7 files changed

Lines changed: 318 additions & 32 deletions

File tree

docs/advanced-features/new-ai-plugin-usage.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,38 @@ customCompatibleAIModels: [
248248
]
249249
```
250250

251+
#### 自定义添加 MCP Servers
252+
在options参数中可以通过如下参数配置MCP:
253+
- mcpConfig.mcpServers: 该参数配置添加自定义MCP 服务器,添加后的 MCP 服务器可以在AI插件的Sender输入框MCP配置中进行管理(控制服务器开关、查看并切换工具开关)。
254+
配置示例格式如下:
255+
```typescript
256+
interface McpServerConfig {
257+
type: 'SSE' | 'StreamableHttp'; // 必填,仅支持两种类型
258+
url: string; // 必填,MCP Server地址
259+
name?: string; // 可选,显示名称
260+
description?: string; // 可选,server描述
261+
icon?: string; // 可选,自定义图标URL
262+
}
263+
interface McpConfig {
264+
mcpServers: {
265+
[serverName: string]: McpServerConfig; // server名称作为key,值为server配置
266+
};
267+
}
268+
// 使用示例
269+
const config: McpConfig = {
270+
mcpServers: {
271+
'img-search': {
272+
type: 'SSE',
273+
url: 'https://xxxx/mcp',
274+
name: '自定义MCP服务器',
275+
description: 'xxx',
276+
icon: 'https://xxx',
277+
},
278+
// 可以添加更多server配置...
279+
},
280+
};
281+
```
282+
251283
#### 自定义 Agent 模式上下文功能
252284

253285
- enableResourceContext: 该参数配置是否在提示词上下文携带资源插件图片,AI 会在生成的页面中自动匹配合适的图片资源,默认开启

packages/common/composable/mcp/toolUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const registerTools = (state: IState, tools: ToolItem[]) => {
6060

6161
if (toolInstance) {
6262
state.toolInstanceMap.set(name, toolInstance)
63+
state.toolList.push(tool)
6364
}
6465

6566
return toolInstance

packages/plugins/robot/meta.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ export default {
1515
// 支持通过注册表传入chat和agent模式的实现
1616
// chat: useCustomChatMode
1717
// agent: useCustomAgentMode
18+
},
19+
mcpConfig: {
20+
mcpServers: {
21+
// 支持添加自定义MCP Server服务器
22+
// 'img-search': { // MCP 服务器 id, 使用英文
23+
// type: 'SSE', // 支持 SSE 和 StreamableHttp 两种类型
24+
// name: '自定义MCP服务器', // 显示名称
25+
// description: 'xxx', // 描述信息
26+
// icon: 'https://xxx', // 自定义图标
27+
// url: 'https://xxxx/mcp' // 自定义MCP Server地址
28+
// }
29+
}
1830
}
1931
}
2032
}

packages/plugins/robot/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
"license": "MIT",
2626
"homepage": "https://opentiny.design/tiny-engine",
2727
"dependencies": {
28+
"@modelcontextprotocol/sdk": "^1.20.2",
2829
"@opentiny/tiny-engine-common": "workspace:*",
2930
"@opentiny/tiny-engine-meta-register": "workspace:*",
3031
"@opentiny/tiny-engine-utils": "workspace:*",
31-
"@opentiny/tiny-robot": "0.3.0",
32-
"@opentiny/tiny-robot-kit": "0.3.0",
33-
"@opentiny/tiny-robot-svgs": "0.3.0",
32+
"@opentiny/tiny-robot": "0.3.1",
33+
"@opentiny/tiny-robot-kit": "0.3.1",
34+
"@opentiny/tiny-robot-svgs": "0.3.1",
3435
"@opentiny/tiny-schema-renderer": "1.0.0-beta.6",
3536
"@vueuse/core": "^9.13.0",
3637
"dompurify": "^3.0.1",

packages/plugins/robot/src/components/footer-extension/McpServer.vue

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
:show-market-tab="false"
1818
@plugin-expand="handlePluginExpand"
1919
@plugin-add="updateMcpServerStatus"
20-
@plugin-toggle="handlePluginToggle"
20+
@plugin-toggle="updateMcpServerToggle"
2121
@tool-toggle="updateMcpServerToolStatus"
2222
/>
2323
</div>
@@ -51,14 +51,10 @@ const {
5151
inUseMcpServers: installedPlugins,
5252
refreshMcpServerTools,
5353
updateMcpServerToolStatus,
54-
updateMcpServerStatus
54+
updateMcpServerStatus,
55+
updateMcpServerToggle
5556
} = useMcpServer()
5657
57-
// 插件状态切换
58-
const handlePluginToggle = (plugin: PluginInfo, enabled: boolean) => {
59-
plugin.enabled = enabled
60-
}
61-
6258
// 插件展开状态变化
6359
const handlePluginExpand = (plugin: PluginInfo, expanded: boolean) => {
6460
const targetPlugin = installedPlugins.value.find((p) => p.id === plugin.id)

packages/plugins/robot/src/composables/features/useMcp.ts

Lines changed: 132 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,26 @@ import type { PluginInfo, PluginTool } from '@opentiny/tiny-robot'
33
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
44
import type { McpTool } from '../../types/mcp.types'
55
import type { RequestTool } from '../../types/chat.types'
6+
import { getRobotServiceOptions } from '../../utils'
7+
import { MCPHost } from '../../services/MCPHost'
8+
9+
const mcpHost = new MCPHost()
10+
11+
const defaultMcpIcon =
12+
'https://res.hc-cdn.com/lowcode-portal/1.1.80.20250515160330/assets/opentiny-tinyengine-logo-4f8a3801.svg'
13+
14+
enum PluginAddState {
15+
Added = 'added',
16+
Idle = 'idle',
17+
Loading = 'loading'
18+
}
619

720
const ENGINE_MCP_SERVER: PluginInfo = {
821
id: 'tiny-engine-mcp-server',
922
name: 'Tiny Engine MCP 工具',
10-
icon: 'https://res.hc-cdn.com/lowcode-portal/1.1.80.20250515160330/assets/opentiny-tinyengine-logo-4f8a3801.svg',
23+
icon: defaultMcpIcon,
1124
description: '使用TinyEngine设计器能力,如操作画布、编辑页面等',
12-
added: true
25+
addState: PluginAddState.Added
1326
}
1427

1528
const inUseMcpServers = ref<PluginInfo[]>([{ ...ENGINE_MCP_SERVER, enabled: true, expanded: true, tools: [] }])
@@ -18,6 +31,9 @@ const updateServerTools = (serverId: string, tools: PluginTool[]) => {
1831
const mcpServer = inUseMcpServers.value.find((item) => item.id === serverId)
1932
if (mcpServer) {
2033
mcpServer.tools = tools
34+
if (!mcpHost.getClient(serverId) && serverId === ENGINE_MCP_SERVER.id) {
35+
mcpHost.setClient(serverId, getMetaApi(META_SERVICE.McpService)?.getMcpClient())
36+
}
2137
}
2238
}
2339

@@ -31,13 +47,14 @@ const updateEngineTools = async () => {
3147
enabled: tool.status === 'enabled'
3248
}))
3349
updateServerTools(ENGINE_MCP_SERVER.id, engineTools)
50+
return engineTools
3451
}
3552

3653
const convertMCPToOpenAITools = (mcpTools: McpTool[]): RequestTool[] => {
3754
return mcpTools.map((tool: McpTool) => ({
3855
type: 'function',
3956
function: {
40-
name: tool.name,
57+
name: tool.id || tool.name,
4158
description: tool.description || '',
4259
parameters: {
4360
type: 'object',
@@ -50,12 +67,6 @@ const convertMCPToOpenAITools = (mcpTools: McpTool[]): RequestTool[] => {
5067
})) as RequestTool[]
5168
}
5269

53-
const getEngineServer = () => {
54-
return inUseMcpServers.value.find((item) => item.id === ENGINE_MCP_SERVER.id)
55-
}
56-
57-
const isToolsEnabled = computed(() => getEngineServer()?.tools?.some((tool) => tool.enabled))
58-
5970
const updateEngineServerToolStatus = (toolId: string, enabled: boolean) => {
6071
getMetaApi(META_SERVICE.McpService)?.updateTool?.(toolId, { enabled })
6172
}
@@ -67,15 +78,38 @@ const updateEngineServer = (engineServer: PluginInfo, enabled: boolean) => {
6778
})
6879
}
6980

81+
const updateMcpServerToggle = async (server: PluginInfo, enabled: boolean) => {
82+
if (server.id === ENGINE_MCP_SERVER.id) {
83+
server.enabled = enabled
84+
return
85+
}
86+
const inuseServer = inUseMcpServers.value.find((s) => s.id === server.id)
87+
if (!inuseServer) {
88+
return
89+
}
90+
try {
91+
if (enabled) {
92+
const { tools } = await connectMcpServer(inuseServer) // eslint-disable-line
93+
inuseServer.tools = tools.map((tool) => ({ ...tool, enabled: true }))
94+
} else {
95+
await disconnectMcpServer(inuseServer.id) // eslint-disable-line
96+
inuseServer.tools = []
97+
}
98+
inuseServer.enabled = enabled
99+
} catch (error) {
100+
inuseServer.enabled = false
101+
}
102+
}
103+
70104
const updateMcpServerStatus = async (server: PluginInfo, added: boolean) => {
71105
// 市场添加状态修改
72-
server.added = added
106+
server.addState = added ? PluginAddState.Added : PluginAddState.Idle
73107
if (added) {
74108
const newServer: PluginInfo = {
75109
...server,
76110
id: server.id || `mcp-server-${Date.now()}`,
77111
enabled: true,
78-
added: true,
112+
addState: PluginAddState.Added,
79113
expanded: false,
80114
tools: server.tools || []
81115
}
@@ -103,33 +137,109 @@ const updateMcpServerToolStatus = (currentServer: PluginInfo, toolId: string, en
103137
}
104138
}
105139

140+
const updateCustomMcpServers = async () => {
141+
const mcpServersConfig = getRobotServiceOptions().mcpConfig?.mcpServers || {}
142+
if (!Object.keys(mcpServersConfig).length) return
143+
const customMcpServers = Object.entries(mcpServersConfig).map(([id, config]) => ({ id, ...config }))
144+
const logger = console
145+
146+
customMcpServers.forEach((server) => {
147+
if (!['streamablehttp', 'sse'].includes(server.type?.toLowerCase()) || !server.url) {
148+
logger.error(`解析mcpServer: ${server.id} 配置失败,type/url字段缺失或有误.`)
149+
return
150+
}
151+
152+
if (inUseMcpServers.value.find((s) => s.id === server.id)) {
153+
return
154+
}
155+
const newServer: PluginInfo = {
156+
id: server.id,
157+
name: server.name || server.id,
158+
icon: server.icon || defaultMcpIcon,
159+
description: server.description || '',
160+
enabled: false,
161+
addState: PluginAddState.Added,
162+
expanded: false,
163+
type: server.type,
164+
url: server.url,
165+
tools: []
166+
}
167+
inUseMcpServers.value.push(newServer)
168+
})
169+
}
170+
171+
const connectMcpServer = (server: PluginInfo) => {
172+
return mcpHost.connectToServer({
173+
id: server.id,
174+
url: server.url,
175+
type: server.type
176+
})
177+
}
178+
179+
const disconnectMcpServer = (serverId: string) => {
180+
return mcpHost.disconnect(serverId)
181+
}
182+
106183
const refreshMcpServerTools = () => {
107184
updateEngineTools()
185+
updateCustomMcpServers()
108186
}
109187

110-
let llmTools: RequestTool[] | null = null
111-
112-
const listTools = async (): Promise<McpTool[]> => {
113-
const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
114-
return mcpTools?.tools || []
188+
const toolsMap = computed(() => {
189+
return inUseMcpServers.value
190+
.filter((server) => server.enabled)
191+
.reduce((acc, server) => {
192+
server.tools
193+
.filter((tool) => tool.enabled)
194+
.forEach((tool) => {
195+
acc[tool.id || tool.name] = {
196+
server: server.id,
197+
...tool
198+
}
199+
})
200+
return acc
201+
}, {})
202+
})
203+
204+
const callTool = async (toolId: string, args: Record<string, unknown>) => {
205+
return mcpHost.getClient(toolsMap.value[toolId]?.server)?.callTool({ name: toolId, arguments: args || {} }) || {}
115206
}
116207

117-
const callTool = async (toolId: string, args: Record<string, unknown>) =>
118-
getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.callTool({ name: toolId, arguments: args }) || {}
208+
const tools = computed(() => {
209+
return convertMCPToOpenAITools(
210+
inUseMcpServers.value
211+
.filter((server) => server.enabled)
212+
.map((server) => server.tools.filter((tool) => tool.enabled))
213+
.flat()
214+
)
215+
})
119216

120217
const getLLMTools = async () => {
121-
const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
122-
llmTools = convertMCPToOpenAITools(mcpTools?.tools || [])
123-
return llmTools
218+
const servers = inUseMcpServers.value.filter((server) => server.enabled && server.tools.length > 0)
219+
const tools = await Promise.all(
220+
servers.map(async (server) => {
221+
const enabledTools = server.tools?.filter((tool) => tool.enabled).map((tool) => tool.id || tool.name) || []
222+
const client = mcpHost.getClient(server.id)
223+
if (client) {
224+
const listToolResult: { tools: McpTool[] } = await client.listTools()
225+
return listToolResult.tools.filter((tool) => enabledTools.includes(tool.name))
226+
}
227+
return []
228+
})
229+
)
230+
231+
return convertMCPToOpenAITools(tools.flat())
124232
}
125233

234+
const isToolsEnabled = computed(() => tools.value.length > 0)
235+
126236
export default function useMcpServer() {
127237
return {
128238
inUseMcpServers,
129239
refreshMcpServerTools,
130240
updateMcpServerStatus,
131241
updateMcpServerToolStatus,
132-
listTools,
242+
updateMcpServerToggle,
133243
callTool,
134244
getLLMTools,
135245
isToolsEnabled

0 commit comments

Comments
 (0)