@axorum/mcp 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * The server's configuration, read from the environment and checked before anything runs.
3
+ *
4
+ * An MCP server is launched by an agent host — Claude Code, Claude Desktop — as a
5
+ * subprocess speaking JSON-RPC over stdio. Nobody is watching its stdout. So a
6
+ * misconfiguration must not surface as a tool call failing strangely twenty minutes in;
7
+ * it must stop the process at startup with a message that names the variable, says what
8
+ * was wrong with it, and says what to set it to. That is what this module is for.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import { readFileSync } from "node:fs";
13
+ import { endpointSet } from "@axorum/client";
14
+ /**
15
+ * The configuration was wrong, and the server will not start.
16
+ *
17
+ * Distinct from `@axorum/client`'s own `ConfigError`: this one is about *this process's
18
+ * environment*, and its message is written to be read by a human editing an MCP host's
19
+ * config file.
20
+ */
21
+ export class ConfigurationError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "ConfigurationError";
25
+ }
26
+ }
27
+ /** The prefix every PASETO v4.public token carries. */
28
+ const PASETO_V4_PUBLIC = "v4.public.";
29
+ /** The default reader: the real filesystem, as UTF-8. */
30
+ const readFileUtf8 = (path) => readFileSync(path, "utf8");
31
+ /**
32
+ * Reads and validates the configuration from an environment.
33
+ *
34
+ * A **pure function** of `(env, readFile)` — which is the reason every one of its failure
35
+ * messages can be held to account by a unit test without setting a single real environment
36
+ * variable or writing a single real file.
37
+ *
38
+ * @param env - The environment. `process.env` in production.
39
+ * @param readFile - How to read `AXORUM_ATTESTATION_FILE`. Defaults to the filesystem.
40
+ * @throws {ConfigurationError} With a message that names the variable and says what to do.
41
+ */
42
+ export function loadConfig(env = process.env, readFile = readFileUtf8) {
43
+ const base = baseUrl(env);
44
+ const config = {
45
+ baseUrl: base,
46
+ agentUri: agentUri(env),
47
+ attestation: attestation(env, readFile),
48
+ failoverUrls: failoverUrls(env, base),
49
+ };
50
+ // AXORUM_BEARER_TOKEN is retired: the agent plane now authenticates its reads with the
51
+ // attestation, not a transport bearer token. Warn to stderr rather than ignore a set-but-
52
+ // unused value silently — an operator who still has it set should be told it does nothing,
53
+ // not left believing a credential is in play that no longer is. (stderr, not stdout: stdout
54
+ // is the JSON-RPC channel.)
55
+ if (trimmed(env["AXORUM_BEARER_TOKEN"]) !== undefined) {
56
+ process.stderr.write("warning: AXORUM_BEARER_TOKEN is set but no longer used. The agent plane now " +
57
+ "authenticates with the attestation (AXORUM_ATTESTATION / AXORUM_ATTESTATION_FILE). " +
58
+ "Unset AXORUM_BEARER_TOKEN.\n");
59
+ }
60
+ const timeout = timeoutMs(env);
61
+ if (timeout !== undefined) {
62
+ config.timeoutMs = timeout;
63
+ }
64
+ return config;
65
+ }
66
+ /** The value of `name`, trimmed, or `undefined` if it is absent or blank. */
67
+ function trimmed(value) {
68
+ const text = value?.trim();
69
+ return text === undefined || text === "" ? undefined : text;
70
+ }
71
+ /** `AXORUM_BASE_URL` — required, and an absolute `http`/`https` URL. */
72
+ function baseUrl(env) {
73
+ const value = trimmed(env["AXORUM_BASE_URL"]);
74
+ if (value === undefined) {
75
+ throw new ConfigurationError("AXORUM_BASE_URL is not set. Set it to the root of your Axorum service, " +
76
+ "e.g. AXORUM_BASE_URL=http://127.0.0.1:8080 (the /api/v1 prefix is added for you).");
77
+ }
78
+ let parsed;
79
+ try {
80
+ parsed = new URL(value);
81
+ }
82
+ catch {
83
+ throw new ConfigurationError(`AXORUM_BASE_URL is not a URL: '${value}'. Set it to an absolute URL, ` +
84
+ "e.g. AXORUM_BASE_URL=http://127.0.0.1:8080");
85
+ }
86
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
87
+ throw new ConfigurationError(`AXORUM_BASE_URL must be an http or https URL, but '${value}' is '${parsed.protocol}'. ` +
88
+ "Set it to e.g. AXORUM_BASE_URL=http://127.0.0.1:8080");
89
+ }
90
+ return value;
91
+ }
92
+ /** `AXORUM_AGENT_URI` — required, and an `agent://` URI. */
93
+ function agentUri(env) {
94
+ const value = trimmed(env["AXORUM_AGENT_URI"]);
95
+ if (value === undefined) {
96
+ throw new ConfigurationError("AXORUM_AGENT_URI is not set. Set it to the agent:// URI this server acts as, " +
97
+ "e.g. AXORUM_AGENT_URI=agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q. " +
98
+ "It must already be bound to a party in the ledger's registry.");
99
+ }
100
+ if (!value.startsWith("agent://")) {
101
+ throw new ConfigurationError(`AXORUM_AGENT_URI must be an agent:// URI, but it is '${value}'. ` +
102
+ "Set it to e.g. AXORUM_AGENT_URI=agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q");
103
+ }
104
+ return value;
105
+ }
106
+ /**
107
+ * `AXORUM_ATTESTATION` or `AXORUM_ATTESTATION_FILE` — exactly one of them.
108
+ *
109
+ * Both being set is a hard failure rather than a precedence rule. Two sources of an
110
+ * agent's *authority to spend money* is precisely the configuration where a silent
111
+ * precedence rule sends the wrong credential and nobody finds out until the audit.
112
+ */
113
+ function attestation(env, readFile) {
114
+ const inline = trimmed(env["AXORUM_ATTESTATION"]);
115
+ const file = trimmed(env["AXORUM_ATTESTATION_FILE"]);
116
+ if (inline !== undefined && file !== undefined) {
117
+ throw new ConfigurationError("AXORUM_ATTESTATION and AXORUM_ATTESTATION_FILE are both set, and they are two " +
118
+ "different claims about which credential this agent acts under. Unset one of them.");
119
+ }
120
+ if (inline === undefined && file === undefined) {
121
+ throw new ConfigurationError("Neither AXORUM_ATTESTATION nor AXORUM_ATTESTATION_FILE is set, so this agent has " +
122
+ "no proof it may act. Set one of them to a PASETO v4.public capability attestation " +
123
+ "(a token beginning 'v4.public.'), or a path to a file holding one. Mint one with: " +
124
+ "agent-uri attest issue --agent-uri <uri> --capability agent/post --key <signing-key>");
125
+ }
126
+ if (inline !== undefined) {
127
+ return verifyPasetoShape(inline, "AXORUM_ATTESTATION");
128
+ }
129
+ // `file` is a string here: the two branches above eliminated every other case.
130
+ const path = file;
131
+ let contents;
132
+ try {
133
+ contents = readFile(path);
134
+ }
135
+ catch (error) {
136
+ throw new ConfigurationError(`AXORUM_ATTESTATION_FILE points at '${path}', which could not be read: ${describe(error)}. ` +
137
+ "Check the path and its permissions.");
138
+ }
139
+ const token = contents.trim();
140
+ if (token === "") {
141
+ throw new ConfigurationError(`AXORUM_ATTESTATION_FILE points at '${path}', which is empty. It must hold a PASETO ` +
142
+ "v4.public capability attestation (a token beginning 'v4.public.').");
143
+ }
144
+ return verifyPasetoShape(token, `AXORUM_ATTESTATION_FILE ('${path}')`);
145
+ }
146
+ /**
147
+ * Checks the token *looks* like a PASETO v4.public — the shape, not the signature.
148
+ *
149
+ * The ledger is the authority on whether an attestation is valid, and it will say so with
150
+ * a `403`. This is here to catch the ordinary mistake — a path pasted into the inline
151
+ * variable, a base64 blob of the wrong thing — at startup, where the message can be
152
+ * actionable, rather than as an authority failure on the agent's first attempt to spend.
153
+ */
154
+ function verifyPasetoShape(token, source) {
155
+ if (!token.startsWith(PASETO_V4_PUBLIC)) {
156
+ throw new ConfigurationError(`${source} does not hold a PASETO v4.public token: it must begin '${PASETO_V4_PUBLIC}', ` +
157
+ `but it begins '${token.slice(0, 12)}…'. Mint one with: ` +
158
+ "agent-uri attest issue --agent-uri <uri> --capability agent/post --key <signing-key>");
159
+ }
160
+ return token;
161
+ }
162
+ /** `AXORUM_TIMEOUT_MS` — optional, and a positive integer number of milliseconds. */
163
+ function timeoutMs(env) {
164
+ const value = trimmed(env["AXORUM_TIMEOUT_MS"]);
165
+ if (value === undefined) {
166
+ return undefined;
167
+ }
168
+ // `Number` rather than `parseInt`: `parseInt("30s")` is 30, which is exactly the typo a
169
+ // config check exists to catch.
170
+ const parsed = Number(value);
171
+ if (!Number.isInteger(parsed) || parsed <= 0) {
172
+ throw new ConfigurationError(`AXORUM_TIMEOUT_MS must be a positive whole number of milliseconds, but it is '${value}'. ` +
173
+ "Set it to e.g. AXORUM_TIMEOUT_MS=30000, or unset it to use the default of 30000.");
174
+ }
175
+ return parsed;
176
+ }
177
+ /**
178
+ * `AXORUM_FAILOVER_URLS`, optional: every other replica's service root, comma-separated,
179
+ * e.g. `https://axorum-1:8443,https://axorum-2:8443`. Each must be a bare `http(s)` origin
180
+ * on `AXORUM_BASE_URL`'s scheme, and no two may name the same origin, by the same rules
181
+ * `@axorum/client` holds its `failoverUrls` to. Unset or blank, the list is empty.
182
+ */
183
+ function failoverUrls(env, base) {
184
+ const value = trimmed(env["AXORUM_FAILOVER_URLS"]);
185
+ if (value === undefined) {
186
+ return [];
187
+ }
188
+ const urls = value.split(",").map((url) => url.trim());
189
+ const blank = urls.indexOf("");
190
+ if (blank !== -1) {
191
+ throw new ConfigurationError(`AXORUM_FAILOVER_URLS has an empty entry at position ${blank + 1}: '${value}'. List ` +
192
+ "every other replica's service root, comma-separated, e.g. " +
193
+ "AXORUM_FAILOVER_URLS=https://axorum-1:8443,https://axorum-2:8443");
194
+ }
195
+ const set = endpointSet(base, urls);
196
+ if ("refusal" in set) {
197
+ throw new ConfigurationError(`AXORUM_FAILOVER_URLS is refused: ${refusalText(set.refusal, urls)}`);
198
+ }
199
+ return urls;
200
+ }
201
+ /**
202
+ * Why `@axorum/client` refuses a failover list, in this server's terms. `index` counts the
203
+ * base URL as 0, so failover URL `index` is `urls[index - 1]`. Pure.
204
+ */
205
+ function refusalText(refusal, urls) {
206
+ switch (refusal.kind) {
207
+ case "not_an_origin":
208
+ return (`'${refusal.url}' is not a bare http(s) origin: ${refusal.reason}. Give each ` +
209
+ "replica's scheme, host and port only, e.g. https://axorum-1:8443");
210
+ case "mixed_scheme":
211
+ return (`'${urls[refusal.index - 1] ?? ""}' uses ${refusal.found}, but AXORUM_BASE_URL uses ` +
212
+ `${refusal.expected}. Every replica must be reached on one scheme.`);
213
+ case "duplicate_origin": {
214
+ const first = refusal.first === 0 ? "AXORUM_BASE_URL" : `'${urls[refusal.first - 1] ?? ""}'`;
215
+ return (`'${urls[refusal.duplicate - 1] ?? ""}' names the same replica (${refusal.origin}) as ` +
216
+ `${first}. List each replica once.`);
217
+ }
218
+ }
219
+ }
220
+ /** The best available prose for a thrown value, without assuming it is an `Error`. */
221
+ function describe(error) {
222
+ if (error instanceof Error) {
223
+ return error.message === "" ? error.name : error.message;
224
+ }
225
+ return String(error);
226
+ }
227
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The border crossing: validated tool input becomes a wire `IntentDraft`.
3
+ *
4
+ * Everything in here is a **pure function**. The tool callbacks in `server.ts` do I/O and
5
+ * cannot be reasoned about without a service; this module holds the part that *can* be —
6
+ * the lifting of decimal strings into `bigint`s, which is the step where a
7
+ * financial-control server most needs to be right, and the step a test can pin down
8
+ * exactly.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import type { ActionTerm, Amount, Entry, IntentDraft, UsageMove, Value } from "@axorum/client";
13
+ import type { ServerConfig } from "./config.js";
14
+ import type { AmountInput, EntryInput, SubmitIntentInput, UsageMoveInput, ValueInput } from "./schemas.js";
15
+ /**
16
+ * Lifts a validated amount's `minor` from a decimal string into a `bigint`.
17
+ *
18
+ * `BigInt()` cannot throw here: the schema's `INTEGER` pattern has already established
19
+ * that the string is a bare integer literal, which is exactly what `BigInt` parses.
20
+ */
21
+ export declare function toAmount(input: AmountInput): Amount;
22
+ /** Lifts a validated journal leg. */
23
+ export declare function toEntry(input: EntryInput): Entry;
24
+ /**
25
+ * Lifts a validated typed value into its wire form.
26
+ *
27
+ * The variants that carry integers — `Int`, `Tick` — and the one that carries money —
28
+ * `Amount` — are lifted; the rest are already exactly what the wire wants. The `in` checks
29
+ * discriminate the externally tagged union by its single key.
30
+ */
31
+ export declare function toValue(input: ValueInput): Value;
32
+ /** Lifts a validated usage move, its signed delta included. */
33
+ export declare function toUsageMove(input: UsageMoveInput): UsageMove;
34
+ /** Lifts a validated action term and its bindings. */
35
+ export declare function toActionTerm(input: SubmitIntentInput["action"]): ActionTerm;
36
+ /**
37
+ * Builds the draft the client submits, from what the model asked for and how this server
38
+ * is configured.
39
+ *
40
+ * The split is the point: **the model supplies the intent, the configuration supplies the
41
+ * identity.** A model cannot choose which agent it is or what attestation it holds — those
42
+ * come from the environment an operator set, and are not in the tool's input schema at
43
+ * all. There is no argument a model can pass to make this server act as somebody else.
44
+ *
45
+ * The policy pin is absent for the same reason: it is a claim about the *service's* state,
46
+ * not the agent's, and `submitPinned` stamps it from the service's own answer.
47
+ */
48
+ export declare function toDraft(input: SubmitIntentInput, config: ServerConfig): IntentDraft;
49
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE/F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,UAAU,EACX,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEnD;AAED,qCAAqC;AACrC,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAWhD;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAE5D;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,UAAU,CAM3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CAoCnF"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The border crossing: validated tool input becomes a wire `IntentDraft`.
3
+ *
4
+ * Everything in here is a **pure function**. The tool callbacks in `server.ts` do I/O and
5
+ * cannot be reasoned about without a service; this module holds the part that *can* be —
6
+ * the lifting of decimal strings into `bigint`s, which is the step where a
7
+ * financial-control server most needs to be right, and the step a test can pin down
8
+ * exactly.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * Lifts a validated amount's `minor` from a decimal string into a `bigint`.
14
+ *
15
+ * `BigInt()` cannot throw here: the schema's `INTEGER` pattern has already established
16
+ * that the string is a bare integer literal, which is exactly what `BigInt` parses.
17
+ */
18
+ export function toAmount(input) {
19
+ return { minor: BigInt(input.minor), currency: input.currency };
20
+ }
21
+ /** Lifts a validated journal leg. */
22
+ export function toEntry(input) {
23
+ return { account: input.account, side: input.side, amount: toAmount(input.amount) };
24
+ }
25
+ /**
26
+ * Lifts a validated typed value into its wire form.
27
+ *
28
+ * The variants that carry integers — `Int`, `Tick` — and the one that carries money —
29
+ * `Amount` — are lifted; the rest are already exactly what the wire wants. The `in` checks
30
+ * discriminate the externally tagged union by its single key.
31
+ */
32
+ export function toValue(input) {
33
+ if ("Int" in input) {
34
+ return { Int: BigInt(input.Int) };
35
+ }
36
+ if ("Tick" in input) {
37
+ return { Tick: BigInt(input.Tick) };
38
+ }
39
+ if ("Amount" in input) {
40
+ return { Amount: toAmount(input.Amount) };
41
+ }
42
+ return input;
43
+ }
44
+ /** Lifts a validated usage move, its signed delta included. */
45
+ export function toUsageMove(input) {
46
+ return { usage: input.usage, amount: toAmount(input.amount) };
47
+ }
48
+ /** Lifts a validated action term and its bindings. */
49
+ export function toActionTerm(input) {
50
+ const bindings = {};
51
+ for (const [name, value] of Object.entries(input.bindings ?? {})) {
52
+ bindings[name] = toValue(value);
53
+ }
54
+ return { action: input.action, bindings };
55
+ }
56
+ /**
57
+ * Builds the draft the client submits, from what the model asked for and how this server
58
+ * is configured.
59
+ *
60
+ * The split is the point: **the model supplies the intent, the configuration supplies the
61
+ * identity.** A model cannot choose which agent it is or what attestation it holds — those
62
+ * come from the environment an operator set, and are not in the tool's input schema at
63
+ * all. There is no argument a model can pass to make this server act as somebody else.
64
+ *
65
+ * The policy pin is absent for the same reason: it is a claim about the *service's* state,
66
+ * not the agent's, and `submitPinned` stamps it from the service's own answer.
67
+ */
68
+ export function toDraft(input, config) {
69
+ const draft = {
70
+ agent: config.agentUri,
71
+ attestation: config.attestation,
72
+ action: toActionTerm(input.action),
73
+ entries: input.entries.map(toEntry),
74
+ justification: input.justification,
75
+ };
76
+ // Assigned conditionally: `exactOptionalPropertyTypes` distinguishes an absent key from
77
+ // a present-`undefined` one, and `IntentDraft.transaction` being absent is what tells
78
+ // the client to mint an id.
79
+ if (input.transaction !== undefined) {
80
+ draft.transaction = input.transaction;
81
+ }
82
+ if (input.fulfills !== undefined) {
83
+ draft.fulfills = input.fulfills;
84
+ }
85
+ if (input.evidence !== undefined) {
86
+ draft.evidence = input.evidence;
87
+ }
88
+ // Same rule, and it matters more here: the contract says an intent that carries no usage
89
+ // move omits the key **entirely**, never `null`. A model that fills in two of the three
90
+ // must not have the third invented for it.
91
+ if (input.consume_usage !== undefined) {
92
+ draft.consume_usage = toUsageMove(input.consume_usage);
93
+ }
94
+ if (input.meter_usage !== undefined) {
95
+ draft.meter_usage = toUsageMove(input.meter_usage);
96
+ }
97
+ if (input.bill_usage !== undefined) {
98
+ draft.bill_usage = toUsageMove(input.bill_usage);
99
+ }
100
+ return draft;
101
+ }
102
+ //# sourceMappingURL=convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@axorum/mcp` — the official Axorum MCP server.
3
+ *
4
+ * It exposes the Axorum agent plane as MCP tools, so that any MCP-capable agent — Claude
5
+ * Code, Claude Desktop, anything else that speaks the protocol — becomes an
6
+ * Axorum-governed spender with no integration code written.
7
+ *
8
+ * ## The thesis
9
+ *
10
+ * It is safe to hand a language model money-moving tools **precisely because** Axorum sits
11
+ * underneath, refusing what the policy in force forbids and recording the refusal. The
12
+ * guardrail is not a prompt asking the model to be careful. It is a ledger that will not
13
+ * post what it is not permitted to post.
14
+ *
15
+ * Which is why a refusal here is a **result, not an error**. It comes back as an ordinary
16
+ * tool result — `isError` unset — leading with a plain-language verdict line the model
17
+ * reads and adapts to. `isError` is reserved for the cases where the ledger never got to
18
+ * answer at all: a misconfigured server, an unreachable service, a pin that would not hold.
19
+ *
20
+ * ## Running it
21
+ *
22
+ * Normally you do not import this package — you run the `axorum-mcp` binary and let an MCP
23
+ * host talk to it over stdio. The exports here are for embedding the server in a host
24
+ * process, and for the tests.
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+ export { ConfigurationError, loadConfig, type ReadFile, type ServerConfig } from "./config.js";
29
+ export { SERVER_INFO, createServer } from "./server.js";
30
+ export { toActionTerm, toAmount, toDraft, toEntry, toValue } from "./convert.js";
31
+ export { describeRecord, postedFlag, renderError, renderOutcome, renderResult, renderText, verdictLine, } from "./render.js";
32
+ export { toJsonText } from "./json.js";
33
+ export { INTEGER, REFERENCE_SETS, actionSchema, amountSchema, entrySchema, getBalanceShape, getObligationsShape, getTransactionShape, submitIntentShape, valueSchema, type AmountInput, type EntryInput, type SubmitIntentInput, type ValueInput, } from "./schemas.js";
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/F,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEjF,OAAO,EACL,cAAc,EACd,UAAU,EACV,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EACL,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@axorum/mcp` — the official Axorum MCP server.
3
+ *
4
+ * It exposes the Axorum agent plane as MCP tools, so that any MCP-capable agent — Claude
5
+ * Code, Claude Desktop, anything else that speaks the protocol — becomes an
6
+ * Axorum-governed spender with no integration code written.
7
+ *
8
+ * ## The thesis
9
+ *
10
+ * It is safe to hand a language model money-moving tools **precisely because** Axorum sits
11
+ * underneath, refusing what the policy in force forbids and recording the refusal. The
12
+ * guardrail is not a prompt asking the model to be careful. It is a ledger that will not
13
+ * post what it is not permitted to post.
14
+ *
15
+ * Which is why a refusal here is a **result, not an error**. It comes back as an ordinary
16
+ * tool result — `isError` unset — leading with a plain-language verdict line the model
17
+ * reads and adapts to. `isError` is reserved for the cases where the ledger never got to
18
+ * answer at all: a misconfigured server, an unreachable service, a pin that would not hold.
19
+ *
20
+ * ## Running it
21
+ *
22
+ * Normally you do not import this package — you run the `axorum-mcp` binary and let an MCP
23
+ * host talk to it over stdio. The exports here are for embedding the server in a host
24
+ * process, and for the tests.
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+ export { ConfigurationError, loadConfig } from "./config.js";
29
+ export { SERVER_INFO, createServer } from "./server.js";
30
+ export { toActionTerm, toAmount, toDraft, toEntry, toValue } from "./convert.js";
31
+ export { describeRecord, postedFlag, renderError, renderOutcome, renderResult, renderText, verdictLine, } from "./render.js";
32
+ export { toJsonText } from "./json.js";
33
+ export { INTEGER, REFERENCE_SETS, actionSchema, amountSchema, entrySchema, getBalanceShape, getObligationsShape, getTransactionShape, submitIntentShape, valueSchema, } from "./schemas.js";
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAoC,MAAM,aAAa,CAAC;AAE/F,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEjF,OAAO,EACL,cAAc,EACd,UAAU,EACV,WAAW,EACX,aAAa,EACb,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EACL,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,WAAW,GAKZ,MAAM,cAAc,CAAC"}
package/dist/json.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * JSON for a model to read — and the one rule that keeps it from throwing.
3
+ *
4
+ * The Axorum ledger denominates money in 128-bit integer minor units, and `@axorum/client`
5
+ * decodes every JSON integer as a **`bigint`** so that a large one survives the trip. That
6
+ * is the right call on the wire, and it puts one hazard in this server's path:
7
+ * `JSON.stringify` **throws a `TypeError`** on a `bigint`. Not truncates — throws.
8
+ *
9
+ * Every outcome, balance, obligation and transaction record this server hands back is
10
+ * riddled with them. So nothing here calls `JSON.stringify` bare; everything goes through
11
+ * {@link toJsonText}, which writes a `bigint` as a **quoted decimal string**.
12
+ *
13
+ * Quoted, not bare. On the wire the ledger wants a bare integer literal, and the client's
14
+ * own serializer emits one. But this text is not going on a wire — it is going to a
15
+ * language model, which reads it as text and may well hand it back to us as text. A quoted
16
+ * `"50000"` is exact and unambiguous at any magnitude; a bare `50000` invites the model
17
+ * (and any JSON tool it pipes through) to round-trip it as an IEEE-754 double and silently
18
+ * lose the low bits of a large one. Mis-stating a balance is the one thing a
19
+ * financial-control server must never do, so this reads the amounts back as strings.
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ /**
24
+ * Serializes a value to indented JSON text, safely, whatever `bigint`s it holds.
25
+ *
26
+ * A pure function, and the **only** JSON writer in this package — see the module docs for
27
+ * why a bare `JSON.stringify` is a latent crash here rather than a style preference.
28
+ *
29
+ * @param value - Anything, `bigint`s and all.
30
+ * @returns Indented JSON text. `undefined` — which `JSON.stringify` renders as the
31
+ * *absence* of a value rather than as text — becomes `"null"`, because a caller writing
32
+ * this into a content block needs a string, not a hole.
33
+ */
34
+ export declare function toJsonText(value: unknown): string;
35
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD"}
package/dist/json.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * JSON for a model to read — and the one rule that keeps it from throwing.
3
+ *
4
+ * The Axorum ledger denominates money in 128-bit integer minor units, and `@axorum/client`
5
+ * decodes every JSON integer as a **`bigint`** so that a large one survives the trip. That
6
+ * is the right call on the wire, and it puts one hazard in this server's path:
7
+ * `JSON.stringify` **throws a `TypeError`** on a `bigint`. Not truncates — throws.
8
+ *
9
+ * Every outcome, balance, obligation and transaction record this server hands back is
10
+ * riddled with them. So nothing here calls `JSON.stringify` bare; everything goes through
11
+ * {@link toJsonText}, which writes a `bigint` as a **quoted decimal string**.
12
+ *
13
+ * Quoted, not bare. On the wire the ledger wants a bare integer literal, and the client's
14
+ * own serializer emits one. But this text is not going on a wire — it is going to a
15
+ * language model, which reads it as text and may well hand it back to us as text. A quoted
16
+ * `"50000"` is exact and unambiguous at any magnitude; a bare `50000` invites the model
17
+ * (and any JSON tool it pipes through) to round-trip it as an IEEE-754 double and silently
18
+ * lose the low bits of a large one. Mis-stating a balance is the one thing a
19
+ * financial-control server must never do, so this reads the amounts back as strings.
20
+ *
21
+ * @packageDocumentation
22
+ */
23
+ /** Writes a `bigint` as a quoted decimal string; passes everything else through. */
24
+ function bigintToString(_key, value) {
25
+ return typeof value === "bigint" ? value.toString() : value;
26
+ }
27
+ /**
28
+ * Serializes a value to indented JSON text, safely, whatever `bigint`s it holds.
29
+ *
30
+ * A pure function, and the **only** JSON writer in this package — see the module docs for
31
+ * why a bare `JSON.stringify` is a latent crash here rather than a style preference.
32
+ *
33
+ * @param value - Anything, `bigint`s and all.
34
+ * @returns Indented JSON text. `undefined` — which `JSON.stringify` renders as the
35
+ * *absence* of a value rather than as text — becomes `"null"`, because a caller writing
36
+ * this into a content block needs a string, not a hole.
37
+ */
38
+ export function toJsonText(value) {
39
+ return JSON.stringify(value, bigintToString, 2) ?? "null";
40
+ }
41
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,oFAAoF;AACpF,SAAS,cAAc,CAAC,IAAY,EAAE,KAAc;IAClD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC;AAC5D,CAAC"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The `axorum-mcp` binary: an MCP server speaking JSON-RPC over stdio.
4
+ *
5
+ * An MCP host — Claude Code, Claude Desktop — launches this as a subprocess and talks to
6
+ * it on stdin/stdout. Two consequences shape this file:
7
+ *
8
+ * 1. **stdout is the protocol.** Not a log. A stray `console.log` anywhere in this process
9
+ * corrupts the JSON-RPC stream and the host sees a broken server. Every diagnostic here
10
+ * goes to stderr, which is where the host collects logs.
11
+ *
12
+ * 2. **Nobody is watching.** There is no terminal, and no human waiting to read a stack
13
+ * trace. So a bad configuration is caught *here*, before a transport exists, and
14
+ * reported as prose that names the variable and says what to set it to — and the process
15
+ * exits non-zero, so the host reports a failure to start rather than a server that
16
+ * accepts tool calls and fails all of them.
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
package/dist/main.js ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The `axorum-mcp` binary: an MCP server speaking JSON-RPC over stdio.
4
+ *
5
+ * An MCP host — Claude Code, Claude Desktop — launches this as a subprocess and talks to
6
+ * it on stdin/stdout. Two consequences shape this file:
7
+ *
8
+ * 1. **stdout is the protocol.** Not a log. A stray `console.log` anywhere in this process
9
+ * corrupts the JSON-RPC stream and the host sees a broken server. Every diagnostic here
10
+ * goes to stderr, which is where the host collects logs.
11
+ *
12
+ * 2. **Nobody is watching.** There is no terminal, and no human waiting to read a stack
13
+ * trace. So a bad configuration is caught *here*, before a transport exists, and
14
+ * reported as prose that names the variable and says what to set it to — and the process
15
+ * exits non-zero, so the host reports a failure to start rather than a server that
16
+ * accepts tool calls and fails all of them.
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { ConfigurationError, loadConfig } from "./config.js";
22
+ import { createServer } from "./server.js";
23
+ async function main() {
24
+ const config = loadConfig(process.env);
25
+ const server = createServer(config);
26
+ await server.connect(new StdioServerTransport());
27
+ // stderr, never stdout: stdout carries JSON-RPC and nothing else.
28
+ process.stderr.write(`axorum-mcp: serving ${config.agentUri} against ${config.baseUrl}\n`);
29
+ }
30
+ main().catch((error) => {
31
+ if (error instanceof ConfigurationError) {
32
+ // The expected failure, and the one a human can act on. No stack trace — the message is
33
+ // the whole of what they need, and a trace would bury it.
34
+ process.stderr.write(`axorum-mcp: ${error.message}\n`);
35
+ process.exit(1);
36
+ }
37
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
38
+ process.stderr.write(`axorum-mcp: failed to start: ${message}\n`);
39
+ process.exit(1);
40
+ });
41
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IAEjD,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,CAAC,QAAQ,YAAY,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;QACxC,wFAAwF;QACxF,0DAA0D;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,OAAO,IAAI,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}