Portcall
Provides an MCP endpoint for an Obsidian vault via the mcpvault plugin, allowing MCP clients to access and interact with the vault's contents.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Portcallsearch my vault for meeting notes"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 listEach 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 |
| Servers that export a factory as a library | Calls the factory in-process. No child process exists, so there is nothing to reap. |
| 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 carryMCP-Protocol-Version,Mcp-Method, and (for tool calls)Mcp-Nameheaders plus aparams._metablock. There is noinitializehandshake and no long-lived session; discovery isserver/discover.Legacy (2025-era) — served statelessly by default.
GETandDELETE(2025 session operations) answer405. SetPORTCALL_MODERN_ONLY=trueto 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 |
| (required) | Absolute path to the Obsidian vault to serve |
|
| TCP port |
|
| Bind interface |
| (unset) | Static bearer token. Unset means no authentication |
| (unset) | Also mount the named plugin at |
| (unset) | Serve every mount under |
|
| SSE keepalive interval; |
|
| 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:
404responses say onlynot_found. They never list what is mounted.The mount listing moves out of the public
/healthzand into/<prefix>/healthz. The bare/healthzstill 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 startBoth 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/healthzTests
npm test # builds, then runs unit and integration tests
npm run typecheckNo 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 buildersLicense
MIT
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityCmaintenanceA universal MCP server that acts as a unified gateway for dynamically connecting and managing multiple MCP servers via a single HTTP endpoint.106MIT
- AlicenseNot gradedqualityCmaintenanceUnifies multiple MCP servers behind a single endpoint with lazy loading, auto-cleanup, Python plugins, and role-based filtering.2MIT

FlowMCP Serverofficial
AlicenseNot gradedqualityDmaintenanceProvides LocalServer and RemoteServer implementations for running MCP servers locally via stdio or remotely via HTTP/SSE, with simple and advanced deployment options.1MIT- AlicenseNot gradedqualityCmaintenanceThis 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
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
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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