What QuoxMCP is #
QuoxMCP (package @quox/mcp, currently v1.2.0) is a Model Context Protocol server that gives any MCP-speaking Claude surface, Claude Desktop, Claude Code or an app built on the Agent SDK, native access to a QuoxCORE collector’s tools, resources and prompts.
It is deliberately thin: the README states it plainly, “all tool execution, resource serving, and prompt logic calls back to the QuoxCORE collector API. No tool logic lives here”, and reading the source bears that out.
QuoxMCP converts, registers and proxies. It does not decide what an agent is allowed to do, does not execute SSH commands or database queries itself, and does not write the audit trail.
Three properties carry the design:
- Thin adapter. Every capability QuoxMCP exposes is fetched from the collector at process startup and forwarded back to it on every call. Nothing is hardcoded.
- RBAC by delegation. QuoxMCP does not enforce access control. It registers whichever tools the collector’s
/api/v1/tools/listendpoint returns for the givenagent_id, and trusts the collector to have already filtered that list. - Fail loud, not silent. A missing service key, org id or user id, or a malformed collector URL, stops the process at startup with a non-zero exit rather than running in a degraded or unauthenticated state.
QuoxMCP ships as a Node package (engines: Node ≥20), a standalone .mcpbbundle for Claude Desktop, and a tarball for remote fleet deployment. Licence is BUSL-1.1.
Architecture and the bridge flow Stable #
Claude spawns QuoxMCP as a STDIO subprocess (via --mcp-config on the CLI, the Desktop install dialog, or an mcpServers entry in an Agent SDK app). On startup, QuoxMCP calls three collector endpoints in parallel, authenticating each request with an X-Service-Key header:
| Endpoint | Called by | Purpose |
|---|---|---|
GET /api/v1/tools/list?agent_id=… | CollectorClient.listTools | Tool definitions in Anthropic JSON Schema format, scoped to the calling agent |
GET /api/v1/resources/list | CollectorClient.listResources | Read-only resource definitions |
GET /api/v1/prompts/list | CollectorClient.listPrompts | Operational prompt templates |
POST /api/v1/tools/execute | CollectorClient.executeTool | Runs a tool call: tool name, input, agent id, session id, org id, user id, auth token |
Each tool’s JSON Schema is converted to a Zod shape (tool-adapter.js) and registered with the MCP SDK’s server.tool(). Resources are registered with server.resource(); a resource marked live in the collector’s response re-fetches its content on read, cached for 30 seconds, with the cache capped at 100 entries and evicted oldest-first. Prompts are registered withserver.prompt(), with argument values interpolated into a small mustache-style template language.
When Claude calls a tool, QuoxMCP posts to /api/v1/tools/execute and returns whatever the collector replies with as MCP content. The collector is where SSH sessions run, API calls go out, memory operations happen, RBAC is enforced and the AEE audit trail is written. QuoxMCP’s own logging (to stderr, since stdout is reserved for the MCP protocol) records only tool name, agent, and timing, not the result payload.
All of this runs behind a CollectorClient with a 30 second per-request timeout and up to two retries with exponential backoff (capped at 8 seconds between attempts), covered by 18 client tests including simulated HTTP and network failures.
The initialize handshake #
QuoxMCP is built on @modelcontextprotocol/sdk (McpServer +StdioServerTransport). Like any conformant MCP server, it will not accept atools/call, resources/read or prompts/get request until the client has completed the protocol’s initialize handshake over the STDIO connection.
This is standard MCP behaviour supplied by the SDK, not bespoke code inside QuoxMCP, and every one of the three supported clients, Claude Desktop, Claude Code and the Agent SDK, performs it automatically when it spawns the server.
There is nothing for an operator to configure here; it is mentioned because a hand-rolled MCP client or a debugging script that skips the handshake and calls a tool directly will be rejected.
Install in Claude Desktop Beta #
Download quoxmcp.mcpb from the latest GitHub release and double-click it. Claude Desktop opens an install dialog built from manifest.json’suser_config block, which declares five fields, not the two the README’s older quick-start table describes:
| Field | Required | What it is |
|---|---|---|
collector_url | Yes | Base URL of your QuoxCORE collector, e.g. http://10.20.0.126:9848 |
service_key | Yes | INTERNAL_SERVICE_KEY from your QuoxCORE deployment’s .env or dashboard settings |
agent_id | No, defaults to quox | quox gets full access; specialist agents get subsets |
org_id | Yes | Organisation id, for audit trail attribution |
user_id | Yes | User id, for audit trail attribution |
Click Install, restart Claude Desktop, and quox appears in the tools menu. In Claude Desktop, the 🔌 icon shows the connected tool count; a prompt such as “run a fleet health check” should invoke the fleet-healthoperational prompt.
WarningThe .mcpb bundle is unsigned. macOS shows an “unverified developer” warning on first open; right-click → Open bypasses it. The bundle is built by hand with the mcpb CLI and attached to a GitHub release manually, there is no signing or CI publish step yet.

manifest.json’suser_config block: collector_url, service_key,agent_id, org_id and user_id.Manual install (Claude Code, Agent SDK) Stable #
For fleet hosts, headless installs, or running from source (Node ≥20 required):
{
"mcpServers": {
"quox-tools": {
"command": "node",
"args": ["/home/control/quoxmcp/server.js"],
"env": {
"QUOX_AGENT_ID": "quox",
"QUOX_COLLECTOR_URL": "http://127.0.0.1:9848",
"QUOX_SERVICE_KEY": "your-service-key",
"QUOX_ORG_ID": "your-org-id",
"QUOX_USER_ID": "your-user-id"
}
}
}
}Run with claude --mcp-config ~/.claude/mcp-config.json, or drop the samemcpServers block into an Agent SDK app’s MCP configuration.
Known gapThe README’s manual-install example config omits QUOX_ORG_ID and QUOX_USER_ID. Since server startup, verified in server.js, exits with a fatal error when either is missing, that example will not boot as written. The config above includes both; the README needs the same fix.
Verify with a five-second standalone run:
[QuoxMCP] Starting — agent=quox, collector=http://127.0.0.1:9848
[QuoxMCP] Fetched N tools for agent quox
[QuoxMCP] Registered N tools, N resources, N prompts for agent quox
[QuoxMCP] Connected — serving N tools, N resources, N prompts via STDIOThe counts are whatever the collector returns for that agent id at that moment. They are not fixed in QuoxMCP.
RBAC-gated tools #
QuoxMCP does not define tools; it registers whatever/api/v1/tools/list?agent_id={id} returns. RBAC lives entirely in the collector. The figure is 168 governed tools, resolved from this site’s facts module (claims.tools), which pins it to the collector’s agentTools.js tool definitions with a stated definition and an as-of date.
It counts distinct tool definitions an agent can be granted, not tool invocations and not integrations. The orchestrator agent (quox) sees the full set; specialist agents get subsets. This page could not independently re-query a live collector (the local instance returns authentication_required without a service key), so the figure is a cross-repo pin re-verified by hand, not re-counted in the browser.
| Category | Example tools | Documented agents |
|---|---|---|
| Infrastructure | ssh_exec, fleet_status, system_admin | QUOX, CIPHER, DAEDALUS, WARDEN |
| Containers | docker_status, docker_extended | QUOX, DAEDALUS |
| Network | network_check | QUOX, CIPHER, WARDEN |
| Virtualisation | proxmox_vms, proxmox_cluster_status, proxmox_vm_action | QUOX, HYPERION |
| Security | security_audit, ssl_certificates | QUOX, WARDEN |
| Monitoring | metrics_query, alerts_manage | QUOX, METRICS |
| Memory | memory_save, memory_search, entity_note | All agents |
| Orchestration | delegate_to_agent | QUOX only |
| Plugin tools | QuoxFlow workflow triggers, custom integrations | Per plugin RBAC |
Before a tool reaches the MCP SDK, QuoxMCP applies one local check regardless of what the collector sent: the tool name must match ^[a-zA-Z0-9_.-]{1,128}$. Anything else, including a name shaped like a path traversal, is skipped and logged rather than registered. This is a defence-in-depth check on QuoxMCP’s own registration surface, not a substitute for the collector’s RBAC decision.

MCP resources Stable #
Resources give Claude read-only infrastructure context without a tool call. The README documents five, matching the count in a live standalone run captured in the same document:
| Resource | Description |
|---|---|
| System Identity | Server name, version, agent id, uptime |
| Fleet Topology | Managed hosts, roles, network layout |
| Agent Capabilities | Tools and permissions available per agent |
| Service Status | Health of all connected services |
| Configuration | Current MCP server settings and environment |
A resource flagged live by the collector is re-fetched on read and cached for 30 seconds; the cache is capped at 100 entries with oldest-first eviction, so a long-running server cannot grow it unbounded. A resource URI must use thequox:, https: or http: scheme, checked before registration; file: and javascript: URIs are rejected and logged. Registration and caching are covered by 13 resource-adapter tests.
MCP prompts Stable #
Prompts are pre-built operational workflows with structured arguments. The README documents four:
| Prompt | Description |
|---|---|
| Incident Triage | Structured diagnostic workflow for investigating alerts |
| Security Sweep | Multi-pass security audit with fix suggestions |
| Deploy Checklist | Pre/post deployment verification steps |
| Fleet Review | Comprehensive infrastructure health check |
Templates use a small mustache subset: {{var}}, conditional blocks{{#var}}…{{/var}} and {{^var}}…{{/var}}, and a default form {{var|default}}. Argument values are escaped before interpolation, so a user passing a value shaped like {{admin}} cannot expand another placeholder or inject a conditional block. This is covered by 23 prompt-adapter tests plus five dedicated template injection tests in the security suite.
Security hardening Stable #
A single validate.js module centralises the patterns and sanitisers used across the adapter layer:
SAFE_ID /^[a-zA-Z0-9_-]{1,64}$/ # agent id, session id
SAFE_TOOL_NAME /^[a-zA-Z0-9_.-]{1,128}$/ # tool names
MAX_INPUT_SIZE 1MB # tool input cap
ALLOWED_URI_SCHEMES quox:, https:, http: # resource URIs- Startup fail-loud: no service key, no org id or no user id exits the process with an error before it connects. An invalid agent or session id, or a collector URL that is not a valid
http/httpsURL, does the same. - Input size limits: tool input over 1MB is rejected before it reaches the collector, including a circular-reference-safe size check.
- Error sanitisation: internal IP addresses and collector URLs with port numbers are stripped from error text before it can reach the model or the user, tested against both patterns explicitly.
- Template injection prevention: covered above under MCP prompts.
All of this is exercised by a 40-test security.test.js file: service key enforcement on every request, id and URL validation, tool name validation including skip-on registration, response shape validation (a collector reply missing itstools, resources or prompts array is rejected rather than trusted), URI scheme enforcement, input size limits, and error sanitisation.
Stable, beta and planned #
No standalone status and limits document exists for QuoxMCP in the dashboard docs at the time of writing; this section is compiled directly from the quoxmcp repository (README, manifest, source and test suite) rather than mirroring a separate audit document.
Stable Verified and pinned by regression tests
- The MCP protocol adapter core: STDIO transport, JSON Schema→Zod tool registration, resource registration with TTL caching, prompt registration with template interpolation
- The collector HTTP client: service-key auth header, retries with exponential backoff, request timeout
- Startup validation that fails loud on a missing service key, org id, user id, or a malformed agent id, session id or collector URL
- The security-hardening layer: id and tool-name allowlists, resource URI scheme allowlist, 1MB input cap, template-injection escaping, error sanitisation
- Manual install and run via
node server.jswith an MCP config, once the config includes the org and user id server.js now requires
Beta Working, not yet independently re-verified in this review
- The Claude Desktop
.mcpbone-click install: functional per the documented install flow, but the bundle is unsigned and hand-built - Remote fleet deployment (
deploy/bundle.sh,quoxagent/deploy/deploy-to-host.sh --check-mcp): real code, and a built bundle and tarball exist on disk, but no live remote install was exercised for this page - Live resource re-fetching against a real collector: covered by unit tests with a mocked client, not observed against a running collector for this page (the local collector requires authentication this review did not have)
Planned Designed or reserved, nothing shipped
- No explicit unbuilt roadmap item for QuoxMCP was found in the repository or its docs. Signing the
.mcpbbundle is an open item mentioned in the README’s own warning, not a scheduled one
Known gap Documented limitations, not softened
- The README’s manual-install
mcp-config.jsonexample omitsQUOX_ORG_IDandQUOX_USER_ID, whichserver.jsrequires at boot; following it literally will not start the server - The
.mcpbbundle is unsigned; macOS shows an unverified-developer warning on first open - The shipped
manifest.jsonand README still carry lower tool, resource and prompt figures than the collector actually registers. Every page on this site now reads 168 tools, 8 resources and 7 prompts from a single facts module, so the site no longer disagrees with itself; the bundled README is the remaining stale surface and is fixed in the QuoxMCP repository, not here - QuoxMCP cannot verify RBAC itself; a collector that returns an over-broad tool list for an agent id would have that list registered and offered to Claude as-is
Test coverage #
157 tests across 7 files, run locally for this page with npx vitest run, all passing:
| File | Tests | Covers |
|---|---|---|
security.test.js | 40 | Service key enforcement, id/tool-name/URL validation, template injection, resource URI schemes, input size limits, error sanitisation |
validate.test.js | 33 | Unit tests for every function in lib/validate.js |
adapter.test.js | 23 | JSON Schema→Zod conversion and tool registration onto the MCP server |
prompt-adapter.test.js | 23 | Prompt registration and mustache-style template interpolation |
client.test.js | 18 | CollectorClient HTTP behaviour, auth headers, retries and backoff, against mocked HTTP |
resource-adapter.test.js | 13 | Resource registration, live re-fetch, TTL cache and eviction |
server.test.js | 7 | Full integration: registerTools against a real McpServer instance, server metadata |
Stated plainly, what this corpus does not verify: a real collector round trip (the collector client is exercised against mocked HTTP, not a running collector), an end-to-end install through Claude Desktop or Claude Code, and a live remote fleet deployment. Those are the items carrying the Beta tag above.
Operating notes #
The environment variables that matter operationally:
QUOX_COLLECTOR_URL=http://127.0.0.1:9848 # default; must be valid http(s)
QUOX_SERVICE_KEY= # required (or INTERNAL_SERVICE_KEY); boot fails without one
QUOX_ORG_ID= # required; boot fails without it
QUOX_USER_ID= # required; boot fails without it
QUOX_AGENT_ID=quox # default; RBAC identity, alnum/dash/underscore ≤64 chars
QUOX_SESSION_ID= # optional; auto-generated UUID if unset| Setting | Default | Effect |
|---|---|---|
QUOX_SERVICE_KEY | none | Sent as X-Service-Key on every collector request. Falls back to INTERNAL_SERVICE_KEY if unset. Missing both is a fatal startup error |
QUOX_ORG_ID / QUOX_USER_ID | none | Required for audit trail attribution; both are fatal-if-missing as of the current server.js |
QUOX_AGENT_ID | quox | Determines which tools the collector returns. Must match SAFE_ID |
QUOX_COLLECTOR_URL | http://127.0.0.1:9848 | Warns (does not block) on plain HTTP to a non-private host |
NoteQuoxMCP logs exclusively to stderr; stdout carries only the MCP STDIO protocol. If a tool call, resource read or prompt invocation is failing silently in a client, the process log (~/Library/Logs/Claude/mcp-server-quoxmcp.log on macOS,%APPDATA%\Claude\Logs\ on Windows) is where to look first.