@axorum/mcp 0.0.0-stage → 1.1.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/LICENSE +202 -0
- package/README.md +245 -2
- package/dist/config.d.ts +52 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +227 -0
- package/dist/config.js.map +1 -0
- package/dist/convert.d.ts +49 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +102 -0
- package/dist/convert.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +35 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +41 -0
- package/dist/json.js.map +1 -0
- package/dist/main.d.ts +21 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +41 -0
- package/dist/main.js.map +1 -0
- package/dist/render.d.ts +156 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +482 -0
- package/dist/render.js.map +1 -0
- package/dist/schemas.d.ts +238 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +234 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +48 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +439 -0
- package/dist/server.js.map +1 -0
- package/dist/verify.d.ts +109 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +297 -0
- package/dist/verify.js.map +1 -0
- package/package.json +64 -3
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 --key <signing-key> --agent <uri> --agent-key <agent-public-key> --capability agent/post");
|
|
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 --key <signing-key> --agent <uri> --agent-key <agent-public-key> --capability agent/post");
|
|
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":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,WAAW,EAA2B,MAAM,gBAAgB,CAAC;AAuBtE;;;;;;GAMG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,uDAAuD;AACvD,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAKtC,yDAAyD;AACzD,MAAM,YAAY,GAAa,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CACxB,MAAyB,OAAO,CAAC,GAAG,EACpC,WAAqB,YAAY;IAEjC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,MAAM,GAMR;QACF,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC;QACvB,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC;QACvC,YAAY,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC;KACtC,CAAC;IAEF,uFAAuF;IACvF,0FAA0F;IAC1F,2FAA2F;IAC3F,4FAA4F;IAC5F,4BAA4B;IAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8EAA8E;YAC5E,qFAAqF;YACrF,8BAA8B,CACjC,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6EAA6E;AAC7E,SAAS,OAAO,CAAC,KAAyB;IACxC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3B,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED,wEAAwE;AACxE,SAAS,OAAO,CAAC,GAAsB;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,kBAAkB,CAC1B,yEAAyE;YACvE,mFAAmF,CACtF,CAAC;IACJ,CAAC;IAED,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAC1B,kCAAkC,KAAK,gCAAgC;YACrE,4CAA4C,CAC/C,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,MAAM,IAAI,kBAAkB,CAC1B,sDAAsD,KAAK,SAAS,MAAM,CAAC,QAAQ,KAAK;YACtF,sDAAsD,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4DAA4D;AAC5D,SAAS,QAAQ,CAAC,GAAsB;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,kBAAkB,CAC1B,+EAA+E;YAC7E,oFAAoF;YACpF,+DAA+D,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,kBAAkB,CAC1B,wDAAwD,KAAK,KAAK;YAChE,4FAA4F,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,GAAsB,EAAE,QAAkB;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAErD,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,kBAAkB,CAC1B,gFAAgF;YAC9E,mFAAmF,CACtF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,kBAAkB,CAC1B,mFAAmF;YACjF,oFAAoF;YACpF,oFAAoF;YACpF,iHAAiH,CACpH,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,iBAAiB,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACzD,CAAC;IAED,+EAA+E;IAC/E,MAAM,IAAI,GAAG,IAAc,CAAC;IAC5B,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,kBAAkB,CAC1B,sCAAsC,IAAI,+BAA+B,QAAQ,CAAC,KAAK,CAAC,IAAI;YAC1F,qCAAqC,CACxC,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,kBAAkB,CAC1B,sCAAsC,IAAI,2CAA2C;YACnF,oEAAoE,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,iBAAiB,CAAC,KAAK,EAAE,6BAA6B,IAAI,IAAI,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,KAAa,EAAE,MAAc;IACtD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,kBAAkB,CAC1B,GAAG,MAAM,2DAA2D,gBAAgB,KAAK;YACvF,kBAAkB,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB;YACzD,iHAAiH,CACpH,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,qFAAqF;AACrF,SAAS,SAAS,CAAC,GAAsB;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAChD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,wFAAwF;IACxF,gCAAgC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,kBAAkB,CAC1B,iFAAiF,KAAK,KAAK;YACzF,kFAAkF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,GAAsB,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACnD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,kBAAkB,CAC1B,uDAAuD,KAAK,GAAG,CAAC,MAAM,KAAK,UAAU;YACnF,4DAA4D;YAC5D,kEAAkE,CACrE,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACpC,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,kBAAkB,CAAC,oCAAoC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACrG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,OAA2B,EAAE,IAAuB;IACvE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,eAAe;YAClB,OAAO,CACL,IAAI,OAAO,CAAC,GAAG,mCAAmC,OAAO,CAAC,MAAM,cAAc;gBAC9E,kEAAkE,CACnE,CAAC;QACJ,KAAK,cAAc;YACjB,OAAO,CACL,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,UAAU,OAAO,CAAC,KAAK,6BAA6B;gBACrF,GAAG,OAAO,CAAC,QAAQ,gDAAgD,CACpE,CAAC;QACJ,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;YAC7F,OAAO,CACL,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,6BAA6B,OAAO,CAAC,MAAM,OAAO;gBACvF,GAAG,KAAK,2BAA2B,CACpC,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -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"}
|
package/dist/convert.js
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
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, 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
|
package/dist/json.js.map
ADDED
|
@@ -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
|
package/dist/main.js.map
ADDED
|
@@ -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"}
|