Move use_mcp_tool and access_mcp_resource to tool files (#2101)

This commit is contained in:
Matt Rubens 2025-03-30 01:47:19 -04:00 • committed by GitHub
parent 937b1f3c90
commit 9b0790fce7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 190 additions and 162 deletions

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}