nanocoai/nanoclaw · warning
Stripping cwd from stored MCP server without plugin provenan
Error message
Stripping cwd from stored MCP server without plugin provenance
What it means
A stored MCP server entry in the container_configs DB has a `cwd` set but no `pluginRoot` provenance. Since cwd is meant to resolve relative to a plugin root, without provenance nothing can resolve it, so the host strips it and warns. The runtime would pass the provenance-less server through untouched, making this sanitize layer the only protection.
Source
Thrown at src/container-config.ts:301
}
try {
validateMcpServerName(name);
const server = parseMcpServerConfig(entry as Record<string, unknown>);
const pluginRoot = (entry as Record<string, unknown>).pluginRoot;
if (
server.type !== 'http' &&
typeof pluginRoot === 'string' &&
pluginRoot.startsWith(`${CONTAINER_PLUGINS_DIR}/`)
) {
server.pluginRoot = pluginRoot;
}
if (server.type !== 'http' && server.cwd && !server.pluginRoot) {
// cwd resolves against a plugin root; without provenance nothing can
// resolve it. This strip is the ONLY layer (the runtime passes
// provenance-less servers through untouched), and the breadcrumb
// lands in host logs instead of nowhere.
delete server.cwd;
log.warn('Stripping cwd from stored MCP server without plugin provenance', { group: groupName, server: name });
}
servers[name] = server;
// eslint-disable-next-line no-catch-all/no-catch-all -- validation failures are data errors, not bugs
} catch (err) {
log.warn('Dropping invalid stored MCP server', {
group: groupName,
server: name,
reason: err instanceof Error ? err.message : String(err),
});
}
}
return servers;
}
/**
* runtime_tier is an isolation control: dropping an unknown stored value would
* silently compose the group at the default tier — a weaker boundary than the
* one the value asked for. Fail closed instead: the group refuses to composeView on GitHub (pinned to 294ef2aee8)
Solutions
- Remove `cwd` from the stored server config, or run the server command with an absolute path
- If the server came from a plugin, reinstall it via the plugin flow so `pluginRoot` is recorded
- Update the stored row via `ncl groups config update` / `add-mcp-server` with absolute paths
Example fix
// before
{"command":"node","args":["server.js"],"cwd":"./plugin"}
// after
{"command":"node","args":["/abs/path/to/plugin/server.js"]} Defensive patterns
Strategy: validation
Validate before calling
const server = JSON.parse(row.mcp_servers)[name];
if (server.type !== 'http' && server.cwd && !server.pluginRoot) {
delete server.cwd; // normalize before storing/using
} Type guard
function hasResolvedCwd(s: McpServerConfig): boolean {
return s.type === 'http' || !s.cwd || typeof s.pluginRoot === 'string';
} Prevention
- Always store MCP servers with absolute command paths
- Install plugin-provided MCP servers via the plugin flow so pluginRoot is recorded
- Validate stored config with `ncl groups config get` after manual edits
When it happens
Trigger: An MCP server config written directly to the container_configs table (e.g. via `ncl groups config add-mcp-server` or a manual DB edit / older migration) that includes `cwd` but omits `pluginRoot`. Only fires for non-`http` server types.
Common situations: Hand-crafted MCP server JSON from docs or an older schema version that predates plugin provenance; copying an MCP server config from a plugin-installed setup into a plain stored config.
Related errors
- server name must be 1-64 characters of letters, digits, "_"
- unsupported transport "sse"
- type must be "stdio", "http", or "streamable-http"
- type "stdio" requires command
- type "http" requires url
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/9c2f82dfaec06b9a.
Report an issue: GitHub.