Skip to main content
Glama
Claude Code MCP Server

Claude Code MCP Server

Claude Code MCP Server

npm version

MCP server that lets AI agents with an always-on cloud computer (e.g. Muse by Meta, Grok Bot by SpaceXSI, ChatGPT Dots by OpenAI, Claude Duet by Anthropic, etc) orchestrate Claude Code cloud sessions natively via the Claude Code CLI.

It is the first server in a planned MCP orchestration suite: one MCP server per agent tool (Claude Code now; Codex, Antigravity, Grok and Cursor later), so an orchestrator such as Muse, Grok Bot, ChatGPT Dots or Claude Duet can coordinate tasks across several coding agents through the same kind of tool calls. The servers are meant to share one job model (#28), so one orchestration loop works with all of them.

Requires the Claude Code CLI, installed and signed in, on the machine that runs this server.

  1. Install it: npm install -g @anthropic-ai/claude-code

  2. Sign in: run claude once in a terminal and complete the claude.ai login (claude auth status should then report "loggedIn": true).

  3. Cloud sessions need a claude.ai account with Claude Code on the web, and the Claude GitHub app on the repositories sessions work in.

Without the CLI, every tool returns CLI_NOT_FOUND with the install command (and the server prints the same warning on stderr at startup). When the CLI is not signed in, tools return CLI_NOT_SIGNED_IN, which says to sign in with claude first.

It exposes seven tools over stdio: create a cloud session, queue a follow-up message into one, read the GitHub branch and pull request a session produced, cancel a create or local run that is stuck, run Claude Code headless in a local checkout (off until you list allowed directories), report how much of the Claude plan's usage allowance is left, and check the CLI's sign-in state. Each tool maps to one fixed claude invocation, and each returns structured JSON. Agents call typed tools and get typed results back, so they never need to write shell commands.

Design

  • Strict input schemas. Every field is typed, required fields are listed explicitly, and every schema sets additionalProperties: false. Invalid input is rejected by schema validation before any process is spawned.

  • No escape hatch. The tool surface is exactly the seven tools below. There is no "run any claude command" tool and no shell access.

  • Deterministic argv. Arguments are built from validated inputs in a fixed order and passed as an array (shell: false). Values are attached as --flag=value, or placed after --, so a value that starts with - can never be read as a flag. claude --cloud=<description> is executed on a pseudo-terminal because the CLI rejects that form when stdout is a pipe; the pty helper execs the same argv and never goes through a shell.

  • Structured outputs. Every tool declares an outputSchema and returns structuredContent with parsed fields, such as session_id and session_url. CLI stdout and stderr are returned in cli with terminal control sequences removed.

  • Cloud sessions. Create and follow-up always pass --cloud=. Session status runs print-mode claude --teleport=<session> inside the server's cache so the CLI can check out the branch the cloud session pushed, then the process exits. That argv carries no prompt and is not attached to a terminal.

  • Local runs are opt-in. claude_run_local is the only tool that runs Claude Code on this machine (claude -p). It refuses every call until CLAUDE_MCP_LOCAL_ROOTS lists directories, only runs inside them (after resolving symlinks), always passes an explicit permission mode from a fixed set, and never bypasses permissions. Until a second opt-in, CLAUDE_MCP_LOCAL_ALLOW_SHELL, is set, every run uses Claude Code's --restricted mode: no shell or code-running tools, and the checkout's settings, hooks and .mcp.json aren't loaded.

  • No orphaned processes. Every child (git, gh, the pty helper, claude) leads its own process group and is tracked. A cancelled create, or an MCP request the client cancels (for example on its own timeout), stops its processes with SIGTERM, then SIGKILL 5 s later. When the client closes stdin, or the server gets SIGINT, SIGTERM or SIGHUP, the server stops every child before it exits. If the server is killed outright, the pty helper notices and stops claude --cloud the same way. Only git and gh processes that are already running can outlive a SIGKILL, and each is bound by CLAUDE_MCP_TIMEOUT_MS.

  • Remote access is opt-in and private. By default the server speaks MCP over stdio and opens no port. Setting CLAUDE_MCP_HTTP_PORT adds a Streamable HTTP listener on 127.0.0.1. It refuses to start without CLAUDE_MCP_TOKEN, checks the Host and Origin headers against an allow list (DNS-rebinding protection), and answers 401 to any request without the right bearer token before MCP sees it. Expose it to other machines with tailscale serve, never Funnel or the public internet.

  • No telemetry. The server runs the local claude binary. Its own network calls are git clone / git fetch of a validated https://github.com/owner/name.git into the cache, and a read-only gh pr list when status needs a pull request number.

Related MCP server: Claude Code MCP

Prerequisites

  • Node.js 20 or later

  • The Claude Code CLI (npm install -g @anthropic-ai/claude-code), signed in by running claude once and completing the claude.ai login (claude auth status should report "loggedIn": true). The account needs cloud sessions (Claude Code on the web). Set CLAUDE_BIN when claude is not on the server's PATH.

  • Python 3 (the standard library only: pty, fcntl, termios) for claude_create_cloud_session. No pip packages. Follow-ups, session status, and auth status do not need Python. On Debian/Ubuntu this is the python3 package, which is usually already installed.

  • Git on PATH, when a call names a github.com repository. The server runs git with a fixed argument list and core.hooksPath=/dev/null.

  • The Claude GitHub app installed on each private repository the cloud VM should clone, push, or open a pull request in. Public repositories can be cloned once the claude.ai account is connected to GitHub. See GitHub access below.

  • The gh CLI, signed in, when session status should resolve a pull request that teleport's output does not already name. The branch is still returned when gh is absent.

Install

The package on npm is @sohampatwardhan/claude-code-mcp-server. Orchestrator agents start it over stdio:

npx -y @sohampatwardhan/claude-code-mcp-server@latest

The published bin is claude-code-mcp-server (#!/usr/bin/env node). Stdio is the only transport. Logs go to stderr so they stay off the JSON-RPC stream. The MCP server name is claude-code-mcp-server.

The client must be able to run the Claude Code CLI. Install it and sign in on the same machine (claude auth status should report "loggedIn": true), with cloud sessions available on the account. Python 3 (stdlib only) is required for claude_create_cloud_session. See Prerequisites.

claude --cloud packages the working directory. Set the client's cwd to the repository the session should work on, or set CLAUDE_MCP_CWD when the host can pass environment variables but not a working directory. The full list is in Environment variables.

Cursor

Add this to ~/.cursor/mcp.json or the project's .cursor/mcp.json:

{
  "mcpServers": {
    "claude-code": {
      "command": "npx",
      "args": ["-y", "@sohampatwardhan/claude-code-mcp-server@latest"],
      "cwd": "/absolute/path/to/the/repo/sessions/should/work/on",
      "env": {
        "CLAUDE_MCP_CWD": "/absolute/path/to/the/repo/sessions/should/work/on"
      }
    }
  }
}

cwd and CLAUDE_MCP_CWD should be the same directory. If the client has no cwd field, CLAUDE_MCP_CWD alone is enough.

Claude Desktop

The same mcpServers block goes in the desktop config:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

Restart Claude Desktop after saving. Point cwd / CLAUDE_MCP_CWD at the repository the cloud session should package.

Generic stdio

Any MCP client that launches a local process uses the same command. Field names vary (mcpServers vs servers, and some clients omit type).

{
  "mcpServers": {
    "claude-code": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@sohampatwardhan/claude-code-mcp-server@latest"],
      "env": {
        "CLAUDE_MCP_CWD": "/absolute/path/to/your/repo",
        "CLAUDE_BIN": "claude",
        "CLAUDE_MCP_TIMEOUT_MS": "300000"
      }
    }
  }
}

Run

The server speaks MCP over stdin/stdout. Normally the MCP client launches it, so you don't run it by hand. From a checkout, after npm run build:

npm start                       # same as: node dist/src/index.js

Remote access over HTTP

To drive Claude Code on one machine (for example a Mac with your checkouts and toolchain) from an orchestrator running somewhere else (Muse, Grok Bot, ChatGPT Dots, Claude Duet), run the server with the Streamable HTTP transport and reach it over your tailnet.

export CLAUDE_MCP_TOKEN="$(openssl rand -hex 32)"
CLAUDE_MCP_HTTP_PORT=8787 npx -y @sohampatwardhan/claude-code-mcp-server@latest

It prints listening on http://127.0.0.1:8787/mcp on stderr and serves MCP at that one path:

  • Stateless Streamable HTTP. Only POST /mcp is answered, with JSON responses. GET and DELETE get 405, and other paths 404. Jobs are shared across requests and connections, so a job_id from one request can be polled or cancelled from any later one, as on stdio.

  • Checks, in order, before any MCP handling:

    • The Host header (any port) must name an allowed host, else 403. That's loopback, the bind address, or CLAUDE_MCP_HTTP_ALLOWED_HOSTS.

    • An Origin header, when present, must too, else 403.

    • Authorization: Bearer <CLAUDE_MCP_TOKEN> must match, compared in constant time, else 401 with WWW-Authenticate: Bearer.

    • Bodies over 1 MiB get 413.

  • Shutdown. SIGINT, SIGTERM or SIGHUP closes the listener and stops every child the server started (create jobs, local runs, git), as on stdio. Closing stdin does nothing in HTTP mode, so it runs under launchd with stdin on /dev/null.

Expose it privately with tailscale serve. Keep the server on 127.0.0.1 and let Tailscale terminate HTTPS for your tailnet only:

tailscale serve --bg --https=443 http://127.0.0.1:8787

Then set CLAUDE_MCP_HTTP_ALLOWED_HOSTS to the machine's tailnet name (for example mymac.tail1234.ts.net), because Tailscale forwards requests with that name as their Host header (a 403 "Host not allowed" means it's missing). Clients on your tailnet connect to https://mymac.tail1234.ts.net/mcp with the bearer token. Never use tailscale funnel, port forwarding, or a public bind address. The token is the only credential on this endpoint, and the tools can start Claude Code sessions on your account.

Run it under launchd on macOS so it starts at login in your user session. Signed-in Claude Code needs your login keychain, which a launch agent in your session can reach and a bare SSH session often can't. Save as ~/Library/LaunchAgents/com.example.claude-code-mcp.plist, put the token in it, chmod 600 it, then launchctl load it:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.example.claude-code-mcp</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/bin/npx</string>
    <string>-y</string>
    <string>@sohampatwardhan/claude-code-mcp-server@latest</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
    <key>CLAUDE_MCP_HTTP_PORT</key><string>8787</string>
    <key>CLAUDE_MCP_TOKEN</key><string>paste-the-token-here</string>
    <key>CLAUDE_MCP_HTTP_ALLOWED_HOSTS</key><string>mymac.tail1234.ts.net</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardErrorPath</key><string>/tmp/claude-code-mcp.log</string>
</dict>
</plist>

Add the other variables you use (CLAUDE_MCP_REPO_CACHE, CLAUDE_MCP_LOCAL_ROOTS, ...) to the same EnvironmentVariables block. The log file gets the listening line and warnings, never the token.

Environment variables

Variable

Default

Meaning

CLAUDE_BIN

claude

Path or name of the Claude Code CLI binary.

CLAUDE_CLOUD_ENVIRONMENT_ID

unset

Default cloud environment id (for example ccpool_...), passed as --environment when a call does not pass environment_id.

CLAUDE_MCP_TIMEOUT_MS

300000

Hard timeout for each spawned CLI process, in milliseconds (positive integer). On timeout the process gets SIGTERM, then SIGKILL 5 s later. A create call can override it with timeout_s.

CLAUDE_MCP_IDLE_TIMEOUT_MS

unset (off)

Stop claude --cloud on create when it prints nothing for this many milliseconds (positive integer). A create call can override it with idle_timeout_s. Off by default, because packaging a large repository may print nothing for a while.

CLAUDE_MCP_TRUST_CWD

unset (off)

1, true, or yes lets create accept Claude Code's workspace-trust dialog for CLAUDE_MCP_CWD only (the process working directory when that is unset). 0, false, no, or unset leaves the dialog unanswered and the call fails with WORKSPACE_NOT_TRUSTED.

CLAUDE_MCP_PYTHON

python3

Python 3 interpreter used to allocate the pseudo-terminal for create.

CLAUDE_MCP_CWD

process working directory

Directory the CLI runs in when a call omits repo. claude --cloud packages this tree, and workspace trust is resolved from it. A relative path is resolved at startup. The path must exist and be a directory.

CLAUDE_MCP_REPOS

empty

Comma-separated allowlist of local directories a create call may select. Each entry is an absolute directory that exists at startup. A github.com owner/name is accepted and ignored for access control: any github.com repository can be cloned into the cache. Other hosts, relative paths, and missing directories are rejected at startup.

CLAUDE_MCP_REPO_CACHE

~/.claude-mcp/repos

Directory that holds checkouts of GitHub repositories named by a call. Created on first use. An existing path must be a directory. This directory is the allowlist for where clones are written.

CLAUDE_MCP_LOCAL_ROOTS

empty (local runs off)

Comma-separated absolute directories claude_run_local may run in, together with everything under them. Each must exist at startup and is compared in its real-path form. The filesystem root / is refused. Print mode skips Claude Code's workspace-trust dialog; while CLAUDE_MCP_LOCAL_ALLOW_SHELL is off, --restricted keeps a checkout's settings from loading, but Claude still reads and edits every file under the roots.

CLAUDE_MCP_USAGE_STOP_PERCENT

unset (no check)

An integer from 1 to 100. When set, create and claude_run_local first read the plan's usage (see claude_usage) and refuse with USAGE_LIMIT_NEAR while the session window or the all-models weekly window is at or above it. Scoped weekly windows (one model or surface) only report. If usage can't be read, work goes ahead.

CLAUDE_MCP_LOCAL_ALLOW_SHELL

off

1 / true / yes drops --restricted from local runs and lets them pre-approve Bash or use permission_mode: "auto", either of which runs shell commands on this machine without asking. It also trusts every checkout under the roots: their project settings, hooks, allow rules and .mcp.json servers load as in a normal claude -p. Off, Bash and auto fail with LOCAL_SHELL_DISABLED.

CLAUDE_MCP_HTTP_PORT

unset (stdio)

Port for the Streamable HTTP transport. Setting it is what turns HTTP on; stdio stays the default. 0 picks a free port. Requires CLAUDE_MCP_TOKEN.

CLAUDE_MCP_HTTP_HOST

127.0.0.1

Address the HTTP transport binds. Keep it on loopback and put tailscale serve in front; the server warns on stderr when it binds anything else.

CLAUDE_MCP_TOKEN

unset

Bearer token every HTTP request must send (Authorization: Bearer <token>): 32 to 1024 printable characters, no whitespace (openssl rand -hex 32). The HTTP transport refuses to start without it (exit 2). Never logged.

CLAUDE_MCP_HTTP_ALLOWED_HOSTS

empty

Extra comma-separated hostnames (no port or scheme) the HTTP transport accepts in the Host and Origin headers, besides localhost, 127.0.0.1, [::1] and the bind address. Add your Tailscale name, for example mymac.tail1234.ts.net, when using tailscale serve.

If any of these values is invalid, the server exits at startup with code 2 and prints a message on stderr.

When a create call omits repo, the CLI runs in CLAUDE_MCP_CWD, or in the server process's working directory when that variable is unset or blank. claude --cloud packages the repository in the directory it is started in: the cloud VM clones that directory's GitHub remote at the current branch, or uploads the local tree when it cannot clone the remote. Set the client's cwd or CLAUDE_MCP_CWD to the default repository. An invalid CLAUDE_MCP_CWD (missing path, or a path that is not a directory) makes the server exit at startup with code 2.

Choosing a repository per call

claude --cloud has no flag for a repository URL. The repository is whichever checkout the command runs in, and the cloud VM clones that directory's GitHub remote at the current branch (Use Claude Code in the cloud). The web UI can pick a repository and base branch directly; the CLI cannot, so this server prepares the checkout:

  • A github.com owner/name (or https://github.com/owner/name) needs no local checkout and does not have to be listed in CLAUDE_MCP_REPOS. The server clones it into CLAUDE_MCP_REPO_CACHE/<owner>/<name> from https://github.com/<owner>/<name>.git, sets origin to that URL, fetches, and checks out base_branch when the call sets one. Omitting base_branch checks out the remote's default branch. The first clone is partial (git clone --filter=blob:none): it downloads every commit and tree but only the file contents the checkout needs, and git fetches older contents on demand. Later calls never reclone a healthy cache. They fetch again and reset that cache checkout, including removing untracked files there. A cache directory that is not a git checkout is removed and cloned again. The create output's cache field says whether a call cloned (cloned: true) or reused the cache, and how long that took (duration_ms). A cache directory whose origin is a different repository is left unchanged and the call fails. base_branch must already exist on origin.

  • An absolute directory on CLAUDE_MCP_REPOS is used as the CLI working directory. The server does not fetch, reset, or otherwise modify that checkout, and base_branch is rejected for it.

  • Any other value, including a subdirectory of an allowlisted directory, a relative path, another host, or a URL with a token, fails before the CLI starts (REPO_NOT_ALLOWED). A clone, fetch, or missing-branch failure is REPO_PREPARE_FAILED.

The clone URL is built by the server. Tool arguments cannot name a host, a credential, or a refspec. Private clones use whatever credential helper git already has on this machine; that credential is what lets the local cache see the repository. It is not uploaded, and it is not what the cloud VM uses to push.

Workspace trust follows the directory that was selected. With CLAUDE_MCP_TRUST_CWD=1, the opt-in writes the trust flag for that directory's main git checkout (or the directory itself when it is not a repository) and answers the dialog only for that folder.

GitHub access

Two different GitHub identities are involved. They are easy to mix up because both end up talking about the same owner/name.

Claude Code's GitHub integration is what the cloud virtual machine uses. From Use Claude Code in the cloud and the web quickstart:

  • The VM clones the GitHub remote of the directory --cloud was started in, at that directory's current branch. It does not upload the local tree when that remote can be cloned. Push local commits first if the VM should see them; this server's cache is reset to the remote, so the branch on GitHub is what the VM clones.

  • Connecting the claude.ai account to GitHub lets a cloud session clone public repositories. Private repositories need the Claude GitHub app installed on that repository (repository settings, or the install URL Claude shows). The app is also required for auto-fix and for Projects.

  • /web-setup in a local Claude Code session stores the machine's gh token on the claude.ai account. Cloud sessions can then use any repository that token can access. The app is still required for auto-fix.

  • Each cloud task gets its own session and its own branch. After the branch is pushed, the session opens a pull request with gh pr create inside the cloud conversation. This server does not call the GitHub API to open a pull request.

  • The browser repository-and-branch picker is not available on the CLI. base_branch plus the cache checkout is how a CLI caller selects them.

This server only makes the local claude --cloud see the right remote and branch, then reads the artifacts back:

  • git clone --filter=blob:none / git fetch / git checkout in CLAUDE_MCP_REPO_CACHE, with hooks disabled and submodules not checked out.

  • claude --teleport=<session> --debug --debug-file=<cache>/sessions/<session>.teleport.log, stdin closed, stdout a pipe. On Claude Code 2.1.300, print mode validates the session, checks out the branch the session pushed, logs Branch after checkout: '<name>', and exits. It does not open a local interactive session. The debug file is required: without --debug that line is not emitted, and a signed-out print-mode teleport exits with empty stdout and stderr.

  • git symbolic-ref HEAD after teleport, used when it disagrees with the pre-teleport branch.

  • gh pr list --repo owner/name --head <branch> --json number,url --limit 1 when the teleport output did not include a pull request URL. That reads the pull request the cloud session already opened.

Teleport has to run in a checkout of the same repository (not a fork) with a clean work tree. The server runs it only in the cache, after a clean reset. It is never run in a directory from CLAUDE_MCP_REPOS. A session that has not pushed a branch yet fails teleport; status then returns CLI_EXIT_NONZERO and no branch. claude --resume, claude logs, and claude agents list local sessions, and claude -p --cloud <session> only queues a follow-up (CLI reference, sessions, headless). None of those report the pushed branch or the pull request.

Workspace trust

claude --cloud <description> requires an interactive terminal. On a pseudo-terminal the CLI then stops at its workspace-trust dialog until someone answers. Older versions asked "Do you trust the files in this folder?"; Claude Code 2.1.316 asks "Quick safety check: Is this a project you created or one you trust?" with "No, exit" selected above "Yes, I trust this folder". The server recognises both wordings. It renders the CLI's cursor moves as the spaces they draw, so a dialog built from positioned words still matches. Trust matters because accepting it lets that folder's project hooks, allow rules, and .mcp.json servers run.

This server does not answer that dialog on its own. Either:

  • Trust the folder yourself: run claude once in that directory and accept the dialog, or set projects["<path>"].hasTrustDialogAccepted to true in ~/.claude.json. <path> is the main git checkout when the selected directory is a repository or worktree, and the directory itself otherwise. The selected directory is repo when the call sets one, and CLAUDE_MCP_CWD otherwise (the process working directory when that variable is unset). Claude Code documents this key under project allow rules and workspace trust.

  • Or set CLAUDE_MCP_TRUST_CWD=1. Create then writes that same key for the one directory above and, if the dialog still appears, answers it once. It reads which option is selected (❯) and moves to "Yes, I trust this folder" with the arrow keys before pressing Enter, since Enter alone would choose "No, exit" on current versions. If the menu's selection can't be read, it doesn't answer and fails with WORKSPACE_NOT_TRUSTED. The opt-in covers that one directory. Create still runs with the CLI's normal permission prompts.

The trust edit holds ~/.claude.json.lock (exclusive create, treated as stale after 5 seconds) so two of these servers do not interleave. Inside the lock it re-reads ~/.claude.json immediately before an atomic rename, skips the write when hasTrustDialogAccepted is already true, and retries the merge when the file changed since the first read so other fields are kept.

Claude Code does not take that lock. A Claude process that saves ~/.claude.json after this server's second read can overwrite the trust flag, and a save that lands between that read and the rename can itself be overwritten. If the dialog comes back, set the flag again while no Claude process is writing the file.

Without the opt-in, the dialog fails the call with WORKSPACE_NOT_TRUSTED as soon as it is drawn (under a second on Claude Code 2.1.316), never after the full timeout. A description that merely contains the words "Do you trust this folder?" is not treated as the dialog. The Created cloud session: title line is ignored when the server looks for the prompt.

Trust is stored in ~/.claude.json in the home directory (not under ~/.claude/). When ~/.claude.json is missing or is not a JSON object, the opt-in leaves it unchanged and answers the dialog on the pty instead. Claude Code treats the home directory itself as trusted for the current session only, so a server whose CLAUDE_MCP_CWD is $HOME still needs the opt-in for the pty to answer the dialog.

Sign-in check

Before create and claude_run_local start anything, the server runs claude auth status in CLAUDE_MCP_CWD, which makes no model call. If it reports "loggedIn": false, the call fails in about a second with CLI_NOT_SIGNED_IN, naming the auth method the CLI reports and the remedy (claude auth login on the server's host; on macOS over SSH the login keychain may be locked). No clone, --cloud or -p process starts. A signed-in result is reused for 60 seconds. If auth status can't be read, times out (10 seconds), or fails for another reason, the run goes ahead and the CLI's own error still surfaces. A missing CLI fails with CLI_NOT_FOUND.

Tool reference

Every successful call returns structuredContent that matches the tool's outputSchema, and the same JSON as a text content block.

claude_create_cloud_session

Creates a new cloud session. Runs, on a pseudo-terminal: claude --cloud=<description> [--environment=<id>]

Create runs in the background and returns within about 2 seconds. On a large repository the clone and claude --cloud can take minutes, longer than an MCP client waits for one request (60 seconds by default in the MCP SDK). The call starts a job, waits up to 2 seconds, then returns:

  • status: "done": the job finished in time, and the fields below are final. A failure in that window comes back as the usual tool error.

  • status: "cloning", "starting" or "running": the job is still going. Call claude_get_session with job_id every few seconds until job.status is "done" or the call returns a tool error (the same codes as below). cloning means the GitHub cache checkout is being prepared. starting means claude --cloud is running and hasn't printed a session yet. running means it printed the session id (session_id and session_url are already set) and hasn't exited. A job stopped with claude_cancel_session reports cancelled.

Jobs live in the server's memory only. A finished job is kept for an hour (at most 100 finished jobs), and all jobs are lost when the server exits. Each process a job runs is bound by timeout_s (default CLAUDE_MCP_TIMEOUT_MS), and claude --cloud also by idle_timeout_s (default CLAUDE_MCP_IDLE_TIMEOUT_MS, off). Both time-outs fail with CLI_TIMEOUT. Use claude_cancel_session to stop a job early.

The CLI rejects --cloud with a task description when stdout is a pipe (--cloud requires an interactive terminal). There is a non-interactive create form, claude -p <task> --environment <ccpool>, but it only targets self-hosted environments and it omits --cloud. This server keeps --cloud= on every create so an older CLI cannot fall through to a local session. A pty has a single output stream, so the CLI's stdout and stderr are both returned in cli.stdout (cli.stderr is empty on this tool). cli.stdout and cli.stderr have terminal control sequences removed (cursor save and restore, CSI including private parameters, and other C0 controls) before they are returned.

Input schema:

{
  "type": "object",
  "properties": {
    "description": { "type": "string", "minLength": 1, "maxLength": 100000, "pattern": "\\S" },
    "environment_id": { "type": "string", "maxLength": 200, "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$" },
    "repo": { "type": "string", "minLength": 1, "maxLength": 4096 },
    "base_branch": { "type": "string", "minLength": 1, "maxLength": 255 },
    "timeout_s": { "type": "integer", "minimum": 1, "maximum": 86400 },
    "idle_timeout_s": { "type": "integer", "minimum": 1, "maximum": 86400 }
  },
  "required": ["description"],
  "additionalProperties": false
}

The environment id is chosen in this order: environment_id from the call, then CLAUDE_CLOUD_ENVIRONMENT_ID, then none (no --environment flag). repo matches an absolute path, a github.com owner/name, or a https://github.com/owner/name URL. A github.com value is cloned into the cache; an absolute path has to be on CLAUDE_MCP_REPOS. Omit repo to use the default directory; the output field is then null. When repo is set, cwd is the directory the CLI ran in and repo is the normalized value (owner/name, or the canonical directory path). base_branch is the branch checked out in the cache before --cloud runs. It is null in the output when the call did not name a github.com repository. For a GitHub repository it is the requested branch, or the remote default branch when the call omitted it. cache is { cloned, duration_ms } once the cache checkout is ready: cloned is true when this call made the first (partial) clone and false when it reused the cache with git fetch, and duration_ms covers the clone or fetch, checkout and clean, not a wait for another call holding the same repository's lock. It is null while the job is still cloning and for a local or default directory. When claude --cloud has to upload the repository instead of having the VM clone it from GitHub (no Claude GitHub app on the repository), a partial clone uploads as a snapshot of the working tree without history (Send local repositories without GitHub).

Output:

{
  "job_id": "job_5f0c2a9e7b1d43c8a6e2f019",
  "status": "done",
  "elapsed_ms": 41230,
  "session_id": "session_01AbC...",
  "session_url": "https://claude.ai/code/session_01AbC...?from=cli&m=0",
  "title": "Fix flaky login test",
  "parsed": true,
  "environment_id": null,
  "cwd": "/absolute/path/to/the/repo",
  "repo": "owner/name",
  "base_branch": "main",
  "args": ["--cloud=Fix flaky login test"],
  "cli": { "exit_code": 0, "stdout": "Created cloud session: ...", "stderr": "" }
}

parsed is true when both session_id and session_url were found in the CLI output. If the output can't be parsed, the call still succeeds with parsed: false, the unparsed fields set to null, and the cleaned output in cli. While the job is still going, cli.exit_code is null and cli.stdout holds the last 4,000 characters of output so far. For a GitHub repository, base_branch is the requested branch, or null if none was requested, until the checkout is ready.

claude_send_followup

Queues a message into an existing cloud session. Runs: claude --cloud=<session> -p -- <message>

Input schema:

{
  "type": "object",
  "properties": {
    "session": {
      "type": "string",
      "maxLength": 2000,
      "pattern": "^(?:(?:session|cse)_[A-Za-z0-9][A-Za-z0-9_-]*|https:\\/\\/claude\\.ai\\/code\\/[^\\s\"'<>]+)$"
    },
    "message": { "type": "string", "minLength": 1, "maxLength": 100000, "pattern": "\\S" }
  },
  "required": ["session", "message"],
  "additionalProperties": false
}

Output:

{
  "session": "session_01AbC...",
  "session_id": "session_01AbC...",
  "session_url": "https://claude.ai/code/session_01AbC...?from=cli&m=0",
  "sent": true,
  "parsed": true,
  "args": ["--cloud=session_01AbC...", "-p", "--", "Also update the docs"],
  "cli": { "exit_code": 0, "stdout": "Sent to cloud session.\n...", "stderr": "" }
}

sent is true when the CLI confirmed delivery (Sent to cloud session.).

claude_get_session

session takes a create or local-run job_id, or a cloud session.

With a job id (job_ and 24 hex characters), it reads the job from memory and runs nothing. For a create job it returns job with the same fields as the create output: progress while it runs, the final result once job.status is "done". For a claude_run_local job it returns local_job the same way, with job set to null. Top-level session_id mirrors the job's session_id. The other top-level fields are null, false or [], and repo is ignored. A failed job returns its tool error. An id this server doesn't know, because it expired, was never issued, or belonged to a server that has since exited, returns JOB_NOT_FOUND.

{
  "job": { "job_id": "job_5f0c2a9e7b1d43c8a6e2f019", "status": "cloning", "elapsed_ms": 18400, "session_id": null, "...": "..." },
  "session_id": null,
  "repo": null, "branch": null, "base_branch": null,
  "pr_number": null, "pr_url": null, "pr_source": null, "pr_error": null,
  "parsed": false, "args": [], "cli": null
}

With a session id or URL, it reports the GitHub artifacts of a cloud session: the branch it pushed, and the pull request URL and number when one exists. Runs, with stdin closed and stdout a pipe:

claude --teleport=<session_id> --debug --debug-file=<cache>/sessions/<session_id>.teleport.log

in the cache checkout of the session's repository. The process is killed if it exceeds CLAUDE_MCP_TIMEOUT_MS. No follow-up text is sent.

Input schema:

{
  "type": "object",
  "properties": {
    "session": {
      "type": "string",
      "maxLength": 2000,
      "pattern": "(?:^(?:(?:session|cse)_[A-Za-z0-9][A-Za-z0-9_-]*|https:\\/\\/claude\\.ai\\/code\\/[^\\s\"'<>]+)$)|(?:^job_[0-9a-f]{24}$)"
    },
    "repo": { "type": "string", "minLength": 1, "maxLength": 4096 }
  },
  "required": ["session"],
  "additionalProperties": false
}

repo is a github.com owner/name or https://github.com/owner/name URL. Omit it when claude_create_cloud_session created this session with a github.com repo: that slug and base branch are stored under the cache. A session created from a local directory has no record; pass repo then. Passing repo selects that repository even when a record exists. When the slug matches the record, the recorded base branch is checked out before teleport so the cache is clean.

Output:

{
  "job": null,
  "session_id": "session_01AbC...",
  "repo": "owner/name",
  "branch": "claude/add-retry",
  "base_branch": "main",
  "pr_number": 17,
  "pr_url": "https://github.com/owner/name/pull/17",
  "pr_source": "gh",
  "pr_error": null,
  "parsed": true,
  "args": ["--teleport=session_01AbC...", "--debug", "--debug-file=/.../sessions/session_01AbC....teleport.log"],
  "cli": { "exit_code": 0, "stdout": "", "stderr": "Branch after checkout: 'claude/add-retry'" }
}

parsed is true when branch was identified. branch is null when teleport exits 0 without moving HEAD off the base branch and without logging a checkout. pr_number and pr_url are null when the branch has no pull request. pr_source is output when teleport's text included the pull request, gh when gh pr list supplied it, and null when neither did. pr_error is set when gh is missing or fails; an empty pull-request list leaves pr_error null.

This tool does not return the assistant's latest reply. Open session_url from create for the conversation. claude -p --cloud <session> queues a message and exits; --output-format stream-json is not supported with --cloud (cloud sessions, CLI reference, also at docs.claude.com). --resume, claude logs, and claude agents --json address local sessions (sessions). --remote is a deprecated alias of --cloud.

On a self-hosted environment a Stop hook on the runner can capture replies (see Test self-hosted environments). That hook runs on the runner, outside this server.

If a session can't push

Cloud sessions sometimes fail to push their branch to the GitHub remote. claude_get_session then returns no branch (teleport fails with CLI_EXIT_NONZERO), or the session says so at its session_url. When that happens, don't wait for a retry; move the changes through the calling agent instead:

  1. Use claude_send_followup to ask the session for a patch (prompt below).

  2. Copy the patch from the session_url. claude_get_session reports only the pushed branch and pull request, not replies, so the patch is visible only there.

  3. Apply it to your own checkout of the base branch with git am (for format-patch output) or git apply --3way (for a plain diff).

  4. Run the tests, commit, push, and open or update the pull request yourself.

Ready-to-send follow-up (replace <base> with the base branch, e.g. main):

Pushing your branch to GitHub failed, so I'll apply your changes myself. Please commit any outstanding work, then run `git format-patch origin/<base>..HEAD --stdout` and paste the complete output in a single fenced code block, with nothing else inside it. If format-patch isn't possible, paste a full unified `git diff origin/<base>` that also includes new and deleted files (stage them first with `git add -A` and use `git diff --cached origin/<base>`). Don't try to push again.

claude_cancel_session

Stops a create job or claude_run_local job that is still running. Every process the job started on this machine (git, the pty helper, claude --cloud or claude -p, and anything they started) gets SIGTERM, then SIGKILL 5 s later. The call waits up to about 6 s for them to exit. Runs no new process.

It does not stop a cloud session. If claude --cloud already created one (the job's status was running), that session keeps running at its session_url. Open it there to stop it.

Input schema:

{
  "type": "object",
  "properties": {
    "session": { "type": "string", "maxLength": 2000, "pattern": "<same as claude_get_session>" }
  },
  "required": ["session"],
  "additionalProperties": false
}

session is the job id, or for a create job the session id or URL it already printed. Output:

{
  "job": { "job_id": "job_5f0c2a9e7b1d43c8a6e2f019", "status": "cancelled", "session_id": null, "...": "..." },
  "local_job": null,
  "cancelled": true,
  "stopped": true
}

For a local run, job is null and local_job holds the run (its session_id can still be resumed). cancelled is true when this call cancelled the job. For a job that already finished or was already cancelled it is false, and job shows that job as it stands. A job that already failed returns its tool error. stopped is true when every process the job started has exited. After a cancel, claude_get_session reports the job with status: "cancelled". A job id the server doesn't hold, or a session no unfinished job printed, returns JOB_NOT_FOUND.

Follow-ups, session status and auth status need no cancel tool. When the client cancels one of those requests (MCP notifications/cancelled, which the SDK sends when its own request timeout fires), the server stops that request's processes.

claude_run_local

Runs Claude Code headless on this machine, in an existing checkout, for work that needs the local toolchain or hardware rather than a cloud VM. Runs:

claude -p [--restricted --strict-mcp-config] --output-format=stream-json --verbose --permission-mode=<mode> [--model=<model>] [--max-turns=<n>] [--allowedTools=<tools>] [--resume=<session_id>] -- <prompt>

in cwd, stdin closed, stdout a pipe. It is off by default: with CLAUDE_MCP_LOCAL_ROOTS unset, every call fails with LOCAL_RUNS_DISABLED.

Restricted unless you allow shell access. With CLAUDE_MCP_LOCAL_ALLOW_SHELL off (the default), the run gets --restricted --strict-mcp-config (restricted mode, Claude Code 2.1.248 or later; an older CLI rejects the flag and the run fails with CLI_EXIT_NONZERO). Claude Code then:

  • removes Bash, PowerShell and the other tools that run commands or code, and WebFetch

  • loads only managed settings: not your user settings, and not the checkout's .claude/settings*.json, hooks or allow rules

  • starts no MCP servers, including the checkout's .mcp.json

  • confines the file tools to cwd, and refuses writes to settings, git and tool-configuration files (there's no one to approve them)

So a checkout can't give the run shell access, and the run can't rewrite .git/config or .claude/ to give a later run shell access either. Print mode never shows the workspace-trust dialog. With CLAUDE_MCP_LOCAL_ALLOW_SHELL=1 none of this applies: the checkout's settings, hooks and MCP servers load, so listing a root then trusts every checkout under it. Only turn it on for roots whose checkouts you'd open in Claude Code yourself.

Input schema:

{
  "type": "object",
  "properties": {
    "cwd": { "type": "string", "minLength": 1, "maxLength": 4096, "pattern": "^\\/[^\\0\\n\\r]*$" },
    "prompt": { "type": "string", "minLength": 1, "maxLength": 100000 },
    "resume": { "type": "string", "pattern": "<UUID>" },
    "model": { "type": "string", "pattern": "<alias or model name>" },
    "permission_mode": { "enum": ["dontAsk", "acceptEdits", "plan", "auto"] },
    "allowed_tools": { "type": "array", "items": { "enum": ["Read", "Glob", "Grep", "Edit", "Write", "NotebookEdit", "Bash", "WebFetch", "WebSearch"] } },
    "max_turns": { "type": "integer", "minimum": 1, "maximum": 1000 },
    "timeout_s": { "type": "integer", "minimum": 1, "maximum": 86400 }
  },
  "required": ["cwd", "prompt"],
  "additionalProperties": false
}
  • cwd must be an existing directory inside a CLAUDE_MCP_LOCAL_ROOTS root, or a root itself. Both are compared after realpath, so a symlink that leads out of a root is refused, one that leads into a root is allowed, and macOS /var/... and /private/var/... spellings agree. Anything else is REPO_NOT_ALLOWED, and the CLI is not started.

  • permission_mode defaults to dontAsk: reads and allowed_tools run, and anything that would prompt is denied (and listed in permission_denials). acceptEdits also allows file edits and common filesystem commands in cwd, plan is read-only exploration, and auto lets a background classifier review each action, shell commands included. The mode is always passed explicitly, so a settings file's defaultMode can't change it. bypassPermissions and --dangerously-skip-permissions are never used.

  • allowed_tools pre-approves built-in tools by name only. No patterns and no MCP tools. Bash allows every shell command.

  • Shell access is a separate opt-in. allowed_tools containing Bash, or permission_mode: "auto", lets the run execute shell commands on this machine without asking anyone, with your user's credentials and network access. Both fail with LOCAL_SHELL_DISABLED unless CLAUDE_MCP_LOCAL_ALLOW_SHELL=1. Without it the run is restricted (above): file reads and, in acceptEdits, file edits in cwd. WebFetch in allowed_tools then has no effect, because restricted mode removes it.

  • resume is the session_id of an earlier run. Claude Code finds the session by id, and may continue it under a new id; the result's session_id is the one to resume next.

  • timeout_s overrides CLAUDE_MCP_TIMEOUT_MS for this run.

Like create, the run is a background job and the call returns within about 2 seconds. Until status is "done", poll claude_get_session with job_id and read local_job. starting means claude -p hasn't reported a session yet, running means session_id is known, and claude_cancel_session with the job_id stops the run. A finished run:

{
  "job_id": "job_9c1d0e2f3a4b5c6d7e8f9012",
  "status": "done",
  "elapsed_ms": 48210,
  "session_id": "1f0e2c9a-6d0b-4c11-9f39-2a77c1d4e8b5",
  "cwd": "/Users/dev/GitRepos/app",
  "permission_mode": "acceptEdits",
  "restricted": true,
  "assistant_messages": 6,
  "parsed": true,
  "subtype": "success",
  "is_error": false,
  "result": "Fixed the failing test in src/parse.ts.",
  "num_turns": 7,
  "total_cost_usd": 0.0421,
  "usage": { "input_tokens": 1200, "output_tokens": 950, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 18000 },
  "permission_denials": ["Bash"],
  "rate_limit": null,
  "errors": [],
  "files": [
    { "path": "src/parse.ts", "status": " M", "from": null, "before": null },
    { "path": "notes.md", "status": " M", "from": null, "before": " M" }
  ],
  "args": ["-p", "--restricted", "--strict-mcp-config", "--output-format=stream-json", "--verbose", "--permission-mode=acceptEdits", "--", "Fix the failing test"],
  "cli": { "exit_code": 0, "stdout": "", "stderr": "" }
}
  • result, subtype, is_error, num_turns, total_cost_usd, usage and permission_denials come from the CLI's final result line. A run that hits max_turns, or ends on an API error, is still returned as data: is_error is true, subtype says why (for example error_max_turns), and errors holds the CLI's messages.

  • files is git status after the run (--porcelain codes), plus paths that were dirty before and are clean now (status: null). before is each path's status before the run, so before: null means the run changed a clean file. Changes the run committed don't show up. files is null when cwd isn't a git work tree, and until the job is done.

  • The stream-json transcript is read as it arrives and not kept: cli.stdout is empty when the result line was read, and otherwise holds the last 4,000 characters for diagnosis. cli.stderr is its last 4,000 characters.

  • With no result line, a non-zero exit is CLI_EXIT_NONZERO (or CLI_NOT_SIGNED_IN), and running past the time limit is CLI_TIMEOUT, whose message names the session so it can be resumed.

claude_usage

Reports how much of the Claude plan's usage allowance is used, the same numbers /usage shows in Claude Code ("Current session: 68% used · resets at 5:10pm"). Runs:

claude -p --restricted --strict-mcp-config --output-format=stream-json --verbose -- /usage

in CLAUDE_MCP_REPO_CACHE. /usage is a local command: Claude Code reads the plan's meters and runs no model turn, so a call costs nothing and takes about 2 seconds. Restricted mode means none of your hooks or settings run. A reading is reused for 60 seconds, and concurrent calls share one run.

Input schema:

{
  "type": "object",
  "properties": { "refresh": { "type": "boolean" } },
  "additionalProperties": false
}

refresh: true reads usage now instead of reusing a recent reading. Output:

{
  "checked_at": "2026-11-02T20:41:07.120Z",
  "cached": false,
  "parsed": true,
  "windows": [
    { "kind": "session", "group": "session", "percent": 68, "resets_at": "2026-11-02T22:10:00.474794+00:00", "severity": "normal", "is_active": true, "scope": null },
    { "kind": "weekly_scoped", "group": "weekly", "percent": 0, "resets_at": "2026-11-02T21:00:00+00:00", "severity": "normal", "is_active": false, "scope": "Cowork" }
  ],
  "extra_usage": { "is_enabled": false, "monthly_limit": null, "used_credits": null, "utilization": null, "currency": null },
  "stop_percent": 90,
  "blocked_by": null,
  "args": ["-p", "--restricted", "--strict-mcp-config", "--output-format=stream-json", "--verbose", "--", "/usage"],
  "cli": { "exit_code": 0, "stdout": "", "stderr": "" }
}
  • windows lists each meter the plan has: session is the rolling session window, weekly_all the all-models weekly window, and weekly_scoped a weekly window for one model or surface (named in scope). percent runs from 0 to 100, and resets_at is when the window resets. severity is the plan's own reading (normal, warning, critical).

  • windows is null when Claude Code can't read the plan's usage, for example with API-key auth or a token without the profile scope.

  • extra_usage is the usage-credit spend for the billing period, in minor units of currency, when the plan has credits.

  • stop_percent is CLAUDE_MCP_USAGE_STOP_PERCENT, and blocked_by names the window that makes new work fail with USAGE_LIMIT_NEAR (null when nothing blocks).

  • The report's text and its summary of local activity aren't returned: cli.stdout is empty when the report was read.

Orchestrators should check claude_usage before fanning out, and schedule new work after resets_at instead of retrying into a limit. During a claude_run_local run, the job's rate_limit field carries the latest rate-limit event the CLI reported (allowed_warning when nearing the limit, rejected when it's hit).

claude_auth_status

Reports the CLI's sign-in state. Runs: claude auth status

Input schema:

{ "type": "object", "properties": {}, "additionalProperties": false }

Output (fields the CLI does not print are null):

{
  "loggedIn": true,
  "authMethod": "claude.ai",
  "email": "dev@example.com",
  "orgName": "Example Org",
  "subscriptionType": "max",
  "cli": { "exit_code": 0, "stdout": "{...}", "stderr": "" }
}

Errors

Failures come back as MCP tool errors (isError: true). The text content block holds JSON with this fixed shape:

{
  "error": {
    "code": "CLI_EXIT_NONZERO",
    "message": "claude exited with code 1: Error: Unable to create cloud session ...",
    "args": ["--cloud=..."],
    "cli": { "exit_code": 1, "stdout": "", "stderr": "Error: ..." }
  }
}

code

When

CLI_EXIT_NONZERO

The CLI exited non-zero or was killed by a signal. message includes the stderr text.

CLI_TIMEOUT

The CLI ran longer than CLAUDE_MCP_TIMEOUT_MS (or create's timeout_s), or claude --cloud printed nothing for idle_timeout_s / CLAUDE_MCP_IDLE_TIMEOUT_MS.

CLI_NOT_FOUND

CLAUDE_BIN could not be found. message names the install command (npm install -g @anthropic-ai/claude-code) and the sign-in step. cli is null on the pipe path.

CLI_NOT_SIGNED_IN

The sign-in check before create or a local run found "loggedIn": false, or the CLI exited non-zero with output that reads like a missing or expired login (for example Invalid API key · Please run /login, or Cloud sessions need a claude.ai sign-in). message says to sign in with claude first.

CLI_SPAWN_FAILED

The process could not be started for another reason. cli is null.

OUTPUT_PARSE_FAILED

claude auth status did not print JSON with a boolean loggedIn.

PTY_UNAVAILABLE

Create could not start the Python interpreter that allocates the pseudo-terminal (CLAUDE_MCP_PYTHON). cli is null.

WORKSPACE_NOT_TRUSTED

Create reached Claude Code's workspace-trust dialog and CLAUDE_MCP_TRUST_CWD is off, so the dialog was not answered.

REPO_NOT_ALLOWED

repo is not an absolute directory on CLAUDE_MCP_REPOS and not a github.com owner/name, or a local run's cwd is missing, not a directory, or outside every CLAUDE_MCP_LOCAL_ROOTS root after resolving symlinks. The CLI is not started.

REPO_PREPARE_FAILED

Preparing the GitHub cache checkout failed (git missing, clone or fetch error, base_branch missing on origin, base_branch passed for a local directory, lock timeout, or the cache directory has a different origin).

SESSION_REPO_UNKNOWN

Session status was called without repo and this server has no record of a github.com repository for that session.

USAGE_LIMIT_NEAR

CLAUDE_MCP_USAGE_STOP_PERCENT is set and the plan's session or weekly window is at or above it. message names the window, its percent and its reset time. The CLI is not started.

LOCAL_RUNS_DISABLED

claude_run_local was called while CLAUDE_MCP_LOCAL_ROOTS lists no directories.

LOCAL_SHELL_DISABLED

claude_run_local asked for allowed_tools Bash or permission_mode auto while CLAUDE_MCP_LOCAL_ALLOW_SHELL is off. The CLI is not started.

JOB_NOT_FOUND

claude_get_session or claude_cancel_session was given a job id this server doesn't hold, or claude_cancel_session a session no unfinished job printed. A job id goes missing when: it was never issued, it finished more than an hour ago (or was pushed out by newer jobs), or the server restarted. Create the session again, or use the session_id from the finished job.

Schema violations (a missing required field, an unknown property, a value that doesn't match a pattern) are rejected by the MCP SDK's input validation as tool errors, and the CLI is never run.

Client configuration

Cursor, Claude Desktop, and a generic stdio entry are in Install. The same npx command is what Muse, Grok Bot, ChatGPT Dots, Claude Duet, and other orchestrators should launch. Pass CLAUDE_CLOUD_ENVIRONMENT_ID when every create should target one cloud environment, CLAUDE_MCP_REPOS for local checkouts a create call may select, and CLAUDE_MCP_REPO_CACHE to choose where GitHub repositories are cloned:

{
  "mcpServers": {
    "claude-code": {
      "command": "npx",
      "args": ["-y", "@sohampatwardhan/claude-code-mcp-server@latest"],
      "env": {
        "CLAUDE_MCP_CWD": "/absolute/path/to/your/repo",
        "CLAUDE_CLOUD_ENVIRONMENT_ID": "ccpool_your_environment_id",
        "CLAUDE_MCP_REPOS": "/absolute/path/to/a/local/checkout",
        "CLAUDE_MCP_REPO_CACHE": "/absolute/path/to/the/github/cache"
      }
    }
  }
}

A global install (npm install -g @sohampatwardhan/claude-code-mcp-server) exposes the same bin as "command": "claude-code-mcp-server" with "args": [].

An orchestrator on another machine connects over HTTP instead: a Streamable HTTP server entry with the URL and the bearer token header, in whatever form the client takes it, for example:

{
  "mcpServers": {
    "claude-code": {
      "type": "http",
      "url": "https://mymac.tail1234.ts.net/mcp",
      "headers": { "Authorization": "Bearer <CLAUDE_MCP_TOKEN>" }
    }
  }
}

Reporting problems (Muse, Grok Bot, ChatGPT Dots, Claude Duet and other agents)

The server's MCP instructions ask calling agents to report problems with the server itself as issues on this repository, using the agent's own GitHub access (the server makes no GitHub calls for this). If your client doesn't pass server instructions to its model, put this text in the agent's own instructions:

Report problems with this server as GitHub issues at https://github.com/sohampatwardhan/claude-code-mcp-server/issues, using your own GitHub access. Report a tool result that looks wrong for what happened: an error code that doesn't fit, an unexpected CLI_EXIT_NONZERO or CLI_TIMEOUT, parsed false on output that did name a session, a job that never finishes, or a CLI prompt or message the server didn't recognise. Search open issues first and comment on a match instead of opening a duplicate. Title it fix: <what went wrong> (or feat: <what is missing>), and include the tool name and arguments, the error code and message, cli.exit_code and the relevant lines of cli.stdout and cli.stderr, the server version from serverInfo, the host OS, what you expected, and whether it happens again. Before posting, remove tokens, credentials, email addresses, and private repository names, paths and code; the issue tracker is public. Don't file issues for problems in the session's own work (failing tests, a wrong change), or for an error whose message already says what to do (CLI_NOT_FOUND, CLI_NOT_SIGNED_IN, WORKSPACE_NOT_TRUSTED, REPO_NOT_ALLOWED) unless doing that didn't help.

Agents report server bugs: an error code that doesn't fit, a timeout or parse failure, a CLI prompt the server didn't recognise. They don't report failures in the session's own work, or setup errors whose message already says what to do. Reports are public, so the instructions tell agents to strip tokens, email addresses and private repository details first.

Development

git clone https://github.com/sohampatwardhan/claude-code-mcp-server.git
cd claude-code-mcp-server
npm install
npm run build
npm test        # unit tests + stdio smoke test (uses a fake CLI; creates no sessions)
src/
  index.ts    entry point (bin): loads config, starts the stdio server
  server.ts   tool definitions, input/output schemas, error shape
  argv.ts     deterministic argv construction for each tool
  repos.ts    per-call directory, GitHub checkout cache, session record, gh pr list, git status
  local.ts    CLAUDE_MCP_LOCAL_ROOTS and the local-run directory check
  parse.ts    CLI output parsing (session id / URL regexes, teleport branch and PR, auth JSON, stream-json, git status, /usage report)
  runner.ts   child_process spawn with timeout and output caps (follow-up, session status, local runs, and auth)
  pty.ts      pseudo-terminal spawn used by create (embedded Python helper)
  trust.ts    workspace-trust dialog detection and the opt-in ~/.claude.json edit
  jobs.ts     in-memory create and local-run jobs (ids, status, retention)
  children.ts registry of child processes: process groups, cancel scopes, shutdown
  http.ts     opt-in Streamable HTTP transport: Host/Origin checks, bearer token, stateless per-request servers
  config.ts   environment variable parsing and validation
test/
  parse.test.ts        parser unit tests against realistic CLI output
  argv-config.test.ts  argv and config unit tests
  repos.test.ts        allowlist, path checks, GitHub cache, and base branch
  trust.test.ts        trust-dialog detection, ~/.claude.json edits, worktree path
  jobs.test.ts         job store: ids, status, retention
  children.test.ts     process groups, scope cancel, stopAll
  http.test.ts         HTTP transport over real requests: token, Host/Origin, limits, shared jobs, shutdown
  local.test.ts        local roots and cwd checks: symlinks, /var vs /private/var, prefixes
  smoke.test.ts        starts the built server over stdio and exercises every tool
  fixtures/fake-claude.mjs  stand-in CLI used by the smoke test (TTY, trust dialog, teleport, print-mode stream-json, /usage)
  fixtures/fake-gh.mjs      stand-in gh used when session status looks up a pull request

prepack compiles TypeScript into dist/ and marks dist/src/index.js executable before the package is packed. CI runs npm ci, then npm run build and npm test.

Releasing

The owner published the first version, 0.1.1, once from a maintainer machine (npm refused 0.1.0 because that version number had been used before). Trusted publishing on npmjs.com can be saved only after that package exists. Later versions then publish from the tag workflow. Skip the v0.1.1 tag for this bootstrap: the workflow would try to publish 0.1.1 again.

npm login
npm ci
npm publish --access public

Leave --provenance off for that local publish. Provenance is added by the GitHub Actions workflow, which has the OIDC token.

On npmjs.com, open @sohampatwardhan/claude-code-mcp-server → Settings → Trusted publishing (the section is also labeled Trusted Publisher). Choose GitHub Actions and enter:

Field

Value

Organization or user

sohampatwardhan

Repository

claude-code-mcp-server

Workflow filename

release.yml

Environment name

empty

The filename is release.yml only, not .github/workflows/release.yml. Allow direct npm publish. A publisher created after 3 September 2026 starts with npm stage publish allowed and does not allow npm publish until that action is selected. Leave dist-tag management off. The same link can be created with npm CLI 11.15.0 or newer:

npm trust github @sohampatwardhan/claude-code-mcp-server \
  --repo sohampatwardhan/claude-code-mcp-server \
  --file release.yml \
  --allow-publish

The first successful publish from that workflow has to happen within two days of saving the publisher, or the configuration expires. Cut the next version for that check:

npm version patch   # or minor, or major
git push --follow-tags

npm version writes the version and a vX.Y.Z tag. .github/workflows/release.yml runs on that tag (and on a manual run that names the tag). It refuses the job when the tag is not v plus package.json "version", then runs npm ci, build, and test, publishes with npm publish --provenance --access public, and opens a GitHub Release with generated notes. Trusted publishing authenticates that step with GitHub OIDC.

License

MIT

Available Tools

7 tools
claude_auth_statusClaude Code auth statusA
Read-onlyIdempotent

Report whether the local Claude Code CLI is signed in, via claude auth status. If it is not, sign in with claude first (run it once in a terminal and complete the claude.ai login). A missing CLI returns CLI_NOT_FOUND with the install command (npm install -g @anthropic-ai/claude-code).

Inputs: no named parameters. Returns: cli, email, orgName, loggedIn, +2 more

claude_cancel_sessionCancel a create job or local runA
DestructiveIdempotent

Stop a claude_create_cloud_session job or claude_run_local job that is still running: every process it started on this machine (git, the pty helper, claude --cloud or claude -p) gets SIGTERM, then SIGKILL 5 s later. Pass the job_id, or for a create job the session id or URL it already printed. Waits up to about 6 s for the processes to exit and reports stopped. This does not stop a cloud session: one the CLI already created keeps running at its session_url. A job that already finished is left as it is (cancelled false), and one that failed returns its error.

Inputs: session (required). Returns: job, stopped, cancelled, local_job

claude_create_cloud_sessionCreate Claude Code cloud sessionA

Create a new Claude Code cloud session via claude --cloud on a pseudo-terminal (the CLI rejects this form when stdout is a pipe). The work runs as a background job and this call returns within a few seconds. If status is done, the result is final: the parsed session_id and session_url, or, when parsing fails, parsed false and the CLI output (terminal controls removed) in cli. Otherwise poll claude_get_session with job_id until job.status is done or the call returns an error; failures surface there with the same error codes. A github.com repo is cloned into CLAUDE_MCP_REPO_CACHE (origin set to that GitHub repository) and, when base_branch is set, checked out there before the CLI runs. Any other absolute directory must be on CLAUDE_MCP_REPOS. Omit repo to use CLAUDE_MCP_CWD. When Claude Code asks to trust the chosen folder, the call fails with WORKSPACE_NOT_TRUSTED unless CLAUDE_MCP_TRUST_CWD=1. The cloud VM's clone, push, and pull request use the Claude GitHub app (or /web-setup), not this server. If the session later can't push its branch to GitHub, ask it via claude_send_followup for a git format-patch (or full git diff) of its changes and apply, test, push and open the PR yourself.

Inputs: description (required), repo, timeout_s, base_branch, +2 more. Returns: cli, cwd, args, repo, +10 more

claude_get_sessionCloud session GitHub statusA
Idempotent

With a job id from claude_create_cloud_session, report that create's progress in job (status, elapsed time, the tail of the CLI output, and session_id once the CLI prints it), its final result once job.status is done, or its error. With a job id from claude_run_local, report that run in local_job the same way. A job read uses memory only and runs nothing. With a session id or URL, report the branch a cloud session pushed and any pull request it opened. Prepares the github.com checkout in CLAUDE_MCP_REPO_CACHE (origin set to that repository), then runs print-mode claude --teleport=<session> --debug there and stops when the CLI exits. Print mode checks out the session branch and exits; it is not given a prompt and it is not started on a terminal, so it does not continue as a local interactive session. The branch is taken from the debug line Branch after checkout when that matches HEAD, otherwise from a HEAD move. The pull request number and URL come from that output when present, otherwise from a read-only gh pr list. Claude's GitHub app (or /web-setup) is what pushes the branch and opens the pull request; this tool only reads them.

Inputs: session (required), repo. Returns: cli, job, args, repo, +9 more

claude_run_localRun Claude Code locallyA
Destructive

Run Claude Code headless on this machine (claude -p) in an existing checkout, instead of a cloud session: for work that needs the local toolchain or hardware. cwd must be inside a CLAUDE_MCP_LOCAL_ROOTS directory after resolving symlinks; with no roots configured every call fails with LOCAL_RUNS_DISABLED. Unless CLAUDE_MCP_LOCAL_ALLOW_SHELL is set, the run uses Claude Code's --restricted mode: no shell or code tools, no WebFetch, no MCP servers, and the checkout's settings, hooks and .mcp.json are not loaded (needs Claude Code 2.1.248 or later). The run is a background job and this call returns within a few seconds: if status is not done, poll claude_get_session with job_id until local_job.status is done or the call returns an error. The final result has the session_id (pass it as resume to continue), the result text, turns, cost and usage, denied tools, and the files git status lists. permission_mode defaults to dontAsk, which denies anything not read-only or in allowed_tools; permission bypass is never used. Pre-approving Bash or using auto mode runs shell commands unasked and fails with LOCAL_SHELL_DISABLED unless CLAUDE_MCP_LOCAL_ALLOW_SHELL is set. claude_cancel_session stops a run.

Inputs: cwd (required), prompt (required), model, resume, +4 more. Returns: cli, cwd, args, files, +17 more

claude_send_followupSend follow-up to Claude Code cloud sessionA

Queue a follow-up message into an existing Claude Code cloud session via claude --cloud <session> -p <message>. Replies are only visible at the session_url; claude_get_session reports the pushed branch and pull request. If the session can't push to GitHub, use this to ask for git format-patch origin/<base>..HEAD --stdout (or a full git diff including new files), then apply it locally with git am / git apply --3way and push it yourself.

Inputs: message (required), session (required). Returns: cli, args, sent, parsed, +3 more

claude_usageClaude plan usageA
Read-onlyIdempotent

Report how much of the Claude plan's usage allowance is used: each window (the rolling session window, weekly windows, model- or surface-scoped ones) with its percent, reset time and severity, plus usage-credit spend. Runs print-mode claude -p --restricted -- /usage, a local command that makes no model call and costs nothing; readings are reused for 60 s unless refresh is true. When CLAUDE_MCP_USAGE_STOP_PERCENT is set, blocked_by names the window that makes create and claude_run_local fail with USAGE_LIMIT_NEAR. windows is null when the CLI can't read plan usage.

Inputs: refresh. Returns: cli, args, cached, parsed, +5 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 7 tools

    Disambiguation4/5

    Each tool has a largely distinct role: create cloud session, run local job, send follow-up, cancel, usage, auth. The main soft spot is claude_get_session, which serves two purposes (polling a background job and inspecting a session's branch/PR), but its description clearly separates the job-id versus session-id modes so an agent can still pick correctly.

    Naming Consistency4/5

    All names share a claude_ prefix and snake_case, with verb_noun forms like create_cloud_session, send_followup, get_session, cancel_session, run_local. The two noun-only names (claude_usage, claude_auth_status) are a minor deviation but are still readable as report-style tools.

    Tool Count5/5

    Seven tools is well-scoped for a Claude Code job runner: lifecycle (create/run/cancel/poll), interaction (followup), and environment checks (auth/usage). No tool feels redundant or missing from the count perspective.

    Completeness4/5

    The surface covers the full lifecycle of cloud and local jobs plus auth and usage preflight checks, which are the key dead-end preventers. The notable gap is the absence of a list/enumerate tool, so agents must retain session and job ids themselves, with no cleanup operation for stale sessions.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers