@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.
- package/README.md +4 -0
- package/dist/index.mjs +200 -52
- 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:
|
|
442
|
-
price:
|
|
471
|
+
limit: transaction.gasLimit,
|
|
472
|
+
price: transaction.gasPrice,
|
|
443
473
|
estimatedFee: asString(payload.totalNetworkFee)
|
|
444
474
|
},
|
|
445
475
|
transaction: {
|
|
446
|
-
to:
|
|
447
|
-
data:
|
|
448
|
-
value:
|
|
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
|
|
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
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
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
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
}
|
|
2216
|
-
})
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2857
|
-
buyToken
|
|
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
|