Skip to main content
Glama
yangsheng6810

Department Web-Search MCP Gateway

Department Web-Search MCP Gateway

Ein selbst gehosteter Web-Suchdienst, den die gesamte Abteilung nutzen kann. Er verwendet eine einzelne angemeldete Browsersitzung (einen gemeinsamen Dienst-Account) wieder, sodass Intranet-/SSO-/Zustimmungs-Logins einmalig behandelt werden – jeder Client ruft einfach ein web_search-Tool auf, ohne Login pro Benutzer oder API-Schlüssel.

Jeder MCP-Client verbindet sich mit einer URL:

  • Chatbox (≥1.14)

  • OpenCode – lokal, auf einem gemeinsamen Server oder via vscode-remote

  • Claude Code (und andere Coding-Agenten, die MCP sprechen)

Es ist das T1 „zentralisierte Such-Gateway“ aus den Forschungsnotizen: eine interne Maschine + ein gemeinsames Chrome-Profil + ein HTTP-MCP-Endpunkt.


Wie es funktioniert

Chatbox / OpenCode(local|server|vscode-remote) / Claude Code
        │  remote MCP (Streamable HTTP, /mcp) — same URL for everyone
        ▼
┌──────────────────────────────────────────────┐
│  Gateway (this service, Node + Express)       │
│   • Bearer token (optional) + Host validation │
│   • MCP tools: web_search / read_webpage      │
└──────────────────────────────────────────────┘
        │  connectOverCDP / launchPersistentContext
        ▼
┌──────────────────────────────────────────────┐
│  Chrome (persistent profile, shared account)  │  ← logged in ONCE via `npm run login`
│   • per-request new tab (isolation)           │
│   • concurrency cap + timeouts                │
└──────────────────────────────────────────────┘
        │  optional fallback
        ▼
   SearXNG (if SEARXNG_URL set) — public-search fallback when browser returns nothing

createMcpHandler bedient sowohl MCP-Clients der 2025-Ära als auch der 2026-Ära am selben /mcp-Endpunkt, sodass die Kompatibilität des Client-Transports kein Problem darstellt.


Related MCP server: local-web-search-service

Headless-Linux-Server + ein Windows-PC für den Login

Server haben keine GUI, aber ein Mensch kann sich an einem Windows-PC anmelden. Wählen Sie einen Modus in .env (BROWSER_MODE) – der Code ist identisch, nur die Konfiguration unterscheidet sich.

⚠️ Kopieren Sie KEIN Windows-Chrome-Profil-Verzeichnis nach Linux. Chromium verschlüsselt Cookies mit betriebssystemgebundenen Schlüsseln (DPAPI unter Windows, Keyring/„peanuts“ unter Linux), sodass ein kopiertes Profil den Login stillschweigend verliert. Verwenden Sie einen der unten stehenden betriebssystemübergreifend sicheren Modi.

Modus C – BROWSER_MODE=cdp (empfohlen): Linux-Gateway verbindet sich mit dem Windows-Browser

  • Windows-PC (bleibt eingeschaltet): Einmal mit dem gemeinsamen Account anmelden, dann Chrome mit einem lokalen Debug-Port laufen lassen:

    chrome --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 ^
           --user-data-dir=C:\dept-search-profile
  • Tragen Sie diesen Port sicher mit einem SSH-Reverse-Tunnel zum Linux-Server (auf dem Windows-PC ausführen; Win10/11 hat OpenSSH an Bord):

    ssh -R 9222:127.0.0.1:9222 linuxuser@gateway.server
  • Linux-Server: .env → BROWSER_MODE=cdp, CDP_ENDPOINT=http://127.0.0.1:9222 (lokal auf dem Server, zurück zum Windows-Browser getunnelt). Dann npm start.

  • Der Login bleibt aktiv (Cookies werden aktualisiert, während der Browser verwendet wird); kein Profil-Kopieren; der nicht authentifizierte CDP-Port ist nie im Netzwerk. Nachteil: Windows-PC aus → Suchanfragen schlagen fehl, bis er wieder an ist (verwenden Sie Modus B, falls das nicht akzeptabel ist).

Modus B – BROWSER_MODE=storagestate: Snapshot, Linux autark

  • Windows-PC: npm run login (mit GUI), anmelden, Enter drücken → schreibt auth.json (betriebssystemunabhängiges JSON mit Cookies + localStorage).

  • Kopieren Sie auth.json auf den Linux-Server, setzen Sie BROWSER_MODE=storagestate, STORAGE_STATE_FILE=./auth.json, führen Sie npm start aus. Linux startet seinen eigenen Headless-Browser, der den Snapshot lädt – kein Tunnel, überlebt, wenn der Windows-PC aus ist.

  • Nachteil: ein eingefrorener Snapshot – erneut exportieren, wenn das SSO-Cookie abläuft; enthält nur Cookies + localStorage (nicht IndexedDB/Client-Zertifikate) – für die meisten SSOs ausreichend.

Modus A – BROWSER_MODE=persistent: Windows-PC führt alles aus

  • Falls ein freier Windows-PC als ständig laufender Dienst-Host dienen kann: npm run login dort (legt das Profil an), dann npm start mit BROWSER_MODE=persistent.

  • Linux-Server sind reine Clients, die auf http://<windows-pc>:8787/mcp zeigen.

  • Am einfachsten von allen – kein Tunnel, keine Snapshot-Zeremonie.

Das Onboarding der Clients ist in jedem Modus identisch: Clients zeigen auf die MCP-URL des Gateways; das Gateway spricht mit dem konfigurierten Browser-Modus.


Inbetriebnahme-Runbook – Modus C (Linux-Gateway + Windows-Browser)

Das bestätigte Setup: Ein Linux-Server betreibt das Gateway; ein ständig eingeschalteter Windows-PC betreibt einen echten Chrome (einmal angemeldet) und einen SSH-Reverse-Tunnel. Es wird kein Browser auf dem Linux-Server heruntergeladen (nur playwright-core).

Windows-PC (einmalig, dann laufen lassen) – siehe windows/README.md

  1. windows\start-browser.ps1 → dedizierter Chrome auf 127.0.0.1:9222, Profil C:\dept-search-profile. Mit dem gemeinsamen Account anmelden (SSO/2FA). Offen lassen.

  2. $env:GATEWAY_SSH = "linuxuser@gateway.server"; windows\start-tunnel.ps1 → hält ssh -R 9222:127.0.0.1:9222 gateway aufrecht, automatische Wiederverbindung.

  3. Beide als Geplante Aufgaben einrichten (Beim Start / Bei Anmeldung, ausführen, ob Benutzer angemeldet oder nicht), sodass der PC ein selbstheilendes Browser-Gerät ist.

Linux-Gateway-Server (diese Maschine)

cd dept-web-search-gateway
cp .env.example .env
# edit .env:
#   BROWSER_MODE=cdp                       (default)
#   CDP_ENDPOINT=http://127.0.0.1:9222     (the tunneled port, local on this server)
#   HOST=0.0.0.0
#   ALLOWED_HOSTS=search.internal,localhost   # hostnames clients will use
#   GATEWAY_TOKEN=...                      (optional; else rely on network ACL)
npm install                 # lean — playwright-core, no Chromium download
npm run build               # typecheck
npm start                   # dev (tsx); or `npm run build && npm run start:prod`
curl http://127.0.0.1:8787/health         # {"ok":true,...}

Weisen Sie Clients auf http://<dieser-server>:8787/mcp (siehe Client-Onboarding).

Tunnel auf Richtigkeit prüfen

Auf dem Linux-Server:

curl -s http://127.0.0.1:9222/json/version   # Chrome's JSON → tunnel + Chrome are up

Leer / Verbindung abgelehnt → der Windows-Chrome oder der Reverse-Tunnel läuft noch nicht; web_search wird fehlschlagen, bis er läuft.


Einrichtung (einmalig)

cd dept-web-search-gateway
npm install                 # also runs `playwright install chromium`
cp .env.example .env       # then edit .env (see knobs below)

1) Gemeinsamen Login einrichten (der Kernpunkt)

Einmalig auf einer Maschine mit Bildschirm (oder unter xvfb-run -a) ausführen:

npm run login
# or, for an internal portal:
LOGIN_START_URL=https://wiki.internal npm run login

Ein echtes Chrome-Fenster öffnet sich. Mit dem gemeinsamen Dienst-Account anmelden (SSO / 2FA), bestätigen, dass Sie bei der Suchmaschine / dem Portal angemeldet sind, dann das Fenster schließen. Die Sitzung wird in BROWSER_PROFILE_DIR (Standard ./.profile) gespeichert und von nun an vom Headless-Gateway wiederverwendet.

Server sind headless? Führen Sie den Schritt npm run login auf dem Windows-PC aus, wählen Sie dann Modus B (kopieren Sie auth.json nach Linux) oder Modus C (SSH-Tunnel CDP nach Linux), wie oben unter „Headless-Linux-Server + ein Windows-PC für den Login“ beschrieben. Erneuerung, wenn die SSO-Sitzung abläuft: Modus A/B → npm run login erneut ausführen (und für B auth.json erneut kopieren); Modus C → einfach im Windows-Chrome erneut anmelden.

2) Gateway starten

npm start                   # dev (tsx)
# or production:
npm run build && npm run start:prod

Sie sollten sehen:

[server] MCP gateway on http://0.0.0.0:8787/mcp  (engine=bing)
[server] profile=./.profile

Client-Onboarding (diese Informationen an Ihre Kollegen weitergeben)

Ersetzen Sie search.internal / 8787 durch Ihren Gateway-Host/Port. Alle verwenden die gleiche URL.

Chatbox (≥1.14)

Einstellungen → MCP → Server hinzufügen → Remote / URL wählen:

  • URL: http://search.internal:8787/mcp

  • (falls GATEWAY_TOKEN gesetzt ist) einen Header Authorization: Bearer <TOKEN> hinzufügen, wo der Client dies unterstützt; andernfalls mit Netzwerk-ACL schützen.

Ein-Klick-Deep-Link (auf Ihre Intranet-Seite setzen):

chatbox://mcp/install?server=<base64 of {"name":"websearch","url":"http://search.internal:8787/mcp"}>

OpenCode – alle drei Varianten

Zu opencode.json (Projekt) oder ~/.config/opencode/opencode.json (global) hinzufügen:

{
  "mcp": {
    "websearch": {
      "type": "remote",
      "url": "http://search.internal:8787/mcp",
      "enabled": true
    }
  }
}
  • Lokales opencode: gleicher Ausschnitt, Host = 127.0.0.1 oder der Gateway-Host.

  • Server opencode: der Prozess läuft auf dem Server → direkt auf die interne URL des Gateways zeigen (der Server muss sie über das interne Netzwerk erreichen können).

  • vscode-remote opencode: der Prozess läuft auf dem entfernten Host → auf die interne URL des Gateways zeigen (von diesem Host aus erreichbar). Kein Tunnel nötig, da das Gateway im internen Netzwerk ist.

  • Überprüfen: opencode mcp list.

Claude Code

claude mcp add --transport http websearch http://search.internal:8787/mcp
# with a token:
claude mcp add --transport http --header "Authorization: Bearer <TOKEN>" \
  websearch http://search.internal:8787/mcp

Cline / Cursor / andere

Wenn sie Remote-MCP unterstützen, auf die gleiche URL zeigen. Wenn sie nur stdio unterstützen, einen kleinen lokalen Shim ausführen, der das HTTP-Gateway aufruft (ein 20-zeiliger Wrapper) – hier nicht enthalten, aber trivial hinzuzufügen.


Verfügbare Tools

Tool

Argumente

Rückgabe

web_search

query (str, erforderlich), engine (bing|google|duck|custom, optional)

Liste von {title, url, snippet} als Text + JSON

read_webpage

url (str, erforderlich)

# title + Haupttext (≤20k Zeichen), Login/SSO behandelt

Der Agent in Chatbox/OpenCode/Claude Code wird web_search aufrufen, wenn er aktuelle Informationen benötigt, und read_webpage, um eine bestimmte Seite zu lesen – keine zusätzliche Verkabelung nötig.


Konfigurationsoptionen (.env)

Variable

Standard

Bedeutung

HOST

0.0.0.0

Bind-Adresse. 127.0.0.1 = nur localhost (+automatischer DNS-Rebinding-Schutz)

ALLOWED_HOSTS

—

Komma-getrennte Liste von Hostnamen, die Clients verwenden (ermöglicht Host-Header-Validierung). Setzen, wenn 0.0.0.0 gebunden wird

PORT

8787

Lauschport

GATEWAY_TOKEN

—

Falls gesetzt, Authorization: Bearer <token> erforderlich. Leer = keine Authentifizierung (nur Netzwerk-ACL)

BROWSER_MODE

cdp

persistent / storagestate / cdp – siehe Topologie-Abschnitt

CDP_ENDPOINT

http://127.0.0.1:9222

CDP-Modus: die CDP-URL des angehängten Browsers (normalerweise ein getunnelter Port)

STORAGE_STATE_FILE

./auth.json

Storagestate-Modus: auf Windows exportierter Login-Snapshot, hierher kopiert

BROWSER_PROFILE_DIR

./.profile

Persistent-Modus: Chrome-Profil, das den gemeinsamen Login enthält

HEADLESS

true

false nur zum Debuggen

MAX_CONCURRENT_PAGES

4

Parallelitätsgrenze (ein Chrome, isolierte Tabs)

PAGE_TIMEOUT_MS

20000

Hartes Timeout pro Seite

SEARCH_ENGINE

bing

bing (optimierter Extractor) / google / duck / custom

SEARCH_URL_TEMPLATE

—

Benutzerdefinierte URL mit {q}-Platzhalter, z.B. https://wiki.internal/search?q={q} (überschreibt Engine-URL)

RESULT_COUNT

10

Ergebnisse pro Abfrage

SEARXNG_URL

—

Optionaler öffentlicher Such-Fallback (benötigt ausgehenden Internetzugang), z.B. http://127.0.0.1:8080


Hinzufügen eines benutzerdefinierten Extractors für ein internes Portal

extractBing in src/tools.ts ist auf Bings DOM abgestimmt. Für ein internes Portal fügen Sie extractPortal(page, count) hinzu und wählen es über den Engine-Namen in searchWithBrowser aus. Der generische extractGeneric gibt bereits Anker-Links + umgebenden Text als brauchbaren Fallback für unbekannte DOMs zurück.


Sicherheits- & Betriebshinweise

  • Binden & freigeben: Bevorzugen Sie, das Gateway im internen Netzwerk zu belassen. Wenn Sie 0.0.0.0 binden, setzen Sie ALLOWED_HOSTS und verwenden Sie eine Firewall / Netzwerk-ACL, oder setzen Sie GATEWAY_TOKEN, oder stellen Sie es hinter einen SSO-Reverse-Proxy.

  • Gemeinsames Profil = gemeinsame Identität: Jede Suche wird dem gemeinsamen Account zugeschrieben. Für einen Abteilungs-Dienst-Account in Ordnung; prüfen Sie, ob das Ziel pro Benutzer prüft oder ein Kontingent hat.

  • Sitzungsverlängerung: Führen Sie npm run login erneut aus, wenn das SSO abläuft. Erwägen Sie einen wöchentlichen Cron-Job, der eine Erinnerung per E-Mail sendet, oder eine Health-Probe, die eine Login-Wand erkennt (read_webpage auf einer bekannten Login-erforderlichen URL gibt den Text der Login-Seite zurück).

  • Parallelität / Skalierung: Ein Chrome mit isolierten Tabs bewältigt eine kleine Abteilung. Wachsen Sie zu einem Browser-Pool (N persistente Kontexte), wenn es gesättigt ist – die withPage-Nahtstelle ist der einzige Ort, der geändert werden muss.

  • Headless Chrome unter Linux: --no-sandbox --disable-dev-shm-usage sind bereits gesetzt (containerfreundlich).


Entwicklung & Testen

  • Probes befinden sich in scripts/ und importieren aus ../dist/, also zuerst bauen: npm run build.

    • scripts/probe-search.mjs "<Abfrage>" – treibt den gemeinsamen Browser direkt an (umgeht MCP); validiert CDP-Attach + den Bing-Extractor.

    • scripts/probe-mcp.mjs <url> "<Abfrage>" – verbindet sich mit einem laufenden Gateway über Streamable HTTP (der echte Client-Pfad), listet Tools auf, ruft web_search auf. Starten Sie zuerst das Gateway: node --env-file=.env dist/server.js.

  • Dev-Modus (npm start → tsx): Unter npm 11 wird das transitive esbuild-Postinstall von tsx standardmäßig durch allow-scripts blockiert. Einmal genehmigen (npm approve-scripts) oder einfach den kompilierten Pfad überall verwenden: npm run build && node --env-file=.env dist/server.js.


Status

Hierbei handelt es sich um ein überprüfbares PoC / Skelett — abgestimmt auf die v2 MCP SDK API (@modelcontextprotocol/server 2.x, createMcpHandler / createMcpExpressApp / requireBearerAuth / toNodeHandler) und Playwrights persistent-context API. Vor dem Produktionseinsatz: exakte Abhängigkeitsversionen festlegen, Tests hinzufügen und die Authentifizierungsschicht (JWT / Introspection anstelle eines statischen Tokens) absichern, falls Sie sie über ein vertrauenswürdiges internes Netzwerk hinaus freigeben.

Der Designkontext (Mode A/B/C-Topologien, die betriebssystemübergreifende Cookie-Verschlüsselungsfalle, SearXNG-Grenzen) befindet sich im obigen Abschnitt „Headless Linux servers + a Windows PC for login“.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers