Skip to content

feat: add next-sdk web skills#195

Merged
kagol merged 2 commits intodevfrom
kagol/add-next-sdk-skills
Mar 10, 2026
Merged

feat: add next-sdk web skills#195
kagol merged 2 commits intodevfrom
kagol/add-next-sdk-skills

Conversation

@kagol
Copy link
Member

@kagol kagol commented Mar 9, 2026

PR

  • 实现在任意页面调用 Web 应用中的任意 MCP 工具完成页面操作,不需要用户先切换到对应的页面才能让 AI 操作
  • 支持 Web Skills,AI 会遇到不会的问题,会先查询 SKILL.md 的技能指导书,再执行对应的操作,从来实现AI自动跳转到多个页面,完成多步骤的复杂任务

演示动画:

AI操作页面

演示动画的提示词:

帮我添加中文词条:订单管理

帮我创建菜单:订单管理
父菜单: 看板
图标: bag
组件: board/order/index
路径: order
国际化: order.management

帮我给角色 admin 绑定菜单 订单管理

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)

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:

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

    • In-app management tools added (locale, menu, permission, role, user) via local MCP/page-tool integration and centralized skill loading; SDK now holds a router navigator for improved routing.
  • Documentation

    • New operator and front-end guides for TinyPro workflows and tooling.
  • Chores

    • Updated SDK dependencies to fixed 0.2.7 versions.

@github-actions github-actions bot added the enhancement New feature or request label Mar 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Walkthrough

Replaces remote agent MCP wiring with a local page-aware MCP server and page-tool registration; pins two SDK/remoter deps to 0.2.7; adds MCP server modules, skill discovery and docs; updates Vue components to register/deregister page tools and sets SDK navigator.

Changes

Cohort / File(s) Summary
Dependency updates
template/tinyvue/package.json
Pinned @opentiny/next-remoter and @opentiny/next-sdk to exact 0.2.7 (removed caret ranges).
App & bootstrap
template/tinyvue/src/App.vue, template/tinyvue/src/main.ts
App.vue now supplies local mcpServers and skills to TinyRemoter; main.ts calls setNavigator(route => router.push(route)). Remote WebMcpClient/WebMcpServer usages removed.
MCP server core
template/tinyvue/src/mcp-servers/index.ts
New bootstrap creating a WebMcpServer with a MessageChannel transport pair; registers management tools, exports server, clientTransport, and createMcpServer().
Tool modules
template/tinyvue/src/mcp-servers/.../tools.ts
template/tinyvue/src/mcp-servers/locale/tools.ts, .../menu/tools.ts, .../permission/tools.ts, .../role/tools.ts, .../user/tools.ts
Added five domain tool modules registering page-aware tools (add-i18n-entry, add-menu, add-permission, add-role & bind-menu-for-role, add-user) with input schemas and route metadata; default exports provided.
Page-tool refactor (views)
template/tinyvue/src/views/locale/.../add-locale.vue, template/tinyvue/src/views/menu/info/.../info-tab.vue, template/tinyvue/src/views/permission/info/.../info-tab.vue, template/tinyvue/src/views/role/info/.../info-tab.vue, template/tinyvue/src/views/userManager/info/.../info-tab.vue
Replaced per-component WebMcpServer wiring with registerPageTool on mount and stored cleanup functions invoked on unmount; removed explicit serverTransport/server.connect logic.
Skills & docs
template/tinyvue/src/skills/index.ts, template/tinyvue/src/skills/tiny-pro-operator/SKILL.md, template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md
Added skills export via import.meta.glob and new skill documentation and front-end guide files.

Sequence Diagram

sequenceDiagram
    participant App as App.vue (Bootstrap)
    participant MCP as MCP Server (mcp-servers)
    participant Transport as MessageChannel Transport
    participant TinyRemoter as TinyRemoter (Component)
    participant Page as Page Component (locale/menu/role/user/permission)

    App->>MCP: call createMcpServer()
    MCP->>MCP: create WebMcpServer + withPageTools
    MCP->>Transport: create serverTransport <-> clientTransport
    MCP->>MCP: register locale, menu, permission, role, user tools
    MCP->>Transport: rawServer.connect(serverTransport)
    MCP-->>App: export server & clientTransport

    App->>TinyRemoter: provide :mcp-servers, :skills, clientTransport
    Page->>TinyRemoter: user triggers tool action
    TinyRemoter->>Page: deliver tool input -> Page handler
    Page-->>TinyRemoter: return confirmation/result
    Page->>Page: onUnmounted -> invoke cleanupPageTool()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code at break of day,

MCP roots tucked in the clay,
Page-tools planted, skills in rows,
Navigator set — the pathway grows,
TinyPro blooms in bright array.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add next-sdk web skills' directly describes the main changes: adding Web Skills functionality for the next-sdk, which enables AI to invoke MCP tools across pages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kagol/add-next-sdk-skills

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 7

🧹 Nitpick comments (10)
template/tinyvue/src/views/locale/components/add-locale.vue (1)

3-3: Remove unused imports WebMcpServer and z.

After migrating to the registerPageTool pattern, WebMcpServer and z are no longer used in this component. These should be removed to keep the imports clean.

Proposed fix
-import { registerPageTool, WebMcpServer, z } from '@opentiny/next-sdk'
+import { registerPageTool } from '@opentiny/next-sdk'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/locale/components/add-locale.vue` at line 3, The
import line includes unused symbols WebMcpServer and z; update the import that
currently reads the named imports (registerPageTool, WebMcpServer, z) to only
import registerPageTool so remove WebMcpServer and z from the import list (keep
registerPageTool) to eliminate unused imports in the add-locale component.
template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md (2)

13-19: Add language identifier to fenced code block.

The code block showing the directory structure should have a language identifier for proper rendering. Since it's a directory tree, use text or plaintext.

Proposed fix
-```
+```text
 web
   src
     views
       test-page
         index.vue
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md
around lines 13 - 19, The fenced code block in front-end-guide.md showing the
directory tree currently lacks a language identifier; update the triple-backtick
fence for the directory tree (the block containing "web / src / views /
test-page / index.vue") to include a language identifier such as text or
plaintext (e.g., change totext) so the directory structure renders
correctly.


</details>

---

`32-181`: **Add alt text to images for accessibility.**

Multiple images throughout this document are missing alternate text. While this doesn't break functionality, adding descriptive alt text improves accessibility and helps when images fail to load.

Example fix for line 32:
```markdown
![添加词条对话框](./images/点击添加词条.png)
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md`
around lines 32 - 181, The markdown images in front-end-guide.md are missing alt
text which hurts accessibility; update each image markdown (e.g., the
occurrences of ./images/点击添加词条.png, ./images/选择语言.png, ./images/添加菜单.png,
./images/绑定菜单.png, ./images/左侧测试页面%20-%20中文.png, ./images/最终页面效果.png and all
other ./images/* entries) to include concise, descriptive alt text (for example
"添加词条对话框", "选择语言", "添加菜单", "绑定菜单成功", "左侧测试页面(中文)", "最终页面效果", etc.) so every
![](...) becomes ![descriptive alt text](...); ensure descriptions are brief and
context-appropriate for each image in the document.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/skills/index.ts (1)</summary><blockquote>

`1-6`: **Remove debug `console.log` and consider excluding self from glob.**

1. The `console.log` on line 6 should be removed before merging to avoid polluting the console in production.
2. The glob pattern `./**/*` will include this `index.ts` file itself in the output. Consider excluding it with a negative pattern if that's unintended.


<details>
<summary>Proposed fix</summary>

```diff
 export const skills = import.meta.glob('./**/*', {
   query: '?raw',      // 以原始文本形式导入,不经过模块解析
   import: 'default',  // 取模块的 default 导出(即文件内容字符串)
   eager: true         // 同步加载,避免异步等待
 }) as Record<string, string>
-console.log('skills:', skills);
```

If you need to exclude `index.ts`:
```typescript
export const skills = import.meta.glob(['./**/*', '!./index.ts'], {
  // ...
}) as Record<string, string>
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/skills/index.ts` around lines 1 - 6, Remove the debug
console.log by deleting the line that logs the skills constant and update the
import.meta.glob call that builds the skills record to exclude this file itself
(so index.ts is not imported into skills); specifically modify the
import.meta.glob invocation that creates the exported skills variable to use an
exclusion pattern for './index.ts' (or similar negative glob) while retaining
query:'?raw', import:'default', eager:true.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/views/role/info/components/info-tab.vue (1)</summary><blockquote>

`2-2`: **Remove unused imports.**

`WebMcpServer` and `z` are imported but never used in this file.


<details>
<summary>♻️ Proposed fix</summary>

```diff
-import { registerPageTool, WebMcpServer, z } from '@opentiny/next-sdk'
+import { registerPageTool } from '@opentiny/next-sdk'
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/role/info/components/info-tab.vue` at line 2, The
import line currently brings in unused symbols WebMcpServer and z; remove
WebMcpServer and z from the import so only used symbols (e.g., registerPageTool)
remain to avoid unused-import lint errors—locate the import statement that
includes registerPageTool, WebMcpServer, z and delete the WebMcpServer and z
identifiers from that import.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/views/userManager/info/components/info-tab.vue (1)</summary><blockquote>

`3-3`: **Remove unused imports.**

`WebMcpServer` and `z` are imported but never used in this file. Similarly, `inject` on line 21 is imported but not used.


<details>
<summary>♻️ Proposed fix</summary>

```diff
-import { registerPageTool, WebMcpServer, z } from '@opentiny/next-sdk'
+import { registerPageTool } from '@opentiny/next-sdk'
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/userManager/info/components/info-tab.vue` at line
3, The import statement includes unused symbols: remove WebMcpServer and z from
the import line "import { registerPageTool, WebMcpServer, z } from
'@opentiny/next-sdk'" and also remove the unused "inject" import (the import
that includes inject on line 21) so only actually used imports remain (e.g.,
keep registerPageTool if used); update the import declarations accordingly to
eliminate unused imports and avoid linter errors.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/App.vue (1)</summary><blockquote>

`3-3`: **Remove unused imports.**

`createMessageChannelPairTransport`, `WebMcpClient`, `WebMcpServer`, and `z` are imported but the code only uses them in ways that are either redundant or can be cleaned up (given the orphaned transport issue above).

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/App.vue` at line 3, The import line unnecessarily
includes createMessageChannelPairTransport, WebMcpClient, WebMcpServer, and z
which are not used; update the import statement to remove these unused symbols
so only the actually used exports from '@opentiny/next-sdk' remain (leave any
required exports intact and remove createMessageChannelPairTransport,
WebMcpClient, WebMcpServer, and z).
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/views/permission/info/components/info-tab.vue (1)</summary><blockquote>

`4-4`: **Remove unused imports.**

`WebMcpServer` and `z` are imported but never used in this file. Similarly, `inject` on line 20 is imported but not used.


<details>
<summary>♻️ Proposed fix</summary>

```diff
-import { registerPageTool, WebMcpServer, z } from '@opentiny/next-sdk'
+import { registerPageTool } from '@opentiny/next-sdk'
```

```diff
-import { computed, reactive, ref, onMounted, onUnmounted, inject } from 'vue'
+import { computed, reactive, ref, onMounted, onUnmounted } from 'vue'
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/permission/info/components/info-tab.vue` at line
4, Remove the unused imports: delete WebMcpServer and z from the import
statement that currently reads "import { registerPageTool, WebMcpServer, z }
from '@opentiny/next-sdk'" and remove the unused "inject" import (wherever
imported on line ~20), leaving only the actually used symbol(s) such as
registerPageTool; ensure no other references to WebMcpServer, z, or inject
remain in the file after removal.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/views/menu/info/components/info-tab.vue (1)</summary><blockquote>

`7-7`: **Remove unused imports.**

`WebMcpServer` and `z` are imported but never used in this file.


<details>
<summary>♻️ Proposed fix</summary>

```diff
-import { registerPageTool, WebMcpServer, z } from '@opentiny/next-sdk'
+import { registerPageTool } from '@opentiny/next-sdk'
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/menu/info/components/info-tab.vue` at line 7, The
import statement pulls in WebMcpServer and z which are unused; modify the import
from '@opentiny/next-sdk' to only import the used symbol registerPageTool (leave
registerPageTool intact and remove WebMcpServer and z) so the file no longer
contains unused imports.
```

</details>

</blockquote></details>
<details>
<summary>template/tinyvue/src/mcp-servers/role/tools.ts (1)</summary><blockquote>

`12-12`: **TODO comment noted - permission name resolution.**

The comment raises a valid UX concern about users providing human-readable permission names vs. IDs. Consider tracking this as a follow-up task.


Would you like me to open an issue to track the permission name-to-ID conversion enhancement?

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/role/tools.ts` at line 12, Implement
permission-name resolution in template/tinyvue/src/mcp-servers/role/tools.ts by
adding a resolver (e.g., resolvePermissionIdentifiers) that accepts mixed
permission inputs (human-readable names like "添加用户"/"删除用户" or numerical IDs) and
returns a canonical array of numeric permission IDs; use an authoritative lookup
(static map or PermissionService.getAll / PermissionRepository) to map names to
IDs, validate unknown names and throw or return clear errors, and update callers
such as assignRolePermissions / removeRolePermissions to call
resolvePermissionIdentifiers before persisting changes (cache the mapping for
performance).
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @template/tinyvue/src/App.vue:

  • Around line 31-32: The local transport created by
    createMessageChannelPairTransport() (the serverTransport variable) is orphaned
    and never connected to the MCP server; remove the
    createMessageChannelPairTransport() call and the provide('serverTransport',
    serverTransport) line, and instead provide the functional transport exported by
    your MCP server module (use the server-side transport exported from
    ./mcp-servers or export one there if missing) so that components receive the
    same transport used in the mcpServers config along with the existing
    clientTransport.

In @template/tinyvue/src/mcp-servers/index.ts:

  • Line 2: The bootstrap only registers registerLocaleManagementTools(server) so
    the other MCP skill modules (menu, permission, role, user) never get wired;
    import their registration functions and call them in the same place you call
    registerLocaleManagementTools(server) (e.g., registerMenu...,
    registerPermission..., registerRole..., registerUser... with the server
    argument) so each module is registered at runtime; locate calls to
    registerLocaleManagementTools in index.ts and add the analogous
    registerXxxManagementTools(server) calls for the menu, permission, role and user
    modules.
  • Around line 13-17: createMcpServer currently re-registers tools and calls
    rawServer.connect(serverTransport) on every invocation; make it idempotent by
    adding an initialization guard and a safe connect check: introduce a
    module-level boolean (e.g., mcpServerInitialized) and return early if true, call
    registerLocaleManagementTools only when first initializing, and before calling
    rawServer.connect(serverTransport) check whether the WebMcpServer is already
    connected (or wrap connect call in try/catch and set the initialized flag only
    after a successful connection); update createMcpServer to set the flag after
    successful registration/connection so repeated calls are no-ops.

In @template/tinyvue/src/mcp-servers/menu/tools.ts:

  • Around line 4-24: The function is misnamed: rename
    registerLocaleManagementTools to registerMenuManagementTools and update the
    default export to export the same name to avoid collision with the locale tools;
    specifically change the function declaration identifier
    (registerLocaleManagementTools) and the export default to
    registerMenuManagementTools, and search for any local references to the old name
    in this file (e.g., the registerTool call remains unchanged) to update them
    accordingly so imports elsewhere use registerMenuManagementTools.

In @template/tinyvue/src/mcp-servers/user/tools.ts:

  • Around line 18-21: The date fields in the schema (probationDate,
    protocolStart, protocolEnd) are using z.date(), which rejects ISO date strings;
    update those validators to z.coerce.date() (and for the array use
    z.array(z.coerce.date())) so incoming ISO string dates are coerced to Date
    objects at validation time; keep probationDuration as string. Locate the symbols
    probationDate, probationDuration, protocolStart, protocolEnd and replace
    z.date() with z.coerce.date() accordingly.

In @template/tinyvue/src/views/permission/info/components/info-tab.vue:

  • Around line 205-212: The handler in info-tab.vue is registered under the wrong
    tool id ('add-menu') so it will never be invoked by the permission server (which
    uses 'add-permission') and also conflicts with the menu tool; change the
    registered id to 'add-permission' and keep the existing logic that calls
    handleAddPermission(), assigns state.permissionAddData.name/desc, and then calls
    handlePermissionAddSubmit() so the permission tool can trigger this flow; ensure
    you update the identifier in the handler registration (replace 'add-menu' with
    'add-permission') and remove/avoid duplicate registrations for 'add-menu' to
    prevent conflicts with menu/tools.ts.

In @template/tinyvue/src/views/userManager/info/components/info-tab.vue:

  • Around line 360-373: The handler registered via registerPageTool in
    cleanupPageTool is using the wrong tool ID ('add-role'), so replace that ID with
    the correct user tool name ('add-user') to match the mcp-server registration;
    leave the handler body (calls to handleAddUser,
    addUserFormRef.value.setUserInfo, and addUserFormRef.value.handleSubmit) intact
    so the user flow triggers properly when the 'add-user' tool is invoked.

Nitpick comments:
In @template/tinyvue/src/App.vue:

  • Line 3: The import line unnecessarily includes
    createMessageChannelPairTransport, WebMcpClient, WebMcpServer, and z which are
    not used; update the import statement to remove these unused symbols so only the
    actually used exports from '@opentiny/next-sdk' remain (leave any required
    exports intact and remove createMessageChannelPairTransport, WebMcpClient,
    WebMcpServer, and z).

In @template/tinyvue/src/mcp-servers/role/tools.ts:

  • Line 12: Implement permission-name resolution in
    template/tinyvue/src/mcp-servers/role/tools.ts by adding a resolver (e.g.,
    resolvePermissionIdentifiers) that accepts mixed permission inputs
    (human-readable names like "添加用户"/"删除用户" or numerical IDs) and returns a
    canonical array of numeric permission IDs; use an authoritative lookup (static
    map or PermissionService.getAll / PermissionRepository) to map names to IDs,
    validate unknown names and throw or return clear errors, and update callers such
    as assignRolePermissions / removeRolePermissions to call
    resolvePermissionIdentifiers before persisting changes (cache the mapping for
    performance).

In @template/tinyvue/src/skills/index.ts:

  • Around line 1-6: Remove the debug console.log by deleting the line that logs
    the skills constant and update the import.meta.glob call that builds the skills
    record to exclude this file itself (so index.ts is not imported into skills);
    specifically modify the import.meta.glob invocation that creates the exported
    skills variable to use an exclusion pattern for './index.ts' (or similar
    negative glob) while retaining query:'?raw', import:'default', eager:true.

In @template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md:

  • Around line 13-19: The fenced code block in front-end-guide.md showing the
    directory tree currently lacks a language identifier; update the triple-backtick
    fence for the directory tree (the block containing "web / src / views /
    test-page / index.vue") to include a language identifier such as text or
    plaintext (e.g., change totext) so the directory structure renders
    correctly.
  • Around line 32-181: The markdown images in front-end-guide.md are missing alt
    text which hurts accessibility; update each image markdown (e.g., the
    occurrences of ./images/点击添加词条.png, ./images/选择语言.png, ./images/添加菜单.png,
    ./images/绑定菜单.png, ./images/左侧测试页面%20-%20中文.png, ./images/最终页面效果.png and all
    other ./images/* entries) to include concise, descriptive alt text (for example
    "添加词条对话框", "选择语言", "添加菜单", "绑定菜单成功", "左侧测试页面(中文)", "最终页面效果", etc.) so every
    becomes descriptive alt text; ensure descriptions are brief and
    context-appropriate for each image in the document.

In @template/tinyvue/src/views/locale/components/add-locale.vue:

  • Line 3: The import line includes unused symbols WebMcpServer and z; update the
    import that currently reads the named imports (registerPageTool, WebMcpServer,
    z) to only import registerPageTool so remove WebMcpServer and z from the import
    list (keep registerPageTool) to eliminate unused imports in the add-locale
    component.

In @template/tinyvue/src/views/menu/info/components/info-tab.vue:

  • Line 7: The import statement pulls in WebMcpServer and z which are unused;
    modify the import from '@opentiny/next-sdk' to only import the used symbol
    registerPageTool (leave registerPageTool intact and remove WebMcpServer and z)
    so the file no longer contains unused imports.

In @template/tinyvue/src/views/permission/info/components/info-tab.vue:

  • Line 4: Remove the unused imports: delete WebMcpServer and z from the import
    statement that currently reads "import { registerPageTool, WebMcpServer, z }
    from '@opentiny/next-sdk'" and remove the unused "inject" import (wherever
    imported on line ~20), leaving only the actually used symbol(s) such as
    registerPageTool; ensure no other references to WebMcpServer, z, or inject
    remain in the file after removal.

In @template/tinyvue/src/views/role/info/components/info-tab.vue:

  • Line 2: The import line currently brings in unused symbols WebMcpServer and z;
    remove WebMcpServer and z from the import so only used symbols (e.g.,
    registerPageTool) remain to avoid unused-import lint errors—locate the import
    statement that includes registerPageTool, WebMcpServer, z and delete the
    WebMcpServer and z identifiers from that import.

In @template/tinyvue/src/views/userManager/info/components/info-tab.vue:

  • Line 3: The import statement includes unused symbols: remove WebMcpServer and
    z from the import line "import { registerPageTool, WebMcpServer, z } from
    '@opentiny/next-sdk'" and also remove the unused "inject" import (the import
    that includes inject on line 21) so only actually used imports remain (e.g.,
    keep registerPageTool if used); update the import declarations accordingly to
    eliminate unused imports and avoid linter errors.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `63bb48e5-010f-4096-a535-f37bf2d8d36d`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0cf5a244dbd1386805347f7a6b4b9f5b0a2b0ef1 and 551357eb2d85fe6b72bbfd9e6ce2317579d2a3f5.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (17)</summary>

* `template/tinyvue/package.json`
* `template/tinyvue/src/App.vue`
* `template/tinyvue/src/main.ts`
* `template/tinyvue/src/mcp-servers/index.ts`
* `template/tinyvue/src/mcp-servers/locale/tools.ts`
* `template/tinyvue/src/mcp-servers/menu/tools.ts`
* `template/tinyvue/src/mcp-servers/permission/tools.ts`
* `template/tinyvue/src/mcp-servers/role/tools.ts`
* `template/tinyvue/src/mcp-servers/user/tools.ts`
* `template/tinyvue/src/skills/index.ts`
* `template/tinyvue/src/skills/tiny-pro-operator/SKILL.md`
* `template/tinyvue/src/skills/tiny-pro-operator/reference/front-end-guide.md`
* `template/tinyvue/src/views/locale/components/add-locale.vue`
* `template/tinyvue/src/views/menu/info/components/info-tab.vue`
* `template/tinyvue/src/views/permission/info/components/info-tab.vue`
* `template/tinyvue/src/views/role/info/components/info-tab.vue`
* `template/tinyvue/src/views/userManager/info/components/info-tab.vue`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +13 to +17
export async function createMcpServer() {
registerLocaleManagementTools(server)
// 最后建立连接,确保所有工具已注册完毕
await rawServer.connect(serverTransport)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In @opentiny/next-sdk0.2.6-beta.0, isWebMcpServer.connect()safe to call multiple times on the same instance, and doesregisterTool() allow duplicate registrations of the same tool name?

💡 Result:

  • WebMcpServer.connect() (in @opentiny/next-sdk v0.2.6-beta.0) is not designed to be safely called multiple times on the same instance. The implementation always assigns this.transport and then calls await this.server.connect(this.transport) with no “already connected” guard or automatic close()/disconnect first—so a second call will attempt to connect again (behavior then depends on the underlying @modelcontextprotocol/sdk server, and may error or lead to inconsistent state). [1]

  • registerTool() does not allow two tools with the same name to coexist. In this SDK it simply delegates to this.server.registerTool(name, ...) (so the tool name is the identity key). [1] The MCP TS SDK docs also describe registerTool() as a way tools can be “added … or updated at runtime” (and that it triggers tool-list-changed notifications), implying re-registering the same name is treated as an update/replace, not a duplicate entry. [2]

Sources: [1] [2]


Make createMcpServer() idempotent.

This exported initializer has side effects on every call: it re-registers tools and reconnects the same server/transport pair. Calling connect() multiple times on the same WebMcpServer instance is unsafe and can cause errors or inconsistent state. A remount or HMR path could trigger this repeatedly.

Proposed fix
+let mcpServerReady: Promise<void> | undefined
+
 export async function createMcpServer() {
-  registerLocaleManagementTools(server)
-  // 最后建立连接,确保所有工具已注册完毕
-  await rawServer.connect(serverTransport)
+  if (!mcpServerReady) {
+    mcpServerReady = (async () => {
+      registerLocaleManagementTools(server)
+      // 最后建立连接,确保所有工具已注册完毕
+      await rawServer.connect(serverTransport)
+    })()
+  }
+
+  await mcpServerReady
 }
📝 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
export async function createMcpServer() {
registerLocaleManagementTools(server)
// 最后建立连接,确保所有工具已注册完毕
await rawServer.connect(serverTransport)
}
let mcpServerReady: Promise<void> | undefined
export async function createMcpServer() {
if (!mcpServerReady) {
mcpServerReady = (async () => {
registerLocaleManagementTools(server)
// 最后建立连接,确保所有工具已注册完毕
await rawServer.connect(serverTransport)
})()
}
await mcpServerReady
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/index.ts` around lines 13 - 17,
createMcpServer currently re-registers tools and calls
rawServer.connect(serverTransport) on every invocation; make it idempotent by
adding an initialization guard and a safe connect check: introduce a
module-level boolean (e.g., mcpServerInitialized) and return early if true, call
registerLocaleManagementTools only when first initializing, and before calling
rawServer.connect(serverTransport) check whether the WebMcpServer is already
connected (or wrap connect call in try/catch and set the initialized flag only
after a successful connection); update createMcpServer to set the flag after
successful registration/connection so repeated calls are no-ops.

Comment on lines +18 to +21
probationDate: z.array(z.date()).describe('试用期起止时间').optional(),
probationDuration: z.string().describe('试用期时长').optional(),
protocolStart: z.date().describe('劳动合同开始日期').optional(),
protocolEnd: z.date().describe('劳动合同结束日期').optional(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does Zod z.date() validate ISO date strings or only Date objects?

💡 Result:

z.date() validates only JavaScript Date instances—it will fail on ISO date/datetime strings. For example, "2022-01-12T06:15:00.000Z" does not pass z.date(). [1]

If you need to accept ISO strings, use a string schema like z.iso.date() (for YYYY-MM-DD) / z.iso.datetime() (for datetimes), or coerce into a Date with z.coerce.date() (then validate as a Date). [1]


🏁 Script executed:

cd template/tinyvue && find . -name "tools.ts" | grep user

Repository: opentiny/tiny-pro

Length of output: 92


🏁 Script executed:

cat -n template/tinyvue/src/mcp-servers/user/tools.ts | head -100

Repository: opentiny/tiny-pro

Length of output: 1375


🏁 Script executed:

# Search for any date coercion or preprocessing related to this schema
rg -i "coerce|date" template/tinyvue/src/mcp-servers/user/tools.ts -A 2 -B 2

Repository: opentiny/tiny-pro

Length of output: 518


Replace z.date() with z.coerce.date() for date fields.

The schema uses z.date() on lines 18, 20, and 21, which validates only JavaScript Date instances and will reject ISO date strings (e.g., "2022-01-12T06:15:00.000Z"). Since dates from the message transport arrive as ISO strings, validation will fail at runtime. Use z.coerce.date() instead to accept ISO strings and automatically coerce them to Date objects:

Suggested fix
- probationDate: z.array(z.date()).describe('试用期起止时间').optional(),
+ probationDate: z.array(z.coerce.date()).describe('试用期起止时间').optional(),
- protocolStart: z.date().describe('劳动合同开始日期').optional(),
+ protocolStart: z.coerce.date().describe('劳动合同开始日期').optional(),
- protocolEnd: z.date().describe('劳动合同结束日期').optional(),
+ protocolEnd: z.coerce.date().describe('劳动合同结束日期').optional(),
📝 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
probationDate: z.array(z.date()).describe('试用期起止时间').optional(),
probationDuration: z.string().describe('试用期时长').optional(),
protocolStart: z.date().describe('劳动合同开始日期').optional(),
protocolEnd: z.date().describe('劳动合同结束日期').optional(),
probationDate: z.array(z.coerce.date()).describe('试用期起止时间').optional(),
probationDuration: z.string().describe('试用期时长').optional(),
protocolStart: z.coerce.date().describe('劳动合同开始日期').optional(),
protocolEnd: z.coerce.date().describe('劳动合同结束日期').optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/user/tools.ts` around lines 18 - 21, The
date fields in the schema (probationDate, protocolStart, protocolEnd) are using
z.date(), which rejects ISO date strings; update those validators to
z.coerce.date() (and for the array use z.array(z.coerce.date())) so incoming ISO
string dates are coerced to Date objects at validation time; keep
probationDuration as string. Locate the symbols probationDate,
probationDuration, protocolStart, protocolEnd and replace z.date() with
z.coerce.date() accordingly.

@kagol kagol force-pushed the kagol/add-next-sdk-skills branch from 551357e to 3ff1e3d Compare March 10, 2026 09:23
Copy link

@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

♻️ Duplicate comments (2)
template/tinyvue/src/App.vue (1)

29-30: ⚠️ Potential issue | 🟠 Major

Provide the connected serverTransport, not a new one.

Lines 29-30 create a second transport pair, but createMcpServer() only connects the pair from template/tinyvue/src/mcp-servers/index.ts. Any descendant injecting serverTransport here will be wired to a dead channel.

#!/bin/bash
set -euo pipefail

# Verify whether any descendants consume the injected transport key.
rg -n -C2 "createMessageChannelPairTransport|provide\(['\"]serverTransport['\"]|inject\(['\"]serverTransport['\"]" template/tinyvue/src
Suggested fix

template/tinyvue/src/App.vue

-import { createMessageChannelPairTransport } from '@opentiny/next-sdk'
 import { TinyConfigProvider } from '@opentiny/vue'
 import TinyThemeTool from '@opentiny/vue-theme/theme-tool'
 import { onMounted, provide } from 'vue'
@@
-import { createMcpServer, clientTransport } from './mcp-servers'
+import { createMcpServer, clientTransport, serverTransport } from './mcp-servers'
@@
-const [serverTransport] = createMessageChannelPairTransport()
 provide('serverTransport', serverTransport)

template/tinyvue/src/mcp-servers/index.ts

-// clientTransport 导出给 TinyRemoter 使用
-export { clientTransport }
+// transports 导出给 TinyRemoter / 页面工具使用
+export { clientTransport, serverTransport }

If the search finds no inject('serverTransport') consumers, removing the provide('serverTransport', ...) line entirely is the better cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/App.vue` around lines 29 - 30, The code is providing a
freshly created transport via createMessageChannelPairTransport() and calling
provide('serverTransport', serverTransport), but createMcpServer() connects a
different transport pair, so descendants injecting 'serverTransport' receive an
unconnected channel; fix by providing the already-connected server transport
from createMcpServer() (or remove the provide call if no
inject('serverTransport') consumers exist). Locate the
provide('serverTransport', ...) call in App.vue and replace it with the
connected transport returned/managed by createMcpServer() (or delete the provide
line if ripgrep shows no inject('serverTransport') usage).
template/tinyvue/src/mcp-servers/index.ts (1)

17-24: ⚠️ Potential issue | 🟠 Major

Guard createMcpServer() against repeated initialization.

Line 24 reconnects the same rawServer/serverTransport pair on every invocation. A remount or HMR path can turn that into duplicate registration or an unsafe second connect().

Search the official `@opentiny/next-sdk` 0.2.7 documentation or source: is WebMcpServer.connect() safe to call multiple times on the same instance, and what happens if registerTool() is called again with the same tool name?
Suggested fix
+let mcpServerReady: Promise<void> | undefined
+
 export async function createMcpServer() {
-  registerLocaleManagementTools(server)
-  registerUserManagementTools(server)
-  registerRoleManagementTools(server)
-  registerPermissionManagementTools(server)
-  registerMenuManagementTools(server)
-  // 最后建立连接,确保所有工具已注册完毕
-  await rawServer.connect(serverTransport)
+  if (!mcpServerReady) {
+    mcpServerReady = (async () => {
+      registerLocaleManagementTools(server)
+      registerUserManagementTools(server)
+      registerRoleManagementTools(server)
+      registerPermissionManagementTools(server)
+      registerMenuManagementTools(server)
+      // 最后建立连接,确保所有工具已注册完毕
+      await rawServer.connect(serverTransport)
+    })().catch((error) => {
+      mcpServerReady = undefined
+      throw error
+    })
+  }
+
+  await mcpServerReady
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/index.ts` around lines 17 - 24,
createMcpServer() currently calls registration helpers and then always calls
rawServer.connect(serverTransport), which can cause duplicate tool registration
or unsafe reconnects on remount/HMR; guard the function so it runs
initialization only once by tracking an initialized flag (e.g., module-level
boolean or a property on server) and return early if already initialized, and
only call rawServer.connect(serverTransport) when not connected yet; set the
flag immediately after successful connect (or after all registrations) and
ensure the guard references createMcpServer, rawServer.connect, and the
register*ManagementTools functions to prevent repeated registration/connect.
🧹 Nitpick comments (1)
template/tinyvue/src/mcp-servers/role/tools.ts (1)

10-14: Good documentation of known limitation.

The TODO comment appropriately flags the permission ID conversion issue. Users may describe permissions in natural language (e.g., "add and delete user permissions") rather than as numeric IDs. This semantic gap will need to be handled, either:

  1. In the LLM prompt/system instructions
  2. By a permission name-to-ID lookup in the handler
  3. By changing the schema to accept string identifiers

Consider tracking this as a follow-up issue if not addressed in this PR.

Would you like me to open an issue to track the permission name-to-ID conversion enhancement?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/role/tools.ts` around lines 10 - 14, The
inputSchema currently requires permissions as z.array(z.number()) which doesn't
handle natural-language permission names; either accept string identifiers in
the schema or convert names to IDs in the handler: update
inputSchema.permissions to accept z.array(z.union([z.number(), z.string()])) (or
similar), implement a helper convertPermissionNamesToIds(permissionInputs) and
call it from the processRoleInput (or the role handler that consumes
inputSchema) to map any string names to numeric IDs using your permission
lookup, and add unit tests covering numeric, string, and mixed inputs; ensure
errors are thrown when unknown names are encountered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@template/tinyvue/src/views/role/info/components/info-tab.vue`:
- Around line 226-239: The code assumes rowData exists after calling
getAllRoleDetail() and immediately accesses rowData.menus and rowData.id; add a
defensive null-check after finding rowData (the result of
data.roleInfo.items.find) and handle the missing case (e.g., return a helpful
response or throw) before calling roleTableRef.value.openMenuModal,
menuDrawerRef.value.treeRef.setChecked, or menuDrawerRef.value.onConfirm;
alternatively, call getAllRoleDetail with a larger limit or paginated fetch
until the role is found (update the call site of getAllRoleDetail and the
variable rowData) so you never dereference undefined.

---

Duplicate comments:
In `@template/tinyvue/src/App.vue`:
- Around line 29-30: The code is providing a freshly created transport via
createMessageChannelPairTransport() and calling provide('serverTransport',
serverTransport), but createMcpServer() connects a different transport pair, so
descendants injecting 'serverTransport' receive an unconnected channel; fix by
providing the already-connected server transport from createMcpServer() (or
remove the provide call if no inject('serverTransport') consumers exist). Locate
the provide('serverTransport', ...) call in App.vue and replace it with the
connected transport returned/managed by createMcpServer() (or delete the provide
line if ripgrep shows no inject('serverTransport') usage).

In `@template/tinyvue/src/mcp-servers/index.ts`:
- Around line 17-24: createMcpServer() currently calls registration helpers and
then always calls rawServer.connect(serverTransport), which can cause duplicate
tool registration or unsafe reconnects on remount/HMR; guard the function so it
runs initialization only once by tracking an initialized flag (e.g.,
module-level boolean or a property on server) and return early if already
initialized, and only call rawServer.connect(serverTransport) when not connected
yet; set the flag immediately after successful connect (or after all
registrations) and ensure the guard references createMcpServer,
rawServer.connect, and the register*ManagementTools functions to prevent
repeated registration/connect.

---

Nitpick comments:
In `@template/tinyvue/src/mcp-servers/role/tools.ts`:
- Around line 10-14: The inputSchema currently requires permissions as
z.array(z.number()) which doesn't handle natural-language permission names;
either accept string identifiers in the schema or convert names to IDs in the
handler: update inputSchema.permissions to accept z.array(z.union([z.number(),
z.string()])) (or similar), implement a helper
convertPermissionNamesToIds(permissionInputs) and call it from the
processRoleInput (or the role handler that consumes inputSchema) to map any
string names to numeric IDs using your permission lookup, and add unit tests
covering numeric, string, and mixed inputs; ensure errors are thrown when
unknown names are encountered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a008282-f401-4f7b-9347-6a0c2350511c

📥 Commits

Reviewing files that changed from the base of the PR and between 551357e and 3ff1e3d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • template/tinyvue/package.json
  • template/tinyvue/src/App.vue
  • template/tinyvue/src/mcp-servers/index.ts
  • template/tinyvue/src/mcp-servers/locale/tools.ts
  • template/tinyvue/src/mcp-servers/menu/tools.ts
  • template/tinyvue/src/mcp-servers/permission/tools.ts
  • template/tinyvue/src/mcp-servers/role/tools.ts
  • template/tinyvue/src/mcp-servers/user/tools.ts
  • template/tinyvue/src/skills/index.ts
  • template/tinyvue/src/skills/tiny-pro-operator/SKILL.md
  • template/tinyvue/src/views/locale/components/add-locale.vue
  • template/tinyvue/src/views/menu/info/components/info-tab.vue
  • template/tinyvue/src/views/permission/info/components/info-tab.vue
  • template/tinyvue/src/views/role/info/components/info-tab.vue
  • template/tinyvue/src/views/userManager/info/components/info-tab.vue
🚧 Files skipped from review as they are similar to previous changes (5)
  • template/tinyvue/package.json
  • template/tinyvue/src/mcp-servers/menu/tools.ts
  • template/tinyvue/src/mcp-servers/user/tools.ts
  • template/tinyvue/src/skills/tiny-pro-operator/SKILL.md
  • template/tinyvue/src/skills/index.ts

@kagol kagol force-pushed the kagol/add-next-sdk-skills branch from 3ff1e3d to 25dc53c Compare March 10, 2026 09:53
Copy link

@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 (4)
template/tinyvue/src/mcp-servers/role/tools.ts (1)

10-14: Address TODO: Permission format conversion is unimplemented.

The comment indicates users may specify permissions in natural language (e.g., "添加用户的权限") rather than numeric IDs. The current schema expects z.array(z.number()), so the AI or an intermediate layer must handle this conversion.

Consider one of:

  1. Accept string identifiers (e.g., user::add) and resolve them server-side.
  2. Document the expected format clearly in the description so the AI can prompt for or infer IDs.
  3. Add a mapping layer before invoking the tool.

Would you like me to open an issue to track this conversion requirement, or help draft a solution?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/mcp-servers/role/tools.ts` around lines 10 - 14, The
inputSchema currently requires permissions as z.array(z.number()) but the TODO
notes users may supply natural-language or string identifiers; update the schema
and add a normalization layer: change inputSchema.permissions to accept numbers
or strings (e.g., z.array(z.union([z.number(), z.string()]))) and implement a
normalizePermissions function (or PERMISSION_MAP lookup) in the same module that
converts user-facing strings (natural language or token like "user::add") to
numeric IDs before the tool uses them; call this normalizer where the tool
processes incoming input (refer to inputSchema.permissions and add/introduce
normalizePermissions or PERMISSION_MAP in tools.ts) and ensure unknown strings
produce a clear validation error.
template/tinyvue/package.json (1)

32-33: Pinned dependencies are set to the latest available versions.

The exact versions pinned (0.2.7 for both packages) are confirmed to exist on npm and are currently the latest available. This locks the dependencies to stable releases, which ensures consistent behavior but requires manual updates for future releases.

If automatic compatibility updates are desired, consider using caret ranges (^0.2.7) instead to receive patch and minor version updates while the major version remains fixed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/package.json` around lines 32 - 33, The package.json
currently pins "@opentiny/next-remoter" and "@opentiny/next-sdk" to exact
versions "0.2.7"; update those dependency entries to use caret ranges ("^0.2.7")
so the project accepts compatible minor/patch updates automatically (change the
values for "@opentiny/next-remoter" and "@opentiny/next-sdk" to "^0.2.7").
template/tinyvue/src/views/role/info/components/info-tab.vue (1)

213-224: Consider replacing hardcoded sleep() with event-driven synchronization.

The sleep(1000) calls assume UI operations complete within 1 second. On slower networks or devices, this may cause race conditions where the form isn't ready or the confirm action fires prematurely.

If this is for demo purposes only, the current approach is acceptable. For production use, consider waiting for specific DOM states or emitting/awaiting component-ready events.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/role/info/components/info-tab.vue` around lines
213 - 224, The handler for 'add-role' currently uses fixed sleep(1000) delays
around onAdd() and before calling addRoleFormRef.value.onConfirm(), which can
race on slow devices; replace these sleeps with event-driven waits: have onAdd()
open the form/modal and emit or return a Promise when the form is fully
initialized (e.g., an "opened" or "ready" event), then await that Promise or
wait for addRoleFormRef.value to be non-null and for a readiness flag/method
(e.g., addRoleFormRef.value.ready or an emitted "ready" event) before calling
setRoleInfo and before calling onConfirm; update the add-role handler to await
those specific readiness signals instead of sleep to ensure deterministic
sequencing for setRoleInfo and onConfirm.
template/tinyvue/src/App.vue (1)

38-39: Make local MCP bootstrap idempotent.

createMcpServer() re-registers every tool and reconnects the same module-level server on each call. Calling it from the onMounted() lifecycle hook in App.vue makes remount and HMR scenarios unnecessarily risky; caching the first init promise in template/tinyvue/src/mcp-servers/index.ts would make this setup much safer.

♻️ Proposed fix in template/tinyvue/src/mcp-servers/index.ts
+let initPromise: Promise<void> | null = null
+
 export async function createMcpServer() {
-  registerLocaleManagementTools(server)
-  registerUserManagementTools(server)
-  registerRoleManagementTools(server)
-  registerPermissionManagementTools(server)
-  registerMenuManagementTools(server)
-  // 最后建立连接,确保所有工具已注册完毕
-  await rawServer.connect(serverTransport)
+  if (!initPromise) {
+    initPromise = (async () => {
+      registerLocaleManagementTools(server)
+      registerUserManagementTools(server)
+      registerRoleManagementTools(server)
+      registerPermissionManagementTools(server)
+      registerMenuManagementTools(server)
+      // 最后建立连接,确保所有工具已注册完毕
+      await rawServer.connect(serverTransport)
+    })()
+  }
+
+  return initPromise
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/App.vue` around lines 38 - 39, createMcpServer() is
currently non-idempotent and is called onMounted in App.vue, causing duplicate
tool registration and reconnects on remount/HMR; make the initialization
idempotent by adding a module-level cache in
template/tinyvue/src/mcp-servers/index.ts (e.g., let initPromise: Promise<void>
| null) and modify the exported createMcpServer function to return the cached
promise if present, otherwise set initPromise to the actual async init logic,
perform registrations/connect once, and return it; also ensure any teardown or
error resets initPromise so failed inits can retry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@template/tinyvue/src/App.vue`:
- Around line 37-39: The TinyRemoter component is rendered before
createMcpServer() completes in onMounted(), exposing it to an uninitialized
server; modify the component to track a ready state (e.g., mcpReady) updated
after await createMcpServer() (and set false on error) and conditionally
render/gate TinyRemoter based on that flag, handling bootstrap errors by not
mounting TinyRemoter when createMcpServer() throws; refer to createMcpServer,
onMounted and TinyRemoter to locate where to add the boolean state and
conditional rendering.

In `@template/tinyvue/src/views/locale/components/add-locale.vue`:
- Around line 141-150: The handler 'add-i18n-entry' returns success before the
async work completes because addLocale() does not return its internal promise
and the handler uses fixed sleep() waits; change addLocale to return the promise
from its internal chain (including the createLocalItem call) so callers can
await its completion and errors, and remove brittle sleep(1000) usages in both
addLocale and the handler—use proper awaits (e.g., await the mutation promise or
Vue's nextTick / an explicit resolved state) and then in the handler await
addLocale(), handle errors with try/catch and only return the success response
after the awaited addLocale() resolves or propagate the error; reference
functions/vars: addLocale, createLocalItem, onOpen, locale, and the
'add-i18n-entry' handler.

In `@template/tinyvue/src/views/menu/info/components/info-tab.vue`:
- Around line 261-276: The handler for 'add-menu' (the async arrow function that
calls handleAddMenu, sleep, getIdByLabel, addMenu.value.setMenuInfo, and
onClickAdd) can dereference addMenu.value before the component is mounted;
replace the fixed sleep with a safe wait: after calling handleAddMenu() await
nextTick() (or loop with small sleeps and a timeout) to ensure addMenu.value is
non-null, then proceed to call addMenu.value.setMenuInfo({ name, order,
parentId, icon, component, menuType: '/', path, locale: menuLocale }); if
addMenu.value remains null after the timeout, log or return early instead of
calling setMenuInfo. Ensure references to handleAddMenu, getIdByLabel,
addMenu.value.setMenuInfo, and onClickAdd are preserved.

In `@template/tinyvue/src/views/permission/info/components/info-tab.vue`:
- Around line 205-212: The 'add-permission' tool handler calls
handlePermissionAddSubmit() without awaiting it and returns success immediately;
update the handler to await handlePermissionAddSubmit() and only return the
success message after the awaited call completes, ensure you still call
handleAddPermission() and set state.permissionAddData.name/desc before awaiting;
additionally modify the flow to catch and rethrow or return an error when
handlePermissionAddSubmit() fails (and remove or surface the empty catch inside
handlePermissionAddSubmit if present) so validation or API errors are propagated
back to the tool caller instead of silently returning "收到: ${name}".

---

Nitpick comments:
In `@template/tinyvue/package.json`:
- Around line 32-33: The package.json currently pins "@opentiny/next-remoter"
and "@opentiny/next-sdk" to exact versions "0.2.7"; update those dependency
entries to use caret ranges ("^0.2.7") so the project accepts compatible
minor/patch updates automatically (change the values for
"@opentiny/next-remoter" and "@opentiny/next-sdk" to "^0.2.7").

In `@template/tinyvue/src/App.vue`:
- Around line 38-39: createMcpServer() is currently non-idempotent and is called
onMounted in App.vue, causing duplicate tool registration and reconnects on
remount/HMR; make the initialization idempotent by adding a module-level cache
in template/tinyvue/src/mcp-servers/index.ts (e.g., let initPromise:
Promise<void> | null) and modify the exported createMcpServer function to return
the cached promise if present, otherwise set initPromise to the actual async
init logic, perform registrations/connect once, and return it; also ensure any
teardown or error resets initPromise so failed inits can retry.

In `@template/tinyvue/src/mcp-servers/role/tools.ts`:
- Around line 10-14: The inputSchema currently requires permissions as
z.array(z.number()) but the TODO notes users may supply natural-language or
string identifiers; update the schema and add a normalization layer: change
inputSchema.permissions to accept numbers or strings (e.g.,
z.array(z.union([z.number(), z.string()]))) and implement a normalizePermissions
function (or PERMISSION_MAP lookup) in the same module that converts user-facing
strings (natural language or token like "user::add") to numeric IDs before the
tool uses them; call this normalizer where the tool processes incoming input
(refer to inputSchema.permissions and add/introduce normalizePermissions or
PERMISSION_MAP in tools.ts) and ensure unknown strings produce a clear
validation error.

In `@template/tinyvue/src/views/role/info/components/info-tab.vue`:
- Around line 213-224: The handler for 'add-role' currently uses fixed
sleep(1000) delays around onAdd() and before calling
addRoleFormRef.value.onConfirm(), which can race on slow devices; replace these
sleeps with event-driven waits: have onAdd() open the form/modal and emit or
return a Promise when the form is fully initialized (e.g., an "opened" or
"ready" event), then await that Promise or wait for addRoleFormRef.value to be
non-null and for a readiness flag/method (e.g., addRoleFormRef.value.ready or an
emitted "ready" event) before calling setRoleInfo and before calling onConfirm;
update the add-role handler to await those specific readiness signals instead of
sleep to ensure deterministic sequencing for setRoleInfo and onConfirm.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1548403f-faf9-44bb-9015-b0a9cb7b9e14

📥 Commits

Reviewing files that changed from the base of the PR and between 3ff1e3d and 25dc53c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • template/tinyvue/package.json
  • template/tinyvue/src/App.vue
  • template/tinyvue/src/mcp-servers/index.ts
  • template/tinyvue/src/mcp-servers/locale/tools.ts
  • template/tinyvue/src/mcp-servers/menu/tools.ts
  • template/tinyvue/src/mcp-servers/permission/tools.ts
  • template/tinyvue/src/mcp-servers/role/tools.ts
  • template/tinyvue/src/mcp-servers/user/tools.ts
  • template/tinyvue/src/skills/index.ts
  • template/tinyvue/src/skills/tiny-pro-operator/SKILL.md
  • template/tinyvue/src/views/locale/components/add-locale.vue
  • template/tinyvue/src/views/menu/info/components/info-tab.vue
  • template/tinyvue/src/views/permission/info/components/info-tab.vue
  • template/tinyvue/src/views/role/info/components/info-tab.vue
  • template/tinyvue/src/views/userManager/info/components/info-tab.vue
🚧 Files skipped from review as they are similar to previous changes (6)
  • template/tinyvue/src/mcp-servers/locale/tools.ts
  • template/tinyvue/src/skills/index.ts
  • template/tinyvue/src/skills/tiny-pro-operator/SKILL.md
  • template/tinyvue/src/mcp-servers/permission/tools.ts
  • template/tinyvue/src/views/userManager/info/components/info-tab.vue
  • template/tinyvue/src/mcp-servers/index.ts

Comment on lines +37 to +39
// 启动 MCP Server(注册工具 + 建立通信通道)
onMounted(async () => {
const server = new WebMcpServer()
const $router = useRouter()
const $route = useRoute()

// TODO: 参数需要优化,用户不会知道具体的路由路径,用户的语言可能是:帮我打开菜单管理页面,这时应该根据名称获取路由路径,再做路由跳转
// 进一步优化:用户可能在任意页面直接提需求:帮我创建 xx 菜单,这时 AI 应该先跳转菜单管理页面,然后调佣创建菜单的工具
server.registerTool(
'switch-router',
{
title: '切换路由',
description: '切换路由',
inputSchema: {
routerPath: z.string().describe('路由路径'),
},
},
async ({ routerPath }) => {
if ($route.path === routerPath) {
return { content: [{ type: 'text', text: routerPath }] }
}

$router.push(import.meta.env.VITE_CONTEXT + routerPath)
return { content: [{ type: 'text', text: routerPath }] }
},
)
await server.connect(serverTransport)

// 创建 WebMcpClient ,并与 WebAgent 连接
const client = new WebMcpClient()
await client.connect(clientTransport)
const { sessionId: sessionID } = await client.connect({
agent: true,

// sessionId 为可选参数。若传入该参数,系统将使用指定值作为会话标识;若未传入,WebAgent 服务将自动生成一个随机的字符串作为 sessionId。为便于通过 MCP Inspector 工具进行调试,此处采用了固定的 sessionId。用户亦可通过浏览器原生提供的 crypto.randomUUID() 方法生成随机字符串作为会话标识。
sessionId: 'd299a869-c674-4125-a84b-bb4e24079b99',

url: `${AGENT_URL}mcp`,
})
sessionId.value = sessionID
await createMcpServer()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Vue 3 Composition API, are child components mounted before the parent component's onMounted()hook runs, and is the initial render committed before an asynconMounted() callback finishes?

💡 Result:

Yes.

  • Child components mount before the parent’s onMounted() runs (for synchronous children): Vue considers a component “mounted” only after all of its synchronous child components have been mounted and the component’s DOM has been created/inserted, so the parent’s mounted / onMounted necessarily happens after those children. [1]

  • The initial render is already committed before an async onMounted(async () => …) finishes: onMounted runs after the component has finished the initial rendering and created the DOM nodes, and Vue does not delay/“await” client rendering on a Promise returned from mounted/onMounted. (Vue only has an explicit “wait for the Promise” lifecycle hook for SSR: serverPrefetch.) [2] [3]

Sources: [1] Vue Options API lifecycle: mounted semantics. [2] Vue Guide: onMounted runs after initial render + DOM creation. [3] Vue Options API lifecycle: serverPrefetch is awaited by the server renderer.


Gate TinyRemoter on successful MCP startup.

createMcpServer() is async and runs in onMounted(), but TinyRemoter is rendered unconditionally during initial render—which happens before onMounted runs. If bootstrap is still in flight or throws, the remoter is exposed to an uninitialized local server with an unhandled error.

🐛 Proposed fix
-import { onMounted } from 'vue'
+import { onMounted, ref } from 'vue'
@@
 const mcpServers = {
   'my-mcp-server': {
     type: 'local',
     transport: clientTransport,
   },
 }
 
+const mcpReady = ref(false)
+
 // 启动 MCP Server(注册工具 + 建立通信通道)
 onMounted(async () => {
-  await createMcpServer()
+  try {
+    await createMcpServer()
+    mcpReady.value = true
+  } catch (error) {
+    console.error('Failed to start local MCP server', error)
+  }
 })
@@
   <TinyRemoter
+    v-if="mcpReady"
     :skills="skills"
     :mcp-servers="mcpServers"
   />
📝 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
// 启动 MCP Server(注册工具 + 建立通信通道)
onMounted(async () => {
const server = new WebMcpServer()
const $router = useRouter()
const $route = useRoute()
// TODO: 参数需要优化,用户不会知道具体的路由路径,用户的语言可能是:帮我打开菜单管理页面,这时应该根据名称获取路由路径,再做路由跳转
// 进一步优化:用户可能在任意页面直接提需求:帮我创建 xx 菜单,这时 AI 应该先跳转菜单管理页面,然后调佣创建菜单的工具
server.registerTool(
'switch-router',
{
title: '切换路由',
description: '切换路由',
inputSchema: {
routerPath: z.string().describe('路由路径'),
},
},
async ({ routerPath }) => {
if ($route.path === routerPath) {
return { content: [{ type: 'text', text: routerPath }] }
}
$router.push(import.meta.env.VITE_CONTEXT + routerPath)
return { content: [{ type: 'text', text: routerPath }] }
},
)
await server.connect(serverTransport)
// 创建 WebMcpClient ,并与 WebAgent 连接
const client = new WebMcpClient()
await client.connect(clientTransport)
const { sessionId: sessionID } = await client.connect({
agent: true,
// sessionId 为可选参数。若传入该参数,系统将使用指定值作为会话标识;若未传入,WebAgent 服务将自动生成一个随机的字符串作为 sessionId。为便于通过 MCP Inspector 工具进行调试,此处采用了固定的 sessionId。用户亦可通过浏览器原生提供的 crypto.randomUUID() 方法生成随机字符串作为会话标识。
sessionId: 'd299a869-c674-4125-a84b-bb4e24079b99',
url: `${AGENT_URL}mcp`,
})
sessionId.value = sessionID
await createMcpServer()
// 启动 MCP Server(注册工具 + 建立通信通道)
onMounted(async () => {
try {
await createMcpServer()
mcpReady.value = true
} catch (error) {
console.error('Failed to start local MCP server', error)
}
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/App.vue` around lines 37 - 39, The TinyRemoter component
is rendered before createMcpServer() completes in onMounted(), exposing it to an
uninitialized server; modify the component to track a ready state (e.g.,
mcpReady) updated after await createMcpServer() (and set false on error) and
conditionally render/gate TinyRemoter based on that flag, handling bootstrap
errors by not mounting TinyRemoter when createMcpServer() throws; refer to
createMcpServer, onMounted and TinyRemoter to locate where to add the boolean
state and conditional rendering.

Comment on lines +141 to 150
'add-i18n-entry': async ({ key, content, lang: langId }) => {
onOpen()
await sleep(1000)
locale.key = key
locale.content = content
locale.lang = langId
await sleep(1000)
addLocale()
return { content: [{ type: 'text', text: `收到: ${key}` }] }
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handler returns success before operation completes.

The addLocale() function (lines 101-126) does not return a promise—it initiates an internal promise chain but returns undefined. This means:

  1. The await on line 148 resolves immediately
  2. The success response is returned before knowing if createLocalItem succeeded
  3. Errors from the API call won't propagate to the MCP tool caller

Additionally, the sleep(1000) calls are fragile—they assume UI state settles within a fixed time rather than awaiting actual state changes.

🔧 Proposed fix: refactor addLocale to return a promise

First, modify addLocale to return the promise chain (around line 101):

-function addLocale() {
-  localeForm.value
+async function addLocale() {
+  await localeForm.value
     .validate()
-    .then(() => {
-      createLocalItem(locale)
-        .then(({ data }) => {
+  try {
+    const { data } = await createLocalItem(locale)
           locale.key = ''
           locale.content = ''
           locale.lang = '' as any
           locales.pushLocale(data)
           i18.mergeLocaleMessage(data.lang.name, {
             [data.key]: data.content,
           })
           emits('localChange')
-        })
-        .catch((reason) => {
-          Notify({
-            type: 'error',
-            message: reason.response.data.message,
-          })
-        })
-        .finally(() => {
-          onClose()
-        })
-    })
+  } catch (reason: any) {
+    Notify({
+      type: 'error',
+      message: reason.response?.data?.message ?? 'Failed to add locale',
+    })
+    throw reason
+  } finally {
+    onClose()
+  }
 }

Then update the handler to catch errors:

       'add-i18n-entry': async ({ key, content, lang: langId }) => {
         onOpen()
         await sleep(1000)
         locale.key = key
         locale.content = content
         locale.lang = langId
         await sleep(1000)
-        addLocale()
-        return { content: [{ type: 'text', text: `收到: ${key}` }] }
+        try {
+          await addLocale()
+          return { content: [{ type: 'text', text: `添加成功: ${key}` }] }
+        } catch (e) {
+          return { content: [{ type: 'text', text: `添加失败: ${key}` }], isError: true }
+        }
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/locale/components/add-locale.vue` around lines 141
- 150, The handler 'add-i18n-entry' returns success before the async work
completes because addLocale() does not return its internal promise and the
handler uses fixed sleep() waits; change addLocale to return the promise from
its internal chain (including the createLocalItem call) so callers can await its
completion and errors, and remove brittle sleep(1000) usages in both addLocale
and the handler—use proper awaits (e.g., await the mutation promise or Vue's
nextTick / an explicit resolved state) and then in the handler await
addLocale(), handle errors with try/catch and only return the success response
after the awaited addLocale() resolves or propagate the error; reference
functions/vars: addLocale, createLocalItem, onOpen, locale, and the
'add-i18n-entry' handler.

Comment on lines +261 to +276
'add-menu': async ({ name, order, parentMenu, icon, component, path, locale: menuLocale }) => {
handleAddMenu()
await sleep(1000)
const parentId = getIdByLabel(i18nMenuData.value, parentMenu)
addMenu.value.setMenuInfo({
name,
order,
parentId,
icon,
component,
menuType: '/',
path,
locale: menuLocale,
})
await sleep(1000)
onClickAdd()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential null reference when accessing addMenu.value.

After handleAddMenu() opens the modal and sleep(1000) completes, addMenu.value is accessed without a null check. While Vue should have rendered the component by then, timing is not guaranteed—especially under heavy load or slower devices.

Consider adding a defensive check or using nextTick to ensure the component is mounted:

🛡️ Proposed defensive check
       'add-menu': async ({ name, order, parentMenu, icon, component, path, locale: menuLocale }) => {
         handleAddMenu()
         await sleep(1000)
+        if (!addMenu.value) {
+          return { content: [{ type: 'text', text: 'Error: Menu form not ready' }] }
+        }
         const parentId = getIdByLabel(i18nMenuData.value, parentMenu)
         addMenu.value.setMenuInfo({
📝 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
'add-menu': async ({ name, order, parentMenu, icon, component, path, locale: menuLocale }) => {
handleAddMenu()
await sleep(1000)
const parentId = getIdByLabel(i18nMenuData.value, parentMenu)
addMenu.value.setMenuInfo({
name,
order,
parentId,
icon,
component,
menuType: '/',
path,
locale: menuLocale,
})
await sleep(1000)
onClickAdd()
'add-menu': async ({ name, order, parentMenu, icon, component, path, locale: menuLocale }) => {
handleAddMenu()
await sleep(1000)
if (!addMenu.value) {
return { content: [{ type: 'text', text: 'Error: Menu form not ready' }] }
}
const parentId = getIdByLabel(i18nMenuData.value, parentMenu)
addMenu.value.setMenuInfo({
name,
order,
parentId,
icon,
component,
menuType: '/',
path,
locale: menuLocale,
})
await sleep(1000)
onClickAdd()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/menu/info/components/info-tab.vue` around lines
261 - 276, The handler for 'add-menu' (the async arrow function that calls
handleAddMenu, sleep, getIdByLabel, addMenu.value.setMenuInfo, and onClickAdd)
can dereference addMenu.value before the component is mounted; replace the fixed
sleep with a safe wait: after calling handleAddMenu() await nextTick() (or loop
with small sleeps and a timeout) to ensure addMenu.value is non-null, then
proceed to call addMenu.value.setMenuInfo({ name, order, parentId, icon,
component, menuType: '/', path, locale: menuLocale }); if addMenu.value remains
null after the timeout, log or return early instead of calling setMenuInfo.
Ensure references to handleAddMenu, getIdByLabel, addMenu.value.setMenuInfo, and
onClickAdd are preserved.

Comment on lines +205 to +212
'add-permission': async ({ name, desc }) => {
handleAddPermission()
await sleep(1000)
state.permissionAddData.name = name
state.permissionAddData.desc = desc
await sleep(1000)
handlePermissionAddSubmit()
return { content: [{ type: 'text', text: `收到: ${name}` }] }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing await on async submission causes premature return.

handlePermissionAddSubmit() is an async function that performs the API call to create the permission. Without await, the tool returns "收到: ${name}" before the permission is actually created, creating a race condition where the caller may assume success prematurely.

Additionally, if form validation fails (line 191's empty catch), the tool still returns a success message without any indication of failure.

🐛 Proposed fix
       'add-permission': async ({ name, desc }) => {
         handleAddPermission()
         await sleep(1000)
         state.permissionAddData.name = name
         state.permissionAddData.desc = desc
         await sleep(1000)
-        handlePermissionAddSubmit()
-        return { content: [{ type: 'text', text: `收到: ${name}` }] }
+        await handlePermissionAddSubmit()
+        return { content: [{ type: 'text', text: `权限已添加: ${name}` }] }
       },

Note: Consider also adding error handling to propagate submission failures back to the tool caller, since the current catch(() => { }) in handlePermissionAddSubmit silently swallows validation errors.

📝 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
'add-permission': async ({ name, desc }) => {
handleAddPermission()
await sleep(1000)
state.permissionAddData.name = name
state.permissionAddData.desc = desc
await sleep(1000)
handlePermissionAddSubmit()
return { content: [{ type: 'text', text: `收到: ${name}` }] }
'add-permission': async ({ name, desc }) => {
handleAddPermission()
await sleep(1000)
state.permissionAddData.name = name
state.permissionAddData.desc = desc
await sleep(1000)
await handlePermissionAddSubmit()
return { content: [{ type: 'text', text: `权限已添加: ${name}` }] }
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@template/tinyvue/src/views/permission/info/components/info-tab.vue` around
lines 205 - 212, The 'add-permission' tool handler calls
handlePermissionAddSubmit() without awaiting it and returns success immediately;
update the handler to await handlePermissionAddSubmit() and only return the
success message after the awaited call completes, ensure you still call
handleAddPermission() and set state.permissionAddData.name/desc before awaiting;
additionally modify the flow to catch and rethrow or return an error when
handlePermissionAddSubmit() fails (and remove or surface the empty catch inside
handlePermissionAddSubmit if present) so validation or API errors are propagated
back to the tool caller instead of silently returning "收到: ${name}".

@kagol kagol merged commit 5f431a7 into dev Mar 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant