Skip to content

[Mirror] Parse port numbers from MCP server URLs in CORS proxy#88

Closed
ngxson wants to merge 4 commits intongxson:masterfrom
eapache:parse-port-from-mcp-urls
Closed

[Mirror] Parse port numbers from MCP server URLs in CORS proxy#88
ngxson wants to merge 4 commits intongxson:masterfrom
eapache:parse-port-from-mcp-urls

Conversation

@ngxson
Copy link
Copy Markdown
Owner

@ngxson ngxson commented Mar 9, 2026

Mirror from upstream PR: ggml-org#20208

Note: @coderabbitai use my 'Mirror PR' preset for reviewing this.

Summary by CodeRabbit

  • New Features

    • Introduced CORS proxy support for web UI to proxy external requests through the server.
    • Enhanced HTTP/HTTPS protocol handling with explicit port parsing and proper scheme-based client selection.
    • Added --webui-mcp-proxy server startup option to enable/disable proxy functionality.
  • Tests

    • Added comprehensive test coverage for proxy functionality.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Port extraction logic added to HTTP URL parsing with default values (80 for HTTP, 443 for HTTPS). Proxy constructor updated to accept scheme and timeout parameters. HTTPS detection switched from port-based (443) to scheme-based comparison. Unit tests introduced for proxy functionality.

Changes

Cohort / File(s) Summary
HTTP URL Port Parsing
common/http.h
Added port field to common_http_url struct; parsing now extracts port from host with scheme-based defaults (80 for HTTP, 443 for HTTPS) and includes port in client request URL construction.
Proxy Constructor Signature
tools/server/server-cors-proxy.h, tools/server/server-models.h
Updated server_http_proxy constructor to include scheme parameter and additional timeout/stop-signal parameters (timeout_read, timeout_write, should_stop) between host/port and existing fields.
Proxy Implementation
tools/server/server-models.cpp
Proxy instantiation now passes scheme ("http") and full request context to constructor; HTTPS client initialization switched from port == 443 check to scheme == "https" comparison for correctness.
Proxy Testing
tools/server/tests/unit/test_proxy.py, tools/server/tests/utils.py
Added autouse fixture to initialize test server and three unit tests: test_mcp_no_proxy (expects 404), test_mcp_proxy (proxies to external URL), test_mcp_proxy_custom_port (proxies to custom port). Added webui_mcp_proxy boolean parameter to ServerProcess class for test configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops with glee at ports now parsed with care,
Schemes declared, no magic port-guessing affair!
From 80 to 443, defaults ring true,
HTTPS knows itself—no more tricks to pursue. 🔐
Tests bloom bright as proxy paths align,
A well-routed warren, simply divine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: parsing port numbers from MCP server URLs in the CORS proxy implementation.
Description check ✅ Passed The description is minimal but adequately identifies this as a mirror PR with a link to the upstream source, meeting basic requirements for a mirror PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/server/server-models.cpp (1)

1083-1091: ⚠️ Potential issue | 🟠 Major

Preserve :port in the forwarded Host header.

Now that explicit ports are parsed and passed through, the Host rewrite still strips the non-default port. A proxied request like http://127.0.0.1:8123/... will be forwarded as Host: 127.0.0.1, which can break upstream routing for the very custom-port case this patch is enabling. Either let httplib synthesize Host or include :port when the port is non-default.

🔧 Suggested fix
         for (const auto & [key, value] : headers) {
             if (key == "Accept-Encoding") {
                 // disable Accept-Encoding to avoid compressed responses
                 continue;
             }
             if (key == "Host" || key == "host") {
-                req.set_header(key, host);
+                const bool default_port =
+                    (scheme == "http"  && port == 80) ||
+                    (scheme == "https" && port == 443);
+                req.set_header(key, default_port ? host : host + ":" + std::to_string(port));
             } else {
                 req.set_header(key, value);
             }
         }

Also applies to: 1155-1162

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/server/server-models.cpp` around lines 1083 - 1091, The Host header
rewrite is stripping explicit ports; when forwarding, preserve the original
:port for non-default ports or omit Host so httplib can synthesize it. Update
the code that builds/overrides headers (uses the parameters scheme, host, port
and the headers map) to either remove any explicit Host entry from headers so
httplib will set it, or construct Host as host + ":" + std::to_string(port) when
port is not the default for the scheme (default 80 for "http", 443 for "https");
apply the same change in the other occurrence noted (the block around the
symbols mentioned at lines ~1155-1162).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/server/tests/unit/test_proxy.py`:
- Around line 27-30: The test currently proxies to http://example.com which
creates an external dependency and can hang; instead spin up a tiny local HTTP
server inside the test (e.g., using Python's http.server.HTTPServer in a
background thread) that returns a small known body like "Example Domain", then
build the proxy URL by embedding that local server's host:port into the existing
url variable (replace the hardcoded example.com with the local server address)
so the cors-proxy routes to the local server, call requests.get(url, timeout=2)
to avoid hanging, and ensure you cleanly shutdown the local HTTPServer/thread
after the assertion so server.server_host/server.server_port and the local
server lifecycle are handled within the test.
- Around line 1-2: Replace the wildcard import "from utils import *" with
explicit imports of the symbols used in this test (e.g., ServerPreset and
requests) so Ruff F403 is avoided and the test is self-contained; update the
import line to "from utils import ServerPreset, requests" (and add any other
specific names referenced in this file) and remove the star import.

---

Outside diff comments:
In `@tools/server/server-models.cpp`:
- Around line 1083-1091: The Host header rewrite is stripping explicit ports;
when forwarding, preserve the original :port for non-default ports or omit Host
so httplib can synthesize it. Update the code that builds/overrides headers
(uses the parameters scheme, host, port and the headers map) to either remove
any explicit Host entry from headers so httplib will set it, or construct Host
as host + ":" + std::to_string(port) when port is not the default for the scheme
(default 80 for "http", 443 for "https"); apply the same change in the other
occurrence noted (the block around the symbols mentioned at lines ~1155-1162).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79737ddf-b7bc-4eb9-b211-2538ea11f4db

📥 Commits

Reviewing files that changed from the base of the PR and between e22cd0a and 192c1a5.

📒 Files selected for processing (6)
  • common/http.h
  • tools/server/server-cors-proxy.h
  • tools/server/server-models.cpp
  • tools/server/server-models.h
  • tools/server/tests/unit/test_proxy.py
  • tools/server/tests/utils.py

Comment on lines +1 to +2
import pytest
from utils import *
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace the star import; it already trips Ruff.

from utils import * introduces the F403 error here and also obscures where ServerPreset and requests come from. Import those names explicitly so this test stays lint-clean and self-contained.

🧹 Suggested fix
 import pytest
-from utils import *
+import requests
+
+from utils import ServerPreset
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
from utils import *
import pytest
import requests
from utils import ServerPreset
🧰 Tools
🪛 Ruff (0.15.4)

[error] 2-2: from utils import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/server/tests/unit/test_proxy.py` around lines 1 - 2, Replace the
wildcard import "from utils import *" with explicit imports of the symbols used
in this test (e.g., ServerPreset and requests) so Ruff F403 is avoided and the
test is self-contained; update the import line to "from utils import
ServerPreset, requests" (and add any other specific names referenced in this
file) and remove the star import.

Comment on lines +27 to +30
url = f"http://{server.server_host}:{server.server_port}/cors-proxy?url=http://example.com"
res = requests.get(url)
assert res.status_code == 200
assert "Example Domain" in res.text
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid a live internet dependency in this test.

Proxying http://example.com makes this unit test flaky in offline or firewalled CI, and the bare requests.get can hang indefinitely if the network stalls. Please proxy to a tiny local HTTP server instead and keep a short client timeout.

🧰 Tools
🪛 Ruff (0.15.4)

[error] 28-28: requests may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/server/tests/unit/test_proxy.py` around lines 27 - 30, The test
currently proxies to http://example.com which creates an external dependency and
can hang; instead spin up a tiny local HTTP server inside the test (e.g., using
Python's http.server.HTTPServer in a background thread) that returns a small
known body like "Example Domain", then build the proxy URL by embedding that
local server's host:port into the existing url variable (replace the hardcoded
example.com with the local server address) so the cors-proxy routes to the local
server, call requests.get(url, timeout=2) to avoid hanging, and ensure you
cleanly shutdown the local HTTPServer/thread after the assertion so
server.server_host/server.server_port and the local server lifecycle are handled
within the test.

@ngxson ngxson closed this Mar 9, 2026
@eapache eapache deleted the parse-port-from-mcp-urls branch March 11, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants