Skip to content

feat: MCP tool for adding tree controls #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions docs/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ declare module 'vue' {
TinyQrCode: typeof import('@opentiny/vue-qr-code')['default']
TinyRobotChat: typeof import('./src/components/tiny-robot-chat.vue')['default']
TinyTag: typeof import('@opentiny/vue-tag')['default']
TinyTree: typeof import('@opentiny/vue-tree')['default']
}
}
5 changes: 5 additions & 0 deletions docs/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const router = createRouter({
name: 'Comprehensive',
component: () => import('../views/comprehensive/index.vue')
},
{
path: '/tree',
name: 'Tree',
component: () => import('../views/tree/tree.vue')
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
Expand Down
75 changes: 75 additions & 0 deletions docs/src/views/tree/tree.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<div class="ecs-container">
<tiny-tree
:tiny_mcp_config="{
server,
business: {
id: 'cpu-tree',
description: 'CPU规格的树'
}
}"
node-key="id"
:data="data"
show-checkbox
highlight-current
:default-expanded-keys="['1']"
:check-on-click-node="true"
:expand-on-click-node="false"
></tiny-tree>
</div>
</template>

<script setup lang="jsx">
import { ref } from 'vue'
import { useNextServer } from '@opentiny/next-vue'
const data = ref([
{
id: '1',
label: '操作系统',
children: [
{ id: '2', label: ' windows',
children: [
{ id: '3', label: ' windows 10' },
{ id: '4', label: ' windows 11' },
{ id: '5', label: ' windows 12' },
{ id: '7', label: ' windows 13' }
]
},
{ id: '8', label: 'linux',
children: [
{ id: '9', label: 'linux 10' },
{ id: '10', label: 'linux 11' },
{ id: '11', label: 'linux 12' },
{ id: '12', label: 'linux 13' }
]
},
{ id: '13', label: 'macos'},
{ id: '14', label: 'harmonyOS'},
]
},
])

const { server } = useNextServer({
serverInfo: { name: 'ecs-console', version: '1.0.0' }
})
</script>

<style scoped>
.checkbox-demo {
display: flex;
margin: 16px;
}

.checkbox-demo .tiny-tree {
flex: 1;
min-width: 300px;
}

.checkbox-demo div {
margin-bottom: 8px;
}

.tip {
font-weight: bold;
}
</style>
2 changes: 2 additions & 0 deletions packages/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Business, ComponentMcpConfig } from './src/utils/defineTool'
import zhCN from './src/lang/zh-CN'
import enUS from './src/lang/en'
import { getGridConfig } from './src/grid'
import { getTreeConfig } from './src/tree'
import { getBaseSelectConfig } from './src/base-select'
import { i18n } from './src/utils/locale'
import { getButtonConfig } from './src/button'
Expand All @@ -28,6 +29,7 @@ export const getTinyVueMcpConfig = ({ t }: { t?: ((path: string) => string) | nu
version: '0.0.1',
components: {
Grid: getGridConfig(),
Tree: getTreeConfig(),
BaseSelect: getBaseSelectConfig(),
Button: getButtonConfig()
}
Expand Down
11 changes: 10 additions & 1 deletion packages/mcp/src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ export default {
},
button: {
description: 'Button component related tool set',
triggerClick: 'Button click, click the button'
triggerClick: 'trigger button click,click self'
},
tree: {
description: 'Collection of Tools Related to Tree Control Components',
setCurrentKey: 'Please enter the ID value to set the current selected state of a node',
expandHlNode: 'Please enter the ID value to set the current deployment status of a node',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix terminology: "deployment" should be "expanded".

The term "deployment status" is incorrect in the context of tree nodes. It should refer to the expanded/collapsed state.

-      expandHlNode: 'Please enter the ID value to set the current deployment status of a node',
+      expandHlNode: 'Please enter the ID value to set the expanded state of a node',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expandHlNode: 'Please enter the ID value to set the current deployment status of a node',
expandHlNode: 'Please enter the ID value to set the expanded state of a node',
🤖 Prompt for AI Agents
In packages/mcp/src/lang/en.ts at line 36, the phrase "deployment status" is
incorrect for describing the state of a tree node. Replace "deployment status"
with "expanded state" to accurately reflect the node's expanded or collapsed
condition.

collapseHlNode: 'Please enter the ID value to set the collapsed state of a node',
removeNode: 'Please enter the ID value to delete a node',
insertBefore: 'Add a node in front of a node',
insertAfter: 'Add a node after a node',
}
}
}
9 changes: 9 additions & 0 deletions packages/mcp/src/lang/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ export default {
button: {
description: '按钮组件相关工具集合',
triggerClick: '按钮点击,点击按钮'
},
tree: {
description: '树形控件组件相关工具集合',
setCurrentKey: '请输入id值设置某个节点的当前选中状态',
expandHlNode: '请输入id值设置某个节点的展开状态',
collapseHlNode: '请输入id值设置某个节点的收起状态',
removeNode: '请输入id值删除某个节点',
insertBefore: '在一个节点的前面增加一个节点',
insertAfter: '在一个节点的后面增加一个节点',
}
}
}
70 changes: 70 additions & 0 deletions packages/mcp/src/tree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { z } from 'zod'
import { defineComponentTool } from '../utils/defineComponentTool'
import resourcesZh from './resouces.zh.md?raw'
import resourcesEn from './resouces.en.md?raw'
import { t } from '../utils/locale'

export const resources = {
zh: resourcesZh,
en: resourcesEn
}

export const getTreeConfig = () =>
defineComponentTool({
name: 'tree_component_tools',
description: t('ai.tree.description'),
tools: {
setCurrentKey: {
paramsSchema: z.string().optional().describe(t('ai.tree.setCurrentKey')),
cb: (instance, value) => {
if(instance.showCheckbox){
instance.setCheckedKeys([value])
}else{
instance.setCurrentKey(value)
}
return { type: 'text', text: 'success' }
}
},
Comment on lines +17 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for undefined values.

The setCurrentKey function doesn't handle the case where value is undefined, which is possible since the schema uses .optional().

       setCurrentKey: {
         paramsSchema:  z.string().optional().describe(t('ai.tree.setCurrentKey')),
         cb: (instance, value) => {
+          if (!value) {
+            return { type: 'text', text: 'Error: ID value is required' }
+          }
           if(instance.showCheckbox){
             instance.setCheckedKeys([value])
           }else{
             instance.setCurrentKey(value)
           }
           return { type: 'text', text: 'success' }
         }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setCurrentKey: {
paramsSchema: z.string().optional().describe(t('ai.tree.setCurrentKey')),
cb: (instance, value) => {
if(instance.showCheckbox){
instance.setCheckedKeys([value])
}else{
instance.setCurrentKey(value)
}
return { type: 'text', text: 'success' }
}
},
setCurrentKey: {
paramsSchema: z.string().optional().describe(t('ai.tree.setCurrentKey')),
cb: (instance, value) => {
if (!value) {
return { type: 'text', text: 'Error: ID value is required' }
}
if(instance.showCheckbox){
instance.setCheckedKeys([value])
}else{
instance.setCurrentKey(value)
}
return { type: 'text', text: 'success' }
}
},
🤖 Prompt for AI Agents
In packages/mcp/src/tree/index.ts between lines 17 and 27, the setCurrentKey
callback does not handle the case when the value parameter is undefined, which
can occur because the paramsSchema marks it as optional. Add a check at the
start of the callback to verify if value is undefined, and if so, return an
appropriate error response or handle it gracefully to prevent runtime errors.
This ensures the function only proceeds with valid defined values.

expandHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.expandHlNode')),
cb: (instance, value) => {
const node = instance.getNode(value)
node.expand()
return { type: 'text', text: 'success' }
}
},
collapseHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.collapseHlNode')),
cb: (instance, value) => {
const node = instance.getNode(value)
node.collapse()
return { type: 'text', text: 'success' }
}
},
Comment on lines +28 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for node operations.

The expandHlNode and collapseHlNode functions don't handle cases where getNode() returns null/undefined or when the value parameter is undefined.

       expandHlNode: {
         paramsSchema:  z.string().optional().describe(t('ai.tree.expandHlNode')),
         cb: (instance, value) => {
+          if (!value) {
+            return { type: 'text', text: 'Error: ID value is required' }
+          }
           const node = instance.getNode(value)
+          if (!node) {
+            return { type: 'text', text: `Error: Node with ID ${value} not found` }
+          }
           node.expand()
           return { type: 'text', text: 'success' }
         }
       },
       collapseHlNode: {
         paramsSchema:  z.string().optional().describe(t('ai.tree.collapseHlNode')),
         cb: (instance, value) => {
+          if (!value) {
+            return { type: 'text', text: 'Error: ID value is required' }
+          }
           const node = instance.getNode(value)
+          if (!node) {
+            return { type: 'text', text: `Error: Node with ID ${value} not found` }
+          }
           node.collapse()
           return { type: 'text', text: 'success' }
         }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expandHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.expandHlNode')),
cb: (instance, value) => {
const node = instance.getNode(value)
node.expand()
return { type: 'text', text: 'success' }
}
},
collapseHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.collapseHlNode')),
cb: (instance, value) => {
const node = instance.getNode(value)
node.collapse()
return { type: 'text', text: 'success' }
}
},
expandHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.expandHlNode')),
cb: (instance, value) => {
if (!value) {
return { type: 'text', text: 'Error: ID value is required' }
}
const node = instance.getNode(value)
if (!node) {
return { type: 'text', text: `Error: Node with ID ${value} not found` }
}
node.expand()
return { type: 'text', text: 'success' }
}
},
collapseHlNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.collapseHlNode')),
cb: (instance, value) => {
if (!value) {
return { type: 'text', text: 'Error: ID value is required' }
}
const node = instance.getNode(value)
if (!node) {
return { type: 'text', text: `Error: Node with ID ${value} not found` }
}
node.collapse()
return { type: 'text', text: 'success' }
}
},
🤖 Prompt for AI Agents
In packages/mcp/src/tree/index.ts around lines 28 to 43, the expandHlNode and
collapseHlNode callbacks do not handle cases where the value parameter is
undefined or getNode(value) returns null or undefined. Add checks to verify that
value is defined and that getNode(value) returns a valid node before calling
expand() or collapse(). If these checks fail, return an error response instead
of proceeding with the operation.

removeNode: {
paramsSchema: z.string().optional().describe(t('ai.tree.removeNode')),
cb: (instance, value) => {
instance.remove(value)
return { type: 'text', text: 'success' }
}
},
insertBefore: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertBefore')),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve validation schema for insert operations.

The current schema z.record(z.any()) is too permissive and doesn't validate the required structure for insert operations.

-        paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertBefore')),
+        paramsSchema: z.object({
+          id: z.string().describe('Target node ID'),
+          label: z.string().describe('New node label')
+        }).describe(t('ai.tree.insertBefore')),

Apply the same change to insertAfter:

-        paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertAfter')),
+        paramsSchema: z.object({
+          id: z.string().describe('Target node ID'),
+          label: z.string().describe('New node label')
+        }).describe(t('ai.tree.insertAfter')),

Also applies to: 61-61

🤖 Prompt for AI Agents
In packages/mcp/src/tree/index.ts at lines 52 and 61, the validation schema for
insert operations uses z.record(z.any()), which is too permissive and does not
enforce the required structure. Update the schema to a more specific zod schema
that accurately reflects the expected shape of the insert operation parameters,
ensuring proper validation. Apply this improved schema to both insertBefore
(line 52) and insertAfter (line 61).

cb: (instance, value) => {
let id = 1000
instance.insertBefore({ id, label: value.label }, value.id)
id++
return { type: 'text', text: 'success' }
}
},
insertAfter: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertAfter')),
cb: (instance, value) => {
let id = 1000
instance.insertAfter({ id, label: value.label }, value.id)
id++
return { type: 'text', text: 'success' }
}
},
Comment on lines +51 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix ID generation logic causing duplicate IDs.

The insertBefore and insertAfter functions have a critical bug where the id variable is redeclared each time, always resulting in ID 1000. This will cause duplicate IDs and potential conflicts.

+let nextId = 1000 // Move outside the functions

       insertBefore: {
         paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertBefore')),
         cb: (instance, value) => {
-          let id = 1000
-          instance.insertBefore({ id, label: value.label }, value.id)
-          id++
+          if (!value || !value.id || !value.label) {
+            return { type: 'text', text: 'Error: Both id and label are required' }
+          }
+          instance.insertBefore({ id: nextId++, label: value.label }, value.id)
           return { type: 'text', text: 'success' }
         }
       },
       insertAfter: {
         paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertAfter')),
         cb: (instance, value) => {
-          let id = 1000
-          instance.insertAfter({ id, label: value.label }, value.id)
-          id++
+          if (!value || !value.id || !value.label) {
+            return { type: 'text', text: 'Error: Both id and label are required' }
+          }
+          instance.insertAfter({ id: nextId++, label: value.label }, value.id)
           return { type: 'text', text: 'success' }
         }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
insertBefore: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertBefore')),
cb: (instance, value) => {
let id = 1000
instance.insertBefore({ id, label: value.label }, value.id)
id++
return { type: 'text', text: 'success' }
}
},
insertAfter: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertAfter')),
cb: (instance, value) => {
let id = 1000
instance.insertAfter({ id, label: value.label }, value.id)
id++
return { type: 'text', text: 'success' }
}
},
// Move this to the top of the file (module scope)
let nextId = 1000
insertBefore: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertBefore')),
cb: (instance, value) => {
if (!value || !value.id || !value.label) {
return { type: 'text', text: 'Error: Both id and label are required' }
}
instance.insertBefore(
{ id: nextId++, label: value.label },
value.id
)
return { type: 'text', text: 'success' }
}
},
insertAfter: {
paramsSchema: z.record(z.any()).optional().describe(t('ai.tree.insertAfter')),
cb: (instance, value) => {
if (!value || !value.id || !value.label) {
return { type: 'text', text: 'Error: Both id and label are required' }
}
instance.insertAfter(
{ id: nextId++, label: value.label },
value.id
)
return { type: 'text', text: 'success' }
}
},
🤖 Prompt for AI Agents
In packages/mcp/src/tree/index.ts around lines 51 to 68, the id variable is
redeclared and reset to 1000 inside each callback, causing duplicate IDs. To fix
this, move the id declaration outside the callback functions so it persists
across calls and increments properly, ensuring unique IDs are generated for each
insertBefore and insertAfter operation.

}
})
3 changes: 3 additions & 0 deletions packages/mcp/src/tree/resouces.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Tree Component

It can display data with parent-child hierarchy, and supports functions such as selecting, dragging, and editing nodes.
3 changes: 3 additions & 0 deletions packages/mcp/src/tree/resouces.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Tree 树形控件

可进行展示有父子层级的数据,支持选择,拖拽和编辑节点等功能。