Fix MCP routing and OpenAI optional tool params#458
Conversation
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>
|
The check failures again look like transient network problems related to dependency downloads. |
|
@itkonen |
|
@zikajk Yes, there's a real gotcha in the OpenAI APIs:
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. |
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_issuestool, 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 forlinear__list_issuescould 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_issuestool has apriorityfilter that should be optional: omitting it should mean "do not filter by priority." But whenstrictwas 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_issuesandgithub__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 toeca.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. Whenstrictwas 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'spriorityfilter 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
eca.features.tools.mcp/call-tool!now resolves tools against the specified server instead of scanning connected servers by short tool name.strict: false.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 alist_issuestool.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: falseavoids 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:
strict: truewould require a broader schema transformation layer, including handling optional fields as nullable required fields, and would be a larger behavioral change.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-testclj-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