Skip to main content
Glama
yangsheng6810

Department Web-Search MCP Gateway

MCP-шлюз веб-поиска отдела

Самодостаточный веб-поисковый сервис, которым может пользоваться весь отдел. Он использует один сеанс браузера с выполненным входом (общая служебная учётная запись), поэтому вход в интранет / SSO / страницы с согласием обрабатывается один раз — каждый клиент просто вызывает инструмент web_search, без отдельного входа или API-ключа.

Любой MCP-клиент подключается к одному URL:

  • Chatbox (≥1.14)

  • OpenCode — локально, на общем сервере или через vscode-remote

  • Claude Code (и другие агенты программирования, поддерживающие MCP)

Это T1 «централизованный поисковый шлюз» из исследовательских заметок: одна внутренняя машина + один общий профиль Chrome + одна HTTP MCP-конечная точка.


Как это работает

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 обслуживает как MCP-клиентов эпохи 2025, так и эпохи 2026 на одной конечной точке /mcp, поэтому совместимость транспортного протокола клиента не имеет значения.


Related MCP server: local-web-search-service

Серверы без графического интерфейса + ПК с Windows для входа

На серверах нет графического интерфейса, но человек может войти на ПК с Windows. Выберите режим в .env (BROWSER_MODE) — код одинаков, отличается только конфигурация.

⚠️ НЕ копируйте каталог профиля Windows Chrome на Linux. Chromium шифрует файлы cookie с помощью ключей, привязанных к ОС (DPAPI в Windows, keyring/«peanuts» в Linux), поэтому скопированный профиль незаметно теряет файлы cookie. Используйте один из безопасных для разных ОС режимов ниже.

Режим C — BROWSER_MODE=cdp (рекомендуется): шлюз Linux подключается к браузеру Windows

  • ПК с Windows (остаётся включённым): войдите один раз с общей учётной записью, затем оставьте Chrome запущенным с локальным отладочным портом:

chrome --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 ^
       --user-data-dir=C:\dept-search-profile
  • Передайте этот порт на сервер Linux безопасно с помощью обратного SSH-туннеля (запускается на ПК с Windows; в Win10/11 встроен OpenSSH):

ssh -R 9222:127.0.0.1:9222 linuxuser@gateway.server
  • Сервер Linux: .env → BROWSER_MODE=cdp, CDP_ENDPOINT=http://127.0.0.1:9222 (локально на сервере, туннелирован обратно в браузер Windows). Затем npm start.

  • Сеанс остаётся активным (файлы cookie обновляются при использовании браузера); копирование профиля не требуется; неаутентифицированный порт CDP никогда не находится в сети. Недостаток: если ПК с Windows выключен — поиск не работает, пока он не включится (используйте Режим B, если это неприемлемо).

Режим B — BROWSER_MODE=storagestate: снимок, Linux самодостаточен

  • ПК с Windows: npm run login (с графическим интерфейсом), войдите, нажмите Enter → записывается auth.json (независимый от ОС JSON с файлами cookie + localStorage).

  • Скопируйте auth.json на сервер Linux, установите BROWSER_MODE=storagestate, STORAGE_STATE_FILE=./auth.json, запустите npm start. Linux запускает собственный безголовый браузер, загружающий снимок — без туннеля, работает даже при выключенном ПК с Windows.

  • Компромисс: замороженный снимок — повторно экспортируйте, когда истечёт срок действия SSO-куки; содержит только файлы cookie + localStorage (не IndexedDB/клиентские сертификаты) — подходит для большинства SSO.

Режим A — BROWSER_MODE=persistent: ПК с Windows запускает всё

  • Если есть запасной ПК с Windows, который может быть постоянно включённым хостом сервиса: выполните npm run login на нём (создаётся профиль), затем npm start с BROWSER_MODE=persistent.

  • Серверы Linux — это просто клиенты, указывающие на http://<windows-pc>:8787/mcp.

  • Самый простой вариант — без туннеля, без церемоний со снимками.

Подключение клиента одинаково во всех режимах: клиенты указывают на URL MCP-шлюза; шлюз взаимодействует с выбранным режимом браузера.


Инструкция по запуску — Режим C (шлюз Linux + браузер Windows)

Подтверждённая настройка: сервер Linux запускает шлюз; постоянно включённый ПК с Windows запускает настоящий Chrome (один раз выполнен вход) и обратный SSH-туннель. Браузер не загружается на сервер Linux (только playwright-core).

ПК с Windows (один раз, затем оставить работающим) — см. windows/README.md

  1. windows\start-browser.ps1 → выделенный Chrome на 127.0.0.1:9222, профиль C:\dept-search-profile. Войдите с общей учётной записью (SSO/2FA). Оставьте открытым.

  2. $env:GATEWAY_SSH = "linuxuser@gateway.server"; windows\start-tunnel.ps1 → поддерживает ssh -R 9222:127.0.0.1:9222 gateway, автоматически переподключается.

  3. Сделайте оба запланированными задачами (При запуске / При входе в систему, выполнять независимо от того, выполнен ли вход пользователя), чтобы ПК был самовосстанавливающимся браузерным устройством.

Сервер шлюза Linux (эта машина)

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,...}

Укажите клиентам http://<this-server>:8787/mcp (см. Подключение клиентов).

Проверка туннеля

На сервере Linux:

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

Пусто / соединение отклонено → Chrome на Windows или обратный туннель ещё не запущены; web_search будет выдавать ошибку, пока не заработает.


Настройка (однократно)

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

1) Создание общего сеанса входа (ключевой момент)

Запустите один раз на машине с дисплеем (или под xvfb-run -a):

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

Откроется настоящее окно Chrome. Войдите с общей служебной учётной записью (SSO / 2FA), подтвердите, что вы вошли в поисковую систему / портал, затем закройте окно. Сеанс сохраняется в BROWSER_PROFILE_DIR (по умолчанию ./.profile) и будет использоваться безголовым шлюзом в дальнейшем.

Серверы без графического интерфейса? Выполните шаг npm run login на ПК с Windows, затем выберите Режим B (скопируйте auth.json на Linux) или Режим C (SSH-туннель CDP на Linux), как описано в разделе «Серверы без графического интерфейса + ПК с Windows для входа» выше. Обновление при истечении сеанса SSO: Режим A/B → повторно запустите npm run login (и повторно скопируйте auth.json для B); Режим C → просто повторно войдите в Chrome на Windows.

2) Запуск шлюза

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

Вы должны увидеть:

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

Подключение клиентов (дайте эти инструкции коллегам)

Замените search.internal / 8787 на хост/порт вашего шлюза. Все используют один и тот же URL.

Chatbox (≥1.14)

Настройки → MCP → Добавить сервер → выберите Удалённый / URL:

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

  • (если установлен GATEWAY_TOKEN) добавьте заголовок Authorization: Bearer <TOKEN>, если клиент это поддерживает; в противном случае защитите с помощью сетевого ACL.

Глубокая ссылка в один клик (разместите на странице интранета):

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

OpenCode — все три варианта

Добавьте в opencode.json (проект) или ~/.config/opencode/opencode.json (глобальный):

{
  "mcp": {
    "websearch": {
      "type": "remote",
      "url": "http://search.internal:8787/mcp",
      "enabled": true
    }
  }
}
  • Локальный opencode: тот же фрагмент, хост = 127.0.0.1 или хост шлюза.

  • Серверный opencode: процесс выполняется на сервере → указывайте на внутренний URL шлюза напрямую (сервер должен иметь доступ к нему через внутреннюю сеть).

  • vscode-remote opencode: процесс выполняется на удалённом хосте → указывайте на внутренний URL шлюза (доступный с этого хоста). Туннелирование не требуется, так как шлюз находится во внутренней сети.

  • Проверка: 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 / другие

Если они поддерживают удалённый MCP, укажите тот же URL. Если они поддерживают только stdio, запустите крошечную локальную прослойку, которая вызывает HTTP-шлюз (обёртка из 20 строк) — не включено сюда, но добавить тривиально.


Предоставляемые инструменты

Инструмент

Аргументы

Возвращает

web_search

query (строка, обязательно), engine (bing|google|duck|custom, необязательно)

список {title, url, snippet} в виде текста + JSON

read_webpage

url (строка, обязательно)

# title + основной текст (≤20k символов), вход/SSO обработаны

Агент в Chatbox/OpenCode/Claude Code будет вызывать web_search, когда ему нужна свежая информация, и read_webpage для чтения конкретной страницы — никаких дополнительных настроек.


Параметры конфигурации (.env)

Переменная

По умолчанию

Значение

HOST

0.0.0.0

адрес привязки. 127.0.0.1 = только локальный (+автоматическая защита от перепривязки DNS)

ALLOWED_HOSTS

—

список имён хостов через запятую, которые используют клиенты (включает проверку заголовка Host). Установите при привязке к 0.0.0.0

PORT

8787

порт прослушивания

GATEWAY_TOKEN

—

если установлен, требует Authorization: Bearer <token>. Пусто = без аутентификации (только сетевой ACL)

BROWSER_MODE

cdp

persistent / storagestate / cdp — см. раздел топологии

CDP_ENDPOINT

http://127.0.0.1:9222

режим cdp: URL CDP подключённого браузера (обычно туннелированный порт)

STORAGE_STATE_FILE

./auth.json

режим storagestate: снимок входа, экспортированный на Windows, скопирован сюда

BROWSER_PROFILE_DIR

./.profile

режим persistent: профиль Chrome, хранящий общий сеанс входа

HEADLESS

true

false только для отладки

MAX_CONCURRENT_PAGES

4

ограничение параллельности (один Chrome, изолированные вкладки)

PAGE_TIMEOUT_MS

20000

жёсткий таймаут на страницу

SEARCH_ENGINE

bing

bing (настроенный извлекатель) / google / duck / custom

SEARCH_URL_TEMPLATE

—

пользовательский URL с плейсхолдером {q}, например https://wiki.internal/search?q={q} (переопределяет URL движка)

RESULT_COUNT

10

результатов на запрос

SEARXNG_URL

—

дополнительный запасной публичный поиск (требует исходящий интернет), например http://127.0.0.1:8080


Добавление пользовательского извлекателя для внутреннего портала

extractBing в src/tools.ts настроен на DOM Bing. Для внутреннего портала добавьте extractPortal(page, count) и выберите его по имени движка в searchWithBrowser. Универсальный extractGeneric уже возвращает ссылки на якоря + близлежащий текст как приемлемый запасной вариант для неизвестных DOM.


Заметки по безопасности и эксплуатации

  • Привязка и доступ: предпочтительно оставлять шлюз во внутренней сети. Если вы привязываетесь к 0.0.0.0, установите ALLOWED_HOSTS и используйте брандмауэр / сетевой ACL, или установите GATEWAY_TOKEN, или поместите за обратный прокси с SSO.

  • Общий профиль = общая идентичность: каждый поиск приписывается общей учётной записи. Нормально для служебной учётной записи отдела; проверьте, если целевая система отслеживает пользователей или имеет квоту.

  • Обновление сеанса: повторно запустите npm run login, когда истечёт SSO. Рассмотрите еженедельный cron, отправляющий напоминание по электронной почте, или проверку работоспособности, которая обнаруживает страницу входа (read_webpage на известном URL, требующем входа, возвращает текст страницы входа).

  • Параллельность / масштабирование: один Chrome с изолированными вкладками справляется с небольшим отделом. Для увеличения масштаба используйте пул браузеров (N постоянных контекстов), если он насыщается — место для изменения — это только withPage.

  • Безголовый Chrome на Linux: --no-sandbox --disable-dev-shm-usage уже установлены (дружественно к контейнерам).


Разработка и тестирование

  • Пробы находятся в scripts/ и импортируют из ../dist/, поэтому сначала соберите: npm run build.

    • scripts/probe-search.mjs "<query>" — напрямую управляет общим браузером (минуя MCP); проверяет подключение CDP + извлекатель Bing.

    • scripts/probe-mcp.mjs <url> "<query>" — подключается к запущенному шлюзу через Streamable HTTP (реальный путь клиента), выводит список инструментов, вызывает web_search. Сначала запустите шлюз: node --env-file=.env dist/server.js.

  • Режим разработки (npm start → tsx): в npm 11 транзитивный esbuild от tsx блокируется allow-scripts по умолчанию. Разрешите один раз (npm approve-scripts) или используйте скомпилированный путь везде: npm run build && node --env-file=.env dist/server.js.


Статус

Это обозреваемый PoC / скелет — проверенный на соответствие API v2 MCP SDK (@modelcontextprotocol/server 2.x, createMcpHandler / createMcpExpressApp / requireBearerAuth / toNodeHandler) и API постоянного контекста Playwright. Перед производством: зафиксируйте точные версии зависимостей, добавьте тесты и укрепите уровень аутентификации (JWT / интроспекция вместо статического токена), если вы выставляете его за пределы доверенной внутренней сети.

Дизайн-контекст (топологии режимов A/B/C, межплатформенная проблема шифрования cookie, границы SearXNG) находится в разделе «Безголовые серверы Linux + ПК с Windows для входа» выше.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers