Docs for SCORPIOX CODE @ 13253cf

Native MCP 2.0 & OAuth 2.1 in SCORPIOX CODE: Architecture vs Other Harnesses

Remote Streamable HTTP tools, OAuth 2.1 PKCE authorization, and pure C architecture.

You want the agent to reach beyond the tools it ships with — your mail server, your DevKit, a hosted search API, your own scripts on a box in another rack. The Model Context Protocol (MCP) is how that happens in practice, and every agent harness you can name has some answer to it. Most of those answers look the same from the outside and are very different underneath: a Node subprocess tree, a Python virtual environment, a browser-bound login flow, a config file that quietly spawns processes you never inspect.

SCORPIOX CODE takes a different road. MCP is implemented natively, in the same language as the product — a JSON-RPC 2.0 client over both stdio and Streamable HTTP, a complete OAuth 2.1 PKCE authorization flow that runs in a terminal, and an MCP server mode that turns a folder of your own scripts into tools any MCP client can call. There is no runtime to install, no SDK to pin, no node_modules to audit, and nothing in the auth path that assumes you have a desktop.

Docs for SCORPIOX CODE @ 13253cf.

The whole idea in one line: SCORPIOX CODE speaks MCP 2.0 natively — local servers over stdio, remote servers over Streamable HTTP with a full OAuth 2.1 PKCE login (scorpiox-mcp-login) that works headless over SSH, token caching and refresh included — and it can serve your own scripts as MCP tools with scorpiox-server --mcp, all in pure C with zero runtime dependencies.


What "native MCP 2.0" means here

MCP is a JSON-RPC 2.0 protocol with two common transports. A stdio transport talks to a local server process over pipes. A Streamable HTTP transport sends each JSON-RPC request as an HTTP POST and reads the response back — plain JSON or an event stream — which is what makes remote, authenticated, multi-tenant tool servers possible at all.

SCORPIOX CODE implements both transports itself, plus the authorization layer the remote transport actually requires in the field:

PieceWhat it isWhat it talks to
scorpiox-mcpLocal MCP client over stdio (JSON-RPC 2.0, newline framing)Server processes you configure, spawned and managed per call
scorpiox-mcp-httpclientRemote MCP client over Streamable HTTPAny MCP 2.0 HTTP endpoint, with or without OAuth
scorpiox-mcp-loginOAuth 2.1 PKCE authorization clientAny MCP server that authenticates with OAuth
scorpiox-server --mcpMCP server mode (Streamable HTTP)Any MCP client, including other agents and the llama.cpp Web UI

Three things to hold onto before the details:


The four CLIs, in one table

CommandModeKey subcommands
scorpiox-mcpstdio clientdiscover, call, list, info, test
scorpiox-mcp-httpclientStreamable HTTP clientlist, call, info, test
scorpiox-mcp-loginOAuth 2.1 PKCE clientfull flow, --force, --refresh, --status, --token, --list
scorpiox-server --mcpStreamable HTTP server--name, -p, -r <repo-url>

Each one is a standalone static binary. That is not a marketing line; it is the reason they behave the same on a laptop, in a container, and on a headless server with nothing on it but SSH.


Local servers: stdio, configured once

Local MCP servers are defined in scorpiox-env.txt, which follows the same configuration cascade as everything else in SCORPIOX CODE — machine defaults, then your project override. A server definition is a name, a command, and its arguments:

MCP=1
MCP_SERVER=devkit:/root/devkit/DevKitMCP.Client;fs:npx:-y,@modelcontextprotocol/server-filesystem,/home/you

MCP=1 is the master switch. Then:

scorpiox-mcp list                     # servers as configured
scorpiox-mcp discover                 # every tool across every server
scorpiox-mcp info devkit send_email   # one tool's JSON schema
scorpiox-mcp test devkit              # start, handshake, tools/list, report
scorpiox-mcp call devkit send_email '{"to":"ops@example.com","body":"build 42 is green"}'

discover connects to each server in turn, sends initialize plus the notifications/initialized notification, calls tools/list, and prints the result per server. call opens a session, sends tools/call with your JSON as arguments, concatenates the returned text blocks, and propagates the server's isError flag as a non-zero exit code so shell scripts and the agent both see the failure.

Three properties worth knowing before you rely on this:

Two config formats, one behavior

Besides MCP_SERVER, two other forms are accepted, and they exist because the rest of the ecosystem does not agree on one format:

KeyFormatUse it when
MCP_SERVERname:command:arg1,arg2 (semicolon-separated for several servers)You want everything in scorpiox-env.txt
MCP_CONFIGStandard mcpServers JSON fileYou already maintain the JSON format other tools use
MCP_FILELine-based .mcp fileYou are running a named agent that ships its own servers

The .mcp format is the most interesting of the three, because it does per-server work that would otherwise need a second config file:

devkit=/root/devkit/DevKitMCP.Client APIKEY__sk-xyz|REGION__eu,MailingTool(send_email:list_recent);GitTool

Read that as: server devkit, binary path, two environment variables injected into the server's environment (APIKEY, REGION), and a tool filter — only send_email and list_recent from the MailingTool class, and everything from GitTool. Class names are matched case-insensitively against snake_case tool names, so a server that advertises MailingToolSendEmail still matches send_email.

How this lands in a session

When you start SCORPIOX CODE with MCP enabled, tools are discovered once at startup and injected into the tool list next to the built-ins. From the model's point of view there is no special category: an MCP tool is a tool, named mcp__<server>__<tool> — for example mcp__devkit__send_email. The agent calls it, the result comes back through the normal tool-result path, and the call is recorded in the session event stream like any other tool use.

You can manage these tools like any other:

/disable_tool mcp__devkit__send_email   # blocked for this session, still listed
/enable_tool  mcp__devkit__send_email   # back again

Named agent instances get this for free. When an agent directory carries a .mcp file, the agent writes MCP=1 and MCP_FILE=<path> into that instance's own project config — instance-scoped MCP without touching your global settings.


Remote servers: OAuth 2.1 with PKCE, designed for terminals

This is the part that most harnesses handle with a browser, a GUI, or a vendored SDK. SCORPIOX CODE does it with one command that runs anywhere a terminal exists.

scorpiox-mcp-login https://mcp.scorpiox.net

The flow follows the MCP authentication specification end to end, seven steps, printed as it goes:

  1. Probe the endpoint. A minimal initialize is POSTed; a 401 response with a WWW-Authenticate header carrying a resource_metadata URL is the expected, healthy answer. A 200 means the server needs no auth at all, and the command says so and stops.
  2. Read the protected-resource metadata. This names the authorization server(s) and the scopes that apply to this resource.
  3. Read the authorization-server metadata. Endpoints, scopes, whether dynamic client registration is offered, whether the none token auth method is supported, and whether S256 PKCE is advertised (you get a clear warning if it is not).
  4. Register a client dynamically. A POST /register with the product name, both loopback redirect URIs (127.0.0.1 and localhost), the authorization-code and refresh-token grant types, and token_endpoint_auth_method: none — a public client, no secret to leak. If the server does not offer registration, a default client identifier is used instead.
  5. Generate the PKCE challenge. 32 bytes from a cryptographically seeded random generator, base64url-encoded into a verifier, SHA-256 hashed into the S256 challenge, plus a random state value.
  6. Authorize. The URL is printed for you to open in a browser on any device. The client binds a local listener on http://127.0.0.1:19457/mcp/callback and waits for the redirect; the state parameter is verified on arrival. If the port cannot be bound — a common situation on a remote box with no forwarded loopback — the command says so and drops to paste mode: you paste either the code or the full callback URL, and the code is extracted for you. This fallback is what makes the flow work over SSH without any local browser.
  7. Exchange and store. The code plus the verifier are swapped for tokens at the token endpoint, and the result is saved.

The saved file lives at ~/.mcp/credentials/<sha256-of-url>.json and contains the URL, client id, access token, refresh token, granted scopes, token endpoint, revocation endpoint, and an absolute expiry time. The directory is created with 0700.

scorpiox-mcp-login https://mcp.scorpiox.net --status    # validity, remaining seconds, scopes, file path
scorpiox-mcp-login https://mcp.scorpiox.net --refresh   # new access token, keeps or rotates the refresh token
scorpiox-mcp-login https://mcp.scorpiox.net --token     # print the bearer token to stdout (scripts)
scorpiox-mcp-login --list                               # every saved credential on this machine
scorpiox-mcp-login https://mcp.scorpiox.net --force     # re-authenticate, replacing what is stored

Two behaviors make this genuinely low-maintenance rather than merely present:

Calling remote tools

scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp test
scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp list
scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp info geocode
scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp call geocode '{"address":"Auckland"}'
scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp call geocode address="Auckland"
scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp call websearch --file params.json
echo '{"query":"llama.cpp"}' | scorpiox-mcp-httpclient https://mcp.scorpiox.net/mcp call websearch

Each command performs the full handshake itself: initialize, the initialized notification, then your request, then exit. The test command is the fastest way to check a server — it reports the negotiated protocol version, the server's own name and version, the session id if one was issued, and the tool count. Requests accept plain JSON, key=value named arguments, a --file payload for large or nested inputs, or piped stdin. Responses arrive as plain JSON or as an event stream; both are parsed, so you do not have to know which one a given server speaks.

One built-in skill ships with the product and teaches the agent exactly this: prefer scorpiox-mcp for local stdio servers, scorpiox-mcp-login plus scorpiox-mcp-httpclient for remote ones, and named arguments over hand-built JSON. In practice this means you can ask the agent to "list the tools on that server and call the geocoder on Auckland" and it will reach for the right commands by itself.


Serving your own tools: scorpiox-server --mcp

The dual role is the part most harnesses simply do not have. SCORPIOX CODE consumes MCP servers, and it is one:

scorpiox-server --mcp -p 8888                 # serve scripts from the configured folder
scorpiox-server -r https://git.example.com/org/tools.git --mcp   # serve a git-deployed repo
scorpiox-server --mcp --name myserver         # custom serverInfo name

Every executable and .py script in the served folder becomes a tool; the filename is the tool name. A script that supports a --schema flag describes itself in two lines, and the server builds a real JSON schema from it:

#!/usr/bin/env python3
# deploy_report --schema prints:
#
# Build a deployment report for an environment.
# environment:string:staging or production:required
# since:string:ISO date, e.g. 2026-09-01:optional

At startup the server probes each script with a short timeout, registers what answers, prints the tool list, and then answers MCP traffic on POST /mcp:

This is what makes the llama.cpp Web UI integration a one-liner: point it at http://host:8888/mcp and it discovers your tools on its own.

Because this server also runs unattended in production, the knobs around it matter, and they are the same ones that govern its script-serving role:

SettingEffect
SERVER_IP_WHITELISTRestrict /mcp (and everything else) to listed IPs or CIDR ranges
SERVER_SCRIPT_TIMEOUTIdle timeout for a tool call's script, 300 s by default
SERVER_MAX_RESPONSE_MBCap on captured tool output, 200 MB by default
MCP_TOOL_EXCLUDEGlob patterns for scripts that must not become tools — helper-*,setup,*.bak

Be deliberate here: a tool call executes a script with your privileges, and the endpoint answers whatever client can reach the port. Put the whitelist on, put a TLS terminator in front if it crosses a network, and use MCP_TOOL_EXCLUDE so nothing auxiliary and dangerous gets advertised as a tool by accident.


Tool filtering: allow and deny, per server, with globs

Discovery is filtered at the source, so a denied tool never reaches the model's tool list at all — it cannot be called by accident because it does not exist as far as the session is concerned.

MCP_ALLOW=devkit:mailing_tool_*,git_smart_commit_*;fs:read_*
MCP_DENY=devkit:*shutdown*,*delete*;*:*admin*

The semantics are worth reading once, carefully:

The practical pattern: allow-list the useful surface, deny-list the destructive verbs, and do both per server so one noisy server cannot widen another's exposure. Combined with the per-tool .mcp filters above, you can express "this server, only these classes, only these methods" in one line.

There is also the session-level lever: /disable_tool accepts mcp__ names verbatim, so you can take a single tool out of a running session without editing config, then bring it back the same way.


What this costs you: the runtime question

This is the part where the comparison stops being about features and starts being about operations.

SCORPIOX CODETypical Node/Python harness
Language of the MCP clientPure C99, statically linkedJavaScript (Node/Bun) or Python
Third-party SDK for MCPNone — the protocol is implemented in the productOfficial MCP SDK (@modelcontextprotocol/sdk, mcp, rmcp) as a versioned dependency
Install surface for MCPThe binaries you already haveAn SDK plus its transitive dependency tree
Runtime to keep patchedNone beyond the binariesAn interpreter plus packages, updated per release
Cold start of a tool callImmediate — process start, connect, requestInterpreter startup before the first byte
Protocol upgradesRebuild the binaryBump the SDK, re-test the dependency matrix

Two concrete risks disappear with the second column. First, supply chain: every SDK release you pin is a set of transitive packages you inherit and must watch for advisories; a native implementation has no such list. Second, environment drift: a Python MCP stack means virtual environments, interpreter versions, and a package manager that must all agree on the same box — including the remote one you are SSH'd into at 2 a.m. A static binary sidesteps the entire class of problems.

The performance story is the same direction, stated carefully. A stdio tool call is a process start and a pipe round-trip; a Streamable HTTP call is one HTTP POST. Neither pays an interpreter's startup cost first. On long sessions with many tool calls, that difference compounds; on a cold start, it is the difference between the first tool running immediately and waiting for a runtime to boot.


How this compares to other harnesses

The short version: stdio-local MCP is table stakes — everyone in this table has it. The differentiators are the transport (who can reach remote servers natively), the authorization model (who can complete an OAuth 2.1 flow without a GUI or a heavyweight SDK), whether the harness can serve tools as well as consume them, and how precisely you can filter the tool surface. Here is the honest comparison.

HarnessProtocol supportAuth for remote serversRuntime / dependenciesServes MCP toolsHeadless / SSH friendlinessTool filtering
SCORPIOX CODEstdio client and Streamable HTTP client, MCP protocol version 2024-11-05; built-in MCP server mode over Streamable HTTPNative OAuth 2.1 PKCE client: RFC-style discovery, dynamic client registration, loopback callback or paste mode, cached tokens, automatic refresh on 401Pure C99, statically linked, no runtime, no MCP SDKYes — scorpiox-server --mcp, scripts become tools, schema via --schemaDesigned for it: paste-mode auth, no browser or desktop required, static binariesPer-server glob allow and deny lists, plus per-server class/method filters in .mcp, plus session-level /disable_tool
Claude Codestdio (.mcp.json command/args), plus sse, http, and ws server typesOAuth handled for sse/http types (browser prompt on first use, tokens managed, refresh automatic); token headers for the restNode.js CLI; MCP servers are typically npx child processesYes — claude mcp serve runs Claude Code itself as an MCP serverCLI login with a --no-browser stdin redirect exists for SSH, but the day-to-day surface is a GUI/IDE flowServer-level managed allow/deny policy lists (allowedMcpServers / deniedMcpServers); per-tool globs are not the model
Cursorstdio servers spawned by the editor; remote HTTP endpoints configurable in settingsManual: API keys and header values entered by hand in the editor UIElectron editor; MCP servers run as child processes of the editor's Node runtimeNo — an editor, not a tool serverWeak by construction: it is a desktop IDEServer-level enable/disable in settings UI
OpenCodestdio, Streamable HTTP, and SSE — via the official TypeScript MCP SDKOAuth via the same SDK: dynamic registration, PKCE, browser redirect to a local callback server (default port 19876)Bun/TypeScript, SDK plus its dependency treeIts serve mode is a control-plane HTTP API for the harness, not an MCP tools serverCLI commands exist (mcp add/auth/logout), but auth expects a browser on or near the machinePer-server enabled switch; no per-tool glob filtering
Hermes Agentstdio (with managed npx/uvx launchers) and Streamable HTTP/SSE, via the official Python MCP SDKOAuth 2.1 with PKCE through the SDK, browser plus loopback callback, tokens cached per serverPython, SDK mcp as a pinned extra, plus a large dependency treePartial — it can expose a curated subset of its own tools to another agent over stdioCLI-first and SSH-tolerant, with browser-open fallbacksPer-server include/exclude globs — the closest peer to SCORPIOX CODE's model
Goosestdio and Streamable HTTP, via the official Rust MCP SDKOAuth flow with browser open and loopback callback through the SDKRust core, but the shipped desktop app is Electron (Node)Server-side SDK capability exists in the ecosystem; the product's role is primarily a clientDesktop-firstServer-level enable/disable; per-extension filtering rather than glob rules

Read that table the way an operator would, not a marketer:

Where the others genuinely lead: Claude Code's OAuth automation for its sse/http types is polished and its managed policy lists are the strongest organization-level control here; Hermes's per-server include/exclude globs match SCORPIOX CODE's filtering model and it adds a curated install catalog on top; OpenCode's SDK-based transport matrix covers SSE explicitly. These are real capabilities. What they have in common is that all of them arrive as someone else's library, versioned and inherited, while the SCORPIOX CODE implementation is part of the product you are already shipping.


Gotchas worth knowing before you deploy