@floatonnear/mcp 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/amounts.d.ts +20 -0
- package/dist/amounts.js +55 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/payloads.d.ts +105 -0
- package/dist/payloads.js +292 -0
- package/dist/quote.d.ts +80 -0
- package/dist/quote.js +159 -0
- package/dist/rpc.d.ts +80 -0
- package/dist/rpc.js +134 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +216 -0
- package/dist/sign.d.ts +69 -0
- package/dist/sign.js +72 -0
- package/package.json +42 -6
- package/README.md +0 -3
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONLY place in this server that touches a key.
|
|
3
|
+
*
|
|
4
|
+
* It signs exactly one method — `request_payment` — with the restricted key
|
|
5
|
+
* issued by `create_agent_account`. That key is cryptographically incapable of
|
|
6
|
+
* doing anything else: its `receiver_id` is the vault, its `method_names` is
|
|
7
|
+
* `["request_payment"]`, and a NEAR function-call key cannot attach a deposit
|
|
8
|
+
* at all. So the worst a compromised copy of this server can do is make
|
|
9
|
+
* payments the vault's on-chain policy already permits.
|
|
10
|
+
*
|
|
11
|
+
* Owner methods are deliberately NOT here. They are emitted as unsigned
|
|
12
|
+
* payloads (see payloads.ts) for the human to approve in their own wallet,
|
|
13
|
+
* which keeps this server out of the trust path for anything that changes
|
|
14
|
+
* policy — the property that distinguishes the design from a custodial agent
|
|
15
|
+
* wallet.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Fee and ordering fields for a Base-routed payment. Decimal strings.
|
|
19
|
+
*
|
|
20
|
+
* There is no `nonce`. The CONTRACT allocates it, because it is part of the
|
|
21
|
+
* signed payload and a caller cannot choose one safely: two payments in the
|
|
22
|
+
* same block read the same chain state, sign for the same nonce, and one is
|
|
23
|
+
* rejected AFTER its NEAR reservation has been consumed. The contract is the
|
|
24
|
+
* only place that sees every Base signature in a serial order.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* The contract's `route` argument.
|
|
28
|
+
*
|
|
29
|
+
* Note what is NOT here: no payload, no destination, no amount. For a
|
|
30
|
+
* cross-chain payment the contract transfers to the quote's deposit address
|
|
31
|
+
* and checks the allowlist against the final payee; nothing a caller passes
|
|
32
|
+
* can redirect where value ends up. If this type ever grows a `payload`
|
|
33
|
+
* field, that property is gone.
|
|
34
|
+
*
|
|
35
|
+
* A cross-chain payment is an ordinary NEP-141 transfer to a solver's
|
|
36
|
+
* deposit address — no derived address, no ETH to fund, no nonce.
|
|
37
|
+
*/
|
|
38
|
+
export type RouteArg = "near_direct" | {
|
|
39
|
+
intents: {
|
|
40
|
+
deposit_address: string;
|
|
41
|
+
quote_deadline_ns: string;
|
|
42
|
+
signature: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export declare function buildRoute(rail: "near"): RouteArg;
|
|
46
|
+
export declare function buildRoute(rail: "intents", intents: IntentsRouteFields): RouteArg;
|
|
47
|
+
/** Fields of a cross-chain route, all produced by the quote signer. */
|
|
48
|
+
export interface IntentsRouteFields {
|
|
49
|
+
depositAddress: string;
|
|
50
|
+
quoteDeadlineNs: string;
|
|
51
|
+
/** base64 ed25519 over borsh(QuoteBinding). See quote.ts. */
|
|
52
|
+
signature: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PayArgs {
|
|
55
|
+
rpcUrl: string;
|
|
56
|
+
vault: string;
|
|
57
|
+
agentId: string;
|
|
58
|
+
agentKey: string;
|
|
59
|
+
requestId: string;
|
|
60
|
+
token: string;
|
|
61
|
+
/** Base units. Already scaled by amounts.toBaseUnits. */
|
|
62
|
+
amount: bigint;
|
|
63
|
+
recipient: string;
|
|
64
|
+
memo: string;
|
|
65
|
+
gasTgas: number;
|
|
66
|
+
/** Omitted means the NEAR rail, matching the contract's `Option<Route>`. */
|
|
67
|
+
route?: RouteArg;
|
|
68
|
+
}
|
|
69
|
+
export declare function sendRequestPayment(a: PayArgs): Promise<unknown>;
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONLY place in this server that touches a key.
|
|
3
|
+
*
|
|
4
|
+
* It signs exactly one method — `request_payment` — with the restricted key
|
|
5
|
+
* issued by `create_agent_account`. That key is cryptographically incapable of
|
|
6
|
+
* doing anything else: its `receiver_id` is the vault, its `method_names` is
|
|
7
|
+
* `["request_payment"]`, and a NEAR function-call key cannot attach a deposit
|
|
8
|
+
* at all. So the worst a compromised copy of this server can do is make
|
|
9
|
+
* payments the vault's on-chain policy already permits.
|
|
10
|
+
*
|
|
11
|
+
* Owner methods are deliberately NOT here. They are emitted as unsigned
|
|
12
|
+
* payloads (see payloads.ts) for the human to approve in their own wallet,
|
|
13
|
+
* which keeps this server out of the trust path for anything that changes
|
|
14
|
+
* policy — the property that distinguishes the design from a custodial agent
|
|
15
|
+
* wallet.
|
|
16
|
+
*/
|
|
17
|
+
import { Account, JsonRpcProvider, KeyPair, KeyPairSigner } from "near-api-js";
|
|
18
|
+
/** Must match AGENT_ALLOWED_METHOD in the contract; pinned by constants.test.ts. */
|
|
19
|
+
const ALLOWED_METHOD = "request_payment";
|
|
20
|
+
export function buildRoute(rail, intents) {
|
|
21
|
+
if (rail === "near")
|
|
22
|
+
return "near_direct";
|
|
23
|
+
if (!intents) {
|
|
24
|
+
throw new Error("rail 'intents' requires depositAddress, quoteDeadlineNs and signature");
|
|
25
|
+
}
|
|
26
|
+
if (!/^[0-9]+$/.test(intents.quoteDeadlineNs)) {
|
|
27
|
+
// U64 crosses the ABI as a decimal string; a JSON number loses precision
|
|
28
|
+
// above 2^53 and a nanosecond deadline is far past that.
|
|
29
|
+
throw new Error(`quoteDeadlineNs must be a decimal string, got ${intents.quoteDeadlineNs}`);
|
|
30
|
+
}
|
|
31
|
+
if (!intents.depositAddress) {
|
|
32
|
+
// An empty deposit address is what a DRY quote returns. Sending one would
|
|
33
|
+
// transfer real funds to "", which the token contract rejects only after
|
|
34
|
+
// the vault has already reserved the budget.
|
|
35
|
+
throw new Error("depositAddress is empty — a dry quote cannot be paid");
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
intents: {
|
|
39
|
+
deposit_address: intents.depositAddress,
|
|
40
|
+
quote_deadline_ns: intents.quoteDeadlineNs,
|
|
41
|
+
signature: intents.signature,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function sendRequestPayment(a) {
|
|
46
|
+
const provider = new JsonRpcProvider({ url: a.rpcUrl });
|
|
47
|
+
const signer = new KeyPairSigner(KeyPair.fromString(a.agentKey));
|
|
48
|
+
const account = new Account(a.agentId, provider, signer);
|
|
49
|
+
return account.callFunction({
|
|
50
|
+
contractId: a.vault,
|
|
51
|
+
methodName: ALLOWED_METHOD,
|
|
52
|
+
args: {
|
|
53
|
+
request_id: a.requestId,
|
|
54
|
+
token: a.token,
|
|
55
|
+
// U128 crosses the ABI as a decimal string; a JSON number would lose
|
|
56
|
+
// precision above 2^53.
|
|
57
|
+
amount: a.amount.toString(),
|
|
58
|
+
recipient: a.recipient,
|
|
59
|
+
memo: a.memo,
|
|
60
|
+
// Omitted entirely when undefined: the contract takes `Option<Route>`
|
|
61
|
+
// and a missing field deserialises to None, which is the NEAR rail.
|
|
62
|
+
...(a.route === undefined ? {} : { route: a.route }),
|
|
63
|
+
},
|
|
64
|
+
gas: BigInt(a.gasTgas) * 10n ** 12n,
|
|
65
|
+
// Hardcoded zero, and not a parameter. A function-call key that attaches
|
|
66
|
+
// any deposit is rejected by the protocol with
|
|
67
|
+
// InvalidAccessKeyError(DepositWithFunctionCall) — so a non-zero value
|
|
68
|
+
// here would not be a security hole, it would simply make every payment
|
|
69
|
+
// fail. Keeping it a constant means that cannot be introduced by a caller.
|
|
70
|
+
deposit: 0n,
|
|
71
|
+
});
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@floatonnear/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@floatonnear/mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
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
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp": "dist/server.js",
|
|
9
|
+
"float-mcp": "dist/server.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"prepack": "npm run build",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
22
|
+
"@noble/curves": "^1.9.7",
|
|
23
|
+
"@noble/hashes": "^1.8.0",
|
|
24
|
+
"near-api-js": "^7.4.0",
|
|
25
|
+
"zod": "^4.6.7"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^24.0.0",
|
|
29
|
+
"typescript": "^5.9.0",
|
|
30
|
+
"vitest": "^5.0.5"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./dist/index.js",
|
|
34
|
+
"./amounts": "./dist/amounts.js",
|
|
35
|
+
"./sign": "./dist/sign.js",
|
|
36
|
+
"./payloads": "./dist/payloads.js",
|
|
37
|
+
"./quote": {
|
|
38
|
+
"types": "./dist/quote.d.ts",
|
|
39
|
+
"import": "./dist/quote.js"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/README.md
DELETED