nanocoai/nanoclaw · error · Error
MCP instructions must be a string
Error message
MCP instructions must be a string
What it means
parseMcpServerConfig rejects an `instructions` field that is present but not a string. `instructions` is optional free-form guidance passed to the agent; any object/array/number value indicates malformed config JSON and is rejected.
Source
Thrown at src/container-config.ts:143
*/
export function parseMcpServerConfig(input: Record<string, unknown>): McpServerConfig {
const command = typeof input.command === 'string' && input.command.trim() ? input.command : undefined;
const url = typeof input.url === 'string' && input.url.trim() ? input.url.trim() : undefined;
// A declared transport is honored; absence keeps the legacy CLI inference
// (url → http, command → stdio). "streamable-http" is the Agent Plugins
// spelling of the internal "http".
const type = input.type === 'streamable-http' ? 'http' : input.type;
if (type === 'sse') throw new Error('unsupported transport "sse"');
if (type !== undefined && type !== 'stdio' && type !== 'http') {
throw new Error('type must be "stdio", "http", or "streamable-http"');
}
if (type === 'stdio' && !command) throw new Error('type "stdio" requires command');
if (type === 'http' && !url) throw new Error('type "http" requires url');
const instructions = input.instructions;
if (instructions !== undefined && typeof instructions !== 'string') {
throw new Error('MCP instructions must be a string');
}
if (url !== undefined) {
if (command !== undefined) throw new Error('Provide exactly one of command or url');
if (input.args !== undefined || input.env !== undefined || input.cwd !== undefined) {
throw new Error('args, env, and cwd are only valid with command');
}
let parsed: URL;
try {
parsed = new URL(url);
} catch (err) {
throw new Error('url must be a valid HTTP(S) URL', { cause: err });
}
const loopback = ['localhost', '127.0.0.1', '[::1]', 'host.docker.internal'].includes(parsed.hostname);
if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback)) {
throw new Error('url must use HTTPS (plain HTTP is allowed only for localhost and host.docker.internal)');
}
if (parsed.username || parsed.password || parsed.hash) {View on GitHub (pinned to 294ef2aee8)
Solutions
- Make instructions a single string: {"instructions":"Use this server for web search"}
- Omit the field entirely if you don't need it
- If hand-editing the DB, validate the JSON with a linter before restarting
Example fix
// before
{"instructions":["search","fetch"]}
// after
{"instructions":"search and fetch pages"} Defensive patterns
Strategy: type-guard
Validate before calling
if (srv.instructions !== undefined && typeof srv.instructions !== 'string') { srv.instructions = String(srv.instructions); } Type guard
const hasStringInstructions = (e: any) => e.instructions === undefined || typeof e.instructions === 'string';
Try / catch
catch (err) { if (err.message.includes('instructions must be a string')) coerceOrStrip('instructions'); else throw err; } Prevention
- Keep instructions as one prose string
- Run JSON payloads through a schema check (zod) at the UI edge
When it happens
Trigger: Passing instructions as an array of strings, an object, or a number in an MCP server entry via ncl or the add_mcp_server tool.
Common situations: Quoting mistakes in shell JSON (instructions ending up parsed as an object); hand-editing container_configs rows directly in SQLite with wrong JSON shapes.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- args must be a JSON array of strings
- ${flag} must be a JSON object with string values
- type "stdio" requires command
- type "http" requires url
- Provide exactly one of command or url
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/45cc87fd6cca257d.
Report an issue: GitHub.