Skip to content

Commit ea2b30e

Browse files
committed
fix(stage-ui): outdated for mcp.ts
1 parent 5bbf955 commit ea2b30e

1 file changed

Lines changed: 154 additions & 40 deletions

File tree

  • packages/stage-ui/src/tools

packages/stage-ui/src/tools/mcp.ts

Lines changed: 154 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,159 @@
1+
import type { Tool } from '@xsai/shared-chat'
2+
3+
import { errorMessageFrom } from '@moeru/std'
14
import { tool } from '@xsai/tool'
25
import { z } from 'zod'
36

4-
import { getMcpToolBridge } from '../stores/mcp-tool-bridge'
5-
6-
const tools = [
7-
tool({
8-
name: 'builtIn_mcpListTools',
9-
description: 'List all available MCP tools. Call this first to discover tool names before calling builtIn_mcpCallTool.',
10-
execute: async () => {
11-
try {
12-
return await getMcpToolBridge().listTools()
13-
}
14-
catch (error) {
15-
console.warn('[builtIn_mcpListTools] failed to list tools:', error)
16-
return ''
17-
}
18-
},
19-
parameters: z.object({}).strict(),
20-
}),
21-
tool({
22-
name: 'builtIn_mcpCallTool',
23-
description: 'Call an MCP tool by name. Use builtIn_mcpListTools first to get available tool names.',
24-
execute: async ({ name, arguments: argsJson }) => {
25-
try {
26-
const args = argsJson ? JSON.parse(argsJson) : {}
27-
return await getMcpToolBridge().callTool({ name, arguments: args })
28-
}
29-
catch (error) {
30-
return {
31-
isError: true,
32-
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
7+
/**
8+
* Describes an MCP tool that can be exposed to the shared LLM runtime.
9+
*
10+
* Use when:
11+
* - A runtime needs to list available MCP tools before exposing them to models
12+
*
13+
* Expects:
14+
* - `name` is the fully-qualified tool name used for invocation
15+
*
16+
* Returns:
17+
* - The MCP tool descriptor metadata reported by the runtime
18+
*/
19+
export interface McpToolDescriptor {
20+
serverName: string
21+
name: string
22+
toolName: string
23+
description?: string
24+
inputSchema: Record<string, unknown>
25+
}
26+
27+
/**
28+
* Payload for invoking an MCP tool through a runtime-specific transport.
29+
*
30+
* Use when:
31+
* - A runtime needs to forward a tool invocation into the MCP layer
32+
*
33+
* Expects:
34+
* - `name` matches a descriptor returned from `listTools`
35+
* - `arguments` is a JSON-compatible object when provided
36+
*
37+
* Returns:
38+
* - The MCP tool call input envelope
39+
*/
40+
export interface McpCallToolPayload {
41+
name: string
42+
arguments?: Record<string, unknown>
43+
}
44+
45+
/**
46+
* Result returned from an MCP tool invocation.
47+
*
48+
* Use when:
49+
* - An MCP runtime returns tool output back to the shared LLM layer
50+
*
51+
* Expects:
52+
* - Error responses set `isError` when the tool execution failed
53+
*
54+
* Returns:
55+
* - Structured and unstructured MCP tool output
56+
*/
57+
export interface McpCallToolResult {
58+
content?: Array<Record<string, unknown>>
59+
structuredContent?: Record<string, unknown>
60+
toolResult?: unknown
61+
isError?: boolean
62+
}
63+
64+
/**
65+
* Runtime contract for wiring MCP tool discovery and execution into `stage-ui`.
66+
*
67+
* Use when:
68+
* - A concrete runtime such as Electron needs to provide MCP access without a singleton bridge
69+
*
70+
* Expects:
71+
* - `listTools` and `callTool` are safe to call multiple times
72+
*
73+
* Returns:
74+
* - An object that can back `createMcpTools`
75+
*/
76+
export interface McpToolRuntime {
77+
listTools: () => Promise<McpToolDescriptor[]>
78+
callTool: (payload: McpCallToolPayload) => Promise<McpCallToolResult>
79+
}
80+
81+
/**
82+
* Creates MCP proxy tools backed by a runtime-provided transport.
83+
*
84+
* Use when:
85+
* - A runtime wants to register MCP tools into the shared LLM tool store
86+
*
87+
* Expects:
88+
* - The runtime implements the `McpToolRuntime` contract
89+
*
90+
* Returns:
91+
* - xsai tool definition promises for MCP listing and invocation
92+
*/
93+
export function createMcpTools(runtime: McpToolRuntime): Array<Promise<Tool>> {
94+
return [
95+
tool({
96+
name: 'builtIn_mcpListTools',
97+
description: 'List all available MCP tools. Call this first to discover tool names before calling builtIn_mcpCallTool.',
98+
execute: async () => {
99+
try {
100+
return await runtime.listTools()
33101
}
34-
}
102+
catch (error) {
103+
console.warn('[builtIn_mcpListTools] failed to list tools:', error)
104+
return ''
105+
}
106+
},
107+
parameters: z.object({}).strict(),
108+
}),
109+
tool({
110+
name: 'builtIn_mcpCallTool',
111+
description: 'Call an MCP tool by name. Use builtIn_mcpListTools first to get available tool names.',
112+
execute: async ({ name, arguments: argsJson }) => {
113+
try {
114+
const args = argsJson ? JSON.parse(argsJson) : {}
115+
return await runtime.callTool({ name, arguments: args })
116+
}
117+
catch (error) {
118+
return {
119+
isError: true,
120+
content: [{ type: 'text', text: errorMessageFrom(error) ?? String(error) }],
121+
}
122+
}
123+
},
124+
// NOTICE: `arguments` is z.string() (JSON) because z.unknown() produces `{}` (no `type` key)
125+
// and z.record() emits `propertyNames`, both rejected by OpenAI.
126+
parameters: z.object({
127+
name: z.string().describe('Tool name in "<serverName>::<toolName>" format'),
128+
arguments: z.string().describe('JSON object of tool arguments, e.g. {"query":"hello","limit":10}'),
129+
}).strict(),
130+
}),
131+
]
132+
}
133+
134+
function createUnavailableMcpToolRuntime(): McpToolRuntime {
135+
return {
136+
async listTools() {
137+
throw new Error('MCP tools are not available in this runtime.')
138+
},
139+
async callTool() {
140+
throw new Error('MCP tools are not available in this runtime.')
35141
},
36-
// NOTICE: `arguments` is z.string() (JSON) because z.unknown() produces `{}` (no `type` key)
37-
// and z.record() emits `propertyNames`, both rejected by OpenAI.
38-
parameters: z.object({
39-
name: z.string().describe('Tool name in "<serverName>::<toolName>" format'),
40-
arguments: z.string().describe('JSON object of tool arguments, e.g. {"query":"hello","limit":10}'),
41-
}).strict(),
42-
}),
43-
]
44-
45-
export const mcp = async () => Promise.all(tools)
142+
}
143+
}
144+
145+
/**
146+
* Builds the default stage-ui MCP tool set without depending on runtime singletons.
147+
*
148+
* Use when:
149+
* - Shared code needs the MCP tool schema before a concrete runtime registers live implementations
150+
*
151+
* Expects:
152+
* - Runtime-specific callers override these tools through `useLlmToolsStore`
153+
*
154+
* Returns:
155+
* - MCP tool definitions with an unavailable-runtime fallback
156+
*/
157+
export async function mcp(): Promise<Tool[]> {
158+
return await Promise.all(createMcpTools(createUnavailableMcpToolRuntime()))
159+
}

0 commit comments

Comments
 (0)