Skip to main content
Glama

Portcall

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

The name is a nautical pun: port callport (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.

Related MCP server: mcp-unify

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

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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.
    10
    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
  • A
    license
    Not graded
    quality
    C
    maintenance
    This server bridges a stdio MCP server to HTTP, allowing MCP clients that communicate over HTTP to use the server's tools. It includes a per-tool allow/deny filter for security.
    MIT

View all related MCP servers

Related MCP Connectors

  • A basic MCP server to operate on the Postman API.

  • A MCP server built for developers enabling Git based project management with project and personal…

  • An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pizza6899-crypto/portcall'

If you have feedback or need assistance with the MCP directory API, please join our Discord server