Skip to content

MCP (Model Context Protocol) ​

Nexrall Code connects to external tools and data sources through the Model Context Protocol — an open standard for giving an agent access to servers that expose tools, resources, and prompts. An MCP server is a small program (or remote endpoint) the agent can call; wiring one in is how you let the agent read your design docs, update tickets, or query an internal API.

Transport support ​

Nexrall Code's MCP client speaks three transports:

TransportUse when
stdioThe server is a local command nex launches and talks to over stdin/stdout — what nex mcp add <name> <command> registers, and how nex mcp serve runs.
SSE (sse)The server is a remote HTTP endpoint that streams events over Server-Sent Events — the older remote transport; prefer http where both are offered.
HTTP (http)The server is a plain HTTP JSON-RPC endpoint (Streamable HTTP).

All three are configured in .nexrall/mcp.json and negotiate the protocol the same way on connect.

Configuring a server ​

Add servers to .nexrall/mcp.json (project) or ~/.nexrall/mcp.json (user-level). Each entry names the server and tells the client how to reach it:

json
{
  "mcpServers": {
    "github": {
      "transport": "http",
      "url": "https://mcp.example.com/github"
    },
    "local-tools": {
      "transport": "sse",
      "url": "https://mcp.example.com/sse"
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
    }
  }
}

The exact fields follow the server's transport — a url for remote HTTP/SSE servers, or command (with optional args and env) for a stdio server nex spawns itself. nex mcp add writes the same shapes for you: nex mcp add fs -- npx -y @modelcontextprotocol/server-filesystem . for stdio, nex mcp add --transport http docs https://example.com/mcp for remote.

Keeping a server's tools always loaded ​

When many servers are configured, the combined tool catalogue can grow past what fits in the model's context — tools from less-relevant servers are then deferred until the model asks for them. If a server carries tools you never want out of reach, mark its entry "alwaysLoad": true:

json
{
  "mcpServers": {
    "github": {
      "transport": "http",
      "url": "https://mcp.example.com/github",
      "alwaysLoad": true
    }
  }
}

nex mcp add … --always-load writes the same flag, nex mcp list shows always-load next to the server, and /mcp marks the row once it is connected. Pinned servers are named on every request and their tools stay in context regardless of how the rest of the catalogue is selected. Off by default: pin the servers whose absence would break the session, not every server you have.

Signing in to a remote server ​

A remote server may require OAuth. Until you sign in, it is listed as needs authentication and its tools stay out of the session. Sign in once and every later session picks the token up by itself:

sh
nex mcp login github      # opens the browser, waits for the redirect
nex mcp logout github     # forget the stored token

Inside a session, /mcp lists your servers and their state, and /mcp auth <name> runs the same sign-in without leaving the CLI. The VS Code extension offers it in its MCP panel.

What login actually does: it asks the server which authorization server it trusts (RFC 9728 protected-resource metadata, falling back to RFC 8414 discovery on the URL itself), registers itself as a client when the server allows dynamic registration, then opens the browser with a PKCE (S256) authorization request and waits for the redirect on a loopback port. Afterwards it connects to the server to confirm the token works — a token the server rejects is reported as a failed sign-in rather than saved as success.

It refuses up front when the name is unknown, when the server is a local (stdio) one — those authenticate with env vars in mcp.json — or when the entry already pins an Authorization header, since a static header wins over a stored token and a silent conflict is worse than an error.

Servers that don't offer dynamic registration (Slack, several enterprise setups) take a pre-registered client instead: pass nex mcp login github --client-id <id> --client-secret <secret>, or put clientId / clientSecret in the server's mcp.json entry so every sign-in on this machine uses them. When credentials are present the client skips dynamic registration entirely, and the secret travels via client_secret_post at the token exchange and at every later refresh.

Tokens live in ~/.nexrall/mcp-auth.json (mode 0600, never printed by any command) and are refreshed automatically when they expire. Set NEXRALL_MCP_AUTH_FILE to keep them somewhere else.

Scopes work in both directions: the client asks for what the server advertises, and when a call later comes back 401 asking for more (insufficient_scope), that server is marked for re-authentication — /mcp shows which scope is missing — and /mcp auth <name> re-runs the flow with the union of the scopes. You will not see a server silently refuse every call because its token was minted before a permission was added.

Using MCP tools ​

Once configured and connected, the server's tools become available to the agent alongside its built-in tools. Tools from a server are typically namespaced so they don't collide with built-ins. Whether a given MCP tool runs automatically or prompts for approval is governed by the same permission rules as everything else.

A connection negotiates its protocol revision: Nexrall offers the newest it implements (2025-11-25) and keeps whichever revision the server answers with — including a newer one — so a server that switches revisions is spoken to in its own terms, and /mcp shows the revision actually in effect.

Servers can also change under a live session: a tools/list_changed notification refreshes the catalogue in place (no reconnect needed), and the same goes for prompts. And a server that supplies instructions when it connects gets them into the system prompt (size-capped per server and in total), so its own usage notes reach the model without you copying them into nexrall.md.

Large tool results ​

A tool result above ~30 000 characters is truncated to its head and tail, with the full output spilled to a temp file the model can read back with read_file (offset/limit) — so one query against a database or log server cannot inline megabytes into the conversation (and into every later request).

A server whose tool occasionally returns an inherently large payload (a schema dump, a file tree) can raise the cap for that tool by annotating its tools/list entry:

json
{
  "name": "get_schema",
  "description": "Returns the full database schema",
  "_meta": { "anthropic/maxResultSizeChars": 200000 }
}

The value is clamped to a hard ceiling of 500 000 characters, and anything non-numeric is ignored (the default cap applies) — a server can opt into bigger inline results, never out of truncation entirely.

Prompts as slash commands ​

Servers can also expose prompts — reusable templates that take arguments (summarize, review, explain, …). Every prompt from the connected servers is listed as a command in the / menu as <server>:<prompt>:

/jira:create Fix the login redirect
  • Arguments are positional, and single or double quotes keep a value together: /jira:create fix "the login bug" high.
  • The menu shows what the server declared — <summary> [priority] — and a required argument is enforced: running the command without it is refused with the missing name, before anything is sent.
  • The same prompt answers to Claude Code's spelling (/mcp__jira__create), and /mcp prompts lists them all (or /mcp prompts jira for one server).

The server fills the template; the result becomes your turn as an ordinary message, so checkpoints, transcripts and compaction treat it like anything you typed. Prompts are not tool calls and they never run a command on their own. If a skill happens to share the exact name, the skill wins — rename one of the two.

Elicitation (a server asking you something) ​

A server can need something only you have in the middle of a tool call — which project, a preference, or a sign-in it cannot do itself. MCP calls this elicitation: the server sends a request and waits, and the tool call completes with whatever you answered.

Nexrall advertises the capability in both of its forms (form and url), so servers know they may ask:

  • Form mode — the server describes fields, and the CLI asks for them with the same dialogs it uses everywhere else: enums and booleans become a choice box, strings and numbers a typed line. A field the schema marks required is enforced; numbers are sent as numbers, not strings.
  • URL mode — the server needs a browser round-trip (OAuth-style). The CLI offers to open the page and tells the server you are done; it never needs to see what happens in the tab.

Esc (or Ctrl+C) means decline — the request is answered, not ignored, so the tool call resumes with "the user said no" instead of hanging. In a headless run (nex --print, CI) or in a host with no dialog, the answer is a clean decline by default — an unattended session can never deadlock on a question.

If you want something other than a dialog — fill the form programmatically, audit answers, or refuse categorically — use the Elicitation and ElicitationResult hooks. They run before and after the dialog respectively, and a hook that answers skips the dialog entirely, which is how a CI run fills a form nobody is there to type.

A server with a strict capability schema can refuse to connect over the richer {form: {}, url: {}} shape. Give that server "bareElicitationCapability": true in its mcp.json entry and the client declares the older empty elicitation: {} instead (the same escape hatch, with the same key, that Claude Code offers) — forms still work, only url mode stops being advertised.

Serving Nexrall to another agent (nex mcp serve) ​

Nexrall can also be the server. nex mcp serve runs a read-only toolset on stdio, so another agent — Claude Code, an IDE, your own script — can read this machine's files with the tools Nexrall already has:

bash
# from the other agent's side (Claude Code shown):
claude mcp add nexrall -- nex mcp serve --cwd /path/to/project

# a session's own view of it, using the same config machinery:
nex mcp add nexrall -- nex mcp serve
nex mcp list

With Claude Code's default scope the entry goes to your machine-local config and connects right away. If you share it with a team instead (claude mcp add -s project …, i.e. a committed .mcp.json), each person sees ⏸ Pending approval until they approve it in the Claude Code TUI — deliberate on Claude Code's side, for the same reason Nexrall asks before trusting a folder: a cloned repository must not be able to launch its own servers.

It offers four tools — read_file, list_directory, glob, search_files — answered by the same executor a Nexrall turn uses, so output, limits and error messages are identical to what you see in a session.

Read-only, deliberately. Everything it exposes runs with no human in the loop: the caller is another agent, and the server has no dialog to raise. A tool this server does not offer is refused as tool output ("not offered … Available: …") rather than by failing the call, so the calling model can pick another route instead of concluding the connection broke. Writes, deletes and bash are not exposed; when they are, an approval story has to come first.

Notes from running it:

  • stdout is the protocol channel. nex mcp serve prints nothing there — the ready note and errors go to stderr, where the client collects server logs. A stray console.log mid-stream is what breaks MCP clients, so the test suite asserts that every line stdout ever carried parses as a JSON-RPC frame.
  • --cwd <dir> is the root relative paths resolve against; an invalid one is refused at startup with exit code 1 and nothing written to stdout.
  • Closing stdin is how the client says stop — the server exits 0. initialize echoes your protocol version when it is one we implement (2025-11-25 / 2025-06-18 / 2025-03-26 / 2024-11-05), and answers our default (2025-11-25) for anything else.

Security ​

MCP servers run code on your behalf, so Nexrall treats them as a trust boundary:

  • Plugins that ship MCP config require explicit confirmation before install — this can't be bypassed with --yes, because an MCP server can execute arbitrary commands as part of your sessions (see Plugins).
  • Configure servers you don't fully trust with ask rules so each call is reviewed before it runs.

Next steps ​

  • Configuration — the .nexrall/ directory where mcp.json lives.
  • Plugins — how MCP config is bundled and merged from installed plugins.
  • Agent SDK — McpManager in the npm package, for connecting MCP servers programmatically.

Built by Maxrall, Inc.