@fidesbuild/mcp-server 0.0.0-stage → 0.4.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 CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # @fidesbuild/mcp-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ FIDES for Claude, and for any other MCP app. You sign a FIDES once, with a budget, the contracts the agent may call and an expiry. Then you ask Claude in plain words, for example "pay 1 USDG to 0x…". Claude plans the actions through FIDES, shows you what is inside the FIDES and what is blocked, and executes only when you say so. Anything outside the FIDES is never sent.
4
+
5
+ - Funds move from your FIDES Account, never from your wallet.
6
+ - The agent key this server makes holds no funds. It can only sign actions inside the FIDES you signed for it.
7
+ - Gas is paid by the hosted FIDES relayer, so the agent key needs no ETH.
8
+
9
+ ## Set up
10
+
11
+ 1. **Add FIDES to Claude.**
12
+
13
+ Claude Desktop: download [the FIDES extension](https://fidesbuilder.xyz/downloads/fides.mcpb), double-click it and click Install.
14
+
15
+ Claude Code, in a terminal, on Windows:
16
+
17
+ ```bash
18
+ claude mcp add fides -- cmd /c npx -y @fidesbuild/mcp-server@latest
19
+ ```
20
+
21
+ On macOS or Linux:
22
+
23
+ ```bash
24
+ claude mcp add fides -- npx -y @fidesbuild/mcp-server@latest
25
+ ```
26
+
27
+ 2. **In Claude, say "Set up FIDES".** Claude gives you a link to the FIDES app with its agent address filled in.
28
+
29
+ 3. **Open the link,** set the budget, the contracts and the expiry, sign, then create and fund your FIDES Account.
30
+
31
+ 4. **Ask Claude for what you want.** It picks up the signed FIDES by itself; there is nothing to download.
32
+
33
+ Claude Code and other MCP apps need [Node](https://nodejs.org) 20.10 or later. Claude Desktop has its own.
34
+
35
+ ## Tools
36
+
37
+ | Tool | What it does |
38
+ | --- | --- |
39
+ | `fides_setup` | A link to the FIDES app with this agent filled in |
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 |
42
+ | `fides_execute` | Executes a plan by its id. Only the actions inside the FIDES are sent |
43
+ | `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
+ | `fides_receipts` | Every transaction executed under the FIDES, with explorer links |
45
+ | `fides_help` | What you can do with FIDES, the paid APIs known to work with their prices, and what to try |
46
+ | `fides_use_file` | Loads a `fides.json` file, for a FIDES that was not signed in the app |
47
+
48
+ The FIDES used is the newest valid one signed for this agent: from the FIDES API, where the app shares a FIDES when it is signed, or from the copy kept in `~/.fides`. Each is verified onchain before it is used.
49
+
50
+ ## Paid APIs
51
+
52
+ Claude can buy from APIs that charge per request over x402, from your FIDES. Each paid service is named in the FIDES with a cap; anything not named, or past its cap, is refused before paying. To try it, ask Claude: "Set up FIDES with the fides-demo paid service, up to 0.1 USDG", sign, then "Buy the chain stats from the FIDES demo" (0.01 USDG for Robinhood Chain's latest block, gas price and USDG supply).
53
+
54
+ Paid APIs known to work, each bought through FIDES with real payments on mainnet:
55
+
56
+ | Service id | URL | Price |
57
+ | --- | --- | --- |
58
+ | `fides-demo` | `https://api.fidesbuilder.xyz/v1/demo/chain-stats` | 0.01 USDG |
59
+ | `spraay` | `https://gateway.spraay.app/api/v1/models` | 0.001 USDG |
60
+ | `madeonsol` | `https://madeonsol.com/api/x402/rhc/kol/feed?limit=5` | 0.04 USDG |
61
+
62
+ Any API that charges USDG on Robinhood Chain over x402 (`exact`, EIP-3009) works the same way. The service id is any name you choose when you sign. Ask Claude "What can you do with FIDES?" to get this list in the chat.
63
+
64
+ ## Other MCP apps
65
+
66
+ Add a server with the command `npx` and the arguments `-y @fidesbuild/mcp-server@latest` (on Windows, the command `cmd` and the arguments `/c npx -y @fidesbuild/mcp-server@latest`). `npx -y @fidesbuild/mcp-server setup` makes the agent key and prints the setup link from a terminal.
67
+
68
+ ## Settings
69
+
70
+ All optional, set in the environment of the server.
71
+
72
+ | Variable | Default |
73
+ | --- | --- |
74
+ | `FIDES_HOME` | `~/.fides`, where `agent.json` and the FIDES in use are kept |
75
+ | `FIDES_FILE` | `~/.fides/fides.json` |
76
+ | `FIDES_RPC` | `https://rpc.mainnet.chain.robinhood.com` |
77
+ | `FIDES_API` | `https://api.fidesbuilder.xyz`: the relayer that pays gas, and where signed FIDES are looked up |
78
+ | `FIDES_APP` | `https://fidesbuilder.xyz/app`, for the setup link |
79
+ | `FIDES_RELAY` | Set to `self` to have the agent key pay gas itself (give it a little ETH) |
80
+ | `FIDES_LOOKUP` | Set to `off` to use only the FIDES kept in `FIDES_FILE` |
81
+ | `FIDES_AGENT_KEY` | Use this private key instead of `~/.fides/agent.json` |
82
+
83
+ The hosted relayer has a daily gas budget shared by everyone. If it runs out, `fides_execute` says so; use `FIDES_RELAY=self` or try later.
84
+
85
+ Docs: [fidesbuilder.xyz/docs](https://fidesbuilder.xyz/docs#/claude)
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { type Hex } from 'viem';
4
+ import { type PrivateKeyAccount } from 'viem/accounts';
5
+ import { type FidesAddresses } from '@fidesbuild/sdk';
6
+ export type Config = {
7
+ /** Folder for agent.json and the default fides.json. */
8
+ home: string;
9
+ rpc: string;
10
+ chainId: number;
11
+ /** The relaying API, or null to have the agent key pay gas itself. */
12
+ api: string | null;
13
+ /** The FIDES API, asked for the FIDES signed for this agent. Null to only use fidesFile. */
14
+ directory: string | null;
15
+ /** The FIDES app, for the setup link. */
16
+ app: string;
17
+ fidesFile: string;
18
+ addresses?: FidesAddresses;
19
+ agentKey?: Hex;
20
+ };
21
+ export declare function configFromEnv(env?: NodeJS.ProcessEnv): Config;
22
+ /** This computer's agent key: from FIDES_AGENT_KEY, or ~/.fides/agent.json, made on first use. */
23
+ export declare function agentKey(cfg: Config): {
24
+ account: PrivateKeyAccount;
25
+ file: string | null;
26
+ created: boolean;
27
+ };
28
+ export declare function createFidesMcpServer(cfg: Config): McpServer & {
29
+ agent: `0x${string}`;
30
+ };
package/dist/server.js ADDED
@@ -0,0 +1,591 @@
1
+ #!/usr/bin/env node
2
+ // fides-mcp — FIDES as tools for Claude and any other MCP client.
3
+ //
4
+ // npx -y @fidesbuild/mcp-server setup make this computer's agent key and print what to do next
5
+ // npx -y @fidesbuild/mcp-server run the MCP server over stdio (what Claude starts)
6
+ //
7
+ // The agent key lives in ~/.fides/agent.json and holds no funds. It can only sign actions inside the
8
+ // FIDES the owner signed for it. Bundles are relayed by the hosted FIDES API, which pays the gas,
9
+ // unless FIDES_RELAY=self, in which case the agent key pays.
10
+ import fs from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
14
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { z } from 'zod';
17
+ import { encodeFunctionData, erc20Abi, formatUnits, parseUnits, isAddress, getAddress } from 'viem';
18
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
19
+ import { Fides, FidesBuilder, Policy, Receipts, createFidesClient, fromJson, toJson } from '@fidesbuild/sdk';
20
+ import { createX402, decodePaymentRequired, exactEip3009 } from '@fidesbuild/x402';
21
+ export function configFromEnv(env = process.env) {
22
+ const home = env.FIDES_HOME ?? path.join(os.homedir(), '.fides');
23
+ return {
24
+ home,
25
+ rpc: env.FIDES_RPC ?? 'https://rpc.mainnet.chain.robinhood.com',
26
+ chainId: Number(env.FIDES_CHAIN_ID ?? 4663),
27
+ api: env.FIDES_RELAY === 'self' ? null : (env.FIDES_API ?? 'https://api.fidesbuilder.xyz').replace(/\/$/, ''),
28
+ directory: env.FIDES_LOOKUP === 'off' ? null : (env.FIDES_API ?? 'https://api.fidesbuilder.xyz').replace(/\/$/, ''),
29
+ app: (env.FIDES_APP ?? 'https://fidesbuilder.xyz/app').replace(/\/$/, ''),
30
+ fidesFile: env.FIDES_FILE ?? path.join(home, 'fides.json'),
31
+ addresses: env.FIDES_ADDRESSES ? JSON.parse(fs.readFileSync(env.FIDES_ADDRESSES, 'utf8')) : undefined,
32
+ agentKey: env.FIDES_AGENT_KEY,
33
+ };
34
+ }
35
+ /** This computer's agent key: from FIDES_AGENT_KEY, or ~/.fides/agent.json, made on first use. */
36
+ export function agentKey(cfg) {
37
+ if (cfg.agentKey)
38
+ return { account: privateKeyToAccount(cfg.agentKey), file: null, created: false };
39
+ const file = path.join(cfg.home, 'agent.json');
40
+ if (fs.existsSync(file))
41
+ return { account: privateKeyToAccount(JSON.parse(fs.readFileSync(file, 'utf8')).privateKey), file, created: false };
42
+ fs.mkdirSync(cfg.home, { recursive: true, mode: 0o700 });
43
+ const privateKey = generatePrivateKey();
44
+ const account = privateKeyToAccount(privateKey);
45
+ fs.writeFileSync(file, JSON.stringify({ address: account.address, privateKey, note: 'FIDES agent key. It holds no funds and can only act inside the FIDES signed for it. Keep this file private.' }, null, 2) + '\n', { mode: 0o600 });
46
+ return { account, file, created: true };
47
+ }
48
+ const EXPLORER = { 4663: 'https://robinhoodchain.blockscout.com' };
49
+ const SELECTORS = { '0xa9059cbb': 'transfer', '0x095ea7b3': 'approve', '0x23b872dd': 'transferFrom' };
50
+ const REASONS = {
51
+ SPEND_LIMIT_EXCEEDED: 'over the FIDES’s budget', ASSET_NOT_ALLOWED: 'token not allowed by the FIDES', TARGET_NOT_ALLOWED: 'contract not allowed by the FIDES',
52
+ SELECTOR_NOT_ALLOWED: 'function not allowed by the FIDES', NATIVE_VALUE_EXCEEDED: 'sends more ETH than the FIDES allows', ACTION_LIMIT_EXCEEDED: 'over the FIDES’s action limit',
53
+ FIDES_EXPIRED: 'the FIDES has expired', FIDES_REVOKED: 'the FIDES has been revoked', DEPENDENCY_REJECTED: 'depends on a blocked action',
54
+ };
55
+ /** Simulation and relay failures, in words. */
56
+ function failure(reason) {
57
+ const r = String(reason ?? '');
58
+ if (/InsufficientBalance|InsufficientFunds|0x356680b7|exceeds balance/i.test(r))
59
+ return 'The FIDES Account does not hold enough of the token. The owner needs to fund it.';
60
+ if (/SpendLimitExceeded|AssetOutflowExceeded/.test(r))
61
+ return 'This would go over the FIDES’s budget, counting what it has already spent.';
62
+ if (/TargetNotAllowed|SelectorNotAllowed/.test(r))
63
+ return 'This calls a contract or function the FIDES does not allow.';
64
+ if (/FidesRevoked/.test(r))
65
+ return 'The FIDES has been revoked.';
66
+ if (/FidesExpired|FidesNotYetValid/.test(r))
67
+ return 'The FIDES is outside its valid time window.';
68
+ if (/NonceAlreadyUsed/.test(r))
69
+ return 'This plan was already executed. Make a new plan.';
70
+ if (/RELAYER_BUDGET_EXHAUSTED|RELAYER_OUT_OF_GAS_MONEY|NO_RELAYER/.test(r))
71
+ return 'The hosted relayer cannot pay for gas right now. Try later, or set FIDES_RELAY=self and give the agent address a little ETH.';
72
+ return r;
73
+ }
74
+ // Paid APIs that have been bought through FIDES with real payments on mainnet (the docs list the same ones).
75
+ const KNOWN_SERVICES = [
76
+ { id: 'fides-demo', name: 'FIDES demo', url: 'https://api.fidesbuilder.xyz/v1/demo/chain-stats', price: '0.01 USDG', returns: 'Robinhood Chain\'s latest block, gas price and USDG supply', run_by: 'FIDES' },
77
+ { 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' },
78
+ { 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' },
79
+ ];
80
+ const ok = (value) => ({ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] });
81
+ const fail = (message) => ({ content: [{ type: 'text', text: message }], isError: true });
82
+ export function createFidesMcpServer(cfg) {
83
+ const agent = agentKey(cfg).account;
84
+ const client = createFidesClient({ chainId: cfg.chainId, rpc: cfg.rpc, addresses: cfg.addresses });
85
+ const explorer = EXPLORER[cfg.chainId];
86
+ const txLink = (h) => (h && explorer ? `${explorer}/tx/${h}` : h);
87
+ const plans = new Map();
88
+ const tokens = new Map();
89
+ const token = async (asset) => {
90
+ const key = asset.toLowerCase();
91
+ if (!tokens.has(key)) {
92
+ const [symbol, decimals] = await Promise.all([
93
+ client.public.readContract({ address: asset, abi: erc20Abi, functionName: 'symbol' }).catch(() => 'TOKEN'),
94
+ client.public.readContract({ address: asset, abi: erc20Abi, functionName: 'decimals' }).catch(() => 18),
95
+ ]);
96
+ tokens.set(key, { symbol: String(symbol), decimals: Number(decimals) });
97
+ }
98
+ return tokens.get(key);
99
+ };
100
+ const setupLink = `${cfg.app}/#/create?agent=${agent.address}&via=claude`;
101
+ const notLoaded = () => `No FIDES is signed for this agent yet. This computer's agent address is ${agent.address}.\n\n` +
102
+ `To set one up, the user opens this link: ${setupLink}\n` +
103
+ 'It opens the FIDES app with this agent filled in. They set the budget, contracts and expiry, sign with their wallet, then create and fund their FIDES Account. ' +
104
+ 'Then they come back here: the signed FIDES is picked up automatically, with nothing to download.';
105
+ function readFides() {
106
+ if (!fs.existsSync(cfg.fidesFile))
107
+ return null;
108
+ return fromJson(fs.readFileSync(cfg.fidesFile, 'utf8'));
109
+ }
110
+ const keep = (fides) => { fs.mkdirSync(path.dirname(cfg.fidesFile), { recursive: true }); fs.writeFileSync(cfg.fidesFile, toJson(fides, 2) + '\n'); };
111
+ // The FIDES to act under: the newest valid one signed for this agent, from the copy kept here or from
112
+ // the FIDES API, where the app registers a FIDES when the owner signs it. Each is verified onchain here.
113
+ async function currentFides() {
114
+ const mine = (o) => Boolean(o && String(o.agent).toLowerCase() === agent.address.toLowerCase() && Number(o.chainId) === cfg.chainId);
115
+ const local = readFides();
116
+ const candidates = mine(local) ? [local] : [];
117
+ if (cfg.directory) {
118
+ try {
119
+ const res = await fetch(`${cfg.directory}/v1/agents/${agent.address}/fides`);
120
+ if (res.ok)
121
+ for (const x of fromJson(await res.text()).fidesList ?? [])
122
+ if (mine(x.fides) && x.status?.valid)
123
+ candidates.push(x.fides);
124
+ }
125
+ catch { /* the API is optional: the copy kept here still works */ }
126
+ }
127
+ const valid = [];
128
+ for (const o of candidates)
129
+ if ((await Fides.verify(client, o).catch(() => null))?.valid)
130
+ valid.push(o);
131
+ valid.sort((a, b) => Number(BigInt(b.validAfter) - BigInt(a.validAfter)));
132
+ const best = valid[0];
133
+ if (best && (!local || local.id !== best.id)) {
134
+ keep(best);
135
+ plans.clear();
136
+ }
137
+ return best ?? local;
138
+ }
139
+ async function summary(fides) {
140
+ const t = await token(fides.spendLimits[0].asset);
141
+ const [check, [spend], balance, code] = await Promise.all([
142
+ Fides.verify(client, fides),
143
+ Policy.remainingSpend(client, fides),
144
+ client.public.readContract({ address: fides.spendLimits[0].asset, abi: erc20Abi, functionName: 'balanceOf', args: [fides.account] }),
145
+ client.public.getCode({ address: fides.account }),
146
+ ]);
147
+ const amount = (v) => `${formatUnits(v, t.decimals)} ${t.symbol}`;
148
+ const outside = await stray(fides.spendLimits[0].asset);
149
+ const status = check.revoked ? 'revoked' : check.expired ? 'expired' : check.notYetValid ? 'not valid yet' : !check.validSignature || !check.idMatches ? 'signature does not verify' : 'active';
150
+ return {
151
+ status,
152
+ fides: fides.id,
153
+ owner: fides.owner,
154
+ agent: fides.agent,
155
+ fides_account: fides.account,
156
+ fides_account_created: Boolean(code),
157
+ fides_account_balance: amount(balance),
158
+ budget: amount(spend.limit),
159
+ spent: amount(spend.spent),
160
+ remaining: amount(spend.remaining),
161
+ budget_token: fides.spendLimits[0].asset,
162
+ expires: new Date(Number(fides.validUntil) * 1000).toISOString(),
163
+ allowed_contracts: fides.targets.map((r) => ({ contract: r.target, functions: r.selectors.map((s) => SELECTORS[s.toLowerCase()] ? `${SELECTORS[s.toLowerCase()]} (${s})` : s) })),
164
+ allowed_services: fides.services.map((s) => s.serviceId),
165
+ max_actions: fides.maxActions.toString(),
166
+ relay: cfg.api ? `hosted relayer at ${cfg.api} pays the gas` : `the agent key ${agent.address} pays the gas`,
167
+ ...(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.` } : {}),
168
+ };
169
+ }
170
+ const server = new McpServer({ name: 'fides', version: '0.4.1' }, {
171
+ 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. ' +
172
+ '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, ' +
173
+ '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". ' +
174
+ 'When the user asks what you can do with FIDES, which paid APIs or services there are, or how to start, call fides_help and answer from it: give the paid APIs by name with their price and suggest a next step. ' +
175
+ 'A paid service\'s id is a name the user chooses when signing, like "spraay"; nobody issues it. Any API that charges USDG on Robinhood Chain over x402 (exact, EIP-3009) works, not only the ones fides_help lists.',
176
+ });
177
+ server.registerTool('fides_help', {
178
+ title: 'What FIDES can do',
179
+ description: 'Use when the user asks what you can do with FIDES, what paid APIs or services are available, what to try, or how to get started. Returns what FIDES can do, paid APIs known to work with their URL and price, example requests, and the next step for this user.',
180
+ inputSchema: {},
181
+ }, async () => {
182
+ let fides = null;
183
+ try {
184
+ fides = await currentFides();
185
+ }
186
+ catch { /* answer without it */ }
187
+ const allowed = fides?.services.map((s) => s.serviceId) ?? [];
188
+ return ok({
189
+ can_do: [
190
+ 'Send USDG from the FIDES Account, to one address or many, inside the budget. Several transfers go out as one transaction.',
191
+ 'Call contracts the FIDES allows, with the functions it allows.',
192
+ 'Buy from paid APIs that charge per request over x402, paying from the FIDES budget, each capped per service.',
193
+ 'Show the budget spent and left, what is allowed, the expiry, and every transaction executed, with explorer links.',
194
+ 'Everything is planned and simulated first, and shown to the user before anything is sent. Anything outside the FIDES is blocked. The hosted relayer pays the gas.',
195
+ ],
196
+ paid_apis: KNOWN_SERVICES.map((s) => ({ ...s, in_this_fides: allowed.includes(s.id) })),
197
+ other_paid_apis: 'Any API that charges USDG on Robinhood Chain over x402 (exact scheme, EIP-3009) works the same way. The user adds it as a service with a cap when signing (the id is any name they choose), then gives you its URL.',
198
+ try_saying: [
199
+ 'Set up FIDES with these paid services: fides-demo up to 0.1 USDG, spraay up to 0.01 USDG, madeonsol up to 0.1 USDG',
200
+ 'Pay 0.5 USDG to 0x... and 0.5 USDG to 0x...',
201
+ 'Buy the chain stats from the FIDES demo',
202
+ 'What\'s left in my FIDES?',
203
+ 'Show what you\'ve executed',
204
+ ],
205
+ next_step: !fides ? 'No FIDES is set up yet. Offer to call fides_setup, with any paid services the user wants and a cap for each.'
206
+ : !allowed.length ? 'This FIDES has no paid services. To use a paid API, call fides_setup with the services and caps, and the user signs the new FIDES.'
207
+ : `This FIDES allows these paid services: ${allowed.join(', ')}. Make sure the FIDES Account holds USDG (fides_status shows the balance).`,
208
+ docs: 'https://fidesbuilder.xyz/docs#/claude',
209
+ });
210
+ });
211
+ server.registerTool('fides_status', {
212
+ title: 'FIDES status',
213
+ description: 'The loaded FIDES: whether it is active, the budget spent and remaining, the FIDES Account balance, the allowed contracts and the expiry. Also gives this computer\'s agent address: use it when the user asks for their FIDES agent address. With no FIDES loaded, explains how to set one up.',
214
+ inputSchema: {},
215
+ }, async () => {
216
+ try {
217
+ const fides = await currentFides();
218
+ if (!fides)
219
+ return ok(notLoaded());
220
+ if (fides.agent.toLowerCase() !== agent.address.toLowerCase())
221
+ return fail(`The loaded FIDES is for agent ${fides.agent}, but this computer's agent key is ${agent.address}. Create a FIDES for ${agent.address}.`);
222
+ return ok(await summary(fides));
223
+ }
224
+ catch (err) {
225
+ return fail('Could not read the FIDES: ' + (err?.shortMessage ?? err?.message ?? err));
226
+ }
227
+ });
228
+ server.registerTool('fides_setup', {
229
+ title: 'Set up FIDES',
230
+ 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 x402 API, the id is any name the user chooses.',
231
+ inputSchema: { services: z.array(z.object({ id: z.string().describe('A name for the paid service, chosen by the user, e.g. "fides-demo" or "spraay"'), max: z.string().describe('Most the agent may spend on it, in USDG, e.g. "0.1"') })).optional() },
232
+ }, async ({ services }) => ok({
233
+ agent_address: agent.address,
234
+ link: setupLink + (services ?? []).map((x) => `&service=${encodeURIComponent(x.id)}:${encodeURIComponent(x.max)}`).join(''),
235
+ services: services?.length ? services.map((x) => `${x.id}: up to ${x.max} USDG`) : undefined,
236
+ steps: [
237
+ 'Open the link. It opens the FIDES app with this agent already filled in.',
238
+ 'Connect your wallet. Set the budget, the contracts allowed and the expiry. Start small.',
239
+ 'Sign (free), click Create FIDES Account (a little gas), and send the budget to the FIDES Account address shown.',
240
+ 'Come back and ask me for what you want. I pick up the signed FIDES by myself.',
241
+ ],
242
+ }));
243
+ server.registerTool('fides_use_file', {
244
+ title: 'Use a FIDES file',
245
+ 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.',
246
+ inputSchema: { path: z.string().describe('Full path to the fides.json file, e.g. C:\\Users\\me\\Downloads\\fides.json') },
247
+ }, async ({ path: file }) => {
248
+ try {
249
+ const full = file.replace(/^~(?=$|[\\/])/, os.homedir());
250
+ if (!fs.existsSync(full))
251
+ return fail(`No file at ${full}.`);
252
+ let fides;
253
+ try {
254
+ fides = fromJson(fs.readFileSync(full, 'utf8'));
255
+ }
256
+ catch {
257
+ return fail(`${full} is not valid JSON.`);
258
+ }
259
+ if (!fides || !fides.id || !fides.signature || !fides.agent || !Array.isArray(fides.spendLimits))
260
+ return fail('That file is not a signed FIDES. In the FIDES app, open the FIDES and click "Download signed FIDES".');
261
+ if (fides.agent.toLowerCase() !== agent.address.toLowerCase())
262
+ return fail(`That FIDES is for agent ${fides.agent}, but this computer's agent key is ${agent.address}. Create a new FIDES in the app with "An external agent" and paste ${agent.address}.`);
263
+ if (Number(fides.chainId) !== cfg.chainId)
264
+ return fail(`That FIDES is for chain ${fides.chainId}; this server is set up for chain ${cfg.chainId}.`);
265
+ keep(fides);
266
+ plans.clear();
267
+ return ok({ loaded: cfg.fidesFile, ...(await summary(fides)) });
268
+ }
269
+ catch (err) {
270
+ return fail('Could not load the FIDES: ' + (err?.shortMessage ?? err?.message ?? err));
271
+ }
272
+ });
273
+ const actionSchema = z.object({
274
+ kind: z.enum(['transfer', 'call']).describe('"transfer" sends a token from the FIDES Account. "call" calls an allowed contract with raw calldata.'),
275
+ to: z.string().optional().describe('transfer: the recipient address'),
276
+ 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'),
277
+ token: z.string().optional().describe('transfer: the token address. Defaults to the FIDES\'s budget token'),
278
+ target: z.string().optional().describe('call: the contract address'),
279
+ data: z.string().optional().describe('call: hex calldata, starting with the 4-byte function selector'),
280
+ label: z.string().optional().describe('A short description shown in the plan'),
281
+ });
282
+ server.registerTool('fides_plan', {
283
+ title: 'Plan actions under the FIDES',
284
+ 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.',
285
+ inputSchema: { actions: z.array(actionSchema).min(1).max(50) },
286
+ }, async ({ actions }) => {
287
+ try {
288
+ const fides = await currentFides();
289
+ if (!fides)
290
+ return fail(notLoaded());
291
+ if (fides.agent.toLowerCase() !== agent.address.toLowerCase())
292
+ return fail(`The loaded FIDES is for agent ${fides.agent}, not this computer's agent key ${agent.address}.`);
293
+ const budget = fides.spendLimits[0].asset;
294
+ const built = [];
295
+ for (const [i, a] of actions.entries()) {
296
+ const n = i + 1;
297
+ if (a.kind === 'transfer') {
298
+ if (!a.to || !isAddress(a.to))
299
+ return fail(`Action ${n}: "to" must be an address.`);
300
+ const asset = a.token ? (isAddress(a.token) ? getAddress(a.token) : null) : budget;
301
+ if (!asset)
302
+ return fail(`Action ${n}: "token" must be an address.`);
303
+ const t = await token(asset);
304
+ let amount;
305
+ try {
306
+ amount = parseUnits(String(a.amount ?? ''), t.decimals);
307
+ }
308
+ catch {
309
+ return fail(`Action ${n}: "amount" must be a number of ${t.symbol}, like "1.5".`);
310
+ }
311
+ if (amount <= 0n)
312
+ return fail(`Action ${n}: "amount" must be more than zero.`);
313
+ built.push({ id: `a${n}`, type: 'TOKEN_TRANSFER', label: a.label ?? `send ${formatUnits(amount, t.decimals)} ${t.symbol} to ${getAddress(a.to)}`, target: asset, spend: { asset, amount },
314
+ payload: encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [getAddress(a.to), amount] }) });
315
+ }
316
+ else {
317
+ if (!a.target || !isAddress(a.target))
318
+ return fail(`Action ${n}: "target" must be a contract address.`);
319
+ if (!a.data || !/^0x([0-9a-fA-F]{2}){4,}$/.test(a.data))
320
+ return fail(`Action ${n}: "data" must be hex calldata starting with the 4-byte selector.`);
321
+ const t = await token(budget);
322
+ let spend = 0n;
323
+ if (a.amount) {
324
+ try {
325
+ spend = parseUnits(a.amount, t.decimals);
326
+ }
327
+ catch {
328
+ return fail(`Action ${n}: "amount" must be a number of ${t.symbol}.`);
329
+ }
330
+ }
331
+ built.push({ id: `a${n}`, type: 'CONTRACT_WRITE', label: a.label ?? `call ${a.data.slice(0, 10)} on ${getAddress(a.target)}`, target: getAddress(a.target), payload: a.data,
332
+ ...(spend > 0n ? { spend: { asset: budget, amount: spend } } : {}) });
333
+ }
334
+ }
335
+ const plan = await new FidesBuilder({ client, fides, agent }).add(built).build();
336
+ const savings = plan.estimatedSavings();
337
+ const simulations = savings.estimatedTransactions ? await plan.simulate() : [];
338
+ const failed = simulations.filter((s) => !s.success);
339
+ const id = plan.planHash.slice(0, 18);
340
+ if (savings.estimatedTransactions && !failed.length)
341
+ plans.set(id, { plan, actions: built, fides });
342
+ return ok({
343
+ plan_id: savings.estimatedTransactions && !failed.length ? id : null,
344
+ ready_to_execute: Boolean(savings.estimatedTransactions && !failed.length),
345
+ actions: plan.actions().map((a) => ({ action: a.label, decision: a.decision === 'AUTHORIZED' ? 'inside the FIDES' : a.decision === 'REJECTED' ? `blocked: ${REASONS[a.reason ?? ''] ?? a.reason} (${a.reason})` : 'duplicate, sent once' })),
346
+ transactions: `${savings.baselineTransactions} onchain write(s) go out as ${savings.estimatedTransactions} transaction(s)`,
347
+ simulation: !simulations.length ? 'nothing to execute: no action is inside the FIDES' : failed.length ? `would fail: ${failure(failed[0].revertReason)}` : 'simulated against the chain: it would succeed',
348
+ sent: 'nothing has been sent',
349
+ });
350
+ }
351
+ catch (err) {
352
+ return fail('Could not build the plan: ' + (err?.shortMessage ?? err?.message ?? err));
353
+ }
354
+ });
355
+ async function post(route, body) {
356
+ const res = await fetch(cfg.api + route, { method: 'POST', headers: { 'content-type': 'application/json' }, body: toJson(body) });
357
+ const json = fromJson(await res.text());
358
+ if (!res.ok || json?.error)
359
+ throw new Error(json?.error?.code ? `${json.error.code}: ${json.error.message}` : `HTTP ${res.status}`);
360
+ return json;
361
+ }
362
+ /** Sends a plan's bundles: through the hosted relayer, or paid by the agent key with FIDES_RELAY=self. */
363
+ async function send(plan, actions, fides) {
364
+ if (!cfg.api)
365
+ return (await plan.execute()).bundles;
366
+ const stored = await post('/v1/plans/build', { fides, actions });
367
+ if (stored.planHash !== plan.planHash)
368
+ throw new Error('The relayer built a different plan from the same actions, so nothing was sent.');
369
+ return (await post(`/v1/plans/${plan.planHash}/execute`, { bundles: await plan.signedBundles() })).bundles;
370
+ }
371
+ // What each paid service has cost so far, while this server runs. The onchain limit is the budget;
372
+ // the per-service caps are checked here, before anything is signed.
373
+ const servicePaid = new Map();
374
+ // The float: what this tool moved to the agent key through a FIDES and has not spent yet, by FIDES and
375
+ // token. Kept on disk so a restart does not forget it. Only this is ever used to pay; anything else on
376
+ // the agent key (someone sent it there directly) did not pass through the FIDES, so it is left alone.
377
+ const floatFile = path.join(cfg.home, 'float.json');
378
+ const floats = () => { try {
379
+ return JSON.parse(fs.readFileSync(floatFile, 'utf8'));
380
+ }
381
+ catch {
382
+ return {};
383
+ } };
384
+ const floatKey = (fides, asset) => `${fides.id}:${asset.toLowerCase()}`;
385
+ const floatOf = (fides, asset) => BigInt(floats()[floatKey(fides, asset)] ?? '0');
386
+ const setFloat = (fides, asset, v) => { const m = floats(); m[floatKey(fides, asset)] = (v > 0n ? v : 0n).toString(); fs.mkdirSync(cfg.home, { recursive: true }); fs.writeFileSync(floatFile, JSON.stringify(m, null, 2)); };
387
+ /** What sits on the agent key that did not come through any FIDES by this tool. */
388
+ const stray = async (asset) => {
389
+ const held = await client.public.readContract({ address: asset, abi: erc20Abi, functionName: 'balanceOf', args: [agent.address] });
390
+ const ours = Object.entries(floats()).filter(([k]) => k.endsWith(':' + asset.toLowerCase())).reduce((n, [, v]) => n + BigInt(v), 0n);
391
+ return held > ours ? held - ours : 0n;
392
+ };
393
+ server.registerTool('fides_pay_request', {
394
+ title: 'Buy from a paid API',
395
+ description: `Fetches a URL from a paid API that charges per request over x402, paying from the FIDES budget. Reads the price first and refuses if it is over the service\'s cap in the FIDES or over max_price. Moves exactly the price from the FIDES Account to the agent key, pays, and returns the response. The service must be in the FIDES (its services list). Known URLs: ${KNOWN_SERVICES.map((s) => `"${s.id}" ${s.url}`).join(', ')}. Tell the user the price and get their OK before paying unless they already asked you to buy it.`,
396
+ inputSchema: {
397
+ url: z.string().describe('The paid API URL'),
398
+ service: z.string().optional().describe('The service id in the FIDES. Can be left out if the FIDES has exactly one service'),
399
+ max_price: z.string().optional().describe('Refuse if the price is above this, in USDG'),
400
+ },
401
+ }, async ({ url, service, max_price }) => {
402
+ try {
403
+ const fides = await currentFides();
404
+ if (!fides)
405
+ return fail(notLoaded());
406
+ if (!fides.services.length)
407
+ 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" }].`);
408
+ const id = service ?? (fides.services.length === 1 ? fides.services[0].serviceId : undefined);
409
+ if (!id)
410
+ return fail(`Say which service this is. The FIDES allows: ${fides.services.map((x) => x.serviceId).join(', ')}.`);
411
+ const rule = fides.services.find((x) => x.serviceId === id);
412
+ if (!rule)
413
+ return fail(`"${id}" is not a service in this FIDES. It allows: ${fides.services.map((x) => x.serviceId).join(', ')}. Anything else is blocked.`);
414
+ // 1. the price
415
+ let first;
416
+ try {
417
+ first = await fetch(url);
418
+ }
419
+ catch (err) {
420
+ return fail('Could not reach ' + url + ': ' + (err?.message ?? err));
421
+ }
422
+ const header = first.headers.get('PAYMENT-REQUIRED');
423
+ if (first.status !== 402 || !header) {
424
+ const text = await first.text();
425
+ return ok({ paid: 'nothing: this URL did not ask for payment', status: first.status, response: text.slice(0, 4000) });
426
+ }
427
+ const required = decodePaymentRequired(header);
428
+ const offer = required.accepts.find((r) => exactEip3009.supports(r, { fides, payer: agent }) && fides.spendLimits.some((l) => l.asset.toLowerCase() === r.asset.toLowerCase()));
429
+ if (!offer)
430
+ return fail('This service asks for a payment FIDES cannot make from this FIDES: it needs the budget token, paid with EIP-3009 on this chain. Nothing was paid.');
431
+ const t = await token(offer.asset);
432
+ const price = BigInt(offer.amount);
433
+ const shown = `${formatUnits(price, t.decimals)} ${t.symbol}`;
434
+ if (max_price) {
435
+ let cap;
436
+ try {
437
+ cap = parseUnits(max_price, t.decimals);
438
+ }
439
+ catch {
440
+ return fail('max_price must be a number, like "0.05".');
441
+ }
442
+ if (price > cap)
443
+ return fail(`Blocked: the price is ${shown}, over your max of ${max_price}. Nothing was paid.`);
444
+ }
445
+ const paidSoFar = servicePaid.get(id) ?? 0n;
446
+ if (paidSoFar + price > BigInt(rule.maxSpend))
447
+ return fail(`Blocked: ${shown} would take "${id}" past its cap of ${formatUnits(BigInt(rule.maxSpend), t.decimals)} ${t.symbol} in this FIDES (${formatUnits(paidSoFar, t.decimals)} spent). Nothing was paid.`);
448
+ // 2. fund the agent key through the FIDES: reuse only float this tool moved there under this FIDES
449
+ const held = await client.public.readContract({ address: offer.asset, abi: erc20Abi, functionName: 'balanceOf', args: [agent.address] });
450
+ let float = floatOf(fides, offer.asset);
451
+ if (float > held)
452
+ float = held;
453
+ const x402 = createX402({ fides, payer: agent, serviceId: () => id });
454
+ let funded;
455
+ if (float < price) {
456
+ const actions = [x402.float(offer.asset, price - float, 'pay-float')];
457
+ const plan = await new FidesBuilder({ client, fides, agent }).add(actions).build();
458
+ if (plan.rejected().length) {
459
+ const r = plan.rejected()[0];
460
+ return fail(`Blocked by the FIDES: ${REASONS[r.reason ?? ''] ?? r.reason}. Nothing was paid.`);
461
+ }
462
+ const [sim] = await plan.simulate();
463
+ if (sim && !sim.success)
464
+ return fail('Blocked: ' + failure(sim.revertReason) + ' Nothing was paid.');
465
+ const bundles = await send(plan, actions, fides);
466
+ if (!bundles.length || bundles.some((b) => b.status !== 'EXECUTED'))
467
+ return fail('Could not move the price to the agent key: ' + failure(bundles.find((b) => b.error)?.error) + ' Nothing was paid.');
468
+ funded = txLink(bundles[0].txHash);
469
+ float = price;
470
+ setFloat(fides, offer.asset, float);
471
+ }
472
+ // 3. pay and fetch
473
+ const res = await x402.fetch(url);
474
+ const body = await res.text();
475
+ const p = x402.payments[0];
476
+ // No confirmed payment (a 402, or an error before the service settled): the price moved to the agent
477
+ // key is still there, recorded as float, and pays for the next request instead of moving it again.
478
+ if (!p)
479
+ return fail(`The service did not take the payment (it answered ${res.status}${body ? ': ' + body.slice(0, 200) : ''}), so nothing was paid. ${funded ? 'The price already moved to the agent key stays there and pays for the next attempt, so a retry does not move it again.' : ''}`);
480
+ if (p) {
481
+ servicePaid.set(id, paidSoFar + p.amount);
482
+ setFloat(fides, offer.asset, float - p.amount);
483
+ }
484
+ let response = body.slice(0, 4000);
485
+ try {
486
+ response = JSON.parse(body);
487
+ }
488
+ catch { }
489
+ const [spend] = await Policy.remainingSpend(client, fides);
490
+ return ok({
491
+ paid: p ? shown : 'nothing',
492
+ service: id,
493
+ settlement: txLink(p?.settlement?.transaction),
494
+ moved_to_agent_key: funded,
495
+ status: res.status,
496
+ response,
497
+ budget_remaining: `${formatUnits(spend.remaining, t.decimals)} of ${formatUnits(spend.limit, t.decimals)} ${t.symbol}`,
498
+ });
499
+ }
500
+ catch (err) {
501
+ return fail('Paid request stopped: ' + failure(err?.shortMessage ?? err?.message ?? err));
502
+ }
503
+ });
504
+ server.registerTool('fides_execute', {
505
+ title: 'Execute a plan',
506
+ description: 'Executes a plan made by fides_plan, by its plan_id. Only the actions inside the FIDES are sent; blocked ones never are. Moves real funds from the FIDES Account: confirm with the user first.',
507
+ inputSchema: { plan_id: z.string() },
508
+ }, async ({ plan_id }) => {
509
+ const entry = plans.get(plan_id);
510
+ if (!entry)
511
+ return fail('No plan with that id. Make one with fides_plan first (plans are kept until they are executed or the server restarts).');
512
+ plans.delete(plan_id); // a plan's bundles can be sent once
513
+ const { plan, actions, fides } = entry;
514
+ try {
515
+ const bundles = await send(plan, actions, fides);
516
+ const [spend] = await Policy.remainingSpend(client, fides);
517
+ const t = await token(fides.spendLimits[0].asset);
518
+ const executed = bundles.filter((b) => b.status === 'EXECUTED').length;
519
+ return ok({
520
+ result: executed === bundles.length ? 'executed' : executed ? 'partly executed' : 'not executed',
521
+ transactions: bundles.map((b) => ({ status: b.status.toLowerCase(), transaction: txLink(b.txHash), ...(b.error ? { reason: failure(b.error) } : {}) })),
522
+ blocked_and_not_sent: plan.rejected().map((a) => `${a.label}: ${REASONS[a.reason ?? ''] ?? a.reason}`),
523
+ budget_remaining: `${formatUnits(spend.remaining, t.decimals)} of ${formatUnits(spend.limit, t.decimals)} ${t.symbol}`,
524
+ });
525
+ }
526
+ catch (err) {
527
+ return fail('Execution stopped: ' + failure(err?.shortMessage ?? err?.message ?? err));
528
+ }
529
+ });
530
+ server.registerTool('fides_receipts', {
531
+ title: 'Executed transactions',
532
+ description: 'Every bundle executed onchain under the loaded FIDES, with transaction links, and the budget spent.',
533
+ inputSchema: {},
534
+ }, async () => {
535
+ try {
536
+ const fides = await currentFides();
537
+ if (!fides)
538
+ return fail(notLoaded());
539
+ const [list, [spend], t] = await Promise.all([Receipts.get(client, fides), Policy.remainingSpend(client, fides), token(fides.spendLimits[0].asset)]);
540
+ return ok({
541
+ executed: list.map((r) => ({ transaction: txLink(r.txHash), calls_succeeded: r.succeeded, calls_failed: r.failed, block: r.blockNumber.toString() })),
542
+ spent: `${formatUnits(spend.spent, t.decimals)} of ${formatUnits(spend.limit, t.decimals)} ${t.symbol}`,
543
+ });
544
+ }
545
+ catch (err) {
546
+ return fail('Could not read receipts: ' + (err?.shortMessage ?? err?.message ?? err));
547
+ }
548
+ });
549
+ return Object.assign(server, { agent: agent.address });
550
+ }
551
+ function setup(cfg) {
552
+ const { account, file, created } = agentKey(cfg);
553
+ const win = process.platform === 'win32';
554
+ const desktop = JSON.stringify({ mcpServers: { fides: win ? { command: 'cmd', args: ['/c', 'npx', '-y', '@fidesbuild/mcp-server@latest'] } : { command: 'npx', args: ['-y', '@fidesbuild/mcp-server@latest'] } } }, null, 2);
555
+ console.log(`FIDES for Claude
556
+
557
+ Agent address: ${account.address}
558
+ ${file ? `${created ? 'New key saved' : 'Key'} in ${file}. Keep that file private. It holds no funds and can only act inside the FIDES you sign for it.` : 'Key from FIDES_AGENT_KEY.'}
559
+
560
+ Next:
561
+ 1. Open this link. It opens the FIDES app with this agent filled in:
562
+ ${cfg.app}/#/create?agent=${account.address}&via=claude
563
+ 2. Set the budget, contracts and expiry, then sign. Create your FIDES Account and fund it.
564
+ 3. Add FIDES to Claude, if you have not yet.
565
+ Claude Code, in a terminal:
566
+ claude mcp add fides -- ${win ? 'cmd /c ' : ''}npx -y @fidesbuild/mcp-server@latest
567
+ Claude Desktop: Settings, Developer, Edit Config, and add:
568
+ ${desktop.split('\n').map((l) => ' ' + l).join('\n')}
569
+ Then restart Claude Desktop.
570
+ 4. Ask Claude for what you want. It picks up the signed FIDES by itself.
571
+ `);
572
+ }
573
+ // Run directly, or through the npm bin link (which may be a symlink to this file).
574
+ const entry = (() => { try {
575
+ return pathToFileURL(fs.realpathSync(process.argv[1] ?? '')).href;
576
+ }
577
+ catch {
578
+ return '';
579
+ } })();
580
+ if (import.meta.url === entry) {
581
+ const cfg = configFromEnv();
582
+ if (process.argv[2] === 'setup')
583
+ setup(cfg);
584
+ else if (process.argv[2] === '--help' || process.argv[2] === '-h')
585
+ console.log('npx -y @fidesbuild/mcp-server setup make the agent key and print the next steps\nnpx -y @fidesbuild/mcp-server run the MCP server over stdio\n\nEnvironment: FIDES_HOME, FIDES_FILE, FIDES_RPC, FIDES_CHAIN_ID, FIDES_API, FIDES_APP, FIDES_RELAY=self, FIDES_LOOKUP=off, FIDES_AGENT_KEY, FIDES_ADDRESSES');
586
+ else {
587
+ const server = createFidesMcpServer(cfg);
588
+ await server.connect(new StdioServerTransport());
589
+ console.error(`fides-mcp running. Agent address ${server.agent}.`);
590
+ }
591
+ }
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@fidesbuild/mcp-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.4.1",
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
+ "keywords": [
6
+ "fides",
7
+ "agents",
8
+ "transaction-builder",
9
+ "mcp",
10
+ "claude",
11
+ "robinhood-chain",
12
+ "eip-712"
13
+ ],
14
+ "homepage": "https://fidesbuilder.xyz/docs",
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/server.d.ts",
20
+ "default": "./dist/server.js"
21
+ }
22
+ },
23
+ "bin": {
24
+ "fides-mcp": "./dist/server.js"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "!dist/*.tsbuildinfo",
29
+ "!dist/.tsbuildinfo"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20.10"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.32.1",
39
+ "@fidesbuild/sdk": "0.2.0",
40
+ "viem": "^2.21.0",
41
+ "zod": "^4.6.5",
42
+ "@fidesbuild/x402": "0.2.0"
43
+ }
6
44
  }