The command
courier-mcp serves the endpoints your project offers to agents — those marked .agent(), and those its agents policy offers — over stdio, the transport an editor's agent starts a local MCP server with. It loads courier.config.ts the way your app's runtime and Studio load it — TypeScript, extensionless imports and tsconfig path aliases included — and offers every source it declares:
npm install -D @noverax/courier-mcp
npx @noverax/courier-mcp --dotenv .env.localStarted by hand it waits for a client on stdin; an editor starts it for you.
In one command
npx @noverax/create-courier --mcp sets Courier up with this done: agents: { offer: "reads" } in the config, the example API's reads described with .agent(), the package installed, and .mcp.json written for Claude Code. See Create Courier.
Write the package's full name
npx runs the project's own copy of a command. Where it finds none — an editor that starts servers outside the project, or before npm install — it fetches the package the command names from npm, and with no terminal to ask in, it does not ask. Under the bare name courier-mcp, npm keeps a package that is not Courier's; @noverax/courier-mcp runs your project's copy, or Courier's own.
Add it to your editor
Claude Code — from the project's root; --scope project writes .mcp.json for everyone who works on it:
claude mcp add courier --scope project -- npx @noverax/courier-mcp --dotenv .env.localCursor — in .cursor/mcp.json:
{
"mcpServers": {
"courier": { "command": "npx", "args": ["@noverax/courier-mcp", "--dotenv", ".env.local"] }
}
}VS Code — in .vscode/mcp.json:
{
"servers": {
"courier": {
"type": "stdio",
"command": "npx",
"args": ["@noverax/courier-mcp", "--cwd", "${workspaceFolder}", "--dotenv", ".env.local"]
}
}
}With the Courier extension
The Courier extension offers VS Code's agent each project's servers on its own — the API's, handed the project's .env files, and Studio's — with nothing to write here.
Codex — in ~/.codex/config.toml:
[mcp_servers.courier]
command = "npx"
args = ["@noverax/courier-mcp", "--cwd", "/path/to/project", "--dotenv", ".env.local"]An editor that starts servers somewhere other than the project names it with --cwd.
Options
| Option | What it does |
|---|---|
--cwd <dir> | The project's root, where courier.config.* is (default: the working directory) |
--source <names> | The sources to offer, comma-separated (default: every source). With one, tool names carry no source prefix |
--dotenv <files> | .env files to load before the config, comma-separated, relative to the root |
--read-only | Offer reads alone, whatever the config's agents allows |
--deny <patterns> | Endpoints to keep from agents as well as the config's own deny, comma-separated: billing.**,shop.users.* |
--list | Print what is offered to agents and why, and exit without serving |
--no-watch | Keep the tools the server started with: no reload on a save |
--allow-destructive | For a client that cannot ask a person, leave a call a person confirms to the client's own approval; a client that can ask is asked all the same |
--timeout <ms> | How long a call may take when its endpoint names no timeout (default: 60 000) |
--name <name> | The name the server gives its client (default: courier) |
Node's own --env-file is not one of them: Node takes that flag for itself wherever it appears.
Behind a corporate proxy, or with a certificate authority of your own, give the server NODE_USE_ENV_PROXY=1, HTTPS_PROXY and NODE_EXTRA_CA_CERTS in its env in the editor's configuration — Node reads them as it starts, before --dotenv loads anything. See Proxies & Certificates.
--read-only and --deny only ever narrow what the config offers: what an agent may call is widened in code and config, which go through review, never on a command line.
What is offered
--list reads the project as the server would and prints what it decided, without serving or reaching Studio:
$ npx @noverax/courier-mcp --list
3 of 6 endpoints offered to agents
agents: offer "reads", access "destructive", confirm "destructive", deny billing.**
orders_get read shop.orders.get marked .agent()
orders_list read shop.orders.list offer: "reads"
orders_cancel destructive, a person confirms shop.orders.cancel marked .agent()
Kept from agents
shop.orders.create POST orders a write not marked .agent()
shop.orders.export GET orders/export .agent(false)
billing.charge POST charge denied by billing.**It takes --source, --read-only and --deny as the server does, so it shows what a start with them would offer — a step a CI job can run, too. Warnings follow the list: a pattern that names no endpoint, a tool left out for its name, more than 40 tools.
While you edit
The server follows the project: a save to courier.config.ts, or to any module of the project it reaches — an API's definition, the agents policy — loads the config again, and the client is told when the tools it sees change. Nothing needs restarting:
- Only what changed is transformed. A module whose bytes are the same is not compiled again; a burst of saves — a branch checked out — is one reload. From the save to the client being told takes about a tenth of a second on a project of 40 tools, 75 ms of it the wait that gathers a burst.
- Told only of a real change. A save no agent can see — a comment, a formatting pass, a file touched — tells the client nothing.
- Both eras. A client on the 2025 handshake gets
notifications/tools/list_changed; a 2026-07-28 client gets it on the stream it opened withsubscriptions/listen. A client that asks for neither sees the new tools the next time it lists them. - A broken save keeps the server serving. The config or the tools a save breaks are logged, the last good ones stay, and the save that mends them loads them.
- Nearly free at rest. Each directory is watched — saves an editor makes by renaming a file included — and the few files the config reaches are looked at again every two seconds, for a filesystem that announces no saves: about 0.03 % of a core.
--no-watch keeps the tools the server started with, for a server that should not change while it runs.
What the agent gets
The tools are your endpoints, called with the credentials your config gives them — the API keys your middleware reads from the environment, for one. The agent can do what those credentials allow, through the endpoints the project offers and no others; a destructive one asks you first, in the editor, with the exact call — and so does every write where the policy says confirm: "write".
On start, the server lists the tools it offers in its log — which an editor shows as the server's output — with the policy's warnings, and stops with one line when the config cannot be loaded, its agents policy cannot be decided by, or it names no source it was asked for. A project that offers nothing gets a server with no tools, and a line saying so.
Watch it in Studio
With Studio running, the server joins it as the application's runtime does, and every call an agent makes shows in the timeline marked agent — its tool, the client, the variables, the answer, the middleware it ran through. Choose Agent in the Mode filter to see them alone. A call that did not run — declined by you, or refused for its arguments — is there too.
npx @noverax/courier-studioStudio serves the other direction too: the agent you code with can read what Studio recorded — the failed requests, the Audit's findings, the mocks — and send a request again after a fix, through courier-studio mcp. See Studio for Your Agent.
stdout is the protocol's
Over stdio, every byte on stdout is read as an MCP message. The server keeps it for the protocol: whatever else writes there — a console.log in your config, a library's banner — goes to stderr, which the editor shows as the server's log.