@florent-uzio/custody-mcp 0.0.0-stage → 0.1.1

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/README.md CHANGED
@@ -1,3 +1,153 @@
1
- # Temporary Holding Version
1
+ # custody-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An MCP server that lets you read from and act on Ripple Custody instances, as your own Custody user, from an AI client such as Claude Code.
4
+
5
+ > **For test and demo instances.** The server can Propose, Approve and Reject Intents that move real assets on the instance it points at. Use it with dev boxes, demo sandboxes and staging, not with production custody.
6
+
7
+ ## Supported clients
8
+
9
+ The server speaks only the 2026-07-28 MCP protocol (the one `@mastra/mcp` 2.x serves over stdio). Claude Code works. A client that only speaks a 2025 protocol revision fails at `initialize` with `Unsupported protocol version`.
10
+
11
+ ## Use it from your MCP client
12
+
13
+ The server runs with `npx`, so there is nothing to clone or build. In Claude Code, add it for all your projects with:
14
+
15
+ ```sh
16
+ claude mcp add custody --scope user -- npx -y @florent-uzio/custody-mcp@latest
17
+ ```
18
+
19
+ For other clients, add it to the client's MCP config:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "custody": {
25
+ "command": "npx",
26
+ "args": ["-y", "@florent-uzio/custody-mcp@latest"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ `@latest` makes `npx` check npm each time the client starts the server, so you get new releases on your next client restart instead of a copy cached by `npx`. To stay on one version, pin it instead, e.g. `@florent-uzio/custody-mcp@0.1.0`.
33
+
34
+ The server needs at least one **Environment**: a Ripple Custody instance together with the credentials of your Custody user on it. Set one up with the setup CLI below.
35
+
36
+ ## Setup CLI
37
+
38
+ The same `custody-mcp` command, run with a subcommand, manages your Environments. With no subcommand it starts the MCP server.
39
+
40
+ | Command | What it does |
41
+ | --------------------------------------------------------- | -------------------------------------------------------------------- |
42
+ | `npx @florent-uzio/custody-mcp@latest add-env <name>` | Sets up a new Environment, step by step. |
43
+ | `npx @florent-uzio/custody-mcp@latest list-envs` | Shows every Environment and where its key is stored (never the key). |
44
+ | `npx @florent-uzio/custody-mcp@latest remove-env <name>` | Removes an Environment from the config file. |
45
+ | `npx @florent-uzio/custody-mcp@latest set-default <name>` | Makes an Environment the one read tools use when a call names none. |
46
+
47
+ Changes take effect straight away: the server re-reads the config file on every tool call, so you do not need to restart your AI client.
48
+
49
+ ### `add-env <name>`
50
+
51
+ ```sh
52
+ npx @florent-uzio/custody-mcp@latest add-env dev
53
+ ```
54
+
55
+ It walks you through these steps:
56
+
57
+ 1. Asks for the instance's **auth URL** and **API URL**.
58
+ 2. Generates a new keypair, or imports yours (see the options below), and stores the private key.
59
+ 3. Prints the **public key** in the format Custody expects. Register it for your Custody user, then press Enter.
60
+ 4. Checks that the login works by calling Custody's "me" endpoint. If it fails (wrong URL, key not registered yet), nothing is saved and you can run `add-env` again.
61
+ 5. Lists the domains your user belongs to and asks which one is the **Default domain** (Enter picks the first).
62
+ 6. Asks for the **Access level**: `read` (the default), `propose` (read + Propose Intents) or `approve` (propose + Approve/Reject Intents).
63
+ 7. Saves the Environment.
64
+
65
+ Options:
66
+
67
+ | Option | Default | What it does |
68
+ | ------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
69
+ | `--algorithm ed25519\|secp256k1\|secp256r1` | `ed25519` | The algorithm of the keypair to generate. |
70
+ | `--import <pem-file>` | | Uses an existing private key PEM instead of generating one, e.g. a key already registered in Custody. |
71
+ | `--store keychain\|file\|env` | `keychain` | Where the private key is kept (see below). |
72
+
73
+ ### Where private keys are kept
74
+
75
+ The config file never holds a private key, only a **Key reference** saying where it is (see [ADR-0002](docs/adr/0002-key-references-in-config.md)). So you can read, diff or share the config file without leaking a key.
76
+
77
+ | `--store` | Key reference | Notes |
78
+ | ---------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79
+ | `keychain` | `keychain:custody-mcp/<name>` | The macOS Keychain (through the `security` tool). |
80
+ | `file` | `file:<config dir>/keys/<name>.pem` | A PEM file next to the config file, readable only by you (`0600`). For machines without a Keychain. |
81
+ | `env` | `env:<VAR>` | Points at a key you have already exported in an env var: `add-env` asks for the var's name and reads the key from it. The env var must also be set where the MCP server runs. |
82
+
83
+ A key is read the first time its Environment is used, so a broken Key reference only affects that one Environment.
84
+
85
+ `remove-env` does not delete the private key: it prints where the key is, so you can delete it yourself if you no longer need it.
86
+
87
+ ## Config file
88
+
89
+ Environments are saved in `~/.config/custody-mcp/config.json`. Set `CUSTODY_MCP_CONFIG` to use a file somewhere else, for example to keep separate sets of Environments.
90
+
91
+ ```json
92
+ {
93
+ "default": "dev",
94
+ "environments": {
95
+ "dev": {
96
+ "authUrl": "https://auth.example.com/token",
97
+ "apiUrl": "https://api.example.com",
98
+ "publicKey": "MCowBQYDK2VwAyEA…",
99
+ "keyReference": "keychain:custody-mcp/dev",
100
+ "access": "read",
101
+ "defaultDomainId": "…"
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Zero-setup env vars
108
+
109
+ If `AUTH_URL`, `API_URL`, `PUBLIC_KEY` and `PRIVATE_KEY` are set (as in custody-mpt-demo), they are picked up as an extra Environment named `env`. Its Access level is `read` unless you set `CUSTODY_ACCESS` to `propose` or `approve`.
110
+
111
+ ## Access levels and confirmations
112
+
113
+ Each Environment has an **Access level**, chosen in `add-env` (or with `CUSTODY_ACCESS` for the `env` Environment). It caps what the AI client can do through that Environment:
114
+
115
+ | Access level | Allows |
116
+ | ---------------- | ---------------------------------------------------------------------------------------- |
117
+ | `read` (default) | Reading: users, domains, accounts, balances, Intents, transfers, transactions, Dry runs. |
118
+ | `propose` | `read`, plus Proposing Intents (`proposeXrplIntent`, `proposeIntent`). |
119
+ | `approve` | `propose`, plus Approving and Rejecting Intents (`approveIntent`, `rejectIntent`). |
120
+
121
+ The Access level only limits this server. What your Custody user may do is still decided by Custody's own roles and policies.
122
+
123
+ ### Guardrails
124
+
125
+ - Every propose, approve or reject call must name its Environment explicitly: a write never falls back to the default.
126
+ - A tool above an Environment's Access level is refused before anything is sent to Custody.
127
+ - Every Propose runs a Dry run first and proposes nothing if it fails.
128
+ - Every Approve or Reject asks you for confirmation in your client (see [ADR-0001](docs/adr/0001-approve-through-mcp.md)): the client shows a summary of the Intent, and nothing reaches Custody unless you accept. If your client cannot show a confirmation, the assistant is instructed to summarise the Intent and wait for your explicit "yes", and the result says that no in-client confirmation was shown.
129
+ - Proposing returns at once. `waitForIntent` follows an Intent for a bounded time; `Executed` means Custody accepted the order, not that it settled on the ledger.
130
+
131
+ ## Development
132
+
133
+ ```sh
134
+ npm ci
135
+ npm test # unit tests, plus a check that the packed tarball installs and starts
136
+ npm run mcp # the server from source, over stdio
137
+ ```
138
+
139
+ ### Smoke test against a real instance
140
+
141
+ `scripts/smoke.ts` is a manual end-to-end check, never run in CI: it proposes and approves a real (tiny) XRP Payment. It starts the server from source over stdio, with your own config file, and prints the backend version, runs reads, a Dry run, a Propose, `waitForIntent` and an Approve.
142
+
143
+ ```sh
144
+ npx tsx scripts/smoke.ts --env devbox --account <r… sender> --destination <r… receiver> [--amount <drops>] [--approver devbox-2]
145
+ ```
146
+
147
+ - `--env` needs Access level `approve`. The script first approves as the proposer and records whether Custody lets that approval count (self-approval).
148
+ - If it does not, pass `--approver`: a second Environment, for another Custody user with Access level `approve`, which then approves.
149
+ - Each Approve asks you to confirm in the terminal, as your client would.
150
+
151
+ ### Releasing
152
+
153
+ Releases use [Changesets](https://github.com/changesets/changesets). Add a changeset with your change (`npx changeset`). On `main`, the release workflow opens a "Version Packages" PR; merging it publishes to npm with npm trusted publishing (no npm token in the repo). `npm publish` first runs the `prepublishOnly` guard: formatting, types and tests, including installing the packed tarball and starting it.