@florent-uzio/custody-mcp 0.0.0-stage → 0.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/README.md CHANGED
@@ -1,3 +1,145 @@
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
+ Add the server to your client's MCP config. It runs with `npx`, so there is nothing to clone or build:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "custody": {
19
+ "command": "npx",
20
+ "args": ["-y", "@florent-uzio/custody-mcp"]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ 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.
27
+
28
+ ## Setup CLI
29
+
30
+ The same `custody-mcp` command, run with a subcommand, manages your Environments. With no subcommand it starts the MCP server.
31
+
32
+ | Command | What it does |
33
+ | -------------------------------------------------- | -------------------------------------------------------------------- |
34
+ | `npx @florent-uzio/custody-mcp add-env <name>` | Sets up a new Environment, step by step. |
35
+ | `npx @florent-uzio/custody-mcp list-envs` | Shows every Environment and where its key is stored (never the key). |
36
+ | `npx @florent-uzio/custody-mcp remove-env <name>` | Removes an Environment from the config file. |
37
+ | `npx @florent-uzio/custody-mcp set-default <name>` | Makes an Environment the one read tools use when a call names none. |
38
+
39
+ 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.
40
+
41
+ ### `add-env <name>`
42
+
43
+ ```sh
44
+ npx @florent-uzio/custody-mcp add-env dev
45
+ ```
46
+
47
+ It walks you through these steps:
48
+
49
+ 1. Asks for the instance's **auth URL** and **API URL**.
50
+ 2. Generates a new keypair, or imports yours (see the options below), and stores the private key.
51
+ 3. Prints the **public key** in the format Custody expects. Register it for your Custody user, then press Enter.
52
+ 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.
53
+ 5. Lists the domains your user belongs to and asks which one is the **Default domain** (Enter picks the first).
54
+ 6. Asks for the **Access level**: `read` (the default), `propose` (read + Propose Intents) or `approve` (propose + Approve/Reject Intents).
55
+ 7. Saves the Environment.
56
+
57
+ Options:
58
+
59
+ | Option | Default | What it does |
60
+ | ------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
61
+ | `--algorithm ed25519\|secp256k1\|secp256r1` | `ed25519` | The algorithm of the keypair to generate. |
62
+ | `--import <pem-file>` | | Uses an existing private key PEM instead of generating one, e.g. a key already registered in Custody. |
63
+ | `--store keychain\|file\|env` | `keychain` | Where the private key is kept (see below). |
64
+
65
+ ### Where private keys are kept
66
+
67
+ 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.
68
+
69
+ | `--store` | Key reference | Notes |
70
+ | ---------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `keychain` | `keychain:custody-mcp/<name>` | The macOS Keychain (through the `security` tool). |
72
+ | `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. |
73
+ | `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. |
74
+
75
+ A key is read the first time its Environment is used, so a broken Key reference only affects that one Environment.
76
+
77
+ `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.
78
+
79
+ ## Config file
80
+
81
+ 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.
82
+
83
+ ```json
84
+ {
85
+ "default": "dev",
86
+ "environments": {
87
+ "dev": {
88
+ "authUrl": "https://auth.example.com/token",
89
+ "apiUrl": "https://api.example.com",
90
+ "publicKey": "MCowBQYDK2VwAyEA…",
91
+ "keyReference": "keychain:custody-mcp/dev",
92
+ "access": "read",
93
+ "defaultDomainId": "…"
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### Zero-setup env vars
100
+
101
+ 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`.
102
+
103
+ ## Access levels and confirmations
104
+
105
+ 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:
106
+
107
+ | Access level | Allows |
108
+ | ---------------- | ---------------------------------------------------------------------------------------- |
109
+ | `read` (default) | Reading: users, domains, accounts, balances, Intents, transfers, transactions, Dry runs. |
110
+ | `propose` | `read`, plus Proposing Intents (`proposeXrplIntent`, `proposeIntent`). |
111
+ | `approve` | `propose`, plus Approving and Rejecting Intents (`approveIntent`, `rejectIntent`). |
112
+
113
+ The Access level only limits this server. What your Custody user may do is still decided by Custody's own roles and policies.
114
+
115
+ ### Guardrails
116
+
117
+ - Every propose, approve or reject call must name its Environment explicitly: a write never falls back to the default.
118
+ - A tool above an Environment's Access level is refused before anything is sent to Custody.
119
+ - Every Propose runs a Dry run first and proposes nothing if it fails.
120
+ - 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.
121
+ - 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.
122
+
123
+ ## Development
124
+
125
+ ```sh
126
+ npm ci
127
+ npm test # unit tests, plus a check that the packed tarball installs and starts
128
+ npm run mcp # the server from source, over stdio
129
+ ```
130
+
131
+ ### Smoke test against a real instance
132
+
133
+ `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.
134
+
135
+ ```sh
136
+ npx tsx scripts/smoke.ts --env devbox --account <r… sender> --destination <r… receiver> [--amount <drops>] [--approver devbox-2]
137
+ ```
138
+
139
+ - `--env` needs Access level `approve`. The script first approves as the proposer and records whether Custody lets that approval count (self-approval).
140
+ - If it does not, pass `--approver`: a second Environment, for another Custody user with Access level `approve`, which then approves.
141
+ - Each Approve asks you to confirm in the terminal, as your client would.
142
+
143
+ ### Releasing
144
+
145
+ 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.