@floatonnear/mcp 0.9.0 → 0.10.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/SKILL.md +630 -0
- package/dist/server.js +104 -4
- package/package.json +4 -3
package/SKILL.md
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: float
|
|
3
|
+
description: |
|
|
4
|
+
Give an AI agent a spending budget that a smart contract enforces, on NEAR.
|
|
5
|
+
Pay in stablecoins on NEAR, or cross-chain to Base/Ethereum/Solana via NEAR
|
|
6
|
+
Intents. The agent's key can call exactly one method on exactly one contract:
|
|
7
|
+
it cannot move tokens directly, cannot raise its own limit, and cannot reach
|
|
8
|
+
owner functions.
|
|
9
|
+
TRIGGERS: pay an invoice, send USDC, agent budget, spending limit for an
|
|
10
|
+
agent, treasury for agents, cross-chain payment, float, near vault.
|
|
11
|
+
homepage: https://github.com/bshuaibu1/float-near
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Float — a budget an agent cannot talk its way out of
|
|
15
|
+
|
|
16
|
+
## If you were told "set up this URL", this is the whole of it
|
|
17
|
+
|
|
18
|
+
{ "mcpServers": { "float": { "command": "npx", "args": ["-y", "@floatonnear/mcp"] } } }
|
|
19
|
+
|
|
20
|
+
Add that to your client's MCP config, restart the client, then ask for a vault.
|
|
21
|
+
Nothing else is needed to start: no key, no existing vault, no configuration.
|
|
22
|
+
|
|
23
|
+
> **This file is ~29 KB.** If you reached it through a tool that summarises web
|
|
24
|
+
> pages, you are probably reading a lossy précis and the exact commands may be
|
|
25
|
+
> missing or altered. Fetch the raw file before following any instruction in
|
|
26
|
+
> it. The JSON block above is the one thing worth trusting a summary for.
|
|
27
|
+
|
|
28
|
+
Most agent-payment tools give the agent a wallet key and a per-call cap that
|
|
29
|
+
the agent itself can change. Float gives it a **function-call key restricted to
|
|
30
|
+
one method on one contract**, and puts the limits in a contract the key cannot
|
|
31
|
+
reach.
|
|
32
|
+
|
|
33
|
+
Read the next section before doing anything. It is the part that decides which
|
|
34
|
+
of your requests can succeed.
|
|
35
|
+
|
|
36
|
+
## Setup — do this first if you arrived here cold
|
|
37
|
+
|
|
38
|
+
If someone told you "set up <this url>", this section is what they meant.
|
|
39
|
+
|
|
40
|
+
Float's tools come from an MCP server published as `@floatonnear/mcp`. Add it
|
|
41
|
+
to whichever client you are, then re-read this file.
|
|
42
|
+
|
|
43
|
+
**The JSON entry is the reliable form**, because no shell gets to reinterpret
|
|
44
|
+
it. Any client that keeps servers in a file — `.mcp.json` in a project root,
|
|
45
|
+
`claude_desktop_config.json`, `~/.cursor/mcp.json`:
|
|
46
|
+
|
|
47
|
+
{ "mcpServers": { "float": { "command": "npx", "args": ["-y", "@floatonnear/mcp"] } } }
|
|
48
|
+
|
|
49
|
+
Claude Code, from a shell:
|
|
50
|
+
|
|
51
|
+
claude mcp add float -- npx -y @floatonnear/mcp
|
|
52
|
+
|
|
53
|
+
**On Windows PowerShell that line fails.** PowerShell consumes the bare `--`,
|
|
54
|
+
so `claude` sees `-y` as one of its OWN options and aborts with
|
|
55
|
+
`error: unknown option '-y'` — nothing is registered. QUOTE THE SEPARATOR:
|
|
56
|
+
|
|
57
|
+
claude mcp add float "--" npx -y @floatonnear/mcp
|
|
58
|
+
|
|
59
|
+
Measured on PowerShell 5.1: the quoted form registers
|
|
60
|
+
`command="npx", args=["-y","@floatonnear/mcp"]`; the unquoted form and the
|
|
61
|
+
no-separator form both fail. `--%` does not help — `claude` rejects it as an
|
|
62
|
+
unknown option. Or just write the JSON entry above and skip the shell entirely.
|
|
63
|
+
|
|
64
|
+
`-y` matters. Without it `npx` stops to ask whether to install, and the server
|
|
65
|
+
never starts — which looks like a hang with no cause.
|
|
66
|
+
|
|
67
|
+
**If `float` is already taken, name this one `float-near`.** There is an
|
|
68
|
+
unrelated package called `@usefloat/mcp` that is commonly registered as
|
|
69
|
+
`float`, and its tools (`float_pay`, `allocate_budget`, `deploy_agent`) are a
|
|
70
|
+
different product entirely. Two servers under one name is ambiguous; the tools
|
|
71
|
+
in this file are all prefixed `vault_`, so if you see `float_*` instead you are
|
|
72
|
+
talking to the other one.
|
|
73
|
+
|
|
74
|
+
**Tools appear after a RESTART, not immediately.** A client reads its server
|
|
75
|
+
list once at startup, so a server added mid-session connects but its tools are
|
|
76
|
+
not callable until you restart (or reconnect via `/mcp`). "Connected" and
|
|
77
|
+
"callable" are different states, and confusing them looks like a broken install.
|
|
78
|
+
|
|
79
|
+
Confirm it worked by listing your tools. You should see `vault_create`,
|
|
80
|
+
`vault_tree`, `vault_budget` and `vault_pay` — **27 in total, 14 of them
|
|
81
|
+
`vault_plan_*`**. If you see none, the server did not start; say so rather than
|
|
82
|
+
guessing at the chain directly.
|
|
83
|
+
|
|
84
|
+
**Nothing needs configuring to begin.** `vault_create` takes no key and needs
|
|
85
|
+
no existing vault — that is deliberate, so a user with nothing can be onboarded
|
|
86
|
+
in one signature. Every other tool will tell you `NEAR_VAULT_CONTRACT` is unset
|
|
87
|
+
rather than inventing a vault.
|
|
88
|
+
|
|
89
|
+
### Then OFFER a vault. Do not wait to be asked, and do not create one unasked
|
|
90
|
+
|
|
91
|
+
As soon as setup is done, say what they have and what it would cost. Something
|
|
92
|
+
close to this, in your own words:
|
|
93
|
+
|
|
94
|
+
> You don't have a vault yet. I can set one up — it's **one signature in your
|
|
95
|
+
> own wallet**, and you need to be holding about **3.55 NEAR**. Almost all of
|
|
96
|
+
> that is a refundable storage deposit your vault keeps, not a fee. Want me to?
|
|
97
|
+
|
|
98
|
+
Three things that wording is carrying, and all three matter:
|
|
99
|
+
|
|
100
|
+
- **the cost, before they say yes.** Burying it until after they agree is how a
|
|
101
|
+
stranger feels tricked by a number their wallet shows them.
|
|
102
|
+
- **one signature, in their own wallet.** It is the answer to "what are you
|
|
103
|
+
about to do with my money", and it is true: you cannot create a vault, only
|
|
104
|
+
hand them a link.
|
|
105
|
+
- **refundable.** 3.55 NEAR reads as a fee unless you say otherwise. It is a
|
|
106
|
+
storage deposit the vault holds and returns on teardown.
|
|
107
|
+
|
|
108
|
+
**Quote `vault_create`'s own `totalRequiredNear`, not the 3.55 above**, once you
|
|
109
|
+
have called it. That figure is derived from the exact contract being deployed;
|
|
110
|
+
this one is an illustration and will drift as the contract changes.
|
|
111
|
+
|
|
112
|
+
**Do NOT auto-create one.** It spends real money, and it cannot complete
|
|
113
|
+
without them anyway — `vault_create` returns a link they approve in their own
|
|
114
|
+
wallet, so running it unasked produces a link nobody wanted and still waits.
|
|
115
|
+
Some installs also already HAVE a vault: a reinstall, a second machine, another
|
|
116
|
+
client.
|
|
117
|
+
|
|
118
|
+
**Do NOT interview them first.** `vault_create` works with zero arguments and
|
|
119
|
+
every field has a sensible default — mainnet, USDC, a starter cap. Asking about
|
|
120
|
+
networks, tokens and limits before anything exists is how people leave. Create
|
|
121
|
+
it, then adjust what they actually want to change.
|
|
122
|
+
|
|
123
|
+
**Never ask the user for a private key or a seed phrase.** The entire product
|
|
124
|
+
exists so that giving an agent a budget does not require handing it the keys
|
|
125
|
+
those limits are meant to constrain. If a flow seems to need one, you have the
|
|
126
|
+
wrong flow — say so.
|
|
127
|
+
|
|
128
|
+
If you cannot run MCP servers at all, the same operations exist as a CLI,
|
|
129
|
+
`npx @floatonnear/create-float`. Prefer the MCP: the CLI needs a full-access
|
|
130
|
+
key on the machine, which is the thing above.
|
|
131
|
+
|
|
132
|
+
## What you (the agent) can and cannot do
|
|
133
|
+
|
|
134
|
+
**Can:**
|
|
135
|
+
- read the budget tree, the caps, today's spend, and past requests
|
|
136
|
+
- request a payment from your node, on either rail
|
|
137
|
+
- produce an UNSIGNED plan for an owner action, for a human to approve
|
|
138
|
+
|
|
139
|
+
**Cannot — these fail by key permissions, not by policy:**
|
|
140
|
+
- move tokens directly. Your key's `receiver_id` is the vault, so you cannot
|
|
141
|
+
call the token contract at all.
|
|
142
|
+
- raise your own limit, freeze a node, change the payee allowlist, or withdraw.
|
|
143
|
+
Your key's `method_names` is `["request_payment"]` and nothing else.
|
|
144
|
+
- attach NEAR to anything. A NEAR function-call key cannot carry a deposit.
|
|
145
|
+
- **fund the vault.** That needs the owner's tokens and the owner's key. If the
|
|
146
|
+
vault is empty, say so and ask the human — do not try to fix it yourself.
|
|
147
|
+
|
|
148
|
+
If a request needs one of those, produce a plan with `vault_plan_*` and hand it
|
|
149
|
+
to the human. Do not attempt it and report the error.
|
|
150
|
+
|
|
151
|
+
## Daily use — the MCP tools
|
|
152
|
+
|
|
153
|
+
If `float` is configured in your MCP client, you have these. Read before you
|
|
154
|
+
spend.
|
|
155
|
+
|
|
156
|
+
| tool | use |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `vault_config` | vault id, owner, network, paused, node count |
|
|
159
|
+
| `vault_tree` | every node, its caps, its payee allowlist |
|
|
160
|
+
| `vault_budget` | today's cap / executed / pending / available for one node |
|
|
161
|
+
| `vault_request` | look up one past payment by id |
|
|
162
|
+
| `vault_pay` | **spend** |
|
|
163
|
+
| `vault_roles` | which agents exist, which node each spends from |
|
|
164
|
+
| `vault_gas` | how much gas each agent has left, and what a top-up costs or buys |
|
|
165
|
+
| `vault_pending_approval` | payments waiting on a human, when a threshold is set |
|
|
166
|
+
| `vault_teardown_report` | what closing this vault would return, and what it would strand |
|
|
167
|
+
| `vault_plan_*` | unsigned owner actions: add node, set caps, freeze, set recipients, set cooldown, pause, purge, withdraw, bind/unbind an agent, approve/reject a payment, set the approval threshold |
|
|
168
|
+
| `vault_add_role` | add an agent with its own node and funded gas key |
|
|
169
|
+
|
|
170
|
+
### Check before you pay
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
vault_budget { "nodeId": 0 }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`nodeId`, camelCase — not `node_id`. The contract uses snake_case; this tool
|
|
177
|
+
does not, and the schema will reject the wrong one.
|
|
178
|
+
|
|
179
|
+
### Pay
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
vault_pay {
|
|
183
|
+
"amount": "12.50", // HUMAN units as a string, not base units
|
|
184
|
+
"recipient": "alice.near",
|
|
185
|
+
"memo": "invoice 1041",
|
|
186
|
+
"rail": "near" // "near" | "intents" (default "near")
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- **`rail: "near"`** — a NEP-141 transfer on NEAR. `SETTLED` means the money
|
|
191
|
+
moved.
|
|
192
|
+
- **`rail: "intents"`** — cross-chain. The recipient is an address on the
|
|
193
|
+
destination chain, **lowercased** (`0xab…`, never the checksummed `0xAb…` —
|
|
194
|
+
the allowlist is exact string equality and a mixed-case address is not even a
|
|
195
|
+
valid NEAR account id). Needs the operator's quote-signer key configured; if
|
|
196
|
+
it is not, the rail is closed and the call says so.
|
|
197
|
+
|
|
198
|
+
**On the Intents rail, `SETTLED` does not mean delivered.** It means the
|
|
199
|
+
transfer to the solver's deposit address landed. The far-chain delivery is
|
|
200
|
+
off-chain and the contract cannot see it. Say that accurately — check a block
|
|
201
|
+
explorer before telling anyone the money arrived.
|
|
202
|
+
|
|
203
|
+
**Cross-chain costs more than the amount.** Quotes are EXACT_OUTPUT: the payee
|
|
204
|
+
receives `amount`, and the vault is charged more — measured 1.5% at 0.50 USDC,
|
|
205
|
+
3.5% at 0.10, because the fixed part of a solver's cost dominates at small
|
|
206
|
+
sizes. The budget is charged the larger figure, so a 0.51 cap cannot deliver a
|
|
207
|
+
round 0.50 unless the cap was sized for it.
|
|
208
|
+
|
|
209
|
+
## Getting the money back out
|
|
210
|
+
|
|
211
|
+
**The deposit is stake, not a fee.** A vault holds ~3.35 NEAR to pay for its own
|
|
212
|
+
storage, and that returns in full when the vault is closed. Gas that an agent
|
|
213
|
+
actually spends is spent — like any transaction — at roughly 0.0007 NEAR per
|
|
214
|
+
payment. So the honest summary is: *your deposit comes back, your gas is spent.*
|
|
215
|
+
|
|
216
|
+
Say so when a user asks "what does this cost?", because the answer most people
|
|
217
|
+
expect — a fee — is wrong.
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
vault_teardown_report
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Read-only. It reports what closing the vault would return, what it would
|
|
224
|
+
strand, and the exact command. It needs no key and signs nothing.
|
|
225
|
+
|
|
226
|
+
**Closing is a CLI job, not a tool, and that is deliberate.** The six steps
|
|
227
|
+
include actions only the VAULT can sign, and a browser wallet cannot sign as
|
|
228
|
+
the vault — wallets silently sign as the connected account instead and report
|
|
229
|
+
success. So:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
npx -p @floatonnear/create-float float-exit --vault <vault> --owner <you> --dry-run
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`--dry-run` quotes the refund and writes nothing. Drop it to execute.
|
|
236
|
+
|
|
237
|
+
It returns the tokens first (a deleted account does **not** return fungible
|
|
238
|
+
tokens), then each gas-key balance, then the storage deposit. Order matters:
|
|
239
|
+
`DeleteKey` on a funded gas key **destroys** the balance and reports success,
|
|
240
|
+
so the gas keys are emptied before anything is removed.
|
|
241
|
+
|
|
242
|
+
### When an agent runs out of gas
|
|
243
|
+
|
|
244
|
+
An agent's key pays its own gas and stops when empty. That is a feature — it
|
|
245
|
+
is the per-agent spending bound, enforced by the protocol rather than by
|
|
246
|
+
policy — but it means an agent can go quiet for a boring reason.
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
vault_gas -> every agent, payments left
|
|
250
|
+
vault_gas { "agent": "marketing", "payments": 100 } -> what 100 more costs
|
|
251
|
+
vault_gas { "agent": "marketing", "amountNear": "0.05" } -> what 0.05 buys
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
It returns the exact `npx -p @floatonnear/create-float float-topup` command. Topping up is
|
|
255
|
+
signed by the vault, so like closing, it is a CLI job.
|
|
256
|
+
|
|
257
|
+
## When a payment is refused
|
|
258
|
+
|
|
259
|
+
A refusal comes back as a **value**, not an error. `status: "BLOCKED"` with a
|
|
260
|
+
`reason_code`. Do not retry it — nothing about retrying changes the answer.
|
|
261
|
+
|
|
262
|
+
### Never show a reason code to the user
|
|
263
|
+
|
|
264
|
+
`vault_pay` already returns a plain-English `say`, a `next_step`, and a
|
|
265
|
+
`money_moved` flag. **Relay `say`, add `next_step` if there is one, and stop.**
|
|
266
|
+
The `reason_code` lives under `technical`; quote it only if the user asks why,
|
|
267
|
+
asks for detail, or is plainly a developer.
|
|
268
|
+
|
|
269
|
+
> Say: *"That's too big for one payment — the most per payment is 10 USDC, and
|
|
270
|
+
> this asked for 15. Nothing was sent. Send 10 USDC or less, or ask the vault
|
|
271
|
+
> owner to raise the limit."*
|
|
272
|
+
>
|
|
273
|
+
> Not: *"The payment was BLOCKED with reason_code PARENT_PER_TX_LIMIT."*
|
|
274
|
+
|
|
275
|
+
The same applies when it works: *"Sent 0.25 USDC to alice.near. There's 24.75
|
|
276
|
+
USDC left to spend today."* — not `status: SETTLED`.
|
|
277
|
+
|
|
278
|
+
Someone who asked to pay a vendor and got `PARENT_PER_TX_LIMIT` has been told
|
|
279
|
+
nothing they can act on. The codes are precise and that precision is a real
|
|
280
|
+
asset for debugging and for explaining the design — it is just not what a
|
|
281
|
+
person wants first.
|
|
282
|
+
|
|
283
|
+
### The codes themselves
|
|
284
|
+
|
|
285
|
+
You still need to understand these, to give correct advice and to avoid
|
|
286
|
+
retrying something a retry cannot fix. These are the exact strings the contract
|
|
287
|
+
emits. Match on them literally.
|
|
288
|
+
|
|
289
|
+
| reason_code | what it means | what to do |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `PER_TX_LIMIT` | this payment exceeds **your node's** per-tx cap | report the cap and the amount; offer `vault_plan_set_caps` |
|
|
292
|
+
| `PARENT_PER_TX_LIMIT` | it exceeds an **ancestor's** per-tx cap | same, but the cap to change is higher up the tree |
|
|
293
|
+
| `DAILY_BUDGET` | your node's budget for today is used up | report it; it resets at UTC midnight |
|
|
294
|
+
| `PARENT_DAILY_BUDGET` | an **ancestor's** daily budget is used up — a sibling has been spending | report it; your own limit still has room, and that is the point of the tree |
|
|
295
|
+
| `RECIPIENT_NOT_ALLOWED` | payee is not on the allowlist | offer `vault_plan_set_recipients`; **never** suggest paying a different address instead |
|
|
296
|
+
| `TOKEN_NOT_ALLOWED` | this node has no limit set for that token | offer `vault_plan_set_caps` for the token |
|
|
297
|
+
| `NODE_FROZEN` | a human froze this branch | stop; ask why |
|
|
298
|
+
| `VAULT_PAUSED` | a human pressed the kill switch | stop; do not retry |
|
|
299
|
+
| `COOLDOWN` | paying again too soon after the last one | wait; the node has a minimum interval |
|
|
300
|
+
| `DUPLICATE_REQUEST` | this `requestId` was already used | generate a new one — or do not pass one and let it be generated |
|
|
301
|
+
| `INVALID_AMOUNT` / `INVALID_MEMO` | malformed input | your own mistake; fix and retry |
|
|
302
|
+
| `QUOTE_SIGNER_UNSET` | the cross-chain rail is not configured | use `rail: "near"`, or ask the operator |
|
|
303
|
+
| `QUOTE_EXPIRED` / `QUOTE_BAD_SIGNATURE` | the cross-chain quote is stale or does not verify | retry once to get a fresh quote; if it persists, the operator's signer is misconfigured |
|
|
304
|
+
| `TRANSFER_FAILED` | the transfer failed; the reservation was released, nothing was spent | usually an empty vault — check the balance and tell the human |
|
|
305
|
+
| `PARENT_COOLDOWN` | an **ancestor's** minimum interval has not elapsed — a sibling paid recently | wait; your own node may have no cooldown at all and still be throttled by the one above |
|
|
306
|
+
| `NEAR_BUDGET` | the vault's own **NEAR** (not token) ceiling for today is spent | cross-chain is closed until UTC midnight; `rail: "near"` still works. Do **not** retry in a loop — each cross-chain attempt is what consumes this |
|
|
307
|
+
| `NO_RECIPIENTS_SET` | no allowlist exists anywhere up the tree, so nothing can match | different from `RECIPIENT_NOT_ALLOWED`: there is no list to add to. Offer `vault_plan_set_recipients` on the root |
|
|
308
|
+
| `INSUFFICIENT_GAS` | the call arrived with less than 50 TGas | attach more gas and retry; this is a caller setting, not a policy refusal |
|
|
309
|
+
| `INVALID_REQUEST_ID` | the `requestId` is empty, too long, or has illegal characters | your own mistake; fix it or omit it and let one be generated |
|
|
310
|
+
| `LEDGER_INVARIANT` | the vault's internal accounting disagrees with itself | **stop. Do not retry.** Tell the human immediately and quote the request id |
|
|
311
|
+
| `APPROVAL_REQUIRED` | **not a refusal, and not final** — inside every cap but above the node approval threshold, so a human has been asked | tell the human the amount, payee and request id, then STOP. Do not retry, do not split the amount into smaller payments to get under the threshold: that defeats the control the owner asked for. The owner calls `approve_payment` or `reject_payment`. Nothing is charged while it waits |
|
|
312
|
+
| `APPROVAL_REJECTED` | the owner declined a held payment | **do not retry.** Ask the human what to do instead |
|
|
313
|
+
| `NO_SUCH_APPROVAL` | `approve_payment`/`reject_payment` named a request with no ticket | already approved, already rejected, or never held. Check `vault_request` for the real state |
|
|
314
|
+
| `TRANSFER_PENDING` | **not a refusal** — the payment is in flight | poll `vault_request` for the final state. Do **not** resubmit: a second attempt with the same id returns `DUPLICATE_REQUEST`, and with a new id it pays twice |
|
|
315
|
+
|
|
316
|
+
**You cannot raise your own limit, and you must not imply otherwise.** Report
|
|
317
|
+
the limit and let the human decide. A budget the agent can talk its way past is
|
|
318
|
+
not a budget.
|
|
319
|
+
|
|
320
|
+
## When the call FAILS instead of being refused
|
|
321
|
+
|
|
322
|
+
Everything above is a **BLOCKED** value with a `reason_code`. The errors below
|
|
323
|
+
are different: the transaction **fails** and you get a panic message, not an
|
|
324
|
+
outcome object. That is deliberate — these happen before the vault can work out
|
|
325
|
+
which node you spend from, so there is no node to attribute a refusal to.
|
|
326
|
+
|
|
327
|
+
Match on the string in the failure. **None of these is retryable**; every one
|
|
328
|
+
needs a human to change something.
|
|
329
|
+
|
|
330
|
+
| failure | what it means | what to do |
|
|
331
|
+
| --- | --- | --- |
|
|
332
|
+
| `NOT_AGENT` | your ACCOUNT is not bound to a node | stop. Ask the human to bind it, or check you are calling as the right account |
|
|
333
|
+
| `NOT_VIRTUAL_AGENT` | your SIGNING KEY is not bound to a node | stop. The key was never bound, or was unbound. Do not retry with a different id — the key is the problem |
|
|
334
|
+
| `BAD_AGENT_SIGNATURE` | the signature does not verify | your own bug: you signed the wrong bytes, or with the wrong key |
|
|
335
|
+
| `NONCE_NOT_FRESH` | this nonce was already used | increment it. Nonces must strictly increase |
|
|
336
|
+
| `AGENT_SIGNATURE_EXPIRED` | the deadline in the signed payload has passed | re-sign with a later deadline; do not reuse the old payload |
|
|
337
|
+
|
|
338
|
+
## Three ways an agent is wired, and how to tell which you are
|
|
339
|
+
|
|
340
|
+
All three land in the same budget tree and obey every limit above. You do not
|
|
341
|
+
choose — the operator configured one.
|
|
342
|
+
|
|
343
|
+
| | you hold | who pays gas | tell |
|
|
344
|
+
| --- | --- | --- | --- |
|
|
345
|
+
| **agent account** | a key on `<name>.<vault>`, pinned to `request_payment` | you, from that account | you call as your own account |
|
|
346
|
+
| **self-submit** | a key **on the vault**, pinned to `request_payment` | you, from that key's own balance | you call as the VAULT; failures say `NOT_VIRTUAL_AGENT` |
|
|
347
|
+
| **virtual agent** | a raw keypair, no account | whoever relays for you | you sign a payload and hand it to a relayer |
|
|
348
|
+
|
|
349
|
+
For **self-submit** the operator did two separate things — bound your public key
|
|
350
|
+
to a node, and installed that key on the vault. Either one missing gives
|
|
351
|
+
`NOT_VIRTUAL_AGENT`, and only the human can fix it.
|
|
352
|
+
|
|
353
|
+
If your key's gas balance runs out you simply stop being able to pay. Say so
|
|
354
|
+
plainly and ask for a top-up; **do not** interpret it as a budget refusal.
|
|
355
|
+
|
|
356
|
+
## Setting up a new vault
|
|
357
|
+
|
|
358
|
+
One command. It creates the account, deploys the contract, builds the budget
|
|
359
|
+
tree, registers the vault on the token, optionally funds it, and writes the MCP
|
|
360
|
+
config for every client it finds.
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npx @floatonnear/create-float --owner you.near --fund 1
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**This creates a vault that cannot pay yet, and that is TWO commands, not one.**
|
|
367
|
+
The wizard builds the vault and its budget tree; it does not bind a spending
|
|
368
|
+
agent. Bind one next:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
npx -p @floatonnear/create-float float-agent --vault float.you.near --owner you.near
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
That installs a NEP-611 gas key restricted to `request_payment` on that vault
|
|
375
|
+
and funds it (~0.12 NEAR), so the agent pays its own gas. Until it runs,
|
|
376
|
+
`vault_pay` answers *"Paying requires NEAR_VAULT_AGENT_ID and a key, and neither
|
|
377
|
+
is set."*
|
|
378
|
+
|
|
379
|
+
**Do NOT reach for `--agents`.** It refuses for any non-zero value. It belonged
|
|
380
|
+
to a retired design that created a sub-account per agent, and those accounts
|
|
381
|
+
could never be emptied — a function-call key cannot sign `DeleteAccount`, so
|
|
382
|
+
10.7752 NEAR is stranded across 18 testnet agents and 0.54434 on mainnet.
|
|
383
|
+
Virtual agents replaced it.
|
|
384
|
+
|
|
385
|
+
The browser path (`vault_create`) differs here: it can bind an agent inside the
|
|
386
|
+
creation transaction via `agent_pk`, so a vault made that way can pay
|
|
387
|
+
immediately. The CLI cannot, because the key has to be installed by the vault
|
|
388
|
+
itself.
|
|
389
|
+
|
|
390
|
+
**Mainnet is the default**, and there is no testnet path worth taking: NEAR
|
|
391
|
+
Intents has no testnet, so a testnet vault cannot make a cross-chain payment at
|
|
392
|
+
all. The wizard prints the full bill and requires the word `mainnet` to be typed
|
|
393
|
+
before it sends anything.
|
|
394
|
+
|
|
395
|
+
Useful flags: `--dry-run` (plan and cost, sends nothing), `--yes`
|
|
396
|
+
(non-interactive), `--fund 5` (move that many tokens into the vault),
|
|
397
|
+
`--token <id>`, `--vault <id>`.
|
|
398
|
+
|
|
399
|
+
**Run `--dry-run` first and show the human the cost.** Every step spends real
|
|
400
|
+
NEAR and several are irreversible.
|
|
401
|
+
|
|
402
|
+
## The user has no vault yet
|
|
403
|
+
|
|
404
|
+
**Use `vault_create` and send them a link. Do not ask for a private key.**
|
|
405
|
+
|
|
406
|
+
Pass `bindAgent: true` to generate your key and bind it in the SAME
|
|
407
|
+
transaction, so one signature creates the vault AND authorises you. The user
|
|
408
|
+
is shown the public key and must confirm it before the wallet opens. Your
|
|
409
|
+
private key is written to `.float/agent-keys.json` — never printed, and never
|
|
410
|
+
something to paste back into chat.
|
|
411
|
+
|
|
412
|
+
**FIRST, CHECK WHICH SITUATION YOU ARE IN.** There are two, and only one of
|
|
413
|
+
them starts with the greeting:
|
|
414
|
+
|
|
415
|
+
| they say | do |
|
|
416
|
+
|---|---|
|
|
417
|
+
| "I want a vault" / nothing exists yet | the greeting below |
|
|
418
|
+
| **"I already created it"**, or a vault id appears | **`vault_await_create({ owner })` — nothing else** |
|
|
419
|
+
|
|
420
|
+
**NEVER CALL `vault_create` TO FIND OUT WHETHER A VAULT EXISTS.** It is a
|
|
421
|
+
creation tool. When the vault is already there it THROWS — "already exists and
|
|
422
|
+
<owner> owns it" — and that is a refusal, not a confirmation. Measured
|
|
423
|
+
2026-11-10: an agent with no adoption path called it anyway, got that error,
|
|
424
|
+
and reported it to the user as "Confirmed ✅". It happened to be right, which
|
|
425
|
+
is worse than being wrong, because nothing distinguished it from the case where
|
|
426
|
+
the throw says the vault belongs to SOMEBODY ELSE.
|
|
427
|
+
|
|
428
|
+
`vault_await_create` takes `owner` alone — it derives the address from the
|
|
429
|
+
factory's own `vault_id_for` and reads `get_config.owner` back. That is the
|
|
430
|
+
tool that answers "is it there, and is it mine", and it answers with four
|
|
431
|
+
outcomes rather than a thrown string.
|
|
432
|
+
|
|
433
|
+
**SEND EXACTLY THIS, AND NOTHING ELSE.** Not a paraphrase, not an expanded
|
|
434
|
+
version, not with the trust model or the storage-stake mechanics added:
|
|
435
|
+
|
|
436
|
+
> Let's set up your Float vault. I just need one quick thing to start:
|
|
437
|
+
>
|
|
438
|
+
> **1. Your NEAR account ID** (e.g. `alice.near`)
|
|
439
|
+
>
|
|
440
|
+
> *I will deploy on Mainnet and bind an agent key, so one signature does
|
|
441
|
+
> everything. Your master vault budget — the ceiling every agent shares — will
|
|
442
|
+
> be 25 USDC/day, max 10 USDC per payment. Say if you want different numbers,
|
|
443
|
+
> or prefer testnet.*
|
|
444
|
+
>
|
|
445
|
+
> Reply with your account ID and I will generate your creation link.
|
|
446
|
+
|
|
447
|
+
**WHY IT IS SHORT, because the instinct to expand is strong and wrong.** The
|
|
448
|
+
first version of this section explained the reasoning instead of dictating the
|
|
449
|
+
output, and the agent produced nine paragraphs: the refundable-stake argument,
|
|
450
|
+
the function-call-key trust model, why Float will never ask for a seed phrase,
|
|
451
|
+
the exact gas reservation. Every sentence true, every sentence unprompted. A
|
|
452
|
+
user who asked for a vault could not find the two questions in it.
|
|
453
|
+
|
|
454
|
+
The reasoning still applies — ASK ABOUT WHAT IS EXPENSIVE TO CHANGE, PROPOSE
|
|
455
|
+
WHAT IS CHEAP — but it governs WHICH questions exist, not how much you say
|
|
456
|
+
about them:
|
|
457
|
+
|
|
458
|
+
- **"Master vault budget" and "every agent shares" are LOAD-BEARING, not
|
|
459
|
+
padding.** The greeting used to say "standard limits of 25 USDC/day", which
|
|
460
|
+
is ambiguous between the whole vault's ceiling and this agent's personal
|
|
461
|
+
allowance — and on day one BOTH readings are true, which is why nobody
|
|
462
|
+
caught it. `daily`/`perTx` become `root_limits`, and the bound agent gets a
|
|
463
|
+
child node whose caps are *cloned from root* (`agent_limits =
|
|
464
|
+
root_limits.clone()`, `contracts/vault/src/lib.rs:322`). The readings
|
|
465
|
+
diverge at agent #2: the master stays 25/day shared, each agent still shows
|
|
466
|
+
its own 25/day, and a sibling draining the pool returns
|
|
467
|
+
`PARENT_DAILY_BUDGET` while the refused agent's own cap looks free. That is
|
|
468
|
+
the product's headline differentiator, and the old wording set the user up
|
|
469
|
+
to meet it as a surprise. It also now matches the dashboard, which has
|
|
470
|
+
always called this "MASTER VAULT BUDGET / Shared by every agent" — one
|
|
471
|
+
concept had two names across two surfaces.
|
|
472
|
+
- **Only the account id is asked**, because nothing can be derived without
|
|
473
|
+
it: `vault_create` returns `vault: null` and `confirm.args: null` without
|
|
474
|
+
one, which silently disables the verification step below.
|
|
475
|
+
- **Network, caps and the agent key are stated, not asked.** Cross-chain only
|
|
476
|
+
exists on mainnet, so mainnet is the answer for almost everyone and offering
|
|
477
|
+
the choice spends the user's attention on a decision you already know. Caps
|
|
478
|
+
are one signed `vault_plan_set_caps` away. The agent key is a line item.
|
|
479
|
+
None of them earns a question — but all of them must still be SAID, so a
|
|
480
|
+
user who wants testnet or different numbers knows to say so.
|
|
481
|
+
|
|
482
|
+
**PASS `network: "mainnet"` EXPLICITLY. ALWAYS.** `vault_create` falls back to
|
|
483
|
+
`cfg.network`, which is `NEAR_VAULT_NETWORK ?? "testnet"` — so omitting it
|
|
484
|
+
after the greeting promised mainnet hands the user a TESTNET link while they
|
|
485
|
+
have been told otherwise. They would sign with play money and believe they had
|
|
486
|
+
a real vault. The greeting states a network; the call must carry the same one,
|
|
487
|
+
and `scripts/check-quoted-defaults.mjs` fails the build if the two disagree.
|
|
488
|
+
|
|
489
|
+
**Hold the rest until it is actionable.** Cost belongs with the link, not the
|
|
490
|
+
greeting — quote `totalRequiredNear` when you hand the link over, since that
|
|
491
|
+
is the moment it means something. The trust model belongs in an answer to "is
|
|
492
|
+
this safe?", not in front of someone who has not asked. If they want the
|
|
493
|
+
detail they will ask, and the rest of this file is here for when they do.
|
|
494
|
+
|
|
495
|
+
**Never state a cap figure you have not read.** The numbers above are the
|
|
496
|
+
page's live defaults; `scripts/check-quoted-defaults.mjs` fails the build if
|
|
497
|
+
this text and `web/index.html` disagree. If the user names their own figures,
|
|
498
|
+
size the per-tx cap ABOVE the payments they intend, never equal to one:
|
|
499
|
+
cross-chain is EXACT_OUTPUT, so the vault is charged more than the payee
|
|
500
|
+
receives and a cap of 5 refuses a round 5.00. A cap is a ceiling, not a
|
|
501
|
+
payment size. Pass figures in HUMAN units (`"100"`, `"25"`) — the page scales
|
|
502
|
+
by `decimals` itself — and pass `token` only when it is not the network
|
|
503
|
+
default.
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
vault_create({
|
|
507
|
+
owner: "alice.near", // asked FIRST — everything else depends on it
|
|
508
|
+
network: "mainnet",
|
|
509
|
+
bindAgent: true,
|
|
510
|
+
daily: "25", perTx: "10", // proposed, not interrogated
|
|
511
|
+
})
|
|
512
|
+
-> vault: alice-near.<factory>
|
|
513
|
+
deposit: <totalRequiredNear> — QUOTE THIS FIELD, never a figure you recall
|
|
514
|
+
url: https://usefloat.xyz/?owner=alice.near&factory=...&daily=25&pertx=10
|
|
515
|
+
confirm: { tool: "vault_await_create", vault: "alice-near.<factory>" }
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Never quote a deposit from memory.** It moves with the contract's byte size
|
|
519
|
+
and with whether an agent is bound, and it has been wrong twice from a
|
|
520
|
+
remembered number. Read `totalRequiredNear` out of the response — a wallet
|
|
521
|
+
checks the balance against the whole amount, gas reservation included, and
|
|
522
|
+
refuses below it.
|
|
523
|
+
|
|
524
|
+
Omit a cap and the page's own default applies (25/10 mainnet, 100/10 testnet) —
|
|
525
|
+
don't substitute one yourself, or two places decide the same number.
|
|
526
|
+
|
|
527
|
+
**A THROW FROM `vault_create` IS NEVER A SUCCESS.** It refuses in three
|
|
528
|
+
different ways and only one of them is benign: the vault is yours, the vault is
|
|
529
|
+
someone ELSE's (the derivation is not injective, so this is real), or the owner
|
|
530
|
+
could not be read at all. Relay which one. Do not compress them into a tick.
|
|
531
|
+
|
|
532
|
+
**Then verify it yourself. Do not ask them to paste the vault id back** — you
|
|
533
|
+
already derived it. When they say they have signed, call `vault_await_create`
|
|
534
|
+
with `confirm.args`. It polls for the account and reads `get_config.owner` back,
|
|
535
|
+
because an account existing at the derived id is *not* proof it is theirs: the
|
|
536
|
+
derivation is not injective, so a collision means whoever created first holds
|
|
537
|
+
the address. Four outcomes, and only one of them is good:
|
|
538
|
+
|
|
539
|
+
| status | what it means |
|
|
540
|
+
|---|---|
|
|
541
|
+
| `LIVE` | exists AND owned by them. Set `NEAR_VAULT_CONTRACT` and carry on. |
|
|
542
|
+
| `NOT_YET` | not signed yet, or cancelled. Ask; call again. Never report success. |
|
|
543
|
+
| `NOT_YOURS` | someone else owns that address. Do not configure anything against it. |
|
|
544
|
+
| `UNVERIFIED` | it exists but could not be read. "I could not check" is not "it is yours". |
|
|
545
|
+
|
|
546
|
+
A label changes the address (`<prefix>-<label>.<factory>`), so pass one only if
|
|
547
|
+
they want a second vault — and `vault_create` reports the labelled id, not the
|
|
548
|
+
bare one.
|
|
549
|
+
|
|
550
|
+
You do the work — the interview, deriving the address from the factory's own
|
|
551
|
+
view, reading the real deposit, checking the name is not already taken, and
|
|
552
|
+
confirming the result — and the human does one thing: opens the link and
|
|
553
|
+
approves it in their own wallet. Nothing that can hurt them touches disk.
|
|
554
|
+
|
|
555
|
+
**Why this and not the CLI.** `create-float` needs a FULL-ACCESS key in
|
|
556
|
+
`~/.near-credentials`, because a `DeployContract` action cannot be signed by a
|
|
557
|
+
function-call key. A full-access key can drain the account, delete it, or
|
|
558
|
+
replace the vault's code. Asking for one in order to set up spending limits
|
|
559
|
+
contradicts the entire product — "a budget an agent cannot talk its way out of"
|
|
560
|
+
is not credible from an agent that just asked for unrestricted access. The
|
|
561
|
+
browser path needs no key at all: the user signs ONE ordinary function call,
|
|
562
|
+
and a function-call key cannot deploy, cannot transfer and cannot add keys.
|
|
563
|
+
|
|
564
|
+
**Never offer to run the CLI on someone else's behalf.** The CLI is for an
|
|
565
|
+
operator on their own machine, with their own key, where no new trust is being
|
|
566
|
+
extended. That is a different situation from a user you are onboarding, and the
|
|
567
|
+
difference is not cosmetic.
|
|
568
|
+
|
|
569
|
+
**The operator path, for your own machine only.** `create-float` needs a NEAR
|
|
570
|
+
account whose full-access key is already in `~/.near-credentials`, and the
|
|
571
|
+
account must hold **3.5 NEAR** for a vault plus one agent (**3.4** for a bare
|
|
572
|
+
vault, plus ~0.12 per agent) along with some of the token the agents will
|
|
573
|
+
spend. Mostly a refundable storage stake rather than a fee, but the wizard
|
|
574
|
+
checks the balance up front and refuses if it is short — so a round "3" is not
|
|
575
|
+
enough. If any of that is missing, stop and say which. Do not improvise around
|
|
576
|
+
it.
|
|
577
|
+
|
|
578
|
+
### What to tell them, in the same message as the link
|
|
579
|
+
|
|
580
|
+
1. what the link does — creates a vault they own, which they approve in their
|
|
581
|
+
wallet
|
|
582
|
+
2. that they need to HOLD **3.51 NEAR** at signing time: a 3.31 deposit plus a
|
|
583
|
+
0.20 gas reservation — or **3.63** if you are binding an agent, which adds
|
|
584
|
+
0.12 of gas-key funding. The reservation is returned seconds later and only
|
|
585
|
+
~0.004 is actually spent, but a wallet checks the balance against the full
|
|
586
|
+
amount and refuses below it. Quote the total, not the net — quoting the net
|
|
587
|
+
is what makes a wallet reject a transaction the user was told they could
|
|
588
|
+
afford.
|
|
589
|
+
|
|
590
|
+
Quote `vault_create`'s own `totalRequiredNear`, never a figure from memory.
|
|
591
|
+
This document said 3.24 until 2026-11-03, because the deposit was priced at
|
|
592
|
+
the factory's on-chain floor (`required_deposit + code storage`) while the
|
|
593
|
+
page actually attaches the code stake plus 0.36 of state headroom. Both are
|
|
594
|
+
real numbers; they differ by 0.26, and the one that matters is the one the
|
|
595
|
+
page puts in front of the wallet.
|
|
596
|
+
3. that almost all of it is a **refundable storage stake** held by their own
|
|
597
|
+
vault, not a fee
|
|
598
|
+
4. that nothing can spend from it until they add an agent and give it a budget
|
|
599
|
+
|
|
600
|
+
If `vault_create` reports no factory for the network, say so and stop. There is
|
|
601
|
+
deliberately no built-in mainnet default: guessing one wrong would send a real
|
|
602
|
+
3.31 NEAR deposit to an account nobody verified.
|
|
603
|
+
|
|
604
|
+
If it reports the vault already exists, that IS their vault — connect to it
|
|
605
|
+
with `NEAR_VAULT_CONTRACT`, do not create a second one.
|
|
606
|
+
|
|
607
|
+
## Asking a human for something
|
|
608
|
+
|
|
609
|
+
When you need a human action, say **what it is for** in the same message as the
|
|
610
|
+
link or command. A bare URL or a bare command from an agent asking for account
|
|
611
|
+
access is indistinguishable from a phishing attempt, and a person who has
|
|
612
|
+
learned to click them is worse off.
|
|
613
|
+
|
|
614
|
+
**Never ask for, display, or store a private key or seed phrase.** They are not
|
|
615
|
+
needed for anything in this document. The wizard writes agent keys to
|
|
616
|
+
`.float/` itself, gitignored; leave them there.
|
|
617
|
+
|
|
618
|
+
## Checking rather than trusting
|
|
619
|
+
|
|
620
|
+
After any change, the explorer shows the truth:
|
|
621
|
+
|
|
622
|
+
```
|
|
623
|
+
https://explorer.near.org/accounts/<vault> # mainnet
|
|
624
|
+
https://explorer.testnet.near.org/accounts/<vault> # testnet
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Worth confirming once per vault: the agent account has exactly **one** key, it
|
|
628
|
+
is function-call only, its `receiver_id` is the vault, and its `method_names`
|
|
629
|
+
is `["request_payment"]`. If any of that is not true, something is wrong with
|
|
630
|
+
the setup and the budget is not actually enforced — say so loudly.
|
package/dist/server.js
CHANGED
|
@@ -58,7 +58,9 @@ import { DEFAULT_GAS_TGAS, planAddNode, planAddRole, planBindVirtualAgent, planU
|
|
|
58
58
|
import { accountExists, rpcForNetwork, RPC_BY_NETWORK, newRequestId, requireAgentKey, viewAccessKeys, viewAccount, viewFunction, } from "./rpc.js";
|
|
59
59
|
import { AGENT_NODE_ID, generateAgentKey } from "./agent-key.js";
|
|
60
60
|
import { resolveKeySource } from "./key-file.js";
|
|
61
|
-
import { dirname } from "node:path";
|
|
61
|
+
import { dirname, resolve } from "node:path";
|
|
62
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
63
|
+
import { fileURLToPath } from "node:url";
|
|
62
64
|
import { listAgents, listRoles, selectRole } from "./roles.js";
|
|
63
65
|
import { label as tokenLabel, tokenMeta } from "./token-meta.js";
|
|
64
66
|
/**
|
|
@@ -223,9 +225,40 @@ const cfg = {
|
|
|
223
225
|
// "configured" and unreachable.
|
|
224
226
|
agentKeysPath: process.env.NEAR_VAULT_AGENT_KEYS ?? ".float/agent-keys.json",
|
|
225
227
|
};
|
|
228
|
+
/**
|
|
229
|
+
* NO VAULT IS THE COMMONEST STATE, SO THE MESSAGE HAS TO BE AN ONRAMP.
|
|
230
|
+
*
|
|
231
|
+
* This said "NEAR_VAULT_CONTRACT is not set" — true, and a dead end. Measured
|
|
232
|
+
* 2026-11-12: an agent asked to pay someone got that from `vault_config` and
|
|
233
|
+
* `vault_tree`, concluded the NEAR rail was unavailable, then searched three
|
|
234
|
+
* unrelated wallets for something that could reach a `.near` address, and
|
|
235
|
+
* finally asked the user to paste a contract id. It never suggested MAKING a
|
|
236
|
+
* vault, because nothing in the failure told it that was possible without one.
|
|
237
|
+
*
|
|
238
|
+
* The WORKFLOW preamble already said "No vault yet? vault_create" and that was
|
|
239
|
+
* not enough: at the moment of failure an agent reads the ERROR, not the
|
|
240
|
+
* instructions it was handed at startup. So the routing belongs here.
|
|
241
|
+
*
|
|
242
|
+
* It is the FIRST thing a new install hits, and the product's answer is
|
|
243
|
+
* genuinely good: `vault_create` needs no key, no existing vault and no
|
|
244
|
+
* configuration. An error that does not say so turns the normal first step
|
|
245
|
+
* into a support question.
|
|
246
|
+
*
|
|
247
|
+
* Names BOTH cases — "you have none" and "you have one this client does not
|
|
248
|
+
* know about" — because the fix differs completely and the message cannot tell
|
|
249
|
+
* which it is.
|
|
250
|
+
*/
|
|
226
251
|
function requireVault() {
|
|
227
|
-
if (!cfg.vault)
|
|
228
|
-
throw new Error("
|
|
252
|
+
if (!cfg.vault) {
|
|
253
|
+
throw new Error("No vault is configured, so there is nothing to read yet. " +
|
|
254
|
+
"If the user does not have a vault, call vault_create — it needs NO key, no existing " +
|
|
255
|
+
"vault and no configuration, and returns a link they approve in their own wallet. That " +
|
|
256
|
+
"is the normal first step, not an error. " +
|
|
257
|
+
"If they DO have one, set NEAR_VAULT_CONTRACT to its address and restart the client " +
|
|
258
|
+
"(servers are read once at startup); vault_create with an `owner` reports the address " +
|
|
259
|
+
"the factory derives for them. " +
|
|
260
|
+
"Do not go looking for another way to send money — no other rail can reach a NEAR vault.");
|
|
261
|
+
}
|
|
229
262
|
return cfg.vault;
|
|
230
263
|
}
|
|
231
264
|
function tokenFor(t) {
|
|
@@ -301,6 +334,12 @@ THREE LANES, and the difference is the whole design:
|
|
|
301
334
|
PLAN vault_plan_*. These RETURN AN UNSIGNED PAYLOAD. This server never signs a policy
|
|
302
335
|
change; the human approves it in their own wallet. Do not describe a plan as done.
|
|
303
336
|
|
|
337
|
+
THE FULL GUIDE IS A RESOURCE: read "float://skill" when you need depth.
|
|
338
|
+
It carries every refusal code and what to do about it, the teardown ORDER (which decides
|
|
339
|
+
whether an agent's gas is recovered or BURNED), and which flows need a human signature.
|
|
340
|
+
Read it before a first vault, before any teardown, and whenever a refusal is not
|
|
341
|
+
self-explanatory. What follows here is only the shape.
|
|
342
|
+
|
|
304
343
|
WORKFLOW
|
|
305
344
|
1. No vault yet? vault_create. It needs no key and no existing vault, and returns a URL the
|
|
306
345
|
user approves in their own wallet. Never ask anyone for a private key or seed phrase.
|
|
@@ -342,7 +381,68 @@ WORTH KNOWING
|
|
|
342
381
|
An implicit account (a 64-character hex id) cannot own a vault at all. Tell the user to use a
|
|
343
382
|
named account rather than retrying.
|
|
344
383
|
Amounts are whole tokens in these tools (1.5 means 1.5 USDC), never base units.`;
|
|
345
|
-
const server = new McpServer({ name: "float", version: "0.
|
|
384
|
+
const server = new McpServer({ name: "float", version: "0.10.0" }, { instructions: INSTRUCTIONS });
|
|
385
|
+
/**
|
|
386
|
+
* THE FULL OPERATING GUIDE, AS AN MCP RESOURCE.
|
|
387
|
+
*
|
|
388
|
+
* SKILL.md is ~34 KB of operational knowledge: the refusal vocabulary, the
|
|
389
|
+
* teardown ORDER (which is the difference between recovering an agent's gas
|
|
390
|
+
* and burning it), what the three lanes mean, which flows need a human
|
|
391
|
+
* signature. `instructions` cannot carry that — it is injected into the
|
|
392
|
+
* client's system prompt, so it has a budget measured in paragraphs.
|
|
393
|
+
*
|
|
394
|
+
* The gap was being filled by accident. On a DEVELOPER machine you can install
|
|
395
|
+
* SKILL.md as a Claude Code skill and it loads on demand; a real user has no
|
|
396
|
+
* repo to install from, and most MCP clients have no skill mechanism at all.
|
|
397
|
+
* So the depth existed for us and not for them, which is the wrong way round.
|
|
398
|
+
*
|
|
399
|
+
* A resource is the mechanism MCP already has for exactly this: the client
|
|
400
|
+
* lists it, reads it when it decides to, and pays no system-prompt cost until
|
|
401
|
+
* then. It ships INSIDE this package, so it cannot drift from the version of
|
|
402
|
+
* the server being run — `prepack` copies the root SKILL.md, the same way
|
|
403
|
+
* create-float does, so there is one source of bytes.
|
|
404
|
+
*
|
|
405
|
+
* NOT A TOOL, deliberately. A `float_guide` tool would work in every client
|
|
406
|
+
* including those without resources, but it spends a slot in a list of 27 and
|
|
407
|
+
* invites the model to call it speculatively. Clients without resource support
|
|
408
|
+
* still get the instructions and the error messages, which is where the
|
|
409
|
+
* load-bearing routing lives.
|
|
410
|
+
*/
|
|
411
|
+
{
|
|
412
|
+
// Resolved from THIS file's location, so it works from dist/ at any depth
|
|
413
|
+
// and does not depend on the process's working directory — a server is
|
|
414
|
+
// started by a client from wherever that client happens to be.
|
|
415
|
+
const guidePath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "SKILL.md");
|
|
416
|
+
server.registerResource("float-guide", "float://skill", {
|
|
417
|
+
title: "Float operating guide",
|
|
418
|
+
description: "The full guide: every refusal code and what to do about it, the three lanes, the " +
|
|
419
|
+
"teardown ORDER that decides whether an agent's gas is recovered or burned, and which " +
|
|
420
|
+
"flows require a human signature. Read this before a first vault, before a teardown, " +
|
|
421
|
+
"or whenever a refusal is not self-explanatory.",
|
|
422
|
+
mimeType: "text/markdown",
|
|
423
|
+
}, async () => {
|
|
424
|
+
/**
|
|
425
|
+
* A MISSING GUIDE IS REPORTED, NOT FAKED. If the copy did not ship, say
|
|
426
|
+
* so and name the cause — an empty resource that reads as "there is no
|
|
427
|
+
* guidance" is worse than an error, because the agent proceeds.
|
|
428
|
+
*/
|
|
429
|
+
if (!existsSync(guidePath)) {
|
|
430
|
+
throw new Error(`the operating guide is missing from this install (${guidePath}). It is copied into ` +
|
|
431
|
+
`the package by prepack; a published tarball should always contain it. Fall back to ` +
|
|
432
|
+
`https://usefloat.xyz/skill.md, and fetch the RAW file — a summariser drops the ` +
|
|
433
|
+
`exact commands.`);
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
contents: [
|
|
437
|
+
{
|
|
438
|
+
uri: "float://skill",
|
|
439
|
+
mimeType: "text/markdown",
|
|
440
|
+
text: readFileSync(guidePath, "utf8"),
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
}
|
|
346
446
|
// ----------------------------------------------------------------- reads
|
|
347
447
|
/**
|
|
348
448
|
* Wait for a create to land, and prove the result belongs to the user.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floatonnear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for the hierarchical budget vault. The agent key signs request_payment only; owner methods emit unsigned payloads for wallet approval.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
"float-mcp": "dist/server.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"dist/"
|
|
12
|
+
"dist/",
|
|
13
|
+
"SKILL.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
|
-
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"build": "node -e \"require('fs').copyFileSync('../../SKILL.md','SKILL.md')\" && tsc -p tsconfig.json",
|
|
16
17
|
"prepack": "npm run build",
|
|
17
18
|
"test": "vitest run",
|
|
18
19
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|