Skip to content

bug: MCP tool calls fail when LLM emits empty strings for optional parameters #2887

@shanewhite97

Description

@shanewhite97

Problem

When Mux invokes MCP tools (e.g. gitlab_mcp_list_issues), the LLM may supply empty strings or placeholder values for optional parameters:

{
  "project_id": "42332",
  "assignee_id": "",
  "author_id": "",
  "author_username": "",
  "iteration_id": "",
  "search": ""
}

Mux forwards these arguments verbatim to MCP servers via wrapMCPTools in src/node/services/mcpServerManager.ts. Downstream APIs that require optional parameters to be omitted (not empty) reject the request. For example, GitLab returns:

400 Bad Request
author_id, author_username are mutually exclusive
assignee_id should be an integer, none, or any, however got ""
iteration_id should be an integer, none, any, or current, however got ""

From a user perspective, this surfaces as a failed tool call with no obvious workaround.

Root Cause

  1. LLMs commonly emit empty strings ("") for optional fields they don't have values for, rather than omitting them.
  2. wrapMCPTools wraps tool execution for timeout/abort handling and activity tracking, but passes args through without any sanitization.
  3. Many REST-backed MCP servers (GitLab, and likely others) treat empty strings as present-but-invalid, causing 400 errors.

Proposed Fix

Introduce light argument normalization in wrapMCPTools before invoking the original execute function. Specifically:

  • Strip keys with empty string values ("") from the arguments object before forwarding. Empty strings for optional parameters are almost always LLM artifacts, not intentional values.
  • Do NOT strip null values — some MCP servers may use explicit null to mean "clear/unset this field."
  • Do NOT strip empty arrays ([]) — these are generally treated as "no filter" by APIs and are benign.

Example normalization (conceptual)

function sanitizeArgs(args: Record<string, unknown>): Record<string, unknown> {
  const sanitized: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(args)) {
    if (value === "") continue; // Skip empty strings
    sanitized[key] = value;
  }
  return sanitized;
}

This could be applied inside wrapMCPTools before calling originalExecute(args, context).

Considerations

  • Different MCP servers may interpret empty vs. missing differently. The normalization should be conservative (empty strings only).
  • Opt-out escape hatch (future): If any MCP server legitimately needs empty strings passed through, a per-server config flag like sanitizeArgs: false could be added later.
  • Nested objects: For v1, top-level empty string stripping is sufficient. Deep/recursive sanitization can be considered if needed.

Affected Component

src/node/services/mcpServerManager.tswrapMCPTools function (line ~131)

Impact

This affects any MCP server backed by a strict REST API that distinguishes between "parameter omitted" and "parameter present but empty." GitLab MCP is one confirmed case, but the issue is generic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions