import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge"; import type { McpExtensionState } from "./state.ts"; import type { ToolMetadata, McpTool, McpResource, ServerEntry } from "./types.ts"; import { formatToolName, isToolExcluded } from "./types.ts"; import { resourceNameToToolName } from "./resource-tools.ts"; import { extractToolUiStreamMode } from "./utils.ts"; export function buildToolMetadata( tools: McpTool[], resources: McpResource[], definition: ServerEntry, serverName: string, prefix: "server" | "none" | "short" ): { metadata: ToolMetadata[]; failedTools: string[] } { const metadata: ToolMetadata[] = []; const failedTools: string[] = []; for (const tool of tools) { if (!tool?.name) { failedTools.push("(unnamed)"); continue; } if (isToolExcluded(tool.name, serverName, prefix, definition.excludeTools)) { continue; } let uiResourceUri: string | undefined; try { uiResourceUri = getToolUiResourceUri({ _meta: tool._meta }); } catch { failedTools.push(tool.name); } metadata.push({ name: formatToolName(tool.name, serverName, prefix), originalName: tool.name, description: tool.description ?? "", inputSchema: tool.inputSchema, uiResourceUri, uiStreamMode: extractToolUiStreamMode(tool._meta), }); } if (definition.exposeResources !== false) { for (const resource of resources) { const baseName = `get_${resourceNameToToolName(resource.name)}`; if (isToolExcluded(baseName, serverName, prefix, definition.excludeTools)) { continue; } metadata.push({ name: formatToolName(baseName, serverName, prefix), originalName: baseName, description: resource.description ?? `Read resource: ${resource.uri}`, resourceUri: resource.uri, }); } } return { metadata, failedTools }; } export function getToolNames(state: McpExtensionState, serverName: string): string[] { return state.toolMetadata.get(serverName)?.map(m => m.name) ?? []; } export function totalToolCount(state: McpExtensionState): number { let count = 0; for (const metadata of state.toolMetadata.values()) { count += metadata.length; } return count; } export function findToolByName(metadata: ToolMetadata[] | undefined, toolName: string): ToolMetadata | undefined { if (!metadata) return undefined; const exact = metadata.find(m => m.name === toolName); if (exact) return exact; const normalized = toolName.replace(/-/g, "_"); return metadata.find(m => m.name.replace(/-/g, "_") === normalized); } export function formatSchema(schema: unknown, indent = " "): string { if (!schema || typeof schema !== "object") { return `${indent}(no schema)`; } const s = schema as Record; if (s.type === "object" && s.properties && typeof s.properties === "object") { const props = s.properties as Record; const required = Array.isArray(s.required) ? s.required as string[] : []; if (Object.keys(props).length === 0) { return `${indent}(no parameters)`; } const lines: string[] = []; for (const [name, propSchema] of Object.entries(props)) { const isRequired = required.includes(name); const propLine = formatProperty(name, propSchema, isRequired, indent); lines.push(propLine); } return lines.join("\n"); } if (s.type) { return `${indent}(${s.type})`; } return `${indent}(complex schema)`; } function formatProperty(name: string, schema: unknown, required: boolean, indent: string): string { if (!schema || typeof schema !== "object") { return `${indent}${name}${required ? " *required*" : ""}`; } const s = schema as Record; const parts: string[] = []; let typeStr = ""; if (s.type) { if (Array.isArray(s.type)) { typeStr = s.type.join(" | "); } else { typeStr = String(s.type); } } else if (s.enum) { typeStr = "enum"; } else if (s.anyOf || s.oneOf) { typeStr = "union"; } if (Array.isArray(s.enum)) { const enumVals = s.enum.map(v => JSON.stringify(v)).join(", "); typeStr = `enum: ${enumVals}`; } parts.push(`${indent}${name}`); if (typeStr) parts.push(`(${typeStr})`); if (required) parts.push("*required*"); if (s.description && typeof s.description === "string") { parts.push(`- ${s.description}`); } if (s.default !== undefined) { parts.push(`[default: ${JSON.stringify(s.default)}]`); } return parts.join(" "); }