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),
91 lines • 3.95 kB
JavaScript
// llama.cpp (llama-server) probes for the panel orchestrator — the two
// launch-time gotchas the provider must surface instead of failing cryptically:
//
// * `--jinja` is MANDATORY for tool calling: without it llama-server rejects
// any request carrying `tools` (and can never emit finish_reason=
// tool_calls). A server that "connects fine" then errors on the first real
// message reads as a wedge — detect it at connect time instead.
// * context is fixed at LAUNCH (`-c`), not per request: the comfyui-mcp tool
// payload needs ~16K minimum, so a small n_ctx silently truncates. /props
// reports the server's actual window.
//
// Best-effort: failures return "unknown" rather than blocking a connect.
import { logger } from "../utils/logger.js";
/** The REST root (strip a trailing /v1 — native endpoints hang off the root). */
function serverRoot(host) {
return host.replace(/\/+$/, "").replace(/\/v1$/, "");
}
export async function llamacppProps(host) {
try {
const res = await fetch(`${serverRoot(host)}/props`, { signal: AbortSignal.timeout(4000) });
if (!res.ok)
return {};
const body = (await res.json());
return {
nCtx: body.default_generation_settings?.n_ctx,
modelPath: body.model_path,
};
}
catch {
return {};
}
}
/** Can this server take tool-calling requests?
*
* Primary signal: /props `chat_template_caps.supports_tools` — authoritative
* on current builds (which enable jinja BY DEFAULT; verified live on b9945:
* a plain launch reports supports_tools:true and accepts tools). Fallback for
* OLD builds (where --jinja was opt-in and tools requests get REJECTED): a
* minimal max_tokens-1 tools request, "no" on the tools/jinja error signature. */
export async function llamacppToolsReady(host, model) {
try {
const res = await fetch(`${serverRoot(host)}/props`, { signal: AbortSignal.timeout(4000) });
if (res.ok) {
const body = (await res.json());
const caps = body.chat_template_caps;
if (caps && (caps.supports_tools === false || caps.supports_tool_calls === false)) {
logger.warn("[llamacpp] /props reports the chat template does NOT support tools");
return "no";
}
if (caps?.supports_tools || caps?.supports_tool_calls)
return "yes";
}
}
catch {
// fall through to the request probe
}
try {
const res = await fetch(`${host.replace(/\/+$/, "")}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model,
messages: [{ role: "user", content: "ok" }],
tools: [
{
type: "function",
function: { name: "noop", description: "noop", parameters: { type: "object", properties: {} } },
},
],
max_tokens: 1,
}),
// generous: this may also be the JIT/model warm-up request
signal: AbortSignal.timeout(120000),
});
if (res.ok)
return "yes";
// unknown-ok: "" cannot match either marker, so an unreadable body falls
// through to the `return "unknown"` below rather than to `return "no"` — the
// three states are already distinct and a failed read lands on the right one.
const text = (await res.text().catch(() => "")).toLowerCase();
if (text.includes("jinja") || text.includes("tools")) {
logger.warn(`[llamacpp] tools probe rejected (server without --jinja?): ${text.slice(0, 160)}`);
return "no";
}
return "unknown";
}
catch {
return "unknown";
}
}
//# sourceMappingURL=llamacpp-probe.js.map