Connect zot to MCP (Model Context Protocol) servers.
This extension reads MCP server configurations from standard locations (same format as Claude Desktop, Cursor, Cline, etc.) and bridges their tools into zot so the LLM can call them directly.
- Standard config format — same JSON as Claude Desktop, Cursor, Cline
- On-demand tool discovery — a fixed set of six small tools is always advertised; matching MCP schemas load when needed instead of bloating every model request
- Resources and prompts —
mcp__resourceslists/reads resources and templates,mcp__promptslists/renders prompt templates - Live catalogue —
mcp__callandmcp__describereach tools discovered after startup;tools/list_changedrefreshes the cache automatically - Server notifications — MCP logging at warning and above, and resource-update notifications, surface as zot notifications
- Roots — the zot working directory is announced as the project root
- Smart lazy loading — cached definitions register as deferred tools at startup, servers wake for refresh or tool calls, then auto-sleep after idle time
- Auto-respawn — calling a loaded tool on a sleeping server wakes it up automatically
- Multi-transport — stdio, streamable-http, and SSE transports
- Multi-server — connect to any number of MCP servers simultaneously
- Tool namespacing — tools appear as
mcp__<server>__<tool>to avoid collisions - Tool annotations — read-only, destructive, idempotent hints surfaced to LLM
- Configurable timeouts — per-server connect, request, and idle timeouts
- Custom headers — auth tokens and other headers for HTTP servers
- Slash commands —
/mcpto check status, start/stop/restart servers - Better error messages — context-aware errors with actionable suggestions
For an HTTP server requiring browser authorization, run /mcp auth <server> to open the authorization URL in your default browser (open on macOS, rundll32 on Windows, xdg-open elsewhere). The URL is also displayed as a manual fallback; clipboard contents are not changed. The bridge uses the official modelcontextprotocol/go-sdk auth package for metadata discovery (protected-resource and authorization-server metadata, with the 2025-03-26 fallback), public-client registration (a stored registration is reused; a fresh /mcp auth registers anew), PKCE, token refresh, and step-up scopes. Discovery deliberately allows private IPs, because VPN-hosted MCP servers are the common case here. A loopback callback listener checks state and expires after five minutes. Background discovery never launches a login flow.
The command returns immediately; the result arrives as a notification once the browser round-trip completes, and the server reconnects on its own. Tokens and client registration are stored per exact resource URL under $ZOT_HOME/mcp-oauth/, using atomic writes and mode 0600 files (0700 directory on Unix). These files contain credentials: do not share or commit them. On Windows, protect the state directory with account-specific ACLs.
/mcp logout <server> stops that connection and deletes its local credentials; it does not revoke the authorization grant at the provider. Servers sharing an exact URL share credentials. Browser authorization, re-authorization after a purged client registration and fresh registration were validated end-to-end against n8n's MCP OAuth server.
oauth accepts true, false, or an object with clientId, clientSecret, scope, and redirectUri. Fields support environment expansion. Explicit false disables OAuth; omission retains the existing stored-credential and explicit /mcp auth behavior. Configured clients take precedence over saved registrations; incompatible saved tokens are not reused.
redirectUri, if supplied, must be an HTTP loopback IP URL with an explicit port, e.g. http://127.0.0.1:33418/callback. Remote callbacks are not supported. Omit it for an ephemeral loopback port. Keep client secrets in environment variables, not committed config files.
URL-only server definitions infer Streamable HTTP unless transport or type selects another transport. Millisecond timeouts retain exact precision. Disabled entries stay visible in status and never connect or require environment secrets.
Like Claude Code, the bridge expands ${VAR}, ${VAR:-default} and (zot-mcp style) $env:VAR in command, args, cwd, env values, url, and headers values. This is client configuration compatibility, not a requirement of the MCP protocol.
{"mcpServers":{"api":{"transport":"streamable-http","url":"${API_BASE_URL:-https://api.example.com}/mcp","headers":{"Authorization":"Bearer ${API_KEY}"}}}}Expansion uses the bridge process environment after global/project configurations are merged. Defaults apply to unset variables; an explicitly empty variable stays empty. Values are expanded once, without shell execution, bare $VAR expansion, nested defaults, or references to sibling env entries. Config files are not rewritten. A missing variable without a default disables that server and reports the server, field, and variable name, never the field value; other valid servers remain available.
Requires Go 1.25+ on PATH. Built on the official modelcontextprotocol/go-sdk (v2.0.0+; v1.x used mark3labs/mcp-go). Upgrading from v1.x keeps OAuth credentials; tool schemas are re-cached once because go-sdk preserves them verbatim. Like the upstream examples, the extension runs from source via go run . — no architecture-specific binary to ship. To skip the compile step at startup, go build -o mcp-bridge . in the installed directory and set "exec": "./mcp-bridge" (drop args) in extension.json.
-
Install the extension:
zot ext install https://git.miskam.xyz/mxm/mcp-bridge
From a checkout of the monorepo instead:
cd extensions/mcp-bridge && make. -
Create a project config file:
mkdir -p .zot cat > .zot/mcp.json << 'EOF' { "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }, "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } } } EOF
-
Restart zot. On first run the extension refreshes its tool cache in the background. When zot shows
MCP tool cache changed, run/reload-extonce. Future launches register the cached MCP tools immediately as deferred definitions.
The model initially sees one small loader tool, mcp__search_tools. It searches cached MCP tool names and descriptions locally, activates up to eight relevant definitions by default, and then calls the selected MCP tool normally. This keeps large MCP installations compatible with providers that limit request or tool-schema size.
Config files are merged in this order; a later file replaces a same-named server entirely:
| Location | Scope |
|---|---|
$XDG_CONFIG_HOME/mcp/mcp.json (default ~/.config/mcp/mcp.json) |
Shared across MCP clients (cross-client convention, all platforms) |
~/.agents/mcp.json, ~/.agents/mcp/mcp.json |
Shared across agents |
$ZOT_HOME/mcp.json |
Global, platform-native (~/Library/Application Support/zot on macOS, $XDG_STATE_HOME/zot when set) |
.mcp.json |
Project-level (Claude Code compatible) |
.zot/mcp.json |
Project-level, zot-specific |
Standard MCP config — same as Claude Desktop and Claude Code, with a few optional extensions:
| Field | Type | Default | Description |
|---|---|---|---|
command |
string | — | Executable to spawn (stdio only) |
args |
string[] | [] | Arguments for the command |
env |
object | — | Extra environment variables |
cwd |
string | project dir | Working directory for the subprocess; ~ and relative paths supported |
disabled |
bool | false | Keep the entry but never start the server |
transport |
string | "stdio" | "stdio", "streamable-http", or "sse" |
type |
string | — | Claude Code alias: "stdio", "http" (= streamable-http), or "sse" |
url |
string | — | Server URL (HTTP transports only) |
headers |
object | — | Static HTTP headers (HTTP transports only). Omit for OAuth servers and use /mcp auth |
connectTimeout |
number | 30 | Connection timeout in seconds |
requestTimeout |
number | 60 | Per-request timeout in seconds |
idleTimeout |
number | 300 | Idle timeout before stopping in seconds |
connectTimeoutMs, requestTimeoutMs |
number | — | Millisecond aliases (zot-mcp compatible); take precedence |
/mcp setup add you registers the keyless You.com MCP profile
(you-search). It needs no account or API key. To use the authenticated
server and its additional tools, edit $ZOT_HOME/mcp.json after adding
the template:
{
"mcpServers": {
"you": {
"transport": "streamable-http",
"url": "https://api.you.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_YDC_API_KEY"
}
}
}
}The authenticated endpoint does not expose you-finance by default. Request
it explicitly with the ?tools= URL parameter or the X-Allowed-Tools header.
┌──────────────────────────────────────────────────────────────┐
│ zot agent │
│ │
│ ┌──────────┐ tool_call ┌──────────────┐ │
│ │ LLM │───────────────▶│ mcp-bridge │ │
│ │ │◀───────────────│ (extension) │ │
│ └──────────┘ tool_result └──────┬───────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ MCP │ │ MCP │ │ MCP │ │
│ │ server 1 │ │ server 2 │ │ server 3 │ │
│ │ (stdio) │ │ (stdio) │ │ (stdio) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────┘
- Startup: mcp-bridge reads config and registers tools from
mcp-tools-cache.json - Background refresh: starts configured MCP servers, calls
tools/list, and updates the cache when definitions change - Reload: if the cache changed, run
/reload-extonce so zot rebuilds the tool registry with the new definitions - Naming: tools appear as
mcp__<server>__<tool>(e.g.,mcp__filesystem__read_file) - Idle timeout: servers not used for 5 minutes are automatically stopped
- Auto-respawn: calling a tool on a stopped server wakes it up
- Routing: tool calls are forwarded to the appropriate MCP server
| Command | Description |
|---|---|
/mcp |
Status of all configured servers (last known, not a live check) |
/mcp status <name> |
Details and recent lifecycle log for one server |
/mcp start <name|all> |
Start one server, or all |
/mcp stop <name|all> |
Stop one server, or all |
/mcp restart |
Restart all servers |
/mcp refresh |
Rediscover tools and update the cache |
/mcp auth <name> |
Browser OAuth authorization (alias: login) |
/mcp logout <name> |
Remove local OAuth credentials |
/mcp setup templates |
List setup templates (grep, context7, you) |
/mcp setup add <template> [--global|--project] [--name <server-name>] |
Add a server from a template |
/mcp help |
Command reference |
The model always sees six fixed tools; everything else is deferred and loaded on demand.
| Tool | Purpose |
|---|---|
mcp__search_tools {query, limit} |
Search cached MCP tool names/descriptions and activate the matching deferred definitions |
mcp__call {server, tool, args} |
Call any tool by server and MCP tool name, including tools discovered after startup |
mcp__describe {server, tool?} |
Live tool list of a server, or one tool's input/output schema and annotations |
mcp__resources {server, action: list|read|subscribe|unsubscribe, uri?} |
Resources and templates; text/images natively, other binary content as base64 JSON |
mcp__prompts {server, action: list|get, name?, args?} |
Prompt templates and rendered messages |
mcp__control {server, action: ping|logging/set|complete, ...} |
Health check, logging level, prompt/resource argument completion |
Completion takes ref (type: ref/prompt with name, or type: ref/resource with uri), argument: {name, value}, and optional context: {arguments: {...}}. Logging takes level from debug through emergency. Tool-call progress is reported through notifications with a request token.
Subscriptions keep the connection awake. Explicit stop, logout, disconnect or extension reload ends subscriptions; subscribe again after reconnecting. Resource/prompt list-change notifications announce changes; listing always fetches the live catalogue.
Deferred tools are namespaced mcp__<server>__<tool> (non-alphanumerics become _), e.g. mcp__filesystem__read_file. Once activated by mcp__search_tools they are called natively with their real schema, which is why the bridge keeps them alongside the generic mcp__call.
The bridge uses a "smart lazy" strategy:
- On startup: cached tool definitions are registered without blocking zot startup
- In the background: servers start long enough to refresh the tool cache
- During use: servers stay running for fast tool calls
- After 5 min idle: unused servers are automatically stopped (saves memory/CPU)
- On next tool call: the server is respawned automatically (~1-3s delay)
This gives you:
- Cached tools visible to the LLM immediately
- Fast tool calls when actively working
- Memory freed when not using MCP tools
- One manual
/reload-extonly when tool definitions change
Check server status:
/mcp
View extension logs:
zot ext logs mcp-bridge -fCommon issues:
- Server fails to start: check that
commandexists in your PATH, or use absolute path - Tool not found: run
/mcpto see if the server started successfully - Slow first call: server is respawning after idle timeout (normal)
- OAuth scope —
/mcp auth <server>supports HTTPS servers with dynamic public-client registration. Remote/headless callback forwarding is not supported. Static header authentication remains available. - No sampling/elicitation — zot's extension protocol has no host API for nested model requests or user dialogs, so these server-to-client requests are not advertised
- Parity validation in progress — SSE authorization retry has a local HTTP test, not a full live SSE/browser acceptance run. Subscription continuity across reconnects is not implemented.
- No automatic config hot reload — run
/reload-extafter setup/config changes
Forgejo Actions (.forgejo/workflows/release.yml) runs on new v* tags. GoReleaser v2.18.2 cross-compiles with CGO_ENABLED=0 on a Linux runner:
mcp-bridge_<version>_linux_amd64.tar.gz— Linux x86_64mcp-bridge_<version>_linux_arm64.tar.gz— Linux aarch64mcp-bridge_<version>_darwin_arm64.tar.gz— Apple Silicon macOS (unsigned, not notarized)checksums.txt— SHA-256 hashes
Each archive contains the executable, README, MIT license and an extension.json pointing at ./mcp-bridge. The git manifest still uses go run ., so source installations remain compatible with current zot. The proposed binary manifest block is not enabled until zot supports downloading and verifying it.
Download your archive and checksums.txt from the same release. In a new working directory, verify before extracting (replace the filename below with the downloaded asset):
asset=mcp-bridge_VERSION_darwin_arm64.tar.gz
# Select exactly this asset from the checksum file; fail if absent or duplicated.
awk -v asset="$asset" '$2 == asset { print; n++ } END { if (n != 1) exit 1 }' checksums.txt > selected-checksum.txt &&
shasum -a 256 -c selected-checksum.txt &&
mkdir extracted && tar -xzf "$asset" -C extracted &&
zot ext install ./extractedUse sha256sum -c instead of shasum -a 256 -c on Linux if needed. Checksums detect corruption; they are not an independent release signature. Do not extract over your source checkout or an existing installation.
CI needs an ubuntu-24.04 Forgejo runner with Node.js for JavaScript actions and network access to download Go and GoReleaser. The job token is passed as GITEA_TOKEN to publish to this repository's releases; repository write permission must be enabled. No macOS runner or Go installation is needed on the user's machine for binary installations.
Local packaging check (does not publish):
goreleaser check
goreleaser release --snapshot --clean --skip=publishSnapshots have a -next artifact name but keep the source version inside the binary and manifest. For releases, CI checks the tag against the manifest, and the existing version test checks the manifest against the code. Push a new version tag after merging the workflow; existing tags do not trigger it retroactively.
Source of truth: https://git.miskam.xyz/mxm/mcp-bridge. The monorepo zot-extension consumes it as a submodule at extensions/mcp-bridge. Work in a checkout outside $ZOT_HOME/extensions/; make install refuses to run from the installed copy or with unpushed commits, because zot ext remove deletes that directory.
go vet ./... && go test ./... # unit tests
zot --ext . # run one zot session against this checkout
make install # zot ext remove + zot ext install .
zot ext logs mcp-bridge -f # extension stderrsdkcompare/ is a separate module that drives the official go-sdk and the former mcp-go dependency through one interface; see its README for the migration acceptance results.
Release: bump version.go and extension.json (a test enforces they match), commit, git tag -a v<version>, git push --tags.
Validated against: @modelcontextprotocol/server-filesystem (stdio), @zereight/mcp-gitlab (stdio), grep.app (streamable-http), Atlassian MCP behind static headers (streamable-http), n8n MCP with OAuth 2.1 (streamable-http).
MIT
{ "mcpServers": { // stdio: local subprocess "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."], "env": { "NODE_ENV": "production" }, "cwd": "~/projects", // optional; default: zot's working directory "idleTimeout": 300 // seconds before an unused server is stopped }, // streamable-http with a static token ("type": "http" is the Claude Code alias) "atlassian": { "type": "http", "url": "https://mcp.example.com/mcp/", "headers": { "Authorization": "Bearer ${ATLASSIAN_TOKEN}" } }, // streamable-http with browser OAuth: no headers, run /mcp auth n8n once "n8n": { "transport": "streamable-http", "url": "https://n8n.example.com/mcp-server/http", "requestTimeout": 120 }, // legacy SSE "legacy": { "transport": "sse", "url": "https://example.com/sse" }, // kept in the file, never started "experimental": { "command": "node", "args": ["server.js"], "disabled": true } } }