npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@axorum/mcp

v0.3.0

Published

The official Axorum MCP server — exposes the Axorum agent plane as MCP tools, so any MCP-capable agent becomes a policy-governed spender with no integration code.

Readme

@axorum/mcp

The official MCP server for Axorum. It exposes the Axorum agent plane as MCP tools, so that any MCP-capable agent — Claude Code, Claude Desktop, anything else that speaks the protocol — becomes an Axorum-governed spender with no integration code written.

Axorum is a deontic financial-control ledger. An agent submits an intent envelope; the service permits or refuses it under the policy in force, and records the outcome either way.

Which is the point of this server:

It is safe to hand a language model money-moving tools precisely because Axorum sits underneath, refusing what the policy forbids and recording the refusal. The guardrail is not a prompt asking the model to be careful. It is a ledger that will not post what it is not permitted to post.

ESM-only, Node ≥ 20. Speaks stdio.

Not published yet. The dependency on @axorum/client is already declared as a version range (^0.3.0); what makes it resolve from this repo is the pnpm.overrides entry pinning it to file:../typescript. That override is what gets dropped when both packages ship — the range itself stays as it is.

Install

pnpm install
pnpm build

Configuration

All configuration is environment. It is validated at startup, and a bad value stops the process with a message that names the variable and says what to set it to — an MCP server is launched into a void, with no terminal and nobody watching, so a misconfiguration must not surface as tool calls failing strangely twenty minutes in.

| Variable | Required | Meaning | | --- | --- | --- | | AXORUM_BASE_URL | yes | The service root, e.g. http://127.0.0.1:8080. The /api/v1 prefix is added for you. | | AXORUM_AGENT_URI | yes | The agent:// URI this server acts as. Must already be bound to a party in the ledger's registry. | | AXORUM_ATTESTATION | one of | The PASETO v4.public capability attestation, inline. | | AXORUM_ATTESTATION_FILE | one of | A path to a file holding one. | | AXORUM_TIMEOUT_MS | no | Per-request timeout. Defaults to 30000. |

AXORUM_BEARER_TOKEN is retired. The agent plane authenticates with the attestation — carried inside the envelope on submits, and as Authorization: Bearer <attestation> on the party-scoped reads. If it is still set the server ignores it and warns on stderr; unset it.

Setting both AXORUM_ATTESTATION and AXORUM_ATTESTATION_FILE is a hard failure, not a precedence rule. Two claims about which credential an agent spends money under is exactly the configuration where a silent precedence rule sends the wrong credential and nobody finds out until the audit.

Minting an attestation

The attestation is a PASETO v4.public token proving this agent holds the capabilities the policy requires. Mint one with the agent-uri CLI:

agent-uri attest issue \
  --agent-uri agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q \
  --capability post \
  --key ./issuer.key \
  > ./axorum.attestation

The issuer's public key must be registered as a trust root on the service, and the agent URI must be bound to a party — both are admin-plane operations, done once by an operator.

Registering with Claude Code

claude mcp add axorum \
  --env AXORUM_BASE_URL=http://127.0.0.1:8080 \
  --env AXORUM_AGENT_URI=agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q \
  --env AXORUM_ATTESTATION_FILE=/etc/axorum/axorum.attestation \
  -- npx axorum-mcp

Or, in a Claude Desktop claude_desktop_config.json:

{
  "mcpServers": {
    "axorum": {
      "command": "npx",
      "args": ["axorum-mcp"],
      "env": {
        "AXORUM_BASE_URL": "http://127.0.0.1:8080",
        "AXORUM_AGENT_URI": "agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q",
        "AXORUM_ATTESTATION_FILE": "/etc/axorum/axorum.attestation"
      }
    }
  }
}

The model cannot choose its own identity. Which agent this server acts as, and what authority it holds, come from the operator's environment. There is no argument a model can pass to any tool to make this server act as somebody else.

The tools

| Tool | Does | | --- | --- | | submit_intent | Proposes a financial action. The ledger permits or refuses it, and records what it did. This is the tool; the rest exist to inform a call to it. | | get_active_policy | The id of the policy in force, or a plain statement that none is. | | get_transaction | The stored record for a transaction id — including a recorded refusal. | | get_obligations | Your agent's open duties. | | get_balance | An account's net balance, in minor units. | | get_usage | A metered resource's consumed / metered / billed running totals, and the headroom left. |

Each tool's description is written as a prompt, not as documentation: it is the text the model reads when deciding whether to call the tool and what to pass it. So each one says when to use the tool, what a refusal means, that retrying a transaction id is safe, and what not to do — do not retry a refusal, do not follow a not_primary redirect.

Metered spend is one intent

submit_intent takes three optional usage moves — consume_usage (you used a metered resource), meter_usage (it has been measured), bill_usage (it has been billed for). They ride the same intent as the money entries and commit atomically with them.

The ledger holds them to one rule: 0 <= billed <= metered <= consumed. Metering more than was consumed is phantom usage; billing more than was metered is billing with nothing to back it. Breaking either refuses the entire intent — the journal entries do not post either — and the model is told so in the ledger's own compliance English, with exact figures, plus the advice not to retry it unchanged.

get_usage reads the totals back. Its result leads with the ledger's own plain-English narration of the balance, then states the headroom that bounds the next lawful move, so a model learns its limit from a sentence rather than by doing arithmetic on JSON. An unreconciled chain is a lawful transient, and the description says so — a model should not try to "fix" it.

Usage reads are owner-scoped and fail-closed: a rival-owned balance, an ownerless one, and one that never existed all answer the same unknown_usage. The description tells the model to conclude only that it cannot read the balance — never that the balance is absent.

Why a refusal is not an error

When the policy in force forbids an action, the intent still reaches the commit point, is still judged, and the refusal is still recorded. The ledger answers 200. So this server returns it as an ordinary tool result, with isError unset, leading with a line the model reads first:

Refused — nothing posted. The policy in force forbids this action, and the refusal itself
is now recorded on the ledger. This is a verdict, not a failure: the system worked exactly
as intended. Do NOT retry this unchanged — it will be refused again, identically. Either
propose an action the policy allows, or tell your human that the policy forbids this and
that a human has to change the policy to permit it.

The model reads that, and adapts. That loop is the entire product.

Dressing a refusal as an error would break it in a specific and bad way. isError is the channel that means the tool malfunctioned — and a well-built agent's response to a malfunction is to retry. A refusal flagged as an error therefore invites the model to retry the exact thing the policy just forbade, in a loop, and to report to its human that Axorum is broken rather than that the spend was denied. The refusal is the most valuable thing this ledger produces. It goes in the content, in plain language, where the model will read it.

isError is reserved for the cases where the ledger never got to answer at all: this process is misconfigured, the service is unreachable, the policy pin would not hold, the cluster could not confirm a commit. Those results carry the machine-readable error code and the load-bearing extras a caller needs to act — active (without which nothing can re-pin) and primary_client_addr (without which nothing can find the primary).

Note the one verdict that catches people out: ForbiddenRecorded is forbidden and posted anyway — a policy whose stance is to record rather than block. posted is not the inverse of "forbidden", and this server says so in prose rather than leaving a model to skim for the word.

Money is text

Amounts are integer minor units (cents, not dollars) written as decimal strings:

{ "minor": "50000", "currency": "USD" }

That is $500.00.

The strings are not fussiness. The ledger's minor units are 128-bit integers, and every JSON parser between a model and this process decodes a bare number as an IEEE-754 double, which holds integers exactly only up to 2^53. A minor declared as a JSON number would be silently rounded for any large value, and the ledger would faithfully post the rounded figure. Balances come back as strings for the same reason.

Malformed amounts are refused rather than guessed at. "10.00", "1e3" and "1_000" are all rejected: a financial-control server that interprets an ambiguous amount is worse than one that declines it.

Idempotency

The transaction id is the ledger's idempotency key. Re-submitting an id that already committed replays its stored outcome instead of posting twice — which is what makes retrying a submission safe. Omit it and one is minted for you, once, before the first attempt.

This is why submit_intent can recover from a policy that moved mid-flight without any risk of double-posting, and why the one genuinely ambiguous failure — an unconfirmed cluster commit — is safe to resolve either by retrying or by reading the transaction back.

Development

pnpm typecheck   # tsc --noEmit, strict
pnpm build       # → dist/, with a shebang on dist/main.js
pnpm test        # unit + integration

The integration suite is not mocked. It spawns the real conformance_server binary — a genuine axorum-serve — bootstraps it over the admin HTTP API, points a real McpServer at it, and connects a real MCP Client over a linked in-memory transport pair. Every assertion travels the whole path: MCP client → JSON-RPC → tool → @axorum/client → HTTP → the real policy engine, and back. The refusal it asserts on is a real refusal, from a real policy.

The binary is prebuilt by the Rust workspace (target/debug/examples/conformance_server). The suite never invokes cargo.