@x402storage/mcp 1.0.0

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 ADDED
@@ -0,0 +1,100 @@
1
+ # x402store-mcp
2
+
3
+ MCP server for storing files permanently on IPFS via [x402.storage](https://x402.storage).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g x402store-mcp
9
+ ```
10
+
11
+ Or clone and build locally:
12
+
13
+ ```bash
14
+ git clone https://github.com/anthropics/x402store-mcp
15
+ cd x402store-mcp
16
+ npm install
17
+ npm run build
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ ### 1. Set up your wallet
23
+
24
+ Export your Base wallet private key:
25
+
26
+ ```bash
27
+ export X402_PRIVATE_KEY=0x...
28
+ ```
29
+
30
+ Need a wallet? Create one at [x402.storage](https://x402.storage).
31
+
32
+ ### 2. Configure Claude Desktop
33
+
34
+ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "x402store": {
40
+ "command": "node",
41
+ "args": ["/path/to/x402store-mcp/dist/index.js"],
42
+ "env": {
43
+ "X402_PRIVATE_KEY": "0x..."
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Or if installed globally:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "x402store": {
56
+ "command": "x402store-mcp",
57
+ "env": {
58
+ "X402_PRIVATE_KEY": "0x..."
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### 3. Restart Claude Desktop
66
+
67
+ Fully quit and restart Claude Desktop for the configuration to take effect.
68
+
69
+ ## Usage
70
+
71
+ Once configured, you can ask Claude to store files:
72
+
73
+ > "Store the file /path/to/document.pdf permanently"
74
+
75
+ Claude will use the `store_file` tool to upload the file and return the permanent IPFS URL.
76
+
77
+ ## Tool
78
+
79
+ ### store_file
80
+
81
+ Store a file permanently on IPFS.
82
+
83
+ **Input:**
84
+ - `file_path` (string): Absolute or relative path to the file
85
+
86
+ **Output:**
87
+ - Permanent IPFS gateway URL (e.g., `https://x402.storage/bafybeig...`)
88
+
89
+ **Errors:**
90
+ - File not found
91
+ - Insufficient balance (fund wallet at x402.storage)
92
+ - Network error
93
+
94
+ ## Cost
95
+
96
+ Each upload costs $0.01 USDC on Base. Files are stored permanently.
97
+
98
+ ## License
99
+
100
+ MIT
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Environment validation for x402store MCP server
3
+ */
4
+ /**
5
+ * Validates required environment variables.
6
+ * Returns error message if X402_PRIVATE_KEY is missing, null if valid.
7
+ */
8
+ export declare function validateEnvironment(): string | null;
9
+ /**
10
+ * Gets the private key from environment.
11
+ * Should only be called after validateEnvironment() returns null.
12
+ */
13
+ export declare function getPrivateKey(): `0x${string}`;
package/dist/config.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Environment validation for x402store MCP server
3
+ */
4
+ /**
5
+ * Validates required environment variables.
6
+ * Returns error message if X402_PRIVATE_KEY is missing, null if valid.
7
+ */
8
+ export function validateEnvironment() {
9
+ if (!process.env.X402_PRIVATE_KEY) {
10
+ return 'X402_PRIVATE_KEY not set. Export your Base wallet private key: export X402_PRIVATE_KEY=0x... Need a wallet? Create one at https://x402.storage';
11
+ }
12
+ return null;
13
+ }
14
+ /**
15
+ * Gets the private key from environment.
16
+ * Should only be called after validateEnvironment() returns null.
17
+ */
18
+ export function getPrivateKey() {
19
+ return process.env.X402_PRIVATE_KEY;
20
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import { resolve } from 'node:path';
6
+ import { validateEnvironment, getPrivateKey } from './config.js';
7
+ import { uploadFile, FileNotFoundError, UploadError } from './upload.js';
8
+ // Create MCP server
9
+ const server = new McpServer({
10
+ name: 'x402store',
11
+ version: '1.0.0',
12
+ });
13
+ // Register store_file tool
14
+ server.tool('store_file', 'Store a file permanently on IPFS via x402.storage. Returns the permanent gateway URL.', {
15
+ file_path: z
16
+ .string()
17
+ .describe('Absolute or relative path to the file to upload'),
18
+ }, async ({ file_path }) => {
19
+ // Validate environment first
20
+ const envError = validateEnvironment();
21
+ if (envError) {
22
+ return {
23
+ content: [{ type: 'text', text: `Error: ${envError}` }],
24
+ };
25
+ }
26
+ try {
27
+ const resolvedPath = resolve(file_path);
28
+ const privateKey = getPrivateKey();
29
+ const result = await uploadFile(resolvedPath, privateKey);
30
+ return {
31
+ content: [
32
+ {
33
+ type: 'text',
34
+ text: result.gateway,
35
+ },
36
+ ],
37
+ };
38
+ }
39
+ catch (error) {
40
+ if (error instanceof FileNotFoundError) {
41
+ return {
42
+ content: [{ type: 'text', text: `Error: File not found: ${file_path}` }],
43
+ };
44
+ }
45
+ if (error instanceof UploadError) {
46
+ if (error.type === 'insufficient_balance') {
47
+ return {
48
+ content: [
49
+ {
50
+ type: 'text',
51
+ text: 'Error: Insufficient balance. Fund your wallet at https://x402.storage or transfer USDC on Base.',
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ if (error.type === 'network_error') {
57
+ return {
58
+ content: [
59
+ { type: 'text', text: 'Error: Network error. Check your connection and try again.' },
60
+ ],
61
+ };
62
+ }
63
+ return {
64
+ content: [{ type: 'text', text: `Error: Upload failed: ${error.message}` }],
65
+ };
66
+ }
67
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
68
+ return {
69
+ content: [{ type: 'text', text: `Error: ${message}` }],
70
+ };
71
+ }
72
+ });
73
+ // Run server with stdio transport
74
+ async function main() {
75
+ const transport = new StdioServerTransport();
76
+ await server.connect(transport);
77
+ // Note: MCP servers must not write to stdout (breaks JSON-RPC)
78
+ // Use console.error for any debugging
79
+ console.error('x402store MCP server running on stdio');
80
+ }
81
+ main().catch((error) => {
82
+ console.error('Fatal error:', error);
83
+ process.exit(1);
84
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Upload logic with x402 payment integration
3
+ */
4
+ /**
5
+ * Error thrown when file doesn't exist
6
+ */
7
+ export declare class FileNotFoundError extends Error {
8
+ constructor(filePath: string);
9
+ }
10
+ /**
11
+ * Types of upload errors
12
+ */
13
+ export type UploadErrorType = 'network_error' | 'payment_failed' | 'upload_failed' | 'insufficient_balance';
14
+ /**
15
+ * Error thrown during upload
16
+ */
17
+ export declare class UploadError extends Error {
18
+ readonly type: UploadErrorType;
19
+ constructor(message: string, type: UploadErrorType);
20
+ }
21
+ /**
22
+ * Response from the upload API
23
+ */
24
+ interface UploadResponse {
25
+ cid: string;
26
+ gateway: string;
27
+ }
28
+ /**
29
+ * Uploads a file to api.x402.storage with automatic payment handling
30
+ *
31
+ * @param filePath - Path to the file to upload
32
+ * @param privateKey - Private key for x402 payment signing
33
+ * @returns Object containing CID and gateway URL
34
+ * @throws FileNotFoundError if file doesn't exist
35
+ * @throws UploadError on network, payment, or upload failures
36
+ */
37
+ export declare function uploadFile(filePath: string, privateKey: string): Promise<UploadResponse>;
38
+ export {};
package/dist/upload.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Upload logic with x402 payment integration
3
+ */
4
+ import { x402Client } from '@x402/core/client';
5
+ import { x402HTTPClient } from '@x402/core/http';
6
+ import { ExactEvmScheme } from '@x402/evm/exact/client';
7
+ import { wrapFetchWithPayment } from '@x402/fetch';
8
+ import { privateKeyToAccount } from 'viem/accounts';
9
+ import { openAsBlob } from 'node:fs';
10
+ import { stat } from 'node:fs/promises';
11
+ import { basename } from 'node:path';
12
+ /**
13
+ * Error thrown when file doesn't exist
14
+ */
15
+ export class FileNotFoundError extends Error {
16
+ constructor(filePath) {
17
+ super(`File not found: ${filePath}`);
18
+ this.name = 'FileNotFoundError';
19
+ }
20
+ }
21
+ /**
22
+ * Error thrown during upload
23
+ */
24
+ export class UploadError extends Error {
25
+ type;
26
+ constructor(message, type) {
27
+ super(message);
28
+ this.name = 'UploadError';
29
+ this.type = type;
30
+ }
31
+ }
32
+ /**
33
+ * Creates an x402 payment client with the given private key
34
+ */
35
+ function createPaymentClient(privateKey) {
36
+ const signer = privateKeyToAccount(privateKey);
37
+ const coreClient = new x402Client().register('eip155:*', new ExactEvmScheme(signer));
38
+ return new x402HTTPClient(coreClient);
39
+ }
40
+ /**
41
+ * Uploads a file to api.x402.storage with automatic payment handling
42
+ *
43
+ * @param filePath - Path to the file to upload
44
+ * @param privateKey - Private key for x402 payment signing
45
+ * @returns Object containing CID and gateway URL
46
+ * @throws FileNotFoundError if file doesn't exist
47
+ * @throws UploadError on network, payment, or upload failures
48
+ */
49
+ export async function uploadFile(filePath, privateKey) {
50
+ // Check file exists
51
+ try {
52
+ await stat(filePath);
53
+ }
54
+ catch {
55
+ throw new FileNotFoundError(filePath);
56
+ }
57
+ // Create payment client and wrap fetch
58
+ const client = createPaymentClient(privateKey);
59
+ const fetchWithPayment = wrapFetchWithPayment(fetch, client);
60
+ // Read file as blob
61
+ const blob = await openAsBlob(filePath);
62
+ const fileName = basename(filePath);
63
+ // Create FormData with file
64
+ const formData = new FormData();
65
+ formData.append('file', blob, fileName);
66
+ // Upload to api.x402.storage (root endpoint, not /store)
67
+ let response;
68
+ try {
69
+ response = await fetchWithPayment('https://api.x402.storage', {
70
+ method: 'POST',
71
+ body: formData,
72
+ // Do NOT set Content-Type header - let Node.js set boundary
73
+ });
74
+ }
75
+ catch (error) {
76
+ // Network or payment errors during fetch
77
+ const message = error instanceof Error ? error.message : 'Unknown network error';
78
+ // Check for payment-related errors
79
+ if (message.toLowerCase().includes('insufficient')) {
80
+ throw new UploadError(message, 'insufficient_balance');
81
+ }
82
+ if (message.toLowerCase().includes('payment') ||
83
+ message.toLowerCase().includes('402')) {
84
+ throw new UploadError(message, 'payment_failed');
85
+ }
86
+ throw new UploadError(message, 'network_error');
87
+ }
88
+ // Handle non-OK responses
89
+ if (!response.ok) {
90
+ let errorMessage = `Upload failed: ${response.status} ${response.statusText}`;
91
+ try {
92
+ const errorBody = (await response.json());
93
+ if (errorBody.error) {
94
+ errorMessage = errorBody.error;
95
+ }
96
+ }
97
+ catch {
98
+ // Ignore JSON parse errors, use status message
99
+ }
100
+ // Determine error type from status code
101
+ if (response.status === 402) {
102
+ throw new UploadError(errorMessage, 'payment_failed');
103
+ }
104
+ if (response.status >= 500) {
105
+ throw new UploadError(errorMessage, 'network_error');
106
+ }
107
+ throw new UploadError(errorMessage, 'upload_failed');
108
+ }
109
+ // Parse successful response
110
+ const result = (await response.json());
111
+ return {
112
+ cid: result.cid,
113
+ gateway: result.gateway,
114
+ };
115
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@x402storage/mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for x402.storage file uploads",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/rawgroundbeef/x402.storage"
10
+ },
11
+ "bin": {
12
+ "x402-mcp": "./dist/index.js"
13
+ },
14
+ "files": ["dist"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": ["x402", "ipfs", "storage", "mcp", "claude"],
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.25.0",
25
+ "@x402/core": "^2.2.0",
26
+ "@x402/evm": "latest",
27
+ "@x402/fetch": "latest",
28
+ "viem": "^2.21.0",
29
+ "zod": "^3.24.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.7.0"
34
+ }
35
+ }