High-performance MCP-to-OpenAPI 3.1.0 gateway, built for the Open WebUI ecosystem.
Built with Bun 1.4 and TypeScript. It aggregates locally deployed MCP (Model Context Protocol) service nodes — native binary stdio, Node/Bun script streaming pipelines, or SSE/HTTP network transports — into HTTP RESTful routes strictly aligned with the OpenAPI 3.1.0 specification, ready to be consumed as tools by external clients such as Open WebUI.
Community project — not affiliated with the Open WebUI team.
- Strict OpenAPI 3.1.0 contract: tool inputs are mounted under
requestBody.content['application/json'].schema; generatedoperationIds match the original tool names exactly, with notool_*_postnaming pollution - Full transport coverage: Native Binary Stdio / Script Stdio / SSE / HTTP unified under one gateway, with protocol wrapping, lifecycle self-healing, and hot reloading
- Blue-green session pool: service session hot-swapping guarded by an AsyncMutex, so config updates never interrupt in-flight requests
- Cancellation-aware lifecycle: connection handshakes accept an
AbortSignal; graceful shutdown aborts in-flight handshakes and releases sessions instead of deadlocking against the shutdown watchdog.AsyncMutexis deliberately non-reentrant — nestingrunExclusivecalls on the same key self-deadlocks by contract (covered by regression tests) - Cross-platform XDG layout: strict separation of config, data, state, and cache directories — zero pollution of the source tree
- Single-binary distribution:
bun build --compileproduces a native executable with no runtime dependencies - Reverse-proxy friendly:
X-Forwarded-*headers are parsed automatically, soservers.urlin OpenAPI documents always reflects the real external base URL
┌──────────────────────────────┐
│ External Clients │
│ Open WebUI │
└──────────────┬───────────────┘
│ HTTP / HTTPS
▼
┌──────────────────────────────┐
│ Reverse Proxy (Caddy/Nginx) │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ Open MCP Gateway │
│ - OpenAPI 3.1.0 generation │
│ - Blue-green session pool │
│ - XDG paths & interpolation │
└──────┬───────────────┬───────┘
│ JSON-RPC (stdio)
▼
┌──────────────────────────────┐
│ MCP service processes │
│ (codebase-memory, etc.) │
└──────────────────────────────┘# Run from source (requires Bun 1.4+)
bun install
bun run dev
# Or compile into a single native executable
bun run build # Output: build/open-mcp-gateway(.exe)The config file lives at $XDG_CONFIG_HOME/open-mcp-gateway/config.json5 (JSON5 format, comments and trailing commas supported). Paths support environment variable interpolation — ${APP_DATA_DIR}, ${XDG_DATA_HOME}, ${HOME} / %USERPROFILE%, and more — for cross-machine portability.
{
mcpServers: {
// Example 1: local native binary MCP (Native Binary Stdio)
codebase_memory: {
command: "${HOME}/.local/bin/codebase-memory-mcp",
args: [],
env: {},
},
// Example 2: npm-packaged MCP (Script Stdio)
// External packages live under ${APP_DATA_DIR}; see Plugin Management below
another_mcp: {
command: "bun",
args: ["run", "${APP_DATA_DIR}/node_modules/<pkg>/dist/index.js"],
env: {},
},
},
// OWUI direct-write target for the image attach side effect (see below)
owui: {
base: "https://owui.example.com",
token: "<OWUI API key>",
},
host: "127.0.0.1",
port: 9090,
}| Directory | Env Variable | Purpose |
|---|---|---|
| Config | XDG_CONFIG_HOME |
Main config config.json5 |
| Data | XDG_DATA_HOME |
External MCP plugin repository (its own package.json) |
| State | XDG_STATE_HOME |
Runtime state and persistent logs |
| Cache | XDG_CACHE_HOME |
Spec document cache and transient runtime data |
Business MCP extension packages are never installed inside this repository; they are centrally managed in the data directory:
cd "$XDG_DATA_HOME/open-mcp-gateway"
bun add <mcp-package-name> # install
bun update <pkg> # upgrade- Start the gateway behind a reverse proxy (e.g., Caddy). The gateway parses
X-Forwarded-*headers automatically to generate the correct external base URL - In Open WebUI's admin panel (Functions / Tools / OpenAPI), register each MCP's OpenAPI document URL:
http://<gateway-host>:8444/codebase_memory/openapi.json- Done. Open WebUI can now use all of your local MCP services as native tools.
For tool results carrying exactly one MCP image block, the gateway reshapes the response and — when OWUI session context is present — writes the image into the OWUI conversation directly:
- The tool response body becomes a bare
data:URI string (text/plain). Open WebUI passes the string through unchanged and its streaming path splitsdata:-URI tool images intoinput_imageparts — model-only vision in the same turn. (Negative space of Open WebUI:data:images in tool results are never shown to the user by themselves — they are vision input only.) - As a side effect, the gateway uploads the same bytes to OWUI (
POST /api/v1/files/) and attaches the OWUI-hosted file URL (/api/v1/files/{id}/content) to the message'sfilesfield via thefilesmessage event — the same mechanism the built-ingenerate_imageuses. The URL is relative to the OWUI origin, so every client (including phones) resolves it against a reachable host; never point message attachments at the gateway's internal address. The frontend rendersmessage.filesnatively, and the next turn re-injects the image for the model.
The side effect requires owui: { base, token } in config.json5 (hot-reloadable) plus the OWUI-injected X-OpenWebUI-Chat-Id / X-OpenWebUI-Message-Id request headers. Without either, only the bare data: URI is returned. All side effects are best-effort and never fail the tool response.
Three representations (per-server switch owuiImageCompat, omitted = on; false = envelope only):
attach(default*/*): baredata:URI string + the OWUI direct-write side effect (see above).raw(Accept: image/*): raw image bytes withContent-Disposition: inline.envelope(Accept: application/json, or switch off): the JSONCallToolResultenvelope.
Historical alias X-OMG-Compat: owui-image remains supported (= attach). See src/server/image-shape.ts, src/server/owui-attach.ts and tests/gateway.compat.test.ts.
bun run test # bun test: concurrency contract tests for AsyncMutex (FIFO /
# no-overlap / release-on-throw / non-reentrant), the P1
# shutdown regression (60s-handshake node must still shut down
# within the 5s watchdog window), pool hot-reload contracts, and
# the OWUI image content-negotiation suite (unit + HTTP integration)
tail -f "$XDG_STATE_HOME/open-mcp-gateway/logs/gateway.log" # log monitoringMIT