Skip to content

MCP

Two related surfaces share this page: the mcps catalogue declares the external Model Context Protocol servers an agent can call (plus a per-server tool policy), and the mcp block configures hyprpilot's three in-tree servers. At launch, hyprpilot merges the catalogue and projects it onto the vendor's native MCP surface — you keep one catalogue, every vendor reads it.

The mcps catalogue

Each mcps entry carries either a file path or an inline mcp_servers map (exactly one — declaring both, or neither, is a load error). File paths follow the standard { "mcpServers": { … } } shape that Claude Code, Codex, and Cursor all read, so you can drop your existing ~/.claude.json straight in:

yaml
profiles:
  - id: engineer
    agent: claude-code
    mcps:
      - file: ~/.claude.json
      - file: ~/.config/hyprpilot/mcps/team.json
        ignore:
          - scratch-*
          - '*-internal'

Inside each file:

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}" }
    }
  }
}

Files iterate in order; a later file's server overrides an earlier one of the same name. A single malformed file warns and is skipped rather than aborting the launch. Transport is inferred by field presence (command → stdio, url → http/sse). Everything except the typed hyprpilot policy block stays opaque, so vendor-specific server fields pass through untouched.

Fields

FieldTypeDefaultWhat it does
filepathAn { "mcpServers": { … } } JSON file. Exactly one of file / mcp_servers.
mcp_serversmapInline server map, same shape as the file's mcpServers value.
ignorestring[] (globs)[]Server names matching any pattern are dropped.

Inline servers

If you want a one-off server without a file, declare mcp_servers on the entry directly:

yaml
mcps:
  - mcp_servers:
      hyprpilot-nvim:
        command: uvx
        args:
          - hyprpilot-nvim-mcp

Ignoring servers

ignore is an optional glob array per entry. Server names matching any pattern are dropped before they reach the agent. Globs anchor against the full server name — work-* matches work-foo but not pre-work-foo.

Per-profile override

mcps on a profile wholesale-replaces the shared catalogue for that profile. mcps: [] means "no MCPs at all" — handy for a sandboxed read-only profile. To share one catalogue across every profile, put it in a patches entry instead of repeating it.

Reserved names

Each in-tree server's resolved name is reserved — by default hyprpilot, hyprpilot-skills, and hyprpilot-harness (see the mcp block). A configured server of that name is replaced by the injected entry, and renaming a server via mcp.<server>.name moves which name is reserved.

Tool policy

Each server entry takes an optional hyprpilot block for tool visibility and approval policy:

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
      "hyprpilot": {
        "includeTools": ["read_*", "list_*"],
        "excludeTools": ["delete_*"],
        "autoAcceptTools": ["read_*"],
        "autoRejectTools": ["delete_*"]
      }
    }
  }
}
FieldTypeDefaultWhat it does
includeToolsstring[] (globs)unsetVisibility allow-list. Unset = no allow-list; [] = deny all.
excludeToolsstring[] (globs)[]Visibility deny-list. Exclude beats include.
autoAcceptToolsstring[] (globs)inheritedApproval accept list. Falls back to mcp.autoAcceptTools.
autoRejectToolsstring[] (globs)inheritedApproval reject list. Reject beats accept.
  • Globs are server-relative — write read_*, not mcp__filesystem__read_*; the mcp__<server>__ prefix is implicit.
  • includeTools / excludeTools control visibility; autoAcceptTools / autoRejectTools control approval.
  • Servers with no per-server override inherit the mcp block's autoAcceptTools (default ['*']) / autoRejectTools.
  • Every other key on a server definition passes through to the vendor untouched.

The mcp block

hyprpilot ships three in-tree MCP servers. Each is its own subcommand, its own process, and its own catalogue entry, so each can be enabled, renamed, and given a tool policy independently:

ServerSubcommandDefault nameServesDefault
General toolshyprpilot mcp servehyprpilotopenenabled
Skillshyprpilot mcp skillshyprpilot-skillslist_skills / read_skill / list_skill_references / read_skill_references / reloadenabled
Agent harnesshyprpilot mcp harnesshyprpilot-harnesslist_profiles / spawn / session_*disabled

The mcp block gates and configures all three:

yaml
mcp:
  enabled: true # master gate over every in-tree server
  autoAcceptTools:
    - '*'
  autoRejectTools: []

  serve:
    enabled: true

  skills:
    dirs:
      - dir: ~/.config/hyprpilot/skills
      - dir: ~/.team/shared-skills
        ignore:
          - work-*
          - '*-experimental'

  harness:
    enabled: true
    max_sessions: 64
    max_live_sessions: 0
FieldTypeDefaultWhat it does
enabledbooltrueMaster gate. false auto-injects nothing, whatever the per-server blocks say.
serveobjectThe general-tools server. See below.
skillsobjectThe skills server. See below.
harnessobjectThe agent-harness server. See below.
autoAcceptToolsstring[] (globs)['*']Default tool-approval accept list, copied onto servers with no per-server policy.
autoRejectToolsstring[] (globs)[]Default tool-approval reject list. Reject beats accept.

A profile's mcp field wholesale-replaces this block. autoAcceptTools / autoRejectTools are glob-validated at config load (like the ignore lists) — a malformed glob errors at startup with a field-path message instead of silently failing at match time.

Every per-server block accepts enabled, name, autoAcceptTools, and autoRejectTools. The default names in the table above are not compiled in — they are seeded as mcp.serve.name / mcp.skills.name / mcp.harness.name in the shipped [[patches]], and the injector reads that field and nothing else, so a rename is a config edit. name is what the vendor prefixes tool calls with, so renaming the skills server to docs turns mcp__hyprpilot-skills__read_skill into mcp__docs__read_skill — anything that addresses a tool by name (a skill file, a system prompt) has to follow. The hyprpilot:// resource URIs are a fixed scheme and never change. A per-server autoAcceptTools overrides the block-level default rather than merging with it.

mcp.serve

The general-tools server — the surface for things that are neither a skills read nor an agent launch. open today. Stateless, so nothing to reload or reap.

mcp.skills

FieldTypeDefaultWhat it does
dirs{ dir, ignore? }[]XDG rootSkill roots — flat directories of <slug>/SKILL.md bundles.

Unlike the other two, this server is also gated on having something to serve: if dirs resolves to no skills at all, nothing is injected. The root defaults to ~/.config/hyprpilot/skills, seeded through an unscoped patches entry rather than a compiled default, so a user layer's patches extends the seed instead of replacing it.

dirs entries

FieldTypeDefaultWhat it does
dirpathSkill root to scan. Missing roots warn and are skipped.
ignorestring[] (globs)[]Slugs matching any pattern are skipped. First root wins on slug collision.

mcp.harness

FieldTypeDefaultWhat it does
max_depthint1Levels of delegation allowed. See below.
max_sessionsint64Finished sessions retained before the oldest are evicted. 0 retains every one. See below.
max_live_sessionsint0Sessions allowed to run at once before spawn is refused. 0 allows any number. See below.
notify_on_completebooltruePush a completion event into the lead's context when a turn finishes. See Agent Harness.
includeProfilesstring[] (globs)unsetProfile ids this launch may delegate to. Unset applies no filter; [] means none. See below.
excludeProfilesstring[] (globs)[]Profile ids this launch may not delegate to. Beats includeProfiles on overlap.
mcpmcp blockunsetThe [mcp] block every delegate this harness spawns receives. See below.

max_depth, max_sessions, max_live_sessions and notify_on_complete are seeded by the compiled defaults, so your own config overrides them per field without restating the rest.

Write a seeded key the way the seed writes it

Config keys accept both snake_case and camelCase, but patches merge by key string before anything is typed. These four are seeded snake_case, so overriding one as maxDepth / maxSessions / maxLiveSessions / notifyOnComplete arrives as a second key and fails config load with duplicate field. Every other key is unaffected — nothing seeds them, so there is nothing to collide with.

includeProfiles / excludeProfiles are the launcher's scope, distinct from the target's own profiles.harness opt-in. The two AND — a glob here narrows what is already nominated and can never promote a profile that never opted in. * crosses / (same globset semantics as $match.profile), so personal/* reaches personal/kilic/glm-5.2. Full treatment in Agent Harness → Scoping delegation per launch.

mcp.harness.max_depth

How many levels deep delegation may go. It is read in exactly one place — the mcp.harness block a gate is deciding on — and answers two questions with the same comparison, depth < max_depth:

  • whether a session running at that depth gets a harness server injected at all, and
  • whether a running sidecar at that depth may spawn.
ValueEffect
1Default. You delegate; your delegates do not. They get no harness server at all.
2Your delegates get a harness and may delegate once more. Theirs may not.
0Nothing anywhere gets a harness injected, and nothing may spawn.

The injection half is absolute — no enabled: true re-opens it, because a harness at the cap could only ever refuse spawn, so injecting one buys a long-lived process and seven tools that exist to error.

Raising it is a resource decision, not a security one. A max_live_sessions ceiling, where you set one at all, covers only its own sidecar's table, so an extra level lets N delegates each run N sessions — a fan-out no single ceiling catches and the lead that started the tree cannot see. There is deliberately no upper bound; the trade is yours to make.

mcp.harness.max_live_sessions

How many sessions this sidecar may have running at once. Past it, spawn is refused with a message naming the knob; session_kill on a finished or runaway session frees a slot. It bounds breadth where max_depth bounds recursion.

It is 0 — off — by default. How many agents are worth running at once is a property of your machine and your work, and hyprpilot has no way to guess it, so it refuses nothing until you say otherwise. Set a number on a shared or resource-tight host, where a profile's command being an arbitrary binary makes an unbounded fan-out something the host pays for. Leave it at 0 where an agent fanning out wide is exactly the point.

mcp.harness.max_sessions

How many finished sessions are retained before the oldest are evicted with their transcripts. 0 retains all of them.

Running sessions are outside this count, not merely spared by it — they hold a live model connection rather than history. Counting one would spend the retention budget on work still in flight, so with the concurrency ceiling off, enough concurrent agents would evict every transcript the cap exists to keep. session_list follows the same priority: running sessions lead, then most recent turn first.

Only distinct spawns grow the table — a conversation reuses its session however many turns it runs — so this bounds a long-lived sidecar's memory and temp directories without a tool you have to remember to call.

mcp.harness.mcp

An mcp block, same shape as the top-level one, applied to every delegate this harness spawns. It is folded per key onto whatever the delegate profile itself resolved: a key you set here wins, a key you leave unset inherits.

yaml
mcp:
  harness:
    enabled: true
    # what the agents I delegate to get
    mcp:
      serve:
        enabled: false
      skills:
        dirs:
          - dir: ~/.config/hyprpilot/skills/delegate

Distinct from includeProfiles, which answers which profiles you may reach. This answers what those agents can reach once running.

Setting harness.enabled: true here does not give a delegate a harness — the depth gate is checked first and does not consult enabled. Setting harness.max_depth here does, because the gate reads max_depth from the block it is deciding on, and the overlay is folded into that block. That is the supported way to hand one specific delegation an extra level without raising the cap for everything.

Because the fold is per key and not wholesale, a block naming only skills.enabled keeps the delegate's skills.dirs. Arrays replace rather than merge, so autoAcceptTools set here is the delegate's whole accept list.

Off by default, and that is a security property rather than a preference. A profile's command is an arbitrary binary, so anything that can call spawn executes commands as you. Turn it on deliberately — see Runtime → Agent Harness.

Vendor projection

The merged catalogue and policy are projected into each vendor's native shape at launch:

VendorServers viaPolicy via
claude-code--mcp-config <path> (0600 temp file)--allowedTools / --disallowedTools (mcp__server__tool)
codex-c mcp_servers.<name>.* overridesexact-name enabled_tools / disabled_tools / approval_mode
opencodeOPENCODE_CONFIG_CONTENT envordered OPENCODE_PERMISSION rules (server_tool)

Codex does not support wildcard tool patterns in those fields, so wildcard patterns are skipped for Codex with a warning. Provider-native arguments you pass after -- (or env you set on the agent) suppress the generated equivalents.

Secrets in the vendor handoff

MCP server entries commonly carry secrets — bearer tokens in HTTP headers, API keys in stdio env. hyprpilot keeps expanded secret material out of the vendor's argv, because a process's argv is world-readable through /proc/<pid>/cmdline on Linux:

  • claude-code — the resolved MCP config (with ${VAR} header/env references already expanded) is written to a per-launch 0600 temp file and passed as --mcp-config <path>. The file is created owner-only from the start, so the secret never lands in argv. hyprpilot exec()s into the vendor and does not delete the file first — the vendor needs to read it after the handoff; it is a launch-scoped temp the OS reclaims on tmp cleanup.
  • codex — bearer tokens are projected as mcp_servers.<name>.bearer_token_env_var / env_http_headers references (the env var name, not its value), so codex resolves the secret from its own environment.
  • opencode — the generated config rides the OPENCODE_CONFIG_CONTENT env var. Env is not world-readable like argv, but does inherit into child processes; this is the residual, lower-risk surface.