* Execute a tool call on the appropriate MCP server. * @param {string} fullName - Tool name in format mcp__serverName__toolName * @param {object} args - Tool arguments * @returns {object} { result, error }
(fullName, args)
| 101 | * @returns {object} { result, error } |
| 102 | */ |
| 103 | async callTool(fullName, args) { |
| 104 | // Parse mcp__serverName__toolName |
| 105 | const parts = fullName.split('__'); |
| 106 | if (parts.length < 3 || parts[0] !== 'mcp') { |
| 107 | return { error: `Invalid MCP tool name: ${fullName}` }; |
| 108 | } |
| 109 | const serverName = parts[1]; |
| 110 | const toolName = parts.slice(2).join('__'); // Handle tools with __ in name |
| 111 | |
| 112 | const server = this.servers.get(serverName); |
| 113 | if (!server || !server.connected) { |
| 114 | return { error: `MCP server '${serverName}' is not connected` }; |
| 115 | } |
| 116 | |
| 117 | try { |
| 118 | const response = await this._sendRequest(server, 'tools/call', { |
| 119 | name: toolName, |
| 120 | arguments: args, |
| 121 | }); |
| 122 | |
| 123 | if (!response) return { error: `No response from ${serverName}` }; |
| 124 | |
| 125 | const content = response.content || []; |
| 126 | const text = content |
| 127 | .filter(c => c.type === 'text') |
| 128 | .map(c => c.text || '') |
| 129 | .join('\n'); |
| 130 | |
| 131 | if (response.isError) { |
| 132 | return { error: text || 'MCP tool returned error' }; |
| 133 | } |
| 134 | // Sanitize MCP server output before returning — external servers can |
| 135 | // return ANSI escapes, secrets from their own env, or binary garbage. |
| 136 | const { sanitizeToolOutput } = require('../security/sanitize'); |
| 137 | return { result: sanitizeToolOutput(text) || '(no output)' }; |
| 138 | } catch (err) { |
| 139 | return { error: `MCP call failed: ${err.message}` }; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Check if a tool name belongs to an MCP server. |
no test coverage detected