nexu-io/open-design · error · Error

inputs must be an object

Error message

inputs must be an object

What it means

Thrown by the MCP run-start helper while building POST /api/runs. The `inputs` argument becomes `body.pluginInputs`, a key/value map passed to the resolved plugin. The guard rejects null, arrays, and primitives because plugin input resolution expects a plain object; a non-object would either crash later dispatch or be silently mis-applied.

Source

Thrown at apps/daemon/src/mcp.ts:2536

  if (args.resume !== undefined) {
    if (typeof args.resume !== 'boolean') throw new Error('resume must be a boolean');
    body.resume = args.resume;
  }
  if (typeof args.prompt === 'string' && args.prompt.length > 0) {
    body.message = args.prompt;
    body.currentPrompt = args.prompt;
  }
  if (typeof args.skill === 'string' && args.skill.length > 0) body.skillId = args.skill;
  if (Array.isArray(args.skills) && args.skills.length > 0) body.skillIds = args.skills;
  if (typeof args.plugin === 'string' && args.plugin.length > 0) body.pluginId = args.plugin;
  if (typeof args.agent === 'string' && args.agent.length > 0) body.agentId = args.agent;
  if (typeof args.model === 'string' && args.model.length > 0) body.model = args.model;
  if (typeof args.serviceTier === 'string' && args.serviceTier.length > 0) {
    body.serviceTier = args.serviceTier;
  }
  if (args.inputs !== undefined) {
    if (args.inputs === null || typeof args.inputs !== 'object' || Array.isArray(args.inputs)) {
      throw new Error('inputs must be an object');
    }
    body.pluginInputs = args.inputs;
  }
  const created = await postJson<JsonObject>(
    `${baseUrl}/api/runs`,
    body,
    { ...options.analyticsHeaders, ...headers },
  );
  // Build studioUrl (conversation-level — no entry file yet) so the
  // outer agent has a URL to give the user right away. The daemon
  // returns conversationId in the response now that POST /api/runs
  // falls back to the project's default conversation for MCP callers.
  const webBase = await getWebBaseUrl(baseUrl);
  const studioUrl = buildStudioUrl(webBase, id, created?.conversationId, null);
  return ok(
    withActiveEcho(
      {
        ...created,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Pass a plain object: `inputs: { field: value }`.
  2. If you hold a JSON string, parse first: `inputs: JSON.parse(raw)`.
  3. If you have an array of one object, unwrap it: `inputs: arr[0]`.
  4. Omit `inputs` when the plugin takes no inputs rather than passing `null`.

Example fix

// before
{ plugin: id, inputs: [{ tone: "bold" }] }
// after
{ plugin: id, inputs: { tone: "bold" } }
Defensive patterns

Strategy: type-guard

Validate before calling

if (inputs !== undefined && (inputs === null || typeof inputs !== 'object' || Array.isArray(inputs))) {
  throw new TypeError('inputs must be a plain object');
}

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: Calling start_run with `inputs: [{...}]` (array of objects), `inputs: null`, `inputs: "{"k":1}"` (a JSON string), or `inputs: ""`. Common when a caller wraps inputs in an extra array layer or forgets to JSON.parse a serialized payload.

Common situations: LLM tool-call args serialized as arrays by mistake; config files that store inputs as a JSON string; passing the whole plugin snapshot instead of its `.inputs` field.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/079ee69d4c34d377. Report an issue: GitHub.