MCP
The Model Context Protocol is how meka reaches tools, resources and prompts it does not implement itself. A server is a process meka spawns or an HTTP endpoint it connects to; what it advertises is registered alongside the built-in tools and called the same way.
This page covers running servers: the command suite, where their secrets live, what happens on the wire, and what the agent can reach. The keys themselves are in the config file reference, which is also where tool permissions are resolved.
meka mcp CLI
Manage configured servers without editing config.toml by hand:
| Command | Action |
|---|---|
meka mcp list [--format json] | Print all configured servers, plus any stored OAuth credential that no server claims (see Leftover credentials). Under json, {"servers": [...]} with each server’s name, transport, required, disabled, and its permission, command, args or url where set. |
meka mcp get <name> [--format json] | Print full details for one server: the listing’s fields plus env_keys, header_keys, the kinds of credentials stored (never their values), credential_origin, auth, allowed_tools, disabled_tools and tool_permissions. |
meka mcp add <name> <url-or-command> [args...] [flags] | Persist a server. Transport is auto-detected: a URL starting with http[s]:// means HTTP, anything else means stdio. Preserves existing formatting/comments via toml_edit. |
meka mcp remove <name> | Best-effort revoke stored OAuth tokens (RFC 7009) at the provider, then delete the server entry, clear stored credentials, and drop any resource-update ledger entries. A name with stored credentials but no config entry is cleaned rather than refused. |
meka mcp disable <name> | Set disabled = true on the server entry. The next meka start skips it entirely. |
meka mcp enable <name> | Clear the disabled flag, so the server connects on the next start. |
meka mcp reconnect <name> | Smoke-test a connect; exits non-zero with the error when it fails. |
meka mcp tools <name> [--format json] | Connect and list every advertised tool with its resolved permission, the chain step that decided it, and whether the current config allows it. Useful for populating --allow-tool, --disable-tool, or --tool-permission overrides without leaving the CLI. Under json, the object GET /v1/mcp/{name}/tools answers with (server, tools[] of raw_name, description, required_permission, permission_source, allowed) plus read_only_hint_declined. |
meka mcp login <name> | Drive interactive OAuth. If the server has no [auth] block and uses HTTP, assumes type = "oauth" and persists the block on success. With --auth-token-stdin or --client-secret-stdin, stores that secret and exits instead, which is also how you rotate one. |
meka mcp logout <name> | Call the provider’s revocation_endpoint (RFC 7009) best-effort, then clear every stored credential for the server. |
Credentials
An MCP server’s bearer token, OAuth client secret and OAuth token bundle are kept in the store (mcp_credentials, keyed by server name and kind), never in config.toml. This is the same rule accounts follow, and for the same reason: config.toml is a plaintext file people commit, sync and share.
Each is read from stdin so it never reaches ps output or your shell history. One command reads one secret, so --auth-token-stdin and --client-secret-stdin cannot be combined:
$ pass show notion-token | meka mcp add notion https://mcp.notion.com/mcp --auth-token-stdin
$ pass show acme-secret | meka mcp login acme --client-secret-stdin
A confidential OAuth client holds two at once: the long-lived client secret it authenticates with, and the refreshable bundle it obtained. Store the secret first, then run meka mcp login <name> to complete the flow. Refreshing the bundle leaves the client secret alone.
meka mcp get <name> lists which kinds a server has, without printing any of them, and shows the origin an OAuth bundle was issued for as issued for: <scheme>://<host>[:port]. That flags the case a rotated url leaves behind: a bundle minted against the old host is still stored and still sent, so the line names a mismatch rather than letting the next call fail as a bare 401. meka mcp list names servers that have a stored credential but no [[mcp.servers]] entry, which is what a hand-edited config strands.
meka mcp add flags
| Flag | Purpose |
|---|---|
--transport <TRANSPORT> | Force the transport, stdio or http; auto-detected otherwise. |
--env <KEY=VALUE> | Environment variable for stdio (repeatable). |
--header <KEY=VALUE> | HTTP header (repeatable). |
--auth <AUTH> | Configure the [auth] block: oauth, client_credentials or client_credentials_jwt. |
--auth-token-stdin | Read a static bearer token from stdin and store it. Mutually exclusive with --auth. |
--client-secret-stdin | Read an OAuth client secret from stdin and store it. Required by --auth client_credentials. |
--client-id <CLIENT_ID> | OAuth or client_credentials client id. Not a secret, so it goes in config.toml. |
--signing-key <SIGNING_KEY>, --signing-algorithm <SIGNING_ALGORITHM> | JWT signing key path and algorithm (RS256, RS384, RS512, ES256, ES384), client_credentials_jwt only. |
--scope <SCOPE> | OAuth scope (repeatable). |
--redirect-port <REDIRECT_PORT> | Fixed OAuth redirect port (default: ephemeral). |
--permission <LEVEL> | Per-server permission cap, applied to every tool on the server: none, read, workspace or unrestricted (default: read). |
--allow-tool <TOOL> | Raw tool name to allow (repeatable). When set, only listed tools register. |
--disable-tool <TOOL> | Raw tool name to block (repeatable). Applied after --allow-tool. |
--eager-load-tool <TOOL> | Raw tool name to eager-load (repeatable). Listed tools skip the load_tool round-trip and ship in the cacheable tools-array prefix from turn 1. |
--tool-permission <TOOL=LEVEL> | Per-tool permission override (repeatable). LEVEL is none, read, workspace or unrestricted. |
--no-login | Skip the auto-login an HTTP server’s probe would otherwise start; run meka mcp login <name> later. |
--required | Persist required = true, so a turn is refused while this server is not connected. Omitted, the server inherits [mcp].default_required and is optional by default. |
--disabled | Persist disabled = true, so the server is skipped entirely at startup. Re-enable with meka mcp enable <name>. |
Example: Notion
These signposts are info logs, so they need -v; at the default warn level the command
succeeds silently and the exit code carries the result. Timestamps and targets are elided here.
$ meka -v mcp add notion https://mcp.notion.com/mcp
added 'notion' to ~/.config/meka/config.toml
probe: 'notion' requires OAuth
running OAuth authorization for 'notion' (use --no-login to skip)
no [auth] block for 'notion'; assuming OAuth authorization_code
…
authorized 'notion'
meka mcp add on an HTTP endpoint:
-
Probe: issues an unauthenticated
GET(3 s timeout, redirects off) and classifies the response per the MCP authorization spec + RFC 6750 + RFC 9728:2xx→ server is open, no login needed.401/403withWWW-Authenticate: Bearer …→ OAuth required. Theresource_metadata="…"attribute (RFC 9728) is captured at DEBUG.- Any other status → couldn’t infer, prints the status code.
- Network failure → prints the error.
-
Auto-login: if the probe says OAuth is required (or
--auth oauthwas explicitly set), the OAuth authorization_code flow runs immediately as though the user had chainedmeka mcp login <name>themselves. The synthesized[auth] = oauthblock is written back toconfig.tomlon success. -
Rollback on failure: if the OAuth flow errors out, the entry we just wrote is purged from
config.toml(alongside any partial credentials), leaving the user’s config clean. The command exits non-zero. -
--no-login: skips step 2. The entry is still persisted and the probe’s hint is still printed; runmeka mcp login <name>when ready. Useful for scripted setup or when you expect to edit[auth]by hand.
The probe and the auto-login only run for HTTP servers, and only when the user didn’t provide --auth-token-stdin (static bearer) or --auth (other than oauth). Stdio servers skip both.
Only meka mcp login runs the browser flow. A host that connects a server and finds no stored credential (the REPL at startup, meka serve, meka acp, /mcp reconnect) marks it failed and names meka mcp login <name> as the remedy, rather than printing a URL to a terminal nobody may be watching.
Remote hosts / SSH sessions
The OAuth flow redirects the browser to http://127.0.0.1:<port>/callback. When meka is running on a different host than the browser (SSH session, container, Codespace, WSL), the browser can’t reach back and shows a “connection refused” error page. meka handles this automatically:
- While
meka mcp login <name>waits for the callback it also watches stdin. - The browser’s address bar still contains the full callback URL (including
codeandstate) even when the connection fails. Copy it, paste it into the meka prompt, and press Enter. - Whichever completes first, the TCP callback or the pasted URL, wins.
meka prints the URL exactly once and leaves opening it to you, so the flow is the same on a desktop
and over SSH. The authorized line is an info log, shown here with -v.
$ meka -v mcp login notion
To authorize, open this URL in your browser:
https://mcp.notion.com/authorize?response_type=code&…
Waiting up to 120s for the callback, or paste the callback URL here and press Enter:
http://127.0.0.1:46437/callback?code=…&state=… ← paste here
authorized 'notion'
REPL parity
Inside the REPL:
/mcp list(or a bare/mcp): list configured servers./mcp reconnect <server>: reconnect smoke-test./mcp login <server>//mcp logout <server>: run the auth flow or revoke./mcp <server>:<prompt> [args...]: render a server-defined prompt as the next user turn.
Resources and prompts
In addition to tools, meka exposes MCP resources and prompts through several builtin tools (deferred: the agent calls load_tool first to fetch the schema, then invokes them):
| Builtin | Purpose |
|---|---|
mcp_resource_list | List resources from one or every configured server. |
mcp_resource_read | Read a resource by server + uri; text inline, binary base64-encoded. |
mcp_prompt_list | List prompts from one or every configured server, including their declared arguments. |
mcp_prompt_get | Render a prompt by server + name with optional arguments; returns <role>: <text> lines. |
mcp_resource_subscribe | Subscribe to resources/updated notifications for a specific URI. |
mcp_resource_unsubscribe | Cancel a prior subscription. |
mcp_resource_updates_list | Print every resource that has been reported as updated since the session started. |
Startup concurrency
MCP servers connect in parallel at startup, partitioned by transport so a fleet of stdio servers (process-spawn bound) doesn’t fight a fleet of HTTP servers (network bound):
- stdio:
[mcp].stdio_concurrency(default3) - http:
[mcp].http_concurrency(default20)
Both are tuning knobs: rarely needed, but useful if you’re running ~30 stdio servers on a constrained box (lower it) or ~50 HTTP servers (raise it). 0 is refused at startup.
Connection lifecycle
- Reconnection is automatic for all transports (stdio, plain HTTP, OAuth-authenticated HTTP) when the transport closes mid-session. HTTP transports use exponential backoff (1s, 2s, 4s, 8s, 16s, capped 30s, max 5 attempts); stdio gets one immediate retry. The reconnect runs on a blocking thread to work around an upstream rmcp bug where the auth future is
!Send. - Failed initial connect is retried in the background with its own backoff (5s doubling to a 5 minute ceiling) until the server comes up, and the server’s tools are registered into every live session when it does. A server that is slow to boot, or that starts after meka, therefore recovers on its own rather than staying
failedfor the life of the process. This matters most for arequiredserver, where every turn is refused until it connects. - Session-expired recovery: rmcp transparently re-initializes HTTP sessions on 404 / JSON-RPC
-32001. meka relies on this; no per-call handling is required. - Cancellation: when the agent cancels a tool call (e.g. Ctrl-C), meka sends
notifications/cancelledto the server with the in-flight request id so the server can stop work. - Timeouts: tool calls default to 10 minutes; override with
MEKA_MCP_TOOL_TIMEOUT, a duration such as20m. - Tool list refresh: on
tools/list_changed, meka re-discovers the server’s tools and hot-swaps them in the registry; no restart needed. - Progress notifications: MCP tool calls attach a per-request
progressToken; incomingnotifications/progressrender as a live status line under the tool invocation. - Call identity:
tools/callcarries two extra keys in_metaalongside the progress token.meka/sessionIdis the id (a UUID) of the session the call came from, letting a server scope per-session state (a cache, a workspace, an audit trail) to one conversation; a sub-agent reports its own child session id.meka/toolUseIdis the provider’s tool-use id for the call. Both are absent for calls made outside a session, such as connection-time handshakes. - Server instructions:
InitializeResult.instructionsis captured once per connection and delivered in the per-turn context (sanitized and truncated to 2048 chars) under[MCP server instructions]. A server that connects late, or reconnects with different instructions, is announced as a change rather than rewriting anything already sent. - stdio server logs: a stdio server’s own stderr (many servers log there) is captured, not inherited, so it never corrupts the REPL display. Each line is re-emitted on meka’s
tracingstream atdebuglevel tagged with the server name, so it stays silent at default verbosity and surfaces under-v/RUST_LOG. resources/list_changed,prompts/list_changed, andresources/updatednotifications are logged atinfo/debuglevel.
Server-to-client features
| Feature | meka behavior |
|---|---|
elicitation/create | Routed to the calling session’s frontend (REPL / ACP form or URL prompt) with a 60s timeout. Auto-declines when no in-flight tool call’s frontend is registered or the user doesn’t answer in time. |