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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +124 -59
  3. package/package.json +12 -10
package/README.md CHANGED
@@ -7,7 +7,7 @@ MCP server for calling [x402](https://x402.org)-protected APIs with automatic pa
7
7
  ### Claude Code
8
8
 
9
9
  ```bash
10
- claude mcp add x402scan -- npx -y x402scan-mcp@latest
10
+ claude mcp add x402scan --scope user -- npx -y x402scan-mcp@latest
11
11
  ```
12
12
 
13
13
  ### Codex
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 schema without making a payment. Returns payment requirements, accepted networks, and Bazaar schema if available.",
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 CHAIN_IDS = {
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 SIWE (Sign-In with Ethereum) proof for wallet authentication. Returns a proof string to use in X-SIWE-PROOF header.",
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
- '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")'),
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
- 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")
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, chainId, chain, expirationMinutes }) => {
1082
+ async ({ domain, uri, statement, network, expirationMinutes }) => {
1046
1083
  try {
1047
1084
  const { account, address } = await getOrCreateWallet();
1048
- const resolvedChainId = chainId ?? (chain ? CHAIN_IDS[chain] : CHAIN_IDS.base);
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: resolvedChainId,
1056
- nonce: generateNonce(),
1057
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1058
- expirationTime: new Date(
1059
- Date.now() + expirationMinutes * 60 * 1e3
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 = JSON.stringify({
1065
- message: JSON.stringify(siweMessage),
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
- chainId: resolvedChainId,
1072
- expiresAt: siweMessage.expirationTime,
1073
- usage: "Add to request headers as: X-SIWE-PROOF: <proof>"
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 CHAIN_IDS2 = {
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
- async function createSiweProof(account, domain, uri, chainId) {
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: generateNonce2(),
1103
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1104
- expirationTime: new Date(Date.now() + 36e5).toISOString()
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
- message: JSON.stringify(siweMessage),
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 SIWE wallet authentication. Useful for APIs that require wallet ownership proof.",
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
- 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")
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, siweHeader, chainId, chain }) => {
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 resolvedChainId = chainId ?? (chain ? CHAIN_IDS2[chain] : CHAIN_IDS2.base);
1133
- const proof = await createSiweProof(
1197
+ const caip2Network = NETWORKS2[network];
1198
+ const proof = await createSiwxProof(
1134
1199
  account,
1135
1200
  parsedUrl.host,
1136
1201
  parsedUrl.origin,
1137
- resolvedChainId
1202
+ caip2Network
1138
1203
  );
1139
1204
  const response = await fetch(url, {
1140
1205
  method,
1141
1206
  headers: {
1142
1207
  "Content-Type": "application/json",
1143
- [siweHeader]: proof,
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",
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
+ }