Skip to content

Commit 92d5a61

Browse files
authored
feat: add mcp service (opentiny#1528)
1 parent 068a6f3 commit 92d5a61

55 files changed

Lines changed: 2779 additions & 17 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/extension-capabilities-tutorial/mcpService.md

Lines changed: 695 additions & 0 deletions
Large diffs are not rendered by default.

packages/canvas/DesignCanvas/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import { HOOK_NAME } from '@opentiny/tiny-engine-meta-register'
22
import DesignCanvas from './src/DesignCanvas.vue'
33
import metaData from './meta'
44
import api from './src/api'
5+
import mcp from './src/mcp'
6+
57
export default {
68
...metaData,
79
entry: DesignCanvas,
810
apis: api(),
911
composable: {
1012
name: HOOK_NAME.useCanvas
11-
}
13+
},
14+
mcp
1215
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {
2+
getCurrentSelectedNode,
3+
getPageSchema,
4+
queryNodeById,
5+
delNode,
6+
addNode,
7+
changeNodeProps,
8+
selectSpecificNode
9+
} from './tools'
10+
11+
export default {
12+
tools: [getCurrentSelectedNode, getPageSchema, queryNodeById, delNode, addNode, changeNodeProps, selectSpecificNode]
13+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { z } from 'zod'
2+
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
3+
import { utils } from '@opentiny/tiny-engine-utils'
4+
5+
const { validateParams } = utils
6+
7+
type NodeSchema = z.ZodObject<{
8+
componentName: z.ZodString
9+
props: z.ZodObject<Record<string, z.ZodTypeAny>, 'strip', z.ZodTypeAny>
10+
children: z.ZodArray<z.ZodLazy<any>, 'many'>
11+
}>
12+
13+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
14+
const nodeArraySchema = z.lazy(() => nodeSchema)
15+
16+
const nodeSchema: NodeSchema = z.object({
17+
componentName: z.string().describe('The name of the component.'),
18+
props: z.object({}).describe('The props of the component.'),
19+
children: z.array(z.lazy(() => nodeArraySchema)).describe('The children of the component')
20+
})
21+
22+
const inputSchema = z.object({
23+
parentId: z
24+
.string()
25+
.optional()
26+
.describe(
27+
'The id of the parent node. If not provided, the new node will be added to the root. if you don\'t know the parentId, you can use the tool "get_page_schema" to get the page schema. if you want to add to page root, just don\'t provide the parentId.'
28+
),
29+
newNodeData: z.lazy(() => nodeSchema).describe('The new node data.'),
30+
position: z
31+
.enum(['before', 'after'])
32+
.optional()
33+
.describe(
34+
'The position of the new node. If not provided, the new node will be added to the end of the parent node.'
35+
),
36+
referTargetNodeId: z
37+
.string()
38+
.optional()
39+
.describe(
40+
'The id of the reference target node. If not provided, the new node will be added to the end of the parent node. if you don\'t know the referTargetNodeId, you can use the tool "get_page_schema" to get the page schema. if you dont want to refer to any node, just don\'t provide the referTargetNodeId.'
41+
)
42+
})
43+
44+
export const addNode = {
45+
name: 'add_node',
46+
title: '添加节点',
47+
description:
48+
'Add a new node to the current TinyEngine low-code application. Use this when you need to add new node to your application.',
49+
inputSchema: inputSchema.shape,
50+
annotations: {
51+
title: '添加节点',
52+
readOnlyHint: false,
53+
destructiveHint: false,
54+
idempotentHint: false,
55+
openWorldHint: false
56+
},
57+
callback: async (args: z.infer<typeof inputSchema>) => {
58+
const { parentId, newNodeData, position, referTargetNodeId } = args
59+
const componentName = newNodeData.componentName
60+
const { props = {}, children = [] } = newNodeData
61+
62+
const validateResult = validateParams(args, {
63+
componentName: {
64+
required: true,
65+
message:
66+
'Component name is required, if you don\'t know the component name, you can use the tool "get_component_list" to get the component detail.'
67+
},
68+
parentId: {
69+
validator: (value: string) => {
70+
const parentNode = useCanvas().getNodeById(value)
71+
return !!parentNode
72+
},
73+
message:
74+
'Parent node not found, please check the parentId is correct. if you don\'t know the parentId, you can use the tool "get_page_schema" to get the page schema. if you want to add to page root, just don\'t provide the parentId.'
75+
},
76+
referTargetNodeId: {
77+
validator: (value: string) => {
78+
const referTargetNode = useCanvas().getNodeById(value)
79+
return !!referTargetNode
80+
},
81+
message:
82+
"Refer target node not found, please check the referTargetNodeId is correct. if you don't want to refer to any node, just don't provide the referTargetNodeId."
83+
}
84+
})
85+
86+
if (!validateResult.isValid) {
87+
return validateResult.error
88+
}
89+
90+
useCanvas().operateNode({
91+
type: 'insert',
92+
parentId: parentId!,
93+
// @ts-ignore
94+
newNodeData: {
95+
componentName,
96+
props,
97+
children
98+
},
99+
position: position!,
100+
referTargetNodeId
101+
})
102+
103+
const res = {
104+
status: 'success',
105+
message: `Node added successfully`,
106+
data: {
107+
componentName,
108+
props,
109+
children
110+
}
111+
}
112+
113+
return {
114+
content: [
115+
{
116+
type: 'text',
117+
text: JSON.stringify(res)
118+
}
119+
]
120+
}
121+
}
122+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { z } from 'zod'
2+
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
3+
4+
const inputSchema = z.object({
5+
id: z.string().describe('The id of the node to change the props of.'),
6+
props: z
7+
.object({})
8+
.describe(
9+
'The props of the component. if you don\'t know available props, you can use the "get_component_detail" tool to get component detail and available props.'
10+
),
11+
overwrite: z.boolean().optional().describe('Whether to overwrite the existing props.')
12+
})
13+
14+
export const changeNodeProps = {
15+
name: 'change_node_props',
16+
description:
17+
'Change the props of a node in the current TinyEngine low-code application. Use this when you need to change the props of a node in your application.',
18+
inputSchema: inputSchema.shape,
19+
// 添加 annotations 配置
20+
annotations: {
21+
title: '修改节点属性', // 人性化标题
22+
readOnlyHint: false, // 非只读操作,会修改节点属性
23+
destructiveHint: false, // 非破坏性操作,只是修改属性值
24+
idempotentHint: false, // 非幂等操作,不同的属性修改会产生不同效果
25+
openWorldHint: false // 不与外部世界交互,只在 TinyEngine 内部操作
26+
},
27+
callback: async (args: z.infer<typeof inputSchema>) => {
28+
const { id, overwrite = false } = args
29+
let props = args.props
30+
31+
if (!props || typeof props !== 'object') {
32+
props = {}
33+
}
34+
35+
useCanvas().operateNode({
36+
type: 'changeProps',
37+
id,
38+
value: { props },
39+
option: {
40+
overwrite
41+
}
42+
})
43+
44+
const res = {
45+
status: 'success',
46+
message: `Node props changed successfully`,
47+
data: {
48+
id,
49+
props
50+
}
51+
}
52+
53+
return {
54+
content: [
55+
{
56+
type: 'text',
57+
text: JSON.stringify(res)
58+
}
59+
]
60+
}
61+
}
62+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { z } from 'zod'
2+
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
3+
4+
const inputSchema = z.object({
5+
id: z.string().describe('The id of the node to delete.')
6+
})
7+
8+
export const delNode = {
9+
name: 'del_node',
10+
description:
11+
'Delete a node from the current TinyEngine low-code application. Use this when you need to delete a node from your application.',
12+
inputSchema: inputSchema.shape,
13+
// 添加 annotations 配置
14+
annotations: {
15+
title: '删除节点', // 人性化标题
16+
readOnlyHint: false, // 非只读操作,会删除节点
17+
destructiveHint: true, // 破坏性操作,会永久删除节点
18+
idempotentHint: true, // 幂等操作,删除同一个节点多次没有额外效果
19+
openWorldHint: false // 不与外部世界交互,只在 TinyEngine 内部操作
20+
},
21+
callback: async (args: z.infer<typeof inputSchema>) => {
22+
const { id } = args
23+
const node = useCanvas().getNodeById(id)
24+
25+
if (!node) {
26+
return {
27+
content: [
28+
{
29+
type: 'json',
30+
value: {
31+
status: 'error',
32+
message: 'Node not found, please check the id is correct.'
33+
}
34+
}
35+
]
36+
}
37+
}
38+
39+
useCanvas().operateNode({
40+
type: 'delete',
41+
id
42+
})
43+
44+
const res = {
45+
status: 'success',
46+
message: `Node deleted successfully`,
47+
data: {
48+
id
49+
}
50+
}
51+
52+
return {
53+
content: [
54+
{
55+
type: 'text',
56+
text: JSON.stringify(res)
57+
}
58+
]
59+
}
60+
}
61+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { z } from 'zod'
2+
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
3+
4+
const inputSchema = z.object({})
5+
6+
export const getCurrentSelectedNode = {
7+
name: 'get_current_selected_node',
8+
description:
9+
'Get the current selected node from the current TinyEngine low-code application. Use this when you need to get the current selected node from your application.',
10+
inputSchema: inputSchema.shape,
11+
// 添加 annotations 配置
12+
annotations: {
13+
title: '获取当前选中节点', // 人性化标题
14+
readOnlyHint: true, // 只读操作,不会修改任何状态
15+
openWorldHint: false // 不与外部世界交互,只在 TinyEngine 内部操作
16+
},
17+
callback: async (_args: z.infer<typeof inputSchema>) => {
18+
const currentSelectedNode = useCanvas().canvasApi.value?.getCurrent?.()
19+
20+
// 安全检查,确保 currentSelectedNode 存在
21+
if (!currentSelectedNode) {
22+
return {
23+
content: [
24+
{
25+
isError: true,
26+
type: 'text',
27+
text: JSON.stringify({
28+
status: 'error',
29+
message: 'No node is currently selected'
30+
})
31+
}
32+
]
33+
}
34+
}
35+
36+
const { schema, parent } = currentSelectedNode
37+
38+
const res = {
39+
status: 'success',
40+
message: `Current selected node retrieved successfully`,
41+
data: {
42+
currentSelectedNode: schema,
43+
parent
44+
}
45+
}
46+
47+
return {
48+
content: [
49+
{
50+
type: 'text',
51+
text: JSON.stringify(res)
52+
}
53+
]
54+
}
55+
}
56+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { z } from 'zod'
2+
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
3+
4+
const inputSchema = z.object({})
5+
6+
export const getPageSchema = {
7+
name: 'get_page_schema',
8+
description:
9+
'Get current editing page schema from the current TinyEngine low-code application. Use this when you need to get current editing page schema from your application.',
10+
inputSchema: inputSchema.shape,
11+
// 添加 annotations 配置
12+
annotations: {
13+
title: '获取页面结构', // 人性化标题
14+
readOnlyHint: true, // 只读操作,不会修改任何状态
15+
openWorldHint: false // 不与外部世界交互,只在 TinyEngine 内部操作
16+
},
17+
callback: async (_args: z.infer<typeof inputSchema>) => {
18+
const pageSchema = useCanvas().getSchema()
19+
20+
const res = {
21+
status: 'success',
22+
message: `Page schema retrieved successfully`,
23+
data: {
24+
pageSchema
25+
}
26+
}
27+
28+
return {
29+
content: [
30+
{
31+
type: 'text',
32+
text: JSON.stringify(res)
33+
}
34+
]
35+
}
36+
}
37+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { getCurrentSelectedNode } from './getCurrentSelectedNode'
2+
export { getPageSchema } from './getPageSchema'
3+
export { queryNodeById } from './queryNodeById'
4+
export { delNode } from './delNode'
5+
export { addNode } from './addNode'
6+
export { changeNodeProps } from './changeNodeProps'
7+
export { selectSpecificNode } from './selectSpecificNode'

0 commit comments

Comments
 (0)