Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"homepage": "https://opentiny.design/pro/",
"pnpm": {
"overrides": {
"@modelcontextprotocol/sdk": "1.16.0",
"@opentiny/tiny-engine-common>@opentiny/vue-renderless": "~3.20.0",
"@opentiny/tiny-engine-plugin-datasource>@opentiny/vue-renderless": "~3.20.0",
"@opentiny/tiny-engine-plugin-block>@opentiny/vue-renderless": "~3.20.0",
Expand Down
1,028 changes: 668 additions & 360 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions template/tinyvue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"@babel/core": "^7.25.2",
"@gaonengwww/mock-server": "^1.0.5",
"@opentiny/icons": "^0.1.3",
"@opentiny/next-remoter": "^0.0.10",
"@opentiny/next-sdk": "^0.1.15",
"@opentiny/vue": "^3.28.0",
"@opentiny/vue-huicharts": "~3.28.0",
"@opentiny/vue-icon": "~3.28.0",
Expand Down
43 changes: 42 additions & 1 deletion template/tinyvue/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,35 @@

<global-setting />
</div>
<tiny-remoter
agent-root="https://agent.opentiny.design/api/v1/webmcp-trial/"
:session-id="sessionId"
:menuItems="[
{
action: 'qr-code',
show: false
},
{
action: 'remote-control',
show: false
},
{
action: 'remote-url',
show: false
}
]"
/>
</template>

<script lang="ts" setup>
import { provide } from 'vue';
import { onMounted, provide, ref } from 'vue';
import { TinyConfigProvider } from '@opentiny/vue';
import GlobalSetting from '@/components/global-setting/index.vue';
import { createMessageChannelPairTransport, WebMcpClient } from '@opentiny/next-sdk'
import { TinyRemoter } from '@opentiny/next-remoter'
import TinyThemeTool from '@opentiny/vue-theme/theme-tool';
import { useTheme } from './hooks/useTheme';
import '@opentiny/next-remoter/dist/style.css'
const theme = new TinyThemeTool();
useTheme(theme);
Expand All @@ -30,6 +51,26 @@
},
},
};
const sessionId = ref('')
const [serverTransport, clientTransport] = createMessageChannelPairTransport()
provide('serverTransport', serverTransport)
onMounted(async () => {
// 创建 WebMcpClient ,并与 WebAgent 连接
const client = new WebMcpClient()
await client.connect(clientTransport)
const { sessionId: sessionID } = await client.connect({
Comment on lines +63 to +64
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:

@opentiny/next-sdk WebMcpClient connect method API documentation

💡 Result:

Summary of WebMcpClient.connect (from @opentiny/next-sdk):

  • Signature: async connect(options: Transport | ClientConnectOptions): Promise<{ transport: Transport; sessionId: string }>. [1]
  • Options: can pass a Transport directly or an object with:
    • url: string (agent service URL)
    • token?: string
    • sessionId?: string
    • type?: 'channel' | 'sse'
    • agent?: boolean (connect to WebAgent)
    • onError?: (error: Error) => void. [1]
  • Returns: { transport, sessionId }. [1]
  • Examples: connecting with a MessageChannel transport, connecting to a remote WebAgent (streamable HTTP by default) or using SSE via type: 'sse', and passing sessionId or onError callbacks. [1][2]

Sources:
[1] OpenTiny NEXT docs — WebMcpClient class (connect).
[2] @opentiny/next-sdk package info / examples (npm / community guides).


Remove the duplicate client.connect() call on line 63.

The WebMcpClient.connect() method accepts either a Transport directly or a ClientConnectOptions object—not both sequentially. The current code calls connect() twice, which violates the API contract. Keep only line 64's call with the options object, or refactor to pass the transport through the options object if needed.

🤖 Prompt for AI Agents
In @template/tinyvue/src/App.vue around lines 63 - 64, The code calls
client.connect() twice (first with clientTransport then with an options object),
which violates WebMcpClient.connect()’s contract; remove the duplicate first
call and only call client.connect with the ClientConnectOptions object (ensuring
the transport is passed via that options object if needed) so the single call
returns the sessionId used as sessionID.

agent: true,
// sessionId 为可选参数。若传入该参数,系统将使用指定值作为会话标识;若未传入,WebAgent 服务将自动生成一个随机的字符串作为 sessionId。为便于通过 MCP Inspector 工具进行调试,此处采用了固定的 sessionId。用户亦可通过浏览器原生提供的 crypto.randomUUID() 方法生成随机字符串作为会话标识。
sessionId: 'd299a869-c674-4125-a84b-bb4e24079b99',
url: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
Comment on lines +68 to +70
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

Consider removing hardcoded sessionId for production.

The hardcoded sessionId on line 68 is noted as being for debugging convenience with MCP Inspector. However, using a fixed session identifier could cause conflicts when multiple users or browser tabs attempt to use the same session simultaneously, leading to unpredictable behavior.

Consider:

  • Using crypto.randomUUID() (as mentioned in the comment) for production
  • Making this configurable via environment variable
  • Only using the hardcoded value in development mode
♻️ Proposed fix
+  const MCP_URL = import.meta.env.VITE_MCP_URL || 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
+  const DEBUG_SESSION_ID = import.meta.env.DEV ? 'd299a869-c674-4125-a84b-bb4e24079b99' : crypto.randomUUID()

Then update the connect call:

       const { sessionId: sessionID } = await client.connect({
         agent: true,
-        // sessionId 为可选参数。若传入该参数,系统将使用指定值作为会话标识;若未传入,WebAgent 服务将自动生成一个随机的字符串作为 sessionId。为便于通过 MCP Inspector 工具进行调试,此处采用了固定的 sessionId。用户亦可通过浏览器原生提供的 crypto.randomUUID() 方法生成随机字符串作为会话标识。
-        sessionId: 'd299a869-c674-4125-a84b-bb4e24079b99',
-        url: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
+        sessionId: DEBUG_SESSION_ID,
+        url: MCP_URL
       })

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @template/tinyvue/src/App.vue around lines 68 - 70, The hardcoded sessionId
property (sessionId) in App.vue should not be used in production because it can
cause session collisions; change the initialization so that in production it
generates a unique id (e.g., via crypto.randomUUID()), allow overriding via an
environment/config variable, and keep the hardcoded value only when running in
development mode; update any call that uses this value (the connect call that
passes sessionId) to use the new computed/configured sessionId instead.

})
sessionId.value = sessionID
})
Comment on lines +59 to +73
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

Add error handling for MCP connection failures.

The onMounted hook performs asynchronous MCP client operations without any error handling. If the connection fails, users will experience silent failures with no feedback.

🛡️ Proposed fix with error handling
   onMounted(async () => {
+    try {
+      // 创建 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: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
+      })
+      sessionId.value = sessionID
+    } catch (error) {
+      console.error('Failed to initialize MCP client:', error)
+      // Consider showing user-facing error notification
+    }
-    // 创建 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: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
-    })
-    sessionId.value = sessionID
   })
📝 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
onMounted(async () => {
// 创建 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: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
})
sessionId.value = sessionID
})
onMounted(async () => {
try {
// 创建 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: 'https://agent.opentiny.design/api/v1/webmcp-trial/mcp'
})
sessionId.value = sessionID
} catch (error) {
console.error('Failed to initialize MCP client:', error)
// Consider showing user-facing error notification
}
})
🤖 Prompt for AI Agents
In @template/tinyvue/src/App.vue around lines 59 - 73, The onMounted block
performing asynchronous MCP operations (creating WebMcpClient and calling
client.connect) lacks error handling; wrap the async sequence starting at
WebMcpClient instantiation and both client.connect calls in a try/catch, only
assign sessionId.value on success, call process/UI-facing error reporting (e.g.,
console.error or a reactive error state to show a message), and in the catch
ensure the client is cleaned up/closed if partially connected to avoid leaks;
reference the onMounted callback, WebMcpClient, client.connect, and sessionId
for locating and applying the fix.

</script>

<style lang="less" scoped>
Expand Down
4 changes: 4 additions & 0 deletions template/tinyvue/src/assets/style/global.less
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ body {

.tiny-button {
--tv-Button-min-width: unset;
}

.tr-container {
z-index: 999 !important;
}
4 changes: 4 additions & 0 deletions template/tinyvue/src/utils/base-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export function isCommonError(error: unknown): error is CommonError {
);
}

export const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms))
}

export default class BaseUtils {
public static getErrorMessage(error: unknown): string {
return this.convertToCommonError(error).message;
Expand Down
37 changes: 36 additions & 1 deletion template/tinyvue/src/views/locale/components/add-locale.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@
Option as TinyOption,
Popover as TinyPopover,
} from '@opentiny/vue';
import { computed, reactive, ref, watch } from 'vue';
import { WebMcpServer, z } from '@opentiny/next-sdk'
import { computed, reactive, ref, watch, onMounted, inject } from 'vue';
import { useI18n } from 'vue-i18n';
import { sleep } from '@/utils/base-utils';
import langTable from './lang-table.vue';
const emits = defineEmits<{
Expand Down Expand Up @@ -216,6 +218,39 @@
setLangTableClose()
}
})
onMounted(async () => {
const server = new WebMcpServer({
name: 'i18n-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject<any>('serverTransport')
server.registerTool(
'add-i18n-entry',
{
title: '添加国际化词条',
description: '添加国际化词条',
inputSchema: {
key: z.string().describe('词条关键字'),
content: z.string().describe('词条内容'),
lang: z.union([z.literal(1), z.literal(2)]).describe('词条语言ID,英文 enUS 为:1,中文 zhCN 为:2'),
}
},
async ({ key, content, lang }) => {
onOpen()
await sleep(1000)
locale.key = key
locale.content = content
locale.lang = lang
await sleep(1000)
addLocale()
return { content: [{ type: 'text', text: `收到: ${key}` }] }
}
)
await server.connect(serverTransport)
});
Comment on lines +222 to +253
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

🏁 Script executed:

# Check if Transport type is exported from @opentiny/next-sdk
rg -nP "export.*Transport" node_modules/@opentiny/next-sdk -A 2

Repository: opentiny/tiny-pro

Length of output: 132


🏁 Script executed:

# Also check the actual file to verify it exists and code matches
if [ -f "template/tinyvue/src/views/locale/components/add-locale.vue" ]; then
  echo "=== File exists. Checking lines 222-253 ==="
  sed -n '222,253p' "template/tinyvue/src/views/locale/components/add-locale.vue"
else
  echo "File not found at template/tinyvue/src/views/locale/components/add-locale.vue"
  # Try to find it
  find . -name "add-locale.vue" -type f 2>/dev/null
fi

Repository: opentiny/tiny-pro

Length of output: 994


🏁 Script executed:

# Check if info-tab.vue exists and has similar MCP server setup pattern
find . -name "info-tab.vue" -type f 2>/dev/null | head -5

Repository: opentiny/tiny-pro

Length of output: 388


🏁 Script executed:

# Check if @opentiny/next-sdk is in package.json
cat package.json | grep -A 5 -B 5 "@opentiny/next-sdk"

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

# Check one info-tab.vue file to see similar MCP server pattern
head -n 400 "./template/tinyvue/src/views/menu/info/components/info-tab.vue" | grep -A 50 "onMounted"

Repository: opentiny/tiny-pro

Length of output: 3091


🏁 Script executed:

# Check package.json file itself for @opentiny/next-sdk and dependencies section
head -n 100 package.json | tail -n 50

Repository: opentiny/tiny-pro

Length of output: 2327


🏁 Script executed:

# Search for Transport type usage in the codebase
rg "Transport" --type vue --type ts --type js | head -20

Repository: opentiny/tiny-pro

Length of output: 88


🏁 Script executed:

# Search for Transport type usage more broadly
rg "Transport" --type ts --type js | head -20

Repository: opentiny/tiny-pro

Length of output: 880


🏁 Script executed:

# Check full package.json dependencies for @opentiny/next-sdk and related packages
rg "@opentiny" package.json

Repository: opentiny/tiny-pro

Length of output: 172


🏁 Script executed:

# Look for WebMcpServer import to understand where Transport might come from
rg "WebMcpServer|import.*Transport" -A 2 -B 2 | head -30

Repository: opentiny/tiny-pro

Length of output: 2406


🏁 Script executed:

# Check App.vue to see how serverTransport is typed and used
rg -A 10 "createMessageChannelPairTransport" "template/tinyvue/src/App.vue"

Repository: opentiny/tiny-pro

Length of output: 991


🏁 Script executed:

# Look for type definitions or interfaces related to Transport in the codebase
rg "type.*Transport|interface.*Transport" --type ts -A 3

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

# Check if there's a type definition file or declaration in node_modules or src
find . -name "*.d.ts" -o -name "*.ts" | xargs grep -l "Transport" 2>/dev/null | head -10

Repository: opentiny/tiny-pro

Length of output: 43


Add error handling and improve type safety.

The MCP server setup mirrors the pattern in info-tab.vue and has similar concerns:

  1. Type safety: Line 227 uses any for serverTransport. This should use a proper type instead of any.

  2. Hard-coded delays: Lines 242 and 246 use fixed 1000ms delays. Extract these as named constants.

  3. Missing error handling: No try-catch blocks around async operations. Both the tool handler (lines 240-248) and server connection (line 252) should handle potential errors.

  4. UI orchestration: The tool handler directly calls onOpen() and addLocale(), tightly coupling MCP logic to UI implementation.

These same issues appear in info-tab.vue (menu-management-mcp-server setup).

Proposed improvements
+const MCP_UI_DELAY = 1000; // ms
+
 onMounted(async () => {
+  try {
     const server = new WebMcpServer({
       name: 'i18n-management-mcp-server',
       version: '1.0.0'
     })
-    const serverTransport = inject<any>('serverTransport')
+    const serverTransport = inject('serverTransport')
+    if (!serverTransport) {
+      console.error('serverTransport not provided')
+      return
+    }

     server.registerTool(
       'add-i18n-entry',
       {
         title: '添加国际化词条',
         description: '添加国际化词条',
         inputSchema: {
           key: z.string().describe('词条关键字'),
           content: z.string().describe('词条内容'),
           lang: z.union([z.literal(1), z.literal(2)]).describe('词条语言ID,英文 enUS 为:1,中文 zhCN 为:2'),
         }
       },
       async ({ key, content, lang }) => {
+        try {
           onOpen()
-          await sleep(1000)
+          await sleep(MCP_UI_DELAY)
           locale.key = key
           locale.content = content
           locale.lang = lang
-          await sleep(1000)
+          await sleep(MCP_UI_DELAY)
           addLocale()
           return { content: [{ type: 'text', text: `收到: ${key}` }] }
+        } catch (error) {
+          console.error('Failed to add i18n entry via MCP:', error)
+          return { 
+            content: [{ 
+              type: 'text', 
+              text: `添加词条失败: ${error instanceof Error ? error.message : String(error)}` 
+            }],
+            isError: true
+          }
+        }
       }
     )

     await server.connect(serverTransport)
+    console.log('i18n MCP server connected successfully')
+  } catch (error) {
+    console.error('Failed to initialize i18n MCP server:', error)
+  }
 });
📝 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
onMounted(async () => {
const server = new WebMcpServer({
name: 'i18n-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject<any>('serverTransport')
server.registerTool(
'add-i18n-entry',
{
title: '添加国际化词条',
description: '添加国际化词条',
inputSchema: {
key: z.string().describe('词条关键字'),
content: z.string().describe('词条内容'),
lang: z.union([z.literal(1), z.literal(2)]).describe('词条语言ID,英文 enUS 为:1,中文 zhCN 为:2'),
}
},
async ({ key, content, lang }) => {
onOpen()
await sleep(1000)
locale.key = key
locale.content = content
locale.lang = lang
await sleep(1000)
addLocale()
return { content: [{ type: 'text', text: `收到: ${key}` }] }
}
)
await server.connect(serverTransport)
});
const MCP_UI_DELAY = 1000; // ms
onMounted(async () => {
try {
const server = new WebMcpServer({
name: 'i18n-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject('serverTransport')
if (!serverTransport) {
console.error('serverTransport not provided')
return
}
server.registerTool(
'add-i18n-entry',
{
title: '添加国际化词条',
description: '添加国际化词条',
inputSchema: {
key: z.string().describe('词条关键字'),
content: z.string().describe('词条内容'),
lang: z.union([z.literal(1), z.literal(2)]).describe('词条语言ID,英文 enUS 为:1,中文 zhCN 为:2'),
}
},
async ({ key, content, lang }) => {
try {
onOpen()
await sleep(MCP_UI_DELAY)
locale.key = key
locale.content = content
locale.lang = lang
await sleep(MCP_UI_DELAY)
addLocale()
return { content: [{ type: 'text', text: `收到: ${key}` }] }
} catch (error) {
console.error('Failed to add i18n entry via MCP:', error)
return {
content: [{
type: 'text',
text: `添加词条失败: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
}
}
}
)
await server.connect(serverTransport)
console.log('i18n MCP server connected successfully')
} catch (error) {
console.error('Failed to initialize i18n MCP server:', error)
}
});
🤖 Prompt for AI Agents
In template/tinyvue/src/views/locale/components/add-locale.vue around lines
222-253, replace the loosely-typed inject<any>('serverTransport') with the
correct transport type (use the MCP transport interface/type used elsewhere,
e.g., WebMcpTransport or the same type from info-tab.vue) to improve type
safety; extract the hard-coded 1000ms sleeps into named constants (e.g.,
HANDLER_DELAY_MS) at the top of the module and reference those constants; wrap
the async tool handler and the await server.connect(...) call in try-catch
blocks and log/return meaningful errors instead of letting exceptions bubble;
and decouple UI operations from the MCP handler by emitting an event or calling
a small adapter function that maps the handler result to UI actions
(onOpen/addLocale) so the handler does not directly call UI functions.

</script>

<style scoped lang="less">
Expand Down
10 changes: 10 additions & 0 deletions template/tinyvue/src/views/menu/info/components/add-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@
: Number(menuInfo.parentId),
};
},
setMenuInfo: (data: Omit<CreateMenuDto, 'id'>) => {
menuInfo.name = data.name;
menuInfo.path = data.path;
menuInfo.component = data.component;
menuInfo.icon = data.icon;
menuInfo.menuType = data.menuType;
menuInfo.parentId = data.parentId;
menuInfo.order = data.order;
menuInfo.locale = data.locale;
},
valid: async () => {
return menuForm.value.validate();
},
Expand Down
46 changes: 45 additions & 1 deletion template/tinyvue/src/views/menu/info/components/info-tab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Modal as TinyModal,
Loading,
} from '@opentiny/vue';
import { WebMcpServer, z } from '@opentiny/next-sdk'
import { getAllLocalItems } from '@/api/local';
import useLoading from '@/hooks/loading';
import {
Expand All @@ -26,6 +27,7 @@
import menuTree, { Node } from './menu-tree.vue';
import UpdateForm from './update-form.vue';
import AddMenu from './add-menu.vue';
import { sleep } from '@/utils/base-utils';
const { modalSize } = useResponsiveSize()
Expand Down Expand Up @@ -241,10 +243,52 @@
fetchLocalItems();
});
onMounted(() => {
onMounted(async () => {
Promise.all([fetchMenu(), fetchLocalItems()]).finally(() => {
treeLoading.value = false;
});
const server = new WebMcpServer({
name: 'menu-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject<any>('serverTransport')
Comment on lines +246 to +255
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and view the relevant lines
if [ -f "template/tinyveo/src/views/menu/info/components/info-tab.vue" ]; then
  sed -n '246,255p' "template/tinyveo/src/views/menu/info/components/info-tab.vue"
else
  echo "Checking for correct file path..."
  find . -name "info-tab.vue" -type f 2>/dev/null | head -5
fi

Repository: opentiny/tiny-pro

Length of output: 178


🏁 Script executed:

# Check if @opentiny/next-sdk is in the project dependencies
find . -name "package.json" -type f | xargs grep -l "@opentiny/next-sdk" 2>/dev/null | head -5

Repository: opentiny/tiny-pro

Length of output: 92


🏁 Script executed:

# Check the node_modules for Transport type export from @opentiny/next-sdk
if [ -d "node_modules/@opentiny/next-sdk" ]; then
  echo "=== Checking for Transport type ==="
  find node_modules/@opentiny/next-sdk -name "*.d.ts" -o -name "*.ts" | xargs grep -l "export.*Transport" 2>/dev/null | head -5
else
  echo "@opentiny/next-sdk not found in node_modules"
fi

Repository: opentiny/tiny-pro

Length of output: 105


🏁 Script executed:

# View the file content at the specified lines
sed -n '240,260p' template/tinyvue/src/views/menu/info/components/info-tab.vue

Repository: opentiny/tiny-pro

Length of output: 525


🏁 Script executed:

# Search for Transport type definition in the codebase
rg "type.*Transport|export.*Transport" --type ts --type vue -i

Repository: opentiny/tiny-pro

Length of output: 88


🏁 Script executed:

# Check imports in the info-tab.vue file to understand what's already imported
head -30 template/tinyvue/src/views/menu/info/components/info-tab.vue

Repository: opentiny/tiny-pro

Length of output: 1117


🏁 Script executed:

# Check the package.json to see what version of @opentiny/next-sdk is used
rg "@opentiny/next-sdk" template/tinyvue/package.json -A 1 -B 1

Repository: opentiny/tiny-pro

Length of output: 170


🏁 Script executed:

# Search for all imports from @opentiny/next-sdk to see what types are available
rg "from ['\"]@opentiny/next-sdk['\"]" -A 3 -B 1

Repository: opentiny/tiny-pro

Length of output: 1562


🏁 Script executed:

# Search for Transport or transport-related types in the codebase
rg -i "transport" template/tinyvue/src/views/menu/info/components/info-tab.vue -B 2 -A 2

Repository: opentiny/tiny-pro

Length of output: 243


🏁 Script executed:

# Check if there are any type definitions or .d.ts files in the repo that might define Transport
find . -name "*.d.ts" -type f | xargs grep -l "Transport" 2>/dev/null | head -10

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

# Search for how serverTransport is actually used after injection
sed -n '255,280p' template/tinyvue/src/views/menu/info/components/info-tab.vue

Repository: opentiny/tiny-pro

Length of output: 880


🏁 Script executed:

# Check how createMessageChannelPairTransport is used in App.vue to understand transport types
sed -n '1,50p' template/tinyvue/src/App.vue | grep -A 10 "createMessageChannelPairTransport"

Repository: opentiny/tiny-pro

Length of output: 469


🏁 Script executed:

# Search for server.connect usage patterns
rg "server\.connect" -B 3 -A 3

Repository: opentiny/tiny-pro

Length of output: 1058


🏁 Script executed:

# Search for any transport-related type definitions or interfaces in the codebase
rg "transport" template/tinyvue/src -i --type ts --type vue | head -30

Repository: opentiny/tiny-pro

Length of output: 88


🏁 Script executed:

# Check if there's a web_search to verify what's exported from @opentiny/next-sdk
# We'll search the package documentation

Repository: opentiny/tiny-pro

Length of output: 43


🌐 Web query:

@opentiny/next-sdk Transport type export documentation

💡 Result:

You can import the Transport type from the package. Example:

import type { Transport } from '@opentiny/next-sdk'

(See the WebMcpClient connect signature which uses Transport in the OpenTiny NEXT docs.) [1]

Sources

  • OpenTiny NEXT — WebMcpClient (connect signature uses Transport). [1]

Replace any type for serverTransport with proper Transport type.

Line 255 uses any for the injected serverTransport, which bypasses TypeScript's type checking. Import the Transport type from @opentiny/next-sdk and apply it to maintain type safety.

Proposed fix
+import type { Transport } from '@opentiny/next-sdk'

 onMounted(async () => {
   // ...
   const server = new WebMcpServer({
     name: 'menu-management-mcp-server',
     version: '1.0.0'
   })
-  const serverTransport = inject<any>('serverTransport')
+  const serverTransport = inject<Transport>('serverTransport')
+  if (!serverTransport) {
+    console.error('serverTransport not provided')
+    return
+  }
📝 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
onMounted(async () => {
Promise.all([fetchMenu(), fetchLocalItems()]).finally(() => {
treeLoading.value = false;
});
const server = new WebMcpServer({
name: 'menu-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject<any>('serverTransport')
import type { Transport } from '@opentiny/next-sdk'
onMounted(async () => {
Promise.all([fetchMenu(), fetchLocalItems()]).finally(() => {
treeLoading.value = false;
});
const server = new WebMcpServer({
name: 'menu-management-mcp-server',
version: '1.0.0'
})
const serverTransport = inject<Transport>('serverTransport')
if (!serverTransport) {
console.error('serverTransport not provided')
return
}
🤖 Prompt for AI Agents
In template/tinyvue/src/views/menu/info/components/info-tab.vue around lines 246
to 255, the injected serverTransport is typed as any which disables TypeScript
checks; import the Transport type from '@opentiny/next-sdk' (add: import type {
Transport } from '@opentiny/next-sdk') and replace
inject<any>('serverTransport') with inject<Transport>('serverTransport') (or
const serverTransport = inject<Transport>('serverTransport') || /* handle
undefined */) so the injected value is strongly typed and handle the possible
undefined return from inject.

server.registerTool(
'add-menu',
{
title: '添加菜单',
description: '添加菜单',
inputSchema: {
name: z.string().describe('名称'),
order: z.number().describe('优先级').default(0),
parentId: z.number().describe('父菜单ID').optional(),
icon: z.string().describe('图标').optional().default(''),
component: z.string().describe('组件'),
path: z.string().describe('路径'),
locale: z.string().describe('国际化'),
}
},
async ({ name, order, parentId, icon, component, path, locale }) => {
handleAddMenu()
await sleep(1000)
addMenu.value.setMenuInfo({
name,
order,
parentId,
icon,
component,
menuType: "/",
path,
locale,
})
await sleep(1000)
onClickAdd()
return { content: [{ type: 'text', text: `收到: ${name}` }] }
}
)
Comment on lines +257 to +289
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

Add error handling and make delays configurable.

The MCP tool registration has several concerns:

  1. Hard-coded delays: Lines 274 and 285 use fixed 1000ms delays. These magic numbers make the code harder to maintain and test. Consider extracting them as named constants or making them configurable.

  2. Missing error handling: The tool handler lacks try-catch blocks. If handleAddMenu(), setMenuInfo(), or onClickAdd() fail, the error will propagate uncaught and the tool will return an incomplete response.

  3. UI orchestration in tool handler: The handler directly manipulates UI state (handleAddMenu(), onClickAdd()), which tightly couples the MCP tool to the component's internal implementation. This pattern may be difficult to test and maintain.

Proposed improvements
+const MCP_UI_DELAY = 1000; // ms - delay for UI updates to be visible
+
 onMounted(async () => {
   // ... server setup ...
   
   server.registerTool(
     'add-menu',
     {
       title: '添加菜单',
       description: '添加菜单',
       inputSchema: {
         name: z.string().describe('名称'),
         order: z.number().describe('优先级').default(0),
         parentId: z.number().describe('父菜单ID').optional(),
         icon: z.string().describe('图标').optional().default(''),
         component: z.string().describe('组件'),
         path: z.string().describe('路径'),
         locale: z.string().describe('国际化'),
       }
     },
     async ({ name, order, parentId, icon, component, path, locale }) => {
+      try {
         handleAddMenu()
-        await sleep(1000)
+        await sleep(MCP_UI_DELAY)
         addMenu.value.setMenuInfo({
           name,
           order,
           parentId,
           icon,
           component,
           menuType: "/",
           path,
           locale,
         })
-        await sleep(1000)
+        await sleep(MCP_UI_DELAY)
         onClickAdd()
         return { content: [{ type: 'text', text: `收到: ${name}` }] }
+      } catch (error) {
+        console.error('Failed to add menu via MCP:', error)
+        return { 
+          content: [{ 
+            type: 'text', 
+            text: `添加菜单失败: ${error instanceof Error ? error.message : String(error)}` 
+          }],
+          isError: true
+        }
+      }
     }
   )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
template/tinyvue/src/views/menu/info/components/info-tab.vue lines 257-289: the
MCP tool handler uses hard-coded 1000ms sleeps, has no error handling, and
directly manipulates component UI state; extract the delays into named constants
or a configurable prop (e.g., ADD_MENU_DELAY_MS) and replace literal 1000 with
that constant/prop, wrap the entire handler body in a try-catch that
logs/returns a clear error response on failure, and stop tightly coupling to
internal UI functions by emitting component events or calling injected callbacks
instead of directly invoking handleAddMenu() and onClickAdd() so the tool only
triggers abstracted actions and returns success/failure after setMenuInfo and
the delay complete.

await server.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

Add error handling for server connection.

The server.connect() call on line 291 lacks error handling. If the connection fails, the MCP server won't be available and tools won't be accessible, but the component will continue functioning normally without any indication of the problem.

Add try-catch for server connection
-  await server.connect(serverTransport)
+  try {
+    await server.connect(serverTransport)
+    console.log('MCP server connected successfully')
+  } catch (error) {
+    console.error('Failed to connect MCP server:', error)
+    // Consider showing a user notification or disabling MCP features
+  }
 });
📝 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
await server.connect(serverTransport)
try {
await server.connect(serverTransport)
console.log('MCP server connected successfully')
} catch (error) {
console.error('Failed to connect MCP server:', error)
// Consider showing a user notification or disabling MCP features
}
🤖 Prompt for AI Agents
In template/tinyvue/src/views/menu/info/components/info-tab.vue around line 291,
the await server.connect(serverTransport) call lacks error handling; wrap the
connect call in a try-catch, catch and log the error (use existing logger or
console.error), update component state to reflect connection failure (e.g., set
a boolean like serverConnected = false or set an error message reactive
property), and surface a user-visible notification or disable dependent UI/tools
so users know the MCP server is unavailable.

});
</script>

Expand Down
Loading