Skip to content

fix: mcp tool call issue#1587

Merged
hexqi merged 9 commits intoopentiny:release/v2.8.xfrom
chilingling:fix/mcpToolIssue
Aug 21, 2025
Merged

fix: mcp tool call issue#1587
hexqi merged 9 commits intoopentiny:release/v2.8.xfrom
chilingling:fix/mcpToolIssue

Conversation

@chilingling
Copy link
Member

@chilingling chilingling commented Aug 20, 2025

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • System prompt for consistent assistant behavior (Chinese, tool-first); robot plugin adds options; new error-serialization helper; utility to list all pages.
  • Bug Fixes

    • Pre-checks validate nodes, pages, and plugins before actions and return guided error responses.
    • Plugin discovery/filtering corrected.
    • Sending assistant content no longer blocked by unsaved page/block state.
  • Documentation

    • Expanded guidance for ids, routes, parentId and clearer page-creation error messages.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Walkthrough

Adds runtime pre-checks and structured error responses across canvas, layout, and page tools; introduces material-driven validation for addNode; flattens page lists via new getAllPages utility; expands several input schemas (props → records, richer id/route descriptions); refines robot plugin error serialization, tool-call recording, and adds a system prompt document.

Changes

Cohort / File(s) Summary of Changes
Canvas node tools
packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts, packages/canvas/DesignCanvas/src/mcp/tools/changeNodeProps.ts, packages/canvas/DesignCanvas/src/mcp/tools/delNode.ts, packages/canvas/DesignCanvas/src/mcp/tools/queryNodeById.ts, packages/canvas/DesignCanvas/src/mcp/tools/selectSpecificNode.ts
addNode: import/useMaterial and call getMaterial(componentName); return structured error COMPONENT_NAME_REQUIRED with next_action if missing/empty; on success pass explicit insertData and return content-wrapped JSON. Schema changes: newNodeData.propsz.record(z.string(), z.any()), childrenz.array(z.record(z.string(), z.any())). changeNodeProps: propsrecord<string, any> and added getNodeById(id) pre-check returning NODE_NOT_FOUND structured error. delNode/queryNodeById/selectSpecificNode: expanded id descriptions and return structured node-not-found errors with next_action; success flows preserved.
Layout plugin
packages/layout/src/composable/useLayout.ts, packages/layout/src/mcp/tools/switchPlugin.ts
useLayout plugin filter changed to item.type === 'plugins'. switchPlugin now awaits getAllPlugins() (destructured from useLayout), looks up plugin by id, returns PLUGIN_NOT_FOUND structured error with next_action if missing; otherwise opens via activePlugin(pluginId) or closes via closePlugin(true).
Page tools & utils
packages/plugins/page/src/composable/usePage.ts, packages/plugins/page/src/mcp/tools/addPage.ts, packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts, packages/plugins/page/src/mcp/tools/delPage.ts, packages/plugins/page/src/mcp/tools/editSpecificPage.ts, packages/plugins/page/src/mcp/tools/getPageDetail.ts, packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts
New get_all_pages_utils.ts exports getAllPages() to flatten hierarchical page lists. Multiple page tools call getAllPages to pre-check existence and return PAGE_NOT_FOUND structured errors with next_action when missing. addPage: tightened route/parentId descriptions and surfaces underlying error in response. usePage catch blocks serialize errors via JSON.stringify(error instanceof Error ? error.message : error).
Robot plugin UI & tool handling
packages/plugins/robot/src/Main.vue, packages/plugins/robot/src/mcp/utils.ts, packages/plugins/robot/index.ts, packages/plugins/robot/src/system-prompt.md
Main.vue: removed saved-block pre-send guard, typed messages, adjusted prompt list and roles. utils.ts: added serializeError, track toolCallStatus/toolCallResult, convert tool results to string, update renderContent and history handling. index.ts: added options: {} to default export. Added system-prompt.md defining tool-first assistant behavior and policies.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Tool as add_node
  participant Material as useMaterial
  participant Canvas as useCanvas
  User->>Tool: call add_node(newNodeData)
  Tool->>Material: getMaterial(componentName)
  alt material missing or empty
    Tool-->>User: structured error { errorCode: COMPONENT_NAME_REQUIRED, next_action: get_component_list }
  else valid material
    Tool->>Canvas: operateNode(insertData)
    Canvas-->>Tool: result
    Tool-->>User: success { content: JSON.stringify(result) }
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant Tool as change_node_props
  participant Canvas as useCanvas
  User->>Tool: call change_node_props({ id, props })
  Tool->>Canvas: getNodeById(id)
  alt node not found
    Tool-->>User: structured error { errorCode: NODE_NOT_FOUND, next_action: [...] }
  else node found
    Tool->>Canvas: operateNode(update props)
    Canvas-->>Tool: ack
    Tool-->>User: success payload
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant Tool as switch_plugin
  participant Layout as useLayout
  User->>Tool: call switch_plugin({ pluginId, operation })
  Tool->>Layout: getAllPlugins()
  Layout-->>Tool: plugins[]
  alt plugin not found
    Tool-->>User: structured error { errorCode: PLUGIN_NOT_FOUND, next_action: get_all_plugins }
  else found
    alt operation == "open"
      Tool->>Layout: activePlugin(pluginId)
    else
      Tool->>Layout: closePlugin(true)
    end
    Layout-->>Tool: ack
    Tool-->>User: success payload
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant Tool as page_tool
  participant Utils as getAllPages
  participant Page as usePage
  User->>Tool: call page_tool({ id, ... })
  Tool->>Utils: getAllPages()
  Utils-->>Tool: pages[]
  alt id not present
    Tool-->>User: structured error { errorCode: PAGE_NOT_FOUND, next_action: get_page_list }
  else present
    Tool->>Page: perform action (fetch/create/update/delete/switch)
    Page-->>Tool: result
    Tool-->>User: success or bubbled error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

我在草丛里查名字与 id,
把页面树铺平成一行行地图。
组件若缺料,我递上下一步,
插件若走失,我轻声找回路。
小兔挥耳笑,改动稳又实。 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added bug Something isn't working release merge to release/ branch, before release period labels Aug 20, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (23)
packages/layout/src/mcp/tools/switchPlugin.ts (1)

18-19: Nit: Awaiting a synchronous function.

useLayout().getAllPlugins() currently returns a plain array. Awaiting a non-promise is harmless but unnecessary. Either remove await here or make getAllPlugins async for consistency across call sites.

packages/canvas/DesignCanvas/src/mcp/tools/delNode.ts (3)

5-9: Tighten and clarify the input description

Current copy is verbose and grammatically inconsistent. Recommend concise guidance with correct capitalization.

-    .describe(
-      'The id of the node to delete. if you don\'t know the id, you can use the tool "get_current_selected_node" to get the current selected node. or you can use the tool "get_page_schema" to get the page schema. when get the page schema, you can find the id in the "id" field.'
-    )
+    .describe(
+      'ID of the node to delete. If unknown: 1) call "get_current_selected_node" to get the current selection; or 2) call "get_page_schema" and read the "id" field from the returned schema.'
+    )

30-41: Standardize error payload format with other MCP tools

Other MCP tools (page.*) return error payloads as text-encoded JSON with isError and structured fields (errorCode, reason, userMessage, next_action). This tool returns type: 'json' with value, which is inconsistent and harder to consume uniformly.

-      return {
-        content: [
-          {
-            type: 'json',
-            value: {
-              status: 'error',
-              message: 'Node not found, please check the id is correct.'
-            }
-          }
-        ]
-      }
+      return {
+        content: [
+          {
+            isError: true,
+            type: 'text',
+            text: JSON.stringify({
+              errorCode: 'NODE_NOT_FOUND',
+              reason: `Unknown nodeId: ${id}`,
+              userMessage: 'Node not found. Verify the ID or select a node first.',
+              next_action: [
+                {
+                  type: 'tool_call',
+                  name: 'get_current_selected_node',
+                  args: {}
+                }
+              ]
+            })
+          }
+        ]
+      }

27-29: Add try/catch around deletion and reuse a single canvas instance

operateNode may throw. Also, you call useCanvas() twice; cache it once for clarity and potential performance.

-    const { id } = args
-    const node = useCanvas().getNodeById(id)
+    const { id } = args
+    const canvas = useCanvas()
+    const node = canvas.getNodeById(id)
@@
-    useCanvas().operateNode({
-      type: 'delete',
-      id
-    })
+    try {
+      canvas.operateNode({
+        type: 'delete',
+        id
+      })
+    } catch (error) {
+      return {
+        content: [
+          {
+            isError: true,
+            type: 'text',
+            text: JSON.stringify({
+              errorCode: 'NODE_DELETE_FAILED',
+              reason: error instanceof Error ? error.message : 'Unknown error',
+              userMessage: 'Failed to delete node. Please try again.',
+            })
+          }
+        ]
+      }
+    }

Also applies to: 44-65

packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts (2)

11-11: Enforce documented constraints via zod (name capitalization, route format)

You document constraints but don’t enforce them. Add minimal validation to fail fast and improve tool UX.

-  name: z.string().describe('The name of the page. The name must be unique and Capitalize the first letter.'),
+  name: z
+    .string()
+    .min(1, 'Name is required')
+    .refine((s) => /^[A-Z]/.test(s), { message: 'Name must start with an uppercase letter' })
+    .describe('The name of the page. Must be unique and start with an uppercase letter.'),
-  route: z
-    .string()
-    .describe(
-      'The route of the page. only allow contain english letter, number, underline, hyphen, slash, and start with english letter.'
-    ),
+  route: z
+    .string()
+    .regex(/^[A-Za-z][A-Za-z0-9_/-]*$/, 'Route must start with a letter and contain only letters, numbers, underscore (_), hyphen (-), or slash (/).'),

Also applies to: 12-16


78-87: Include parentId in the success payload

You accept parentId as an input (and presumably support moving pages). Return it for completeness.

       data: {
         id,
         name,
-        route,
+        route,
+        parentId,
         type: 'page'
       }
packages/plugins/page/src/mcp/tools/getPageDetail.ts (2)

6-10: Ensure non-empty id in schema

Fast-fail on empty strings to avoid unnecessary API calls.

-  id: z
-    .string()
+  id: z
+    .string()
+    .min(1, 'id is required')

24-49: Pre-validation pattern looks good; consider caching getAllPages per request

Repeated calls to getAllPages across tools cause extra network round-trips. If these tools are invoked in a sequence, consider memoizing getAllPages for the request lifecycle.

packages/canvas/DesignCanvas/src/mcp/tools/selectSpecificNode.ts (2)

5-9: Clarify input description and add basic validation (trim + non-empty).
Small polish to make the guidance concise and enforce a non-empty id.

-  id: z
-    .string()
-    .describe(
-      'The id of the node to select. if you don\'t know the id, you can use the tool "get_page_schema" to get the page schema. when get the page schema, you can find the id in the "id" field.'
-    )
+  id: z
+    .string()
+    .trim()
+    .min(1, 'id is required')
+    .describe(
+      'The node id to select. If unknown, call "get_page_schema" to retrieve the page schema, then locate the "id" field of the target node.'
+    )

30-56: Mark error payloads consistently with isError: true.
Other tools in this PR mark structured error content with isError: true; align this one for consumers.

     if (!node) {
       return {
         content: [
           {
-            type: 'text',
+            isError: true,
+            type: 'text',
             text: JSON.stringify({
               errorCode: 'NODE_NOT_FOUND',
               reason: `Node not found: ${id}`,
               userMessage: `Node not found: ${id}. Fetch the available node list.`,
               next_action: [
packages/plugins/page/src/mcp/tools/addPage.ts (3)

6-10: Enforce and document route constraints at the schema level.
You describe allowed characters but don’t enforce them. Add a regex and tighten wording.

-  route: z
-    .string()
-    .describe(
-      'The route of the page. only allow contain english letter, number, underline, hyphen, slash, and start with english letter.'
-    ),
+  route: z
+    .string()
+    .regex(/^[A-Za-z][A-Za-z0-9/_-]*$/, 'Must start with a letter; allowed: letters, numbers, underscore (_), hyphen (-), slash (/).')
+    .describe(
+      'The route of the page. Only letters, numbers, underscore (_), hyphen (-), and slash (/). Must start with a letter.'
+    ),

15-16: Polish parentId description for clarity.
Minor grammar tweaks improve readability.

-    .describe(
-      'The parent id of the page, if not provided, the page will be created at the root level. if provided, the page will be created at the specified parent id. if you don\'t know the parentId, you can use the tool "get_page_list" to get the page list.'
-    )
+    .describe(
+      'The parent id of the page. If omitted, the page is created at the root level; otherwise under the specified parent. If you don\'t know the parentId, call "get_page_list" to retrieve pages.'
+    )

30-38: Avoid double-encoded error strings in the response.
createNewPage currently returns error as a JSON-stringified string; embedding it and then JSON.stringify-ing the response produces values like ""Failed"" in data.error. Normalize before serializing.

-    const { success, data, error } = await createNewPage({ name, route, parentId })
+    const { success, data, error } = await createNewPage({ name, route, parentId })
@@
     if (!success) {
-      const res = {
+      const normalizedError = typeof error === 'string'
+        ? (() => { try { return JSON.parse(error) } catch { return error } })()
+        : error
+      const res = {
         status: 'error',
         message: 'Failed to create page',
         data: {
-          error: error || 'Failed to create page'
+          error: normalizedError || 'Failed to create page'
         }
       }

Follow-up: consider returning a plain string (not JSON.stringify-ed) from createNewPage to simplify all call sites.

packages/plugins/page/src/composable/usePage.ts (1)

604-607: Return a plain error message instead of JSON-stringifying it.
Stringifying a string introduces extraneous quotes and forces downstream normalization. Return a simple string.

-    return {
-      success: false,
-      error: JSON.stringify(error instanceof Error ? error.message : error)
-    }
+    return {
+      success: false,
+      error: error instanceof Error ? error.message : String(error)
+    }

Optional: unify deletePage/updatePageById to use the same pattern for consistency.

packages/plugins/page/src/mcp/tools/editSpecificPage.ts (3)

6-10: Tighten id description wording.
Minor grammar improvements.

-  id: z
-    .string()
-    .describe(
-      'The id of the page. if you don\'t know the id, you can use the tool "get_page_list" to get the page list.'
-    )
+  id: z
+    .string()
+    .describe(
+      'The page id. If you don\'t know it, call the "get_page_list" tool to retrieve the page list.'
+    )

33-42: Add a “when” hint to next_action for consistency with other tools.
Other tools include a when field to guide callers.

               next_action: [
                 {
                   type: 'tool_call',
                   name: 'get_page_list',
-                  args: {}
+                  args: {},
+                  when: 'you want to fetch the available page list before retrying'
                 }
               ]

50-55: Include the page id in the success data.
Helps callers correlate the action outcome.

   const res = {
     status: 'success',
     message: `Page now can be edited.`,
-    data: {}
+    data: { id }
   }
packages/canvas/DesignCanvas/src/mcp/tools/changeNodeProps.ts (2)

5-9: Clarify id guidance and enforce non-empty input.
Minor grammar cleanup plus trim/min to avoid accidental blanks.

-  id: z
-    .string()
-    .describe(
-      'The id of the node to change the props of. if you don\'t know the id, you can use the tool "get_current_selected_node" to get the current selected node. or you can use the tool "get_page_schema" to get the page schema. when get the page schema, you can find the id in the "id" field.'
-    ),
+  id: z
+    .string()
+    .trim()
+    .min(1, 'id is required')
+    .describe(
+      'The node id whose props to change. If unknown, call "get_current_selected_node" or "get_page_schema" and locate the target node\'s "id".'
+    ),

33-39: Simplify args handling; z.record already guarantees an object.
You can destructure props directly and drop the defensive fallback.

-    const { id, overwrite = false } = args
-    let props = args.props
-
-    if (!props || typeof props !== 'object') {
-      props = {}
-    }
+    const { id, overwrite = false, props } = args
packages/canvas/DesignCanvas/src/mcp/tools/queryNodeById.ts (2)

5-9: Polish the parameter description for clarity and tone

The guidance is helpful; minor grammar/punctuation tweaks will improve readability and consistency with other tools’ descriptions.

-    .describe(
-      'The id of the node to query. if you don\'t know the id, you can use the tool "get_current_selected_node" to get the current selected node. or you can use the tool "get_page_schema" to get the page schema. when get the page schema, you can find the id in the "id" field.'
-    )
+    .describe(
+      'The id of the node to query. If unknown, call "get_current_selected_node" for the current selection, or call "get_page_schema" and use the node\'s "id" field.'
+    )

36-52: Unify error response envelope across MCP tools

Currently, error-only responses vary in their shape:

• Missing status: 'error' (success responses include status: 'success')
isError is sometimes nested inside content (should always be top-level)
next_action is sometimes an object (should always be an array of actions)

Affected files:

  • packages/canvas/DesignCanvas/src/mcp/tools/queryNodeById.ts (lines 36–52)
  • packages/layout/src/mcp/tools/switchPlugin.ts (around line 30)
  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (around line 98)

Please refactor all error returns to:

• Return at the top level:
status: 'error'
isError: true
• Keep the structured payload inside a single content: [ { type: 'text', text: JSON.stringify({...}) } ]
• Inside that JSON:
– Omit any nested isError
– Include status: 'error'
– Use next_action: [ { … }, … ] (always an array)
• Update userMessage to reference the suggested follow-up tools

Example diff for queryNodeById.ts:

 return {
-  content: [
+  status: 'error',
+  isError: true,
+  content: [
     {
       type: 'text',
       text: JSON.stringify({
+        status: 'error',
         errorCode: 'NODE_NOT_FOUND',
         reason: `Node not found: ${id}`,
-        userMessage: `Node not found: ${id}. Fetch the available node list.`,
+        userMessage: `Node not found: ${id}. Use "get_current_selected_node" for the current selection or "get_page_schema" to browse valid ids.`,
-        next_action: [
+        next_action: [
           {
             type: 'tool_call',
             name: 'get_current_selected_node',
             args: {},
             when: 'you want to query the current selected node'
           },
           {
             type: 'tool_call',
             name: 'get_page_schema',
             args: {},
             when: 'you want to query the node with the specified id'
           }
         ]
       })
     }
   ]
 }

Apply the same pattern to switchPlugin.ts and addNode.ts, converting their next_action: { … } to an array and promoting isError/status: 'error' to the top level.

packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (2)

18-20: Schema loosening: confirm intent and potential validation gap

Changing props to z.record(z.string(), z.any()) and children to z.array(nodeArraySchema) broadens accepted shapes. This likely matches the dynamic nature of component props, but it also eliminates structural validation and allows any value types (including functions) via z.any.

  • If you only need “unknown JSON-like” values, consider z.unknown() instead of z.any() to avoid implicit trust in any executable/function values.
  • If certain props must be constrained (e.g., primitives, arrays, objects), consider z.union([...]) to balance flexibility and safety.

99-104: Standardize next_action to an array of actions

Other tools in this PR use next_action as an array. Aligning here avoids downstream parsing conditionals.

-              next_action: {
-                type: 'tool_call',
-                name: 'get_component_list',
-                args: {}
-              }
+              next_action: [
+                {
+                  type: 'tool_call',
+                  name: 'get_component_list',
+                  args: {}
+                }
+              ]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 12b6384 and 418ef0e.

📒 Files selected for processing (14)
  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (3 hunks)
  • packages/canvas/DesignCanvas/src/mcp/tools/changeNodeProps.ts (2 hunks)
  • packages/canvas/DesignCanvas/src/mcp/tools/delNode.ts (1 hunks)
  • packages/canvas/DesignCanvas/src/mcp/tools/queryNodeById.ts (2 hunks)
  • packages/canvas/DesignCanvas/src/mcp/tools/selectSpecificNode.ts (2 hunks)
  • packages/layout/src/composable/useLayout.ts (1 hunks)
  • packages/layout/src/mcp/tools/switchPlugin.ts (1 hunks)
  • packages/plugins/page/src/composable/usePage.ts (1 hunks)
  • packages/plugins/page/src/mcp/tools/addPage.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/delPage.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/editSpecificPage.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/getPageDetail.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-28T03:58:31.212Z
Learnt from: chilingling
PR: opentiny/tiny-engine#1440
File: packages/plugins/materials/src/composable/useResource.ts:82-84
Timestamp: 2025-05-28T03:58:31.212Z
Learning: In the TinyEngine codebase, there are two different data structures for page information:
1. App schema components tree (appSchemaState.pageTree) uses nested meta structure with page.meta?.id
2. API responses from pagePluginApi.getPageById() return flattened structure with pageInfo.id and pageInfo.occupier directly
The code should use page.meta?.id when working with pageTree data and pageInfo.id when working with API response data.

Applied to files:

  • packages/plugins/page/src/mcp/tools/editSpecificPage.ts
  • packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts
🧬 Code Graph Analysis (6)
packages/plugins/page/src/mcp/tools/delPage.ts (1)
packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)
  • getAllPages (3-25)
packages/plugins/page/src/mcp/tools/editSpecificPage.ts (1)
packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)
  • getAllPages (3-25)
packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts (1)
packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)
  • getAllPages (3-25)
packages/layout/src/mcp/tools/switchPlugin.ts (3)
packages/register/src/hooks.ts (1)
  • useLayout (76-76)
packages/layout/src/mcp/tools/getAllPlugins.ts (1)
  • getAllPlugins (6-30)
packages/layout/src/composable/useLayout.ts (1)
  • plugin (247-557)
packages/plugins/page/src/mcp/tools/getPageDetail.ts (1)
packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)
  • getAllPages (3-25)
packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (1)
scripts/buildMaterials.mjs (1)
  • material (107-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (13)
packages/layout/src/composable/useLayout.ts (1)

504-514: No singular “plugin” entries in runtime—filtering on “plugins” is safe

A search across all meta definitions shows that every production meta file uses type: 'plugins'. The only occurrences of type: 'plugin' are in isolated test fixtures under packages/register/test. Therefore, the existing filter remains correct and no backward‐compatibility change is required.

packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)

3-25: Harden getAllPages and introduce type safety

Right now, destructuring await getPageList() and spreading .data directly will throw if either group or its .data is ever undefined, and returning any[] loses all downstream type guarantees. Consider:

  • Verifying exactly how getPageList() is typed (tuple vs. array, guaranteed data property) before changing.
  • Defaulting to [] for missing groups or .data (e.g. const left = groups?.[0]?.data ?? []).
  • Defining proper TS types for your page nodes and summary objects instead of any[].
  • Refactoring the recursive walker to use those types and guard against missing children.

Please confirm the precise return shape of getPageList() (and whether .data is always present) before you apply a full refactor.

packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts (2)

35-59: LGTM on pre-validation and structured errors

The PAGE_NOT_FOUND error structure, isError flag, and next_action to get_page_list align with the new pattern across page tools.


34-37: ✅ IDs aligned: getAllPages() and updatePageById share the same API page IDs

getAllPages() flattens the HTTP API response (pageInfo.id → PageData.id), and updatePageById/deletePage both consume that same identifier. No further changes are needed.

packages/plugins/page/src/mcp/tools/delPage.ts (2)

23-47: LGTM on existence pre-check and actionable error

The PAGE_NOT_FOUND payload with next_action to get_page_list is consistent and helpful.


49-69: Propagate detailed backend errors and align payload

Before applying this optional refactor, please verify the signature of deletePage:

  • Does it return { success: boolean; error?: string }, throw on failure, or use a different shape?
  • Based on that, wrap the call in try/catch and extract the error message accordingly.

File: packages/plugins/page/src/mcp/tools/delPage.ts (lines 49–69)

Proposed change:

--- a/packages/plugins/page/src/mcp/tools/delPage.ts
+++ b/packages/plugins/page/src/mcp/tools/delPage.ts
@@ -47,22 +47,35 @@ export async function delPage(id: string) {
-  const { success } = await deletePage(id)
+  let success = false
+  let errorMsg: string | undefined
+  try {
+    const result: { success: boolean; error?: string } = await deletePage(id)
+    success = result.success
+    errorMsg = result.error
+  } catch (e) {
+    errorMsg = e instanceof Error ? e.message : String(e)
+  }
@@ -52,20 +65,10 @@ export async function delPage(id: string) {
-  if (!success) {
-    const res = {
-      status: 'error',
-      message: 'Failed to delete page',
-      data: { error: 'Failed to delete page' },
-    }
-
-    return {
-      content: [
-        { isError: true, type: 'text', text: JSON.stringify(res) }
-      ]
-    }
-  }
+  if (!success) {
+    return {
+      content: [
+        {
+          isError: true,
+          type: 'text',
+          text: JSON.stringify({
+            errorCode: 'PAGE_DELETE_FAILED',
+            reason: errorMsg ?? 'Failed to delete page',
+            userMessage: 'Failed to delete page. Please try again.',
+          }),
+        },
+      ],
+    }
+  }
packages/plugins/page/src/mcp/tools/getPageDetail.ts (2)

51-66: LGTM on success and error handling

  • Success payload structure is consistent.
  • Error handling includes isError and preserves error messages.

Also applies to: 67-84


3-3: No additional raw fetch calls found in page MCP tools
Both getPageDetail.ts and get_all_pages_utils.ts use the intended abstractions (fetchPageDetail and usePage().getPageList, respectively) and there are no leftover direct fetch usages.

packages/canvas/DesignCanvas/src/mcp/tools/selectSpecificNode.ts (1)

59-76: Selection flow LGTM.
Early return on not-found, then select via canvas API and return a structured success payload. Matches the pattern used across node tools.

packages/plugins/page/src/mcp/tools/addPage.ts (1)

52-61: Success payload structure LGTM.
Includes id/name/route/type and a clear message.

packages/plugins/page/src/mcp/tools/editSpecificPage.ts (1)

21-21: Pre-validation approach LGTM.
Fetching and validating with getAllPages before switching prevents confusing canvas state.

packages/canvas/DesignCanvas/src/mcp/tools/changeNodeProps.ts (1)

40-69: Structured not-found response LGTM.
The existence check and guided next_action align with adjacent node tools.

packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (1)

2-2: Import of useMaterial is appropriate

Importing useMaterial to validate component availability aligns with the new pre-validation pattern in this PR.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/plugins/robot/src/mcp/utils.ts (1)

7-7: Global requestOptions causes cross-request bleed; pass options explicitly (race condition).

Module-scoped mutable state will interleave across concurrent conversations, causing wrong URL/headers on follow-up tool calls. Remove the global and thread options through.

Apply these diffs:

  1. Remove the global:
-let requestOptions: RequestOptions = {}
  1. Don’t default fetchLLM to a shared variable:
-const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = requestOptions) => {
+const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => {
  1. Thread options through handleToolCall:
-const handleToolCall = async (
-  res: LLMResponse,
-  tools: RequestTool[],
-  messages: RobotMessage[],
-  contextMessages?: RobotMessage[]
-) => {
+const handleToolCall = async (
+  res: LLMResponse,
+  tools: RequestTool[],
+  messages: RobotMessage[],
+  contextMessages?: RobotMessage[],
+  options: RequestOptions = {}
+) => {
  1. Use options for the follow-up LLM call:
-    const newResp = await fetchLLM(toolMessages, tools)
+    const newResp = await fetchLLM(toolMessages, tools, options)
  1. Stop mutating the removed global:
-  requestOptions = options
  1. Pass options into handleToolCall from sendMcpRequest:
-    await handleToolCall(res, tools, messages)
+    await handleToolCall(res, tools, messages, undefined, options)

Also applies to: 17-17, 42-47, 102-102, 122-122, 128-128

🧹 Nitpick comments (1)
packages/plugins/robot/src/mcp/utils.ts (1)

34-40: Optional: normalize args to an object for tools.

If a tool expects an object, passing a raw string can be brittle. Consider always returning an object, e.g., { __raw: args } on parse failure, or validate per-tool schema before invocation.

Example:

const parseArgs = (args: string) => {
  try {
    return JSON.parse(args)
  } catch {
    return { __raw: args }
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 418ef0e and e580d97.

📒 Files selected for processing (2)
  • packages/plugins/robot/src/Main.vue (1 hunks)
  • packages/plugins/robot/src/mcp/utils.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/plugins/robot/src/Main.vue
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/plugins/robot/src/mcp/utils.ts (1)
packages/plugins/robot/src/mcp/useMcp.ts (1)
  • useMcpServer (135-147)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (3)
packages/plugins/robot/src/mcp/utils.ts (3)

89-89: LGTM: forward tool output as raw string in the 'tool' role message.

This aligns with OpenAI-style tool messages that expect a text content payload.


94-94: LGTM: status propagation to UI is correct.

Updating the last renderContent entry to reflect success/failed gives clear execution feedback.


97-97: LGTM: preserve full tool result string in renderContent.result.

This keeps structured error payloads intact for downstream handling and display.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
packages/layout/src/mcp/tools/switchPlugin.ts (1)

17-39: Gate plugin lookup behind operation === 'open' and include status: 'error' in error payload.

As previously noted, the early not-found check runs for both 'open' and 'close', causing a valid 'close' to fail when pluginId is unknown. Also, error responses should include status: 'error' and align next_action with other tools.

Apply this diff:

-    const { activePlugin, closePlugin, getAllPlugins } = useLayout()
-    const plugins = await getAllPlugins()
-    const plugin = plugins.find((item) => item.id === pluginId)
-
-    if (!plugin) {
-      return {
-        content: [
-          {
-            type: 'text',
-            text: JSON.stringify({
-              errorCode: 'PLUGIN_NOT_FOUND',
-              reason: `Unknown pluginId: ${pluginId}`,
-              userMessage: 'Plugin not found. Fetch the available plugin list.',
-              next_action: {
-                type: 'tool_call',
-                name: 'get_all_plugins',
-                args: {}
-              }
-            })
-          }
-        ]
-      }
-    }
+    const { activePlugin, closePlugin, getAllPlugins } = useLayout()
+    if (operation === 'open') {
+      const plugins = await getAllPlugins()
+      const plugin = plugins.find((item) => item.id === pluginId)
+      if (!plugin) {
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify({
+                status: 'error',
+                errorCode: 'PLUGIN_NOT_FOUND',
+                reason: `Unknown pluginId: ${pluginId}`,
+                userMessage: 'Plugin not found. Fetch the available plugin list.',
+                next_action: [
+                  {
+                    type: 'tool_call',
+                    name: 'get_all_plugins',
+                    args: {}
+                  }
+                ]
+              })
+            }
+          ]
+        }
+      }
+    }
packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (1)

76-99: Guard getMaterial for nullish returns and standardize the error payload.

Object.keys(material) will throw if getMaterial returns null/undefined. Also, the error path conflates “missing name” and “unknown component,” and next_action should align with other tools (array). This mirrors prior feedback.

Apply this diff:

-    const { getMaterial } = useMaterial()
-    const material = getMaterial(componentName)
-
-    if (!newNodeData.componentName || !Object.keys(material).length) {
-      return {
-        isError: true,
-        content: [
-          {
-            type: 'text',
-            text: JSON.stringify({
-              status: 'error',
-              errorCode: 'COMPONENT_NAME_REQUIRED',
-              reason: 'Component name is required',
-              userMessage: 'Component name is required. Fetch the available component list.',
-              next_action: {
-                type: 'tool_call',
-                name: 'get_component_list',
-                args: {}
-              }
-            })
-          }
-        ]
-      }
-    }
+    const { getMaterial } = useMaterial()
+    const hasComponentName = !!newNodeData.componentName && newNodeData.componentName.trim().length > 0
+    const material = hasComponentName ? getMaterial(componentName) : undefined
+    const materialIsEmptyPlainObject =
+      !!material && typeof material === 'object' && !Array.isArray(material) && Object.keys(material as Record<string, unknown>).length === 0
+    const materialMissing = !material || materialIsEmptyPlainObject
+
+    if (!hasComponentName || materialMissing) {
+      const missingName = !hasComponentName
+      const payload = {
+        status: 'error',
+        errorCode: missingName ? 'COMPONENT_NAME_REQUIRED' : 'COMPONENT_NOT_FOUND',
+        reason: missingName ? 'Component name is required' : `Component "${componentName}" is not available`,
+        userMessage: missingName
+          ? 'Component name is required. Fetch the available component list.'
+          : `Component "${componentName}" is not available. Fetch the available component list.`,
+        next_action: [
+          {
+            type: 'tool_call',
+            name: 'get_component_list',
+            args: {}
+          }
+        ]
+      }
+      return {
+        isError: true,
+        content: [
+          {
+            type: 'text',
+            text: JSON.stringify(payload)
+          }
+        ]
+      }
+    }
packages/plugins/robot/src/mcp/utils.ts (1)

122-130: Don’t pre-mark success; serialize thrown Error objects safely.

toolCallStatus is set to 'success' before the await resolves, and JSON.stringify(error) collapses Error to {}. Serialize safely and set status after success. This matches prior feedback.

Apply this diff:

-      let toolCallResult
-      let toolCallStatus
-      try {
-        toolCallStatus = 'success'
-        toolCallResult = (await useMcpServer().callTool(name, parsedArgs)).content
-      } catch (error) {
-        toolCallStatus = 'failed'
-        toolCallResult = JSON.stringify(error)
-      }
+      let toolCallResult: string
+      let toolCallStatus: 'success' | 'failed'
+      try {
+        const resp = await useMcpServer().callTool(name, parsedArgs)
+        toolCallResult = resp.content
+        toolCallStatus = 'success'
+      } catch (error) {
+        toolCallStatus = 'failed'
+        toolCallResult = serializeError(error)
+      }

Add once in this module (additional snippet):

function serializeError(err: unknown): string {
  if (err instanceof Error) return JSON.stringify({ name: err.name, message: err.message })
  if (typeof err === 'string') return err
  try {
    return JSON.stringify(err)
  } catch {
    return String(err)
  }
}
🧹 Nitpick comments (10)
packages/plugins/robot/index.ts (1)

20-21: Add a typed, documented options slot to avoid shape drift and aid discoverability.

The empty options: {} placeholder is fine, but without a declared shape it’s easy for downstream code to rely on ad-hoc keys. Consider exporting a minimal type and documenting supported flags (e.g., disableSystemPrompt, customSystemPrompt) to align with utils.ts.

Example (additional snippet; no in-place diff required here):

// packages/plugins/robot/src/types.ts
export interface RobotPluginOptions {
  disableSystemPrompt?: boolean
  customSystemPrompt?: string
}

// packages/plugins/robot/index.ts
import type { RobotPluginOptions } from './src/types'
// ...
export default {
  ...metaData,
  options: {} as RobotPluginOptions,
  icon: RobotIcon
}
packages/layout/src/mcp/tools/switchPlugin.ts (2)

4-7: Make pluginId optional and enforce “required when open” at the schema level.

Force-supplying a meaningless pluginId for 'close' needlessly complicates callers. Keep inputSchema.shape compatibility by using superRefine.

-const inputSchema = z.object({
-  pluginId: z.string().describe('The id of the plugin to operate.'),
-  operation: z.enum(['open', 'close']).describe('The operation to perform on the plugin.')
-})
+const inputSchema = z
+  .object({
+    pluginId: z.string().optional().describe('The id of the plugin to operate (required when operation is "open").'),
+    operation: z.enum(['open', 'close']).describe('The operation to perform on the plugin.')
+  })
+  .superRefine((val, ctx) => {
+    if (val.operation === 'open' && !val.pluginId) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        path: ['pluginId'],
+        message: 'pluginId is required when operation is "open".'
+      })
+    }
+  })

41-45: Use operation to branch and remove redundant pluginId check; preserve forceClose argument

We confirmed that closePlugin is defined as const closePlugin = (forceClose?: boolean) => { … } in packages/layout/src/composable/useLayout.ts (line 238), so calling closePlugin(true) is valid and intentionally forces the panel to close even if it’s pinned. Since pluginId is already guaranteed (via schema validation and existence check), you can simplify the branch to depend only on operation.

Please apply this diff in packages/layout/src/mcp/tools/switchPlugin.ts around lines 41–45:

-    if (operation === 'open' && pluginId) {
-      await activePlugin(pluginId)
-    } else {
-      await closePlugin(true)
-    }
+    if (operation === 'open') {
+      await activePlugin(pluginId)
+    } else {
+      await closePlugin(true)
+    }
packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (3)

14-20: Reinstate a recursive node schema to validate children structurally (not plain records).

children: z.array(z.record(...)) loses structure and allows arbitrary shapes. Prefer a recursive schema to catch malformed inputs early.

Additional snippet (define once above inputSchema):

const NodeSchema: z.ZodType<{
  componentName: string
  props?: Record<string, any>
  children?: any[]
}> = z.lazy(() =>
  z.object({
    componentName: z.string().describe('The name of the component.'),
    props: z.record(z.string(), z.any()).default({}).describe('The props of the component.'),
    children: z.array(NodeSchema).default([]).describe('Children nodes of the same shape.')
  })
)

Then adjust the schema via diff:

-  newNodeData: z.object({
-    componentName: z.string().describe('The name of the component.'),
-    props: z.record(z.string(), z.any()).describe('The props of the component.'),
-    children: z
-      .array(z.record(z.string(), z.any()))
-      .describe('Array of child nodes; each child has the same shape as newNodeData (recursive tree).')
-  }),
+  newNodeData: NodeSchema,

101-112: Avoid non-null assertions and remove // @ts-ignore by aligning types in the insert op.

Passing parentId! and position! forwards undefined values while suppressing type checking. Build the op object conditionally and ensure newNodeData matches the expected type to drop the ts-ignore.

-    const insertData = {
+    const insertData = {
       componentName,
       props,
       children
     }
-
-    useCanvas().operateNode({
-      type: 'insert',
-      parentId: parentId!,
-      // @ts-ignore
-      newNodeData: insertData,
-      position: position!,
-      referTargetNodeId
-    })
+    const op: any = {
+      type: 'insert',
+      newNodeData: insertData
+    }
+    if (parentId) op.parentId = parentId
+    if (position) op.position = position
+    if (referTargetNodeId) op.referTargetNodeId = referTargetNodeId
+    useCanvas().operateNode(op)

116-120: Success payload is good; consider returning the inserted node id if available.

If operateNode returns the created node id, include it to help follow-up operations reference the node precisely.

packages/plugins/robot/src/system-prompt.md (2)

33-46: Codify the standard error payload shape to reduce ambiguity across tools.

Recommend explicitly documenting and standardizing { status: 'error', errorCode, reason, userMessage, next_action: [] } to match recent tool changes and ease client parsing.


1-139: Minor editorial nits (non-user-facing doc).

The document reads clearly; language tool hints are mostly false positives. No blocking issues.

packages/plugins/robot/src/mcp/utils.ts (2)

145-151: Enforce single-tool-per-reply at runtime to match the system prompt.

The recursive handling will keep executing additional tool calls in the same round. Process only the first tool call and end the round; surface follow-up guidance via minimal text if needed.

Proposed change (illustrative):

-    for (const tool of tool_calls) {
+    // Enforce single-tool-per-reply: only process the first tool call
+    const [tool] = tool_calls

19-34: Consider removing the module-global requestOptions to avoid cross-request bleed.

A shared mutable default can leak headers/model across concurrent requests. Prefer per-call options wiring.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e580d97 and cdf7262.

📒 Files selected for processing (6)
  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (2 hunks)
  • packages/layout/src/mcp/tools/switchPlugin.ts (1 hunks)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/mcp/utils.ts (4 hunks)
  • packages/plugins/robot/src/system-prompt.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/src/Main.vue
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-14T04:25:08.323Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/material-function/material-getter.ts:55-88
Timestamp: 2025-01-14T04:25:08.323Z
Learning: The BlockLoadError component in packages/canvas/render/src/material-function/material-getter.ts requires a `name` prop to display which block failed to load.

Applied to files:

  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts
🧬 Code Graph Analysis (2)
packages/plugins/robot/src/mcp/utils.ts (3)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/mcp/types.ts (1)
  • LLMMessage (30-34)
packages/plugins/robot/src/mcp/useMcp.ts (1)
  • useMcpServer (135-147)
packages/layout/src/mcp/tools/switchPlugin.ts (3)
packages/register/src/hooks.ts (1)
  • useLayout (76-76)
packages/layout/src/mcp/tools/getAllPlugins.ts (1)
  • getAllPlugins (6-30)
packages/layout/src/composable/useLayout.ts (1)
  • plugin (247-557)
🪛 LanguageTool
packages/plugins/robot/src/system-prompt.md

[grammar] ~15-~15: There might be a mistake here.
Context: ...main in Chinese. ## Thinking Principles - Systems thinking: reason about the relat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...s, materials, and plugins before acting. - Read → Validate → Write: always perform ...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...ls to identify targets before mutations. - Safety-first: apply risk classification,...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...cks, and recovery guidance for failures. - Determinism and minimality: only do what...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...ultiple rounds, one tool call per round. - Minimal text: keep user-visible text to ...

(QB_NEW_EN)


[grammar] ~26-~26: There might be a mistake here.
Context: ...: keep user-visible text to the minimum. - Success path: a one-line result summary ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ies, functions, classes, and tool names. - Code/JSON blocks: only when essential fo...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...e non-idempotency. - Pre-check doctrine: - Page: resolve target via get_page_list...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...nfo, edit_page_in_canvas, del_page. - Node: resolve via get_current_selected_...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...ps, del_node, select_specific_node. - Component/Material: validate via get_co...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...changes constrained by component schema. - Plugin panel: resolve plugin via `get_al...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...witch_plugin_panel. - Failure recovery: - If tool returns errorCode/isError` wi...

(QB_NEW_EN)


[grammar] ~56-~56: There might be a mistake here.
Context: ...anations. ## Tool Invocation Guidelines - Parameters: supply only required and min...

(QB_NEW_EN)


[grammar] ~57-~57: There might be a mistake here.
Context: ... each tool’s schema; avoid extra fields. - Ordering: follow read → validate → (if n...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...sts. - Template (do not over-apologize): - “由于合规与安全原因,当前请求无法协助完成。你可以考虑:1) 调整目标与范围;2...

(QB_NEW_EN)


[grammar] ~96-~96: Ensure spelling is correct
Context: ...议。 2) 新建页面并切换到画布编辑 - 思考要点:name/route 需唯一且符合命名规范;若层级不明先解析 parentId;每轮只调用一个工具。 - 回合式(单轮单工具): -...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~96-~96: There might be a mistake here.
Context: ...唯一且符合命名规范;若层级不明先解析 parentId;每轮只调用一个工具。 - 回合式(单轮单工具): - 第1轮(如需):调用 `get_page_lis...

(QB_NEW_EN)


[grammar] ~97-~97: There might be a mistake here.
Context: ...明先解析 parentId;每轮只调用一个工具。 - 回合式(单轮单工具): - 第1轮(如需):调用 get_page_list,解析可用层级以确定 `pa...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ...page_list,解析可用层级以确定 parentId(若用户未提供)。 - 成功最小回传:可用层级数量与目标 parentId` 概要。 - 第2轮:...

(QB_NEW_EN)


[grammar] ~99-~99: There might be a mistake here.
Context: ...)。 - 成功最小回传:可用层级数量与目标 parentId 概要。 - 第2轮:调用 add_page,参数 `{ name, route, par...

(QB_NEW_EN)


[grammar] ~100-~100: There might be a mistake here.
Context: ... route, parentId? };仅记录返回的 id供下一轮使用。 - 成功最小回传:新页面id概要。 - 第3轮:调用edit_pag...

(QB_NEW_EN)


[grammar] ~101-~101: There might be a mistake here.
Context: ...的 id 供下一轮使用。 - 成功最小回传:新页面 id 概要。 - 第3轮:调用 edit_page_in_canvas,参数 { id }...

(QB_NEW_EN)


[grammar] ~102-~102: There might be a mistake here.
Context: ...edit_page_in_canvas,参数 { id }`(来自上一轮)。 - 成功最小回传:已切换到画布编辑。 3) 修改 Text 组件的文本或 Tiny...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...3) 修改 Text 组件的文本或 TinyButton 的文字(选中节点场景) - 思考要点:确保已有选中节点并获取 id 与组件名;必要时通过 `get_co...

(QB_NEW_EN)


[grammar] ~106-~106: There might be a mistake here.
Context: ...get_component_detail核对文本属性键;每轮只调用一个工具。 - 回合式(单轮单工具): - 第1轮:调用get_current_sele...

(QB_NEW_EN)


[grammar] ~107-~107: There might be a mistake here.
Context: ...detail核对文本属性键;每轮只调用一个工具。 - 回合式(单轮单工具): - 第1轮:调用get_current_selected_node,获取 s...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...schema.id 与可能的 schema.componentName。 - 成功最小回传:选中节点 id/componentName 概要。 - 第...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ... - 成功最小回传:选中节点 id/componentName 概要。 - 第2轮(必要时):调用 get_component_detail,参数 `{...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...ntName },识别文本属性键(常见为 textlabel)。 - 成功最小回传:可用文本属性键概要。 - 第3轮:调用 change_nod...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...extlabel)。 - 成功最小回传:可用文本属性键概要。 - 第3轮:调用 change_node_props,仅变更文本相关属性,ov...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...node_props,仅变更文本相关属性,overwrite=false`。 - 成功最小回传:目标属性与新值概要。 4) 新增节点、删除节点 - 思考要点:新...

(QB_NEW_EN)


[grammar] ~120-~120: There might be a mistake here.
Context: ...ren }, position?, referTargetNodeId? }。 - 缺省行为:若未提供 position/referTargetNodeId`,则...

(QB_NEW_EN)


[grammar] ~122-~122: There might be a mistake here.
Context: ...供 parentId,追加到页面根(文档流)末尾。 - 删除节点(回合式): - 第1轮:调用 query_node_by_id 或 `get_current...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ...或 get_current_selected_node,确认目标 id。 - 第2轮:调用 del_node,参数 { id }。 ## Examp...

(QB_NEW_EN)


[grammar] ~126-~126: There might be a mistake here.
Context: ...Answer Structure (Per-round, tool-first) - 本轮工具:仅列出将要调用的工具名与关键参数来源(必要时附最小 JSON)。 - ...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ... - 本轮工具:仅列出将要调用的工具名与关键参数来源(必要时附最小 JSON)。 - 参数来源:来自上一轮工具结果或明确的用户输入。 - 成功最小回传:一行结果摘要(...

(QB_NEW_EN)


[grammar] ~128-~128: There might be a mistake here.
Context: ...(必要时附最小 JSON)。 - 参数来源:来自上一轮工具结果或明确的用户输入。 - 成功最小回传:一行结果摘要(例如“已获取到 N 条记录 / 已切换到画布编辑”)...

(QB_NEW_EN)


[grammar] ~129-~129: There might be a mistake here.
Context: ...功最小回传:一行结果摘要(例如“已获取到 N 条记录 / 已切换到画布编辑”)。 - 失败最小回传:错误码 + 最小可行动的下一步工具名(优先使用返回的 `next_...

(QB_NEW_EN)


[grammar] ~130-~130: There might be a mistake here.
Context: ...码 + 最小可行动的下一步工具名(优先使用返回的 next_action)。 - 下一轮指引(如需):仅指出下一轮将调用的工具名,不在本轮继续调用。 - 禁止:在...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ...n`)。 - 下一轮指引(如需):仅指出下一轮将调用的工具名,不在本轮继续调用。 - 禁止:在同一轮中串行调用多个工具,或以话术替代应调用的工具。 ## Non-g...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ...xternal network or non-registered tools. - Keep outputs concise, structured, and pr...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/plugins/page/src/mcp/tools/editSpecificPage.ts (4)

3-3: Import of getAllPages is appropriate; consider tightening types.

LGTM functionally. To avoid leaking any from get_all_pages_utils, consider introducing a local type alias from the return type:

type PageRec = Awaited<ReturnType<typeof getAllPages>>[number]

and annotate usages accordingly to keep downstream strictly typed.


6-10: Harden the schema: accept numeric IDs, coerce to string, enforce non-empty, and polish copy.

Avoids tool-call friction when upstream passes a numeric id and guarantees non-empty IDs.

-  id: z
-    .string()
-    .describe(
-      'The id of the page. if you don\'t know the id, you can use the tool "get_page_list" to get the page list.'
-    )
+  id: z
+    .union([z.string(), z.number()])
+    .transform((v) => String(v))
+    .pipe(z.string().min(1))
+    .describe(
+      'The page ID. If you do not know it, call the "get_page_list" tool to list available pages first.'
+    )

22-23: Pre-check is good; prefer switching with the canonical page.id.

You normalize for lookup (nice), but you later call switchPage with the raw input id. Passing the found page.id avoids type drift and ensures you use the system’s canonical identifier.

For clarity (no diff since Line 48 is outside this hunk), consider:

// after the not-found guard
await switchPage(page.id as any /* keep native type from usePage() */)

Would you confirm switchPage’s parameter type? If it expects a number in some engines, using page.id prevents accidental mismatches from string input.


22-49: Add a try/catch and emit a consistent INTERNAL_ERROR on unexpected failures.

Uncaught failures from getAllPages() or switchPage() will surface as opaque errors to the caller. Wrap the sequence and return a structured error payload.

-    const allPages = await getAllPages()
-    const page = allPages.find((page) => String(page.id) === String(id))
-
-    if (!page) {
-      return {
-        content: [
-          {
-            isError: true,
-            type: 'text',
-            text: JSON.stringify({
-              errorCode: 'PAGE_NOT_FOUND',
-              reason: `Unknown pageId: ${id}`,
-              userMessage: `Page not found. Fetch the available page list.`,
-              next_action: [
-                {
-                  type: 'tool_call',
-                  name: 'get_page_list',
-                  args: {}
-                }
-              ]
-            })
-          }
-        ]
-      }
-    }
-
-    await switchPage(id)
+    try {
+      const allPages = await getAllPages()
+      const page = allPages.find((p) => String(p.id) === String(id))
+
+      if (!page) {
+        return {
+          content: [
+            {
+              isError: true,
+              type: 'text',
+              text: JSON.stringify({
+                errorCode: 'PAGE_NOT_FOUND',
+                reason: `Unknown pageId: ${id}`,
+                userMessage: `Page not found. Use the "get_page_list" tool to list available pages and choose a valid ID.`,
+                next_action: [
+                  {
+                    type: 'tool_call',
+                    name: 'get_page_list',
+                    args: {}
+                  }
+                ]
+              })
+            }
+          ]
+        }
+      }
+
+      await switchPage(page.id as any)
+    } catch (err) {
+      return {
+        content: [
+          {
+            isError: true,
+            type: 'text',
+            text: JSON.stringify({
+              errorCode: 'INTERNAL_ERROR',
+              reason: err instanceof Error ? err.message : String(err),
+              userMessage: 'Failed to open the page. Please try again or list pages to re-select.'
+            })
+          }
+        ]
+      }
+    }

This keeps the not-found UX while preventing raw exceptions from bubbling to the agent layer.

If you want, I can add a quick unit/integration test that covers:

  • valid id -> success
  • unknown id -> PAGE_NOT_FOUND with next_action
  • getAllPages throw -> INTERNAL_ERROR
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cdf7262 and d7228f7.

📒 Files selected for processing (6)
  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts (2 hunks)
  • packages/layout/src/mcp/tools/switchPlugin.ts (1 hunks)
  • packages/plugins/page/src/composable/usePage.ts (3 hunks)
  • packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts (2 hunks)
  • packages/plugins/page/src/mcp/tools/editSpecificPage.ts (2 hunks)
  • packages/plugins/robot/src/mcp/utils.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/plugins/page/src/mcp/tools/changePageBasicInfo.ts
  • packages/plugins/page/src/composable/usePage.ts
  • packages/layout/src/mcp/tools/switchPlugin.ts
  • packages/plugins/robot/src/mcp/utils.ts
  • packages/canvas/DesignCanvas/src/mcp/tools/addNode.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-28T03:58:31.212Z
Learnt from: chilingling
PR: opentiny/tiny-engine#1440
File: packages/plugins/materials/src/composable/useResource.ts:82-84
Timestamp: 2025-05-28T03:58:31.212Z
Learning: In the TinyEngine codebase, there are two different data structures for page information:
1. App schema components tree (appSchemaState.pageTree) uses nested meta structure with page.meta?.id
2. API responses from pagePluginApi.getPageById() return flattened structure with pageInfo.id and pageInfo.occupier directly
The code should use page.meta?.id when working with pageTree data and pageInfo.id when working with API response data.

Applied to files:

  • packages/plugins/page/src/mcp/tools/editSpecificPage.ts
🧬 Code Graph Analysis (1)
packages/plugins/page/src/mcp/tools/editSpecificPage.ts (1)
packages/plugins/page/src/mcp/tools/get_all_pages_utils.ts (1)
  • getAllPages (3-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (1)
packages/plugins/page/src/mcp/tools/editSpecificPage.ts (1)

25-46: Refine error payload message and confirm JSON payload support

  • Update the userMessage copy in editSpecificPage.ts (and similarly in other tools) as follows:

    -              userMessage: `Page not found. Fetch the available page list.`,
    +              userMessage: `Page not found. Use the "get_page_list" tool to list available pages and choose a valid ID.`,
  • Content‐type contract check: all existing MCP tools under packages/plugins/**/src/mcp/tools emit errors with

      type: 'text',
      text: JSON.stringify({})

    (e.g. in changePageBasicInfo.ts, getPageDetail.ts, addPage.ts, delPage.ts, editSpecificPage.ts) [see search results]. No type: 'json' usage was found.

    Please verify whether the host runtime supports structured payloads in the form:

      {
        isError: true,
        type: 'json',
        json: { /* errorCode, reason, userMessage, next_action… */ }
      }

    If supported, switching to type: 'json' with a native json field will simplify downstream parsing; otherwise continue stringifying into type: 'text'.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/plugins/robot/src/mcp/utils.ts (1)

137-139: Critical: last user message is omitted from the LLM request.

Using messages.slice(0, -1) drops the active message, so the model answers without the latest user input.

Apply this diff:

-  const historyRaw = toRaw(messages.slice(0, -1)) as LLMMessage[]
-  const res = await fetchLLM(formatMessages(historyRaw), tools, options)
+  const historyRaw = toRaw(messages) as LLMMessage[]
+  const res = await fetchLLM(formatMessages(historyRaw), tools, options)

Note: if you have a system-prompt injector upstream, run it on the full array before formatting.

🧹 Nitpick comments (5)
packages/plugins/robot/src/mcp/utils.ts (5)

90-99: Set status after await — good; also coerce tool result to a string.

Minor hardening: ensure the tool result is always a string before passing to the LLM.

Apply this diff:

-        const resp = await useMcpServer().callTool(name, parsedArgs)
-        toolCallStatus = 'success'
-        toolCallResult = resp.content
+        const resp = await useMcpServer().callTool(name, parsedArgs)
+        toolCallStatus = 'success'
+        toolCallResult =
+          typeof (resp as any)?.content === 'string'
+            ? (resp as any).content
+            : JSON.stringify((resp as any)?.content ?? '')

100-105: Don't send UI-only "type" in LLM messages; ensure tool message content is a string.

The API message for role "tool" should be minimal: { role, content, tool_call_id }. Extra UI fields (type: 'text') can confuse servers.

Apply this diff:

-      toolMessages.push({
-        type: 'text',
-        content: toolCallResult,
-        role: 'tool',
-        tool_call_id: tool.id
-      })
+      const toolMessageContent =
+        typeof toolCallResult === 'string' ? toolCallResult : JSON.stringify(toolCallResult ?? '')
+      toolMessages.push({
+        role: 'tool',
+        content: toolMessageContent,
+        tool_call_id: tool.id
+      })

143-150: Avoid quoting empty strings and reduce any-casting when finalizing lastMsg.content.

JSON.stringify(renderedContent ?? '') yields '""' when undefined; also you can avoid any here.

Apply this diff:

-    const lastMsg: any = messages.at(-1) as any
-    const renderList: any[] | undefined = Array.isArray(lastMsg.renderContent)
-      ? (lastMsg.renderContent as any[])
-      : undefined
+    const lastMsg = messages.at(-1) as RobotMessage
+    const renderList = Array.isArray(lastMsg.renderContent) ? lastMsg.renderContent : undefined
     const lastRendered: any = renderList && renderList.length > 0 ? renderList[renderList.length - 1] : undefined
     const renderedContent: unknown = lastRendered?.content
-    lastMsg.content = typeof renderedContent === 'string' ? renderedContent : JSON.stringify(renderedContent ?? '')
+    lastMsg.content =
+      typeof renderedContent === 'string'
+        ? renderedContent
+        : renderedContent != null
+          ? JSON.stringify(renderedContent)
+          : ''

75-77: Sanitize history before follow-up tool calls to avoid leaking UI fields.

In handleToolCall, historyMessages may contain UI-only props (renderContent, formatPretty, etc.). You're sending toolMessages directly to fetchLLM without formatting, which risks API rejection on strict servers.

Consider introducing an API-focused formatter that preserves tool metadata but strips UI fields:

// new helper (outside the function)
function formatMessagesForApi(msgs: any[]) {
  return msgs.map((m) => {
    if (m?.role === 'tool') {
      return { role: 'tool', content: String(m.content ?? ''), tool_call_id: m.tool_call_id }
    }
    if (m?.role === 'assistant' && m.tool_calls) {
      return { role: 'assistant', content: m.content ?? '', tool_calls: m.tool_calls }
    }
    return { role: m.role, content: m.content ?? '' }
  })
}

Then call:

-    const newResp = await fetchLLM(toolMessages, tools)
+    const newResp = await fetchLLM(formatMessagesForApi(toolMessages), tools)

Also applies to: 114-114


54-59: Avoid global mutable requestOptions; pass options through to nested tool-call requests.

Relying on a module-scoped requestOptions can bleed headers/URL between concurrent chats.

Apply this diff:

-const handleToolCall = async (
+const handleToolCall = async (
   res: LLMResponse,
   tools: RequestTool[],
   messages: RobotMessage[],
-  contextMessages?: RobotMessage[]
+  contextMessages?: RobotMessage[],
+  options: RequestOptions = requestOptions
 ) => {
@@
-    const newResp = await fetchLLM(toolMessages, tools)
+    const newResp = await fetchLLM(toolMessages, tools, options)
@@
-      await handleToolCall(newResp, tools, messages, toolMessages)
+      await handleToolCall(newResp, tools, messages, toolMessages, options)

And when first invoking:

-    await handleToolCall(res, tools, messages)
+    await handleToolCall(res, tools, messages, undefined, options)

Also applies to: 114-114, 118-118, 142-142

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d7228f7 and bf478f2.

📒 Files selected for processing (1)
  • packages/plugins/robot/src/mcp/utils.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/plugins/robot/src/mcp/utils.ts (2)
packages/plugins/robot/src/mcp/useMcp.ts (1)
  • useMcpServer (135-147)
packages/plugins/robot/src/mcp/types.ts (1)
  • LLMMessage (30-34)
🔇 Additional comments (2)
packages/plugins/robot/src/mcp/utils.ts (2)

42-52: Safe error serialization added — good improvement.

Capturing only name/message avoids stack leakage and fixes JSON.stringify(Error) => "{}". Nice.


107-111: Render status/result updates — looks good.

Status is flipped from 'running' to 'success'/'failed' and result is captured; UI can now reflect tool lifecycle clearly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
packages/plugins/robot/src/mcp/utils.ts (4)

42-52: Good: safe error serialization added (prevents leaking stacks and preserves message).

The helper covers Error, string, and generic objects with sensible fallbacks. Consider optionally enriching known HTTP/fetch/axios-like errors (status/code/response message) without PII if that’s useful to users.

Example extension (outside this hunk):

// augment: pick a few non-PII fields if present
if (err && typeof err === 'object' && 'response' in err) {
  const r = (err as any).response
  const payload = { name: (err as any).name, message: (err as any).message, status: r?.status, statusText: r?.statusText }
  return JSON.stringify(payload)
}

102-105: Schema check: tool message content is string (good), but consider sanitizing message objects sent to the LLM.

Downstream providers often reject unknown fields. When you later call fetchLLM with toolMessages, they may include UI-only keys from history. Sanitize to allowed keys (role, content, name, tool_call_id, tool_calls) before sending.

You can pass a sanitized copy here and in the next fetch:

-  const newResp = await fetchLLM(toolMessages, tools)
+  const newResp = await fetchLLM(sanitizeForLLM(toolMessages), tools)

Add once in this module:

function sanitizeForLLM(msgs: any[]): LLMMessage[] {
  return msgs.map((m) => {
    const out: any = { role: m.role, content: m.content ?? '' }
    if (m.name) out.name = m.name
    if (m.tool_call_id) out.tool_call_id = m.tool_call_id
    if (m.tool_calls) out.tool_calls = m.tool_calls
    return out
  })
}

143-150: Final content sync: safe but may render JSON blobs when last rendered item isn’t markdown.

If the last rendered item is a tool result object, users will see JSON. Prefer the latest assistant markdown when available.

Example tweak (outside this hunk): search backward for the last render item with type === 'markdown' and fall back to JSON only if none is found.


137-139: Verified message ordering; optional guard suggested

I’ve confirmed that in packages/plugins/robot/src/Main.vue the user message is pushed, followed by the assistant-placeholder, immediately before sendRequest():

  • Line 315: messages.value.push(message)
  • Line 323: messages.value.push({ role: 'assistant', content: '好的,正在执行相关操作,请稍等片刻...', name: 'AI' })
  • Next: await scrollContent() then sendRequest()

The ordering is correct, so the slice in sendMcpRequest is intentional. To prevent future regressions if someone changes this flow, I still recommend adding a lightweight guard/assert around that slice:

--- a/packages/plugins/robot/src/mcp/utils.ts
+++ b/packages/plugins/robot/src/mcp/utils.ts
@@ -136,7 +136,13 @@ export async function sendMcpRequest(
   const last = messages.at(-1)
   if (last?.role !== 'assistant') {
     console.warn(
-      '[sendMcpRequest] Expected last message to be placeholder assistant; got', last?.role
+      '[sendMcpRequest] Expected last message to be placeholder assistant; got',
+      last?.role,
+      '—sending full history to LLM may be unintended'
     )
   }
   const historyRaw = toRaw(messages.slice(0, -1)) as LLMMessage[]

This adds minimal overhead and will surface any unexpected ordering changes at runtime.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bf478f2 and 21d5015.

📒 Files selected for processing (2)
  • packages/plugins/robot/src/Main.vue (5 hunks)
  • packages/plugins/robot/src/mcp/utils.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/plugins/robot/src/mcp/utils.ts (2)
packages/plugins/robot/src/mcp/useMcp.ts (1)
  • useMcpServer (135-147)
packages/plugins/robot/src/mcp/types.ts (1)
  • LLMMessage (30-34)
🔇 Additional comments (7)
packages/plugins/robot/src/mcp/utils.ts (1)

107-111: UI status propagation looks right.

Setting the last render item’s status and embedding both params and result aligns the transcript with the tool lifecycle.

packages/plugins/robot/src/Main.vue (6)

107-109: Good: imports align with new MCP flow; type-only import avoids runtime cost.

Bringing in serializeError centralizes error formatting; RobotMessage typing improves safety in the chat state.


125-125: Minor: narrowed useCanvas destructure (initData, clearCurrentState).

Assuming the removed fields were unused, this reduces coupling. No action needed.


131-132: Typed messages as RobotMessage[] — good for downstream utils compatibility.

This matches utils’ expectations and prevents accidental shape drift.


414-415: Copy tweak LGTM.

Prompt description change is clear and action-oriented for users.


446-448: Roles config improvement LGTM (user renderer + hidden system).

Hiding system keeps UI clean; MarkdownRenderer for user bubbles keeps parity with assistant.


226-237: Ignore the function‐call suggestion for isToolsEnabled

isToolsEnabled is defined as a Vue computed ref (i.e. a ComputedRef<boolean?>), not a method. You should read its value via .value, not invoke it as a function. Please update the check accordingly:

• In packages/plugins/robot/src/Main.vue (around line 226):

-    if (useMcpServer().isToolsEnabled) {
+    if (useMcpServer().isToolsEnabled.value) {

With this change, the conditional will correctly reflect whether any tools are enabled.

Likely an incorrect or invalid review comment.

@hexqi hexqi merged commit fce0406 into opentiny:release/v2.8.x Aug 21, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 3, 2025
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working release merge to release/ branch, before release period

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants