@floatonnear/mcp 0.9.1 → 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.
Files changed (3) hide show
  1. package/SKILL.md +630 -0
  2. package/dist/server.js +71 -2
  3. 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
  /**
@@ -332,6 +334,12 @@ THREE LANES, and the difference is the whole design:
332
334
  PLAN vault_plan_*. These RETURN AN UNSIGNED PAYLOAD. This server never signs a policy
333
335
  change; the human approves it in their own wallet. Do not describe a plan as done.
334
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
+
335
343
  WORKFLOW
336
344
  1. No vault yet? vault_create. It needs no key and no existing vault, and returns a URL the
337
345
  user approves in their own wallet. Never ask anyone for a private key or seed phrase.
@@ -373,7 +381,68 @@ WORTH KNOWING
373
381
  An implicit account (a 64-character hex id) cannot own a vault at all. Tell the user to use a
374
382
  named account rather than retrying.
375
383
  Amounts are whole tokens in these tools (1.5 means 1.5 USDC), never base units.`;
376
- const server = new McpServer({ name: "float", version: "0.9.1" }, { instructions: INSTRUCTIONS });
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
+ }
377
446
  // ----------------------------------------------------------------- reads
378
447
  /**
379
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.9.1",
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"