Preserve query params in remote MCP server URLs#4154
Conversation
9f96879 to
0bfcf1a
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4154 +/- ##
==========================================
- Coverage 68.88% 68.84% -0.04%
==========================================
Files 461 464 +3
Lines 46562 46785 +223
==========================================
+ Hits 32075 32210 +135
+ Misses 11987 11983 -4
- Partials 2500 2592 +92 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0bfcf1a to
a2fb370
Compare
When a remote MCP server is registered with query parameters in the URL (e.g., ?toolsets=core,alerting,...), the transparent proxy was silently dropping them before forwarding requests to the upstream server. The remote server received only the base path and fell back to its default behavior, returning fewer capabilities than configured with no error or warning. Extract the raw query string alongside the path from the registration URL and merge it into every outbound request via a new WithRemoteRawQuery proxy option. Remote query params are prepended before any client-supplied params so operator-configured values take precedence (first-value-wins semantics). Raw string concatenation is used intentionally to preserve literal characters like commas that url.Values.Encode() would percent-encode. Also fix GenerateMCPServerURL to include query parameters in the URL returned to clients for SSE and streamable transports. Add a TransparentProxy.ListenerAddr() method to expose the bound address after start, enabling cross-package tests to use port 0. Add unit tests covering the proxy director merge logic, the HTTPTransport.Start() extraction path, and URL generation. Add an E2E test using a local mock upstream to cover the full CLI→proxy→upstream stack. Closes stacklok#4153 Signed-off-by: Jon Christiansen <467023+theJC@users.noreply.github.com> Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
a2fb370 to
8f316c7
Compare
amirejaz
left a comment
There was a problem hiding this comment.
The PR looks good. Added one comment related to query paramter doubling as it is added to client configuration.
amirejaz
left a comment
There was a problem hiding this comment.
Approved. Thanks for working on this.
|
Can I please put in a plug for a review of #4150 ? With this one that is now merged, and that one, once they are both released, that unblocks us from successfully using Toolhive with the Datadog MCP server. Once both are delivered and we are able to use Toolhive and Datadog's MCP server together, I will do my best to push DD to add Toolhive to their documented client list w/directions as well. |
|
Hey @theJC, just wanted to check in and see how things are going with Toolhive + the Datadog MCP server. Really appreciate you pushing on the Datadog side. If there’s anything you need from us to help move things along. Would be great to see this land 🙂 |
Summary
?toolsets=core,alerting,...), ToolHive was silently dropping them before forwarding requests to the upstream server. The server fell back to its default behavior, returning fewer capabilities than configured — with no error or warning to indicate anything was wrong. For example, registering the Datadog MCP server with explicit toolsets returned ~20 tools (default) instead of ~76 (configured).scheme+hostas itstargetURI, with the path extracted separately asremoteBasePath. Query parameters were never accounted for in this design and were discarded at parse time.RawQueryfrom the registration URL alongside the path and forward it on every outbound request via a newWithRemoteRawQueryproxy option. Remote params are prepended before any client-supplied params (first-value-wins semantics). Raw string concatenation is used intentionally —url.Values.Encode()would percent-encode characters like commas that some APIs (e.g., Datadog'stoolsetsparameter) expect as literals.GenerateMCPServerURLfor SSE and streamable transports.TransparentProxy.ListenerAddr()to expose the bound address after start, enabling cross-package tests to use OS-assigned ports (port 0) without fixed port assignments.Fixes #4153
Type of change
Test plan
task test)task lint-fix)Registered the Datadog MCP server with full toolsets query parameter and verified ~76 tools were available via Claude Code (vs ~20 before the fix).
Changes
pkg/transport/http.goremoteRawQueryfromremoteURL; pass viaWithRemoteRawQueryoptionpkg/transport/proxy/transparent/transparent_proxy.goremoteRawQueryfield,WithRemoteRawQueryoption, director merge logic,ListenerAddr()methodpkg/transport/url.gogenerateRemoteMCPServerURLhelper; preserveRawQueryin generated client URLspkg/transport/url_test.gopkg/transport/proxy/transparent/remote_path_test.goTestRemoteQueryForwarding(4 cases); migrate toListenerAddr()pkg/transport/http_remote_query_test.goTestHTTPTransport_Start_RemoteURLQueryParams— covers theStart()extraction pathtest/e2e/remote_mcp_query_params_test.goDoes this introduce a user-facing change?
Remote MCP servers registered with query parameters in the URL will now have those parameters forwarded to the upstream server on every request, honoring the configuration as specified.
Special notes for reviewers
The merge strategy in the proxy director prepends remote params before any client-supplied params (
remoteRawQuery + "&" + existingQuery). This means if a client happens to send the same key, the server receives it twice — but most HTTP servers adopt first-value-wins semantics, so the operator-configured value takes precedence. The comment in the director explains this explicitly.Generated with Claude Code