agent setup: mcp, the skill, agents.md, and hooks

crosscode start installs all of these for you. this page is what it installs, for anyone configuring a client by hand or using one start does not write.

the server is also listed on mcp.so.

mcp is the baseline: it is what makes every agent work. the guidance teaches an agent what to do with it, and it ships in two places so that every agent gets it: as a skill for claude code, and as a block in AGENTS.md for codex cli, cursor, opencode, gemini cli, and anything else that reads that file. hooks are a bonus for claude code and codex, and everything degrades cleanly where they do not exist.

the mcp server

apps/mcp-server speaks mcp over stdio and forwards tool calls to the local crosscode daemon for the current checkout. it takes no arguments and discovers the checkout from its working directory, so every client must launch it with cwd set to the checkout root.

it never starts a daemon. if there is no daemon for the checkout, every tool answers with DAEMON_UNAVAILABLE and a hint to run crosscode start.

claude code

.mcp.json at the checkout root:

{
  "mcpServers": {
    "crosscode": { "command": "crosscode-mcp", "args": [] }
  }
}

or claude mcp add crosscode -- crosscode-mcp.

codex cli

~/.codex/config.toml:

[mcp_servers.crosscode]
command = "crosscode-mcp"
args = []

codex spawns mcp servers with the working directory of the codex session, so start codex from the checkout root.

cursor, gemini cli, opencode

cursor uses .cursor/mcp.json, gemini cli uses .gemini/settings.json, and both take the same mcpServers block as claude code. opencode uses opencode.json:

{
  "mcp": {
    "crosscode": { "type": "local", "command": ["crosscode-mcp"], "enabled": true }
  }
}

running from source

a clone of this repository runs the same server through tsx:

{
  "command": "/absolute/path/to/crosscode/node_modules/.bin/tsx",
  "args": ["/absolute/path/to/crosscode/apps/mcp-server/src/main.ts"],
  "cwd": "/absolute/path/to/your/project"
}

available tools

four, and there will not be a fifth. the list below is generated by pnpm --filter @crosscode/mcp-server generate:docs from apps/mcp-server/src/tool-catalog.ts, the same module that answers tools/list. input schemas come from the zod schemas in packages/protocol/src/sync.ts.

conflicts ride on every response

every response from every tool carries a conflicts array, including status and pause, which nobody would call to ask about conflicts, plus an attention line when it is non-empty:

{
  "status": { "branch": "main", "connected": true, "paused": false, "cursor": 41,
              "pendingConflicts": 1, "peers": [] },
  "conflicts": [{ "id": "c-91", "path": "src/auth.ts", "detectedAt": "2026-08-05T10:00:00.000Z",
                  "binary": false, "peer": "bob" }],
  "attention": "1 unresolved Crosscode conflict: src/auth.ts. Merge it from its ours/theirs/ancestor text and call `resolve`. …"
}

what rides along is a summary: which files are conflicted and since when, not their contents. the ours/theirs/ancestor text comes from the conflicts tool, one call away, and only when the agent is actually about to merge. a pause call made for unrelated reasons has no business injecting three whole copies of a file into the agent's context.

this is the reason the design works. an agent only looks at anything when it is invoked, so a conflict arriving while it is idle would sit unseen until someone thought to ask. riding out on every response means the agent trips over it on its next call, whatever that call was for.

typed results

every tool declares an outputSchema in tools/list, and every successful call answers with structuredContent matching it as well as the json text block. a client that reads structured output gets a typed result; one that ignores it parses the text exactly as before. errors keep their plain text shape and carry no structured content.

the skill, and agents.md

skills/crosscode/SKILL.md is one skill, not a suite. it tells the agent what is happening in the background, how to resolve a conflict, and, the part that matters most, when to do nothing. an agent that narrates sync activity to its user has broken the product.

crosscode start installs that one file in two places, for every client, on every run:

both are idempotent. whatever else your AGENTS.md already says is preserved, and re-running start rewrites only what is between the two markers.

pre-edit hooks

the hook runs before a file edit and tells the agent about a conflict on that exact file before it writes over it. without a hook the agent still finds out on its next tool call; the hook just moves the moment earlier.

the command is crosscode-mcp hook, not crosscode hook. both bins in the published package point at the same file and it dispatches on the name it was invoked under, so crosscode is the cli and crosscode-mcp is the mcp entrypoint, and the hook is a subcommand of the mcp entrypoint, not a sixth cli command. crosscode hook is an unknown subcommand and exits with a USAGE_ERROR, which a hook runner will read as a hook that declined to say anything.

it reads the client's hook payload as json on stdin (or takes a path as its argument), and:

claude code

.claude/settings.local.json, which is the untracked counterpart to settings.json -- the hook names a binary only this machine has installed, so it must not ship to the rest of the repo:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit|NotebookEdit",
        "hooks": [{ "type": "command", "command": "crosscode-mcp hook" }]
      }
    ]
  }
}

if you ran crosscode start on 0.1.0, check this block before trusting it: that release wrote crosscode status --json here instead. that command ignores stdin, so it never learns which file is being edited, prints cli status json rather than a hook response, and cannot exit 2 to stop an edit.

re-running crosscode start on a later release rewrites that command in place. it does not append a second entry and does not leave the old one alone. an installer that skipped a hook it had already written would leave every 0.1.0 user on the old entry forever. editing the command string by hand does the same job; nothing else in the entry changes.

codex cli

codex's hook configuration lives in ~/.codex/config.toml and its pre-edit event has moved between releases, so crosscode start writes the entry only for versions it recognizes and leaves the file alone otherwise. the command is the same crosscode-mcp hook, and the payload parser looks for the file path in the shapes codex has used (tool_input, input, arguments, or a bare path), so the entry does not need updating when the field moves.

where a client has no pre-edit hook at all, mcp alone covers it. that is the intended degradation, not a gap.

from a clone

the hook is a subcommand of the mcp entrypoint, so a source checkout runs it as:

node_modules/.bin/tsx apps/mcp-server/src/main.ts hook

the installed crosscode-mcp hook spelling is what crosscode start writes into agent configs.

view raw markdown · generated from docs/mcp-clients.md at build time, do not hand-edit this page.