1 · Connecting
What you will have done: minted a token, pointed your harness at an instance, and proved the connection by listing the tools it offers.
The endpoint
Every instance answers on POST /mcp. It speaks JSON-RPC 2.0 over streamable HTTP, one message per request, one JSON response back. There are no sessions and no session ids, nothing streams, and GET /mcp answers 405 method not allowed because there is no server-initiated channel to open.
Seven methods are supported: initialize, ping, tools/list, tools/call, resources/list, resources/templates/list and resources/read. Asking for anything else gets -32601 method not supported.
initialize reports the protocol version the instance implements and its own name.
{"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2025-03-26",
"capabilities":{"tools":{"listChanged":false}},
"serverInfo":{"name":"neutron:Neutron","version":"1"}}}The token
Authentication is a bearer token, minted in the product and prefixed nck_.
Settings › Access & Security › API. The tab is visible to administrators. Minting asks for three things:
| Field | What it decides |
|---|---|
| Name | What the token is called in the audit log. Lower case, letters, digits and hyphens. bootstrap and api are reserved and refused. |
| Expires in | Whole days, 1 to 365. Absent or zero means never. |
| Mode | Whether the token acts as you, or as nothing but its own attached policies. |
| Scopes | Two checkboxes, both off by default. See below. |
Mode is the choice that matters. A token bound to a user acts as that user: their policies, their admin-ness, their view of the board, exactly as a browser session would. A service token is bound to nobody and carries only the policies attached to it, so a service token with no policies can do nothing at all. That is deliberate: default-deny is the resting state, not an error.
The raw value is shown once. Only its hash is stored, so a token you did not copy is a token you re-mint.
Wiring up a harness
Every recipe below is the same two facts in that tool’s grammar: the URL is https://<host>/mcp, and the header is Authorization: Bearer nck_….
Claude Code
claude mcp add -s user -t http neutron-<instance> https://<host>/mcp \
-H "Authorization: Bearer <token>"-s user registers it for every project on the machine; the default is the current directory only, which is how a connection goes quietly missing the next day. -t http is the streamable-HTTP transport. -H may be repeated.
Verified against Claude Code 2.1.274.
Codex
Codex has no flag for a literal header, so this goes in ~/.codex/config.toml by hand.
[mcp_servers.neutron-<instance>]
url = "https://<host>/mcp"
http_headers = { "Authorization" = "Bearer <token>" }The presence of url is what makes the server remote. Use bearer_token_env_var = "NEUTRON_TOKEN" instead if you would rather the token lived in the environment than in the file. The inline table has to stay on one line.
Verified against Codex CLI 0.147.0, whose remote MCP client is native. Older instructions telling you to set experimental_use_rmcp_client are out of date; that setting is gone.
OpenCode
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"neutron-<instance>": {
"type": "remote",
"url": "https://<host>/mcp",
"enabled": true,
"oauth": false,
"headers": { "Authorization": "Bearer <token>" }
}
}
}The discriminator is remote, not http, and the schema refuses unknown keys outright. "oauth": false matters here: OpenCode probes a remote server for OAuth metadata and can take that path in preference to your header. {env:NEUTRON_TOKEN} works anywhere in the file if you want the value out of it.
Verified against the OpenCode configuration schema as published on 2026-09-16, with OpenCode 1.17.18.
ChatGPT
ChatGPT reaches the endpoint as a custom connector in Developer mode. Turn Developer mode on, then Settings › Apps › Create, and fill in the name, the description, the MCP server URL and the authentication, choosing the token option and pasting the nck_ value.
Two things to know. Developer mode is enabled per administrator on a Business workspace rather than once for everybody, and ChatGPT will only accept a public HTTPS endpoint, so an instance that is not on the open internet cannot be connected this way.
There is a public walkthrough of exactly this for one instance, with the screens named as ChatGPT shows them.
Prove it worked
The connection is proved by a call, not by a green tick. Ask the endpoint what it can do.
curl -sS https://<host>/mcp \
-H "Authorization: Bearer $(cat ~/.config/neutron/<instance>-mcp-token)" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| jq -r '.result.tools[].name'A working connection lists every tool on the next page. A wrong or missing header answers 401 with one sentence: unauthorized: mint a token in Settings → API.
In Claude Code the same check is claude mcp list, which prints one line per server:
neutron-<instance>: https://<host>/mcp (HTTP) - ✔ ConnectedA server whose header is wrong reads ! Needs authentication instead. That output also prints your configured URLs, so it is not something to paste into an issue.
Keeping the token
Put it in a file, not in a shell history and not in a repository.
install -m 600 /dev/null ~/.config/neutron/<instance>-mcp-token
printf '%s' '<token>' > ~/.config/neutron/<instance>-mcp-tokenMode 600 is the point of the first line. Every example in this section reads the token from that path rather than naming it, and so should anything you write.
Scopes
A token carries scopes, chosen when it is minted. A scope does not grant anything. It narrows what an otherwise capable token may reach, which matters because a token bound to an administrator is an administrator, and without this every minted credential could approve a send.
Both are off by default, and neither is inherited from the user the token acts as. The mint form shows them as two checkboxes.
| Checkbox | What it permits |
|---|---|
| Can put letters on the wire (wire) | The sixteen call sites that put something beyond recall into the world. |
| Can run Lua against this instance (scripts) | The run tool, which takes Lua of your own. |
What wire guards
These are the acts a person is supposed to perform, and a token without wire is refused on every one of them.
| The act | Why it is on this list |
|---|---|
| Approving a send | A letter reaches a stranger. |
| Sending a drafted answer | The same, on a reply. |
| Lifting a suppression | Somebody who asked to be left alone gets written to again. |
| Activating a campaign | It begins minting letters. |
| Enrolling people into a sequencer | They are handed to whatever sends. |
| Changing a campaign’s review policy, its no-human-review confirmation or its identity pool | Zeroing the review policy hands every remaining letter to the ladder unread. Changing the pool changes which addresses they go out as. |
| Resuming a paused domain | The warden paused it on evidence, and every letter behind it goes back on the wire. |
| Creating a sending identity | It is handing out the right to send as an address. |
| Setting a sending identity active | The same right, handed back. Deactivating one is not refused. |
| Inviting somebody | This instance mails a person who did not ask for it. |
| Registering the vendor webhook | It mints a fresh receiver token and retires the one the vendor is still signing with. |
| Rotating the webhook secret | The same, from the other end. |
| Deleting a provider’s key | Sending or reply reporting stops, with nothing put back in its place. |
| Saving a provider’s keys | The same surface, from the other direction. |
| Setting the events webhook address | It decides where this instance’s activity goes. |
| Rotating the events signing secret | Deliveries stop verifying until every receiver is updated. |
Stopping, pausing, deactivating and verifying people are all open. The list is what cannot be taken back, not everything that writes.
Two MCP tools carry the check. sequence_enrol makes it itself, and webhook_register inherits it from the route it calls. On a chat turn a token drives, two of the instance’s own agent tools are gated the same way: invite_user, and outreach_verify_email when the action is to send.
The refusal is one sentence, and it names what to do:
this token cannot put letters on the wire: mint one with the wire scope in Settings → Access & Security → API
What scripts guards
Only the run tool, which takes Lua of your own. The eleven named procedures need no scope. Its refusal reads the same way:
this token cannot run scripts: mint one with the scripts scope in Settings → Access & Security → API
No token mints tokens
Minting is how a scope is granted, so a token that could mint could grant itself the wire. Every token is refused on the mint route with a token cannot mint tokens, whatever scopes it carries.
There is exactly one exception, and it is not a token. A first-boot provisioning key mints the first real token, and it is accepted only while no minted token exists. It stays refused on every wire route.
wire when a person is driving it and watching, and leave it off anything unattended. Give scripts to a token you are exploring with yourself.