diff --git a/package.json b/package.json index a55166b..752a9ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codesys-mcp-sp21-plus", - "version": "0.6.0", + "version": "0.6.1", "description": "Codesys-MCP-SP21+ -- fork of luke-harriman/Codesys-MCP carrying CODESYS V3.5 SP22 Patch 1 fixes (and forward-compat with later SPs): script-engine API drift, online/runtime tool auto-login, dual-SHA release classifier, set_pou_code omitted-decl wipe fix, add_library managed-overload, etc. MCP server for CODESYS with persistent UI instance and file-based IPC.", "main": "dist/server.js", "bin": { diff --git a/src/scripts/add_library.py b/src/scripts/add_library.py index a38896b..c9d38d3 100644 --- a/src/scripts/add_library.py +++ b/src/scripts/add_library.py @@ -43,6 +43,7 @@ import sys, scriptengine as script_engine, os, traceback LIBRARY_NAME = "{LIBRARY_NAME}" USE_DIRECT = "{USE_DIRECT}" == "1" FORCE_DUP = "{FORCE_DUP}" == "1" +ALLOW_UNRESOLVED = "{ALLOW_UNRESOLVED}" == "1" def _resolve_in_repo(name): @@ -224,6 +225,27 @@ try: print("DEBUG: Pre-resolved '%s' to installed library '%s'." % (LIBRARY_NAME, disp)) else: print("DEBUG: Pre-resolve via library_manager.find_library returned no hit for '%s'." % LIBRARY_NAME) + # HARD REFUSE: if the name does not resolve in the installed + # library repository, do NOT add anything. The post-add + # _is_resolved() guard alone is insufficient -- CODESYS lets + # add_placeholder(name_string) create a reference where + # is_placeholder=False but the library is still unresolvable on + # next project open, bricking compile with + # "The placeholder library '' could not be resolved." + # Opt-in via ALLOW_UNRESOLVED=1 for the rare case where a + # placeholder for a not-yet-installed library is genuinely wanted. + if not ALLOW_UNRESOLVED: + msg = ("Refused: library '%s' is not installed in the CODESYS library " + "repository (library_manager.find_library returned no hit). " + "Install it via the Library Repository (Tools > Library Repository, " + "or via CODESYS Installer for SL/add-on packages) before re-running, " + "or pass allowUnresolved=true if you really want a placeholder for " + "a not-yet-installed library." + % LIBRARY_NAME) + print("ERROR: %s" % msg) + print("SCRIPT_ERROR: %s" % msg) + sys.exit(1) + print("DEBUG: ALLOW_UNRESOLVED=1 -- proceeding with placeholder add despite no repo hit.") # Step 2: add the reference. Default is add_placeholder() to match the # modern ', * (System)' convention (placeholder resolves at diff --git a/src/server.ts b/src/server.ts index 6fee077..cf70a3c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1871,14 +1871,15 @@ export async function startMcpServer(config: ServerConfig): Promise { s.tool( 'add_library', - "Adds a library reference to the CODESYS project. By default uses add_placeholder() to match the modern ', * (System)' convention so transitive deps resolve at compile time -- pass direct=true to opt into the legacy direct add_library() (specific version pin). Pre-checks lm.references for an existing reference with the same name and no-ops with a confirmation message unless force=true. The library must be installed in the CODESYS library repository.", + "Adds a library reference to the CODESYS project. **Refuses upfront** if the library name does not resolve in the installed library repository (Tools > Library Repository) -- this prevents the silent-broken-placeholder bug where add_placeholder() creates a hollow reference that bricks the next project open with 'placeholder library X could not be resolved'. Install the library first (Library Repository for stock libs, CODESYS Installer for SL/add-on packages), or pass allowUnresolved=true to opt into the dangerous behaviour. Default add path is add_placeholder() for the modern ', * (System)' convention; pass direct=true for the legacy specific-version pin. Pre-checks lm.references and no-ops with a confirmation message if a reference with the same name already exists, unless force=true.", { projectFilePath: z.string().describe("Path to the project file."), - libraryName: z.string().describe("Name of the library to add (e.g., 'Standard', 'Util', 'CAA Memory')."), + libraryName: z.string().describe("Name of the library to add (e.g., 'Standard', 'Util', 'CAA Memory'). Must match exactly an installed library in the CODESYS Library Repository unless allowUnresolved=true."), direct: z.boolean().optional().describe("If true, use direct add_library() (specific-version pin) instead of the default add_placeholder() (resolves at compile)."), force: z.boolean().optional().describe("If true, add even if a reference with the same name already exists (creates a duplicate). Default: dedup -- silently no-op with a confirmation message."), + allowUnresolved: z.boolean().optional().describe("DANGEROUS. If true, skip the pre-flight 'is this library installed?' check and add a placeholder anyway. Will brick the next project open if the library is genuinely not installed. Only use when you intentionally want a placeholder for a not-yet-installed library."), }, - async (args: { projectFilePath: string; libraryName: string; direct?: boolean; force?: boolean }) => { + async (args: { projectFilePath: string; libraryName: string; direct?: boolean; force?: boolean; allowUnresolved?: boolean }) => { const escaped = resolvePath(args.projectFilePath, workspaceDir); const script = scriptManager.prepareScriptWithHelpers( 'add_library', @@ -1887,6 +1888,7 @@ export async function startMcpServer(config: ServerConfig): Promise { LIBRARY_NAME: args.libraryName.trim(), USE_DIRECT: args.direct ? '1' : '0', FORCE_DUP: args.force ? '1' : '0', + ALLOW_UNRESOLVED: args.allowUnresolved ? '1' : '0', }, ['ensure_project_open'] );