Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/advanced-features/new-ai-plugin-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,23 @@ customCompatibleAIModels: [
]
```

#### 自定义添加 MCP Servers
在options参数中可以通过如下参数配置MCP:
- mcpConfig.mcpServers: 该参数配置添加自定义MCP 服务器,添加后的 MCP 服务器可以在AI插件的Sender输入框MCP配置中进行管理(控制服务器开关、查看并切换工具开关)。
配置示例格式如下:
```javascript
mcpConfig: {
mcpServers: {
// 支持添加自定义MCP Server服务器
'img-search': {
type: 'SSE', // 支持SSE和StreamableHttp两种类型
icon: 'https://xxx', // 自定义图标
url: 'https://xxxx/mcp' // 自定义MCP Server地址
}
}
}
```

#### 自定义 Agent 模式上下文功能

- enableResourceContext: 该参数配置是否在提示词上下文携带资源插件图片,AI 会在生成的页面中自动匹配合适的图片资源,默认开启
Expand Down
1 change: 1 addition & 0 deletions packages/common/composable/mcp/toolUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const registerTools = (state: IState, tools: ToolItem[]) => {

if (toolInstance) {
state.toolInstanceMap.set(name, toolInstance)
state.toolList.push(tool)
}

return toolInstance
Expand Down
10 changes: 10 additions & 0 deletions packages/plugins/robot/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export default {
// 支持通过注册表传入chat和agent模式的实现
// chat: useCustomChatMode
// agent: useCustomAgentMode
},
mcpConfig: {
mcpServers: {
// 支持添加自定义MCP Server服务器
// 'img-search': {
// type: 'SSE', // 支持SSE和StreamableHttp两种类型
// icon: 'https://xxx', // 自定义图标
// url: 'https://xxxx/mcp' // 自定义MCP Server地址
// }
}
}
}
}
7 changes: 4 additions & 3 deletions packages/plugins/robot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
"license": "MIT",
"homepage": "https://opentiny.design/tiny-engine",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.20.2",
"@opentiny/tiny-engine-common": "workspace:*",
"@opentiny/tiny-engine-meta-register": "workspace:*",
"@opentiny/tiny-engine-utils": "workspace:*",
"@opentiny/tiny-robot": "0.3.0",
"@opentiny/tiny-robot-kit": "0.3.0",
"@opentiny/tiny-robot-svgs": "0.3.0",
"@opentiny/tiny-robot": "0.3.1",
"@opentiny/tiny-robot-kit": "0.3.1",
"@opentiny/tiny-robot-svgs": "0.3.1",
"@opentiny/tiny-schema-renderer": "1.0.0-beta.6",
"@vueuse/core": "^9.13.0",
"dompurify": "^3.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ const {
inUseMcpServers: installedPlugins,
refreshMcpServerTools,
updateMcpServerToolStatus,
updateMcpServerStatus
updateMcpServerStatus,
updateMcpServerToggle
} = useMcpServer()

// 插件状态切换
const handlePluginToggle = (plugin: PluginInfo, enabled: boolean) => {
plugin.enabled = enabled
updateMcpServerToggle(plugin, enabled)
}

// 插件展开状态变化
Expand Down
146 changes: 125 additions & 21 deletions packages/plugins/robot/src/composables/features/useMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import type { PluginInfo, PluginTool } from '@opentiny/tiny-robot'
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
import type { McpTool } from '../../types/mcp.types'
import type { RequestTool } from '../../types/chat.types'
import { getRobotServiceOptions } from '../../utils'
import { MCPHost } from '../../services/MCPHost'

const mcpHost = new MCPHost()

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

const inUseMcpServers = ref<PluginInfo[]>([{ ...ENGINE_MCP_SERVER, enabled: true, expanded: true, tools: [] }])
Expand All @@ -18,6 +22,9 @@ const updateServerTools = (serverId: string, tools: PluginTool[]) => {
const mcpServer = inUseMcpServers.value.find((item) => item.id === serverId)
if (mcpServer) {
mcpServer.tools = tools
if (!mcpHost.getClient(serverId) && serverId === ENGINE_MCP_SERVER.id) {
mcpHost.setClient(serverId, getMetaApi(META_SERVICE.McpService)?.getMcpClient())
}
}
}

Expand All @@ -31,13 +38,14 @@ const updateEngineTools = async () => {
enabled: tool.status === 'enabled'
}))
updateServerTools(ENGINE_MCP_SERVER.id, engineTools)
return engineTools
}

const convertMCPToOpenAITools = (mcpTools: McpTool[]): RequestTool[] => {
return mcpTools.map((tool: McpTool) => ({
type: 'function',
function: {
name: tool.name,
name: tool.id || tool.name,
description: tool.description || '',
parameters: {
type: 'object',
Expand All @@ -50,12 +58,6 @@ const convertMCPToOpenAITools = (mcpTools: McpTool[]): RequestTool[] => {
})) as RequestTool[]
}

const getEngineServer = () => {
return inUseMcpServers.value.find((item) => item.id === ENGINE_MCP_SERVER.id)
}

const isToolsEnabled = computed(() => getEngineServer()?.tools?.some((tool) => tool.enabled))

const updateEngineServerToolStatus = (toolId: string, enabled: boolean) => {
getMetaApi(META_SERVICE.McpService)?.updateTool?.(toolId, { enabled })
}
Expand All @@ -67,15 +69,38 @@ const updateEngineServer = (engineServer: PluginInfo, enabled: boolean) => {
})
}

const updateMcpServerToggle = async (server: PluginInfo, enabled: boolean) => {
if (server.id === ENGINE_MCP_SERVER.id) {
server.enabled = enabled
return
}
const inuseServer = inUseMcpServers.value.find((s) => s.id === server.id)
if (!inuseServer) {
return
}
try {
if (enabled) {
const { tools } = await connectMcpServer(inuseServer) // eslint-disable-line
inuseServer.tools = tools.map((tool) => ({ ...tool, enabled: true }))
} else {
await disconnectMcpServer(inuseServer.id) // eslint-disable-line
inuseServer.tools = []
}
inuseServer.enabled = enabled
} catch (error) {
inuseServer.enabled = false
}
}

const updateMcpServerStatus = async (server: PluginInfo, added: boolean) => {
// 市场添加状态修改
server.added = added
server.addState = added ? 'added' : 'idle'
if (added) {
const newServer: PluginInfo = {
...server,
id: server.id || `mcp-server-${Date.now()}`,
enabled: true,
added: true,
addState: 'added',
expanded: false,
tools: server.tools || []
}
Expand Down Expand Up @@ -103,33 +128,112 @@ const updateMcpServerToolStatus = (currentServer: PluginInfo, toolId: string, en
}
}

const updateCustomMcpServers = async () => {
const customMcpServers = getRobotServiceOptions().mcpConfig?.mcpServers || {}
if (Object.keys(customMcpServers).length > 0) {
if (
Object.values(customMcpServers).some(
(server) => !['streamablehttp', 'sse'].includes(server.type?.toLowerCase()) || !server.url
)
) {
return {
result: 'failed',
message: '解析JSON失败,缺少type或url字段'
}
}
for (const [serverName, server] of Object.entries(customMcpServers)) {
if (inUseMcpServers.value.find((s) => s.id === serverName)) {
continue
}
const newServer: PluginInfo = {
id: serverName,
name: serverName,
icon: server.icon,
description: server.description,
enabled: false,
addState: 'added',
expanded: false,
type: server.type,
url: server.url,
tools: []
}
inUseMcpServers.value.push(newServer)
}
}
}

const connectMcpServer = (server: PluginInfo) => {
return mcpHost.connectToServer({
id: server.id,
url: server.url,
type: server.type
})
}

const disconnectMcpServer = (serverId: string) => {
return mcpHost.disconnect(serverId)
}

const refreshMcpServerTools = () => {
updateEngineTools()
updateCustomMcpServers()
}

let llmTools: RequestTool[] | null = null

const listTools = async (): Promise<McpTool[]> => {
const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
return mcpTools?.tools || []
const toolsMap = computed(() => {
return inUseMcpServers.value
.filter((server) => server.enabled)
.reduce((acc, server) => {
server.tools
.filter((tool) => tool.enabled)
.forEach((tool) => {
acc[tool.id || tool.name] = {
server: server.id,
...tool
}
})
return acc
}, {})
})

const callTool = async (toolId: string, args: Record<string, unknown>) => {
return mcpHost.getClient(toolsMap.value[toolId]?.server)?.callTool({ name: toolId, arguments: args || {} }) || {}
}

const callTool = async (toolId: string, args: Record<string, unknown>) =>
getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.callTool({ name: toolId, arguments: args }) || {}
const tools = computed(() => {
return convertMCPToOpenAITools(
inUseMcpServers.value
.filter((server) => server.enabled)
.map((server) => server.tools.filter((tool) => tool.enabled))
.flat()
)
})

const getLLMTools = async () => {
const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
llmTools = convertMCPToOpenAITools(mcpTools?.tools || [])
return llmTools
const servers = inUseMcpServers.value.filter((server) => server.enabled && server.tools.length > 0)
const tools = await Promise.all(
servers.map(async (server) => {
const enabledTools = server.tools?.filter((tool) => tool.enabled).map((tool) => tool.id || tool.name) || []
const client = mcpHost.getClient(server.id)
if (client) {
const listToolResult: { tools: McpTool[] } = await client.listTools()
return listToolResult.tools.filter((tool) => enabledTools.includes(tool.name))
}
return []
})
)

return convertMCPToOpenAITools(tools.flat())
}

const isToolsEnabled = computed(() => tools.value.length > 0)

export default function useMcpServer() {
return {
inUseMcpServers,
refreshMcpServerTools,
updateMcpServerStatus,
updateMcpServerToolStatus,
listTools,
updateMcpServerToggle,
callTool,
getLLMTools,
isToolsEnabled
Expand Down
Loading