x402scan-mcp 0.0.2 → 0.0.3
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/index.js +167 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1014,6 +1014,170 @@ function registerExecuteCallTool(server) {
|
|
|
1014
1014
|
);
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
+
// src/tools/siwe.ts
|
|
1018
|
+
import { z as z4 } from "zod";
|
|
1019
|
+
import { SiweMessage, generateNonce } from "siwe";
|
|
1020
|
+
var CHAIN_IDS = {
|
|
1021
|
+
mainnet: 1,
|
|
1022
|
+
base: 8453,
|
|
1023
|
+
"base-sepolia": 84532,
|
|
1024
|
+
optimism: 10,
|
|
1025
|
+
arbitrum: 42161,
|
|
1026
|
+
polygon: 137,
|
|
1027
|
+
sepolia: 11155111
|
|
1028
|
+
};
|
|
1029
|
+
function registerCreateSiweProofTool(server) {
|
|
1030
|
+
server.tool(
|
|
1031
|
+
"create_siwe_proof",
|
|
1032
|
+
"Create a SIWE (Sign-In with Ethereum) proof for wallet authentication. Returns a proof string to use in X-SIWE-PROOF header.",
|
|
1033
|
+
{
|
|
1034
|
+
domain: z4.string().describe(
|
|
1035
|
+
'Domain requesting auth (e.g., "localhost:3000" or "stablestudio.io")'
|
|
1036
|
+
),
|
|
1037
|
+
uri: z4.string().url().describe('Full URI of the API (e.g., "http://localhost:3000")'),
|
|
1038
|
+
statement: z4.string().optional().default("Authenticate to API").describe("Human-readable statement"),
|
|
1039
|
+
chainId: z4.number().optional().describe(
|
|
1040
|
+
"Chain ID (default: 8453 for Base). Common IDs: 1=mainnet, 8453=base, 84532=base-sepolia, 10=optimism"
|
|
1041
|
+
),
|
|
1042
|
+
chain: z4.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().describe("Chain name (alternative to chainId). Default: base"),
|
|
1043
|
+
expirationMinutes: z4.number().optional().default(60).describe("Proof validity in minutes")
|
|
1044
|
+
},
|
|
1045
|
+
async ({ domain, uri, statement, chainId, chain, expirationMinutes }) => {
|
|
1046
|
+
try {
|
|
1047
|
+
const { account, address } = await getOrCreateWallet();
|
|
1048
|
+
const resolvedChainId = chainId ?? (chain ? CHAIN_IDS[chain] : CHAIN_IDS.base);
|
|
1049
|
+
const siweMessage = new SiweMessage({
|
|
1050
|
+
domain,
|
|
1051
|
+
address,
|
|
1052
|
+
statement,
|
|
1053
|
+
uri,
|
|
1054
|
+
version: "1",
|
|
1055
|
+
chainId: resolvedChainId,
|
|
1056
|
+
nonce: generateNonce(),
|
|
1057
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1058
|
+
expirationTime: new Date(
|
|
1059
|
+
Date.now() + expirationMinutes * 60 * 1e3
|
|
1060
|
+
).toISOString()
|
|
1061
|
+
});
|
|
1062
|
+
const message = siweMessage.prepareMessage();
|
|
1063
|
+
const signature = await account.signMessage({ message });
|
|
1064
|
+
const proof = JSON.stringify({
|
|
1065
|
+
message: JSON.stringify(siweMessage),
|
|
1066
|
+
signature
|
|
1067
|
+
});
|
|
1068
|
+
return mcpSuccess({
|
|
1069
|
+
proof,
|
|
1070
|
+
address,
|
|
1071
|
+
chainId: resolvedChainId,
|
|
1072
|
+
expiresAt: siweMessage.expirationTime,
|
|
1073
|
+
usage: "Add to request headers as: X-SIWE-PROOF: <proof>"
|
|
1074
|
+
});
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
return mcpError(err, { tool: "create_siwe_proof" });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/tools/fetch_with_siwe.ts
|
|
1083
|
+
import { z as z5 } from "zod";
|
|
1084
|
+
import { SiweMessage as SiweMessage2, generateNonce as generateNonce2 } from "siwe";
|
|
1085
|
+
var CHAIN_IDS2 = {
|
|
1086
|
+
mainnet: 1,
|
|
1087
|
+
base: 8453,
|
|
1088
|
+
"base-sepolia": 84532,
|
|
1089
|
+
optimism: 10,
|
|
1090
|
+
arbitrum: 42161,
|
|
1091
|
+
polygon: 137,
|
|
1092
|
+
sepolia: 11155111
|
|
1093
|
+
};
|
|
1094
|
+
async function createSiweProof(account, domain, uri, chainId) {
|
|
1095
|
+
const siweMessage = new SiweMessage2({
|
|
1096
|
+
domain,
|
|
1097
|
+
address: account.address,
|
|
1098
|
+
statement: "Authenticate to API",
|
|
1099
|
+
uri,
|
|
1100
|
+
version: "1",
|
|
1101
|
+
chainId,
|
|
1102
|
+
nonce: generateNonce2(),
|
|
1103
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1104
|
+
expirationTime: new Date(Date.now() + 36e5).toISOString()
|
|
1105
|
+
});
|
|
1106
|
+
const message = siweMessage.prepareMessage();
|
|
1107
|
+
const signature = await account.signMessage({ message });
|
|
1108
|
+
return JSON.stringify({
|
|
1109
|
+
message: JSON.stringify(siweMessage),
|
|
1110
|
+
signature
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function registerFetchWithSiweTool(server) {
|
|
1114
|
+
server.tool(
|
|
1115
|
+
"fetch_with_siwe",
|
|
1116
|
+
"Make an HTTP request with automatic SIWE wallet authentication. Useful for APIs that require wallet ownership proof.",
|
|
1117
|
+
{
|
|
1118
|
+
url: z5.string().url().describe("The URL to fetch"),
|
|
1119
|
+
method: z5.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().default("GET"),
|
|
1120
|
+
body: z5.unknown().optional().describe("Request body for POST/PUT/PATCH"),
|
|
1121
|
+
headers: z5.record(z5.string()).optional().describe("Additional headers"),
|
|
1122
|
+
siweHeader: z5.string().optional().default("X-SIWE-PROOF").describe("Header name for SIWE proof"),
|
|
1123
|
+
chainId: z5.number().optional().describe(
|
|
1124
|
+
"Chain ID for SIWE proof (default: 8453 for Base). Common IDs: 1=mainnet, 8453=base, 84532=base-sepolia"
|
|
1125
|
+
),
|
|
1126
|
+
chain: z5.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().describe("Chain name (alternative to chainId). Default: base")
|
|
1127
|
+
},
|
|
1128
|
+
async ({ url, method, body, headers, siweHeader, chainId, chain }) => {
|
|
1129
|
+
try {
|
|
1130
|
+
const { account } = await getOrCreateWallet();
|
|
1131
|
+
const parsedUrl = new URL(url);
|
|
1132
|
+
const resolvedChainId = chainId ?? (chain ? CHAIN_IDS2[chain] : CHAIN_IDS2.base);
|
|
1133
|
+
const proof = await createSiweProof(
|
|
1134
|
+
account,
|
|
1135
|
+
parsedUrl.host,
|
|
1136
|
+
parsedUrl.origin,
|
|
1137
|
+
resolvedChainId
|
|
1138
|
+
);
|
|
1139
|
+
const response = await fetch(url, {
|
|
1140
|
+
method,
|
|
1141
|
+
headers: {
|
|
1142
|
+
"Content-Type": "application/json",
|
|
1143
|
+
[siweHeader]: proof,
|
|
1144
|
+
...headers
|
|
1145
|
+
},
|
|
1146
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1147
|
+
});
|
|
1148
|
+
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
1149
|
+
if (!response.ok) {
|
|
1150
|
+
let errorBody;
|
|
1151
|
+
try {
|
|
1152
|
+
errorBody = await response.json();
|
|
1153
|
+
} catch {
|
|
1154
|
+
errorBody = await response.text();
|
|
1155
|
+
}
|
|
1156
|
+
return mcpError(`HTTP ${response.status}`, {
|
|
1157
|
+
statusCode: response.status,
|
|
1158
|
+
headers: responseHeaders,
|
|
1159
|
+
body: errorBody
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
let data;
|
|
1163
|
+
const contentType = response.headers.get("content-type");
|
|
1164
|
+
if (contentType?.includes("application/json")) {
|
|
1165
|
+
data = await response.json();
|
|
1166
|
+
} else {
|
|
1167
|
+
data = await response.text();
|
|
1168
|
+
}
|
|
1169
|
+
return mcpSuccess({
|
|
1170
|
+
statusCode: response.status,
|
|
1171
|
+
headers: responseHeaders,
|
|
1172
|
+
data
|
|
1173
|
+
});
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
return mcpError(err, { tool: "fetch_with_siwe", url });
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1017
1181
|
// src/index.ts
|
|
1018
1182
|
async function main() {
|
|
1019
1183
|
log.clear();
|
|
@@ -1026,7 +1190,9 @@ async function main() {
|
|
|
1026
1190
|
registerQueryEndpointTool(server);
|
|
1027
1191
|
registerValidatePaymentTool(server);
|
|
1028
1192
|
registerExecuteCallTool(server);
|
|
1029
|
-
|
|
1193
|
+
registerCreateSiweProofTool(server);
|
|
1194
|
+
registerFetchWithSiweTool(server);
|
|
1195
|
+
log.info("Registered 6 tools: check_balance, query_endpoint, validate_payment, execute_call, create_siwe_proof, fetch_with_siwe");
|
|
1030
1196
|
const transport = new StdioServerTransport();
|
|
1031
1197
|
await server.connect(transport);
|
|
1032
1198
|
log.info(`Connected to transport, ready for requests. Log file: ${log.path}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402scan-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Generic MCP server for calling x402-protected APIs with automatic payment handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,19 +10,11 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsup",
|
|
15
|
-
"build:mcpb": "tsx scripts/build-mcpb.ts",
|
|
16
|
-
"dev": "tsx src/index.ts",
|
|
17
|
-
"dev:bun": "bun run src/index.ts",
|
|
18
|
-
"typecheck": "tsc --noEmit",
|
|
19
|
-
"publish-package": "tsx scripts/publish.ts",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
21
|
-
},
|
|
22
13
|
"dependencies": {
|
|
23
14
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
24
15
|
"@x402/core": "^2.0.0",
|
|
25
16
|
"@x402/evm": "^2.0.0",
|
|
17
|
+
"siwe": "^2.3.2",
|
|
26
18
|
"viem": "^2.31.3",
|
|
27
19
|
"zod": "^3.25.1"
|
|
28
20
|
},
|
|
@@ -50,5 +42,13 @@
|
|
|
50
42
|
"repository": {
|
|
51
43
|
"type": "git",
|
|
52
44
|
"url": "https://github.com/merit-systems/x402scan-mcp"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"build:mcpb": "tsx scripts/build-mcpb.ts",
|
|
49
|
+
"dev": "tsx src/index.ts",
|
|
50
|
+
"dev:bun": "bun run src/index.ts",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"publish-package": "tsx scripts/publish.ts"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|