import type { ToolDef } from '../llm/openai-compat.js'; const SLUG = /^[a-z0-9_-]{1,64}$/; export function normalizeToolName(serverId: string, toolName: string): string { return `mcp__${serverId}__${toolName}`; } export function parseToolName(name: string): { serverId: string; toolName: string } | null { if (!name.startsWith('mcp__')) return null; const parts = name.split('__'); if (parts.length !== 3) return null; const [, serverId, toolName] = parts; if (!SLUG.test(serverId) || !SLUG.test(toolName)) return null; return { serverId, toolName }; } export function matchesAnyPattern(name: string, patterns: string[]): boolean { return patterns.some((p) => { if (p === name) return true; if (p.endsWith('__*')) { const prefix = p.slice(0, -1); return name.startsWith(prefix) && name.length > prefix.length; } return false; }); } export interface CachedTool { serverId: string; toolName: string; description: string | null; inputSchema: string | null; } const MAX_DESCRIPTION = 1000; export function buildToolDefsFromCache( cache: CachedTool[], patterns: string[], serverNames: Map, ): ToolDef[] { const mcpPatterns = patterns.filter((p) => p.startsWith('mcp__')); if (mcpPatterns.length === 0) return []; const defs: ToolDef[] = []; for (const tool of cache) { if (!SLUG.test(tool.serverId) || !SLUG.test(tool.toolName)) continue; const canonical = normalizeToolName(tool.serverId, tool.toolName); if (!matchesAnyPattern(canonical, mcpPatterns)) continue; let params: unknown = { type: 'object', properties: {} }; if (tool.inputSchema) { try { params = JSON.parse(tool.inputSchema); } catch { // Keep default object schema } } const serverLabel = serverNames.get(tool.serverId) ?? tool.serverId; const rawDesc = tool.description ?? ''; const description = `[外部ツール: ${serverLabel} 提供] ${rawDesc}`.slice(0, MAX_DESCRIPTION); defs.push({ type: 'function', function: { name: canonical, description, parameters: params as Record, }, }); } return defs; }