mirror of
https://github.com/hanzoai/code.git
synced 2026-09-30 20:53:35 +00:00
Merge remote-tracking branch 'upstream/mcp' into mcp
This commit is contained in:
@@ -19,6 +19,7 @@ import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js'
|
||||
import { generateUuid } from '../../../../base/common/uuid.js'
|
||||
import { IMCPService, MCPCallTool, MCPToolResultToString } from '../common/mcpService.js'
|
||||
|
||||
|
||||
// tool use for AI
|
||||
@@ -30,6 +31,10 @@ type ValidateParams = { [T in ToolName]: (p: RawToolParamsObj) => ToolCallParams
|
||||
type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise<{ result: ToolResultType[T] | Promise<ToolResultType[T]>, interruptTool?: () => void }> }
|
||||
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awaited<ToolResultType[T]>) => string }
|
||||
|
||||
// Interfaces that accept both internal tools and MCP tools
|
||||
export type ToolHandler = CallTool & MCPCallTool;
|
||||
export type ToolResultToStringHandler = ToolResultToString & MCPToolResultToString
|
||||
|
||||
|
||||
|
||||
const isFalsy = (u: unknown) => {
|
||||
@@ -111,8 +116,8 @@ const checkIfIsFolder = (uriStr: string) => {
|
||||
export interface IToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
validateParams: ValidateParams;
|
||||
callTool: CallTool;
|
||||
stringOfResult: ToolResultToString;
|
||||
callTool: ToolHandler;
|
||||
stringOfResult: ToolResultToStringHandler;
|
||||
}
|
||||
|
||||
export const IToolsService = createDecorator<IToolsService>('ToolsService');
|
||||
@@ -122,8 +127,8 @@ export class ToolsService implements IToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
public validateParams: ValidateParams;
|
||||
public callTool: CallTool;
|
||||
public stringOfResult: ToolResultToString;
|
||||
public callTool: ToolHandler;
|
||||
public stringOfResult: ToolResultToStringHandler;
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -137,6 +142,7 @@ export class ToolsService implements IToolsService {
|
||||
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
|
||||
@IMarkerService private readonly markerService: IMarkerService,
|
||||
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||
@IMCPService private readonly mcpService: IMCPService,
|
||||
) {
|
||||
|
||||
const queryBuilder = instantiationService.createInstance(QueryBuilder);
|
||||
@@ -446,7 +452,8 @@ export class ToolsService implements IToolsService {
|
||||
await this.terminalToolService.killPersistentTerminal(persistentTerminalId)
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
// Returns MCP server call tool functions
|
||||
...this.mcpService.getMCPToolFns().callTool,
|
||||
}
|
||||
|
||||
|
||||
@@ -550,7 +557,8 @@ export class ToolsService implements IToolsService {
|
||||
kill_persistent_terminal: (params, _result) => {
|
||||
return `Successfully closed terminal "${params.persistentTerminalId}".`;
|
||||
},
|
||||
|
||||
// All MCP server result to string functions
|
||||
...this.mcpService.getMCPToolFns().resultToString,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IProductService } from '../../../../platform/product/common/productServ
|
||||
import { VSBuffer } from '../../../../base/common/buffer.js';
|
||||
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
|
||||
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
|
||||
import { MCPServerOfName, MCPConfigFileType, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServerEventResponse, MCPServerObject } from './mcpServiceTypes.js';
|
||||
import { MCPServerOfName, MCPConfigFileType, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServerEventResponse, MCPServerObject, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js';
|
||||
import { Event, Emitter } from '../../../../base/common/event.js';
|
||||
import { InternalToolInfo } from './prompt/prompts.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
@@ -32,11 +32,15 @@ export interface IMCPService {
|
||||
readonly _serviceBrand: undefined;
|
||||
revealMCPConfigFile(): Promise<void>;
|
||||
toggleServer(serverName: string, isOn: boolean): Promise<void>;
|
||||
getMCPToolFns(): {
|
||||
callTool: MCPCallTool;
|
||||
resultToString: MCPToolResultToString
|
||||
};
|
||||
|
||||
readonly state: MCPState;
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
getCurrentMCPTools(): InternalToolInfo[];
|
||||
getCurrentMCPToolNames(): InternalToolInfo[];
|
||||
}
|
||||
|
||||
export const IMCPService = createDecorator<IMCPService>('mcpConfigService');
|
||||
@@ -47,6 +51,23 @@ const MCP_CONFIG_FILE_NAME = 'mcp.json';
|
||||
const MCP_CONFIG_SAMPLE = { mcpServers: {} }
|
||||
const MCP_CONFIG_SAMPLE_STRING = JSON.stringify(MCP_CONFIG_SAMPLE, null, 2);
|
||||
|
||||
|
||||
export interface MCPCallTool {
|
||||
[toolName: string]: (params: any) => Promise<{
|
||||
result: any | Promise<any>,
|
||||
interruptTool?: () => void
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MCPToolResultToString {
|
||||
[toolName: string]: (params: any, result: any) => string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MCPService extends Disposable implements IMCPService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -200,8 +221,8 @@ class MCPService extends Disposable implements IMCPService {
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentMCPTools(): InternalToolInfo[] {
|
||||
const allTools = Object.values(this.state.mcpServerOfName).flatMap(server => {
|
||||
public getCurrentMCPToolNames(): InternalToolInfo[] {
|
||||
const allTools = Object.entries(this.state.mcpServerOfName).flatMap(([serverName, server]) => {
|
||||
return server.tools.map(tool => {
|
||||
// Convert JsonSchema to the expected format
|
||||
const convertedParams: { [paramName: string]: { description: string } } = {};
|
||||
@@ -219,6 +240,7 @@ class MCPService extends Disposable implements IMCPService {
|
||||
description: tool.description || '',
|
||||
params: convertedParams,
|
||||
name: tool.name,
|
||||
serverName,
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -284,6 +306,55 @@ class MCPService extends Disposable implements IMCPService {
|
||||
|
||||
this.channel.call('refreshMCPServers', { mcpConfig: mcpConfigFile, serverStates: updatedServers })
|
||||
}
|
||||
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<MCPGenericToolResponse> {
|
||||
const response = await this.channel.call<MCPGenericToolResponse>('callTool', toolData);
|
||||
return response;
|
||||
}
|
||||
|
||||
public getMCPToolFns(): { callTool: MCPCallTool; resultToString: MCPToolResultToString } {
|
||||
const tools = this.getCurrentMCPToolNames();
|
||||
const toolFns: MCPCallTool = {};
|
||||
const toolResultToStringFns: MCPToolResultToString = {};
|
||||
|
||||
tools.forEach((tool) => {
|
||||
const name = tool.name;
|
||||
const serverName = tool.mcpServerName;
|
||||
|
||||
// Define the tool call function
|
||||
const toolFn = async (params: {
|
||||
serverName: string,
|
||||
toolName: string,
|
||||
args: any
|
||||
}) => {
|
||||
const { serverName, toolName, args } = params;
|
||||
const response = await this.callMCPTool({
|
||||
serverName,
|
||||
toolName,
|
||||
params: args,
|
||||
});
|
||||
return {
|
||||
result: response,
|
||||
};
|
||||
};
|
||||
|
||||
// Define the result-to-string function
|
||||
const resultToStringFn = (params: any, result: MCPGenericToolResponse): string => {
|
||||
if (result.event === 'error' || result.event === 'text') {
|
||||
return result.text;
|
||||
}
|
||||
throw new Error(`MCP Server ${serverName} and Tool ${name} returned an unexpected result: ${JSON.stringify(result)}`);
|
||||
};
|
||||
|
||||
toolFns[name] = toolFn;
|
||||
toolResultToStringFns[name] = resultToStringFn;
|
||||
});
|
||||
|
||||
return {
|
||||
callTool: toolFns,
|
||||
resultToString: toolResultToStringFns
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IMCPService, MCPService, InstantiationType.Eager);
|
||||
|
||||
@@ -215,4 +215,60 @@ export interface MCPConfigFileParseErrorResponse {
|
||||
// Event Param union type
|
||||
// export type MCPServerEventParam = MCPServerEventAddParam | MCPServerEventUpdateParam | MCPServerEventDeleteParam | MCPServerEventLoadingParam;
|
||||
|
||||
// TOOL CALL EVENT TYPES ------------------------------------------
|
||||
|
||||
type MCPToolResponseType = 'text' | 'image' | 'audio' | 'resource' | 'error';
|
||||
|
||||
type ResponseImageTypes = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'image/bmp' | 'image/tiff' | 'image/vnd.microsoft.icon';
|
||||
|
||||
interface ImageData {
|
||||
data: string;
|
||||
mimeType: ResponseImageTypes;
|
||||
}
|
||||
|
||||
interface MCPToolResponseBase {
|
||||
toolName: string;
|
||||
serverName?: string;
|
||||
event: MCPToolResponseType;
|
||||
text?: string;
|
||||
image?: ImageData;
|
||||
}
|
||||
|
||||
type MCPToolResponseConstraints = {
|
||||
'text': {
|
||||
image?: never;
|
||||
text: string;
|
||||
};
|
||||
'error': {
|
||||
image?: never;
|
||||
text: string;
|
||||
};
|
||||
'image': {
|
||||
text?: never;
|
||||
image: ImageData;
|
||||
};
|
||||
'audio': {
|
||||
text?: never;
|
||||
image?: never;
|
||||
};
|
||||
'resource': {
|
||||
text?: never;
|
||||
image?: never;
|
||||
}
|
||||
}
|
||||
|
||||
type MCPToolEventResponse<T extends MCPToolResponseType> = Omit<MCPToolResponseBase, 'event' | keyof MCPToolResponseConstraints> & MCPToolResponseConstraints[T] & { event: T };
|
||||
|
||||
// Response types
|
||||
export type MCPToolTextResponse = MCPToolEventResponse<'text'>;
|
||||
export type MCPToolErrorResponse = MCPToolEventResponse<'error'>;
|
||||
export type MCPToolImageResponse = MCPToolEventResponse<'image'>;
|
||||
export type MCPToolAudioResponse = MCPToolEventResponse<'audio'>;
|
||||
export type MCPToolResourceResponse = MCPToolEventResponse<'resource'>;
|
||||
export type MCPGenericToolResponse = MCPToolTextResponse | MCPToolErrorResponse | MCPToolImageResponse | MCPToolAudioResponse | MCPToolResourceResponse;
|
||||
|
||||
export interface MCPToolCallParams {
|
||||
serverName: string;
|
||||
toolName: string;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,8 @@ export type InternalToolInfo = {
|
||||
params: {
|
||||
[paramName: string]: { description: string }
|
||||
},
|
||||
// Only if the tool is from an MCP server
|
||||
mcpServerName?: string,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPServerModel, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse } from '../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPServerModel, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { equals } from '../../../../base/common/objects.js';
|
||||
import { MCPServerStateOfName } from '../common/voidSettingsTypes.js';
|
||||
|
||||
|
||||
|
||||
// const getLoadingServerObject = (serverName: string, isOn: boolean | undefined) => {
|
||||
// return {
|
||||
// response: {
|
||||
@@ -85,6 +85,8 @@ export class MCPChannel implements IServerChannel {
|
||||
else if (event === 'onDelete_server') return this.mcpEmitters.serverEvent.onDelete.event;
|
||||
// else if (event === 'onLoading_server') return this.mcpEmitters.serverEvent.onChangeLoading.event;
|
||||
|
||||
// tool call events
|
||||
|
||||
// handle unknown events
|
||||
else throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
@@ -101,10 +103,10 @@ export class MCPChannel implements IServerChannel {
|
||||
else if (command === 'toggleServer') {
|
||||
await this._toggleServer(params.serverName, params.isOn)
|
||||
}
|
||||
// TODO!!! is this still needed?
|
||||
// else if (command === 'callTool') {
|
||||
// // TODO: HANDLE THIS
|
||||
// }
|
||||
else if (command === 'callTool') {
|
||||
const response = await this._safeCallTool(params.serverName, params.toolName, params.params)
|
||||
return response
|
||||
}
|
||||
else {
|
||||
throw new Error(`Void sendLLM: command "${command}" not recognized.`)
|
||||
}
|
||||
@@ -114,7 +116,7 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
// call functions
|
||||
// server functions
|
||||
|
||||
private async _refreshMCPServers(params: { mcpConfig: MCPConfigFileType, serverStates: MCPServerStateOfName }) {
|
||||
|
||||
@@ -380,6 +382,70 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
// tool call functions
|
||||
|
||||
private async _callTool(serverName: string, toolName: string, params: any): Promise<MCPGenericToolResponse> {
|
||||
const server = this.clients[serverName]
|
||||
if (!server) throw new Error(`Server ${serverName} not found`)
|
||||
const { client } = server
|
||||
if (!client) throw new Error(`Client for server ${serverName} not found`)
|
||||
|
||||
// Call the tool with the provided parameters
|
||||
const response = await client.callTool({
|
||||
name: toolName,
|
||||
arguments: params
|
||||
})
|
||||
const { content } = response as CallToolResult
|
||||
const returnValue = content[0]
|
||||
|
||||
if (returnValue.type === 'text') {
|
||||
// handle text response
|
||||
|
||||
if (response.isError) {
|
||||
throw new Error(`Tool call error: ${response.content}`)
|
||||
// handle error
|
||||
}
|
||||
|
||||
// handle success
|
||||
return {
|
||||
event: 'text',
|
||||
text: returnValue.text,
|
||||
toolName,
|
||||
serverName,
|
||||
}
|
||||
}
|
||||
|
||||
// if (returnValue.type === 'audio') {
|
||||
// // handle audio response
|
||||
// }
|
||||
|
||||
// if (returnValue.type === 'image') {
|
||||
// // handle image response
|
||||
// }
|
||||
|
||||
// if (returnValue.type === 'resource') {
|
||||
// // handle resource response
|
||||
// }
|
||||
|
||||
throw new Error(`Tool call error: We don\'t support ${returnValue.type} tool response yet for tool ${toolName} on server ${serverName}`)
|
||||
}
|
||||
|
||||
// tool call error wrapper
|
||||
private async _safeCallTool(serverName: string, toolName: string, params: any): Promise<MCPGenericToolResponse> {
|
||||
try {
|
||||
const response = await this._callTool(serverName, toolName, params)
|
||||
return response
|
||||
} catch (err) {
|
||||
const fullErrorMessage = `❌ Failed to call tool "${toolName}" on server "${serverName}": ${typeof err === 'string' ? err : JSON.stringify(err, null, 2)}`
|
||||
const errorResponse: MCPToolErrorResponse = {
|
||||
event: 'error',
|
||||
text: fullErrorMessage,
|
||||
toolName,
|
||||
serverName,
|
||||
}
|
||||
return errorResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user