Noon Docs · Getting Started · Concepts · Guides · Reference

This page gets an agent talking to a running Noon instance: which transport to use, how to get a token, and how to check the connection works.

Pick a transport

Noon exposes two MCP transports. Use whichever matches where your agent runs.

  • Streamable HTTP at POST /mcp, with an Authorization: Bearer <token> header on every request. Use this when the agent is a separate process, possibly on another machine, reaching Noon over a network (a tailnet, or a public address behind a TLS-terminating proxy). The bind host and port come from NOON_MCP_HOST (default 127.0.0.1) and NOON_MCP_PORT (default 3001).
  • stdio, for a client that launches Noon's MCP server as a local child process on the same machine. The token comes from the NOON_TOKEN environment variable of that process, not a header. The server checks the token at startup and refuses to start if it is missing, malformed, expired, or revoked. It also re-checks the token on every tool call, so revoking it stops an already-running stdio session on its next call.

Get a token

Bootstrap, once. On a fresh install, run the bootstrap CLI with ALLOW_BOOTSTRAP set:

ALLOW_BOOTSTRAP=true pnpm --filter @noon/core noon:bootstrap

This prints a root token to the console exactly once. Copy it now. It is never shown again, and running the command a second time does not mint a second one: the underlying claim is single-use even if you forget to unset ALLOW_BOOTSTRAP afterward.

Exchange it for a scoped session token. Do not hand the root token to a routine agent. Instead call:

curl -s -X POST http://localhost:3000/api/v1/tokens \
  -H "Authorization: Bearer $ROOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scopes": ["content:write", "content:read"], "expiresInDays": 14}'

This route requires a root-scoped bearer token. The body is {scopes, expiresInDays}: scopes is a non-empty list and may never include root, and expiresInDays defaults to 14 with a maximum of 365. The response is:

{
  "id": "tok_...",
  "token": "<plaintext, shown once>",
  "scopes": ["content:write", "content:read"],
  "expiresAt": "2026-08-24T00:00:00.000Z"
}

Store token, not id. id is a non-secret reference used later to revoke it.

Revoke a token when it should stop working immediately:

curl -s -X POST http://localhost:3000/api/v1/tokens/<id>/revoke \
  -H "Authorization: Bearer $ROOT_TOKEN"

This also requires root and returns {"id": "<id>", "revoked": true}. An already-revoked or unknown id still returns revoked: false rather than an error. An agent holding a root token can do the same over MCP with the revoke_token tool:

{"tool": "revoke_token", "arguments": {"tokenId": "<id>"}}

which returns {tokenId, revoked}.

Scopes

ScopeUnlocks
rootEverything, including minting and revoking tokens, and the root-only audit_log collection. Keep this out of routine agents.
content:readRead pages, posts, templates, and other content collections through the query tool. Also enough to call describe_schema.
content:writeCreate, update, rename, publish, unpublish, discard, and roll back pages, posts, templates, and site settings. Also enough to call describe_schema. Does not by itself unlock reading through query.
query:readThe same read access as content:read, through the query tool. No write access.
secrets:writeThe only scope that unlocks manage_connection (creating, updating, rotating, and revoking stored connections and credentials). Not implied by content:write.

For a routine authoring agent, issue content:write plus content:read (or query:read): it can write content and read back what it wrote, and nothing else. Add secrets:write only if the agent also manages connections.

Recovery, if the root token is lost

Recovery is a server-side path, not an API call. No HTTP route reads CMS_ROOT_OVERRIDE_KEY; only the recovery CLI does.

  1. Set CMS_ROOT_OVERRIDE_KEY in the server's environment. It must be a real 32-byte (256-bit) high-entropy secret; the CLI refuses to run against a weak value.
  2. Run the recovery CLI on the server:
pnpm --filter @noon/core noon:recover

It prompts for the key with input hidden if run at an interactive terminal, or reads it from piped stdin otherwise. It is never accepted as a command-line argument, so it cannot land in shell history. On a correct key it mints and prints a fresh root token, once, the same way bootstrap does.

Connect Claude Code, or another MCP client

Add this to your client's MCP server configuration, over Streamable HTTP:

{
  "mcpServers": {
    "noon": {
      "type": "http",
      "url": "http://<host>:3001/mcp",
      "headers": { "Authorization": "Bearer <your-token>" }
    }
  }
}

Put this in a user-level config file, not one checked into a repository. The token is a secret.

Check it works

List the tools, then ask the server to describe its content schemas:

curl -s -X POST http://<host>:3001/mcp \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl -s -X POST http://<host>:3001/mcp \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"describe_schema","arguments":{}}}'

A missing, malformed, or revoked token gets a structured auth error before either call reaches the tool layer. If both calls return real data, the agent is connected.

See also