Cook
DocsMCP Servers

MCP Servers

MCP (Model Context Protocol) servers extend Cook with external tool integrations. They let Cook interact with any service that implements the MCP standard.


What Are MCP Servers?

An MCP server is a process that exposes tools to Cook over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Cook's built-in tools. The model can discover and call these tools during a session.

For example, a GitHub MCP server might expose tools like create_issue, list_pull_requests, and search_code. A database server might expose query, list_tables, and describe_schema.

See the MCP specification for protocol details.


Configuration

MCP servers are configured in ~/.cook/config.toml under [mcp_servers.<name>] sections.

To distribute MCP servers to a team, or to restrict which servers users may run (allowedMcpServers / deniedMcpServers in requirements.toml / managed_config.toml, with Claude managed-settings.json advisory for foreign-defined servers), see Distribute across an organization in the Plugins guide.

stdio Transport (Local Process)

Cook spawns a local process and communicates over stdin/stdout:

[mcp_servers.my-server]
command = "/path/to/server"           # Server executable
args = ["--flag", "value"]            # Command arguments
env = { API_KEY = "sk-..." }          # Environment variables
enabled = true                        # Enable or disable the server (default: true)
startup_timeout_sec = 30              # Server startup timeout, seconds (default: 30)
tool_timeout_sec = 6000               # Per-tool-call timeout fallback, seconds (default: 6000)
tool_timeouts = { slow_op = 120 }     # Per-tool timeout overrides, seconds

Global startup-timeout override: instead of setting startup_timeout_sec per server, you can change the default for all servers via the MCP_TIMEOUT environment variable (milliseconds, compatible with Claude Code) or GROK_MCP_STARTUP_TIMEOUT_SECS (seconds). A per-server startup_timeout_sec still takes precedence over both. Cold-start npx/uvx servers that download packages on first launch often need this; the default is 30s.

MCP tool-result size cap: large MCP / use_tool results are truncated inline (full payload spilled under the session mcp/ folder). Default is 20_000 bytes. Override via:

  • env GROK_MAX_MCP_OUTPUT_BYTES or MAX_MCP_OUTPUT_BYTES (bytes; Cook-native wins if both set; Claude-style name, but we bound by bytes not tokens)
  • config.toml — user-level (~/.cook/config.toml) or repo-level (.grok/config.toml anywhere on the cwd → git-root chain; the deepest file wins, and the repo value applies only once the folder is trusted):
[mcp]
max_output_bytes = 40000

Precedence: requirements.toml > env > repo .grok/config.toml > user/managed config > default. Repo edits apply to running sessions in that directory via config hot-reload.

HTTP/SSE Transport (Remote Server)

For remote MCP servers accessible over HTTP:

[mcp_servers.remote-api]
url = "https://mcp.example.com/api"
headers = { "Authorization" = "Bearer token" }

MCP data-plane requests (JSON-RPC and SSE) and the anonymous-access probe carry a default User-Agent: grok-cli/<version> header, where <version> is the Cook binary version. OAuth discovery, client registration, and token requests are issued by the rmcp OAuth client and keep its own behavior (no default User-Agent). A valid User-Agent entry in the server's headers overrides the default; an invalid configured User-Agent value is dropped by header parsing (with a warning), so such a server still receives the default. Exception: Figma MCP servers (server name figma, legacy managed name grok_com_figma, or a figma.com host — all case-insensitive) send the bare token grok-cli with no version unless the config supplies its own User-Agent.

Streamable HTTP with Session ID

[mcp_servers.my-streamable-server]
url = "https://mcp.example.com/api/mcp"
headers = { "x-mcp-session-id" = "{{session_id}}" }

CLI Management

Manage MCP servers from the command line without editing config files:

# List configured MCP servers
cook mcp list
cook mcp list --json          # Machine-readable output

# Add a stdio server. Everything after -- is the server command, so flags
# like -y reach the server instead of being parsed by grok.
cook mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir

# Add a stdio server with environment variables (-e is repeatable)
cook mcp add postgres -e DATABASE_URL=postgres://localhost/mydb -- npx -y @modelcontextprotocol/server-postgres

# Add a remote HTTP server
cook mcp add --transport http sentry https://mcp.sentry.dev/mcp

# Add a remote server with an authentication header (--header is repeatable)
cook mcp add --transport http api https://mcp.example.com/mcp --header "Authorization: Bearer YOUR_TOKEN"

# Add a remote SSE server
cook mcp add --transport sse linear https://mcp.linear.app/sse

# Remove a server
cook mcp remove github

# Enable or disable a local/TOML (or compat-sourced) server
cook mcp enable github
cook mcp disable github

# Diagnose a server's configuration and connectivity
cook mcp doctor               # Check every configured server
cook mcp doctor github        # Check one server
cook mcp doctor --json        # Machine-readable output

The transport defaults to stdio; pass --transport http or --transport sse for remote servers.

By default cook mcp add writes to ~/.cook/config.toml (--scope user). Use --scope project to write to .grok/config.toml in the current directory instead, which can be committed and shared with your team (see Project-Scoped MCP Servers). Header and environment variable values are stored verbatim, so reference secrets as ${VAR} instead of pasting them into a committed project config (see Example Configurations). cook mcp list shows servers from both scopes, marking project-scoped ones with (project) and disabled ones with (disabled).

cook mcp remove searches both scopes and exits 0 after removing the server. It exits 1 when the name is not found, or when the name is defined in both user and project scope — pass --scope to say which one to remove.

cook mcp enable / disable persist the personal on/off state to user ~/.cook/config.toml (disabled_mcp_servers, and [mcp_servers.<name>].enabled when that entry exists). Scope:

  • Known names: user/project Cook TOML, names already on the disabled list, compat sources (.mcp.json, Claude, Cursor), and plugin MCP servers (same discovery as doctor//mcps).
  • Enable only: if the cwd-nearest project definition has sticky enabled = false, that single key is cleared (comments preserved); disable never rewrites project configs.
  • Not full /mcps parity: gateway connectors (managed_gateway:…, stored under disabled_mcp_tools.__managed_gateway_connectors) stay Space-only in the TUI. Idempotent; unknown names exit 1.

Breaking changes from earlier releases: --env now takes one KEY=value per flag (use -e A=1 -e B=2, not --env A=1 B=2), and server names may only contain letters, numbers, hyphens, and underscores.


Project-Scoped MCP Servers

MCP servers can be configured per-project by placing a .grok/config.toml in your repository:

my-project/
  .grok/
    config.toml
  src/
  ...
# .grok/config.toml
[mcp_servers.linear]
url = "https://mcp.linear.app/mcp"
enabled = true

When a server exposes a native HTTP/SSE endpoint, prefer the url form over wrapping it in a stdio proxy such as npx mcp-remote <url>. Cook handles HTTP/SSE and OAuth directly, so the native form avoids an extra subprocess per session. It also registers Cook's own OAuth client with the provider.

Cook walks from the current directory up to the git repo root, loading .grok/config.toml at each level:

Location Scope Priority
~/.cook/config.toml All projects Lowest
<repo-root>/.grok/config.toml This repository Medium
<cwd>/.grok/config.toml Current directory Highest

If a project defines a server with the same name as a global one, the project version replaces it entirely (fields are not merged).

Project-scoped files contribute [mcp_servers], [plugins], and [permission] entries. Cook reads most other config sections only from ~/.cook/config.toml.


Tool Naming

MCP tools are namespaced with the server name to avoid collisions. The catalog key is server__tool (two underscores):

  • Server filesystem with tool read_file becomes filesystem__read_file
  • Server github with tool create_issue becomes github__create_issue
  • A tool segment may start with a digit: server auth with tool 2fa_enable becomes auth__2fa_enable

What Cook admits

Cook admits a listed tool into the session catalog when all of these hold (xai-grok-mcp qualify_mcp_tool_name):

Part Rule
Server name Starts with a letter or underscore. Then ASCII letters, digits, underscores, and hyphens only.
Tool name Non-empty. ASCII letters, digits, underscores, and hyphens only. May start with a digit.
Delimiter Exactly one __. Names with a second __, or with ___, are skipped.
Catalog key server + __ + tool is at most 256 characters.

A rejected tool is skipped. The log line is Skipping MCP tool with the reason. The rest of that server's tools still load.

The 64-character cap is a provider function-name budget. It applies to the meta-tools search_tool and use_tool themselves. It does not apply to catalog keys. A server__tool name longer than 64 characters stays in the catalog. The model still calls it through use_tool with that full name. Cook used to drop those tools at 64 characters. It no longer does.

The server name in [mcp_servers.<name>] / cook mcp add is the catalog prefix. A name that starts with a digit is a valid TOML key. Catalog admission still rejects it (InvalidServerName). Rename the server so it starts with a letter or underscore.

A server name that ends with _ makes server__tool contain ___. Admission skips that key (InvalidOrAmbiguousQualifiedName).

search_tool / use_tool take the qualified catalog key, not the raw MCP tool name. Example: github__create_issue, not create_issue.


Toggle Servers at Runtime

You can enable or disable MCP servers without restarting Cook (TUI /mcps or CLI — see CLI Management).

The /mcps Modal

Open the MCP servers modal in the TUI:

  • Run /mcps as a slash command
  • Or press Ctrl+L (non–VS Code family) and navigate to the MCP Servers tab; on VS Code family use /plugins or /mcp and open the MCP Servers tab

From the modal you can:

  • See each server's source, enabled state, and tool count
  • Enable or disable a server with Space
  • Expand a server to view the tools it provides
  • Refresh the list with r after you edit config.toml
  • Authenticate an OAuth server with i
  • Add a server with a, or remove a local server with x (the modal asks for confirmation; press lowercase y to remove, or any other key to cancel)

Tool Discovery

The model has access to two built-in tools for working with MCP servers:

  • search_tool — Discover available integration tools across all enabled MCP servers. Use this to find tools by name or description.
  • use_tool — Call an integration tool discovered via search_tool. Specify the fully-qualified tool name (e.g., github__create_issue).

File-backed MCP arguments

On the Rust-shell local filesystem (Linux, macOS, and Windows), use_tool accepts exactly one form:

Form Call File contents
Inline {"tool_name":"example__update_page","tool_input":{"body":"Hello"}} None
Arguments file {"tool_name":"example__update_page","tool_input_file":"/tmp/mcp-arguments.json"} The complete remote object, such as {"body":"Hello"}; it cannot override the target.
Invocation file {"file":"/tmp/mcp-call.json"} Canonical {"tool_name":"example__update_page","tool_input":{"body":"Hello"}}.

Use a serializer for large documents or servers that require JSON inside a string:

import json
from pathlib import Path

body = Path("/tmp/page.html").read_text(encoding="utf-8")
arguments = {"input": json.dumps({"page_id": "123", "body": body})}
Path("/tmp/mcp-arguments.json").write_text(json.dumps(arguments), encoding="utf-8")
Path("/tmp/mcp-call.json").write_text(
    json.dumps({"tool_name": "example__update_page", "tool_input": arguments}),
    encoding="utf-8",
)

Nested JSON strings are not decoded again; remote file and tool_input_file keys remain server data. Do not mix forms, delegate to another file, or name a native tool. On-disk invocation keys stay canonical even when wrapper parameters are renamed.

Sources must be complete regular UTF-8 JSON objects, at most 8 MiB. Malformed/trailing JSON, duplicate envelope keys, missing fields, null paths, and non-object invocation arguments are rejected. Each prepared batch permits 16 MiB of source input and 32 MiB of serialized effective invocations, including hook rewrites. File operations have a 10-second deadline excluding permission waits. These limits do not change ordinary read windows, guarantee remote acceptance, or interrupt kernel I/O.

Read authorization and path, memory, symlink, and opted-in ignored-file restrictions apply before loading; transport reads do not satisfy read-before-edit. Target hooks then see loaded arguments, and separate MCP approval shows the resolved target and source. Hooks may rewrite arguments, not retarget or load another file. Authentication retries reuse the approved snapshot even if the file changes. History retains the authored reference; capped hooks/approvals see effective arguments. Server echoes and hook-added context remain unchanged.

ACP client filesystems do not yet support bounded acquisition and reject file mode without agent-local fallback. File-backed invocation is supported only through Rust-shell use_tool; other embedding hosts reject unresolved file inputs, and direct MCP APIs remain unchanged.


Compatibility

Cook loads MCP server configurations from multiple sources for compatibility:

Source Format Location Configurable
config.toml Native Cook config ~/.cook/config.toml, .grok/config.toml Always on
.claude.json Claude Code format ~/.claude.json [compat.claude] mcps
.cursor/mcp.json Cursor format ~/.cursor/mcp.json, <project>/.cursor/mcp.json [compat.cursor] mcps
.mcp.json MCP standard format Project root (cwd to git root) Loaded unless you have imported or dismissed the Claude import prompt (the import marker is set)

All sources are merged in priority order: config.toml > Claude > Cursor > .mcp.json. Servers from higher-priority sources take precedence when names conflict.

The Claude and Cursor MCP sources are scanned by default. To disable scanning for a specific vendor, set [compat.<vendor>] mcps = false in ~/.cook/config.toml or the corresponding environment variable (GROK_CURSOR_MCPS_ENABLED, GROK_CLAUDE_MCPS_ENABLED). See Configuration for details. Use cook inspect to see which MCP servers were loaded and their vendor origin ([cursor], [claude]).


MCP OAuth

For MCP servers that require OAuth authentication, Cook handles the credential flow automatically. When an MCP server requests OAuth credentials, Cook opens a browser-based authorization flow and stores the resulting tokens for future use.


Example Configurations

Use the url form for hosted MCP servers and the command / args form for local stdio tools.

Native HTTP (hosted services)

You must authenticate OAuth-based MCP servers before you can use them. Cook stores the resulting tokens under ~/.cook/mcp_credentials.json as local plaintext with owner-only file permissions (0600 on Unix). Prefer full-disk encryption on the host. After you edit config.toml, press r in the /mcps modal to refresh the server list.

[mcp_servers.linear]
url = "https://mcp.linear.app/mcp"
enabled = true

[mcp_servers.sentry]
url = "https://mcp.sentry.dev/mcp"
enabled = true

[mcp_servers.mixpanel]
url = "https://mcp.mixpanel.com/mcp"
enabled = true

For internal or self-hosted servers that authenticate with a static bearer token rather than OAuth, set the Authorization header explicitly:

[mcp_servers.internal-tools]
url = "https://mcp.internal.example.com/mcp"
enabled = true

[mcp_servers.internal-tools.headers]
Authorization = "Bearer <token>"

To avoid putting secrets in the config file, reference an environment variable with ${VAR} (or ${VAR:-default}). Cook expands string fields in [mcp_servers.*] — url, command, args, and the values in env and headers — at load time:

[mcp_servers.internal-tools]
url = "https://mcp.internal.example.com/mcp"
enabled = true
headers = { "Authorization" = "Bearer ${INTERNAL_MCP_TOKEN}" }

When another process keeps a short-lived token in a file (a credential helper, a sidecar, or a cron job), point bearer_token_file at it. Grok reads the file on every request to the server and sends Authorization: Bearer <contents>, with surrounding whitespace trimmed, so a rotated token takes effect on the next request without restarting Grok or reconnecting the server:

[mcp_servers.internal-tools]
url = "https://mcp.internal.example.com/mcp"
bearer_token_file = "~/.config/internal-tools/token"

The path must be absolute or start with ~/, and it may reference ${VAR}; any other path fails the server with an error. bearer_token_file works for HTTP and SSE servers and replaces an Authorization header or bearer_token_env_var. Servers with a token file skip OAuth discovery. A request fails with an error naming the path when the file is missing, empty, larger than 16 KiB, not UTF-8, or holds characters that are not allowed in an HTTP header.

Replace the token file atomically: write the new token to a temporary file in the same directory, then rename it over the old one. A writer that truncates and rewrites the file in place can race a request, which then fails or sends a partial token.

Local stdio

Use stdio for tools that must run locally (filesystem access, local databases, in-house servers).

# Filesystem access scoped to a directory
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/directory"]

# Local Postgres
[mcp_servers.postgres]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres", "postgresql://user:pass@localhost/db"]

# Custom server with a longer startup timeout and tuned per-tool timeouts
[mcp_servers.my-tools]
command = "/usr/local/bin/my-mcp-server"
args = ["--config", "/etc/my-mcp.json"]
startup_timeout_sec = 30
tool_timeout_sec = 120
tool_timeouts = { slow_analysis = 300, quick_lookup = 10 }

On Windows, npm installs launchers like npx, npm, pnpm, and yarn as .cmd batch shims (there is no npx.exe). Cook resolves a bare command such as npx to its real launcher path on PATH (honoring PATHEXT) before spawning, so these work without manually wrapping them in cmd /c. A command given as an absolute path or one containing a path separator is used as-is.


Available MCP Servers

A partial list of MCP servers you can configure with the url or command forms shown above. Confirm the current endpoint or package name with each provider before use:

Server Transport Endpoint / Package
Linear HTTP (OAuth) https://mcp.linear.app/mcp
Sentry HTTP (OAuth) https://mcp.sentry.dev/mcp
Mixpanel HTTP (OAuth) https://mcp.mixpanel.com/mcp
Filesystem stdio @modelcontextprotocol/server-filesystem
Git stdio @modelcontextprotocol/server-git
GitHub stdio @modelcontextprotocol/server-github
GitLab stdio @modelcontextprotocol/server-gitlab
PostgreSQL stdio @modelcontextprotocol/server-postgres
SQLite stdio @modelcontextprotocol/server-sqlite
Puppeteer stdio @modelcontextprotocol/server-puppeteer

See the MCP Server Registry for the full list of community servers and the MCP specification for protocol details.


Subagents and MCP

When the same server name appears in both config.toml / .mcp.json and the active agent’s mcpServers frontmatter, agent.md wins (including HTTP headers). The overlay is re-applied on config hot-reload, plugin reload, and agent switch so a disk rematerialize cannot restore the toml headers. Switching agents replaces the overlay with the new seat only: servers the new agent omits are dropped, and an agent with no mcpServers clears the prior overlay.

Subagents inherit the parent session’s connected MCP servers by default, including plugin-sourced agents. Use agent frontmatter mcpInheritance to restrict that set (all, none, named, or except). Details are in Subagents — MCP inheritance.

If a child lists search_tool / use_tool but returns an empty catalog, check that:

  1. The parent session actually connected the server (see Extensions / cook inspect)
  2. The agent’s mcpInheritance is not none or a filter that excludes the server
  3. Plugin agents cannot declare their own mcpServers in frontmatter — they only see parent-connected servers

Troubleshooting

Server Not Starting

# Test the server command manually
npx -y @modelcontextprotocol/server-filesystem /path

# Increase startup timeout
# In config.toml:
[mcp_servers.filesystem]
startup_timeout_sec = 30

For stdio servers, Cook captures the process's standard error to ~/.cook/logs/mcp/<server>.stderr.log, truncated on each launch. Check this file when a server starts but fails to handshake:

tail -f ~/.cook/logs/mcp/filesystem.stderr.log

Blocked by organization policy

If native TOML policy or Claude managed-settings.json sets deniedMcpServers, a nonempty allowedMcpServers, or allowManagedMcpServersOnly, Cook drops non-matching servers when it loads the settings and logs MCP server blocked by managed settings policy. Native Cook policy applies to every server; the Claude file applies to external servers only. cook inspect shows the lists, policy scope, and each remaining server. Details and examples: Restrict which MCP servers can run.

A listed tool never appears

The server starts and tools/list returns the tool, but /mcps and search_tool omit it.

  1. Check Skipping MCP tool in GROK_LOG_FILE / --debug. The reason names the rule that failed (invalid server name, invalid tool name, ambiguous __, or catalog key longer than 256 characters).
  2. Confirm the server config key starts with a letter or underscore. A digit-leading key never enters the catalog. A key that ends with _ is skipped as an ambiguous ___ name.
  3. Confirm the tool name uses only [A-Za-z0-9_-]. Dots and colons in the raw MCP name are skipped.
  4. Do not shorten a server__tool key to 64 characters. Catalog keys may be up to 256. The 64-character cap is only for search_tool / use_tool as function names. See Tool Naming.

This is separate from a tool that is missing on the first prompt because the handshake is still running. Send a second prompt after the server is up, or run cook mcp doctor.

Viewing Server Status

Use cook inspect to see all loaded MCP servers and their sources:

cook inspect          # Human-readable
cook inspect --json   # Machine-readable

Debug Logging

RUST_LOG=debug GROK_LOG_FILE=/tmp/cook.log cook
tail -f /tmp/cook.log

Look for log entries containing mcp to trace server startup, tool discovery, and tool call execution.