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),
115 lines • 5.94 kB
JavaScript
// #1318 — a half-extracted install is silent until an unrelated-looking crash.
//
// A global Windows install became unusable: `@modelcontextprotocol/sdk`'s
// `dist/esm/server/` held two empty subdirectories where a healthy install has 42
// entries. Directories created, files never written — an interrupted extraction.
// The install's package.json still read 0.50.46, so it presented as up to date
// while being non-functional.
//
// What the user got was Node's resolver talking about a path:
//
// Error [ERR_MODULE_NOT_FOUND]: Cannot find module
// '…\node_modules\@modelcontextprotocol\sdk\dist\esm\server\mcp.js'
//
// Nothing in that names comfyui-mcp, says the install is damaged, or hints at the
// one-line repair. The reporter filed it for exactly this detection gap, and was
// careful that the CAUSE is unproven — an interrupted `npm install -g` looks
// identical to a failed self-update. So this diagnoses the STATE, which is
// observable, and does not assert how it got there.
/**
* Is this the failure of a missing/unreadable module, rather than an error the
* program threw on its own?
*
* Deliberately narrow. `ERR_MODULE_NOT_FOUND` and `MODULE_NOT_FOUND` are the two
* Node reports for "the file the import points at is not there", which is the
* observable state a half-extracted tree produces. Anything else is a real error
* from real code and must reach the user unchanged — swallowing it behind a
* "reinstall" banner would be its own defect, and a far more confusing one.
*/
export function isModuleResolutionFailure(err) {
const code = err?.code;
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
}
/** The specifier Node could not resolve, when its message carries one. */
export function missingSpecifier(err) {
const msg = typeof err?.message === "string" ? String(err.message) : "";
// "Cannot find module '<path>' imported from …" / "Cannot find package '<name>'"
const quoted = msg.match(/Cannot find (?:module|package) '([^']+)'/);
if (quoted?.[1])
return quoted[1];
return null;
}
/**
* Which of the two failures Node reported — an absent PACKAGE or an absent FILE
* inside one.
*
* Worth distinguishing because the repair reads differently and, more to the
* point, because "a file is missing: zod" is nonsense. Node says "Cannot find
* package" when nothing resolved the bare specifier at all, and "Cannot find
* module" when it resolved to a path that is not on disk — which is the
* half-extracted case in #1318.
*/
export function missingKind(err) {
const msg = typeof err?.message === "string" ? String(err.message) : "";
if (/Cannot find package '/.test(msg))
return "package";
if (/Cannot find module '/.test(msg))
return "module";
return null;
}
/** The package a path inside node_modules belongs to, for naming the culprit. */
export function owningPackage(specifier) {
if (!specifier)
return null;
const norm = specifier.replace(/\\/g, "/");
const at = norm.lastIndexOf("node_modules/");
if (at === -1) {
// Not inside node_modules, so this is one of OUR OWN files, not a dependency.
//
// The first version treated any non-node_modules specifier as a bare package
// name, which on Windows read the drive letter off an absolute path and
// announced a missing dependency called "C:". An absolute or relative path is
// never a package name — only a bare specifier is.
// Tested against the NORMALIZED string: the raw one still has backslashes on
// Windows, so a pattern written for `/` silently matched nothing there — which
// is how `C:\Users\…\boot.js` came out as a dependency named "C:".
if (/^([a-zA-Z]:)?\//.test(norm) || norm.startsWith("."))
return null;
const m = norm.match(/^(@[^/]+\/[^/]+|[^@/][^/]*)/);
return m?.[1] ?? null;
}
const rest = norm.slice(at + "node_modules/".length);
const m = rest.match(/^(@[^/]+\/[^/]+|[^/]+)/);
return m?.[1] ?? null;
}
/**
* The message a user should get instead of a resolver stack trace.
*
* States the OBSERVATION (a module this install needs is not on disk) and the
* repair, and does NOT claim why — the reporter could not prove self-update
* caused it, and an interrupted `npm install -g` is indistinguishable. Asserting
* a cause here would send someone to fix the wrong thing.
*/
export function describeBrokenInstall(err) {
const specifier = missingSpecifier(err);
const pkg = owningPackage(specifier);
const isDependency = Boolean(pkg) && pkg !== "comfyui-mcp";
const what = !specifier
? `a module it needs could not be loaded`
: // Nothing resolved the name at all: the package is absent, not damaged.
// Printing the bare name on the "path" line would read as `is missing: zod`.
missingKind(err) === "package" && isDependency
? `its dependency "${pkg}" is not installed`
: isDependency
? `a file from its dependency "${pkg}" is missing:\n ${specifier}`
: `a file it needs is missing:\n ${specifier}`;
return (`comfyui-mcp could not start: ${what}\n\n` +
`This install is INCOMPLETE, not misconfigured. Directories present with files missing is what an\n` +
`interrupted install or update leaves behind, and the recorded version still looks current — which\n` +
`is why nothing warned you until now.\n\n` +
`Repair it by reinstalling:\n\n` +
` npm install -g comfyui-mcp@latest\n\n` +
`If you run it through npx, clear the cache first (npx clear-npx-cache) so it does not reuse the\n` +
`same damaged tree. Nothing else needs changing: your settings, panel and workflows are untouched.\n`);
}
//# sourceMappingURL=broken-install.js.map