Skip to content

UI : fix SSE transport detection and routing through CORS proxy. Assi… - #24500

Merged
ServeurpersoCom merged 2 commits into
ggml-org:masterfrom
hrpnr:fix-ui-mcp-sse-routing
Jun 17, 2026
Merged

UI : fix SSE transport detection and routing through CORS proxy. Assi…#24500
ServeurpersoCom merged 2 commits into
ggml-org:masterfrom
hrpnr:fix-ui-mcp-sse-routing

Conversation

@hrpnr

@hrpnr hrpnr commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

DuckDuckGo Search (DDGS) MCP Integration Walkthrough

This document summarizes the changes made to enable connecting the ddgs MCP server to the llama.cpp Web UI using SSE transport over the CORS proxy.


1. What was accomplished

We successfully configured the ddgs MCP server to run over SSE transport and integrated it with the llama.cpp Web UI. The Web UI can now connect to the MCP server through llama-server's built-in CORS proxy, discover tools, and execute queries.


2. Why changes were required

Standard Stdio Transport vs. Browser Constraints

  • ddgs mcp runs on stdio transport by default. However, the browser-based Web UI cannot communicate with local processes via stdio and requires HTTP-based transports (SSE / StreamableHTTP).
  • Running ddgs as an SSE server exposed it at http://127.0.0.1:8000/sse.

Transport Selection Bug

  • Svelte's createTransport was hardcoded to only check for WebSocket URLs and defaulted all other HTTP schemes to StreamableHTTPClientTransport. The fallback block for SSEClientTransport was never executed since the StreamableHTTPClientTransport constructor does not throw synchronous network errors.

Relative Endpoint Resolution Bug over CORS Proxy

  • The browser established the SSE connection correctly through the CORS proxy (http://localhost:8080/cors-proxy?url=...).
  • However, when the server responded with a relative message endpoint (e.g., /messages/), the SDK resolved this relative path against the proxy URL, mapping it to the local origin (http://localhost:8080/messages/) instead of wrapping the target through the CORS proxy. This resulted in 404 Not Found errors from llama-server.

3. How it was implemented

Step 1: SSE Transport Detection

We modified detectMcpTransportFromUrl to classify URLs ending with /sse, /sse/, or containing /sse? as MCPTransportType.SSE:

if (
	normalized.endsWith('/sse') ||
	normalized.endsWith('/sse/') ||
	normalized.includes('/sse?')
) {
	return MCPTransportType.SSE;
}

Step 2: Explicit SSE Client Transport Instantiation

We updated createTransport to explicitly check for MCPTransportType.SSE and return SSEClientTransport instead of falling through:

if (config.transport === MCPTransportType.SSE) {
	const url = useProxy ? buildProxiedUrl(config.url) : new URL(config.url);
	// ... (diagnostic fetch setup)
	return {
		transport: new SSEClientTransport(url, {
			requestInit,
			fetch: diagnosticFetch,
			eventSourceInit: { fetch: diagnosticFetch }
		}),
		type: MCPTransportType.SSE,
		stopPhaseLogging
	};
}

Step 3: Fetch Interceptor & CORS Proxy URL Rewrite

We added URL rewrite logic at the beginning of the custom fetch handler inside createDiagnosticFetch. If proxy routing is active and an outgoing request erroneously targets the local origin without /cors-proxy, the interceptor reconstructs the destination relative to the original MCP server and routes it through the CORS proxy:

if (useProxy && typeof window !== 'undefined') {
	let requestUrlStr = '';
	if (typeof input === 'string') {
		requestUrlStr = input;
	} else if (input instanceof URL) {
		requestUrlStr = input.href;
	}

	if (requestUrlStr) {
		const parsedRequestUrl = new URL(requestUrlStr, window.location.origin);
		if (
			parsedRequestUrl.origin === window.location.origin &&
			!parsedRequestUrl.pathname.includes('/cors-proxy')
		) {
			const originalConfigUrl = new URL(config.url);
			const realTargetUrl = new URL(
				parsedRequestUrl.pathname + parsedRequestUrl.search,
				originalConfigUrl.origin
			);
			const proxiedUrl = buildProxiedUrl(realTargetUrl.href);

			if (typeof input === 'string') {
				input = proxiedUrl.href;
			} else if (input instanceof URL) {
				input = proxiedUrl;
			}
		}
	}
}

Step 4: UI Packaging

We compiled the changes with:

cd tools/ui
npm run build

This wrote the production static bundle to tools/ui/dist, enabling llama-server to serve it using the --path tools/ui/dist flag.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, AI built the code with human direction and confirmation

@hrpnr
hrpnr requested a review from a team as a code owner June 12, 2026 05:53
Comment thread tools/ui/src/lib/utils/mcp.ts Outdated
Comment thread tools/ui/src/lib/services/mcp.service.ts Outdated
@allozaur
allozaur requested a review from ServeurpersoCom June 15, 2026 07:48
@allozaur

Copy link
Copy Markdown
Contributor

@hrpnr please fix linting issues

@hrpnr
hrpnr force-pushed the fix-ui-mcp-sse-routing branch from c4439a2 to 794af40 Compare June 16, 2026 08:11
@hrpnr

hrpnr commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@allozaur could you re-review? just fixed linting issues.

@hrpnr
hrpnr requested a review from allozaur June 16, 2026 09:10
@hrpnr
hrpnr requested a review from allozaur June 17, 2026 06:03
@allozaur

Copy link
Copy Markdown
Contributor

@ServeurpersoCom please check this when u have a moment 😄

@ServeurpersoCom ServeurpersoCom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@ServeurpersoCom
ServeurpersoCom merged commit bae36ef into ggml-org:master Jun 17, 2026
6 checks passed
@engrtipusultan

engrtipusultan commented Jun 17, 2026

Copy link
Copy Markdown

Hello @hrpnr I have installed duckduckgo-mcp-server

When I run it with transport sse. It does not work. Can you confirm how does fix apply or I am doing something wrong.

bash  duckduckgo-mcp-server --transport sse
DuckDuckGo MCP Server initialized:
  SafeSearch: MODERATE (kp=-1)
  Default Region: none
  Fetch backend: httpx
Starting DuckDuckGo MCP Server with sse transport
SSE endpoint: http://127.0.0.1:8000/sse
INFO:     Started server process [109640]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:34158 - "POST /sse HTTP/1.1" 405 Method Not Allowed

llama.cpp server.

13:20:50
Creating transport for http://127.0.0.1:8000/sse
13:20:50
Transport ready (streamable_http)
13:20:50
Sending initialize request...
13:20:50
HTTP POST http://127.0.0.1:8000/sse
details

13:20:50
HTTP 405 POST http://127.0.0.1:8000/sse (10ms)
details

{
  "response": {
    "url": "http://127.0.0.1:8000/sse",
    "status": 405,
    "statusText": "Method Not Allowed",
    "headers": {
      "content-length": "18",
      "content-type": "text/plain; charset=utf-8"
    },
    "durationMs": 10
  }
}

13:20:50
Protocol error: Streamable HTTP error: Error POSTing to endpoint: Method Not Allowed
details

{
  "error": {
    "name": "Error",
    "message": "Streamable HTTP error: Error POSTing to endpoint: Method Not Allowed",
    "stack": "kv@http://127.0.0.1:8080/_app/immutable/bundle.DEyh8LrV.js:151:1316\nsend@http://127.0.0.1:8080/_app/immutable/bundle.DEyh8LrV.js:151:6899\n"
  }
}

13:20:50
Connection failed during initialize: Streamable HTTP error: Error POSTing to endpoint: Method Not Allowed
details

{
  "error": {
    "name": "Error",
    "message": "Streamable HTTP error: Error POSTing to endpoint: Method Not Allowed",
    "stack": "kv@http://127.0.0.1:8080/_app/immutable/bundle.DEyh8LrV.js:151:1316\nsend@http://127.0.0.1:8080/_app/immutable/bundle.DEyh8LrV.js:151:6899\n"
  },
  "config": {
    "serverName": "57af7ec2-1ec7-4aa5-9690-5d31af43a18a",
    "configuredUrl": "http://127.0.0.1:8000/sse",
    "effectiveUrl": "http://127.0.0.1:8000/sse",
    "transportType": "streamable_http",
    "useProxy": false,
    "headers": {}
  },
  "browser": {
    "location": "http://127.0.0.1:8080/#/mcp-servers",
    "origin": "http://127.0.0.1:8080",
    "protocol": "http:",
    "isSecureContext": true,
    "targetOrigin": "http://127.0.0.1:8000",
    "targetProtocol": "http:",
    "sameOrigin": false,
    "useProxy": false
  },
  "hints": [
    "This is a cross-origin browser request. If the server is reachable from curl or Node but not from the browser, missing CORS headers are the most likely cause."
  ]
}

@hrpnr

hrpnr commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Hi @engrtipusultan , i use DDGS from https://github.com/deedy5/ddgs

1. Install dependencies (one-time, )

pip install -U ddgs[mcp] mcp-proxy

2. Start mcp-proxy wrapping ddgs

& "path\to\mcp-proxy.exe" `
  --port 8000 `
  --allow-origin "http://localhost:8080" `
  -- "path\to\ddgs.exe" mcp

3. Start llama-server (pointing to the same 8080 or other preferred port)

4. In the UI:

image

http://127.0.0.1:8000/sse

image

Note: ddgs mcp is stdio-only and will always "Abort!" when run directly in a terminal - that's expected. mcp-proxy handles spawning it correctly as a subprocess.

@hrpnr
hrpnr deleted the fix-ui-mcp-sse-routing branch June 17, 2026 13:36
adrianhoehne pushed a commit to adrianhoehne/llama.cpp that referenced this pull request Jul 5, 2026
ggml-org#24500)

* UI : fix SSE transport detection and routing through CORS proxy. Assisted-by: Antigravity

* ui : replace magic strings with constants in MCP transport handling
zommiommy pushed a commit to zommiommy/llama.cpp that referenced this pull request Aug 18, 2026
ggml-org#24500)

* UI : fix SSE transport detection and routing through CORS proxy. Assisted-by: Antigravity

* ui : replace magic strings with constants in MCP transport handling
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.

4 participants