UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

111 lines • 4.55 kB
import { getObjectInfo } from "../comfyui/client.js"; function collectInputNames(def) { const names = []; for (const group of [def.input?.required, def.input?.optional]) { if (group && typeof group === "object") names.push(...Object.keys(group)); } return names; } function collectOutputNames(def) { const out = new Set(); for (const name of def.output_name ?? []) if (typeof name === "string") out.add(name); for (const type of def.output ?? []) if (typeof type === "string") out.add(type); return [...out]; } /** True when `needle` appears in `hay` in order but not necessarily contiguous * (a classic fuzzy subsequence match, e.g. "ksamplr" ⊂ "ksampler"). */ function isSubsequence(needle, hay) { if (!needle) return true; let i = 0; for (let j = 0; j < hay.length && i < needle.length; j++) { if (hay[j] === needle[i]) i++; } return i === needle.length; } /** Score a node against the query terms; returns null when it doesn't match. * `fuzzy` allows subsequence matches on the class/display name (typo tolerance); * strict mode requires a substring match somewhere. */ function scoreNode(terms, f, fuzzy) { if (terms.length === 0) return 1; let score = 0; for (const t of terms) { if (f.name === t || f.disp === t) score += 100; else if (f.name.includes(t)) score += 40; else if (f.disp.includes(t)) score += 30; else if (f.category.includes(t)) score += 15; else if (f.desc.includes(t)) score += 10; else if (f.haystack.includes(t)) score += 5; // input/output names only else if (fuzzy && (isSubsequence(t, f.name) || isSubsequence(t, f.disp))) score += 2; // fuzzy/typo match on an identifier else return null; // this term matched nowhere → node excluded (AND semantics) } return score; } /** * Fuzzy-search node class definitions over class name, display name, category, * description, and input/output names. ALL whitespace-separated query terms must * match (AND). Two-tier for precision: an exact/substring pass runs first, and * only if it finds nothing does a fuzzy (subsequence) pass run — so a typo like * "ksamplr" still finds "KSampler" without a substring pass flooding results with * loose subsequence hits. Scoring prefers class/display-name matches, then * category/description, then input/output names, then fuzzy identifier matches. * An empty query lists everything (bounded by `limit`). */ export function searchObjectInfo(objectInfo, query, limit = 20) { const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean); const run = (fuzzy) => { const hits = []; for (const [classType, def] of Object.entries(objectInfo)) { if (!def || typeof def !== "object") continue; const inputs = collectInputNames(def); const outputs = collectOutputNames(def); const name = classType.toLowerCase(); const disp = (def.display_name ?? "").toLowerCase(); const desc = (def.description ?? "").toLowerCase(); const category = (def.category ?? "").toLowerCase(); const haystack = [name, disp, category, desc, inputs.join(" "), outputs.join(" ")] .join(" ") .toLowerCase(); const score = scoreNode(terms, { name, disp, desc, category, haystack }, fuzzy); if (score === null) continue; hits.push({ class_type: classType, display_name: def.display_name ?? classType, category: def.category ?? "", description: (def.description ?? "").slice(0, 200), inputs, outputs, score, }); } hits.sort((a, b) => b.score - a.score || a.class_type.localeCompare(b.class_type)); return hits.slice(0, Math.max(1, limit)); }; const strict = run(false); if (strict.length > 0 || terms.length === 0) return strict; return run(true); } /** Fetch the connected server's /object_info and fuzzy-search it. */ export async function searchLiveObjectInfo(query, limit = 20) { const objectInfo = await getObjectInfo(); return searchObjectInfo(objectInfo, query, limit); } //# sourceMappingURL=object-info-search.js.map