botanary-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -0
- package/dist/bin/botanary-mcp.js +21 -0
- package/dist/bin/botanary-mcp.js.map +1 -0
- package/dist/src/api-client.js +42 -0
- package/dist/src/api-client.js.map +1 -0
- package/dist/src/cli.js +79 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/identity/backends/exec.js +34 -0
- package/dist/src/identity/backends/exec.js.map +1 -0
- package/dist/src/identity/backends/file-fallback.js +58 -0
- package/dist/src/identity/backends/file-fallback.js.map +1 -0
- package/dist/src/identity/backends/libsecret.js +57 -0
- package/dist/src/identity/backends/libsecret.js.map +1 -0
- package/dist/src/identity/backends/macos-keychain.js +63 -0
- package/dist/src/identity/backends/macos-keychain.js.map +1 -0
- package/dist/src/identity/backends/select.js +34 -0
- package/dist/src/identity/backends/select.js.map +1 -0
- package/dist/src/identity/backends/types.js +16 -0
- package/dist/src/identity/backends/types.js.map +1 -0
- package/dist/src/identity/backends/windows-dpapi.js +89 -0
- package/dist/src/identity/backends/windows-dpapi.js.map +1 -0
- package/dist/src/identity/fingerprint.js +47 -0
- package/dist/src/identity/fingerprint.js.map +1 -0
- package/dist/src/identity/keypair.js +40 -0
- package/dist/src/identity/keypair.js.map +1 -0
- package/dist/src/identity/pairing-code.js +60 -0
- package/dist/src/identity/pairing-code.js.map +1 -0
- package/dist/src/identity/redact.js +21 -0
- package/dist/src/identity/redact.js.map +1 -0
- package/dist/src/identity/store.js +249 -0
- package/dist/src/identity/store.js.map +1 -0
- package/dist/src/identity/types.js +2 -0
- package/dist/src/identity/types.js.map +1 -0
- package/dist/src/paths.js +22 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/runtime.js +195 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/server.js +36 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/tools.js +320 -0
- package/dist/src/tools.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;AAC3E,CAAC;AAED;sGACsG;AACtG,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;AAClD,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,sBAAsB;IACpC,OAAO,IAAI,CAAC,eAAe,EAAE,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { keccak256, toHex, encodePacked } from 'viem';
|
|
2
|
+
import { defaultCandidates } from './identity/backends/select.js';
|
|
3
|
+
import { PairingCodeManager } from './identity/pairing-code.js';
|
|
4
|
+
import { IdentityStore } from './identity/store.js';
|
|
5
|
+
import { BotanaryApiClient, BotanaryApiError } from './api-client.js';
|
|
6
|
+
/** Re-mint this many ms before the backend's own `expiresAt` - session TTL is an hour
|
|
7
|
+
* (`agent-session.store.ts`), so this is a small, deliberately conservative margin, not a tuned value. */
|
|
8
|
+
const SESSION_REFRESH_SKEW_MS = 30_000;
|
|
9
|
+
/** Production's API host. `api.botanary.xyz` (this package's original default) does not resolve - the
|
|
10
|
+
* deployed API is served at `api.app.botanary.xyz` behind Caddy. Overridable with BOTANARY_API_URL for
|
|
11
|
+
* a local stack (`http://localhost:3000`) or a staging host; an explicit `options.baseUrl` still wins,
|
|
12
|
+
* which is what the tests pass. */
|
|
13
|
+
export const DEFAULT_API_BASE_URL = 'https://api.app.botanary.xyz';
|
|
14
|
+
/**
|
|
15
|
+
* Everything a tool handler (see tools.ts) or the CLI (see cli.ts) needs, wired to one IdentityStore.
|
|
16
|
+
* Owns identity, the pairing HTTP exchange, session minting (cached, re-minted on expiry or a 401), and
|
|
17
|
+
* every authenticated read/build/spend call an agent tool makes. `store.sign()`/`store.signHash()` are
|
|
18
|
+
* the only places the private key is ever touched - everything here composes on top of those, never
|
|
19
|
+
* re-deriving key material.
|
|
20
|
+
*/
|
|
21
|
+
export class AgentRuntime {
|
|
22
|
+
#store;
|
|
23
|
+
#api;
|
|
24
|
+
#apiBaseUrl;
|
|
25
|
+
#identity = null;
|
|
26
|
+
#pairing = null;
|
|
27
|
+
#session = null;
|
|
28
|
+
constructor(store, options) {
|
|
29
|
+
this.#store = store;
|
|
30
|
+
this.#apiBaseUrl = options?.baseUrl ?? process.env.BOTANARY_API_URL ?? DEFAULT_API_BASE_URL;
|
|
31
|
+
this.#api = new BotanaryApiClient(this.#apiBaseUrl, options?.fetch ?? fetch);
|
|
32
|
+
}
|
|
33
|
+
/** Exposed so the CLI's `forget` command and tests can reach identity lifecycle operations
|
|
34
|
+
* (`hasIdentity`/`forget`/`sign`) that intentionally have no MCP tool of their own - see README on
|
|
35
|
+
* why "forget" is a human-run CLI command, not something an agent's own tool call can invoke. */
|
|
36
|
+
get store() {
|
|
37
|
+
return this.#store;
|
|
38
|
+
}
|
|
39
|
+
/** The resolved API base URL this runtime actually talks to (an explicit `options.baseUrl`, else
|
|
40
|
+
* `BOTANARY_API_URL`, else `DEFAULT_API_BASE_URL`) - exposed so `tools.ts` can derive the matching
|
|
41
|
+
* Botanary APP origin for the pairing deep link (`pairingUrl`) without re-deriving or duplicating
|
|
42
|
+
* this same fallback chain. */
|
|
43
|
+
get apiBaseUrl() {
|
|
44
|
+
return this.#apiBaseUrl;
|
|
45
|
+
}
|
|
46
|
+
/** The agent's public identity, creating it on first call if none exists yet. Cached for the life of
|
|
47
|
+
* this runtime (the address/fingerprint cannot change without a forget(), which calls reset()). */
|
|
48
|
+
async identity() {
|
|
49
|
+
if (!this.#identity)
|
|
50
|
+
this.#identity = await this.#store.ensure();
|
|
51
|
+
return this.#identity;
|
|
52
|
+
}
|
|
53
|
+
async pairingCode() {
|
|
54
|
+
const pairing = await this.#ensurePairing();
|
|
55
|
+
return pairing.current();
|
|
56
|
+
}
|
|
57
|
+
async regeneratePairingCode() {
|
|
58
|
+
const pairing = await this.#ensurePairing();
|
|
59
|
+
return pairing.regenerate();
|
|
60
|
+
}
|
|
61
|
+
async pair() {
|
|
62
|
+
const code = await this.pairingCode();
|
|
63
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
64
|
+
const identity = await this.identity();
|
|
65
|
+
const message = `${code.code}|${timestamp}`;
|
|
66
|
+
const messageHash = keccak256(toHex(message));
|
|
67
|
+
await this.#api.post('/v1/agents/pair', {
|
|
68
|
+
code: code.code,
|
|
69
|
+
publicKey: identity.publicKey,
|
|
70
|
+
timestamp,
|
|
71
|
+
signature: await this.#store.signHash(messageHash),
|
|
72
|
+
});
|
|
73
|
+
return code;
|
|
74
|
+
}
|
|
75
|
+
/** Mint a FRESH session unconditionally (bypasses the cache - this is what re-minting after expiry or
|
|
76
|
+
* a 401 calls). Also (re)populates the cache, so an ordinary tool call right after this one reuses it
|
|
77
|
+
* via `ensureSession()` instead of minting again. */
|
|
78
|
+
async mintSession() {
|
|
79
|
+
const identity = await this.identity();
|
|
80
|
+
const { nonce } = await this.#api.post('/v1/agents/session/nonce', {
|
|
81
|
+
address: identity.address,
|
|
82
|
+
});
|
|
83
|
+
const { token, expiresAt } = await this.#api.post('/v1/agents/session', {
|
|
84
|
+
address: identity.address,
|
|
85
|
+
nonce,
|
|
86
|
+
signature: await this.#store.signHash(nonce),
|
|
87
|
+
});
|
|
88
|
+
this.#session = { token, safeUntil: new Date(expiresAt).getTime() - SESSION_REFRESH_SKEW_MS };
|
|
89
|
+
return token;
|
|
90
|
+
}
|
|
91
|
+
/** The token every read/build/spend call below actually uses: the cached session if it is still safe
|
|
92
|
+
* to reuse, otherwise a freshly minted one. This is what makes "every tool call does not re-mint" true
|
|
93
|
+
* without any caller having to think about session lifetime. */
|
|
94
|
+
async ensureSession() {
|
|
95
|
+
if (this.#session && this.#session.safeUntil > Date.now())
|
|
96
|
+
return this.#session.token;
|
|
97
|
+
return this.mintSession();
|
|
98
|
+
}
|
|
99
|
+
/** `GET /agents/me` - this agent's own identity, its bound account, and its live grant if any (or an
|
|
100
|
+
* honest `grant: null`). Re-mints the session once and retries on a 401 (a token that expired between
|
|
101
|
+
* `ensureSession()`'s check and the request landing, or one the backend otherwise no longer honors). */
|
|
102
|
+
async me() {
|
|
103
|
+
return this.#withSession((token) => this.#api.get('/v1/agents/me', token));
|
|
104
|
+
}
|
|
105
|
+
/** `GET /balance` - the account address and its token/holdings breakdown, exactly as the backend
|
|
106
|
+
* reports it. */
|
|
107
|
+
async getBalance() {
|
|
108
|
+
return this.#withSession((token) => this.#api.get('/v1/balance', token));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `POST /delegations/{delegationId}/actions` - an UNSIGNED delegated-action op, built in the
|
|
112
|
+
* delegation's OWN Smart Sessions nonce lane with fixed native gas (the session validator cannot
|
|
113
|
+
* gas-estimate a stub signature). This is the ONLY build lane a session-key signature can validate:
|
|
114
|
+
* `POST /money/send/build` (this package's earlier, WRONG choice) builds in the Kernel account's ROOT
|
|
115
|
+
* nonce lane for the OWNER's key - an op built there, then signed with this agent's session key and
|
|
116
|
+
* wrapped in `spendUnderGrant`'s USE envelope, would route validation to the root ECDSA validator,
|
|
117
|
+
* which cannot parse a `(bytes1, bytes32, bytes)` envelope as a 65-byte ECDSA signature. `delegationId`
|
|
118
|
+
* is this agent's OWN grant id (`GET /agents/me`'s `grant.id` - never invented, never another agent's:
|
|
119
|
+
* the backend refuses that with a named 403, see the workspace's delegation.controller.ts). `amount` is
|
|
120
|
+
* DISPLAY units (e.g. `12.5` for 12.5 USDC), matching `DelegatedActionInput.amount` - never wei.
|
|
121
|
+
* `token`, when given, disambiguates which of the delegation's budgeted tokens to move - required
|
|
122
|
+
* whenever the grant budgets more than one stablecoin (omitted, the backend defaults to the first).
|
|
123
|
+
*/
|
|
124
|
+
async buildDelegatedAction(params) {
|
|
125
|
+
const body = {
|
|
126
|
+
recipient: params.recipient,
|
|
127
|
+
amount: params.amount,
|
|
128
|
+
...(params.token ? { token: params.token } : {}),
|
|
129
|
+
};
|
|
130
|
+
return this.#withSession((token) => this.#api.post(`/v1/delegations/${params.delegationId}/actions`, body, token));
|
|
131
|
+
}
|
|
132
|
+
/** `POST /agents/requests` - the "ask" half of the design (Flow 7d step 5): raise a request naming the
|
|
133
|
+
* calls that were declined and why, returning which bound was crossed and the deadline exactly as the
|
|
134
|
+
* backend computed them. */
|
|
135
|
+
async raiseRequest(calls, reason) {
|
|
136
|
+
return this.#withSession((token) => this.#api.post('/v1/agents/requests', { calls, reason }, token));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Sign a built op as the grant's session key and relay it. The USE envelope is EXACTLY the three lines
|
|
140
|
+
* `botanary-fe/src/lib/wallet/signing.ts:49` already uses - `SmartSessionMode.USE` is `0x00`, and the
|
|
141
|
+
* packing is `(bytes1, bytes32, bytes)`. Reproduced rather than re-derived: a second encoding of the same
|
|
142
|
+
* envelope is a second thing to get byte-exact, and only one of them would be tested.
|
|
143
|
+
*
|
|
144
|
+
* `intentType` is the SAME value the build response itself reported (`UserOpBuildResult.intentType`,
|
|
145
|
+
* `'delegated_action'` for `buildDelegatedAction`'s builds) - never assumed or hardcoded here, so the
|
|
146
|
+
* relay always describes what was actually built, which is what `OrchestratorService.submit` uses to
|
|
147
|
+
* label the audit trail entry.
|
|
148
|
+
*
|
|
149
|
+
* Returns the backend's OWN relay response (status/txHash/error, whatever `POST /userops` actually
|
|
150
|
+
* said) rather than nothing - a caller (`propose_payment`) reporting success has to report what the
|
|
151
|
+
* backend reported, never a string this package made up.
|
|
152
|
+
*/
|
|
153
|
+
async spendUnderGrant(userOp, userOpHash, permissionId, token, intentType) {
|
|
154
|
+
const raw = await this.#store.signHash(userOpHash);
|
|
155
|
+
const signature = encodePacked(['bytes1', 'bytes32', 'bytes'], ['0x00', permissionId, raw]);
|
|
156
|
+
return this.#api.post('/v1/userops', { userOp: { ...userOp, signature }, userOpHash, intentType }, token);
|
|
157
|
+
}
|
|
158
|
+
/** Drops cached identity/pairing/session state. Call after `store.forget()` so the next call re-derives
|
|
159
|
+
* from storage (and, in the ordinary case, creates a brand new identity) instead of continuing to serve
|
|
160
|
+
* an in-memory identity - or a session minted for it - whose key is now gone. */
|
|
161
|
+
reset() {
|
|
162
|
+
this.#identity = null;
|
|
163
|
+
this.#pairing = null;
|
|
164
|
+
this.#session = null;
|
|
165
|
+
}
|
|
166
|
+
async #ensurePairing() {
|
|
167
|
+
if (!this.#pairing) {
|
|
168
|
+
const identity = await this.identity();
|
|
169
|
+
this.#pairing = new PairingCodeManager(identity.fingerprint);
|
|
170
|
+
}
|
|
171
|
+
return this.#pairing;
|
|
172
|
+
}
|
|
173
|
+
/** Run `fn` against a valid session token; on a 401 (the cached token expired or was revoked
|
|
174
|
+
* server-side between calls), mint exactly one fresh session and retry once. Any other failure - a
|
|
175
|
+
* 4xx policy decline, a 5xx - is the backend's own answer and is never swallowed or retried. */
|
|
176
|
+
async #withSession(fn) {
|
|
177
|
+
const token = await this.ensureSession();
|
|
178
|
+
try {
|
|
179
|
+
return await fn(token);
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
if (e instanceof BotanaryApiError && e.status === 401) {
|
|
183
|
+
const fresh = await this.mintSession();
|
|
184
|
+
return await fn(fresh);
|
|
185
|
+
}
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** The real wiring: platform-appropriate KeyBackend candidates, real filesystem paths. Everything
|
|
191
|
+
* impure lives in this one function so tests never need to call it. */
|
|
192
|
+
export function createDefaultRuntime() {
|
|
193
|
+
return new AgentRuntime(new IdentityStore(defaultCandidates()));
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { createTools } from './tools.js';
|
|
4
|
+
// Same low-level Server API as tools/agent-harness/mcp/server.mjs, and for the same documented reason
|
|
5
|
+
// (see that file): @modelcontextprotocol/sdk@1.29.0's ergonomic McpServer/zod-schema helper is not
|
|
6
|
+
// required here, and the schema-object registration form (`setRequestHandler(<Schema>, handler)`) is
|
|
7
|
+
// proven working in this exact repo at this exact SDK version. Using it keeps this package's only
|
|
8
|
+
// runtime dependency on the SDK to the same shape as the sibling tool, and avoids a direct dependency
|
|
9
|
+
// on zod purely to describe tool inputs (the three tools below all take no arguments).
|
|
10
|
+
const PACKAGE_VERSION = '0.1.0';
|
|
11
|
+
function errorResult(message) {
|
|
12
|
+
return { content: [{ type: 'text', text: `ERROR: ${message}` }], isError: true };
|
|
13
|
+
}
|
|
14
|
+
/** Builds the MCP server and wires its two handlers to `runtime`'s tools. Split out from bin/ so tests
|
|
15
|
+
* can drive it (via server.setRequestHandler-registered handlers, exercised through the SDK's own
|
|
16
|
+
* request dispatch - see test/server.spec.ts) without spawning a process or a real stdio transport. */
|
|
17
|
+
export function createServer(runtime) {
|
|
18
|
+
const tools = createTools(runtime);
|
|
19
|
+
const server = new Server({ name: 'botanary-mcp', version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
20
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
21
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
|
|
22
|
+
}));
|
|
23
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
24
|
+
const tool = tools.find((t) => t.name === request.params.name);
|
|
25
|
+
if (!tool)
|
|
26
|
+
return errorResult(`unknown tool "${request.params.name}"`);
|
|
27
|
+
try {
|
|
28
|
+
return await tool.run(request.params.arguments ?? {});
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return errorResult(e instanceof Error ? e.message : String(e));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAEnG,OAAO,EAAE,WAAW,EAAmB,MAAM,YAAY,CAAC;AAE1D,sGAAsG;AACtG,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AAClG,sGAAsG;AACtG,uFAAuF;AAEvF,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACnF,CAAC;AAED;;wGAEwG;AACxG,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAE/G,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;KACpG,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI;YAAE,OAAO,WAAW,CAAC,iBAAiB,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QACvE,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,WAAW,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return { content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] };
|
|
3
|
+
}
|
|
4
|
+
function emptySchema() {
|
|
5
|
+
return { type: 'object', properties: {} };
|
|
6
|
+
}
|
|
7
|
+
function identityView(identity) {
|
|
8
|
+
return {
|
|
9
|
+
address: identity.address,
|
|
10
|
+
publicKey: identity.publicKey,
|
|
11
|
+
fingerprint: identity.fingerprint,
|
|
12
|
+
createdAt: identity.createdAt,
|
|
13
|
+
keyStorage: identity.backend,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Botanary's production app origin - the fallback whenever the configured API base URL isn't one of
|
|
17
|
+
* the two shapes `deriveAppOrigin` recognizes below. */
|
|
18
|
+
const PROD_APP_ORIGIN = 'https://app.botanary.xyz';
|
|
19
|
+
/** Derive the Botanary APP origin (what an owner's browser visits) from the API base URL this runtime
|
|
20
|
+
* is actually configured against, so the pairing deep link (`pairingUrl` below) lands on a LOCAL FE
|
|
21
|
+
* when this server is pointed at a local stack, rather than always hardcoding production. Recognizes
|
|
22
|
+
* two shapes:
|
|
23
|
+
* - Production/staging: the API host is `api.` + the app host (`api.app.botanary.xyz` ->
|
|
24
|
+
* `app.botanary.xyz`) - strip that one label.
|
|
25
|
+
* - The local docker-compose stack (`../../docker-compose.yml`), which pairs BE :3000 with FE :3001.
|
|
26
|
+
* Anything else - an unparseable URL, or a host this function does not recognize - falls back to the
|
|
27
|
+
* production app origin: a deep link that opens the wrong (but real) app is safer than one built from
|
|
28
|
+
* a guess that resolves nowhere. */
|
|
29
|
+
export function deriveAppOrigin(apiBaseUrl) {
|
|
30
|
+
let url;
|
|
31
|
+
try {
|
|
32
|
+
url = new URL(apiBaseUrl);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return PROD_APP_ORIGIN;
|
|
36
|
+
}
|
|
37
|
+
if (url.hostname.startsWith('api.')) {
|
|
38
|
+
return `${url.protocol}//${url.hostname.slice('api.'.length)}`;
|
|
39
|
+
}
|
|
40
|
+
if ((url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.port === '3000') {
|
|
41
|
+
return `${url.protocol}//${url.hostname}:3001`;
|
|
42
|
+
}
|
|
43
|
+
return PROD_APP_ORIGIN;
|
|
44
|
+
}
|
|
45
|
+
function pairingView(pairing, identity, apiBaseUrl) {
|
|
46
|
+
const pairingUrl = `${deriveAppOrigin(apiBaseUrl)}/mandates?pair=${pairing.code}`;
|
|
47
|
+
return {
|
|
48
|
+
code: pairing.code,
|
|
49
|
+
address: identity.address,
|
|
50
|
+
fingerprint: pairing.fingerprint,
|
|
51
|
+
issuedAt: pairing.issuedAt,
|
|
52
|
+
expiresAt: pairing.expiresAt,
|
|
53
|
+
pairingUrl,
|
|
54
|
+
instructions: 'In Botanary, open Connected agents and enter this code, then give the agent a name - or open ' +
|
|
55
|
+
`${pairingUrl} to go straight there with the code already filled in.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Plain-English rendering of `GET /agents/me`'s `grant` field - built entirely from backend-reported
|
|
59
|
+
* data (never a fabricated number), shared by `what_may_i_do` and `propose_payment`'s refusal path so
|
|
60
|
+
* the two tools never describe the same grant two different ways. */
|
|
61
|
+
function describeGrant(self) {
|
|
62
|
+
if (!self.grant) {
|
|
63
|
+
return (`Nothing yet - this agent has not been granted anything. It can read balances and draft a ` +
|
|
64
|
+
`payment (propose_payment will explain exactly why it refuses to relay one), but it cannot spend. ` +
|
|
65
|
+
`Ask your owner to create a grant naming this agent's address (${self.address}) in Botanary, or ` +
|
|
66
|
+
`call request_approval to ask for one specific payment right now.`);
|
|
67
|
+
}
|
|
68
|
+
const g = self.grant;
|
|
69
|
+
const meters = g.spentToDate
|
|
70
|
+
.map((m) => `${m.remaining} ${m.token.symbol} remaining of ${m.limit}${m.expiresAt ? ` (until ${m.expiresAt})` : ''}`)
|
|
71
|
+
.join('; ');
|
|
72
|
+
return `Grant status: ${g.status}. ${g.humanSummary}${meters ? ` Remaining: ${meters}.` : ''}`;
|
|
73
|
+
}
|
|
74
|
+
/** The spend meter for `tokenSymbol` on this grant, or undefined when the grant budgets no such token -
|
|
75
|
+
* `spentToDate` is "one meter per budget, in the SAME order as policySet.budgets" (backend's own
|
|
76
|
+
* invariant, `Delegation.spentToDate`'s doc comment), which is what makes this a plain index lookup. */
|
|
77
|
+
function findBudgetMeter(grant, tokenSymbol) {
|
|
78
|
+
const idx = grant.policySet.budgets.findIndex((b) => b.token.symbol.toUpperCase() === tokenSymbol.toUpperCase());
|
|
79
|
+
return idx === -1 ? undefined : grant.spentToDate[idx];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The MCP tool surface: report identity, report/regenerate the pairing code, pair with the backend,
|
|
83
|
+
* report who I am, read balance, propose a payment under a grant, ask for approval when one is out of
|
|
84
|
+
* grant, and read this agent's own bounds in plain terms.
|
|
85
|
+
*
|
|
86
|
+
* `forget` is deliberately NOT here - see README ("Why `forget` has no MCP tool"). An agent that can
|
|
87
|
+
* erase its own identity on request is one an injected instruction can silence; it stays a human-run CLI
|
|
88
|
+
* command (`botanary-mcp forget`).
|
|
89
|
+
*
|
|
90
|
+
* No tool in this file returns a hardcoded success string. Every one either surfaces the backend's own
|
|
91
|
+
* response verbatim, or - for `propose_payment`'s refusal path - a message built from backend-reported
|
|
92
|
+
* numbers (never invented). A tool that cannot do what its description says fails loudly (an MCP error
|
|
93
|
+
* result carrying the real reason), never quietly.
|
|
94
|
+
*/
|
|
95
|
+
export function createTools(runtime) {
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
name: 'get_identity',
|
|
99
|
+
description: "Report this agent's Botanary identity: its address, public key, short fingerprint, when its " +
|
|
100
|
+
'key was created, and which local backend holds the private key (the OS keychain, or the 0600 ' +
|
|
101
|
+
'file fallback if none is available). Generates the key on first call if none exists yet - the ' +
|
|
102
|
+
'key is generated on this machine and never leaves it. Never returns the private key. Having an ' +
|
|
103
|
+
'identity grants nothing by itself: this agent can do nothing until its owner explicitly grants ' +
|
|
104
|
+
'it something inside Botanary.',
|
|
105
|
+
inputSchema: emptySchema(),
|
|
106
|
+
run: async () => text(identityView(await runtime.identity())),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'get_pairing_code',
|
|
110
|
+
description: 'Get the short pairing code to show the user so they can connect this agent: in Botanary, open ' +
|
|
111
|
+
'"Connected agents" and enter the code, then name the agent. The SAME code is returned on ' +
|
|
112
|
+
'repeated calls until it expires (Flow 7d: the agent shows the code and waits for the owner) - ' +
|
|
113
|
+
'call this again after expiresAt for a fresh one. The code only IDENTIFIES this agent - it is ' +
|
|
114
|
+
'derived from its public key plus a fresh single-use nonce, and is not a credential. Completing ' +
|
|
115
|
+
'a connection additionally requires this agent to sign with its private key, which never leaves ' +
|
|
116
|
+
'this machine, so intercepting the code alone grants nothing (see the package README).',
|
|
117
|
+
inputSchema: emptySchema(),
|
|
118
|
+
run: async () => {
|
|
119
|
+
const [pairing, identity] = await Promise.all([runtime.pairingCode(), runtime.identity()]);
|
|
120
|
+
return text(pairingView(pairing, identity, runtime.apiBaseUrl));
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'regenerate_pairing_code',
|
|
125
|
+
description: 'Immediately invalidate the current pairing code and mint a fresh one - e.g. if the user thinks ' +
|
|
126
|
+
'the previous code may have been seen by someone else, or it already expired. Use ' +
|
|
127
|
+
'get_pairing_code for the ordinary "show me the code" case; this is only for forcing a new one ' +
|
|
128
|
+
'before it would otherwise expire on its own.',
|
|
129
|
+
inputSchema: emptySchema(),
|
|
130
|
+
run: async () => {
|
|
131
|
+
const [pairing, identity] = await Promise.all([runtime.regeneratePairingCode(), runtime.identity()]);
|
|
132
|
+
return text(pairingView(pairing, identity, runtime.apiBaseUrl));
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'pair',
|
|
137
|
+
description: 'Pair this agent with the Botanary backend: sign a proof over the pairing code and timestamp, send it to the ' +
|
|
138
|
+
'backend, and receive confirmation. After this completes, the owner can claim the pairing by entering the code ' +
|
|
139
|
+
'and giving the agent a name in the Botanary app. The agent signs with its private key (never sent to the server), ' +
|
|
140
|
+
'so a captured code alone is worthless.',
|
|
141
|
+
inputSchema: emptySchema(),
|
|
142
|
+
run: async () => {
|
|
143
|
+
const pairing = await runtime.pair();
|
|
144
|
+
const identity = await runtime.identity();
|
|
145
|
+
return text(pairingView(pairing, identity, runtime.apiBaseUrl));
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'whoami',
|
|
150
|
+
description: 'Report this agent to the Botanary backend: calls GET /agents/me to verify this agent is a LIVE ' +
|
|
151
|
+
'connected agent and return its account and (if any) live grant, straight from the backend. If ' +
|
|
152
|
+
"this agent was never claimed, or was disconnected, the call fails honestly - it does not " +
|
|
153
|
+
'report a fabricated "connected".',
|
|
154
|
+
inputSchema: emptySchema(),
|
|
155
|
+
run: async () => text(await runtime.me()),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'get_balance',
|
|
159
|
+
description: "Read this account's balance from the Botanary backend (GET /balance) using this agent's " +
|
|
160
|
+
'session - requires the owner to have already claimed this agent (see get_pairing_code/pair). ' +
|
|
161
|
+
'Returns the account address and token balances exactly as the backend reports them.',
|
|
162
|
+
inputSchema: emptySchema(),
|
|
163
|
+
run: async () => text(await runtime.getBalance()),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'propose_payment',
|
|
167
|
+
description: "Build and relay a payment under this agent's OWN grant. Reads the grant's bounds and " +
|
|
168
|
+
'permissionId from the backend (GET /agents/me) FIRST: when there is no grant, the grant is not ' +
|
|
169
|
+
'active, it is on a different chain, or the amount would exceed its remaining budget or ' +
|
|
170
|
+
"per-action cap, this refuses with the backend's own numbers and points at request_approval " +
|
|
171
|
+
'instead of attempting anything - it never pretends to send a payment it did not send. Otherwise ' +
|
|
172
|
+
'it builds the delegated action (POST /delegations/{delegationId}/actions - the ONLY build lane ' +
|
|
173
|
+
"a session-key signature can validate; never the owner-lane /money/send/build) against THIS " +
|
|
174
|
+
"agent's own grant id, then relays it signed with the grant's own session key (POST /userops), " +
|
|
175
|
+
"returning the backend's real relay result. This client-side bounds check is a convenience, " +
|
|
176
|
+
'never the check - the chain is what actually enforces the grant.',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
recipient: { type: 'string', description: 'Recipient address (0x...)' },
|
|
181
|
+
amount: {
|
|
182
|
+
type: 'number',
|
|
183
|
+
description: 'Amount in DISPLAY units (e.g. 12.5 for 12.5 USDC) - never wei, never a hex string.',
|
|
184
|
+
},
|
|
185
|
+
tokenSymbol: { type: 'string', description: 'Token symbol, e.g. "USDC".' },
|
|
186
|
+
chainId: { type: 'number', description: "Chain ID to send on - must match the grant's own chain." },
|
|
187
|
+
},
|
|
188
|
+
required: ['recipient', 'amount', 'tokenSymbol', 'chainId'],
|
|
189
|
+
},
|
|
190
|
+
run: async (args) => {
|
|
191
|
+
const recipient = String(args.recipient);
|
|
192
|
+
const amount = Number(args.amount);
|
|
193
|
+
const tokenSymbol = String(args.tokenSymbol);
|
|
194
|
+
const chainId = Number(args.chainId);
|
|
195
|
+
const self = await runtime.me();
|
|
196
|
+
if (!self.grant) {
|
|
197
|
+
return text({
|
|
198
|
+
proposed: false,
|
|
199
|
+
reason: 'no_grant',
|
|
200
|
+
message: `${describeGrant(self)} Nothing was built or sent.`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const grant = self.grant;
|
|
204
|
+
if (grant.status !== 'active') {
|
|
205
|
+
return text({
|
|
206
|
+
proposed: false,
|
|
207
|
+
reason: 'grant_not_active',
|
|
208
|
+
message: `This grant is "${grant.status}", not active, so it cannot be spent under. Nothing was ` +
|
|
209
|
+
'built or sent. Use request_approval to ask the owner directly.',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (grant.chainId !== chainId) {
|
|
213
|
+
return text({
|
|
214
|
+
proposed: false,
|
|
215
|
+
reason: 'wrong_chain',
|
|
216
|
+
message: `This grant is on chain ${grant.chainId} (${grant.chain}), not chain ${chainId}. Nothing ` +
|
|
217
|
+
'was built or sent. Use request_approval if this payment genuinely needs that chain.',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const meter = findBudgetMeter(grant, tokenSymbol);
|
|
221
|
+
if (!meter) {
|
|
222
|
+
return text({
|
|
223
|
+
proposed: false,
|
|
224
|
+
reason: 'token_not_budgeted',
|
|
225
|
+
message: `This grant has no budget for ${tokenSymbol}. Budgeted tokens: ` +
|
|
226
|
+
`${grant.policySet.budgets.map((b) => b.token.symbol).join(', ') || 'none'}. Nothing was ` +
|
|
227
|
+
'built or sent. Use request_approval instead.',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (amount > meter.remaining) {
|
|
231
|
+
return text({
|
|
232
|
+
proposed: false,
|
|
233
|
+
reason: 'over_budget',
|
|
234
|
+
message: `This grant has ${meter.remaining} ${tokenSymbol} remaining (of ${meter.limit} total); ` +
|
|
235
|
+
`${amount} ${tokenSymbol} would exceed it. Nothing was built or sent. Use request_approval ` +
|
|
236
|
+
'to ask the owner for this specific payment.',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const perActionMax = grant.policySet.perActionMax;
|
|
240
|
+
if (perActionMax && perActionMax.token.symbol.toUpperCase() === tokenSymbol.toUpperCase() && amount > perActionMax.amount) {
|
|
241
|
+
return text({
|
|
242
|
+
proposed: false,
|
|
243
|
+
reason: 'over_per_action_max',
|
|
244
|
+
message: `This grant caps a single action at ${perActionMax.amount} ${tokenSymbol}; ${amount} ` +
|
|
245
|
+
'exceeds it. Nothing was built or sent. Use request_approval instead.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// Within the client-read bounds - build the delegated action AGAINST THIS AGENT'S OWN GRANT ID
|
|
249
|
+
// (grant.id, from GET /agents/me - never invented, never another agent's), then relay it signed
|
|
250
|
+
// with the grant's own session key. This check is a convenience, never the check: Smart Sessions'
|
|
251
|
+
// own on-chain policies are what actually enforce the grant when the relay lands, and the backend
|
|
252
|
+
// itself refuses (403, named reason) a delegation id that does not name this agent.
|
|
253
|
+
const build = await runtime.buildDelegatedAction({
|
|
254
|
+
delegationId: grant.id,
|
|
255
|
+
recipient,
|
|
256
|
+
amount,
|
|
257
|
+
token: tokenSymbol,
|
|
258
|
+
});
|
|
259
|
+
const token = await runtime.ensureSession();
|
|
260
|
+
const relay = await runtime.spendUnderGrant(build.userOp, build.userOpHash, grant.permissionId, token, build.intentType);
|
|
261
|
+
return text({
|
|
262
|
+
proposed: true,
|
|
263
|
+
userOpHash: build.userOpHash,
|
|
264
|
+
relay,
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'request_approval',
|
|
270
|
+
description: 'Ask the owner to approve a specific action this agent could not do under its own grant ' +
|
|
271
|
+
'(POST /agents/requests): names the exact calls and why, and returns which bound was crossed and ' +
|
|
272
|
+
'the deadline exactly as the backend computed them - the request lane propose_payment points at ' +
|
|
273
|
+
'when it refuses. The owner approves it as THEIR OWN action, never as this agent spending under ' +
|
|
274
|
+
'a grant (§5-43) - approving happens in Botanary, not through this tool.',
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
properties: {
|
|
278
|
+
calls: {
|
|
279
|
+
type: 'array',
|
|
280
|
+
items: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
to: { type: 'string', description: 'The target contract address.' },
|
|
284
|
+
data: { type: 'string', description: 'The encoded call data (hex, with 0x prefix).' },
|
|
285
|
+
value: { type: 'string', description: 'Native wei to send, as a decimal string. Usually "0".' },
|
|
286
|
+
chainId: { type: 'number', description: 'The chain ID where this call should execute.' },
|
|
287
|
+
},
|
|
288
|
+
required: ['to', 'data', 'value', 'chainId'],
|
|
289
|
+
},
|
|
290
|
+
description: 'The exact calls that were declined - the same shape a build response already carries.',
|
|
291
|
+
},
|
|
292
|
+
reason: { type: 'string', description: 'A human-readable reason the owner will see, e.g. "Pay the invoice".' },
|
|
293
|
+
},
|
|
294
|
+
required: ['calls', 'reason'],
|
|
295
|
+
},
|
|
296
|
+
run: async (args) => {
|
|
297
|
+
const calls = (Array.isArray(args.calls) ? args.calls : []);
|
|
298
|
+
const reason = String(args.reason);
|
|
299
|
+
return text(await runtime.raiseRequest(calls, reason));
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'what_may_i_do',
|
|
304
|
+
description: "Read this agent's own bounds in plain terms (GET /agents/me), WITHOUT hitting them: either the " +
|
|
305
|
+
"live grant's sentence, remaining budget and status, or an honest \"nothing yet\" when the owner " +
|
|
306
|
+
'has not granted this agent anything. A convenience read for whoever built this agent, never the ' +
|
|
307
|
+
'check - the chain enforces the real bound regardless of what this reports.',
|
|
308
|
+
inputSchema: emptySchema(),
|
|
309
|
+
run: async () => {
|
|
310
|
+
const self = await runtime.me();
|
|
311
|
+
return text({
|
|
312
|
+
agent: { address: self.address, name: self.name, accountId: self.accountId },
|
|
313
|
+
grant: self.grant,
|
|
314
|
+
summary: describeGrant(self),
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "botanary-mcp",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Botanary's agent connector: a local MCP server that lets an outside coding agent (Claude Code, Codex, Cursor, or a custom build) generate its own signing key, keep it in the OS keychain, pair with a Botanary account, read its balance, and spend under whatever grant its owner gave it - refusing honestly, never fabricating success, the moment it has none.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/MorcaLabs/botanary-be.git",
|
|
10
|
+
"directory": "tools/botanary-mcp"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/MorcaLabs/botanary-be/tree/main/tools/botanary-mcp#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/MorcaLabs/botanary-be/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"botanary",
|
|
20
|
+
"agent"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"botanary-mcp": "dist/bin/botanary-mcp.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.0.0 <23"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"prepare": "tsc -p tsconfig.build.json",
|
|
35
|
+
"dev": "node --watch -r @swc-node/register bin/botanary-mcp.ts",
|
|
36
|
+
"start": "node dist/bin/botanary-mcp.js",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
43
|
+
"viem": "^2.54.6"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@swc-node/register": "^1.10.9",
|
|
47
|
+
"@types/node": "^22.10.5",
|
|
48
|
+
"typescript": "^5.7.3",
|
|
49
|
+
"vitest": "^2.1.8"
|
|
50
|
+
}
|
|
51
|
+
}
|