Skip to content

Commit 25dc53c

Browse files
committed
feat: add next-sdk web skills
1 parent 07051a8 commit 25dc53c

16 files changed

Lines changed: 300 additions & 364 deletions

File tree

pnpm-lock.yaml

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

template/tinyvue/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
"@babel/core": "^7.25.2",
3030
"@gaonengwww/mock-server": "^1.0.5",
3131
"@opentiny/icons": "^0.1.3",
32-
"@opentiny/next-remoter": "0.2.6-beta.0",
33-
"@opentiny/next-sdk": "0.2.6-beta.0",
32+
"@opentiny/next-remoter": "0.2.7",
33+
"@opentiny/next-sdk": "0.2.7",
3434
"@opentiny/vue": "^3.28.0",
3535
"@opentiny/vue-huicharts": "~3.28.0",
3636
"@opentiny/vue-icon": "~3.28.0",

template/tinyvue/src/App.vue

Lines changed: 13 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
<script lang="ts" setup>
22
import { TinyRemoter } from '@opentiny/next-remoter'
3-
import { createMessageChannelPairTransport, WebMcpClient, WebMcpServer, z } from '@opentiny/next-sdk'
43
import { TinyConfigProvider } from '@opentiny/vue'
54
import TinyThemeTool from '@opentiny/vue-theme/theme-tool'
6-
import { onMounted, provide, ref } from 'vue'
7-
import { useRoute, useRouter } from 'vue-router'
5+
import { onMounted } from 'vue'
86
import GlobalSetting from '@/components/global-setting/index.vue'
97
import { useTheme } from './hooks/useTheme'
8+
import { clientTransport, createMcpServer } from './mcp-servers'
109
import { skills } from './skills'
1110
import '@opentiny/next-remoter/dist/style.css'
1211
@@ -26,51 +25,18 @@ const design = {
2625
},
2726
}
2827
29-
const sessionId = ref('')
30-
const [serverTransport, clientTransport] = createMessageChannelPairTransport()
31-
provide('serverTransport', serverTransport)
32-
33-
const AGENT_URL = 'https://agent.opentiny.design/api/v1/webmcp-trial/' // 'http://localhost:3030/api/v1/webmcp/'
28+
// 将本地 MCP Server 注册到 TinyRemoter
29+
// key 为服务器名称(自定义),type: 'local' 表示浏览器本地运行
30+
const mcpServers = {
31+
'my-mcp-server': {
32+
type: 'local',
33+
transport: clientTransport,
34+
},
35+
}
3436
37+
// 启动 MCP Server(注册工具 + 建立通信通道)
3538
onMounted(async () => {
36-
const server = new WebMcpServer()
37-
const $router = useRouter()
38-
const $route = useRoute()
39-
40-
// TODO: 参数需要优化,用户不会知道具体的路由路径,用户的语言可能是:帮我打开菜单管理页面,这时应该根据名称获取路由路径,再做路由跳转
41-
// 进一步优化:用户可能在任意页面直接提需求:帮我创建 xx 菜单,这时 AI 应该先跳转菜单管理页面,然后调佣创建菜单的工具
42-
server.registerTool(
43-
'switch-router',
44-
{
45-
title: '切换路由',
46-
description: '切换路由',
47-
inputSchema: {
48-
routerPath: z.string().describe('路由路径'),
49-
},
50-
},
51-
async ({ routerPath }) => {
52-
if ($route.path === routerPath) {
53-
return { content: [{ type: 'text', text: routerPath }] }
54-
}
55-
56-
$router.push(import.meta.env.VITE_CONTEXT + routerPath)
57-
return { content: [{ type: 'text', text: routerPath }] }
58-
},
59-
)
60-
await server.connect(serverTransport)
61-
62-
// 创建 WebMcpClient ,并与 WebAgent 连接
63-
const client = new WebMcpClient()
64-
await client.connect(clientTransport)
65-
const { sessionId: sessionID } = await client.connect({
66-
agent: true,
67-
68-
// sessionId 为可选参数。若传入该参数,系统将使用指定值作为会话标识;若未传入,WebAgent 服务将自动生成一个随机的字符串作为 sessionId。为便于通过 MCP Inspector 工具进行调试,此处采用了固定的 sessionId。用户亦可通过浏览器原生提供的 crypto.randomUUID() 方法生成随机字符串作为会话标识。
69-
sessionId: 'd299a869-c674-4125-a84b-bb4e24079b99',
70-
71-
url: `${AGENT_URL}mcp`,
72-
})
73-
sessionId.value = sessionID
39+
await createMcpServer()
7440
})
7541
</script>
7642

@@ -83,23 +49,8 @@ onMounted(async () => {
8349
<GlobalSetting />
8450
</div>
8551
<TinyRemoter
86-
:agent-root="AGENT_URL"
87-
:session-id="sessionId"
88-
:menu-items="[
89-
{
90-
action: 'qr-code',
91-
show: false,
92-
},
93-
{
94-
action: 'remote-control',
95-
show: false,
96-
},
97-
{
98-
action: 'remote-url',
99-
show: false,
100-
},
101-
]"
10252
:skills="skills"
53+
:mcp-servers="mcpServers"
10354
/>
10455
</template>
10556

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createMessageChannelPairTransport, WebMcpServer, withPageTools } from '@opentiny/next-sdk'
2+
import registerLocaleManagementTools from './locale/tools'
3+
import registerMenuManagementTools from './menu/tools'
4+
import registerPermissionManagementTools from './permission/tools'
5+
import registerRoleManagementTools from './role/tools'
6+
import registerUserManagementTools from './user/tools'
7+
8+
const rawServer = new WebMcpServer()
9+
const [serverTransport, clientTransport] = createMessageChannelPairTransport()
10+
11+
// withPageTools 包装后,registerTool 第三个参数支持路由配置对象
12+
export const server = withPageTools(rawServer)
13+
14+
// clientTransport 导出给 TinyRemoter 使用
15+
export { clientTransport }
16+
17+
export async function createMcpServer() {
18+
registerLocaleManagementTools(server)
19+
registerUserManagementTools(server)
20+
registerRoleManagementTools(server)
21+
registerPermissionManagementTools(server)
22+
registerMenuManagementTools(server)
23+
// 最后建立连接,确保所有工具已注册完毕
24+
await rawServer.connect(serverTransport)
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { PageAwareServer } from '@opentiny/next-sdk'
2+
import { z } from '@opentiny/next-sdk'
3+
4+
function registerLocaleManagementTools(server: PageAwareServer) {
5+
server.registerTool(
6+
'add-i18n-entry',
7+
{
8+
title: '添加国际化词条',
9+
description: '添加国际化词条',
10+
inputSchema: {
11+
key: z.string().describe('词条关键字,请自行创建,不要询问用户'),
12+
content: z.string().describe('词条内容'),
13+
lang: z.union([z.literal(1), z.literal(2)]).describe('词条语言ID,英文 enUS 为:1,中文 zhCN 为:2'),
14+
},
15+
},
16+
// 第三个参数传路由配置:工具被调用时自动跳转到 /locale
17+
// 页面加载完成后,通过 postMessage 把 input 转发给页面内的处理器
18+
{ route: '/vue-pro/locale' },
19+
)
20+
}
21+
22+
export default registerLocaleManagementTools
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { PageAwareServer } from '@opentiny/next-sdk'
2+
import { z } from '@opentiny/next-sdk'
3+
4+
function registerMenuManagementTools(server: PageAwareServer) {
5+
server.registerTool(
6+
'add-menu',
7+
{
8+
title: '添加菜单',
9+
description: '添加菜单',
10+
inputSchema: {
11+
name: z.string().describe('名称'),
12+
order: z.number().describe('优先级').default(0),
13+
parentMenu: z.string().describe('父菜单').optional(),
14+
icon: z.string().describe('图标').optional().default(''),
15+
component: z.string().describe('组件'),
16+
path: z.string().describe('路径'),
17+
locale: z.string().describe('国际化'),
18+
},
19+
},
20+
{ route: '/vue-pro/menu/allMenu' },
21+
)
22+
}
23+
24+
export default registerMenuManagementTools
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { PageAwareServer } from '@opentiny/next-sdk'
2+
import { z } from '@opentiny/next-sdk'
3+
4+
function registerPermissionManagementTools(server: PageAwareServer) {
5+
server.registerTool(
6+
'add-permission',
7+
{
8+
title: '添加权限',
9+
description: '添加权限',
10+
inputSchema: {
11+
name: z.string().describe('权限名称'),
12+
desc: z.string().describe('权限描述'),
13+
},
14+
},
15+
{ route: '/vue-pro/permission/allPermission' },
16+
)
17+
}
18+
19+
export default registerPermissionManagementTools
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { PageAwareServer } from '@opentiny/next-sdk'
2+
import { z } from '@opentiny/next-sdk'
3+
4+
function registerRoleManagementTools(server: PageAwareServer) {
5+
server.registerTool(
6+
'add-role',
7+
{
8+
title: '添加角色',
9+
description: '添加角色,不需要生成角色卡片',
10+
inputSchema: {
11+
name: z.string().describe('角色名称'),
12+
// TODO: 用户的语言可能是添加用户和删除用户的权限,而不是 user::add 和 user::remove 权限或者权限 ID 为 2 和 3,需要做下转换
13+
permissions: z.array(z.number()).describe('角色拥有的权限'),
14+
},
15+
},
16+
{ route: '/vue-pro/role/allRole' },
17+
)
18+
19+
server.registerTool(
20+
'bind-menu-for-role',
21+
{
22+
title: '绑定菜单',
23+
description: '给某个角色绑定菜单',
24+
inputSchema: {
25+
role: z.string().describe('需要绑定菜单的角色名称'),
26+
menu: z.string().describe('需要绑定的菜单名称'),
27+
},
28+
},
29+
{ route: '/vue-pro/role/allRole' },
30+
)
31+
}
32+
33+
export default registerRoleManagementTools
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { PageAwareServer } from '@opentiny/next-sdk'
2+
import { z } from '@opentiny/next-sdk'
3+
4+
function registerUserManagementTools(server: PageAwareServer) {
5+
server.registerTool(
6+
'add-user',
7+
{
8+
title: '添加用户',
9+
description: '添加用户,可选参数不需要用户提供,也不用创建表单卡片,直接根据用户提供的信息添加用户即可',
10+
inputSchema: {
11+
email: z.string().describe('邮箱'),
12+
password: z.string().describe('密码'),
13+
name: z.string().describe('用户名'),
14+
address: z.string().describe('地址').optional(),
15+
department: z.string().describe('所属部门').optional(),
16+
roleIds: z.array(z.number()).describe('职位').optional(),
17+
employeeType: z.string().describe('招聘类型').optional(),
18+
probationDate: z.array(z.date()).describe('试用期起止时间').optional(),
19+
probationDuration: z.string().describe('试用期时长').optional(),
20+
protocolStart: z.date().describe('劳动合同开始日期').optional(),
21+
protocolEnd: z.date().describe('劳动合同结束日期').optional(),
22+
status: z.string().describe('状态').optional(),
23+
},
24+
},
25+
{ route: '/vue-pro/userManager/allInfo' },
26+
)
27+
}
28+
29+
export default registerUserManagementTools

template/tinyvue/src/skills/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ export const skills = import.meta.glob('./**/*', {
33
import: 'default', // 取模块的 default 导出(即文件内容字符串)
44
eager: true // 同步加载,避免异步等待
55
}) as Record<string, string>
6-
console.log('skills:', skills);

0 commit comments

Comments
 (0)