Portcall

Portcall

A plugin gateway that serves local MCP servers over HTTP as independent endpoints, avoiding child-process leaks by calling server factories in-process.

Category
访问服务器

README

Portcall

A small plugin gateway that serves local MCP servers over HTTP.

The name is a nautical pun: port call — port (harbour / network port) + call (a ship's stop / a request).

What it is

Portcall listens on one HTTP port and mounts one or more MCP servers at separate paths:

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

Each mount is an independent MCP endpoint. Clients register them separately — there is no tool aggregation, so no name collisions and no namespacing scheme to maintain.

Exposing the port beyond localhost is deliberately out of scope. Put a tunnel, reverse proxy, or nothing at all in front of it; Portcall binds to 127.0.0.1 by default and does not care what is upstream.

Why not a stdio bridge

The obvious way to put a stdio MCP server on HTTP is a generic bridge such as supergateway. That works, but it has a structural problem: every request or session spawns a child process, and reaping those children is easy to get wrong.

In supergateway specifically, the child is only reclaimed from transport.onclose or transport.onerror. Nothing calls transport.close() on a normally-completed stateless request, so onclose never fires and every successful request leaks a process — only failed requests get cleaned up. Its stateful mode does not leak (a session timer closes the transport), but it holds a long-lived GET SSE stream instead, which some proxies handle badly.

Wrapping the command in npx makes it worse: npx forks the real server, so killing the child kills the wrapper and orphans the grandchild.

Portcall's answer is to not spawn anything when it does not have to.

Adapters

Adapter For How
inProcess Servers that export a factory as a library Calls the factory in-process. No child process exists, so there is nothing to reap.
stdio Third-party servers that only speak stdio Not implemented yet. When it lands it must reclaim the child on normal completion, not just on error, and handle process-group kills for wrapper commands.

inProcess is the interesting case and covers the servers worth self-hosting. @bitbonsai/mcpvault, for example, exports createServer(vaultPath, options) returning an MCP SDK v2 Server; its bin entry is essentially serveStdio(() => createServer(...)). Portcall calls the same function directly and skips stdio entirely.

The SDK builds a fresh server instance per request and disposes it with the request, so there is no session state to time out and no accumulating handles.

Protocol versions

Portcall is built on @modelcontextprotocol/server v2, which serves two protocol eras from a single handler:

  • Modern (2026-07-28) — per-request envelope. Requests carry MCP-Protocol-Version, Mcp-Method, and (for tool calls) Mcp-Name headers plus a params._meta block. There is no initialize handshake and no long-lived session; discovery is server/discover.
  • Legacy (2025-era) — served statelessly by default. GET and DELETE (2025 session operations) answer 405. Set PORTCALL_MODERN_ONLY=true to reject legacy traffic outright.

Because the modern era is per-request, there is no standing SSE stream to keep open. That sidesteps a class of proxy problem: some reverse proxies withhold response headers until the first body byte arrives, which stalls a just-opened-but-silent SSE stream indefinitely. For the streams that do occur, PORTCALL_KEEPALIVE_MS controls the SSE comment-frame interval; lower it if a proxy in front is buffering.

Configuration

All host-specific values come from the environment.

Variable Default Meaning
PORTCALL_VAULT_PATH (required) Absolute path to the Obsidian vault to serve
PORTCALL_PORT 7100 TCP port
PORTCALL_HOST 127.0.0.1 Bind interface
PORTCALL_TOKEN (unset) Static bearer token. Unset means no authentication
PORTCALL_ALIAS_ROOT_MCP (unset) Also mount the named plugin at /mcp
PORTCALL_PATH_PREFIX (unset) Serve every mount under /<prefix>/…
PORTCALL_KEEPALIVE_MS 15000 SSE keepalive interval; 0 disables
PORTCALL_MODERN_ONLY false Reject 2025-era requests instead of serving them

PORTCALL_TOKEN gates every mount with Authorization: Bearer <token>. Note that some MCP clients — Claude's custom connector UI among them — offer no way to set a request header, so for those the token has to be enforced upstream instead (or left off, with access controlled at the network layer).

PORTCALL_PATH_PREFIX is the fallback for exactly those clients: it moves every mount under a segment you choose, so /vault/mcp becomes /<prefix>/vault/mcp and the URL itself carries the secret. Two things follow from that, and the server enforces both:

  • 404 responses say only not_found. They never list what is mounted.
  • The mount listing moves out of the public /healthz and into /<prefix>/healthz. The bare /healthz still answers, so liveness probes keep working, but it discloses no paths.

Treat a path prefix as weaker than a header. URLs reach proxy access logs, crash reports, and anything that records a destination, and a leaked one grants the same access a leaked token would. It raises the bar — it is not authentication.

Which plugins are mounted, and where, is declared in plugins.config.ts.

Running

Requires Node 24 (see .nvmrc).

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

Both npm start and npm run dev load .env if it is present and start without it if it is not, so a daemon can inject the environment directly instead. Variables already set in the environment are not overridden.

npm run dev runs the entry point through tsx with watch. A daemon should run the built output, not tsx.

Check it is up:

curl -s localhost:7100/healthz

Tests

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

No test dependencies: the runner is node:test, and tsx (already needed for npm run dev) loads the TypeScript.

The integration tests are black-box. They spawn the built server against a throwaway vault on an ephemeral port and drive it over real HTTP, so they exercise the same artifact a daemon runs — routing, the /mcp alias, bearer auth, and both protocol eras. The unit tests cover mount resolution and the bearer check, where a silent regression would look like a dead client rather than an error.

Layout

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

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选