Skip to content
YottaCode v0.2.0 is out! 🎉 See the release notes ↗
MCP servers

MCP servers

yottacode is a client for Anthropic’s Model Context Protocol. Configure an MCP server in ~/.yottacode/config.toml and its tools register alongside yottacode’s native tools — same approval modal, same permission rules, same dispatch path. The model doesn’t know (or care) which tools are native vs MCP.

Note

v1 supports stdio transport only — yottacode launches each server as a local subprocess. Remote HTTP/SSE servers aren’t supported yet.

What v1 ships

v1 covers the part of the MCP ecosystem most users actually need:

  • Stdio transport only. yottacode launches each MCP server as a subprocess and talks JSON-RPC over stdin/stdout. Covers every server published as @modelcontextprotocol/server-* and most community servers. Remote servers that only expose HTTP/SSE or Streamable HTTP endpoints (e.g. mcp.excalidraw.com) are not supported yet — you must run a local subprocess.
  • Non-blocking startup. MCP servers initialize in the background after the TUI renders. A slow server (e.g. npx -y downloading a package for the first time) does not block the prompt. /mcp shows “starting…” for servers still initializing; tools register automatically once they come up.
  • Tools only. MCP’s resources and prompts primitives are deferred — they’re rare in practice and add real surface. Most servers ship only tools anyway.
  • Per-tool approval. Each MCP tool defaults to the approval modal. Servers can hint a tool as read-only (annotations.readOnlyHint: true) and yottacode honors that — read-only tools auto-execute. Users can still elevate trust with explicit permission rules.
  • Subprocess sandboxing inherited from your OS. yottacode does not run MCP servers in an isolated container. Each subprocess inherits yottacode’s environment and filesystem permissions. Treat MCP servers as you’d treat any binary you choose to run.

Deferred to follow-ups:

  • HTTP/SSE transport — needed for remote MCP servers that don’t run as local subprocesses. This is a transport-level addition; many remote servers (like Excalidraw) don’t require auth, so transport alone would unblock them.
  • OAuth 2.1 device-flow auth — needed on top of HTTP transport for remote servers that require authentication.
  • MCP resources and prompts — rarely used in practice; most servers only expose tools.

Configuration

Add one [[mcp_servers]] block per server in ~/.yottacode/config.toml. Each entry needs at minimum a name and a command:

[[mcp_servers]]
name    = "filesystem"
command = "npx"
args    = ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/workspace"]

[[mcp_servers]]
name     = "memory"
command  = "npx"
args     = ["-y", "@modelcontextprotocol/server-memory"]
env      = { MEMORY_FILE = "$HOME/.yottacode/mcp-memory.json" }

[[mcp_servers]]
name     = "github"
command  = "npx"
args     = ["-y", "@modelcontextprotocol/server-github"]
env      = { GITHUB_PERSONAL_ACCESS_TOKEN = "$GITHUB_PAT" }
disabled = false

Fields:

FieldRequiredNotes
nameyesUnique. Lowercase letters, digits, -, _. Must start with a letter. Used in tool names and /mcp.
commandyesExecutable (resolved via PATH at session start).
argsnoForwarded verbatim.
envnoPer-server env vars layered on top of yottacode’s inherited environment. Values support $VAR substitution from the process env — keep secrets out of the config file.
disablednotrue skips this entry at session start.

Unknown keys are rejected at load time so typos like trnsport surface immediately instead of silently disabling a feature.

Tool namespacing

Every MCP tool registers as mcp/<server>/<tool>. So the GitHub server’s create_pull_request becomes mcp/github/create_pull_request — distinct from yottacode’s native gh_pr_create. The namespacing also makes permission rules predictable; see below.

Approval and permissions

Default policy: the approval modal fires on every MCP tool call. If the server explicitly hinted a tool as read-only, yottacode skips the modal — that’s a deliberate trust delegation to the server, not a security guarantee. Treat server-declared annotations as suggestions, not contracts.

Elevate trust (or restrict it) with MCP(<server>/<tool>) rules in .yottacode/permissions.json or .yottacode/permissions.local.json:

{
  "permissions": {
    "allow": ["MCP(filesystem/read_*)", "MCP(memory/*)"],
    "ask": ["MCP(github/*)"],
    "deny": ["MCP(*/delete_*)"]
  }
}

Precedence is the same as native tools: deny > ask > allow > default. MCP(*) is a valid pattern (covers everything) but you almost never want it — narrow rules per server are cheap to maintain and minimize blast radius.

/mcp slash command

CommandEffect
/mcpList configured servers, their start status, and tool count.
/mcp add <name> --command <cmd> [args...]Add a server to config.toml, start it immediately, and register its tools — no restart needed. Everything after --command is the executable + arguments.
/mcp remove <name>Remove a server from config.toml. Requires a restart to take effect.
/mcp logs <name>Show the last ~200 lines of the server’s stderr (helpful when a server crashes during init or under load).
/mcp restart <name>Stop the named subprocess, respawn it from the stored config, and swap the registered tools to the fresh client. The tool surface may shrink or grow if the server’s catalog changed between generations.

Examples:

/mcp add podman --command npx -y podman-mcp-server@latest
/mcp add filesystem --command npx -y @modelcontextprotocol/server-filesystem /home/me/workspace
/mcp add excalidraw --command node /path/to/excalidraw-mcp/dist/index.js --stdio

Non-text tool results

MCP servers can return image, audio, resource-link, or embedded-resource content alongside (or instead of) text. yottacode’s v1 bridge passes text content through verbatim; non-text blocks are replaced with explicit placeholder markers like [image omitted: image/png, 1024 bytes — yottacode v1 tools-only bridge passes text only]. The model sees the marker and learns the call succeeded but the data wasn’t text — far safer than receiving a silent empty string and retrying or hallucinating.

If a server uses MCP’s structuredContent field (typed JSON output) without populating content, the bridge JSON-marshals the structured payload as a fallback so it isn’t lost.

Full multi-modal passthrough (image / audio bytes forwarded to a vision-capable model adapter) is deferred to a follow-up wedge.

What yottacode does NOT track

Caution

yottacode’s checkpoints (/checkpoints, Esc Esc) can’t snapshot or restore MCP-driven changes (databases, external APIs, filesystem-via-server) — same limitation as run_bash. Use the underlying system’s own rollback (transactions, git, etc.).

MCP tools mutate state outside yottacode’s file model (databases, external APIs, filesystem-via-server). yottacode’s checkpoint system (/checkpoints, Esc Esc) cannot snapshot or restore MCP-driven changes — same limitation as run_bash. If you need rollback, use the underlying system’s own mechanisms (database transactions, git, etc.).

Running a server

Most servers are published to npm — run them with npx (no install step):

[[mcp_servers]]
name    = "filesystem"
command = "npx"
args    = ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/workspace"]

Curated test servers

Useful starting points; each is published as an npx-runnable npm package.

ServerArgsExercises
@modelcontextprotocol/server-filesystem-y ... /path/to/workspaceStdio baseline, mixed read/write, approval modal on writes.
@modelcontextprotocol/server-memory-y ...Stateful subprocess for the session lifetime.
@modelcontextprotocol/server-fetch-y ...One read-only tool, minimal-server conformance.
@modelcontextprotocol/server-sqlite-y ... /path/to/test.dbMutation approval against a local file resource.

Subprocess cleanup on hard exit

On a graceful yottacode shutdown (TUI quits, Ctrl+C handled), MCP subprocesses receive a shutdown notification, then SIGTERM with a 3s grace, then SIGKILL. On Linux, yottacode also sets PR_SET_PDEATHSIG = SIGTERM so the kernel signals the children automatically if yottacode itself dies ungracefully (SIGKILL, panic, crash). On macOS and BSD there’s no equivalent prctl in the Go stdlib — an ungraceful yottacode exit on those platforms can leave MCP subprocesses parented to launchd / init. ps/pgrep + kill is the manual cleanup path there.

Troubleshooting

  • command not found at session start. yottacode resolves command via PATH. Use an absolute path if the binary lives elsewhere.
  • Server crashes during init. Check /mcp logs <name> for the stderr — npm install errors, missing env vars, or bad args show up there.
  • Tool appears in /mcp but the model never calls it. The schema may be malformed; yottacode passes it through verbatim, and some models refuse tools they can’t parse. Re-check the server’s tools/list output.
  • Stale tools after editing config. Use /mcp restart <name> to reload from the originally-loaded config, or restart the yottacode session to pick up config.toml changes from disk.