@abstract-foundation/agw-mcp 0.1.0-beta.6 → 0.1.0-beta.8

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 (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.mjs +200 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -126,6 +126,9 @@ npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
126
126
 
127
127
  # Custom RPC
128
128
  npx -y @abstract-foundation/agw-mcp serve --chain-id 2741 --rpc-url https://api.mainnet.abs.xyz
129
+
130
+ # 0x API key override (for swap_tokens quote requests)
131
+ npx -y @abstract-foundation/agw-mcp serve --chain-id 2741 --zeroex-api-key YOUR_0X_API_KEY
129
132
  ```
130
133
 
131
134
  Environment variables are also supported:
@@ -133,6 +136,7 @@ Environment variables are also supported:
133
136
  ```bash
134
137
  AGW_MCP_CHAIN_ID=2741 npx -y @abstract-foundation/agw-mcp serve
135
138
  AGW_MCP_RPC_URL=https://api.mainnet.abs.xyz npx -y @abstract-foundation/agw-mcp serve
139
+ AGW_MCP_ZEROEX_API_KEY=YOUR_0X_API_KEY npx -y @abstract-foundation/agw-mcp serve
136
140
  AGW_MCP_APP_URL=https://mcp.abs.xyz npx -y @abstract-foundation/agw-mcp init --chain-id 2741
137
141
  ```
138
142
 
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import os from "node:os";
8
8
  import path from "node:path";
9
9
  import { sessionKeyValidatorAddress } from "@abstract-foundation/agw-client/constants";
10
10
  import { SessionKeyValidatorAbi, createSessionClient, getSessionHash } from "@abstract-foundation/agw-client/sessions";
11
- import { createPublicClient, encodeFunctionData, erc20Abi, formatUnits, http, isAddress, zeroAddress } from "viem";
11
+ import { createPublicClient, encodeFunctionData, erc20Abi, formatUnits, getAddress, http, isAddress, parseAbiItem, zeroAddress } from "viem";
12
12
  import { abstract, abstractTestnet } from "viem/chains";
13
13
  import http$1 from "node:http";
14
14
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -240,7 +240,7 @@ const SUPPORTED_CHAINS = {
240
240
  [abstract.id]: abstract
241
241
  };
242
242
  const DEFAULT_CHAIN_ID = abstract.id;
243
- function normalizeOptionalString(value) {
243
+ function normalizeOptionalString$1(value) {
244
244
  if (typeof value !== "string") return;
245
245
  const normalized = value.trim();
246
246
  return normalized ? normalized : void 0;
@@ -251,9 +251,9 @@ function parseChainId(value, source) {
251
251
  if (!Number.isInteger(chainId) || chainId <= 0) throw new Error(`Invalid chain id from ${source}. Expected a positive integer.`);
252
252
  return chainId;
253
253
  }
254
- function resolveEnvValue(env, keys) {
254
+ function resolveEnvValue$1(env, keys) {
255
255
  for (const key of keys) {
256
- const value = normalizeOptionalString(env[key]);
256
+ const value = normalizeOptionalString$1(env[key]);
257
257
  if (value) return value;
258
258
  }
259
259
  }
@@ -270,17 +270,36 @@ function getDefaultRpcUrl(chain) {
270
270
  function resolveNetworkConfig(input = {}) {
271
271
  const env = input.env ?? process.env;
272
272
  const chainIdFromCli = input.chainId === void 0 ? void 0 : parseChainId(input.chainId, "--chain-id");
273
- const chainIdFromEnvRaw = resolveEnvValue(env, CHAIN_ID_ENV_KEYS);
273
+ const chainIdFromEnvRaw = resolveEnvValue$1(env, CHAIN_ID_ENV_KEYS);
274
274
  const chainIdFromEnv = chainIdFromEnvRaw === void 0 ? void 0 : parseChainId(chainIdFromEnvRaw, "environment");
275
275
  const chainId = chainIdFromCli ?? chainIdFromEnv ?? DEFAULT_CHAIN_ID;
276
276
  const chain = resolveChain(chainId);
277
277
  return {
278
278
  chainId,
279
279
  chain,
280
- rpcUrl: normalizeOptionalString(input.rpcUrl) ?? resolveEnvValue(env, RPC_URL_ENV_KEYS) ?? getDefaultRpcUrl(chain)
280
+ rpcUrl: normalizeOptionalString$1(input.rpcUrl) ?? resolveEnvValue$1(env, RPC_URL_ENV_KEYS) ?? getDefaultRpcUrl(chain)
281
281
  };
282
282
  }
283
283
 
284
+ //#endregion
285
+ //#region src/config/zeroex.ts
286
+ const ZEROEX_API_KEY_ENV_KEYS = ["AGW_MCP_ZEROEX_API_KEY", "ZEROEX_API_KEY"];
287
+ function normalizeOptionalString(value) {
288
+ if (typeof value !== "string") return;
289
+ const normalized = value.trim();
290
+ return normalized ? normalized : void 0;
291
+ }
292
+ function resolveEnvValue(env, keys) {
293
+ for (const key of keys) {
294
+ const value = normalizeOptionalString(env[key]);
295
+ if (value) return value;
296
+ }
297
+ }
298
+ function resolveZeroExConfig(input = {}) {
299
+ const env = input.env ?? process.env;
300
+ return { apiKey: normalizeOptionalString(input.apiKey) ?? resolveEnvValue(env, ZEROEX_API_KEY_ENV_KEYS) };
301
+ }
302
+
284
303
  //#endregion
285
304
  //#region src/integrations/zeroex/quote-adapter.ts
286
305
  const DEFAULT_ZEROEX_API_BASE_URL = "https://api.0x.org";
@@ -420,11 +439,22 @@ function normalizeRouteFills(value) {
420
439
  }
421
440
  return fills;
422
441
  }
442
+ function normalizeTransaction(payload) {
443
+ const transaction = isRecord$5(payload.transaction) ? payload.transaction : {};
444
+ return {
445
+ to: requireString(transaction.to ?? payload.to, "to"),
446
+ data: requireString(transaction.data ?? payload.data, "data"),
447
+ value: asString(transaction.value ?? payload.value) ?? "0",
448
+ gasLimit: asString(transaction.gas ?? payload.gas),
449
+ gasPrice: asString(transaction.gasPrice ?? payload.gasPrice)
450
+ };
451
+ }
423
452
  function normalizeQuotePayload(payload, request) {
424
453
  if (!isRecord$5(payload)) throw new Error("response payload must be an object");
425
454
  const issues = isRecord$5(payload.issues) ? payload.issues : {};
426
455
  const route = isRecord$5(payload.route) ? payload.route : {};
427
456
  const fees = isRecord$5(payload.fees) ? payload.fees : {};
457
+ const transaction = normalizeTransaction(payload);
428
458
  return {
429
459
  quoteId: asString(payload.zid),
430
460
  chainId: normalizePositiveInteger(payload.chainId ?? request.chainId, "chainId"),
@@ -438,14 +468,14 @@ function normalizeQuotePayload(payload, request) {
438
468
  estimatedPriceImpact: asString(payload.estimatedPriceImpact),
439
469
  allowanceTarget: asString(payload.allowanceTarget),
440
470
  gas: {
441
- limit: asString(payload.gas),
442
- price: asString(payload.gasPrice),
471
+ limit: transaction.gasLimit,
472
+ price: transaction.gasPrice,
443
473
  estimatedFee: asString(payload.totalNetworkFee)
444
474
  },
445
475
  transaction: {
446
- to: requireString(payload.to, "to"),
447
- data: requireString(payload.data, "data"),
448
- value: asString(payload.value) ?? "0"
476
+ to: transaction.to,
477
+ data: transaction.data,
478
+ value: transaction.value
449
479
  },
450
480
  fees: {
451
481
  integratorFee: normalizeFee(fees.integratorFee),
@@ -594,7 +624,8 @@ function createZeroExQuoteAdapter(config = {}) {
594
624
  }
595
625
  } };
596
626
  }
597
- const zeroExQuoteAdapter = createZeroExQuoteAdapter();
627
+ const defaultZeroExConfig = resolveZeroExConfig();
628
+ const zeroExQuoteAdapter = createZeroExQuoteAdapter({ apiKey: defaultZeroExConfig.apiKey });
598
629
 
599
630
  //#endregion
600
631
  //#region src/errors/contract.ts
@@ -2100,6 +2131,98 @@ const getSessionStatusTool = {
2100
2131
 
2101
2132
  //#endregion
2102
2133
  //#region src/tools/get-token-list.ts
2134
+ const ERC20_TRANSFER_EVENT = parseAbiItem("event Transfer(address indexed from, address indexed to, uint256 value)");
2135
+ const LOG_SCAN_MIN_CHUNK_SIZE = 50000n;
2136
+ const BALANCE_QUERY_CONCURRENCY = 8;
2137
+ const METADATA_QUERY_CONCURRENCY = 8;
2138
+ function getErrorMessage(error) {
2139
+ if (error instanceof Error) return error.message;
2140
+ try {
2141
+ return JSON.stringify(error);
2142
+ } catch {
2143
+ return String(error);
2144
+ }
2145
+ }
2146
+ function isRpcMethodUnavailableError(error) {
2147
+ const message = getErrorMessage(error).toLowerCase();
2148
+ return message.includes("rpc method is not whitelisted") || message.includes("method not found") || message.includes("code\":-32601") && message.includes("zks_getallaccountbalances");
2149
+ }
2150
+ function isLogRangeError(error) {
2151
+ const message = getErrorMessage(error).toLowerCase();
2152
+ return message.includes("too many results") || message.includes("response size exceeded") || message.includes("query timeout") || message.includes("block range") || message.includes("limit");
2153
+ }
2154
+ async function getTransferLogsWithAdaptiveChunking(publicClient, latestBlock, args) {
2155
+ if (latestBlock < 0n) return [];
2156
+ const logs = [];
2157
+ let chunkSize = latestBlock + 1n;
2158
+ let fromBlock = 0n;
2159
+ while (fromBlock <= latestBlock) {
2160
+ const toBlock = fromBlock + chunkSize - 1n > latestBlock ? latestBlock : fromBlock + chunkSize - 1n;
2161
+ try {
2162
+ const batch = await publicClient.getLogs({
2163
+ event: ERC20_TRANSFER_EVENT,
2164
+ args,
2165
+ fromBlock,
2166
+ toBlock
2167
+ });
2168
+ logs.push(...batch);
2169
+ fromBlock = toBlock + 1n;
2170
+ } catch (error) {
2171
+ if (!isLogRangeError(error) || chunkSize <= LOG_SCAN_MIN_CHUNK_SIZE) throw error;
2172
+ chunkSize = chunkSize / 2n;
2173
+ if (chunkSize < LOG_SCAN_MIN_CHUNK_SIZE) chunkSize = LOG_SCAN_MIN_CHUNK_SIZE;
2174
+ }
2175
+ }
2176
+ return logs;
2177
+ }
2178
+ async function mapWithConcurrency(values, concurrency, mapper) {
2179
+ if (values.length === 0) return [];
2180
+ const workerCount = Math.max(1, Math.min(concurrency, values.length));
2181
+ const results = new Array(values.length);
2182
+ let nextIndex = 0;
2183
+ const workers = Array.from({ length: workerCount }, async () => {
2184
+ while (nextIndex < values.length) {
2185
+ const index = nextIndex;
2186
+ nextIndex += 1;
2187
+ results[index] = await mapper(values[index]);
2188
+ }
2189
+ });
2190
+ await Promise.all(workers);
2191
+ return results;
2192
+ }
2193
+ async function getTokenBalancesFromTransferLogs(publicClient, accountAddress) {
2194
+ const normalizedAccount = getAddress(accountAddress);
2195
+ const latestBlock = await publicClient.getBlockNumber();
2196
+ const [incomingLogs, outgoingLogs] = await Promise.all([getTransferLogsWithAdaptiveChunking(publicClient, latestBlock, { to: normalizedAccount }), getTransferLogsWithAdaptiveChunking(publicClient, latestBlock, { from: normalizedAccount })]);
2197
+ const tokenAddresses = /* @__PURE__ */ new Map();
2198
+ for (const log of [...incomingLogs, ...outgoingLogs]) {
2199
+ if (!isAddress(log.address, { strict: false })) continue;
2200
+ tokenAddresses.set(log.address.toLowerCase(), log.address);
2201
+ }
2202
+ const balanceEntries = await mapWithConcurrency(Array.from(tokenAddresses.values()), BALANCE_QUERY_CONCURRENCY, async (tokenAddress) => {
2203
+ try {
2204
+ const rawBalance = await publicClient.readContract({
2205
+ address: tokenAddress,
2206
+ abi: erc20Abi,
2207
+ functionName: "balanceOf",
2208
+ args: [normalizedAccount]
2209
+ });
2210
+ if (typeof rawBalance !== "bigint" || rawBalance <= 0n) return null;
2211
+ return {
2212
+ tokenAddress,
2213
+ rawBalance
2214
+ };
2215
+ } catch {
2216
+ return null;
2217
+ }
2218
+ });
2219
+ const balances = {};
2220
+ for (const entry of balanceEntries) {
2221
+ if (!entry) continue;
2222
+ balances[entry.tokenAddress] = entry.rawBalance.toString();
2223
+ }
2224
+ return balances;
2225
+ }
2103
2226
  function normalizeTokenBalances(value) {
2104
2227
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("token balances payload must be an object");
2105
2228
  const entries = [];
@@ -2136,18 +2259,23 @@ function createDefaultTokenListReader(input) {
2136
2259
  });
2137
2260
  return {
2138
2261
  getTokenBalances: async (accountAddress) => {
2139
- const request = publicClient.request;
2140
- const response = await request({
2141
- method: "zks_getAllAccountBalances",
2142
- params: [accountAddress]
2143
- });
2144
- if (typeof response !== "object" || response === null || Array.isArray(response)) throw new Error("zks_getAllAccountBalances returned an invalid response payload");
2145
- const balances = {};
2146
- for (const [tokenAddress, rawValue] of Object.entries(response)) {
2147
- if (typeof rawValue !== "string") throw new Error(`token balance for ${tokenAddress} returned a non-string value`);
2148
- balances[tokenAddress] = rawValue;
2262
+ try {
2263
+ const request = publicClient.request;
2264
+ const response = await request({
2265
+ method: "zks_getAllAccountBalances",
2266
+ params: [accountAddress]
2267
+ });
2268
+ if (typeof response !== "object" || response === null || Array.isArray(response)) throw new Error("zks_getAllAccountBalances returned an invalid response payload");
2269
+ const balances = {};
2270
+ for (const [tokenAddress, rawValue] of Object.entries(response)) {
2271
+ if (typeof rawValue !== "string") throw new Error(`token balance for ${tokenAddress} returned a non-string value`);
2272
+ balances[tokenAddress] = rawValue;
2273
+ }
2274
+ return balances;
2275
+ } catch (error) {
2276
+ if (!isRpcMethodUnavailableError(error)) throw error;
2277
+ return getTokenBalancesFromTransferLogs(publicClient, accountAddress);
2149
2278
  }
2150
- return balances;
2151
2279
  },
2152
2280
  getTokenMetadata: async (tokenAddress) => {
2153
2281
  const [symbol, decimals] = await Promise.all([publicClient.readContract({
@@ -2197,27 +2325,28 @@ function createGetTokenListTool(dependencies = { createTokenListReader: createDe
2197
2325
  chainId: networkConfig.chainId,
2198
2326
  rpcUrl: networkConfig.rpcUrl
2199
2327
  });
2200
- const normalizedTokenBalances = normalizeTokenBalances(await reader.getTokenBalances(accountAddress));
2201
- const tokenHoldings = [];
2202
- for (const { tokenAddress, rawValue } of normalizedTokenBalances) try {
2203
- const metadata = await reader.getTokenMetadata(tokenAddress);
2204
- tokenHoldings.push({
2205
- tokenAddress,
2206
- symbol: metadata.symbol,
2207
- decimals: metadata.decimals,
2208
- value: {
2209
- raw: rawValue.toString(),
2210
- formatted: formatUnits(rawValue, metadata.decimals)
2211
- },
2212
- explorer: {
2213
- token: buildExplorerUrl(explorerBase, `/token/${tokenAddress}`),
2214
- holder: buildExplorerUrl(explorerBase, `/token/${tokenAddress}?a=${accountAddress}`)
2215
- }
2216
- });
2217
- } catch (error) {
2218
- const message = error instanceof Error ? error.message : String(error);
2219
- context.logger.warn(`Skipping token ${tokenAddress}: ${message}`);
2220
- }
2328
+ const tokenHoldings = (await mapWithConcurrency(normalizeTokenBalances(await reader.getTokenBalances(accountAddress)), METADATA_QUERY_CONCURRENCY, async ({ tokenAddress, rawValue }) => {
2329
+ try {
2330
+ const metadata = await reader.getTokenMetadata(tokenAddress);
2331
+ return {
2332
+ tokenAddress,
2333
+ symbol: metadata.symbol,
2334
+ decimals: metadata.decimals,
2335
+ value: {
2336
+ raw: rawValue.toString(),
2337
+ formatted: formatUnits(rawValue, metadata.decimals)
2338
+ },
2339
+ explorer: {
2340
+ token: buildExplorerUrl(explorerBase, `/token/${tokenAddress}`),
2341
+ holder: buildExplorerUrl(explorerBase, `/token/${tokenAddress}?a=${accountAddress}`)
2342
+ }
2343
+ };
2344
+ } catch (error) {
2345
+ const message = error instanceof Error ? error.message : String(error);
2346
+ context.logger.warn(`Skipping token ${tokenAddress}: ${message}`);
2347
+ return null;
2348
+ }
2349
+ })).filter((entry) => entry !== null);
2221
2350
  return {
2222
2351
  connected: true,
2223
2352
  sessionStatus: context.sessionManager.getSessionStatus(),
@@ -2792,6 +2921,7 @@ const signTransactionTool = {
2792
2921
 
2793
2922
  //#endregion
2794
2923
  //#region src/tools/swap-tokens.ts
2924
+ const ZEROEX_NATIVE_ETH_SENTINEL = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
2795
2925
  function parseExecute$1(value) {
2796
2926
  if (value === void 0) return false;
2797
2927
  if (typeof value !== "boolean") throw new Error("execute must be a boolean");
@@ -2807,7 +2937,15 @@ function parseOptionalString(value, field) {
2807
2937
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${field} must be a non-empty string when provided`);
2808
2938
  return value.trim();
2809
2939
  }
2810
- function createSwapTokensTool(dependencies = { quoteAdapter: zeroExQuoteAdapter }) {
2940
+ function normalizeSwapToken(value, nativeSymbol) {
2941
+ const normalized = value.trim();
2942
+ if (normalized.toUpperCase() === nativeSymbol.toUpperCase()) return ZEROEX_NATIVE_ETH_SENTINEL;
2943
+ return normalized;
2944
+ }
2945
+ function createSwapTokensTool(dependencies = {}) {
2946
+ const createQuoteAdapter = dependencies.createQuoteAdapter ?? (() => {
2947
+ return createZeroExQuoteAdapter({ apiKey: resolveZeroExConfig().apiKey });
2948
+ });
2811
2949
  return {
2812
2950
  name: "swap_tokens",
2813
2951
  description: "Fetches 0x swap quotes and executes swap transactions through AGW session keys when explicitly requested.",
@@ -2816,11 +2954,11 @@ function createSwapTokensTool(dependencies = { quoteAdapter: zeroExQuoteAdapter
2816
2954
  properties: {
2817
2955
  sellToken: {
2818
2956
  type: "string",
2819
- description: "Token address/symbol to sell"
2957
+ description: "Token address to sell, or native symbol (for example ETH)"
2820
2958
  },
2821
2959
  buyToken: {
2822
2960
  type: "string",
2823
- description: "Token address/symbol to buy"
2961
+ description: "Token address to buy, or native symbol (for example ETH)"
2824
2962
  },
2825
2963
  sellAmount: {
2826
2964
  type: "string",
@@ -2850,11 +2988,15 @@ function createSwapTokensTool(dependencies = { quoteAdapter: zeroExQuoteAdapter
2850
2988
  const session = context.sessionManager.getSession();
2851
2989
  if (!session) throw new Error("session is missing");
2852
2990
  const execute = parseExecute$1(params.execute);
2853
- const quote = await dependencies.quoteAdapter.getQuote({
2991
+ const quoteAdapter = dependencies.quoteAdapter ?? createQuoteAdapter();
2992
+ const networkConfig = resolveToolNetworkConfig(context, session.chainId);
2993
+ const sellToken = normalizeSwapToken(params.sellToken, networkConfig.chain.nativeCurrency.symbol);
2994
+ const buyToken = normalizeSwapToken(params.buyToken, networkConfig.chain.nativeCurrency.symbol);
2995
+ const quote = await quoteAdapter.getQuote({
2854
2996
  chainId: session.chainId,
2855
2997
  taker: session.accountAddress,
2856
- sellToken: params.sellToken.trim(),
2857
- buyToken: params.buyToken.trim(),
2998
+ sellToken,
2999
+ buyToken,
2858
3000
  sellAmount: parseOptionalString(params.sellAmount, "sellAmount"),
2859
3001
  buyAmount: parseOptionalString(params.buyAmount, "buyAmount"),
2860
3002
  slippageBps: parseOptionalInteger(params.slippageBps, "slippageBps")
@@ -2875,7 +3017,6 @@ function createSwapTokensTool(dependencies = { quoteAdapter: zeroExQuoteAdapter
2875
3017
  spender: quote.issues.allowance?.spender ?? null
2876
3018
  }
2877
3019
  };
2878
- const networkConfig = resolveToolNetworkConfig(context, session.chainId);
2879
3020
  await assertMainnetPolicyRegistryPreflight({
2880
3021
  chainId: session.chainId,
2881
3022
  to: txTarget,
@@ -3358,6 +3499,7 @@ var AgwMcpServer = class {
3358
3499
  //#endregion
3359
3500
  //#region src/index.ts
3360
3501
  const logger = new Logger("agw-mcp");
3502
+ const ZEROEX_API_KEY_ENV = "AGW_MCP_ZEROEX_API_KEY";
3361
3503
  function resolveCliVersion() {
3362
3504
  try {
3363
3505
  const packageJsonUrl = new URL("../package.json", import.meta.url);
@@ -3367,6 +3509,11 @@ function resolveCliVersion() {
3367
3509
  } catch {}
3368
3510
  return "0.1.0";
3369
3511
  }
3512
+ function applyZeroExApiKeyOverride(apiKeyValue) {
3513
+ if (apiKeyValue === void 0) return;
3514
+ if (typeof apiKeyValue !== "string" || apiKeyValue.trim() === "") throw new Error("--zeroex-api-key must be a non-empty string");
3515
+ process.env[ZEROEX_API_KEY_ENV] = apiKeyValue.trim();
3516
+ }
3370
3517
  const program = new Command();
3371
3518
  program.name("agw-mcp").description("Local MCP server for AGW session-key workflows").version(resolveCliVersion());
3372
3519
  program.command("init").description("Bootstrap local AGW MCP session storage").option("--chain-id <chainId>", "EVM chain id (env: AGW_MCP_CHAIN_ID)").option("--rpc-url <rpcUrl>", "RPC URL override (env: AGW_MCP_RPC_URL)").option("--app-url <url>", "Hosted session onboarding URL (defaults to https://mcp.abs.xyz; env: AGW_MCP_APP_URL)").option("--storage-dir <dir>", "Session storage directory").action(async (options) => {
@@ -3396,7 +3543,8 @@ program.command("config").description("Print a ready-to-paste local MCP config s
3396
3543
  });
3397
3544
  process.stdout.write(`${JSON.stringify(snippet, null, 2)}\n`);
3398
3545
  });
3399
- program.command("serve").description("Run the local stdio MCP server").option("--chain-id <chainId>", "EVM chain id (env: AGW_MCP_CHAIN_ID)").option("--rpc-url <rpcUrl>", "RPC URL override (env: AGW_MCP_RPC_URL)").option("--storage-dir <dir>", "Session storage directory").action(async (options) => {
3546
+ program.command("serve").description("Run the local stdio MCP server").option("--chain-id <chainId>", "EVM chain id (env: AGW_MCP_CHAIN_ID)").option("--rpc-url <rpcUrl>", "RPC URL override (env: AGW_MCP_RPC_URL)").option("--zeroex-api-key <apiKey>", "0x API key override (env: AGW_MCP_ZEROEX_API_KEY)").option("--storage-dir <dir>", "Session storage directory").action(async (options) => {
3547
+ applyZeroExApiKeyOverride(options.zeroexApiKey);
3400
3548
  const networkConfig = resolveNetworkConfig({
3401
3549
  chainId: options.chainId,
3402
3550
  rpcUrl: options.rpcUrl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abstract-foundation/agw-mcp",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.0-beta.8",
4
4
  "description": "MCP server for Abstract Global Wallet session-key workflows",
5
5
  "license": "MIT",
6
6
  "author": "Abstract Foundation",