x402scan-mcp 0.0.2 → 0.0.4
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 +218 -2
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -745,7 +745,7 @@ async function queryEndpoint(url, httpClient, options = {}) {
|
|
|
745
745
|
function registerQueryEndpointTool(server) {
|
|
746
746
|
server.tool(
|
|
747
747
|
"query_endpoint",
|
|
748
|
-
"Probe an x402-protected endpoint to get pricing and
|
|
748
|
+
"Probe an x402-protected endpoint to get pricing and requirements without payment. Returns payment options, Bazaar schema, and Sign-In-With-X auth requirements (x402 v2) if available.",
|
|
749
749
|
{
|
|
750
750
|
url: z.string().url().describe("The x402-protected endpoint URL to probe"),
|
|
751
751
|
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET").describe("HTTP method to use"),
|
|
@@ -811,6 +811,29 @@ function registerQueryEndpointTool(server) {
|
|
|
811
811
|
hasBazaarExtension: true
|
|
812
812
|
};
|
|
813
813
|
}
|
|
814
|
+
if (pr.extensions?.["sign-in-with-x"]) {
|
|
815
|
+
const siwx = pr.extensions["sign-in-with-x"];
|
|
816
|
+
const info = siwx.info || {};
|
|
817
|
+
const requiredFields = ["domain", "uri", "version", "chainId", "nonce", "issuedAt"];
|
|
818
|
+
const missingFields = requiredFields.filter((f) => !info[f]);
|
|
819
|
+
const validationErrors = [];
|
|
820
|
+
if (!siwx.info) {
|
|
821
|
+
validationErrors.push('Missing "info" object in sign-in-with-x extension');
|
|
822
|
+
} else if (missingFields.length > 0) {
|
|
823
|
+
validationErrors.push(`Missing required fields in info: ${missingFields.join(", ")}`);
|
|
824
|
+
}
|
|
825
|
+
if (!siwx.schema) {
|
|
826
|
+
validationErrors.push('Missing "schema" object in sign-in-with-x extension');
|
|
827
|
+
}
|
|
828
|
+
response.signInWithX = {
|
|
829
|
+
required: true,
|
|
830
|
+
valid: validationErrors.length === 0,
|
|
831
|
+
validationErrors: validationErrors.length > 0 ? validationErrors : void 0,
|
|
832
|
+
info: siwx.info,
|
|
833
|
+
schema: siwx.schema,
|
|
834
|
+
usage: "Use create_siwe_proof or fetch_with_siwe tools to authenticate"
|
|
835
|
+
};
|
|
836
|
+
}
|
|
814
837
|
if (pr.error) {
|
|
815
838
|
response.serverError = pr.error;
|
|
816
839
|
}
|
|
@@ -1014,6 +1037,197 @@ function registerExecuteCallTool(server) {
|
|
|
1014
1037
|
);
|
|
1015
1038
|
}
|
|
1016
1039
|
|
|
1040
|
+
// src/tools/siwe.ts
|
|
1041
|
+
import { z as z4 } from "zod";
|
|
1042
|
+
import { SiweMessage, generateNonce } from "siwe";
|
|
1043
|
+
var NETWORKS = {
|
|
1044
|
+
mainnet: "eip155:1",
|
|
1045
|
+
base: "eip155:8453",
|
|
1046
|
+
"base-sepolia": "eip155:84532",
|
|
1047
|
+
optimism: "eip155:10",
|
|
1048
|
+
arbitrum: "eip155:42161",
|
|
1049
|
+
polygon: "eip155:137",
|
|
1050
|
+
sepolia: "eip155:11155111"
|
|
1051
|
+
};
|
|
1052
|
+
function parseChainId(network) {
|
|
1053
|
+
const parts = network.split(":");
|
|
1054
|
+
return parseInt(parts[1], 10);
|
|
1055
|
+
}
|
|
1056
|
+
function registerCreateSiweProofTool(server) {
|
|
1057
|
+
server.tool(
|
|
1058
|
+
"create_siwe_proof",
|
|
1059
|
+
"Create a CAIP-122 compliant Sign-In-With-X proof for wallet authentication (x402 v2 extension). Returns a flat proof object for the SIGN-IN-WITH-X header.",
|
|
1060
|
+
{
|
|
1061
|
+
domain: z4.string().describe('Domain requesting auth (e.g., "api.example.com")'),
|
|
1062
|
+
uri: z4.string().url().describe('Full URI of the resource (e.g., "https://api.example.com")'),
|
|
1063
|
+
statement: z4.string().optional().default("Authenticate to API").describe("Human-readable statement"),
|
|
1064
|
+
network: z4.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().default("base").describe("Network name (default: base)"),
|
|
1065
|
+
expirationMinutes: z4.number().optional().default(5).describe("Proof validity in minutes (default: 5)")
|
|
1066
|
+
},
|
|
1067
|
+
async ({ domain, uri, statement, network, expirationMinutes }) => {
|
|
1068
|
+
try {
|
|
1069
|
+
const { account, address } = await getOrCreateWallet();
|
|
1070
|
+
const caip2Network = NETWORKS[network];
|
|
1071
|
+
const numericChainId = parseChainId(caip2Network);
|
|
1072
|
+
const nonce = generateNonce();
|
|
1073
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1074
|
+
const expirationTime = new Date(
|
|
1075
|
+
Date.now() + expirationMinutes * 60 * 1e3
|
|
1076
|
+
).toISOString();
|
|
1077
|
+
const siweMessage = new SiweMessage({
|
|
1078
|
+
domain,
|
|
1079
|
+
address,
|
|
1080
|
+
statement,
|
|
1081
|
+
uri,
|
|
1082
|
+
version: "1",
|
|
1083
|
+
chainId: numericChainId,
|
|
1084
|
+
nonce,
|
|
1085
|
+
issuedAt,
|
|
1086
|
+
expirationTime,
|
|
1087
|
+
resources: [uri]
|
|
1088
|
+
});
|
|
1089
|
+
const message = siweMessage.prepareMessage();
|
|
1090
|
+
const signature = await account.signMessage({ message });
|
|
1091
|
+
const proof = {
|
|
1092
|
+
domain,
|
|
1093
|
+
address,
|
|
1094
|
+
statement,
|
|
1095
|
+
uri,
|
|
1096
|
+
version: "1",
|
|
1097
|
+
chainId: caip2Network,
|
|
1098
|
+
nonce,
|
|
1099
|
+
issuedAt,
|
|
1100
|
+
expirationTime,
|
|
1101
|
+
resources: [uri],
|
|
1102
|
+
signature
|
|
1103
|
+
};
|
|
1104
|
+
return mcpSuccess({
|
|
1105
|
+
proof: JSON.stringify(proof),
|
|
1106
|
+
address,
|
|
1107
|
+
network: caip2Network,
|
|
1108
|
+
expiresAt: expirationTime,
|
|
1109
|
+
usage: "Add to request headers as: SIGN-IN-WITH-X: <proof>"
|
|
1110
|
+
});
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
return mcpError(err, { tool: "create_siwe_proof" });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// src/tools/fetch_with_siwe.ts
|
|
1119
|
+
import { z as z5 } from "zod";
|
|
1120
|
+
import { SiweMessage as SiweMessage2, generateNonce as generateNonce2 } from "siwe";
|
|
1121
|
+
var NETWORKS2 = {
|
|
1122
|
+
mainnet: "eip155:1",
|
|
1123
|
+
base: "eip155:8453",
|
|
1124
|
+
"base-sepolia": "eip155:84532",
|
|
1125
|
+
optimism: "eip155:10",
|
|
1126
|
+
arbitrum: "eip155:42161",
|
|
1127
|
+
polygon: "eip155:137",
|
|
1128
|
+
sepolia: "eip155:11155111"
|
|
1129
|
+
};
|
|
1130
|
+
function parseChainId2(network) {
|
|
1131
|
+
const parts = network.split(":");
|
|
1132
|
+
return parseInt(parts[1], 10);
|
|
1133
|
+
}
|
|
1134
|
+
async function createSiwxProof(account, domain, uri, network) {
|
|
1135
|
+
const numericChainId = parseChainId2(network);
|
|
1136
|
+
const nonce = generateNonce2();
|
|
1137
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1138
|
+
const expirationTime = new Date(Date.now() + 3e5).toISOString();
|
|
1139
|
+
const siweMessage = new SiweMessage2({
|
|
1140
|
+
domain,
|
|
1141
|
+
address: account.address,
|
|
1142
|
+
statement: "Authenticate to API",
|
|
1143
|
+
uri,
|
|
1144
|
+
version: "1",
|
|
1145
|
+
chainId: numericChainId,
|
|
1146
|
+
nonce,
|
|
1147
|
+
issuedAt,
|
|
1148
|
+
expirationTime,
|
|
1149
|
+
resources: [uri]
|
|
1150
|
+
});
|
|
1151
|
+
const message = siweMessage.prepareMessage();
|
|
1152
|
+
const signature = await account.signMessage({ message });
|
|
1153
|
+
return JSON.stringify({
|
|
1154
|
+
domain,
|
|
1155
|
+
address: account.address,
|
|
1156
|
+
statement: "Authenticate to API",
|
|
1157
|
+
uri,
|
|
1158
|
+
version: "1",
|
|
1159
|
+
chainId: network,
|
|
1160
|
+
nonce,
|
|
1161
|
+
issuedAt,
|
|
1162
|
+
expirationTime,
|
|
1163
|
+
resources: [uri],
|
|
1164
|
+
signature
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function registerFetchWithSiweTool(server) {
|
|
1168
|
+
server.tool(
|
|
1169
|
+
"fetch_with_siwe",
|
|
1170
|
+
"Make an HTTP request with automatic CAIP-122 Sign-In-With-X wallet authentication (x402 v2 extension).",
|
|
1171
|
+
{
|
|
1172
|
+
url: z5.string().url().describe("The URL to fetch"),
|
|
1173
|
+
method: z5.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().default("GET"),
|
|
1174
|
+
body: z5.unknown().optional().describe("Request body for POST/PUT/PATCH"),
|
|
1175
|
+
headers: z5.record(z5.string()).optional().describe("Additional headers"),
|
|
1176
|
+
network: z5.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().default("base").describe("Network name (default: base)")
|
|
1177
|
+
},
|
|
1178
|
+
async ({ url, method, body, headers, network }) => {
|
|
1179
|
+
try {
|
|
1180
|
+
const { account } = await getOrCreateWallet();
|
|
1181
|
+
const parsedUrl = new URL(url);
|
|
1182
|
+
const caip2Network = NETWORKS2[network];
|
|
1183
|
+
const proof = await createSiwxProof(
|
|
1184
|
+
account,
|
|
1185
|
+
parsedUrl.host,
|
|
1186
|
+
parsedUrl.origin,
|
|
1187
|
+
caip2Network
|
|
1188
|
+
);
|
|
1189
|
+
const response = await fetch(url, {
|
|
1190
|
+
method,
|
|
1191
|
+
headers: {
|
|
1192
|
+
"Content-Type": "application/json",
|
|
1193
|
+
"SIGN-IN-WITH-X": proof,
|
|
1194
|
+
...headers
|
|
1195
|
+
},
|
|
1196
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1197
|
+
});
|
|
1198
|
+
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
1199
|
+
if (!response.ok) {
|
|
1200
|
+
let errorBody;
|
|
1201
|
+
try {
|
|
1202
|
+
errorBody = await response.json();
|
|
1203
|
+
} catch {
|
|
1204
|
+
errorBody = await response.text();
|
|
1205
|
+
}
|
|
1206
|
+
return mcpError(`HTTP ${response.status}`, {
|
|
1207
|
+
statusCode: response.status,
|
|
1208
|
+
headers: responseHeaders,
|
|
1209
|
+
body: errorBody
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
let data;
|
|
1213
|
+
const contentType = response.headers.get("content-type");
|
|
1214
|
+
if (contentType?.includes("application/json")) {
|
|
1215
|
+
data = await response.json();
|
|
1216
|
+
} else {
|
|
1217
|
+
data = await response.text();
|
|
1218
|
+
}
|
|
1219
|
+
return mcpSuccess({
|
|
1220
|
+
statusCode: response.status,
|
|
1221
|
+
headers: responseHeaders,
|
|
1222
|
+
data
|
|
1223
|
+
});
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
return mcpError(err, { tool: "fetch_with_siwe", url });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1017
1231
|
// src/index.ts
|
|
1018
1232
|
async function main() {
|
|
1019
1233
|
log.clear();
|
|
@@ -1026,7 +1240,9 @@ async function main() {
|
|
|
1026
1240
|
registerQueryEndpointTool(server);
|
|
1027
1241
|
registerValidatePaymentTool(server);
|
|
1028
1242
|
registerExecuteCallTool(server);
|
|
1029
|
-
|
|
1243
|
+
registerCreateSiweProofTool(server);
|
|
1244
|
+
registerFetchWithSiweTool(server);
|
|
1245
|
+
log.info("Registered 6 tools: check_balance, query_endpoint, validate_payment, execute_call, create_siwe_proof, fetch_with_siwe");
|
|
1030
1246
|
const transport = new StdioServerTransport();
|
|
1031
1247
|
await server.connect(transport);
|
|
1032
1248
|
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.4",
|
|
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",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
24
24
|
"@x402/core": "^2.0.0",
|
|
25
25
|
"@x402/evm": "^2.0.0",
|
|
26
|
+
"siwe": "^2.3.2",
|
|
26
27
|
"viem": "^2.31.3",
|
|
27
28
|
"zod": "^3.25.1"
|
|
28
29
|
},
|