nexu-io/open-design · error · Error

raw API keys are not accepted by Open Design MCP. Configure

Error message

raw API keys are not accepted by Open Design MCP. Configure Local BYOK in the Open Design UI and start that run from the local product instead.

What it means

startRun refuses to launch a run if args contains apiKey or byokProvider, or if args.inputs contains a field whose name matches a credential pattern (api_key, authorization, access_token, refresh_token, secret, password, checked recursively up to depth 20). Open Design MCP deliberately does not accept raw provider credentials; keys must live in Local BYOK configured through the UI.

Source

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

}

// Commission a generation run. The caller never runs the skill/plugin
// itself; we POST to /api/runs and the daemon spawns its own agent.
// Returns the runId immediately so the caller can poll get_run —
// start+poll because MCP is request/response and generation is
// minutes-long.
async function startRun(
  baseUrl: string,
  args: McpArgs,
  options: HandleMcpToolCallOptions = {},
  headers?: Record<string, string>,
) {
  if (
    Object.prototype.hasOwnProperty.call(args, 'apiKey')
    || Object.prototype.hasOwnProperty.call(args, 'byokProvider')
    || containsMcpCredentialField(args.inputs)
  ) {
    throw new Error(
      'raw API keys are not accepted by Open Design MCP. Configure Local BYOK in the Open Design UI and start that run from the local product instead.',
    );
  }
  const { id, resolved, active } = await resolveProjectArg(baseUrl, args.project, headers);
  if (args.requestId !== undefined) requireString(args.requestId, 'requestId');
  if (
    options.pluginAttribution
    && (typeof args.requestId !== 'string' || args.requestId.length === 0)
  ) {
    throw pluginContractError(
      'requestId is required for attributed start_run calls so a lost response can be retried without starting a second logical run',
    );
  }
  const requestId =
    typeof args.requestId === 'string' && args.requestId.length > 0
      ? args.requestId
      : randomUUID();
  const body: JsonObject = { projectId: id, clientRequestId: requestId };

View on GitHub (pinned to 5be4028344)

Solutions

  1. Remove apiKey, byokProvider, and any credential-shaped fields from the MCP args.
  2. Configure the provider key via Local BYOK in the Open Design UI, then start the run from the local product.
  3. Move secrets into headers managed by the daemon, not into MCP inputs.

Example fix

// before
start_run({ apiKey: 'sk-...', inputs: { authorization: 'Bearer ...', prompt: 'hi' } })

// after
// set the key in Open Design UI -> Local BYOK, then:
start_run({ project: 'my-project', inputs: { prompt: 'hi' } })
Defensive patterns

Strategy: validation

Validate before calling

const CRED = /^(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|secret|password)$/iu;
function leaks(v: unknown, d = 0): boolean {
  if (d > 20) return true;
  if (!v || typeof v !== 'object') return false;
  if (Array.isArray(v)) return v.some((x) => leaks(x, d + 1));
  return Object.keys(v).some((k) => CRED.test(k)) || Object.values(v).some((x) => leaks(x, d + 1));
}
if ('apiKey' in args || 'byokProvider' in args || leaks(args.inputs)) {
  throw new Error('strip credential fields; configure Local BYOK in the Open Design UI instead');
}

Prevention

When it happens

Trigger: Passing apiKey in the MCP call; setting byokProvider; embedding a token in inputs.headers.authorization or inputs.api_key; nested credential fields deeper in the inputs object.

Common situations: An agent porting a direct-provider script into MCP and passing its key; a user pasting a bearer token into inputs.

Related errors


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