Skip to content

Preserve query params in remote MCP server URLs#4154

Merged
amirejaz merged 2 commits intostacklok:mainfrom
theJC:fix/remote-url-query-params
Mar 16, 2026
Merged

Preserve query params in remote MCP server URLs#4154
amirejaz merged 2 commits intostacklok:mainfrom
theJC:fix/remote-url-query-params

Conversation

@theJC
Copy link
Copy Markdown
Contributor

@theJC theJC commented Mar 15, 2026

Summary

  • Why: When a remote MCP server is registered with query parameters in the URL (e.g., ?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).
  • The transparent proxy was built to use only scheme+host as its targetURI, with the path extracted separately as remoteBasePath. Query parameters were never accounted for in this design and were discarded at parse time.
  • Extract RawQuery from the registration URL alongside the path and forward it on every outbound request via a new WithRemoteRawQuery proxy 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's toolsets parameter) expect as literals.
  • Also preserve query parameters in the URL returned to clients by GenerateMCPServerURL for SSE and streamable transports.
  • Add 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

  • Bug fix

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)
  • Manual testing (describe below)

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

File Change
pkg/transport/http.go Extract remoteRawQuery from remoteURL; pass via WithRemoteRawQuery option
pkg/transport/proxy/transparent/transparent_proxy.go Add remoteRawQuery field, WithRemoteRawQuery option, director merge logic, ListenerAddr() method
pkg/transport/url.go Extract generateRemoteMCPServerURL helper; preserve RawQuery in generated client URLs
pkg/transport/url_test.go Add 3 cases covering query params in streamable and SSE generated URLs
pkg/transport/proxy/transparent/remote_path_test.go Add TestRemoteQueryForwarding (4 cases); migrate to ListenerAddr()
pkg/transport/http_remote_query_test.go New: TestHTTPTransport_Start_RemoteURLQueryParams — covers the Start() extraction path
test/e2e/remote_mcp_query_params_test.go New: E2E test using a local mock upstream to verify full CLI→proxy→upstream query forwarding

Does 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

@github-actions github-actions bot added the size/M Medium PR: 300-599 lines changed label Mar 15, 2026
@theJC theJC force-pushed the fix/remote-url-query-params branch from 9f96879 to 0bfcf1a Compare March 15, 2026 19:56
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 15, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 15, 2026

Codecov Report

❌ Patch coverage is 63.63636% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.84%. Comparing base (ba38a12) to head (ad2ebf8).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
...g/transport/proxy/transparent/transparent_proxy.go 61.53% 1 Missing and 4 partials ⚠️
pkg/transport/url.go 68.75% 3 Missing and 2 partials ⚠️
pkg/transport/http.go 50.00% 0 Missing and 2 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@theJC theJC force-pushed the fix/remote-url-query-params branch from 0bfcf1a to a2fb370 Compare March 15, 2026 21:29
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 15, 2026
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>
@theJC theJC force-pushed the fix/remote-url-query-params branch from a2fb370 to 8f316c7 Compare March 15, 2026 22:31
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 15, 2026
Copy link
Copy Markdown
Contributor

@amirejaz amirejaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR looks good. Added one comment related to query paramter doubling as it is added to client configuration.

Comment thread pkg/transport/url.go
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 16, 2026
Copy link
Copy Markdown
Contributor

@amirejaz amirejaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Thanks for working on this.

@amirejaz amirejaz merged commit a0f2504 into stacklok:main Mar 16, 2026
98 of 101 checks passed
@theJC
Copy link
Copy Markdown
Contributor Author

theJC commented Mar 16, 2026

@amirejaz -

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.

@amirejaz
Copy link
Copy Markdown
Contributor

@theJC A new THV release has been deployed, including #4154 and #4150.

@amirejaz
Copy link
Copy Markdown
Contributor

amirejaz commented Apr 1, 2026

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 🙂

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

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remote MCP server URL query parameters are silently dropped, ignoring server configuration

2 participants