fix(mcp): honor callback port in debug (#39259)
This commit is contained in:
@@ -1,44 +1,57 @@
|
||||
import { Client, type Tool as MCPToolDef } from "@modelcontextprotocol/client"
|
||||
import { Client, type CallToolResult, type Tool as MCPToolDef } from "@modelcontextprotocol/client"
|
||||
import { dynamicTool, jsonSchema, type JSONSchema7, type Tool } from "ai"
|
||||
import { Effect } from "effect"
|
||||
|
||||
const DEFAULT_TIMEOUT = 30_000
|
||||
|
||||
export interface McpTool {
|
||||
readonly def: MCPToolDef
|
||||
readonly client: Client
|
||||
readonly timeout?: number
|
||||
}
|
||||
|
||||
export async function callTool(
|
||||
tool: McpTool,
|
||||
args: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CallToolResult> {
|
||||
const result = await tool.client.callTool(
|
||||
{ name: tool.def.name, arguments: args },
|
||||
{
|
||||
resetTimeoutOnProgress: true,
|
||||
signal,
|
||||
timeout: tool.timeout,
|
||||
// The MCP SDK only sends a progress token when this hook is present, enabling timeout resets.
|
||||
onprogress: () => {},
|
||||
},
|
||||
)
|
||||
if (result.isError)
|
||||
throw new Error(
|
||||
result.content
|
||||
.flatMap((item) => (item.type === "text" ? [item.text] : []))
|
||||
.filter((text) => text.trim())
|
||||
.join("\n\n") || "MCP tool returned an error",
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
export function defs(client: Client, timeout?: number) {
|
||||
return listTools(client, timeout ?? DEFAULT_TIMEOUT).pipe(Effect.catch(() => Effect.void))
|
||||
}
|
||||
|
||||
export function convertTool(mcpTool: MCPToolDef, client: Client, timeout?: number): Tool {
|
||||
export function convertTool(tool: McpTool): Tool {
|
||||
const inputSchema: JSONSchema7 = {
|
||||
...(mcpTool.inputSchema as JSONSchema7),
|
||||
...(tool.def.inputSchema as JSONSchema7),
|
||||
type: "object",
|
||||
properties: (mcpTool.inputSchema.properties ?? {}) as JSONSchema7["properties"],
|
||||
properties: (tool.def.inputSchema.properties ?? {}) as JSONSchema7["properties"],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
return dynamicTool({
|
||||
description: mcpTool.description ?? "",
|
||||
description: tool.def.description ?? "",
|
||||
inputSchema: jsonSchema(inputSchema),
|
||||
execute: async (args: unknown, options) => {
|
||||
const result = await client.callTool(
|
||||
{
|
||||
name: mcpTool.name,
|
||||
arguments: (args || {}) as Record<string, unknown>,
|
||||
},
|
||||
{
|
||||
resetTimeoutOnProgress: true,
|
||||
signal: options.abortSignal,
|
||||
timeout,
|
||||
// The MCP SDK only sends a progress token when this hook is present, enabling timeout resets.
|
||||
onprogress: () => {},
|
||||
},
|
||||
)
|
||||
if (result.isError)
|
||||
throw new Error(
|
||||
result.content
|
||||
.flatMap((item) => (item.type === "text" ? [item.text] : []))
|
||||
.filter((text) => text.trim())
|
||||
.join("\n\n") || "MCP tool returned an error",
|
||||
)
|
||||
const result = await callTool(tool, (args || {}) as Record<string, unknown>, options.abortSignal)
|
||||
if (result.content.length > 0 || result.structuredContent === undefined || result.structuredContent === null)
|
||||
return result
|
||||
return {
|
||||
|
||||
@@ -162,12 +162,7 @@ export interface ServerInstructions {
|
||||
}
|
||||
|
||||
/** An MCP tool in its native shape; consumers adapt it to their own tool format. */
|
||||
export interface McpTool {
|
||||
/** Shared cached definition; consumers must copy rather than mutate it. */
|
||||
readonly def: MCPToolDef
|
||||
readonly client: MCPClient
|
||||
readonly timeout?: number
|
||||
}
|
||||
export type McpTool = McpCatalog.McpTool
|
||||
|
||||
export interface Interface {
|
||||
readonly status: () => Effect.Effect<Record<string, Status>>
|
||||
|
||||
Reference in New Issue
Block a user