mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构 * fix: 修复类型问题 * chore: 更新版本到 1.3.2 * Add brave as alternative WebSearchTool * fix: 修正顺序 * fix: 修复对穷鬼模式的 auto dream 和 session memory 越过 * feat: 穷鬼模式去除 session-summary * feat: 创建 builtin-tools 包,搬运所有工具实现 将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/, 内部导入路径已更新为 src/ alias 模式。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/ - src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/ - 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock - tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射 - 新增 packages/builtin-tools/src 至 include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀 所有包名及 import 路径统一添加 @claude-code-best/ 前缀: - builtin-tools → @claude-code-best/builtin-tools - mcp-client → @claude-code-best/mcp-client - agent-tools → @claude-code-best/agent-tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 node 环境没有 bun 的问题 --------- Co-authored-by: Eric-Guo <eric.guocz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
182
packages/mcp-client/src/execution.ts
Normal file
182
packages/mcp-client/src/execution.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
// MCP tool execution — call tools on connected MCP servers
|
||||
// Extracted from src/services/mcp/client.ts (callMCPTool)
|
||||
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import type { ConnectedMCPServer } from './types.js'
|
||||
import type { McpClientDependencies } from './interfaces.js'
|
||||
import {
|
||||
McpToolCallError,
|
||||
McpAuthError,
|
||||
} from './errors.js'
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
/** Default timeout for MCP tool calls (~27.8 hours — effectively infinite) */
|
||||
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000
|
||||
|
||||
// ============================================================================
|
||||
// Tool execution
|
||||
// ============================================================================
|
||||
|
||||
export interface CallToolOptions {
|
||||
/** The connected MCP server to call */
|
||||
client: ConnectedMCPServer
|
||||
/** Tool name (as registered on the server, not the fully qualified name) */
|
||||
tool: string
|
||||
/** Tool arguments */
|
||||
args: Record<string, unknown>
|
||||
/** Optional metadata to send with the call */
|
||||
meta?: Record<string, unknown>
|
||||
/** Abort signal for cancellation */
|
||||
signal: AbortSignal
|
||||
/** Progress callback */
|
||||
onProgress?: (data: { progress?: number; total?: number; message?: string }) => void
|
||||
/** Tool call timeout in ms (defaults to ~27.8 hours) */
|
||||
timeoutMs?: number
|
||||
}
|
||||
|
||||
export interface CallToolResult {
|
||||
content: unknown
|
||||
_meta?: Record<string, unknown>
|
||||
structuredContent?: Record<string, unknown>
|
||||
isError?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a tool on a connected MCP server with timeout and progress handling.
|
||||
*
|
||||
* This is the protocol-level tool execution function. The host is responsible for:
|
||||
* - Session management (reconnection on expiry)
|
||||
* - Result transformation (content processing, truncation, persistence)
|
||||
* - Error wrapping for telemetry
|
||||
*/
|
||||
export async function callMcpTool(
|
||||
options: CallToolOptions,
|
||||
deps: McpClientDependencies,
|
||||
): Promise<CallToolResult> {
|
||||
const { client, tool, args, meta, signal, onProgress, timeoutMs } = options
|
||||
const { name: serverName, client: mcpClient } = client
|
||||
const effectiveTimeout = timeoutMs ?? getMcpToolTimeoutMs()
|
||||
|
||||
let progressInterval: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
try {
|
||||
deps.logger.debug(`[${serverName}] Calling MCP tool: ${tool}`)
|
||||
|
||||
// Progress logging for long-running tools (every 30 seconds)
|
||||
progressInterval = setInterval(
|
||||
() => {
|
||||
deps.logger.debug(`[${serverName}] Tool '${tool}' still running`)
|
||||
},
|
||||
30_000,
|
||||
)
|
||||
|
||||
const result = await Promise.race([
|
||||
mcpClient.callTool(
|
||||
{
|
||||
name: tool,
|
||||
arguments: args,
|
||||
_meta: meta,
|
||||
},
|
||||
CallToolResultSchema,
|
||||
{
|
||||
signal,
|
||||
timeout: effectiveTimeout,
|
||||
onprogress: onProgress,
|
||||
},
|
||||
),
|
||||
createTimeoutPromise(serverName, tool, effectiveTimeout),
|
||||
])
|
||||
|
||||
// Handle isError in result
|
||||
if ('isError' in result && result.isError) {
|
||||
let errorDetails = 'Unknown error'
|
||||
if (
|
||||
'content' in result &&
|
||||
Array.isArray(result.content) &&
|
||||
result.content.length > 0
|
||||
) {
|
||||
const firstContent = result.content[0]
|
||||
if (
|
||||
firstContent &&
|
||||
typeof firstContent === 'object' &&
|
||||
'text' in firstContent
|
||||
) {
|
||||
errorDetails = (firstContent as { text: string }).text
|
||||
}
|
||||
}
|
||||
|
||||
throw new McpToolCallError(serverName, tool, errorDetails)
|
||||
}
|
||||
|
||||
return {
|
||||
content: result,
|
||||
_meta: result._meta as Record<string, unknown> | undefined,
|
||||
structuredContent: result.structuredContent as
|
||||
| Record<string, unknown>
|
||||
| undefined,
|
||||
}
|
||||
} catch (e) {
|
||||
if (progressInterval !== undefined) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
|
||||
if (e instanceof Error && e.name !== 'AbortError') {
|
||||
deps.logger.debug(
|
||||
`[${serverName}] Tool '${tool}' failed: ${e.message}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Check for 401 errors
|
||||
if (e instanceof Error) {
|
||||
const errorCode = 'code' in e ? (e.code as number | undefined) : undefined
|
||||
if (errorCode === 401) {
|
||||
throw new McpAuthError(
|
||||
serverName,
|
||||
`MCP server "${serverName}" requires re-authorization (token expired)`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
} finally {
|
||||
if (progressInterval !== undefined) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
function getMcpToolTimeoutMs(): number {
|
||||
return (
|
||||
parseInt(process.env.MCP_TOOL_TIMEOUT || '', 10) ||
|
||||
DEFAULT_MCP_TOOL_TIMEOUT_MS
|
||||
)
|
||||
}
|
||||
|
||||
function createTimeoutPromise(
|
||||
serverName: string,
|
||||
tool: string,
|
||||
timeoutMs: number,
|
||||
): Promise<never> {
|
||||
return new Promise((_, reject) => {
|
||||
const timeoutId = setTimeout(
|
||||
() => {
|
||||
reject(
|
||||
new Error(
|
||||
`MCP server "${serverName}" tool "${tool}" timed out after ${Math.floor(timeoutMs / 1000)}s`,
|
||||
),
|
||||
)
|
||||
},
|
||||
timeoutMs,
|
||||
)
|
||||
timeoutId.unref?.()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user