Skip to main content
Glama

Portcall

Небольшой шлюз плагинов, который обслуживает локальные MCP-серверы по HTTP.

Название — морской каламбур: port call — port (гавань / сетевой порт) + call (остановка судна / запрос).

Что это

Portcall прослушивает один HTTP-порт и монтирует один или несколько MCP-серверов по отдельным путям:

/vault/mcp   → mcpvault (Obsidian vault)
/healthz     → liveness + mount list

Каждая точка монтирования — это независимая конечная точка MCP. Клиенты регистрируют их по отдельности — агрегации инструментов нет, поэтому нет ни коллизий имён, ни необходимости поддерживать схему пространств имён.

Намеренно не предусмотрено открытие порта за пределами localhost. Поставьте перед ним туннель, обратный прокси или ничего; Portcall по умолчанию привязывается к 127.0.0.1, и ему всё равно, что находится выше по потоку.

Related MCP server: mcp-unify

Почему не stdio-мост

Очевидный способ вывести stdio MCP-сервер на HTTP — универсальный мост вроде supergateway. Это работает, но имеет структурную проблему: каждый запрос или сессия порождает дочерний процесс, и корректно собирать эти дочерние процессы легко напутать.

В частности, в supergateway дочерний процесс освобождается только из transport.onclose или transport.onerror. Никто не вызывает transport.close() при нормально завершённом запросе без сохранения состояния, поэтому onclose никогда не срабатывает, и каждый успешный запрос приводит к утечке процесса — очищаются только неудачные запросы. Его режим с сохранением состояния не даёт утечек (таймер сессии закрывает транспорт), но вместо этого он удерживает долгоживущий поток SSE GET, с которым некоторые прокси справляются плохо.

Обёртывание команды в npx усугубляет ситуацию: npx порождает настоящий сервер, поэтому при убийстве дочернего процесса убивается обёртка, а внучатый процесс остаётся сиротой.

Ответ Portcall — не порождать ничего, когда в этом нет необходимости.

Адаптеры

Адаптер

Для чего

Как

inProcess

Серверы, которые экспортируют фабрику как библиотеку

Вызывает фабрику в том же процессе. Дочернего процесса не существует, поэтому собирать некого.

stdio

Сторонние серверы, которые говорят только на stdio

Пока не реализовано. Когда появится, должен освобождать дочерний процесс при нормальном завершении, а не только при ошибке, и обрабатывать завершение групп процессов для команд-обёрток.

inProcess — интересный случай, который охватывает серверы, заслуживающие самостоятельного хостинга. Например, @bitbonsai/mcpvault экспортирует createServer(vaultPath, options), возвращающий Server из MCP SDK v2; его запись bin — это, по сути, serveStdio(() => createServer(...)). Portcall вызывает ту же функцию напрямую и полностью пропускает stdio.

SDK создаёт новый экземпляр сервера для каждого запроса и уничтожает его вместе с запросом, поэтому нет состояния сеанса, которое могло бы истечь по таймауту, и нет накапливающихся дескрипторов.

Версии протокола

Portcall построен на @modelcontextprotocol/server v2, который обслуживает две эпохи протокола из одного обработчика:

  • Современная (2026-07-28) — конверт на каждый запрос. Запросы несут заголовки MCP-Protocol-Version, Mcp-Method и (для вызовов инструментов) Mcp-Name, а также блок params._meta. Нет рукопожатия initialize и долгоживущей сессии; обнаружение — через server/discover.

  • Устаревшая (эпоха 2025) — по умолчанию обслуживается без сохранения состояния. GET и DELETE (операции сеанса 2025) отвечают 405. Установите PORTCALL_MODERN_ONLY=true, чтобы полностью отклонять устаревший трафик.

Поскольку современная эпоха работает на уровне запросов, нет постоянного потока SSE, который нужно держать открытым. Это обходит класс проблем с прокси: некоторые обратные прокси удерживают заголовки ответа до поступления первого байта тела, что бесконечно задерживает только что открытый, но молчащий поток SSE. Для тех потоков, которые всё же возникают, PORTCALL_KEEPALIVE_MS управляет интервалом комментариев SSE; уменьшите его, если впередистоящий прокси буферизует.

Конфигурация

Все значения, зависящие от хоста, берутся из окружения.

Переменная

По умолчанию

Назначение

PORTCALL_VAULT_PATH

(обязательно)

Абсолютный путь к хранилищу Obsidian для обслуживания

PORTCALL_PORT

7100

TCP-порт

PORTCALL_HOST

127.0.0.1

Интерфейс привязки

PORTCALL_TOKEN

(не задан)

Статический bearer-токен. Если не задан — аутентификация не требуется

PORTCALL_ALIAS_ROOT_MCP

(не задан)

Также смонтировать указанный плагин в /mcp

PORTCALL_PATH_PREFIX

(не задан)

Обслуживать все точки монтирования в /<prefix>/…

PORTCALL_KEEPALIVE_MS

15000

Интервал keepalive SSE; 0 отключает

PORTCALL_MODERN_ONLY

false

Отклонять запросы эпохи 2025 вместо их обслуживания

PORTCALL_TOKEN защищает каждую точку монтирования с помощью Authorization: Bearer <token>. Обратите внимание, что некоторые MCP-клиенты — в том числе пользовательский интерфейс коннектора Claude — не позволяют задать заголовок запроса, поэтому для них токен придётся проверять выше по потоку (или вообще не использовать, контролируя доступ на сетевом уровне).

PORTCALL_PATH_PREFIX — это запасной вариант именно для таких клиентов: он перемещает все точки монтирования в выбранный вами сегмент, так что /vault/mcp становится /<prefix>/vault/mcp, и сам URL несёт в себе секрет. Из этого следует два момента, и сервер обеспечивает оба:

  • Ответы 404 содержат только not_found. Они никогда не перечисляют, что смонтировано.

  • Список точек монтирования перемещается из публичного /healthz в /<prefix>/healthz. Голый /healthz по-прежнему отвечает, поэтому проверки живости продолжают работать, но не раскрывают никаких путей.

Относитесь к префиксу пути как к более слабой защите, чем заголовок. URL-адреса попадают в журналы доступа прокси, отчёты о сбоях и в любые записи, фиксирующие назначение, а утёкший префикс даёт тот же доступ, что и утёкший токен. Это поднимает планку — но это не аутентификация.

Какие плагины монтируются и где, объявляется в plugins.config.ts.

Запуск

Требуется Node 24 (см. .nvmrc).

npm install
npm run build
cp .env.example .env    # then set PORTCALL_VAULT_PATH
npm start

И npm start, и npm run dev загружают .env, если он присутствует, и запускаются без него, если его нет, поэтому демон может вместо этого внедрять окружение напрямую. Уже заданные переменные окружения не переопределяются.

npm run dev запускает точку входа через tsx в режиме watch. Демон должен запускать собранный результат, а не tsx.

Проверьте, что он работает:

curl -s localhost:7100/healthz

Тесты

npm test        # builds, then runs unit and integration tests
npm run typecheck

Нет тестовых зависимостей: раннер — node:test, а TypeScript загружает tsx (он уже нужен для npm run dev).

Интеграционные тесты — «чёрный ящик». Они запускают собранный сервер с одноразовым хранилищем на эфемерном порту и обращаются к нему по реальному HTTP, поэтому проверяют тот же артефакт, который запускает демон: маршрутизацию, алиас /mcp, bearer-аутентификацию и обе эпохи протокола. Модульные тесты покрывают разрешение точек монтирования и проверку bearer-токена, где тихая регрессия выглядела бы как мёртвый клиент, а не ошибка.

Структура

src/
  server.ts            HTTP entry point, wiring, health, shutdown
  routes.ts            mount resolution and URL normalisation
  auth.ts              bearer token check
  config.ts            environment parsing
  log.ts               structured logging
  types.ts             the Plugin interface
  adapters/
    inProcess.ts       library-factory adapter
  plugins/
    vault.ts           mcpvault
plugins.config.ts      which plugins mount at which paths
test/
  integration.test.ts  black-box tests against the built server
  routes.test.ts       mount resolution
  auth.test.ts         bearer token check
  helpers.ts           server harness and MCP request builders

Лицензия

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A universal MCP server that acts as a unified gateway for dynamically connecting and managing multiple MCP servers via a single HTTP endpoint.
    7 npm
    6
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Unifies multiple MCP servers behind a single endpoint with lazy loading, auto-cleanup, Python plugins, and role-based filtering.
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides LocalServer and RemoteServer implementations for running MCP servers locally via stdio or remotely via HTTP/SSE, with simple and advanced deployment options.
    1
    MIT