Skip to content

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:

FieldWhat it decides
NameWhat the token is called in the audit log. Lower case, letters, digits and hyphens. bootstrap and api are reserved and refused.
Expires inWhole days, 1 to 365. Absent or zero means never.
ModeWhether the token acts as you, or as nothing but its own attached policies.
ScopesTwo 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.

Neutron is not an OAuth server. There is no authorization flow to discover, no client registration and no refresh. If a harness offers to negotiate OAuth with the endpoint, switch it off and give it the static header instead. Neutron is an OAuth client of other people’s MCP servers, which is a different thing entirely and lives under Settings › Capabilities.

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) - ✔ Connected

A 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-token

Mode 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.

CheckboxWhat 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 actWhy it is on this list
Approving a sendA letter reaches a stranger.
Sending a drafted answerThe same, on a reply.
Lifting a suppressionSomebody who asked to be left alone gets written to again.
Activating a campaignIt begins minting letters.
Enrolling people into a sequencerThey are handed to whatever sends.
Changing a campaign’s review policy, its no-human-review confirmation or its identity poolZeroing the review policy hands every remaining letter to the ladder unread. Changing the pool changes which addresses they go out as.
Resuming a paused domainThe warden paused it on evidence, and every letter behind it goes back on the wire.
Creating a sending identityIt is handing out the right to send as an address.
Setting a sending identity activeThe same right, handed back. Deactivating one is not refused.
Inviting somebodyThis instance mails a person who did not ask for it.
Registering the vendor webhookIt mints a fresh receiver token and retires the one the vendor is still signing with.
Rotating the webhook secretThe same, from the other end.
Deleting a provider’s keySending or reply reporting stops, with nothing put back in its place.
Saving a provider’s keysThe same surface, from the other direction.
Setting the events webhook addressIt decides where this instance’s activity goes.
Rotating the events signing secretDeliveries 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.

Mint the narrowest thing that does the job. A reader needs neither scope. Give a token 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.
Last updated on