@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.
- package/dist/server.js +48 -21
- 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: '
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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.
|
|
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.
|
|
45
|
+
"@fidesbuild/swap": "0.2.1",
|
|
46
|
+
"@fidesbuild/router": "0.2.0"
|
|
46
47
|
}
|
|
47
48
|
}
|