Skip to content

Fix MCP routing and OpenAI optional tool params#458

Open
itkonen wants to merge 3 commits into
editor-code-assistant:masterfrom
itkonen:fix-mcp-routing-openai-strict
Open

Fix MCP routing and OpenAI optional tool params#458
itkonen wants to merge 3 commits into
editor-code-assistant:masterfrom
itkonen:fix-mcp-routing-openai-strict

Conversation

@itkonen
Copy link
Copy Markdown
Contributor

@itkonen itkonen commented May 11, 2026

I ran into this while using the Linear MCP server together with the GitHub MCP server in the same ECA session. Both servers expose a list_issues tool, and ECA was exposing them to the model as distinct tools, but the actual MCP dispatch path still resolved calls by the short tool name. That meant a call intended for linear__list_issues could be sent to the wrong MCP server when another connected server had the same tool name.

While debugging that, I also found a related issue with OpenAI Responses tool schemas. Linear's list_issues tool has a priority filter that should be optional: omitting it should mean "do not filter by priority." But when strict was omitted from the OpenAI Responses function tool definition, OpenAI could treat optional MCP parameters as if they needed values. In practice, that made it impossible to call the Linear tool without setting a priority filter, even when the intended behavior was to list issues across all priorities.

AI Summary

This PR fixes two separate tool-calling issues that surfaced together when multiple MCP servers were enabled and OpenAI Responses models were used for tool calls.

First, MCP tool routing now follows the server selected by the model-facing tool name. ECA exposes MCP tools with fully-qualified names such as linear__list_issues and github__list_issues, but the dispatch path previously stripped that down to the short tool name and looked for the first connected MCP server with a matching tool. If two servers exposed the same tool name, the selected tool could be routed to the wrong server. The MCP dispatch path now carries the resolved MCP server name through to eca.features.tools.mcp/call-tool!, so calls are resolved against the selected server rather than whichever server happens to match first.

Second, OpenAI Responses function tools now explicitly set strict: false. When strict was omitted, OpenAI could normalize function schemas in a way that treated optional MCP parameters as required or effectively unavoidable. That is a poor fit for arbitrary MCP schemas, where omitted fields often have important semantics: for example, omitting Linear's priority filter should mean "do not filter by priority," not "pick a priority value anyway." Explicitly opting out of strict mode preserves ECA's existing best-effort tool-calling behavior and lets optional MCP parameters remain truly optional.

What changed

  • MCP tool metadata resolution now preserves the selected MCP server name through the tool call path.
  • eca.features.tools.mcp/call-tool! now resolves tools against the specified server instead of scanning connected servers by short tool name.
  • The Clojure MCP dry-run preview path was updated for the new MCP call signature.
  • OpenAI Responses function tool definitions now include strict: false.
  • Regression coverage was added for duplicate MCP tool names and OpenAI Responses tool schema emission.
  • The changelog includes concise Unreleased entries for both fixes.

Why this approach

The MCP routing fix keeps the model-facing fully-qualified tool name as the source of truth. That is the least surprising behavior: if the model chooses linear__list_issues, the execution path should call Linear's MCP server, even if another connected server also has a list_issues tool.

The OpenAI Responses change intentionally avoids strict structured-output validation for MCP function tools. MCP schemas come from external servers and are not guaranteed to satisfy OpenAI's strict schema subset. ECA already validates missing required parameters before executing tools, so setting strict: false avoids provider-side schema rewriting while keeping ECA's own validation in place.

Tradeoffs

The main tradeoff is that OpenAI Responses function calls are not constrained by OpenAI strict structured-output validation. That is intentional here:

  • ECA already validates required parameters before executing tools.
  • MCP schemas are provided by external servers and are not guaranteed to satisfy OpenAI's strict schema requirements.
  • Setting strict: true would require a broader schema transformation layer, including handling optional fields as nullable required fields, and would be a larger behavioral change.
  • Letting Responses implicitly normalize schemas can make previously optional parameters mandatory, which is worse for ECA's current MCP tool-calling model.

So this PR keeps OpenAI Responses tool calling non-strict explicitly, matching the intended best-effort behavior while avoiding provider-side schema rewriting surprises.

Test plan

  • clojure -M:test --focus eca.llm-providers.openai-test --focus eca.features.tools.mcp-test --focus eca.features.tools.util-test

  • clj-kondo --lint src test dev integration-test (reports existing warnings; no errors)

  • I added a entry in changelog under unreleased section.

  • This is not an AI slop.

🤖 Generated with eca

Route MCP tool calls through the server encoded by the selected full tool name so duplicate short names across servers do not collide. Also emit OpenAI Responses function tools with strict disabled to preserve optional MCP parameters.

🤖 Generated with [eca](https://eca.dev)

Co-Authored-By: eca-agent <git@eca.dev>
@itkonen
Copy link
Copy Markdown
Contributor Author

itkonen commented May 11, 2026

The check failures again look like transient network problems related to dependency downloads.

@zikajk
Copy link
Copy Markdown
Member

zikajk commented May 11, 2026

@itkonen
Are you sure that omitting strict is different from setting it to false? We had some problems previously where OpenAI always wanted to fill in optional arguments, so this can be a really nice improvement.

@itkonen
Copy link
Copy Markdown
Contributor Author

itkonen commented May 11, 2026

@zikajk Yes, there's a real gotcha in the OpenAI APIs:

If you omit strict, the default depends on the API: Responses requests will normalize your schema into strict mode (for example, by setting additionalProperties: false and marking all fields as required), which can make previously optional fields mandatory, while Chat Completions requests remain non-strict by default.

So the Chat Completions API is non-strict by default and Responses API is the opposite. So we need to set it false in the Responses APi to get the same behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants