Browse Source

Move use_mcp_tool and access_mcp_resource to tool files (#2101)

Matt Rubens 9 months ago
parent
commit
9b0790fce7
3 changed files with 190 additions and 162 deletions
  1. 13 162
      src/core/Cline.ts
  2. 77 0
      src/core/tools/accessMcpResourceTool.ts
  3. 100 0
      src/core/tools/useMcpToolTool.ts

+ 13 - 162
src/core/Cline.ts

@@ -93,6 +93,8 @@ import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool
 import { searchFilesTool } from "./tools/searchFilesTool"
 import { browserActionTool } from "./tools/browserActionTool"
 import { executeCommandTool } from "./tools/executeCommandTool"
+import { useMcpToolTool } from "./tools/useMcpToolTool"
+import { accessMcpResourceTool } from "./tools/accessMcpResourceTool"
 
 export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
 type UserContent = Array<Anthropic.Messages.ContentBlockParam>
@@ -1630,170 +1632,19 @@ export class Cline extends EventEmitter<ClineEvents> {
 						break
 					}
 					case "use_mcp_tool": {
-						const server_name: string | undefined = block.params.server_name
-						const tool_name: string | undefined = block.params.tool_name
-						const mcp_arguments: string | undefined = block.params.arguments
-						try {
-							if (block.partial) {
-								const partialMessage = JSON.stringify({
-									type: "use_mcp_tool",
-									serverName: removeClosingTag("server_name", server_name),
-									toolName: removeClosingTag("tool_name", tool_name),
-									arguments: removeClosingTag("arguments", mcp_arguments),
-								} satisfies ClineAskUseMcpServer)
-								await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})
-								break
-							} else {
-								if (!server_name) {
-									this.consecutiveMistakeCount++
-									pushToolResult(
-										await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"),
-									)
-									break
-								}
-								if (!tool_name) {
-									this.consecutiveMistakeCount++
-									pushToolResult(
-										await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"),
-									)
-									break
-								}
-								// arguments are optional, but if they are provided they must be valid JSON
-								// if (!mcp_arguments) {
-								// 	this.consecutiveMistakeCount++
-								// 	pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "arguments"))
-								// 	break
-								// }
-								let parsedArguments: Record<string, unknown> | undefined
-								if (mcp_arguments) {
-									try {
-										parsedArguments = JSON.parse(mcp_arguments)
-									} catch (error) {
-										this.consecutiveMistakeCount++
-										await this.say(
-											"error",
-											`Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`,
-										)
-										pushToolResult(
-											formatResponse.toolError(
-												formatResponse.invalidMcpToolArgumentError(server_name, tool_name),
-											),
-										)
-										break
-									}
-								}
-								this.consecutiveMistakeCount = 0
-								const completeMessage = JSON.stringify({
-									type: "use_mcp_tool",
-									serverName: server_name,
-									toolName: tool_name,
-									arguments: mcp_arguments,
-								} satisfies ClineAskUseMcpServer)
-								const didApprove = await askApproval("use_mcp_server", completeMessage)
-								if (!didApprove) {
-									break
-								}
-								// now execute the tool
-								await this.say("mcp_server_request_started") // same as browser_action_result
-								const toolResult = await this.providerRef
-									.deref()
-									?.getMcpHub()
-									?.callTool(server_name, tool_name, parsedArguments)
-
-								// TODO: add progress indicator and ability to parse images and non-text responses
-								const toolResultPretty =
-									(toolResult?.isError ? "Error:\n" : "") +
-										toolResult?.content
-											.map((item) => {
-												if (item.type === "text") {
-													return item.text
-												}
-												if (item.type === "resource") {
-													const { blob, ...rest } = item.resource
-													return JSON.stringify(rest, null, 2)
-												}
-												return ""
-											})
-											.filter(Boolean)
-											.join("\n\n") || "(No response)"
-								await this.say("mcp_server_response", toolResultPretty)
-								pushToolResult(formatResponse.toolResult(toolResultPretty))
-								break
-							}
-						} catch (error) {
-							await handleError("executing MCP tool", error)
-							break
-						}
+						await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)
+						break
 					}
 					case "access_mcp_resource": {
-						const server_name: string | undefined = block.params.server_name
-						const uri: string | undefined = block.params.uri
-						try {
-							if (block.partial) {
-								const partialMessage = JSON.stringify({
-									type: "access_mcp_resource",
-									serverName: removeClosingTag("server_name", server_name),
-									uri: removeClosingTag("uri", uri),
-								} satisfies ClineAskUseMcpServer)
-								await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})
-								break
-							} else {
-								if (!server_name) {
-									this.consecutiveMistakeCount++
-									pushToolResult(
-										await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"),
-									)
-									break
-								}
-								if (!uri) {
-									this.consecutiveMistakeCount++
-									pushToolResult(
-										await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"),
-									)
-									break
-								}
-								this.consecutiveMistakeCount = 0
-								const completeMessage = JSON.stringify({
-									type: "access_mcp_resource",
-									serverName: server_name,
-									uri,
-								} satisfies ClineAskUseMcpServer)
-								const didApprove = await askApproval("use_mcp_server", completeMessage)
-								if (!didApprove) {
-									break
-								}
-								// now execute the tool
-								await this.say("mcp_server_request_started")
-								const resourceResult = await this.providerRef
-									.deref()
-									?.getMcpHub()
-									?.readResource(server_name, uri)
-								const resourceResultPretty =
-									resourceResult?.contents
-										.map((item) => {
-											if (item.text) {
-												return item.text
-											}
-											return ""
-										})
-										.filter(Boolean)
-										.join("\n\n") || "(Empty response)"
-
-								// handle images (image must contain mimetype and blob)
-								let images: string[] = []
-								resourceResult?.contents.forEach((item) => {
-									if (item.mimeType?.startsWith("image") && item.blob) {
-										images.push(item.blob)
-									}
-								})
-								await this.say("mcp_server_response", resourceResultPretty, images)
-								pushToolResult(formatResponse.toolResult(resourceResultPretty, images))
-								break
-							}
-						} catch (error) {
-							await handleError("accessing MCP resource", error)
-							break
-						}
+						await accessMcpResourceTool(
+							this,
+							block,
+							askApproval,
+							handleError,
+							pushToolResult,
+							removeClosingTag,
+						)
+						break
 					}
 					case "ask_followup_question": {
 						const question: string | undefined = block.params.question

+ 77 - 0
src/core/tools/accessMcpResourceTool.ts

@@ -0,0 +1,77 @@
+import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
+import { RemoveClosingTag } from "./types"
+import { ToolUse } from "../assistant-message"
+import { AskApproval, HandleError, PushToolResult } from "./types"
+import { Cline } from "../Cline"
+import { formatResponse } from "../prompts/responses"
+
+export async function accessMcpResourceTool(
+	cline: Cline,
+	block: ToolUse,
+	askApproval: AskApproval,
+	handleError: HandleError,
+	pushToolResult: PushToolResult,
+	removeClosingTag: RemoveClosingTag,
+) {
+	const server_name: string | undefined = block.params.server_name
+	const uri: string | undefined = block.params.uri
+	try {
+		if (block.partial) {
+			const partialMessage = JSON.stringify({
+				type: "access_mcp_resource",
+				serverName: removeClosingTag("server_name", server_name),
+				uri: removeClosingTag("uri", uri),
+			} satisfies ClineAskUseMcpServer)
+			await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})
+			return
+		} else {
+			if (!server_name) {
+				cline.consecutiveMistakeCount++
+				pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "server_name"))
+				return
+			}
+			if (!uri) {
+				cline.consecutiveMistakeCount++
+				pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "uri"))
+				return
+			}
+			cline.consecutiveMistakeCount = 0
+			const completeMessage = JSON.stringify({
+				type: "access_mcp_resource",
+				serverName: server_name,
+				uri,
+			} satisfies ClineAskUseMcpServer)
+			const didApprove = await askApproval("use_mcp_server", completeMessage)
+			if (!didApprove) {
+				return
+			}
+			// now execute the tool
+			await cline.say("mcp_server_request_started")
+			const resourceResult = await cline.providerRef.deref()?.getMcpHub()?.readResource(server_name, uri)
+			const resourceResultPretty =
+				resourceResult?.contents
+					.map((item) => {
+						if (item.text) {
+							return item.text
+						}
+						return ""
+					})
+					.filter(Boolean)
+					.join("\n\n") || "(Empty response)"
+
+			// handle images (image must contain mimetype and blob)
+			let images: string[] = []
+			resourceResult?.contents.forEach((item) => {
+				if (item.mimeType?.startsWith("image") && item.blob) {
+					images.push(item.blob)
+				}
+			})
+			await cline.say("mcp_server_response", resourceResultPretty, images)
+			pushToolResult(formatResponse.toolResult(resourceResultPretty, images))
+			return
+		}
+	} catch (error) {
+		await handleError("accessing MCP resource", error)
+		return
+	}
+}

+ 100 - 0
src/core/tools/useMcpToolTool.ts

@@ -0,0 +1,100 @@
+import { Cline } from "../Cline"
+import { ToolUse } from "../assistant-message"
+import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types"
+import { formatResponse } from "../prompts/responses"
+import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
+
+export async function useMcpToolTool(
+	cline: Cline,
+	block: ToolUse,
+	askApproval: AskApproval,
+	handleError: HandleError,
+	pushToolResult: PushToolResult,
+	removeClosingTag: RemoveClosingTag,
+) {
+	const server_name: string | undefined = block.params.server_name
+	const tool_name: string | undefined = block.params.tool_name
+	const mcp_arguments: string | undefined = block.params.arguments
+	try {
+		if (block.partial) {
+			const partialMessage = JSON.stringify({
+				type: "use_mcp_tool",
+				serverName: removeClosingTag("server_name", server_name),
+				toolName: removeClosingTag("tool_name", tool_name),
+				arguments: removeClosingTag("arguments", mcp_arguments),
+			} satisfies ClineAskUseMcpServer)
+			await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})
+			return
+		} else {
+			if (!server_name) {
+				cline.consecutiveMistakeCount++
+				pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "server_name"))
+				return
+			}
+			if (!tool_name) {
+				cline.consecutiveMistakeCount++
+				pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"))
+				return
+			}
+			// arguments are optional, but if they are provided they must be valid JSON
+			// if (!mcp_arguments) {
+			// 	cline.consecutiveMistakeCount++
+			// 	pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "arguments"))
+			// 	return
+			// }
+			let parsedArguments: Record<string, unknown> | undefined
+			if (mcp_arguments) {
+				try {
+					parsedArguments = JSON.parse(mcp_arguments)
+				} catch (error) {
+					cline.consecutiveMistakeCount++
+					await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`)
+					pushToolResult(
+						formatResponse.toolError(formatResponse.invalidMcpToolArgumentError(server_name, tool_name)),
+					)
+					return
+				}
+			}
+			cline.consecutiveMistakeCount = 0
+			const completeMessage = JSON.stringify({
+				type: "use_mcp_tool",
+				serverName: server_name,
+				toolName: tool_name,
+				arguments: mcp_arguments,
+			} satisfies ClineAskUseMcpServer)
+			const didApprove = await askApproval("use_mcp_server", completeMessage)
+			if (!didApprove) {
+				return
+			}
+			// now execute the tool
+			await cline.say("mcp_server_request_started") // same as browser_action_result
+			const toolResult = await cline.providerRef
+				.deref()
+				?.getMcpHub()
+				?.callTool(server_name, tool_name, parsedArguments)
+
+			// TODO: add progress indicator and ability to parse images and non-text responses
+			const toolResultPretty =
+				(toolResult?.isError ? "Error:\n" : "") +
+					toolResult?.content
+						.map((item) => {
+							if (item.type === "text") {
+								return item.text
+							}
+							if (item.type === "resource") {
+								const { blob, ...rest } = item.resource
+								return JSON.stringify(rest, null, 2)
+							}
+							return ""
+						})
+						.filter(Boolean)
+						.join("\n\n") || "(No response)"
+			await cline.say("mcp_server_response", toolResultPretty)
+			pushToolResult(formatResponse.toolResult(toolResultPretty))
+			return
+		}
+	} catch (error) {
+		await handleError("executing MCP tool", error)
+		return
+	}
+}