x402scan-mcp 0.0.3 → 0.0.5
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 +1 -1
- package/dist/index.js +124 -59
- package/package.json +12 -10
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -742,10 +742,11 @@ async function queryEndpoint(url, httpClient, options = {}) {
|
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
// src/tools/query_endpoint.ts
|
|
745
|
+
import { extractDiscoveryInfoV1, isDiscoverableV1 } from "@x402/extensions/bazaar";
|
|
745
746
|
function registerQueryEndpointTool(server) {
|
|
746
747
|
server.tool(
|
|
747
748
|
"query_endpoint",
|
|
748
|
-
"Probe an x402-protected endpoint to get pricing and
|
|
749
|
+
"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
750
|
{
|
|
750
751
|
url: z.string().url().describe("The x402-protected endpoint URL to probe"),
|
|
751
752
|
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET").describe("HTTP method to use"),
|
|
@@ -810,6 +811,43 @@ function registerQueryEndpointTool(server) {
|
|
|
810
811
|
examples: bazaar.examples,
|
|
811
812
|
hasBazaarExtension: true
|
|
812
813
|
};
|
|
814
|
+
} else if (pr.x402Version === 1 && result.rawBody) {
|
|
815
|
+
const v1Body = result.rawBody;
|
|
816
|
+
const firstAccept = v1Body.accepts?.[0];
|
|
817
|
+
if (firstAccept && isDiscoverableV1(firstAccept)) {
|
|
818
|
+
const discoveryInfo = extractDiscoveryInfoV1(firstAccept);
|
|
819
|
+
if (discoveryInfo) {
|
|
820
|
+
response.bazaar = {
|
|
821
|
+
info: discoveryInfo,
|
|
822
|
+
schema: null,
|
|
823
|
+
hasBazaarExtension: true,
|
|
824
|
+
sourceVersion: 1
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (pr.extensions?.["sign-in-with-x"]) {
|
|
830
|
+
const siwx = pr.extensions["sign-in-with-x"];
|
|
831
|
+
const info = siwx.info || {};
|
|
832
|
+
const requiredFields = ["domain", "uri", "version", "chainId", "nonce", "issuedAt"];
|
|
833
|
+
const missingFields = requiredFields.filter((f) => !info[f]);
|
|
834
|
+
const validationErrors = [];
|
|
835
|
+
if (!siwx.info) {
|
|
836
|
+
validationErrors.push('Missing "info" object in sign-in-with-x extension');
|
|
837
|
+
} else if (missingFields.length > 0) {
|
|
838
|
+
validationErrors.push(`Missing required fields in info: ${missingFields.join(", ")}`);
|
|
839
|
+
}
|
|
840
|
+
if (!siwx.schema) {
|
|
841
|
+
validationErrors.push('Missing "schema" object in sign-in-with-x extension');
|
|
842
|
+
}
|
|
843
|
+
response.signInWithX = {
|
|
844
|
+
required: true,
|
|
845
|
+
valid: validationErrors.length === 0,
|
|
846
|
+
validationErrors: validationErrors.length > 0 ? validationErrors : void 0,
|
|
847
|
+
info: siwx.info,
|
|
848
|
+
schema: siwx.schema,
|
|
849
|
+
usage: "Use create_siwe_proof or fetch_with_siwe tools to authenticate"
|
|
850
|
+
};
|
|
813
851
|
}
|
|
814
852
|
if (pr.error) {
|
|
815
853
|
response.serverError = pr.error;
|
|
@@ -1017,60 +1055,73 @@ function registerExecuteCallTool(server) {
|
|
|
1017
1055
|
// src/tools/siwe.ts
|
|
1018
1056
|
import { z as z4 } from "zod";
|
|
1019
1057
|
import { SiweMessage, generateNonce } from "siwe";
|
|
1020
|
-
var
|
|
1021
|
-
mainnet: 1,
|
|
1022
|
-
base: 8453,
|
|
1023
|
-
"base-sepolia": 84532,
|
|
1024
|
-
optimism: 10,
|
|
1025
|
-
arbitrum: 42161,
|
|
1026
|
-
polygon: 137,
|
|
1027
|
-
sepolia: 11155111
|
|
1058
|
+
var NETWORKS = {
|
|
1059
|
+
mainnet: "eip155:1",
|
|
1060
|
+
base: "eip155:8453",
|
|
1061
|
+
"base-sepolia": "eip155:84532",
|
|
1062
|
+
optimism: "eip155:10",
|
|
1063
|
+
arbitrum: "eip155:42161",
|
|
1064
|
+
polygon: "eip155:137",
|
|
1065
|
+
sepolia: "eip155:11155111"
|
|
1028
1066
|
};
|
|
1067
|
+
function parseChainId(network) {
|
|
1068
|
+
const parts = network.split(":");
|
|
1069
|
+
return parseInt(parts[1], 10);
|
|
1070
|
+
}
|
|
1029
1071
|
function registerCreateSiweProofTool(server) {
|
|
1030
1072
|
server.tool(
|
|
1031
1073
|
"create_siwe_proof",
|
|
1032
|
-
"Create a
|
|
1074
|
+
"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.",
|
|
1033
1075
|
{
|
|
1034
|
-
domain: z4.string().describe(
|
|
1035
|
-
|
|
1036
|
-
),
|
|
1037
|
-
uri: z4.string().url().describe('Full URI of the API (e.g., "http://localhost:3000")'),
|
|
1076
|
+
domain: z4.string().describe('Domain requesting auth (e.g., "api.example.com")'),
|
|
1077
|
+
uri: z4.string().url().describe('Full URI of the resource (e.g., "https://api.example.com")'),
|
|
1038
1078
|
statement: z4.string().optional().default("Authenticate to API").describe("Human-readable statement"),
|
|
1039
|
-
|
|
1040
|
-
|
|
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")
|
|
1079
|
+
network: z4.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().default("base").describe("Network name (default: base)"),
|
|
1080
|
+
expirationMinutes: z4.number().optional().default(5).describe("Proof validity in minutes (default: 5)")
|
|
1044
1081
|
},
|
|
1045
|
-
async ({ domain, uri, statement,
|
|
1082
|
+
async ({ domain, uri, statement, network, expirationMinutes }) => {
|
|
1046
1083
|
try {
|
|
1047
1084
|
const { account, address } = await getOrCreateWallet();
|
|
1048
|
-
const
|
|
1085
|
+
const caip2Network = NETWORKS[network];
|
|
1086
|
+
const numericChainId = parseChainId(caip2Network);
|
|
1087
|
+
const nonce = generateNonce();
|
|
1088
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1089
|
+
const expirationTime = new Date(
|
|
1090
|
+
Date.now() + expirationMinutes * 60 * 1e3
|
|
1091
|
+
).toISOString();
|
|
1049
1092
|
const siweMessage = new SiweMessage({
|
|
1050
1093
|
domain,
|
|
1051
1094
|
address,
|
|
1052
1095
|
statement,
|
|
1053
1096
|
uri,
|
|
1054
1097
|
version: "1",
|
|
1055
|
-
chainId:
|
|
1056
|
-
nonce
|
|
1057
|
-
issuedAt
|
|
1058
|
-
expirationTime
|
|
1059
|
-
|
|
1060
|
-
).toISOString()
|
|
1098
|
+
chainId: numericChainId,
|
|
1099
|
+
nonce,
|
|
1100
|
+
issuedAt,
|
|
1101
|
+
expirationTime,
|
|
1102
|
+
resources: [uri]
|
|
1061
1103
|
});
|
|
1062
1104
|
const message = siweMessage.prepareMessage();
|
|
1063
1105
|
const signature = await account.signMessage({ message });
|
|
1064
|
-
const proof =
|
|
1065
|
-
|
|
1106
|
+
const proof = {
|
|
1107
|
+
domain,
|
|
1108
|
+
address,
|
|
1109
|
+
statement,
|
|
1110
|
+
uri,
|
|
1111
|
+
version: "1",
|
|
1112
|
+
chainId: caip2Network,
|
|
1113
|
+
nonce,
|
|
1114
|
+
issuedAt,
|
|
1115
|
+
expirationTime,
|
|
1116
|
+
resources: [uri],
|
|
1066
1117
|
signature
|
|
1067
|
-
}
|
|
1118
|
+
};
|
|
1068
1119
|
return mcpSuccess({
|
|
1069
|
-
proof,
|
|
1120
|
+
proof: JSON.stringify(proof),
|
|
1070
1121
|
address,
|
|
1071
|
-
|
|
1072
|
-
expiresAt:
|
|
1073
|
-
usage: "Add to request headers as:
|
|
1122
|
+
network: caip2Network,
|
|
1123
|
+
expiresAt: expirationTime,
|
|
1124
|
+
usage: "Add to request headers as: SIGN-IN-WITH-X: <proof>"
|
|
1074
1125
|
});
|
|
1075
1126
|
} catch (err) {
|
|
1076
1127
|
return mcpError(err, { tool: "create_siwe_proof" });
|
|
@@ -1082,65 +1133,79 @@ function registerCreateSiweProofTool(server) {
|
|
|
1082
1133
|
// src/tools/fetch_with_siwe.ts
|
|
1083
1134
|
import { z as z5 } from "zod";
|
|
1084
1135
|
import { SiweMessage as SiweMessage2, generateNonce as generateNonce2 } from "siwe";
|
|
1085
|
-
var
|
|
1086
|
-
mainnet: 1,
|
|
1087
|
-
base: 8453,
|
|
1088
|
-
"base-sepolia": 84532,
|
|
1089
|
-
optimism: 10,
|
|
1090
|
-
arbitrum: 42161,
|
|
1091
|
-
polygon: 137,
|
|
1092
|
-
sepolia: 11155111
|
|
1136
|
+
var NETWORKS2 = {
|
|
1137
|
+
mainnet: "eip155:1",
|
|
1138
|
+
base: "eip155:8453",
|
|
1139
|
+
"base-sepolia": "eip155:84532",
|
|
1140
|
+
optimism: "eip155:10",
|
|
1141
|
+
arbitrum: "eip155:42161",
|
|
1142
|
+
polygon: "eip155:137",
|
|
1143
|
+
sepolia: "eip155:11155111"
|
|
1093
1144
|
};
|
|
1094
|
-
|
|
1145
|
+
function parseChainId2(network) {
|
|
1146
|
+
const parts = network.split(":");
|
|
1147
|
+
return parseInt(parts[1], 10);
|
|
1148
|
+
}
|
|
1149
|
+
async function createSiwxProof(account, domain, uri, network) {
|
|
1150
|
+
const numericChainId = parseChainId2(network);
|
|
1151
|
+
const nonce = generateNonce2();
|
|
1152
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1153
|
+
const expirationTime = new Date(Date.now() + 3e5).toISOString();
|
|
1095
1154
|
const siweMessage = new SiweMessage2({
|
|
1096
1155
|
domain,
|
|
1097
1156
|
address: account.address,
|
|
1098
1157
|
statement: "Authenticate to API",
|
|
1099
1158
|
uri,
|
|
1100
1159
|
version: "1",
|
|
1101
|
-
chainId,
|
|
1102
|
-
nonce
|
|
1103
|
-
issuedAt
|
|
1104
|
-
expirationTime
|
|
1160
|
+
chainId: numericChainId,
|
|
1161
|
+
nonce,
|
|
1162
|
+
issuedAt,
|
|
1163
|
+
expirationTime,
|
|
1164
|
+
resources: [uri]
|
|
1105
1165
|
});
|
|
1106
1166
|
const message = siweMessage.prepareMessage();
|
|
1107
1167
|
const signature = await account.signMessage({ message });
|
|
1108
1168
|
return JSON.stringify({
|
|
1109
|
-
|
|
1169
|
+
domain,
|
|
1170
|
+
address: account.address,
|
|
1171
|
+
statement: "Authenticate to API",
|
|
1172
|
+
uri,
|
|
1173
|
+
version: "1",
|
|
1174
|
+
chainId: network,
|
|
1175
|
+
nonce,
|
|
1176
|
+
issuedAt,
|
|
1177
|
+
expirationTime,
|
|
1178
|
+
resources: [uri],
|
|
1110
1179
|
signature
|
|
1111
1180
|
});
|
|
1112
1181
|
}
|
|
1113
1182
|
function registerFetchWithSiweTool(server) {
|
|
1114
1183
|
server.tool(
|
|
1115
1184
|
"fetch_with_siwe",
|
|
1116
|
-
"Make an HTTP request with automatic
|
|
1185
|
+
"Make an HTTP request with automatic CAIP-122 Sign-In-With-X wallet authentication (x402 v2 extension).",
|
|
1117
1186
|
{
|
|
1118
1187
|
url: z5.string().url().describe("The URL to fetch"),
|
|
1119
1188
|
method: z5.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional().default("GET"),
|
|
1120
1189
|
body: z5.unknown().optional().describe("Request body for POST/PUT/PATCH"),
|
|
1121
1190
|
headers: z5.record(z5.string()).optional().describe("Additional headers"),
|
|
1122
|
-
|
|
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")
|
|
1191
|
+
network: z5.enum(["mainnet", "base", "base-sepolia", "optimism", "arbitrum", "polygon", "sepolia"]).optional().default("base").describe("Network name (default: base)")
|
|
1127
1192
|
},
|
|
1128
|
-
async ({ url, method, body, headers,
|
|
1193
|
+
async ({ url, method, body, headers, network }) => {
|
|
1129
1194
|
try {
|
|
1130
1195
|
const { account } = await getOrCreateWallet();
|
|
1131
1196
|
const parsedUrl = new URL(url);
|
|
1132
|
-
const
|
|
1133
|
-
const proof = await
|
|
1197
|
+
const caip2Network = NETWORKS2[network];
|
|
1198
|
+
const proof = await createSiwxProof(
|
|
1134
1199
|
account,
|
|
1135
1200
|
parsedUrl.host,
|
|
1136
1201
|
parsedUrl.origin,
|
|
1137
|
-
|
|
1202
|
+
caip2Network
|
|
1138
1203
|
);
|
|
1139
1204
|
const response = await fetch(url, {
|
|
1140
1205
|
method,
|
|
1141
1206
|
headers: {
|
|
1142
1207
|
"Content-Type": "application/json",
|
|
1143
|
-
|
|
1208
|
+
"SIGN-IN-WITH-X": proof,
|
|
1144
1209
|
...headers
|
|
1145
1210
|
},
|
|
1146
1211
|
body: body ? JSON.stringify(body) : void 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402scan-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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,10 +10,20 @@
|
|
|
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
|
+
},
|
|
13
22
|
"dependencies": {
|
|
14
23
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
15
24
|
"@x402/core": "^2.0.0",
|
|
16
25
|
"@x402/evm": "^2.0.0",
|
|
26
|
+
"@x402/extensions": "^2.1.0",
|
|
17
27
|
"siwe": "^2.3.2",
|
|
18
28
|
"viem": "^2.31.3",
|
|
19
29
|
"zod": "^3.25.1"
|
|
@@ -42,13 +52,5 @@
|
|
|
42
52
|
"repository": {
|
|
43
53
|
"type": "git",
|
|
44
54
|
"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
55
|
}
|
|
54
|
-
}
|
|
56
|
+
}
|