{"record":{"id":"37a3eb68ff2ff6ea","repo":"nanocoai/nanoclaw","slug":"dropping-invalid-stored-mcp-server","errorCode":null,"errorMessage":"Dropping invalid stored MCP server","messagePattern":"Dropping invalid stored MCP server","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/container-config.ts","lineNumber":306,"sourceCode":"      if (\n        server.type !== 'http' &&\n        typeof pluginRoot === 'string' &&\n        pluginRoot.startsWith(`${CONTAINER_PLUGINS_DIR}/`)\n      ) {\n        server.pluginRoot = pluginRoot;\n      }\n      if (server.type !== 'http' && server.cwd && !server.pluginRoot) {\n        // cwd resolves against a plugin root; without provenance nothing can\n        // resolve it. This strip is the ONLY layer (the runtime passes\n        // provenance-less servers through untouched), and the breadcrumb\n        // lands in host logs instead of nowhere.\n        delete server.cwd;\n        log.warn('Stripping cwd from stored MCP server without plugin provenance', { group: groupName, server: name });\n      }\n      servers[name] = server;\n      // eslint-disable-next-line no-catch-all/no-catch-all -- validation failures are data errors, not bugs\n    } catch (err) {\n      log.warn('Dropping invalid stored MCP server', {\n        group: groupName,\n        server: name,\n        reason: err instanceof Error ? err.message : String(err),\n      });\n    }\n  }\n  return servers;\n}\n\n/**\n * runtime_tier is an isolation control: dropping an unknown stored value would\n * silently compose the group at the default tier — a weaker boundary than the\n * one the value asked for. Fail closed instead: the group refuses to compose\n * until the stored value is fixed. (A *declared* tier the driver cannot\n * realize is refused separately by validateSpec, against the driver's\n * capabilities.)\n */\nfunction parseRuntimeTier(raw: string | null | undefined, groupName: string): 'container' | 'vm' | undefined {","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/container-config.ts#L288-L324","documentation":"A container_config's stored MCP server entry failed validation and was dropped from the sanitized config. The agent will run without that MCP server; the bad entry remains in the DB until corrected.","triggerScenarios":"`ncl groups config add-mcp-server` stored an entry whose shape later failed schema validation — invalid command/env shape, missing required fields, or an entry hand-edited in the DB.","commonSituations":"Version upgrades that tightened MCP server validation; hand-edited container.json or DB rows; entries created by older self-mod tooling.","solutions":["Inspect `ncl groups config get --id <group>` and find the server named in the log","Remove and re-add the server: `ncl groups config remove-mcp-server` then `add-mcp-server` with current syntax","Never edit the DB row directly; use the CLI so validation runs"],"exampleFix":"// before\nconfig.mcpServers['foo'] = { cmd: 'npx foo' }  // hand-edited, invalid\n// after\nncl groups config add-mcp-server --id <group> --name foo --command npx --args foo","handlingStrategy":"validation","validationCode":"ncl groups config get --id <group>  // review mcpServers entries before upgrade","typeGuard":"function looksLikeMcpServer(x: unknown): boolean {\n  return !!x && typeof x === 'object' && typeof (x as any).command === 'string';\n}","tryCatchPattern":"try { sanitize(stored) } catch (e) { /* drop entry, re-add via CLI */ }","preventionTips":["Only add MCP servers via `ncl groups config add-mcp-server`","Never hand-edit container_configs.mcp_servers JSON","After upgrades, diff `config get` to spot dropped servers"],"tags":["mcp","container-config","validation","config-corruption"],"backgroundTag":"config-schema-validation-failed","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}