mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-23 00:43:14 +00:00
190 lines
6.4 KiB
TypeScript
190 lines
6.4 KiB
TypeScript
/**
|
|
* Utilities for sanitizing MCP server and tool names to conform to
|
|
* API function name requirements across all providers.
|
|
*/
|
|
|
|
/**
|
|
* Separator used between MCP prefix, server name, and tool name.
|
|
* We use "--" (double hyphen) because:
|
|
* 1. It's allowed by all providers (dashes are permitted in function names)
|
|
* 2. It won't conflict with underscores in sanitized server/tool names
|
|
* 3. It's unique enough to be a reliable delimiter for parsing
|
|
*/
|
|
export const MCP_TOOL_SEPARATOR = "--"
|
|
|
|
/**
|
|
* Prefix for all MCP tool function names.
|
|
*/
|
|
export const MCP_TOOL_PREFIX = "mcp"
|
|
|
|
/**
|
|
* Normalize a string for comparison by treating hyphens and underscores as equivalent.
|
|
* This is used to match tool names when models convert hyphens to underscores.
|
|
*
|
|
* @param name - The name to normalize
|
|
* @returns The normalized name with all hyphens converted to underscores
|
|
*/
|
|
export function normalizeForComparison(name: string): string {
|
|
return name.replace(/-/g, "_")
|
|
}
|
|
|
|
/**
|
|
* Normalize an MCP tool name by converting underscore separators back to hyphens.
|
|
* This handles the case where models (especially Claude) convert hyphens to underscores
|
|
* in tool names when using native tool calling.
|
|
*
|
|
* For example: "mcp__server__tool" -> "mcp--server--tool"
|
|
*
|
|
* This function uses fuzzy matching - it treats hyphens and underscores as equivalent
|
|
* when normalizing the separator pattern.
|
|
*
|
|
* @param toolName - The tool name that may have underscore separators
|
|
* @returns The normalized tool name with hyphen separators
|
|
*/
|
|
export function normalizeMcpToolName(toolName: string): string {
|
|
// Normalize for comparison to detect MCP tools regardless of separator style
|
|
const normalized = normalizeForComparison(toolName)
|
|
|
|
// Only normalize if it looks like an MCP tool (starts with mcp__)
|
|
if (normalized.startsWith("mcp__")) {
|
|
// Find the pattern: mcp{sep}server{sep}tool where sep is -- or __
|
|
// We need to convert the separators while preserving the rest
|
|
|
|
// First, try to parse assuming all separators are underscores
|
|
// Pattern: mcp__server__tool or mcp__server__tool_with_underscores
|
|
const parts = toolName.split(/__|--/)
|
|
|
|
if (parts.length >= 3 && parts[0].toLowerCase() === "mcp") {
|
|
// Reconstruct with proper -- separators
|
|
const serverName = parts[1]
|
|
const toolNamePart = parts.slice(2).join("--") // Rejoin in case tool name had separator
|
|
return `${MCP_TOOL_PREFIX}${MCP_TOOL_SEPARATOR}${serverName}${MCP_TOOL_SEPARATOR}${toolNamePart}`
|
|
}
|
|
}
|
|
return toolName
|
|
}
|
|
|
|
/**
|
|
* Check if a tool name is an MCP tool (starts with the MCP prefix and separator).
|
|
* Uses fuzzy matching to handle both hyphen and underscore separators.
|
|
*
|
|
* @param toolName - The tool name to check
|
|
* @returns true if the tool name starts with "mcp--" or "mcp__", false otherwise
|
|
*/
|
|
export function isMcpTool(toolName: string): boolean {
|
|
const normalized = normalizeForComparison(toolName)
|
|
return normalized.startsWith(`${MCP_TOOL_PREFIX}__`)
|
|
}
|
|
|
|
/**
|
|
* Sanitize a name to be safe for use in API function names.
|
|
* This removes special characters and ensures the name starts correctly.
|
|
*
|
|
* Note: Hyphens are preserved since they are valid in function names.
|
|
* Models may convert hyphens to underscores, but we handle this with
|
|
* fuzzy matching when parsing tool names.
|
|
*
|
|
* @param name - The original name (e.g., MCP server name or tool name)
|
|
* @returns A sanitized name that conforms to API requirements
|
|
*/
|
|
export function sanitizeMcpName(name: string): string {
|
|
if (!name) {
|
|
return "_"
|
|
}
|
|
|
|
// Replace spaces with underscores first
|
|
let sanitized = name.replace(/\s+/g, "_")
|
|
|
|
// Only allow alphanumeric, underscores, and hyphens
|
|
sanitized = sanitized.replace(/[^a-zA-Z0-9_\-]/g, "")
|
|
|
|
// Replace any double-hyphen sequences with single hyphen to avoid separator conflicts
|
|
sanitized = sanitized.replace(/--+/g, "-")
|
|
|
|
// Ensure the name starts with a letter or underscore
|
|
if (sanitized.length > 0 && !/^[a-zA-Z_]/.test(sanitized)) {
|
|
sanitized = "_" + sanitized
|
|
}
|
|
|
|
// If empty after sanitization, use a placeholder
|
|
if (!sanitized) {
|
|
sanitized = "_unnamed"
|
|
}
|
|
|
|
return sanitized
|
|
}
|
|
|
|
/**
|
|
* Build a full MCP tool function name from server and tool names.
|
|
* The format is: mcp--{sanitized_server_name}--{sanitized_tool_name}
|
|
*
|
|
* The total length is capped at 64 characters to conform to API limits.
|
|
*
|
|
* @param serverName - The MCP server name
|
|
* @param toolName - The tool name
|
|
* @returns A sanitized function name in the format mcp--serverName--toolName
|
|
*/
|
|
export function buildMcpToolName(serverName: string, toolName: string): string {
|
|
const sanitizedServer = sanitizeMcpName(serverName)
|
|
const sanitizedTool = sanitizeMcpName(toolName)
|
|
|
|
// Build the full name: mcp--{server}--{tool}
|
|
const fullName = `${MCP_TOOL_PREFIX}${MCP_TOOL_SEPARATOR}${sanitizedServer}${MCP_TOOL_SEPARATOR}${sanitizedTool}`
|
|
|
|
// Truncate if necessary (max 64 chars for Gemini)
|
|
if (fullName.length > 64) {
|
|
return fullName.slice(0, 64)
|
|
}
|
|
|
|
return fullName
|
|
}
|
|
|
|
/**
|
|
* Parse an MCP tool function name back into server and tool names.
|
|
* This handles both hyphen and underscore separators using fuzzy matching.
|
|
*
|
|
* @param mcpToolName - The full MCP tool name (e.g., "mcp--weather--get_forecast" or "mcp__weather__get_forecast")
|
|
* @returns An object with serverName and toolName, or null if parsing fails
|
|
*/
|
|
export function parseMcpToolName(mcpToolName: string): { serverName: string; toolName: string } | null {
|
|
// Normalize the name to handle both separator styles
|
|
const normalizedName = normalizeMcpToolName(mcpToolName)
|
|
|
|
const prefix = MCP_TOOL_PREFIX + MCP_TOOL_SEPARATOR
|
|
if (!normalizedName.startsWith(prefix)) {
|
|
return null
|
|
}
|
|
|
|
// Remove the "mcp--" prefix
|
|
const remainder = normalizedName.slice(prefix.length)
|
|
|
|
// Split on the separator to get server and tool names
|
|
const separatorIndex = remainder.indexOf(MCP_TOOL_SEPARATOR)
|
|
if (separatorIndex === -1) {
|
|
return null
|
|
}
|
|
|
|
const serverName = remainder.slice(0, separatorIndex)
|
|
const toolName = remainder.slice(separatorIndex + MCP_TOOL_SEPARATOR.length)
|
|
|
|
if (!serverName || !toolName) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
serverName,
|
|
toolName,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if two tool names match using fuzzy comparison.
|
|
* Treats hyphens and underscores as equivalent.
|
|
*
|
|
* @param name1 - First tool name
|
|
* @param name2 - Second tool name
|
|
* @returns true if the names match (treating - and _ as equivalent)
|
|
*/
|
|
export function toolNamesMatch(name1: string, name2: string): boolean {
|
|
return normalizeForComparison(name1) === normalizeForComparison(name2)
|
|
}
|