@fidesbuild/mcp-server 0.6.0 → 0.8.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/README.md +5 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.js +327 -25
- package/package.json +16 -11
- package/src/server.ts +968 -0
package/README.md
CHANGED
|
@@ -38,7 +38,11 @@ Claude Code and other MCP apps need [Node](https://nodejs.org) 20.10 or later. C
|
|
|
38
38
|
| --- | --- |
|
|
39
39
|
| `fides_setup` | A link to the FIDES app with this agent filled in |
|
|
40
40
|
| `fides_status` | The FIDES: active or not, budget spent and remaining, FIDES Account balance, allowed contracts, expiry, agent address |
|
|
41
|
-
| `fides_plan` | Builds transfers and contract calls into one plan, checks each against the FIDES, groups them, simulates. Sends nothing |
|
|
41
|
+
| `fides_plan` | Builds transfers and contract calls into one plan, checks each against the FIDES, groups them, simulates. Sends nothing. Calls any verified contract by function name and plain values, encoded from its verified source |
|
|
42
|
+
| `fides_inspect_contract` | Reads any contract on the chain and lists what an agent could call, with risky functions flagged |
|
|
43
|
+
| `fides_read_contract` | Reads a value from any verified contract by function name, such as a market or a balance. Free |
|
|
44
|
+
| `fides_check_api` | Reads a paid API's price without paying, and says whether a FIDES can pay it |
|
|
45
|
+
| `fides_swap` | Swaps on Uniswap inside the trading mandate the owner signed |
|
|
42
46
|
| `fides_execute` | Executes a plan by its id. Only the actions inside the FIDES are sent |
|
|
43
47
|
| `fides_pay_request` | Buys from a paid API over x402: checks the price against the service's cap in the FIDES, pays from the FIDES, returns the response |
|
|
44
48
|
| `fides_receipts` | Every transaction executed under the FIDES, with explorer links |
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { type Hex } from 'viem';
|
|
3
|
+
import { type Address, type Hex, type AbiFunction, type AbiParameter } from 'viem';
|
|
4
4
|
import { type PrivateKeyAccount } from 'viem/accounts';
|
|
5
5
|
import { type FidesAddresses } from '@fidesbuild/sdk';
|
|
6
6
|
import { type SubAddresses } from '@fidesbuild/sub';
|
|
@@ -28,6 +28,10 @@ export declare function agentKey(cfg: Config): {
|
|
|
28
28
|
file: string | null;
|
|
29
29
|
created: boolean;
|
|
30
30
|
};
|
|
31
|
+
/** Turns plain JSON values into what viem encodes for each ABI type. "me" is the FIDES Account. */
|
|
32
|
+
export declare function coerce(p: AbiParameter, v: unknown, me: Address | null, at: string): unknown;
|
|
33
|
+
/** Encodes a call from one ABI function and plain JSON args. */
|
|
34
|
+
export declare function encodeCall(abi: AbiFunction, args: unknown[], me: Address | null): Hex;
|
|
31
35
|
export declare function createFidesMcpServer(cfg: Config): McpServer & {
|
|
32
36
|
agent: `0x${string}`;
|
|
33
37
|
};
|
package/dist/server.js
CHANGED
|
@@ -14,12 +14,13 @@ import { pathToFileURL } from 'node:url';
|
|
|
14
14
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
15
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
16
|
import { z } from 'zod';
|
|
17
|
-
import { encodeFunctionData, erc20Abi, formatUnits, parseUnits, isAddress, getAddress } from 'viem';
|
|
17
|
+
import { encodeFunctionData, decodeFunctionResult, erc20Abi, formatUnits, parseUnits, isAddress, getAddress } from 'viem';
|
|
18
18
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
19
19
|
import { Fides, FidesBuilder, Policy, Receipts, accountOf, createFidesClient, fromJson, toJson } from '@fidesbuild/sdk';
|
|
20
20
|
import { createX402, decodePaymentRequired, exactEip3009 } from '@fidesbuild/x402';
|
|
21
21
|
import { netObligations, settle, netRoots, PUBLISH_SELECTOR, NetError } from '@fidesbuild/net';
|
|
22
22
|
import { createSubClient, SubFidesKit, SubAgent, SubFidesError, subDeployments } from '@fidesbuild/sub';
|
|
23
|
+
import { quoteUniswap, checkMandate, swapActions, verifySwap, describeRoute, mandateOf, uniswap, NATIVE, SwapError } from '@fidesbuild/swap';
|
|
23
24
|
export function configFromEnv(env = process.env) {
|
|
24
25
|
const home = env.FIDES_HOME ?? path.join(os.homedir(), '.fides');
|
|
25
26
|
return {
|
|
@@ -80,6 +81,52 @@ const KNOWN_SERVICES = [
|
|
|
80
81
|
{ id: 'spraay', name: 'Spraay', url: 'https://gateway.spraay.app/api/v1/models', price: '0.001 USDG', returns: 'The AI models the Spraay gateway offers, with providers and prices', run_by: 'Spraay, not FIDES' },
|
|
81
82
|
{ id: 'madeonsol', name: 'MadeOnSol', url: 'https://madeonsol.com/api/x402/rhc/kol/feed?limit=5', price: '0.04 USDG', returns: 'Recent trades on Robinhood Chain by tracked wallets', run_by: 'MadeOnSol, not FIDES' },
|
|
82
83
|
];
|
|
84
|
+
/** Turns plain JSON values into what viem encodes for each ABI type. "me" is the FIDES Account. */
|
|
85
|
+
export function coerce(p, v, me, at) {
|
|
86
|
+
const arr = p.type.match(/^(.*)\[(\d*)\]$/);
|
|
87
|
+
if (arr) {
|
|
88
|
+
if (!Array.isArray(v))
|
|
89
|
+
throw new Error(`${at} must be a list`);
|
|
90
|
+
return v.map((x, i) => coerce({ ...p, type: arr[1] }, x, me, `${at}[${i}]`));
|
|
91
|
+
}
|
|
92
|
+
if (p.type === 'tuple') {
|
|
93
|
+
const parts = p.components;
|
|
94
|
+
const get = (c, i) => (Array.isArray(v) ? v[i] : v?.[c.name ?? '']);
|
|
95
|
+
if (v === null || typeof v !== 'object')
|
|
96
|
+
throw new Error(`${at} must be an object with ${parts.map((c) => c.name).join(', ')}`);
|
|
97
|
+
const out = parts.map((c, i) => coerce(c, get(c, i), me, `${at}.${c.name ?? i}`));
|
|
98
|
+
return Array.isArray(v) || parts.some((c) => !c.name) ? out : Object.fromEntries(parts.map((c, i) => [c.name, out[i]]));
|
|
99
|
+
}
|
|
100
|
+
if (/^u?int\d*$/.test(p.type)) {
|
|
101
|
+
if (!/^-?\d+$/.test(String(v ?? '').trim()))
|
|
102
|
+
throw new Error(`${at} must be a whole number in the token's smallest unit, as a string (got ${JSON.stringify(v)})`);
|
|
103
|
+
return BigInt(String(v).trim());
|
|
104
|
+
}
|
|
105
|
+
if (p.type === 'address') {
|
|
106
|
+
if (typeof v === 'string' && /^me$/i.test(v)) {
|
|
107
|
+
if (!me)
|
|
108
|
+
throw new Error(`${at}: "me" needs a signed FIDES`);
|
|
109
|
+
return me;
|
|
110
|
+
}
|
|
111
|
+
if (typeof v !== 'string' || !isAddress(v))
|
|
112
|
+
throw new Error(`${at} must be an address`);
|
|
113
|
+
return getAddress(v);
|
|
114
|
+
}
|
|
115
|
+
if (p.type === 'bool')
|
|
116
|
+
return v === true || v === 'true';
|
|
117
|
+
if (/^bytes\d*$/.test(p.type)) {
|
|
118
|
+
const h = v === '' || v == null ? '0x' : String(v);
|
|
119
|
+
if (!/^0x([0-9a-fA-F]{2})*$/.test(h))
|
|
120
|
+
throw new Error(`${at} must be hex bytes, like "0x"`);
|
|
121
|
+
return h;
|
|
122
|
+
}
|
|
123
|
+
return v;
|
|
124
|
+
}
|
|
125
|
+
/** Encodes a call from one ABI function and plain JSON args. */
|
|
126
|
+
export function encodeCall(abi, args, me) {
|
|
127
|
+
const values = abi.inputs.map((p, i) => coerce(p, args[i], me, p.name || `arg ${i + 1}`));
|
|
128
|
+
return encodeFunctionData({ abi: [abi], functionName: abi.name, args: values });
|
|
129
|
+
}
|
|
83
130
|
const ok = (value) => ({ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] });
|
|
84
131
|
const fail = (message) => ({ content: [{ type: 'text', text: message }], isError: true });
|
|
85
132
|
export function createFidesMcpServer(cfg) {
|
|
@@ -175,7 +222,7 @@ export function createFidesMcpServer(cfg) {
|
|
|
175
222
|
...(outside > 0n ? { warning: `${amount(outside)} sits on the agent key ${agent.address} outside the FIDES: it was sent to the agent address directly instead of to the FIDES Account. FIDES never spends it. To get it back, the owner can import the agent key from ${path.join(cfg.home, 'agent.json')} into a wallet on this computer, add a little ETH for gas, and send it to themselves. Funds for the agent always go to the FIDES Account address.` } : {}),
|
|
176
223
|
};
|
|
177
224
|
}
|
|
178
|
-
const server = new McpServer({ name: 'fides', version: '0.
|
|
225
|
+
const server = new McpServer({ name: 'fides', version: '0.8.0' }, {
|
|
179
226
|
instructions: 'FIDES lets you act with the owner\'s funds only inside limits the owner signed (a FIDES): a budget, allowed contracts and functions, paid services, and an expiry. ' +
|
|
180
227
|
'Funds move from the owner\'s FIDES Account, never from the owner\'s wallet. If no FIDES is set up yet, call fides_setup and give the user the link. Always call fides_plan first and show the user what is inside the FIDES, what is blocked and why, ' +
|
|
181
228
|
'and ask before calling fides_execute. Actions outside the FIDES are blocked before anything is sent. Amounts are in whole token units, like "1.5". ' +
|
|
@@ -197,8 +244,10 @@ export function createFidesMcpServer(cfg) {
|
|
|
197
244
|
return ok({
|
|
198
245
|
can_do: [
|
|
199
246
|
'Send USDG from the FIDES Account, to one address or many, inside the budget. Several transfers go out as one transaction.',
|
|
200
|
-
'
|
|
247
|
+
'Use any verified contract on Robinhood Chain: read what it lets an agent do, risky functions flagged (fides_inspect_contract), allow the functions you choose (fides_setup with contracts), read values from it for free (fides_read_contract), and call it by function name and plain values (fides_plan with function and args). Example: supply USDG to a Morpho market and withdraw it, never borrow.',
|
|
248
|
+
'Use any paid API that charges on Robinhood Chain: read its price without paying (fides_check_api), allow it with a cap (fides_setup with services), then pay per request (fides_pay_request).',
|
|
201
249
|
'Buy from paid APIs that charge per request over x402, paying from the FIDES budget, each capped per service.',
|
|
250
|
+
'Swap on Uniswap inside a trading mandate the owner signed: tokens it may buy, worst price, most per swap; best route across Robinhood Chain including v4 hook pools, approval and swap as one transaction (fides_swap). The FIDES must allow swaps: fides_setup with swap.',
|
|
202
251
|
'Hand another agent a slice of the FIDES with its own budget and expiry (fides_delegate, SUB-FIDES); or, as the child, act inside a slice handed to you (fides_sub_plan). The FIDES must allow delegation: fides_setup with delegate: true.',
|
|
203
252
|
'Settle with FIDES NET: give the payments owed to and from the FIDES Account, and they are netted to final positions and settled in one transaction, with the round published onchain (fides_net). The FIDES must allow NET: fides_setup with net: true.',
|
|
204
253
|
'Show the budget spent and left, what is allowed, the expiry, and every transaction executed, with explorer links.',
|
|
@@ -238,23 +287,64 @@ export function createFidesMcpServer(cfg) {
|
|
|
238
287
|
});
|
|
239
288
|
server.registerTool('fides_setup', {
|
|
240
289
|
title: 'Set up FIDES',
|
|
241
|
-
description: 'Use when the user wants to set up FIDES, connect their wallet or sign a new FIDES. Returns this computer\'s agent address and a link to the FIDES app with it filled in, where the user sets the limits and signs. The signed FIDES is then picked up automatically. To let the user buy from paid APIs (fides_pay_request), pass the services with a spending cap each. Paid APIs known to work use the ids "fides-demo", "spraay" and "madeonsol" (fides_help has their URLs and prices); for any other
|
|
290
|
+
description: 'Use when the user wants to set up FIDES, connect their wallet or sign a new FIDES. Returns this computer\'s agent address and a link to the FIDES app with it filled in, where the user sets the limits and signs. The signed FIDES is then picked up automatically. To let the user buy from paid APIs (fides_pay_request), pass the services with a spending cap each. Paid APIs known to work use the ids "fides-demo", "spraay" and "madeonsol" (fides_help has their URLs and prices); for any other paid API, call fides_check_api with its URL and use the service_id it returns. To let the agent call any contract on the chain, pass contracts with the functions it may call by name (fides_inspect_contract lists them and flags the risky ones). When a function pulls tokens from the account (such as Morpho supply, or a vault deposit), also allow approve on that token, so the agent can approve exactly the amount in the same transaction.',
|
|
242
291
|
inputSchema: {
|
|
243
|
-
services: z.array(z.object({ id: z.string().describe('
|
|
292
|
+
services: z.array(z.object({ id: z.string().describe('The service id: "fides-demo", "spraay", "madeonsol", or for any other paid API the service_id fides_check_api returns (its host)'), max: z.string().describe('Most the agent may spend on it, in USDG, e.g. "0.1"') })).optional(),
|
|
293
|
+
contracts: z.array(z.object({
|
|
294
|
+
address: z.string().describe('The contract address'),
|
|
295
|
+
functions: z.array(z.string()).min(1).describe('Function names (e.g. "supply", "withdraw") or 4-byte selectors. fides_inspect_contract lists them'),
|
|
296
|
+
})).optional().describe('Any contracts on the chain the agent may call, and exactly which functions; everything else on them stays blocked'),
|
|
244
297
|
net: z.boolean().optional().describe('true to allow FIDES NET: lets the FIDES publish netting roots on FidesNetRoots, which fides_net needs'),
|
|
245
298
|
delegate: z.boolean().optional().describe('true to let this agent hand slices of the FIDES to other agents (SUB-FIDES), which fides_delegate needs'),
|
|
299
|
+
swap: z.object({
|
|
300
|
+
buy: z.array(z.string()).min(1).describe('Tokens the agent may buy: "ETH", "WETH" or addresses'),
|
|
301
|
+
max_slippage_percent: z.number().positive().max(50).optional().describe('The worst price it may accept against the quote, in percent. Default 1'),
|
|
302
|
+
max_per_swap: z.string().optional().describe('The most one swap may spend, in the budget token. Default "1"'),
|
|
303
|
+
}).optional().describe('Let the agent swap on Uniswap inside a trading mandate the owner signs; fides_swap needs it'),
|
|
246
304
|
},
|
|
247
|
-
}, async ({ services, net, delegate }) =>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
305
|
+
}, async ({ services, net, delegate, swap, contracts }) => {
|
|
306
|
+
let targets = '';
|
|
307
|
+
const added = [];
|
|
308
|
+
for (const c of contracts ?? []) {
|
|
309
|
+
if (!isAddress(c.address))
|
|
310
|
+
return fail(`"${c.address}" is not a contract address.`);
|
|
311
|
+
const sels = c.functions.filter((x) => /^0x[0-9a-fA-F]{8}$/.test(x)).map((x) => x.toLowerCase());
|
|
312
|
+
const names = c.functions.filter((x) => !/^0x[0-9a-fA-F]{8}$/.test(x));
|
|
313
|
+
let label = c.address;
|
|
314
|
+
if (names.length) {
|
|
315
|
+
let info;
|
|
316
|
+
try {
|
|
317
|
+
info = await inspect(c.address);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
return fail(`Could not read ${c.address}: ${err?.message ?? err}`);
|
|
321
|
+
}
|
|
322
|
+
if (!info.verified)
|
|
323
|
+
return fail(`${c.address} is not verified, so its functions cannot be found by name. Give their 4-byte selectors instead.`);
|
|
324
|
+
label = info.name ?? c.address;
|
|
325
|
+
for (const n of names) {
|
|
326
|
+
const fn = info.functions.find((x) => x.name === n || x.signature.replace(/ \w+(?=[,)])/g, '') === n);
|
|
327
|
+
if (!fn)
|
|
328
|
+
return fail(`${label} has no function "${n}" that changes anything. It has: ${info.functions.map((x) => x.name).join(', ')}.`);
|
|
329
|
+
sels.push(fn.selector);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
targets += `&target=${getAddress(c.address)}:${[...new Set(sels)].join(',')}`;
|
|
333
|
+
added.push(`${label}: ${c.functions.join(', ')}`);
|
|
334
|
+
}
|
|
335
|
+
return ok({
|
|
336
|
+
agent_address: agent.address,
|
|
337
|
+
link: setupLink + targets + (services ?? []).map((x) => `&service=${encodeURIComponent(x.id)}:${encodeURIComponent(x.max)}`).join('') + (net && netRoots[cfg.chainId] ? `&target=${netRoots[cfg.chainId]}:${PUBLISH_SELECTOR}` : '') + (delegate ? '&delegate=1' : '') + (swap ? `&swap=${encodeURIComponent(swap.buy.map((t) => (/^weth$/i.test(t) ? uniswap[cfg.chainId]?.weth ?? t : t)).join('|'))}:${Math.round((swap.max_slippage_percent ?? 1) * 100)}:${encodeURIComponent(swap.max_per_swap ?? '1')}` : ''),
|
|
338
|
+
services: services?.length ? services.map((x) => `${x.id}: up to ${x.max} USDG`) : undefined,
|
|
339
|
+
contracts: added.length ? added : undefined,
|
|
340
|
+
steps: [
|
|
341
|
+
'Open the link. It opens the FIDES app with this agent already filled in.',
|
|
342
|
+
'Connect your wallet. Set the budget, the contracts allowed and the expiry. Start small.',
|
|
343
|
+
'Sign (free), click Create FIDES Account (a little gas), and send the budget to the FIDES Account address shown.',
|
|
344
|
+
'Come back and ask me for what you want. I pick up the signed FIDES by myself.',
|
|
345
|
+
],
|
|
346
|
+
});
|
|
347
|
+
});
|
|
258
348
|
server.registerTool('fides_use_file', {
|
|
259
349
|
title: 'Use a FIDES file',
|
|
260
350
|
description: 'Only needed if the user has a fides.json file: loads a signed FIDES from a path on this computer, checks that it is for this computer\'s agent key, and keeps a copy. FIDES signed in the app are picked up without this.',
|
|
@@ -286,18 +376,20 @@ export function createFidesMcpServer(cfg) {
|
|
|
286
376
|
}
|
|
287
377
|
});
|
|
288
378
|
const actionSchema = z.object({
|
|
289
|
-
kind: z.enum(['transfer', 'call']).describe('"transfer" sends a token from the FIDES Account. "call" calls an allowed contract with raw calldata.'),
|
|
379
|
+
kind: z.enum(['transfer', 'call']).describe('"transfer" sends a token from the FIDES Account. "call" calls an allowed contract, by function name and args (encoded from its verified source), or with raw calldata.'),
|
|
290
380
|
to: z.string().optional().describe('transfer: the recipient address'),
|
|
291
|
-
amount: z.string().optional().describe('transfer: how much, in whole token units, e.g. "1.5". call: how much of the budget token the call spends, if any'),
|
|
381
|
+
amount: z.string().optional().describe('transfer: how much, in whole token units, e.g. "1.5". call: how much of the budget token the call spends, if any, in whole units'),
|
|
292
382
|
token: z.string().optional().describe('transfer: the token address. Defaults to the FIDES\'s budget token'),
|
|
293
383
|
target: z.string().optional().describe('call: the contract address'),
|
|
294
|
-
|
|
384
|
+
function: z.string().optional().describe('call: the function name, e.g. "supply", or its signature when the name is overloaded. FIDES encodes the call from the contract\'s verified source'),
|
|
385
|
+
args: z.array(z.any()).optional().describe('call with function: the arguments in order. Integers as strings in the token\'s smallest unit (USDG has 6 decimals: 0.05 USDG is "50000"). Structs as objects keyed by field name. "me" for an address means this FIDES Account. Empty bytes as "0x"'),
|
|
386
|
+
data: z.string().optional().describe('call: hex calldata, starting with the 4-byte function selector. Use function and args instead when you can'),
|
|
295
387
|
label: z.string().optional().describe('A short description shown in the plan'),
|
|
296
388
|
});
|
|
297
389
|
/** The tool's actions as Builder actions, in the budget token unless a transfer names another. */
|
|
298
390
|
async function buildActions(actions, budget) {
|
|
299
391
|
const built = [];
|
|
300
|
-
for (
|
|
392
|
+
for (let [i, a] of actions.entries()) {
|
|
301
393
|
const n = i + 1;
|
|
302
394
|
if (a.kind === 'transfer') {
|
|
303
395
|
if (!a.to || !isAddress(a.to))
|
|
@@ -321,8 +413,14 @@ export function createFidesMcpServer(cfg) {
|
|
|
321
413
|
else {
|
|
322
414
|
if (!a.target || !isAddress(a.target))
|
|
323
415
|
return { error: `Action ${n}: "target" must be a contract address.` };
|
|
416
|
+
if (a.function && !a.data) {
|
|
417
|
+
const enc = await encodeByName(getAddress(a.target), a.function, a.args ?? [], 'write');
|
|
418
|
+
if ('error' in enc)
|
|
419
|
+
return { error: `Action ${n}: ${enc.error}` };
|
|
420
|
+
a = { ...a, data: enc.data, label: a.label ?? enc.label };
|
|
421
|
+
}
|
|
324
422
|
if (!a.data || !/^0x([0-9a-fA-F]{2}){4,}$/.test(a.data))
|
|
325
|
-
return { error: `Action ${n}: "data"
|
|
423
|
+
return { error: `Action ${n}: give "function" and "args", or "data" as hex calldata starting with the 4-byte selector.` };
|
|
326
424
|
const t = await token(budget);
|
|
327
425
|
let spend = 0n;
|
|
328
426
|
if (a.amount) {
|
|
@@ -341,7 +439,7 @@ export function createFidesMcpServer(cfg) {
|
|
|
341
439
|
}
|
|
342
440
|
server.registerTool('fides_plan', {
|
|
343
441
|
title: 'Plan actions under the FIDES',
|
|
344
|
-
description: 'Builds the actions into one execution plan: checks every action against the FIDES, groups what can go out together, and simulates it against the chain. Sends nothing. Returns a plan_id for fides_execute. Show the result to the user before executing.',
|
|
442
|
+
description: 'Builds the actions into one execution plan: checks every action against the FIDES, groups what can go out together, and simulates it against the chain. Sends nothing. Returns a plan_id for fides_execute. Show the result to the user before executing. Call contracts by function name and args; FIDES encodes them from the verified source. When a contract pulls tokens (e.g. Morpho supply), put an approve of exactly that amount first in the same plan, so nothing stays approved, and set amount on the call so it counts against the budget. Use "me" for addresses that should be the FIDES Account.',
|
|
345
443
|
inputSchema: { actions: z.array(actionSchema).min(1).max(50) },
|
|
346
444
|
}, async ({ actions }) => {
|
|
347
445
|
try {
|
|
@@ -503,7 +601,13 @@ export function createFidesMcpServer(cfg) {
|
|
|
503
601
|
return fail(notLoaded());
|
|
504
602
|
if (!fides.services.length)
|
|
505
603
|
return fail(`This FIDES allows no paid services. To buy from a paid API, the user signs a new FIDES that includes one: call fides_setup with services, for example [{ id: "fides-demo", max: "0.1" }].`);
|
|
506
|
-
const
|
|
604
|
+
const host = (() => { try {
|
|
605
|
+
return new URL(url).host.toLowerCase().replace(/^www\./, '');
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return '';
|
|
609
|
+
} })();
|
|
610
|
+
const id = service ?? (fides.services.some((x) => x.serviceId === host) ? host : fides.services.length === 1 ? fides.services[0].serviceId : undefined);
|
|
507
611
|
if (!id)
|
|
508
612
|
return fail(`Say which service this is. The FIDES allows: ${fides.services.map((x) => x.serviceId).join(', ')}.`);
|
|
509
613
|
const rule = fides.services.find((x) => x.serviceId === id);
|
|
@@ -600,6 +704,195 @@ export function createFidesMcpServer(cfg) {
|
|
|
600
704
|
}
|
|
601
705
|
});
|
|
602
706
|
// ---- SUB-FIDES ----
|
|
707
|
+
// ---- Connect anything ----
|
|
708
|
+
const lookupBase = () => cfg.api ?? cfg.directory ?? 'https://api.fidesbuilder.xyz';
|
|
709
|
+
async function inspect(address) {
|
|
710
|
+
const res = await fetch(`${lookupBase()}/v1/contracts/${address}`);
|
|
711
|
+
const json = fromJson(await res.text());
|
|
712
|
+
if (!res.ok || json?.error)
|
|
713
|
+
throw new Error(json?.error?.message ?? `HTTP ${res.status}`);
|
|
714
|
+
return json;
|
|
715
|
+
}
|
|
716
|
+
/** Encodes a call to `fn` on `target` from the contract's verified ABI (standard ERC-20 functions work without one). */
|
|
717
|
+
async function encodeByName(target, fn, args, kind) {
|
|
718
|
+
let info = null;
|
|
719
|
+
try {
|
|
720
|
+
info = await inspect(target);
|
|
721
|
+
}
|
|
722
|
+
catch { /* fall back to ERC-20 below */ }
|
|
723
|
+
const list = (kind === 'write' ? info?.functions : info?.reads) ?? [];
|
|
724
|
+
const byName = (x) => x.abi.name === fn || `${x.abi.name}(${x.abi.inputs.map((i) => i.type).join(',')})` === fn.replace(/\s/g, '') || x.signature === fn;
|
|
725
|
+
let matches = list.filter((x) => x.abi && byName(x));
|
|
726
|
+
if (!matches.length) {
|
|
727
|
+
const std = erc20Abi.filter((x) => x.type === 'function' && x.name === fn && (kind === 'read') === (x.stateMutability === 'view'));
|
|
728
|
+
matches = std.map((abi) => ({ abi }));
|
|
729
|
+
}
|
|
730
|
+
if (matches.length > 1)
|
|
731
|
+
matches = matches.filter((x) => x.abi.inputs.length === args.length);
|
|
732
|
+
if (!matches.length) {
|
|
733
|
+
if (info && !info.verified)
|
|
734
|
+
return { error: `${target} is not verified on Sourcify, so "${fn}" cannot be encoded by name. Pass "data" as hex calldata instead.` };
|
|
735
|
+
const names = list.map((x) => x.name).filter(Boolean);
|
|
736
|
+
return { error: `${info?.name ?? target} has no ${kind === 'write' ? 'write' : 'read'} function "${fn}"${names.length ? `. It has: ${[...new Set(names)].join(', ')}` : ''}.` };
|
|
737
|
+
}
|
|
738
|
+
if (matches.length > 1)
|
|
739
|
+
return { error: `"${fn}" is overloaded; give its signature, one of: ${matches.map((x) => `${x.abi.name}(${x.abi.inputs.map((i) => i.type).join(',')})`).join(', ')}.` };
|
|
740
|
+
const abi = matches[0].abi;
|
|
741
|
+
if (args.length !== abi.inputs.length)
|
|
742
|
+
return { error: `${abi.name} takes ${abi.inputs.length} argument(s): ${abi.inputs.map((i) => `${i.type} ${i.name ?? ''}`.trim()).join(', ')}. Got ${args.length}.` };
|
|
743
|
+
const me = (await currentFides().catch(() => null))?.account ?? null;
|
|
744
|
+
try {
|
|
745
|
+
const data = encodeCall(abi, args, me);
|
|
746
|
+
return { data, label: `${info?.name ?? 'contract'}.${abi.name} on ${target}`, abi, name: info?.name ?? null };
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
return { error: err?.shortMessage ?? err?.message ?? String(err) };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
server.registerTool('fides_inspect_contract', {
|
|
753
|
+
title: 'What a contract lets an agent do',
|
|
754
|
+
description: 'Reads any contract on the chain from its verified source (proxies followed) and lists the functions that change something, in plain words, with risks flagged: "danger" (lasting approvals, handing control to another address, upgrades, debt), "warn" (the agent picks where funds go, or it sends ETH) and "admin" (operator-only; will fail for an agent). Use it before fides_setup with contracts, and recommend only the functions the user needs. Sends nothing.',
|
|
755
|
+
inputSchema: { address: z.string().describe('The contract address') },
|
|
756
|
+
}, async ({ address }) => {
|
|
757
|
+
try {
|
|
758
|
+
if (!isAddress(address))
|
|
759
|
+
return fail('That is not a contract address.');
|
|
760
|
+
const info = await inspect(address);
|
|
761
|
+
if (!info.verified)
|
|
762
|
+
return ok({ address: info.address, verified: false, note: 'Not verified on Sourcify, so its functions cannot be read. The user can still allow it by 4-byte selector.' });
|
|
763
|
+
return ok({
|
|
764
|
+
name: info.name, address: info.address, verified: true, proxy: info.proxy ?? undefined,
|
|
765
|
+
functions: info.functions.map((fn) => ({ name: fn.name, does: fn.about || undefined, signature: fn.signature, selector: fn.selector, risks: fn.risks.length ? fn.risks.map((r) => `${r.level}: ${r.why}`) : undefined })),
|
|
766
|
+
reads: info.reads?.length ? info.reads.map((fn) => fn.signature) : undefined,
|
|
767
|
+
next: 'To allow some of these, call fides_setup with contracts: [{ address, functions: [names] }] and give the user the link. Once signed, call them with fides_plan: { kind: "call", target, function, args }. Read functions are free: fides_read_contract.',
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
return fail('Could not read the contract: ' + (err?.message ?? err));
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
server.registerTool('fides_read_contract', {
|
|
775
|
+
title: 'Read from a contract',
|
|
776
|
+
description: 'Calls a read-only (view) function on any verified contract on the chain, by name, and returns the result. Free: nothing is signed or sent, and it needs no permission in the FIDES. Use it to look up what a write needs, such as a market\'s parameters or a balance. Integers come back as strings in the smallest unit.',
|
|
777
|
+
inputSchema: {
|
|
778
|
+
address: z.string().describe('The contract address'),
|
|
779
|
+
function: z.string().describe('The view function name, or its signature when overloaded'),
|
|
780
|
+
args: z.array(z.any()).optional().describe('The arguments in order. Integers as strings. "me" for an address means this FIDES Account'),
|
|
781
|
+
},
|
|
782
|
+
}, async ({ address, function: fn, args }) => {
|
|
783
|
+
try {
|
|
784
|
+
if (!isAddress(address))
|
|
785
|
+
return fail('That is not a contract address.');
|
|
786
|
+
const enc = await encodeByName(getAddress(address), fn, args ?? [], 'read');
|
|
787
|
+
if ('error' in enc)
|
|
788
|
+
return fail(enc.error);
|
|
789
|
+
const { data } = await client.public.call({ to: getAddress(address), data: enc.data });
|
|
790
|
+
const result = decodeFunctionResult({ abi: [enc.abi], functionName: enc.abi.name, data: data ?? '0x' });
|
|
791
|
+
const named = enc.abi.outputs.length > 1 && enc.abi.outputs.every((o) => o.name) && Array.isArray(result) ? Object.fromEntries(enc.abi.outputs.map((o, i) => [o.name, result[i]])) : result;
|
|
792
|
+
return ok({ contract: enc.name ?? address, function: enc.abi.name, result: JSON.parse(JSON.stringify(named, (_k, x) => (typeof x === 'bigint' ? x.toString() : x))) });
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
return fail('Could not read: ' + failure(err?.shortMessage ?? err?.message ?? err));
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
server.registerTool('fides_check_api', {
|
|
799
|
+
title: 'Can FIDES pay this API?',
|
|
800
|
+
description: 'Calls a paid API\'s URL once, without paying, and reads its x402 price: whether a FIDES on Robinhood Chain can pay it, the price, who is paid, and the service id and a suggested cap to use with fides_setup. Use it when the user wants the agent to use a paid API that is not one of the known ones. Pays nothing.',
|
|
801
|
+
inputSchema: { url: z.string().describe('The API URL, https') },
|
|
802
|
+
}, async ({ url }) => {
|
|
803
|
+
try {
|
|
804
|
+
const res = await fetch(`${lookupBase()}/v1/services/probe`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: toJson({ url, budgetToken: (await currentFides().catch(() => null))?.spendLimits[0].asset ?? '0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168' }) });
|
|
805
|
+
const p = fromJson(await res.text());
|
|
806
|
+
if (!res.ok || p?.error)
|
|
807
|
+
return fail(p?.error?.message ?? `HTTP ${res.status}`);
|
|
808
|
+
if (!p.payable)
|
|
809
|
+
return ok({ url: p.url, payable: false, why: p.reason, charges_on: p.networks?.length ? p.networks : undefined });
|
|
810
|
+
const dec = p.offer.decimals ?? 6, sym = p.offer.symbol ?? 'USDG';
|
|
811
|
+
return ok({
|
|
812
|
+
url: p.url, payable: true, price: `${formatUnits(BigInt(p.offer.amount), dec)} ${sym} a request`, paid_to: p.offer.payTo, what_it_is: p.description,
|
|
813
|
+
service_id: p.serviceId, suggested_cap: `${formatUnits(BigInt(p.suggestedCap), dec)} ${sym} (10 requests)`,
|
|
814
|
+
next: `To allow it, call fides_setup with services: [{ id: "${p.serviceId}", max: "${formatUnits(BigInt(p.suggestedCap), dec)}" }]. Then fides_pay_request with this URL.`,
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
return fail('Could not check the API: ' + (err?.message ?? err));
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
// ---- Uniswap ----
|
|
822
|
+
const tokenArg = (t) => (/^eth$/i.test(t) ? 'ETH' : /^weth$/i.test(t) ? uniswap[cfg.chainId]?.weth : isAddress(t) ? getAddress(t) : null);
|
|
823
|
+
server.registerTool('fides_swap', {
|
|
824
|
+
title: 'Swap on Uniswap inside the FIDES',
|
|
825
|
+
description: 'Gets the best route from Uniswap on Robinhood Chain (v2, v3 and v4 pools, including v4 pools with hooks such as the aggregator hooks) for the FIDES Account, checks it against the trading mandate the owner signed (tokens it may buy, worst price, most per swap), and simulates it to measure what would actually arrive. Sends nothing. Returns a plan_id for fides_execute, which sends an exact approval and the swap as one transaction; gas is paid by the relayer. Show the route, the quoted amount and the worst accepted amount to the user before executing. Needs a FIDES that allows swaps: fides_setup with swap.',
|
|
826
|
+
inputSchema: {
|
|
827
|
+
token_out: z.string().describe('What to buy: "ETH", "WETH" or a token address'),
|
|
828
|
+
amount: z.string().describe('How much of the budget token to spend, e.g. "1"'),
|
|
829
|
+
max_slippage_percent: z.number().positive().optional().describe('Worst price against the quote, in percent. Defaults to the most the FIDES allows'),
|
|
830
|
+
},
|
|
831
|
+
}, async ({ token_out, amount, max_slippage_percent }) => {
|
|
832
|
+
try {
|
|
833
|
+
const fides = await currentFides();
|
|
834
|
+
if (!fides)
|
|
835
|
+
return fail(notLoaded());
|
|
836
|
+
if (fides.agent.toLowerCase() !== agent.address.toLowerCase())
|
|
837
|
+
return fail(`The loaded FIDES is for agent ${fides.agent}, not this computer's agent key ${agent.address}.`);
|
|
838
|
+
const m = mandateOf(fides);
|
|
839
|
+
if (!m)
|
|
840
|
+
return fail('This FIDES does not allow swaps. The user signs one that does: call fides_setup with swap, for example { buy: ["ETH"], max_slippage_percent: 1, max_per_swap: "1" }, and give them the link.');
|
|
841
|
+
const out = tokenArg(token_out);
|
|
842
|
+
if (!out)
|
|
843
|
+
return fail('"token_out" must be "ETH", "WETH" or a token address.');
|
|
844
|
+
const budgetToken = fides.spendLimits[0].asset, tIn = await token(budgetToken);
|
|
845
|
+
let value;
|
|
846
|
+
try {
|
|
847
|
+
value = parseUnits(amount, tIn.decimals);
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
return fail(`"amount" must be a number of ${tIn.symbol}, like "1".`);
|
|
851
|
+
}
|
|
852
|
+
const slippageBps = max_slippage_percent ? Math.round(max_slippage_percent * 100) : m.maxSlippageBps;
|
|
853
|
+
let q, blocked;
|
|
854
|
+
if (cfg.api) {
|
|
855
|
+
const r = await post('/v1/swap/quote', { fides, tokenOut: out, amount: value, slippageBps });
|
|
856
|
+
q = r.quote;
|
|
857
|
+
blocked = r.mandate?.allowed ? null : { code: r.mandate.code, message: r.mandate.message };
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
if (!process.env.UNISWAP_API_KEY)
|
|
861
|
+
return fail('Swaps need the FIDES API, or UNISWAP_API_KEY when running without it.');
|
|
862
|
+
q = await quoteUniswap({ apiKey: process.env.UNISWAP_API_KEY, fides, tokenOut: out, amount: value, slippageBps });
|
|
863
|
+
blocked = checkMandate(fides, q);
|
|
864
|
+
}
|
|
865
|
+
const outInfo = q.tokenOut === NATIVE ? { symbol: 'ETH', decimals: 18 } : await token(q.tokenOut);
|
|
866
|
+
const fmt = (v) => `${formatUnits(v, outInfo.decimals)} ${outInfo.symbol}`;
|
|
867
|
+
if (blocked)
|
|
868
|
+
return fail(`Blocked by the trading mandate: ${blocked.message} Nothing was sent.`);
|
|
869
|
+
const actions = swapActions(q, `swap ${amount} ${tIn.symbol} to ${outInfo.symbol} on Uniswap`);
|
|
870
|
+
const plan = await new FidesBuilder({ client, fides, agent }).add(actions).build();
|
|
871
|
+
if (plan.rejected().length) {
|
|
872
|
+
const r = plan.rejected()[0];
|
|
873
|
+
return fail(`Blocked by the FIDES: ${REASONS[r.reason ?? ''] ?? r.reason}. Nothing was sent.`);
|
|
874
|
+
}
|
|
875
|
+
const v = await verifySwap(client, fides, plan, q);
|
|
876
|
+
const id = plan.planHash.slice(0, 18);
|
|
877
|
+
if (v.ok)
|
|
878
|
+
plans.set(id, { plan, actions, fides, swap: { quote: q, outDecimals: outInfo.decimals, outSymbol: outInfo.symbol } });
|
|
879
|
+
return ok({
|
|
880
|
+
plan_id: v.ok ? id : null,
|
|
881
|
+
ready_to_execute: v.ok,
|
|
882
|
+
route: describeRoute(q),
|
|
883
|
+
spend: `${amount} ${tIn.symbol}`,
|
|
884
|
+
quoted: fmt(q.quotedOut),
|
|
885
|
+
worst_accepted: `${fmt(q.minOut)} (${q.slippageBps / 100}% below the quote)`,
|
|
886
|
+
simulated: v.ok ? `would receive ${fmt(v.received)}` : `refused: ${v.reason}`,
|
|
887
|
+
transactions: `${plan.estimatedSavings().baselineTransactions} onchain write(s) go out as ${plan.estimatedSavings().estimatedTransactions} transaction(s): an exact approval and the swap, nothing left approved`,
|
|
888
|
+
mandate: `may buy ${m.buy.join(', ')}; worst price ${m.maxSlippageBps / 100}%; most per swap ${formatUnits(m.maxPerSwap, tIn.decimals)} ${tIn.symbol}`,
|
|
889
|
+
sent: 'nothing has been sent',
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
return fail(err instanceof SwapError ? `Uniswap: ${err.message}` : 'Could not get a swap: ' + (err?.shortMessage ?? err?.message ?? err));
|
|
894
|
+
}
|
|
895
|
+
});
|
|
603
896
|
server.registerTool('fides_delegate', {
|
|
604
897
|
title: 'Hand another agent a slice of the FIDES (SUB-FIDES)',
|
|
605
898
|
description: 'This agent hands another agent a SUB-FIDES: its own budget, contracts and expiry, inside this FIDES. The child can never exceed it, and everything it spends also counts against this FIDES. This agent signs it; nothing is sent onchain and it costs nothing. The child agent (Claude or an SDK agent running FIDES) finds it by itself, or the user can hand it the file. Needs a FIDES that allows delegation: fides_setup with delegate: true. Confirm the amount and the agent with the user first.',
|
|
@@ -729,7 +1022,7 @@ export function createFidesMcpServer(cfg) {
|
|
|
729
1022
|
const given = fs.existsSync(subsDir) ? fs.readdirSync(subsDir).filter((n) => n.endsWith('.json')).map((n) => fromJson(fs.readFileSync(path.join(subsDir, n), 'utf8')).sub) : [];
|
|
730
1023
|
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
731
1024
|
return ok({
|
|
732
|
-
handed_out: given.map((s) => ({ sub_fides: s.id, child_agent: s.agent, budget: s.spendLimits.map((l) => l.amount.
|
|
1025
|
+
handed_out: await Promise.all(given.map(async (s) => ({ sub_fides: s.id, child_agent: s.agent, budget: (await Promise.all(s.spendLimits.map(async (l) => { const t = await token(l.asset); return `${formatUnits(l.amount, t.decimals)} ${t.symbol}`; }))).join(', '), expires: new Date(Number(s.validUntil) * 1000).toISOString(), expired: now >= s.validUntil }))),
|
|
733
1026
|
received: (await receivedSubs()).map((x) => ({ sub_fides: x.sub.id, delegated_by: x.parent.agent, expires: new Date(Number(x.sub.validUntil) * 1000).toISOString() })),
|
|
734
1027
|
});
|
|
735
1028
|
}
|
|
@@ -762,9 +1055,12 @@ export function createFidesMcpServer(cfg) {
|
|
|
762
1055
|
if (!entry)
|
|
763
1056
|
return fail('No plan with that id. Make one with fides_plan first (plans are kept until they are executed or the server restarts).');
|
|
764
1057
|
plans.delete(plan_id); // a plan's bundles can be sent once
|
|
765
|
-
const { plan, actions, fides, net } = entry;
|
|
1058
|
+
const { plan, actions, fides, net, swap } = entry;
|
|
766
1059
|
try {
|
|
1060
|
+
const outBalance = () => !swap ? Promise.resolve(0n) : swap.quote.tokenOut === NATIVE ? client.public.getBalance({ address: fides.account }) : client.public.readContract({ address: swap.quote.tokenOut, abi: erc20Abi, functionName: 'balanceOf', args: [fides.account] });
|
|
1061
|
+
const before = await outBalance();
|
|
767
1062
|
const bundles = await send(plan, actions, fides);
|
|
1063
|
+
const received = swap ? (await outBalance()) - before : 0n;
|
|
768
1064
|
const [spend] = await Policy.remainingSpend(client, fides);
|
|
769
1065
|
const t = await token(fides.spendLimits[0].asset);
|
|
770
1066
|
const executed = bundles.filter((b) => b.status === 'EXECUTED').length;
|
|
@@ -772,6 +1068,12 @@ export function createFidesMcpServer(cfg) {
|
|
|
772
1068
|
result: executed === bundles.length ? 'executed' : executed ? 'partly executed' : 'not executed',
|
|
773
1069
|
transactions: bundles.map((b) => ({ status: b.status.toLowerCase(), transaction: txLink(b.txHash), ...(b.error ? { reason: failure(b.error) } : {}) })),
|
|
774
1070
|
blocked_and_not_sent: plan.rejected().map((a) => `${a.label}: ${REASONS[a.reason ?? ''] ?? a.reason}`),
|
|
1071
|
+
...(swap && executed === bundles.length ? { best_execution: {
|
|
1072
|
+
quoted: `${formatUnits(swap.quote.quotedOut, swap.outDecimals)} ${swap.outSymbol}`,
|
|
1073
|
+
received: `${formatUnits(received, swap.outDecimals)} ${swap.outSymbol}`,
|
|
1074
|
+
versus_quote: received >= swap.quote.quotedOut ? 'at or better than the quote' : `${(Number(((swap.quote.quotedOut - received) * 10000n) / swap.quote.quotedOut) / 100).toFixed(2)}% below the quote, inside the ${swap.quote.slippageBps / 100}% allowed`,
|
|
1075
|
+
route: describeRoute(swap.quote),
|
|
1076
|
+
} } : {}),
|
|
775
1077
|
...(net && executed === bundles.length ? { netting_record: { contract: netRoots[cfg.chainId], operator: fides.account, epoch: net.epoch.toString(), obligations_root: net.obligationsRoot, settlement_root: net.settlementRoot, contract_link: explorer ? `${explorer}/address/${netRoots[cfg.chainId]}` : undefined } } : {}),
|
|
776
1078
|
budget_remaining: `${formatUnits(spend.remaining, t.decimals)} of ${formatUnits(spend.limit, t.decimals)} ${t.symbol}`,
|
|
777
1079
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidesbuild/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "FIDES as tools for Claude and any MCP client: plan and execute actions inside a signed FIDES, gas paid by the hosted relayer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fides",
|
|
@@ -14,14 +14,9 @@
|
|
|
14
14
|
"homepage": "https://fidesbuilder.xyz/docs",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"type": "module",
|
|
17
|
-
"exports":
|
|
18
|
-
".": {
|
|
19
|
-
"types": "./dist/server.d.ts",
|
|
20
|
-
"default": "./dist/server.js"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
17
|
+
"exports": "./src/server.ts",
|
|
23
18
|
"bin": {
|
|
24
|
-
"fides-mcp": "./
|
|
19
|
+
"fides-mcp": "./src/server.ts"
|
|
25
20
|
},
|
|
26
21
|
"files": [
|
|
27
22
|
"dist",
|
|
@@ -32,7 +27,16 @@
|
|
|
32
27
|
"node": ">=20.10"
|
|
33
28
|
},
|
|
34
29
|
"publishConfig": {
|
|
35
|
-
"access": "public"
|
|
30
|
+
"access": "public",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/server.d.ts",
|
|
34
|
+
"default": "./dist/server.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"fides-mcp": "./dist/server.js"
|
|
39
|
+
}
|
|
36
40
|
},
|
|
37
41
|
"dependencies": {
|
|
38
42
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
@@ -41,6 +45,7 @@
|
|
|
41
45
|
"zod": "^4.6.5",
|
|
42
46
|
"@fidesbuild/x402": "0.2.0",
|
|
43
47
|
"@fidesbuild/net": "0.2.1",
|
|
44
|
-
"@fidesbuild/sub": "0.2.0"
|
|
48
|
+
"@fidesbuild/sub": "0.2.0",
|
|
49
|
+
"@fidesbuild/swap": "0.2.0"
|
|
45
50
|
}
|
|
46
|
-
}
|
|
51
|
+
}
|