calm isn't an MCP server built only for Claude Code — scripts/mcp-launcher.sh
is a shared entrypoint for any stdio-speaking MCP client (Claude Code, Cursor,
VS Code, Windsurf, JetBrains, Codex CLI, Antigravity, or any tool that can
spawn a command). This file explains how the launcher works and how to
point each client at it.
The "Launcher resolves a binary in 3 tiers" section below describes how to self-host
from within a CALM checkout (useful if you're developing calm itself, or your
project is this repo). If you just want calm as a regular MCP server for
a different project, no checkout needed, there are 2 ways:
curl -fsSL https://raw.githubusercontent.com/Eilodon/CALM/main/scripts/install.sh | shDownloads the right prebuilt binary for your current platform (Linux x86_64/aarch64,
macOS Apple Silicon — the same 3-platform matrix release.yml builds), verifies
SHA256 against the SHA256SUMS published with the release, and installs to
~/.local/bin/calm (override via the CI_INSTALL_DIR variable). There's no
build-from-source tier here — there's no source checkout to build from; an
unsupported platform means git clone + cargo build --release --bin calm
by hand per the README instead of an automatic fallback.
{
"mcpServers": {
"calm": {
"command": "npx",
"args": ["-y", "@eilodon/calm-mcp", "serve"]
}
}
}A thin JS package that picks the right prebuilt binary for your platform via
optionalDependencies (no network fetch on postinstall — the binary already
ships inside the npm tarball). See ../npm/README.md for
how to publish/verify this package.
No need to know the config file path ahead of time — the three clients below write the config to the right place with a single command, short enough that the agent itself (Claude/Codex/VS Code, when it has shell access) can run it on the user's behalf if asked something like "install Eilodon's CALM for me":
# Claude Code
claude mcp add --transport stdio calm -- npx -y @eilodon/calm-mcp serve
# Codex CLI
codex mcp add calm -- npx -y @eilodon/calm-mcp serve
# VS Code (writes straight to the user profile, no config file to open)
code --add-mcp '{"name":"calm","command":"npx","args":["-y","@eilodon/calm-mcp","serve"]}'Cursor/Windsurf/Antigravity don't have an equivalent CLI command yet — but since
those tools' agents (in agent/agentic mode) have file-write permission anyway,
the agent can still edit the right config file itself
(.cursor/mcp.json, .vscode/mcp.json, ~/.codeium/windsurf/mcp_config.json,
~/.gemini/config/mcp_config.json) when asked — there just isn't a single
built-in command to type for it.
After installing via method 1 or 2 above (doesn't apply to method 3 — codex/claude mcp add already wrote the config, no calm setup needed): calm setup
From inside the project you want calm to analyze:
calm setupWrites/merges a "calm" entry into .mcp.json, .cursor/mcp.json,
.vscode/mcp.json in that project — without touching any other entries already
there — pointing straight at the binary you just installed. If a "calm"
entry already points somewhere else (e.g. you were previously using the
launcher script), calm setup leaves it alone by default; use
calm setup --force if you really want to overwrite it. Windsurf/JetBrains
still need manual pasting (see their own sections below), since those are
global configs, not project-level.
Want a portable/shareable config (commit .mcp.json to the repo for the
whole team/CI, independent of the binary path on your machine)?
calm setup --npx writes the entry as npx -y @eilodon/calm-mcp@<version> serve instead of an absolute path — only needs Node wherever it runs.
By default it pins to this calm binary's own version, so a fresh npx
resolution (a new CI runner, a teammate's first checkout) always fetches the
exact same release instead of silently picking up whatever npm happens to
have published as @latest at that moment — reproducible tool
schemas/behavior across every machine that reads the same committed config.
Pass --track latest if you deliberately want the old unpinned
npx -y @eilodon/calm-mcp serve behavior instead (always resolves to
npm's current @latest on every cold invocation).
scripts/mcp-launcher.sh always tries these in order, and uses the first
binary it finds:
- Fast path — an already-usable binary:
$CI_MCP_BIN(manual override) →~/.cache/calm-mcp/<tag>/calm(downloaded-and-verified from a previous run) →target/release/calm→target/debug/calm(an existing local build). - Verified download — only applies on Linux x86_64/aarch64, and only when
HEADis sitting exactly on a released git tag (never guesses at a version). Downloads the right platform's asset from that tag's GitHub Release, verifies SHA256 against the publishedSHA256SUMS, then sanity-checks thatcalm --versionmatches the expected version — only after all of that does it cache the binary and exec it. Any step failing (download error, checksum mismatch, version mismatch) falls through to tier 3 — it never execs a binary that hasn't finished verification. - Build from source —
cargo build -p calm-cli, always works as long as the Rust toolchain is present. This is the only path for macOS/Windows, for a checkout mid-development (not sitting on a tag), or an environment with no network access.
Why it doesn't default to fetching "latest release": if you're developing on
main between two releases, fetching "latest" would silently install a binary
older than the source already on your machine — a mismatch that's very
hard to notice. The launcher only downloads by default when the checkout is
sitting exactly on a tag (a matching tag is the only case where it's safe to
trust the download); to prioritize fast startup and accept that version-drift
risk instead, set CI_MCP_LAUNCHER_ALLOW_LATEST=1.
If the SHA256 doesn't match (suspected corrupted or tampered-with download), the launcher does not exec that binary — it logs a clear error to stderr and automatically builds from source instead of just stopping, so the server can still always start.
Whatever binary gets selected above, the launcher by default invokes it via
calm connect instead of calm serve when both conditions hold: running on
Unix (macOS/Linux), and no other arg was passed to the launcher. calm connect
connects to (or spawns, if none exists yet) one daemon shared across the whole
project — multiple clients/sessions opening the same project share one
indexer/watcher/embedder instead of each session running its own (see
docs/adr/0005-daemon-forwarder-shared-process.md). You'll see
.calm/daemon.sock/daemon.meta/daemon.log appear in the project directory
as a sign this is active.
Any custom arg (e.g. a client config that adds its own --preset) makes the
launcher fall back to calm serve as before, unchanged. To turn daemon mode
off entirely (e.g. an environment that shouldn't share a process across
sessions), set CI_MCP_LAUNCHER_NO_DAEMON=1.
Beyond the static --preset/config.json ceiling, a connected client can
call the set_toolset tool to narrow (or reset) which tools this one
session sees, at runtime:
{"name": "set_toolset", "arguments": {"toolsets": ["trace", "security"]}}- Pass toolset names from
TOOLSET_NAMES(visible inrepo_overview's response, or viasearch). The union of the requested toolsets' tools becomes this session's visible set, still capped by the static preset —set_toolsetcan never widen past what--preset/config.jsonalready allows, only narrow within it. - A hard safety floor (
orient+guardrails+recover+edit) is never disableable, no matter what's requested — the tools backing the mandatoryedit_context/diff_impactgates, thesession_context/indexing_statusrecovery tools, andset_toolsetitself always stay reachable, so a session can always callset_toolsetagain to widen back out. - Enforcement happens at both
tools/list(hides the tool) andtools/call(refuses to dispatch it by name even if a client tries anyway) — the server also advertisestools.listChangedand sendsnotifications/tools/list_changedafter a real narrowing change, so a spec-compliant client knows to refetch its tool list. - Pass an empty list, or omit
toolsetsentirely, to reset back to the full preset.
The following three files all point at scripts/mcp-launcher.sh, differing
only in the top-level field name:
| Client | File (repo-level) | Top-level field |
|---|---|---|
| Claude Code | .mcp.json |
mcpServers |
| Cursor | .cursor/mcp.json |
mcpServers |
| VS Code | .vscode/mcp.json |
servers (different name, same command/args shape) |
Clone the repo and all three work immediately — no further configuration needed.
Windsurf rebranded to Devin Desktop (Cognition, June 2026) — still the same underlying Cascade platform, the config path below is unchanged.
Windsurf/Devin only reads config from ~/.codeium/windsurf/mcp_config.json
(per-user, no project-level option) — it can't be checked out with the repo,
so it has to be pasted by hand. The simplest way, no CALM clone needed,
uses npx like the Quick Start section in the README:
{
"mcpServers": {
"calm": {
"command": "npx",
"args": ["-y", "@eilodon/calm-mcp", "serve"]
}
}
}If you're developing on the CALM repo itself (not a different project), point
at scripts/mcp-launcher.sh directly instead of npx — replace
/absolute/path/to/CALM with the real path where you cloned this repo
(unlike the 3 checked-in configs above, the path here must be absolute
since there's no "project root" concept for a single global config file):
IMPORTANT: Because Windsurf uses a global config, you MUST pass
--project-root explicitly so CALM knows which project to index. Without
it, CALM falls back to the Windsurf process's current working directory
(which could be a different directory, or your home directory), leading to
the wrong scope getting indexed.
{
"mcpServers": {
"calm": {
"command": "bash",
"args": ["/absolute/path/to/CALM/scripts/mcp-launcher.sh", "--project-root", "/absolute/path/to/CALM"]
}
}
}Replace /absolute/path/to/CALM with the real path where you cloned this repo.
Both paths (the launcher script and the project root) must be absolute.
Devin Desktop also has its own "MCP Marketplace" right inside the Cascade
panel (the MCPs icon at the top, or Settings → Cascade → MCP Servers), which
supports one-click install via a deeplink shaped like
windsurf://windsurf-mcp-registry?serverName=<server-name> — CALM isn't
listed there as of this writing, so that kind of deeplink doesn't work for
CALM yet; use one of the two manual-paste methods above while a marketplace
submission is pending.
Configured through JetBrains's own UI settings (not a file checked into the
repo) — point command/args at the exact same snippet as the Windsurf section
above (absolute path to scripts/mcp-launcher.sh).
IMPORTANT: Like Windsurf, JetBrains also uses a global config, so you
MUST pass --project-root explicitly so CALM knows which project to index:
{
"command": "bash",
"args": ["/absolute/path/to/CALM/scripts/mcp-launcher.sh", "--project-root", "/absolute/path/to/CALM"]
}Replace /absolute/path/to/CALM with the real path where you cloned this repo.
Fastest way — one command, no manual file editing:
codex mcp add calm -- npx -y @eilodon/calm-mcp serveThis writes to the global config (~/.codex/config.toml) automatically. Check
it with codex mcp list or /mcp inside the Codex TUI.
CORRECTION (2026-07-12): an earlier version of this section said Codex was
"like Windsurf/JetBrains — no project-level option, global config only" — that
was wrong, corrected after re-checking current OpenAI documentation. Codex
does support project-scoped config via .codex/config.toml right inside
the repo, as long as that project is marked "trusted" (the exact trust
mechanism isn't documented in detail by OpenAI). A few sensitive keys
(model_provider, model_providers, openai_base_url, notify) are locked
and can't be overridden at the project level — but mcp_servers.* isn't on
that locked list, so CALM can still be declared here instead of only globally:
# .codex/config.toml (checked into the repo, requires the project be "trusted" by Codex)
[mcp_servers.calm]
command = "npx"
args = ["-y", "@eilodon/calm-mcp", "serve"]Or, if you're developing on the CALM repo itself, point at
scripts/mcp-launcher.sh (absolute path, same reasoning as Windsurf) instead
of npx:
IMPORTANT: If you use the launcher script (instead of npx), you MUST
pass --project-root explicitly so CALM knows which project to index:
[mcp_servers.calm]
command = "bash"
args = ["/absolute/path/to/CALM/scripts/mcp-launcher.sh", "--project-root", "/absolute/path/to/CALM"]Replace /absolute/path/to/CALM with the real path where you cloned this repo.
See details: developers.openai.com/codex/mcp.
Codex Cloud (the hosted/async product, different from the ChatGPT web app): not yet confirmed whether it has an equivalent setup-script/environment-config mechanism for pre-building a binary — OpenAI's public docs aren't detailed enough here (unlike the ChatGPT web app, which is confirmed to not read local Codex config, using its own separate plugin mechanism instead). Anyone who actually needs Codex Cloud support should test it directly rather than guess from the docs.
Also a global config, shared between the Antigravity IDE and the Antigravity
CLI, at ~/.gemini/config/mcp_config.json — same JSON mcpServers shape as
Claude Code/Cursor, just a different file location (global, not project-level):
IMPORTANT: Like Windsurf, Antigravity also uses a global config, so you
MUST pass --project-root explicitly so CALM knows which project to index:
{
"mcpServers": {
"calm": {
"command": "bash",
"args": ["/absolute/path/to/CALM/scripts/mcp-launcher.sh", "--project-root", "/absolute/path/to/CALM"]
}
}
}Replace /absolute/path/to/CALM with the real path where you cloned this repo.
After saving, Antigravity reloads automatically — no restart needed. It can
also be edited from inside the IDE via "..." on the agent panel → "Manage MCP
Servers" → "View raw config". The path to mcp-launcher.sh still must be
absolute, for the same reason as Windsurf.
docs/cloud-environment-setup.md covers a separate issue specific to Claude
Code on the web: the MCP client dials the server in parallel with the
SessionStart hook, with no ordering guarantee — so
.claude/hooks/session-start-build-calm.sh still exists independently of this
launcher. The launcher's fast path (tier 1) only checks "does a binary already
exist," not whether that binary is stale (e.g. mid-edit on calm's own
source) — that's still that SessionStart hook's own job, not replaced by this
launcher.