@fidesbuild/mcp-server 0.9.0 → 0.10.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 +138 -17
  2. package/package.json +3 -2
package/dist/server.js CHANGED
@@ -22,6 +22,7 @@ import { netObligations, settle, netRoots, PUBLISH_SELECTOR, NetError } from '@f
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
24
  import { route } from '@fidesbuild/router';
25
+ import { LongAgent, LongFides, isLong } from '@fidesbuild/long';
25
26
  export function configFromEnv(env = process.env) {
26
27
  const home = env.FIDES_HOME ?? path.join(os.homedir(), '.fides');
27
28
  return {
@@ -142,6 +143,23 @@ export function createFidesMcpServer(cfg) {
142
143
  const subClient = subAddrs ? createSubClient({ chainId: cfg.chainId, rpc: cfg.rpc, base: cfg.addresses, addresses: subAddrs }) : null;
143
144
  const subPlans = new Map();
144
145
  const subsDir = path.join(cfg.home, 'subs');
146
+ // LONG FIDES: the terms (period, budget per period, calls per period) come with the FIDES from the API,
147
+ // and are kept here so a FIDES loaded from the file can still run.
148
+ const longFile = path.join(cfg.home, 'long.json');
149
+ const longTerms = new Map(Object.entries(fs.existsSync(longFile) ? fromJson(fs.readFileSync(longFile, 'utf8')) : {}));
150
+ const keepTerms = (id, terms) => { longTerms.set(id, terms); fs.mkdirSync(cfg.home, { recursive: true }); fs.writeFileSync(longFile, toJson(Object.fromEntries(longTerms), 2) + '\n'); };
151
+ const longPlans = new Map();
152
+ /** The agent for a LONG FIDES, or a message saying why it cannot run. */
153
+ const longAgentFor = (fides) => {
154
+ const terms = longTerms.get(fides.id);
155
+ if (!subClient)
156
+ return `LONG FIDES is not deployed on chain ${cfg.chainId}.`;
157
+ if (!terms)
158
+ return 'This is a LONG FIDES, but its terms (the budget per period) are not here. The app shares them when the owner signs; ask the user to open it in the FIDES app once, or sign it again.';
159
+ return new LongAgent({ client: subClient, fides, terms, agent, ...(cfg.api ? { api: cfg.api } : { relayer: agent }) });
160
+ };
161
+ const every = (t) => ({ 3600: 'hour', 86400: 'day', 604800: 'week', 2592000: '30 days' }[String(t.period)] ?? `${Number(t.period) / 3600} hours`);
162
+ const noLong = (what) => `This is a LONG FIDES. It swaps, pays paid APIs, sends and calls contracts inside its budget per period, but it does not ${what}. For that, the user signs an ordinary FIDES (fides_setup without keeps_going).`;
145
163
  const token = async (asset) => {
146
164
  const key = asset.toLowerCase();
147
165
  if (!tokens.has(key)) {
@@ -175,8 +193,11 @@ export function createFidesMcpServer(cfg) {
175
193
  const res = await fetch(`${cfg.directory}/v1/agents/${agent.address}/fides`);
176
194
  if (res.ok)
177
195
  for (const x of fromJson(await res.text()).fidesList ?? [])
178
- if (mine(x.fides) && x.status?.valid)
196
+ if (mine(x.fides) && x.status?.valid) {
179
197
  candidates.push(x.fides);
198
+ if (x.long)
199
+ keepTerms(x.fides.id, x.long);
200
+ }
180
201
  }
181
202
  catch { /* the API is optional: the copy kept here still works */ }
182
203
  }
@@ -203,9 +224,16 @@ export function createFidesMcpServer(cfg) {
203
224
  const amount = (v) => `${formatUnits(v, t.decimals)} ${t.symbol}`;
204
225
  const outside = await stray(fides.spendLimits[0].asset);
205
226
  const status = check.revoked ? 'revoked' : check.expired ? 'expired' : check.notYetValid ? 'not valid yet' : !check.validSignature || !check.idMatches ? 'signature does not verify' : 'active';
227
+ const terms = isLong(fides) ? longTerms.get(fides.id) : undefined;
228
+ const period = terms && subClient ? await LongFides.status(subClient, fides, terms).catch(() => null) : null;
206
229
  return {
207
230
  status,
208
231
  fides: fides.id,
232
+ ...(isLong(fides) ? { keeps_going: !terms ? 'a LONG FIDES, but its terms are not here yet' : {
233
+ budget_per_period: `${amount(terms.perPeriod[0].amount)} every ${every(terms)}`,
234
+ this_period: period ? { spent: amount(period.thisPeriod[0].spent), left: amount(period.thisPeriod[0].remaining), calls: `${period.actions.used} of ${terms.maxActionsPerPeriod}`, resets_at: new Date(Number(period.resetsAt) * 1000).toISOString() } : 'unknown',
235
+ note: 'The budget per period resets by itself. The total budget is the most it can ever spend.',
236
+ } } : {}),
209
237
  owner: fides.owner,
210
238
  agent: fides.agent,
211
239
  fides_account: fides.account,
@@ -223,12 +251,13 @@ export function createFidesMcpServer(cfg) {
223
251
  ...(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.` } : {}),
224
252
  };
225
253
  }
226
- const server = new McpServer({ name: 'fides', version: '0.8.0' }, {
254
+ const server = new McpServer({ name: 'fides', version: '0.10.0' }, {
227
255
  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. ' +
228
256
  '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, ' +
229
257
  '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". ' +
230
258
  '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. ' +
231
259
  '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. ' +
260
+ 'For a LONG FIDES (one that keeps going, with a budget per period), plans are checked against what is left this period and in total; if this period\'s budget is used up, say when it resets. ' +
232
261
  'When the user has many payments owed between their agent and services (or other parties), fides_net nets them to final positions and builds the FIDES Account\'s side of the settlement, plus the publication of the round\'s roots onchain, into one plan for fides_execute.',
233
262
  });
234
263
  server.registerTool('fides_help', {
@@ -250,6 +279,7 @@ export function createFidesMcpServer(cfg) {
250
279
  'Buy from paid APIs that charge per request over x402, paying from the FIDES budget, each capped per service.',
251
280
  '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.',
252
281
  '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.',
282
+ 'Keep going for weeks on one signature (LONG FIDES): a budget per hour, day, week or month that resets by itself, a total it can never pass and an end date. fides_setup with keeps_going; then fides_plan, fides_swap and fides_pay_request work inside this period\'s budget, and fides_status shows what is left this period and when it resets.',
253
283
  '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.',
254
284
  'Show the budget spent and left, what is allowed, the expiry, and every transaction executed, with explorer links.',
255
285
  '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.',
@@ -302,8 +332,15 @@ export function createFidesMcpServer(cfg) {
302
332
  max_slippage_percent: z.number().positive().max(50).optional().describe('The worst price it may accept against the quote, in percent. Default 1'),
303
333
  max_per_swap: z.string().optional().describe('The most one swap may spend, in the budget token. Default "1"'),
304
334
  }).optional().describe('Let the agent swap on Uniswap inside a trading mandate the owner signs; fides_swap needs it'),
335
+ keeps_going: z.object({
336
+ every: z.enum(['hour', 'day', 'week', 'month']).describe('How often the budget resets'),
337
+ per_period: z.string().describe('The most it may spend each period, in USDG, e.g. "5"'),
338
+ days: z.number().positive().max(365).optional().describe('How long it keeps going, in days. Default 30'),
339
+ }).optional().describe('A LONG FIDES: one signature, and the agent keeps going period after period, with a budget per period that resets by itself, a total it can never pass and an end date. It cannot be combined with delegate or net.'),
305
340
  },
306
- }, async ({ services, net, delegate, swap, contracts }) => {
341
+ }, async ({ services, net, delegate, swap, contracts, keeps_going }) => {
342
+ if (keeps_going && (delegate || net))
343
+ return fail('A LONG FIDES cannot hand out slices or settle with NET. Leave out delegate and net, or sign an ordinary FIDES for those.');
307
344
  let targets = '';
308
345
  const added = [];
309
346
  for (const c of contracts ?? []) {
@@ -335,7 +372,8 @@ export function createFidesMcpServer(cfg) {
335
372
  }
336
373
  return ok({
337
374
  agent_address: agent.address,
338
- 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')}` : ''),
375
+ 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')}` : '') + (keeps_going ? `&every=${keeps_going.every}&per=${encodeURIComponent(keeps_going.per_period)}&days=${keeps_going.days ?? 30}` : ''),
376
+ keeps_going: keeps_going ? `${keeps_going.per_period} USDG every ${keeps_going.every}, for ${keeps_going.days ?? 30} days. The budget resets by itself; the total is the most it can ever spend.` : undefined,
339
377
  services: services?.length ? services.map((x) => `${x.id}: up to ${x.max} USDG`) : undefined,
340
378
  contracts: added.length ? added : undefined,
341
379
  steps: [
@@ -453,6 +491,25 @@ export function createFidesMcpServer(cfg) {
453
491
  if ('error' in made)
454
492
  return fail(made.error);
455
493
  const built = made.built;
494
+ if (isLong(fides)) {
495
+ const la = longAgentFor(fides);
496
+ if (typeof la === 'string')
497
+ return fail(la);
498
+ const p = await la.plan(built);
499
+ const sim = p.bundle ? await la.simulate(p) : { success: false, reason: 'NOTHING_TO_SEND' };
500
+ const id = (p.bundleHash ?? '0x').slice(0, 18);
501
+ if (sim.success)
502
+ longPlans.set(id, { agent: la, plan: p, fides });
503
+ return ok({
504
+ plan_id: sim.success ? id : null,
505
+ ready_to_execute: sim.success,
506
+ keeps_going: `period ${p.period}: checked against this period's budget and the total`,
507
+ actions: p.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}), counting this period's budget` : 'duplicate, sent once' })),
508
+ transactions: p.bundle ? `${p.bundle.calls.length} onchain write(s) go out as 1 transaction` : 'nothing to execute',
509
+ simulation: !p.bundle ? 'nothing to execute: no action fits this period\'s budget' : sim.success ? 'simulated against the chain: it would succeed' : `would fail: ${failure(sim.reason)}`,
510
+ sent: 'nothing has been sent',
511
+ });
512
+ }
456
513
  const plan = await new FidesBuilder({ client, fides, agent }).add(built).build();
457
514
  const savings = plan.estimatedSavings();
458
515
  const simulations = savings.estimatedTransactions ? await plan.simulate() : [];
@@ -490,6 +547,8 @@ export function createFidesMcpServer(cfg) {
490
547
  return fail(notLoaded());
491
548
  if (fides.agent.toLowerCase() !== agent.address.toLowerCase())
492
549
  return fail(`The loaded FIDES is for agent ${fides.agent}, not this computer's agent key ${agent.address}.`);
550
+ if (isLong(fides))
551
+ return fail(noLong('settle with FIDES NET'));
493
552
  const roots = netRoots[cfg.chainId];
494
553
  if (!roots)
495
554
  return fail(`FIDES NET is not deployed on chain ${cfg.chainId}.`);
@@ -657,20 +716,36 @@ export function createFidesMcpServer(cfg) {
657
716
  let funded;
658
717
  if (float < price) {
659
718
  const actions = [x402.float(offer.asset, price - float, 'pay-float')];
660
- const plan = await new FidesBuilder({ client, fides, agent }).add(actions).build();
661
- if (plan.rejected().length) {
662
- const r = plan.rejected()[0];
663
- return fail(`Blocked by the FIDES: ${REASONS[r.reason ?? ''] ?? r.reason}. Nothing was paid.`);
719
+ if (isLong(fides)) {
720
+ const la = longAgentFor(fides);
721
+ if (typeof la === 'string')
722
+ return fail(la);
723
+ const lp = await la.plan(actions);
724
+ if (lp.plan.rejected.length)
725
+ return fail(`Blocked by the FIDES: ${REASONS[lp.plan.rejected[0].reason ?? ''] ?? lp.plan.rejected[0].reason}, counting this period's budget. Nothing was paid.`);
726
+ const r = await la.execute(lp);
727
+ if (r.status !== 'EXECUTED')
728
+ return fail('Could not move the price to the agent key: ' + failure(r.error) + ' Nothing was paid.');
729
+ funded = txLink(r.txHash);
730
+ float = price;
731
+ setFloat(fides, offer.asset, float);
732
+ }
733
+ const plan = isLong(fides) ? null : await new FidesBuilder({ client, fides, agent }).add(actions).build();
734
+ if (plan) {
735
+ if (plan.rejected().length) {
736
+ const r = plan.rejected()[0];
737
+ return fail(`Blocked by the FIDES: ${REASONS[r.reason ?? ''] ?? r.reason}. Nothing was paid.`);
738
+ }
739
+ const [sim] = await plan.simulate();
740
+ if (sim && !sim.success)
741
+ return fail('Blocked: ' + failure(sim.revertReason) + ' Nothing was paid.');
742
+ const bundles = await send(plan, actions, fides);
743
+ if (!bundles.length || bundles.some((b) => b.status !== 'EXECUTED'))
744
+ return fail('Could not move the price to the agent key: ' + failure(bundles.find((b) => b.error)?.error) + ' Nothing was paid.');
745
+ funded = txLink(bundles[0].txHash);
746
+ float = price;
747
+ setFloat(fides, offer.asset, float);
664
748
  }
665
- const [sim] = await plan.simulate();
666
- if (sim && !sim.success)
667
- return fail('Blocked: ' + failure(sim.revertReason) + ' Nothing was paid.');
668
- const bundles = await send(plan, actions, fides);
669
- if (!bundles.length || bundles.some((b) => b.status !== 'EXECUTED'))
670
- return fail('Could not move the price to the agent key: ' + failure(bundles.find((b) => b.error)?.error) + ' Nothing was paid.');
671
- funded = txLink(bundles[0].txHash);
672
- float = price;
673
- setFloat(fides, offer.asset, float);
674
749
  }
675
750
  // 3. pay and fetch
676
751
  const res = await x402.fetch(url);
@@ -882,6 +957,28 @@ export function createFidesMcpServer(cfg) {
882
957
  const mandate = excluded.find((x) => x.why.startsWith('trading mandate'));
883
958
  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.`);
884
959
  }
960
+ if (isLong(fides)) {
961
+ const la = longAgentFor(fides);
962
+ if (typeof la === 'string')
963
+ return fail(la);
964
+ const [bestName, bq] = [...quotes].sort((a, b) => (b[1].quotedOut > a[1].quotedOut ? 1 : -1))[0];
965
+ const outInfo = bq.tokenOut === NATIVE ? { symbol: 'ETH', decimals: 18 } : await token(bq.tokenOut);
966
+ const fmt = (v) => `${formatUnits(v, outInfo.decimals)} ${outInfo.symbol}`;
967
+ const lp = await la.plan(swapActions(bq, `swap ${amount} ${tIn.symbol} to ${outInfo.symbol} on Uniswap`));
968
+ const sim = lp.bundle ? await la.simulate(lp) : { success: false, reason: lp.plan.rejected[0]?.reason ?? 'NOTHING_TO_SEND' };
969
+ const id = (lp.bundleHash ?? '0x').slice(0, 18);
970
+ if (sim.success)
971
+ longPlans.set(id, { agent: la, plan: lp, fides, swap: { quote: bq, outDecimals: outInfo.decimals, outSymbol: outInfo.symbol } });
972
+ return ok({
973
+ plan_id: sim.success ? id : null, ready_to_execute: sim.success,
974
+ routes_compared: [...quotes].map(([name, q]) => ({ route: name, quoted: fmt(q.quotedOut), verdict: name === bestName ? 'best quote, chosen' : 'lower quote' })).concat(excluded.map((x) => ({ route: x.route, quoted: '-', verdict: 'out: ' + x.why }))),
975
+ chosen: bestName, route: describeRoute(bq), spend: `${amount} ${tIn.symbol}`, quoted: fmt(bq.quotedOut),
976
+ worst_accepted: `${fmt(bq.minOut)} (${bq.slippageBps / 100}% below the quote)`,
977
+ keeps_going: `period ${lp.period}: checked against this period's budget and the total`,
978
+ simulation: sim.success ? 'simulated against the chain: it would succeed' : `would fail: ${failure(sim.reason)}`,
979
+ sent: 'nothing has been sent',
980
+ });
981
+ }
885
982
  const first = [...quotes.values()][0];
886
983
  const outInfo = first.tokenOut === NATIVE ? { symbol: 'ETH', decimals: 18 } : await token(first.tokenOut);
887
984
  const fmt = (v) => `${formatUnits(v, outInfo.decimals)} ${outInfo.symbol}`;
@@ -941,6 +1038,8 @@ export function createFidesMcpServer(cfg) {
941
1038
  return fail('"agent" must be an address.');
942
1039
  if (child.toLowerCase() === agent.address.toLowerCase())
943
1040
  return fail('An agent cannot delegate to itself.');
1041
+ if (isLong(parent))
1042
+ return fail(noLong('hand out slices to other agents'));
944
1043
  const v2 = await accountOf(subClient, parent.owner);
945
1044
  if (v2.toLowerCase() !== parent.account.toLowerCase())
946
1045
  return fail('This FIDES cannot delegate: its FIDES Account is not one that supports SUB-FIDES. The user signs a new FIDES that allows delegation: call fides_setup with delegate: true and give them the link.');
@@ -1078,6 +1177,28 @@ export function createFidesMcpServer(cfg) {
1078
1177
  return fail('Execution stopped: ' + failure(err?.shortMessage ?? err?.message ?? err));
1079
1178
  }
1080
1179
  }
1180
+ const longEntry = longPlans.get(plan_id);
1181
+ if (longEntry) {
1182
+ longPlans.delete(plan_id);
1183
+ try {
1184
+ const sw = longEntry.swap, f2 = longEntry.fides;
1185
+ const outBalance = () => !sw ? Promise.resolve(0n) : sw.quote.tokenOut === NATIVE ? client.public.getBalance({ address: f2.account }) : client.public.readContract({ address: sw.quote.tokenOut, abi: erc20Abi, functionName: 'balanceOf', args: [f2.account] });
1186
+ const before = await outBalance();
1187
+ const r = await longEntry.agent.execute(longEntry.plan);
1188
+ const st = await longEntry.agent.status().catch(() => null), t = await token(f2.spendLimits[0].asset);
1189
+ const received = sw && r.status === 'EXECUTED' ? (await outBalance()) - before : 0n;
1190
+ return ok({
1191
+ result: r.status === 'EXECUTED' ? 'executed' : 'not executed',
1192
+ transactions: r.txHash || r.error ? [{ status: r.status.toLowerCase(), transaction: txLink(r.txHash), ...(r.error ? { reason: failure(r.error) } : {}) }] : [],
1193
+ blocked_and_not_sent: longEntry.plan.plan.rejected.map((a) => `${a.label}: ${REASONS[a.reason ?? ''] ?? a.reason}`),
1194
+ ...(sw && r.status === 'EXECUTED' ? { best_execution: { quoted: `${formatUnits(sw.quote.quotedOut, sw.outDecimals)} ${sw.outSymbol}`, received: `${formatUnits(received, sw.outDecimals)} ${sw.outSymbol}`, route: describeRoute(sw.quote) } } : {}),
1195
+ ...(st ? { this_period: { spent: `${formatUnits(st.thisPeriod[0].spent, t.decimals)} ${t.symbol}`, left: `${formatUnits(st.thisPeriod[0].remaining, t.decimals)} ${t.symbol}`, resets_at: new Date(Number(st.resetsAt) * 1000).toISOString() }, total_left: `${formatUnits(st.total[0].remaining, t.decimals)} of ${formatUnits(st.total[0].limit, t.decimals)} ${t.symbol}` } : {}),
1196
+ });
1197
+ }
1198
+ catch (err) {
1199
+ return fail('Execution stopped: ' + failure(err?.shortMessage ?? err?.message ?? err));
1200
+ }
1201
+ }
1081
1202
  const entry = plans.get(plan_id);
1082
1203
  if (!entry)
1083
1204
  return fail('No plan with that id. Make one with fides_plan first (plans are kept until they are executed or the server restarts).');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidesbuild/mcp-server",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",
@@ -43,6 +43,7 @@
43
43
  "@fidesbuild/net": "0.2.1",
44
44
  "@fidesbuild/sub": "0.2.0",
45
45
  "@fidesbuild/swap": "0.2.1",
46
- "@fidesbuild/router": "0.2.0"
46
+ "@fidesbuild/router": "0.2.0",
47
+ "@fidesbuild/long": "0.2.0"
47
48
  }
48
49
  }