@fidesbuild/mcp-server 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/server.js +48 -21
  2. package/package.json +3 -2
package/dist/server.js CHANGED
@@ -21,6 +21,7 @@ import { createX402, decodePaymentRequired, exactEip3009 } from '@fidesbuild/x40
21
21
  import { netObligations, settle, netRoots, PUBLISH_SELECTOR, NetError } from '@fidesbuild/net';
22
22
  import { createSubClient, SubFidesKit, SubAgent, SubFidesError, subDeployments } from '@fidesbuild/sub';
23
23
  import { quoteUniswap, checkMandate, swapActions, verifySwap, describeRoute, mandateOf, uniswap, NATIVE, SwapError } from '@fidesbuild/swap';
24
+ import { route } from '@fidesbuild/router';
24
25
  export function configFromEnv(env = process.env) {
25
26
  const home = env.FIDES_HOME ?? path.join(os.homedir(), '.fides');
26
27
  return {
@@ -822,7 +823,7 @@ export function createFidesMcpServer(cfg) {
822
823
  const tokenArg = (t) => (/^eth$/i.test(t) ? 'ETH' : /^weth$/i.test(t) ? uniswap[cfg.chainId]?.weth : isAddress(t) ? getAddress(t) : null);
823
824
  server.registerTool('fides_swap', {
824
825
  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
+ description: 'Swaps on Uniswap on Robinhood Chain with FIDES ROUTER: gets a route through each kind of pool (v2, v3 and v4, including v4 pools with hooks), checks each against the FIDES and the trading mandate the owner signed (tokens it may buy, worst price, most per swap), simulates them, ranks them by output, and offers only the best valid one. Show the user routes_compared (the ranking, how far behind each route is, and why any are out) with the chosen route, before executing. 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
827
  inputSchema: {
827
828
  token_out: z.string().describe('What to buy: "ETH", "WETH" or a token address'),
828
829
  amount: z.string().describe('How much of the budget token to spend, e.g. "1"'),
@@ -850,28 +851,51 @@ export function createFidesMcpServer(cfg) {
850
851
  return fail(`"amount" must be a number of ${tIn.symbol}, like "1".`);
851
852
  }
852
853
  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);
854
+ if (!cfg.api && !process.env.UNISWAP_API_KEY)
855
+ return fail('Swaps need the FIDES API, or UNISWAP_API_KEY when running without it.');
856
+ // FIDES ROUTER: a route through each kind of Uniswap pool, each checked against the trading mandate and the
857
+ // FIDES, simulated and ranked by output. Only the best valid one is offered to execute.
858
+ const POOLS = [['uniswap v2', ['V2']], ['uniswap v3', ['V3']], ['uniswap v4', ['V4']]];
859
+ const quotes = new Map(), excluded = [];
860
+ await Promise.all(POOLS.map(async ([name, protocols]) => {
861
+ try {
862
+ let q, blocked;
863
+ if (cfg.api) {
864
+ const r = await post('/v1/swap/quote', { fides, tokenOut: out, amount: value, slippageBps, protocols });
865
+ q = r.quote;
866
+ blocked = r.mandate?.allowed ? null : { code: r.mandate.code, message: r.mandate.message };
867
+ }
868
+ else {
869
+ q = await quoteUniswap({ apiKey: process.env.UNISWAP_API_KEY, fides, tokenOut: out, amount: value, slippageBps, protocols });
870
+ blocked = checkMandate(fides, q);
871
+ }
872
+ if (blocked)
873
+ excluded.push({ route: name, why: `trading mandate: ${blocked.message}` });
874
+ else
875
+ quotes.set(name, q);
876
+ }
877
+ catch (err) {
878
+ excluded.push({ route: name, why: `no route: ${err?.message ?? err}` });
879
+ }
880
+ }));
881
+ if (!quotes.size) {
882
+ const mandate = excluded.find((x) => x.why.startsWith('trading mandate'));
883
+ return fail(mandate ? `Blocked by the ${mandate.why} Nothing was sent.` : `Uniswap has no route for this swap: ${excluded.map((x) => `${x.route} (${x.why})`).join('; ')}. Nothing was sent.`);
864
884
  }
865
- const outInfo = q.tokenOut === NATIVE ? { symbol: 'ETH', decimals: 18 } : await token(q.tokenOut);
885
+ const first = [...quotes.values()][0];
886
+ const outInfo = first.tokenOut === NATIVE ? { symbol: 'ETH', decimals: 18 } : await token(first.tokenOut);
866
887
  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
- }
888
+ const label = `swap ${amount} ${tIn.symbol} to ${outInfo.symbol} on Uniswap`;
889
+ const routed = await route({ client, fides, agent }, [...quotes].map(([name, q]) => ({ name, actions: swapActions(q, label), expectedOut: q.quotedOut, minOut: q.minOut })));
890
+ const ranking = [
891
+ ...routed.ranked.map((x) => x.valid
892
+ ? { route: x.name, verdict: x === routed.best ? 'best, chosen' : 'valid', quoted: fmt(x.expectedOut ?? 0n), behind_best: `${(x.slippageBps ?? 0) / 100}%`, gas: String(x.gas), pools: describeRoute(quotes.get(x.name)) }
893
+ : { route: x.name, verdict: 'out', why: REASONS[x.reasons[0]?.split(': ')[1] ?? ''] ?? x.reasons[0] }),
894
+ ...excluded.map((x) => ({ route: x.route, verdict: 'out', why: x.why })),
895
+ ];
896
+ if (!routed.best)
897
+ return fail(`No route is inside the FIDES, so nothing can run: ${ranking.map((x) => `${x.route} (${x.why})`).join('; ')}. Nothing was sent.`);
898
+ const q = quotes.get(routed.best.name), plan = routed.plan(routed.best.name), actions = swapActions(q, label);
875
899
  const v = await verifySwap(client, fides, plan, q);
876
900
  const id = plan.planHash.slice(0, 18);
877
901
  if (v.ok)
@@ -879,6 +903,9 @@ export function createFidesMcpServer(cfg) {
879
903
  return ok({
880
904
  plan_id: v.ok ? id : null,
881
905
  ready_to_execute: v.ok,
906
+ router: `FIDES ROUTER compared ${ranking.length} routes, one per kind of Uniswap pool: each checked against the FIDES and the trading mandate, simulated, and ranked by output. Only the chosen route runs.`,
907
+ routes_compared: ranking,
908
+ chosen: routed.best.name,
882
909
  route: describeRoute(q),
883
910
  spend: `${amount} ${tIn.symbol}`,
884
911
  quoted: fmt(q.quotedOut),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidesbuild/mcp-server",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
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",
@@ -42,6 +42,7 @@
42
42
  "@fidesbuild/x402": "0.2.0",
43
43
  "@fidesbuild/net": "0.2.1",
44
44
  "@fidesbuild/sub": "0.2.0",
45
- "@fidesbuild/swap": "0.2.0"
45
+ "@fidesbuild/swap": "0.2.1",
46
+ "@fidesbuild/router": "0.2.0"
46
47
  }
47
48
  }