@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/LICENSE +202 -0
- package/README.md +241 -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 +47 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +438 -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/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,242 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@axorum/mcp`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official **MCP server** for Axorum. It exposes the Axorum agent plane as MCP tools, so
|
|
4
|
+
that any MCP-capable agent — Claude Code, Claude Desktop, anything else that speaks the
|
|
5
|
+
protocol — becomes an Axorum-governed spender with **no integration code written**.
|
|
6
|
+
|
|
7
|
+
Axorum is a deontic financial-control ledger. An agent submits an *intent envelope*; the
|
|
8
|
+
service permits or refuses it under the policy in force, and **records the outcome either
|
|
9
|
+
way**.
|
|
10
|
+
|
|
11
|
+
Which is the point of this server:
|
|
12
|
+
|
|
13
|
+
> It is safe to hand a language model money-moving tools **precisely because** Axorum sits
|
|
14
|
+
> underneath, refusing what the policy forbids and recording the refusal. The guardrail is
|
|
15
|
+
> not a prompt asking the model to be careful. It is a ledger that will not post what it is
|
|
16
|
+
> not permitted to post.
|
|
17
|
+
|
|
18
|
+
ESM-only, Node ≥ 20. Speaks stdio.
|
|
19
|
+
|
|
20
|
+
> **This package and `@axorum/client` release together, at one version, from one tag.**
|
|
21
|
+
> The client is published first and the release waits until npm serves it, so the range
|
|
22
|
+
> this package declares always resolves. Between releases the tree runs ahead of npm; that
|
|
23
|
+
> is expected and affects nobody installing from the registry. `clients/RELEASING.md` has
|
|
24
|
+
> the ceremony.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Installing it as a dependency needs nothing but npm:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install @axorum/mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Working on it in this repository, `clients/` is one pnpm workspace and the build is
|
|
35
|
+
topological — the client compiles before this package typechecks against it:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
cd clients
|
|
39
|
+
pnpm install
|
|
40
|
+
pnpm -r run build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
All configuration is environment. It is validated **at startup**, and a bad value stops the
|
|
46
|
+
process with a message that names the variable and says what to set it to — an MCP server
|
|
47
|
+
is launched into a void, with no terminal and nobody watching, so a misconfiguration must
|
|
48
|
+
not surface as tool calls failing strangely twenty minutes in.
|
|
49
|
+
|
|
50
|
+
| Variable | Required | Meaning |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `AXORUM_BASE_URL` | **yes** | The service root, e.g. `http://127.0.0.1:8080`. The `/api/v1` prefix is added for you. |
|
|
53
|
+
| `AXORUM_AGENT_URI` | **yes** | The `agent://` URI this server acts as. Must already be bound to a party in the ledger's registry. |
|
|
54
|
+
| `AXORUM_ATTESTATION` | **one of** | The PASETO v4.public capability attestation, inline. |
|
|
55
|
+
| `AXORUM_ATTESTATION_FILE` | **one of** | A path to a file holding one. |
|
|
56
|
+
| `AXORUM_TIMEOUT_MS` | no | Per-request timeout. Defaults to `30000`. |
|
|
57
|
+
| `AXORUM_FAILOVER_URLS` | no | Every other replica's service root, comma-separated, e.g. `https://axorum-1:8443,https://axorum-2:8443`. Reads move to the next replica on a refused connection, a timeout or a `421`, and a submission goes once to the replica that last answered. Each must be a bare origin on `AXORUM_BASE_URL`'s scheme, listed once. Unset, `AXORUM_BASE_URL` must reach the primary. |
|
|
58
|
+
|
|
59
|
+
`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.
|
|
60
|
+
|
|
61
|
+
Setting **both** `AXORUM_ATTESTATION` and `AXORUM_ATTESTATION_FILE` is a hard failure, not a
|
|
62
|
+
precedence rule. Two claims about which credential an agent spends money under is exactly
|
|
63
|
+
the configuration where a silent precedence rule sends the wrong credential and nobody finds
|
|
64
|
+
out until the audit.
|
|
65
|
+
|
|
66
|
+
### Minting an attestation
|
|
67
|
+
|
|
68
|
+
The attestation is a PASETO v4.public token proving this agent holds the capabilities the
|
|
69
|
+
policy requires. Mint one with the `agent-uri` CLI:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
agent-uri attest issue \
|
|
73
|
+
--agent-uri agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q \
|
|
74
|
+
--capability agent/post \
|
|
75
|
+
--key ./issuer.key \
|
|
76
|
+
> ./axorum.attestation
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The issuer's public key must be registered as a trust root on the service, and the agent URI
|
|
80
|
+
must be bound to a party — both are admin-plane operations, done once by an operator.
|
|
81
|
+
|
|
82
|
+
## Registering with Claude Code
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
claude mcp add axorum \
|
|
86
|
+
--env AXORUM_BASE_URL=http://127.0.0.1:8080 \
|
|
87
|
+
--env AXORUM_AGENT_URI=agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q \
|
|
88
|
+
--env AXORUM_ATTESTATION_FILE=/etc/axorum/axorum.attestation \
|
|
89
|
+
-- npx axorum-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or, in a Claude Desktop `claude_desktop_config.json`:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"axorum": {
|
|
98
|
+
"command": "npx",
|
|
99
|
+
"args": ["axorum-mcp"],
|
|
100
|
+
"env": {
|
|
101
|
+
"AXORUM_BASE_URL": "http://127.0.0.1:8080",
|
|
102
|
+
"AXORUM_AGENT_URI": "agent://example.com/agent/clerk_01h455vb4pex5vsknk084sn02q",
|
|
103
|
+
"AXORUM_ATTESTATION_FILE": "/etc/axorum/axorum.attestation"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The model **cannot choose its own identity.** Which agent this server acts as, and what
|
|
111
|
+
authority it holds, come from the operator's environment. There is no argument a model can
|
|
112
|
+
pass to any tool to make this server act as somebody else.
|
|
113
|
+
|
|
114
|
+
## The tools
|
|
115
|
+
|
|
116
|
+
| Tool | Does |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `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. |
|
|
119
|
+
| `get_active_policy` | The id of the policy in force, or a plain statement that none is. |
|
|
120
|
+
| `get_currencies` | Every declared currency with its minor-unit exponent. An amount in any other currency is refused with cause `undeclared_currency`. |
|
|
121
|
+
| `get_transaction` | The stored record for a transaction id — including a recorded refusal. |
|
|
122
|
+
| `get_obligations` | Your agent's open duties. |
|
|
123
|
+
| `get_balance` | An account's net balance, in minor units. |
|
|
124
|
+
| `get_usage` | A metered resource's consumed / metered / billed running totals, and the headroom left. |
|
|
125
|
+
|
|
126
|
+
Each tool's description is written as a **prompt**, not as documentation: it is the text the
|
|
127
|
+
model reads when deciding whether to call the tool and what to pass it. So each one says
|
|
128
|
+
when to use the tool, what a refusal means, that retrying a transaction id is safe, and what
|
|
129
|
+
*not* to do — do not retry a refusal, do not follow a `not_primary` redirect.
|
|
130
|
+
|
|
131
|
+
### Metered spend is one intent
|
|
132
|
+
|
|
133
|
+
`submit_intent` takes three optional usage moves — `consume_usage` (you used a metered
|
|
134
|
+
resource), `meter_usage` (it has been measured), `bill_usage` (it has been billed for). They
|
|
135
|
+
ride the *same* intent as the money entries and commit atomically with them.
|
|
136
|
+
|
|
137
|
+
The ledger holds them to one rule: `0 <= billed <= metered <= consumed`. Metering more than
|
|
138
|
+
was consumed is **phantom usage**; billing more than was metered is billing with nothing to
|
|
139
|
+
back it. Breaking either refuses the **entire** intent — the journal entries do not post
|
|
140
|
+
either — and the model is told so in the ledger's own compliance English, with exact
|
|
141
|
+
figures, plus the advice not to retry it unchanged.
|
|
142
|
+
|
|
143
|
+
`get_usage` reads the totals back. Its result leads with the ledger's own plain-English
|
|
144
|
+
narration of the balance, then states the headroom that bounds the next lawful move, so a
|
|
145
|
+
model learns its limit from a sentence rather than by doing arithmetic on JSON. An
|
|
146
|
+
unreconciled chain is a lawful transient, and the description says so — a model should not
|
|
147
|
+
try to "fix" it.
|
|
148
|
+
|
|
149
|
+
Usage reads are **owner-scoped and fail-closed**: a rival-owned balance, an ownerless one,
|
|
150
|
+
and one that never existed all answer the same `unknown_usage`. The description tells the
|
|
151
|
+
model to conclude only that it cannot read the balance — never that the balance is absent.
|
|
152
|
+
|
|
153
|
+
## Why a refusal is not an error
|
|
154
|
+
|
|
155
|
+
When the policy in force forbids an action, the intent still reaches the commit point, is
|
|
156
|
+
still judged, and the refusal is still **recorded**. The ledger answers `200`. So this
|
|
157
|
+
server returns it as an ordinary tool result, with `isError` **unset**, leading with a line
|
|
158
|
+
the model reads first:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
Refused — nothing posted. The policy in force forbids this action, and the refusal itself
|
|
162
|
+
is now recorded on the ledger. This is a verdict, not a failure: the system worked exactly
|
|
163
|
+
as intended. Do NOT retry this unchanged — it will be refused again, identically. Either
|
|
164
|
+
propose an action the policy allows, or tell your human that the policy forbids this and
|
|
165
|
+
that a human has to change the policy to permit it.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The model reads that, and adapts. That loop is the entire product.
|
|
169
|
+
|
|
170
|
+
## Outcome provenance
|
|
171
|
+
|
|
172
|
+
Structured outcomes preserve the TypeScript client's recorded provenance. Its optional
|
|
173
|
+
`semantics` field names the evaluation law; in MCP tool text it is rendered as an exact
|
|
174
|
+
decimal string alongside other TypeScript `bigint` values. It is omitted for
|
|
175
|
+
pre-versioning records and for archived provenance. `master_data` is likewise omitted
|
|
176
|
+
when the policy did not consult replicated reference data.
|
|
177
|
+
|
|
178
|
+
Dressing a refusal as an error would break it in a specific and bad way. `isError` is the
|
|
179
|
+
channel that means *the tool malfunctioned* — and a well-built agent's response to a
|
|
180
|
+
malfunction is to **retry**. A refusal flagged as an error therefore invites the model to
|
|
181
|
+
retry the exact thing the policy just forbade, in a loop, and to report to its human that
|
|
182
|
+
Axorum is broken rather than that the spend was denied. The refusal is the most valuable
|
|
183
|
+
thing this ledger produces. It goes in the content, in plain language, where the model will
|
|
184
|
+
read it.
|
|
185
|
+
|
|
186
|
+
`isError` is reserved for the cases where the ledger **never got to answer at all**: this
|
|
187
|
+
process is misconfigured, the service is unreachable, the policy pin would not hold, the
|
|
188
|
+
cluster could not confirm a commit. Those results carry the machine-readable error `code`
|
|
189
|
+
and the load-bearing extras a caller needs to act — `active` (without which nothing can
|
|
190
|
+
re-pin) and `primary_api_addr` (the primary's API origin as the follower last knew it, a hint that may be stale).
|
|
191
|
+
|
|
192
|
+
Note the one verdict that catches people out: `ForbiddenRecorded` is **forbidden and posted
|
|
193
|
+
anyway** — a policy whose stance is to record rather than block. `posted` is not the inverse
|
|
194
|
+
of "forbidden", and this server says so in prose rather than leaving a model to skim for the
|
|
195
|
+
word.
|
|
196
|
+
|
|
197
|
+
## Money is text
|
|
198
|
+
|
|
199
|
+
Amounts are integer **minor units** (cents, not dollars) written as **decimal strings**:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{ "minor": "50000", "currency": "USD" }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
That is $500.00.
|
|
206
|
+
|
|
207
|
+
The strings are not fussiness. The ledger's minor units are 128-bit integers, and every JSON
|
|
208
|
+
parser between a model and this process decodes a bare number as an IEEE-754 double, which
|
|
209
|
+
holds integers exactly only up to 2^53. A `minor` declared as a JSON *number* would be
|
|
210
|
+
silently rounded for any large value, and the ledger would faithfully post the rounded
|
|
211
|
+
figure. Balances come back as strings for the same reason.
|
|
212
|
+
|
|
213
|
+
Malformed amounts are refused rather than guessed at. `"10.00"`, `"1e3"` and `"1_000"` are
|
|
214
|
+
all rejected: a financial-control server that interprets an ambiguous amount is worse than
|
|
215
|
+
one that declines it.
|
|
216
|
+
|
|
217
|
+
## Idempotency
|
|
218
|
+
|
|
219
|
+
The `transaction` id is the ledger's **idempotency key**. Re-submitting an id that already
|
|
220
|
+
committed replays its stored outcome instead of posting twice — which is what makes retrying
|
|
221
|
+
a submission safe. Omit it and one is minted for you, once, before the first attempt.
|
|
222
|
+
|
|
223
|
+
This is why `submit_intent` can recover from a policy that moved mid-flight without any risk
|
|
224
|
+
of double-posting, and why the one genuinely ambiguous failure — an unconfirmed cluster
|
|
225
|
+
commit — is safe to resolve either by retrying or by reading the transaction back.
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
pnpm typecheck # tsc --noEmit, strict
|
|
231
|
+
pnpm build # → dist/, with a shebang on dist/main.js
|
|
232
|
+
pnpm test # unit + integration
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The integration suite is not mocked. It spawns the real `conformance_server` binary — a
|
|
236
|
+
genuine `axorum-serve` — bootstraps it over the admin HTTP API, points a real `McpServer` at
|
|
237
|
+
it, and connects a real MCP `Client` over a linked in-memory transport pair. Every assertion
|
|
238
|
+
travels the whole path: MCP client → JSON-RPC → tool → `@axorum/client` → HTTP → the real
|
|
239
|
+
policy engine, and back. The refusal it asserts on is a real refusal, from a real policy.
|
|
240
|
+
|
|
241
|
+
The binary is **prebuilt** by the Rust workspace (`target/debug/examples/conformance_server`).
|
|
242
|
+
The suite never invokes cargo.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
/** A validated configuration. Nothing constructs one but {@link loadConfig}. */
|
|
13
|
+
export interface ServerConfig {
|
|
14
|
+
/** The Axorum service's root, e.g. `http://127.0.0.1:8080`. */
|
|
15
|
+
readonly baseUrl: string;
|
|
16
|
+
/** The agent's `agent://` URI, bound to a party in the ledger's registry. */
|
|
17
|
+
readonly agentUri: string;
|
|
18
|
+
/** The PASETO v4.public capability attestation proving this agent may act. */
|
|
19
|
+
readonly attestation: string;
|
|
20
|
+
/** An optional per-request timeout, in milliseconds. */
|
|
21
|
+
readonly timeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Every other replica's service root, in the order to try them after `baseUrl`. Empty
|
|
24
|
+
* when `AXORUM_FAILOVER_URLS` is unset: `baseUrl` is then assumed to reach the primary.
|
|
25
|
+
*/
|
|
26
|
+
readonly failoverUrls: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The configuration was wrong, and the server will not start.
|
|
30
|
+
*
|
|
31
|
+
* Distinct from `@axorum/client`'s own `ConfigError`: this one is about *this process's
|
|
32
|
+
* environment*, and its message is written to be read by a human editing an MCP host's
|
|
33
|
+
* config file.
|
|
34
|
+
*/
|
|
35
|
+
export declare class ConfigurationError extends Error {
|
|
36
|
+
constructor(message: string);
|
|
37
|
+
}
|
|
38
|
+
/** How to read the attestation file. Injected so the validator stays testable. */
|
|
39
|
+
export type ReadFile = (path: string) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Reads and validates the configuration from an environment.
|
|
42
|
+
*
|
|
43
|
+
* A **pure function** of `(env, readFile)` — which is the reason every one of its failure
|
|
44
|
+
* messages can be held to account by a unit test without setting a single real environment
|
|
45
|
+
* variable or writing a single real file.
|
|
46
|
+
*
|
|
47
|
+
* @param env - The environment. `process.env` in production.
|
|
48
|
+
* @param readFile - How to read `AXORUM_ATTESTATION_FILE`. Defaults to the filesystem.
|
|
49
|
+
* @throws {ConfigurationError} With a message that names the variable and says what to do.
|
|
50
|
+
*/
|
|
51
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv, readFile?: ReadFile): ServerConfig;
|
|
52
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAKD,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAKhD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,QAAuB,GAChC,YAAY,CAiCd"}
|