├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | *.log 4 | .env* 5 | PRIVATE_README.md 6 | vscode 7 | .vscode 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ], 16 | "exclude": [ 17 | "node_modules", 18 | "dist" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@magnetai/free-usdc-transfer", 3 | "version": "0.1.5", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@coinbase/coinbase-sdk": "^0.13.0", 14 | "@ensdomains/ensjs": "^4.0.2", 15 | "@modelcontextprotocol/sdk": "^1.1.0", 16 | "@types/global-agent": "^2.1.3", 17 | "ethers": "^6.13.5", 18 | "global-agent": "^3.0.0", 19 | "zod": "^3.24.1" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^22.10.5", 23 | "ts-node": "^10.9.2", 24 | "typescript": "^5.7.3" 25 | }, 26 | "type": "module", 27 | "bin": { 28 | "free-usdc-transfer": "dist/index.js" 29 | }, 30 | "files": [ 31 | "dist" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Magnet Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Free USDC Transfer MCP Server 2 | 3 | An MCP server implementation enabling free USDC transfers on **[Base](https://base.org)** with **[Coinbase CDP](https://docs.cdp.coinbase.com/)** MPC Wallet integration. 4 | 5 | image 6 | 7 | ## Features 8 | 9 | - Free USDC Transfers: Send USDC to any address or ENS/BaseName domain on Base - no fees, just simple transfers 10 | - Coinbase MPC Wallet: Create and manage your Coinbase MPC wallet for secure, feeless transactions 11 | - Name Resolution: Automatic support for **ENS** and **BaseName** domains 12 | 13 | ## Functions 14 | 15 | ### `tranfer-usdc` 16 | - Description: Analyze the value of the purchased items and transfer USDC to the recipient via the Base chain. Due to the uncertainty of blockchain transaction times, the transaction is only scheduled here and will not wait for the transaction to be completed. 17 | - Inputs: 18 | - usdc_amount (number): USDC amount, greater than 0. 19 | - recipient (string): Recipient's on-chain address or ENS domain (e.g., example.eth). 20 | - Behavior: 21 | - Verifies the recipient's address or resolves ENS domains. 22 | - Schedules a USDC transfer on the Base chain. 23 | - Provides a link to view transaction details on BaseScan. 24 | 25 | ### `create_coinbase_mpc_wallet` 26 | - Description: Create a Coinbase MPC wallet address. 27 | - Behavior: 28 | - Creates a new Coinbase MPC wallet and saves the seed to a secure file. 29 | - If a wallet already exists, returns the existing wallet address. 30 | - The seed file for Coinbase MPC wallets is stored in the Documents directory under the file name mpc_info.json. 31 | 32 | ## Configuration 33 | 34 | ### Getting an API Key 35 | 1. Sign up for a [Coinbase CDP account](https://portal.cdp.coinbase.com/) 36 | 2. Generate your API key from the developer dashboard 37 | 38 | ### Usage with Claude Desktop 39 | 40 | 1. Add this to your `claude_desktop_config.json`: 41 | ```json 42 | { 43 | "mcpServers": { 44 | "free-usdc-transfer": { 45 | "command": "npx", 46 | "args": [ 47 | "-y", 48 | "@magnetai/free-usdc-transfer" 49 | ], 50 | "env": { 51 | "COINBASE_CDP_API_KEY_NAME": "YOUR_COINBASE_CDP_API_KEY_NAME", 52 | "COINBASE_CDP_PRIVATE_KEY": "YOUR_COINBASE_CDP_PRIVATE_KEY" 53 | } 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | 2. Or install the server with **[magnet-desktop](https://github.com/magnetai/magnet-desktop)** 60 | 61 | ## License 62 | 63 | This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. 64 | 65 | --- 66 | 67 | Crafted by [Magnet Labs](https://magnetlabs.xyz) with our vibrant AI & Crypto community 68 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { 6 | CallToolRequestSchema, 7 | ListToolsRequestSchema, 8 | } from "@modelcontextprotocol/sdk/types.js"; 9 | import { z } from "zod"; 10 | import * as fs from 'fs/promises'; 11 | import { http } from 'viem' 12 | import { mainnet } from 'viem/chains' 13 | import { createEnsPublicClient } from '@ensdomains/ensjs' 14 | import { Coinbase, Wallet } from "@coinbase/coinbase-sdk"; 15 | import { ethers } from "ethers"; 16 | 17 | // import { bootstrap } from 'global-agent'; 18 | // process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://127.0.0.1:10080'; 19 | // process.env.GLOBAL_AGENT_HTTPS_PROXY = 'http://127.0.0.1:10080'; 20 | // process.env.GLOBAL_AGENT_NO_PROXY = 'localhost,127.0.0.1'; 21 | // bootstrap(); 22 | 23 | // Base scan 24 | const BASE_SCAN_ADDR = "https://basescan.org/address/" 25 | 26 | // Check for API key 27 | const COINBASE_CDP_API_KEY_NAME = process.env.COINBASE_CDP_API_KEY_NAME!; 28 | if (!COINBASE_CDP_API_KEY_NAME) { 29 | console.error("Error: COINBASE_CDP_API_KEY_NAME environment variable is required"); 30 | process.exit(1); 31 | } 32 | const COINBASE_CDP_PRIVATE_KEY = process.env.COINBASE_CDP_PRIVATE_KEY!; 33 | if (!COINBASE_CDP_PRIVATE_KEY) { 34 | console.error("Error: COINBASE_CDP_SECRET environment variable is required"); 35 | process.exit(1); 36 | } 37 | Coinbase.configure({ apiKeyName: COINBASE_CDP_API_KEY_NAME, privateKey: COINBASE_CDP_PRIVATE_KEY }); 38 | 39 | // Store 40 | import os from 'os'; 41 | import path from 'path'; 42 | const homeDir = os.homedir(); 43 | const documentsDir = path.join(homeDir, 'Documents'); 44 | const seedFilePath = path.join(documentsDir, "mpc_info.json"); 45 | 46 | // Create server instance 47 | const server = new Server( 48 | { 49 | name: "free-usdc-transfer", 50 | version: "0.1.3", 51 | }, 52 | { 53 | capabilities: { 54 | tools: {}, 55 | }, 56 | } 57 | ); 58 | 59 | // Define Zod schemas for validation 60 | const BstsArgumentsSchema = z.object({ 61 | usdc_amount: z.number().gt(0), 62 | recipient: z.string() 63 | }); 64 | 65 | // List available tools 66 | server.setRequestHandler(ListToolsRequestSchema, async () => { 67 | return { 68 | tools: [ 69 | { 70 | name: "tranfer-usdc", 71 | description: "Analyze the value of the purchased items and transfer USDC to the recipient via the Base chain. Due to the uncertainty of blockchain transaction times, the transaction is only scheduled here and will not wait for the transaction to be completed.", 72 | inputSchema: { 73 | type: "object", 74 | properties: { 75 | usdc_amount: { 76 | type: "number", 77 | description: "USDC amount, greater than 0", 78 | }, 79 | recipient: { 80 | type: "string", 81 | description: "Recipient's on-chain address or ENS addresses ending in .eth", 82 | } 83 | }, 84 | required: ["usdc_amount", "recipient"], 85 | }, 86 | }, 87 | { 88 | name: "create_coinbase_mpc_wallet", 89 | description: "Used to create your Coinbase MPC wallet address. The newly created wallet cannot be used directly; the user must first deposit USDC. The transfer after creation requires user confirmation", 90 | inputSchema: { 91 | type: "object" 92 | }, 93 | } 94 | ], 95 | }; 96 | }); 97 | 98 | // Create the client 99 | const client = createEnsPublicClient({ 100 | chain: mainnet, 101 | transport: http(), 102 | }) 103 | 104 | // ENS 105 | async function getAddress(recipient: string) { 106 | if (recipient.toLowerCase().endsWith('.eth')) { 107 | return (await client.getAddressRecord({ name: recipient }))?.value 108 | } 109 | if (!recipient || recipient.length != 42) { 110 | return undefined 111 | } 112 | return recipient; 113 | }; 114 | 115 | async function createMPCWallet() { 116 | let wallet = await Wallet.create({ networkId: "base-mainnet" }); 117 | wallet.saveSeedToFile(seedFilePath); 118 | return (await wallet.getDefaultAddress()).getId(); 119 | } 120 | 121 | async function sendUSDCUseMPCWallet(walletId: string, recipientAddr: string, amount: number) { 122 | const wallet = await Wallet.fetch(walletId) 123 | await wallet.loadSeedFromFile(seedFilePath) 124 | const defaultAddress = await wallet.getDefaultAddress() 125 | 126 | await defaultAddress.createTransfer({ 127 | amount: amount, 128 | assetId: Coinbase.assets.Usdc, 129 | destination: ethers.getAddress(recipientAddr), 130 | gasless: true 131 | }) 132 | 133 | return defaultAddress.getId() 134 | } 135 | 136 | async function queryMpcWallet() { 137 | try { 138 | const jsonString = await fs.readFile(seedFilePath, 'utf8') 139 | const ids = Object.keys(JSON.parse(jsonString)) 140 | if (!ids || ids.length === 0) { 141 | return { mpcAddress: "", mpcId: "" } 142 | } 143 | const wallet = await Wallet.fetch(ids[0]) 144 | await wallet.loadSeedFromFile(seedFilePath) 145 | return { mpcAddress: (await wallet.getDefaultAddress()).getId(), mpcId: ids[0] } 146 | } catch (err) { 147 | console.error(`${err}`) 148 | return { mpcAddress: "", mpcId: "" } 149 | } 150 | } 151 | 152 | async function queryMpcWalletId() { 153 | try { 154 | const jsonString = await fs.readFile(seedFilePath, 'utf8') 155 | const ids = Object.keys(JSON.parse(jsonString)) 156 | if (!ids || ids.length === 0) { 157 | return "" 158 | } 159 | return ids[0] 160 | } catch (err) { 161 | console.error(`${err}`) 162 | return "" 163 | } 164 | } 165 | 166 | // Handle tool execution 167 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 168 | const { name, arguments: args } = request.params; 169 | try { 170 | if (name === "tranfer-usdc") { 171 | const { usdc_amount, recipient } = BstsArgumentsSchema.parse(args); 172 | const mpcId= await queryMpcWalletId(); 173 | if (!mpcId) { 174 | return { 175 | content: [ 176 | { 177 | type: "text", 178 | text: "You haven't created a Coinbase MPC wallet yet", 179 | }, 180 | ], 181 | }; 182 | } 183 | const recipientAddr = await getAddress(recipient) 184 | if (!recipientAddr) { 185 | return { 186 | content: [ 187 | { 188 | type: "text", 189 | text: 'Invalid address or ENS', 190 | }, 191 | ] 192 | } 193 | } 194 | const addr= await sendUSDCUseMPCWallet(mpcId, recipientAddr, usdc_amount) 195 | const linkAddr = BASE_SCAN_ADDR + addr + "#tokentxns" 196 | return { 197 | content: [ 198 | { 199 | type: "text", 200 | text: `The transaction (sending ${usdc_amount} USDC to ${recipientAddr}) has been scheduled on the Base chain. You can view the details via the link: ${linkAddr}`, 201 | }, 202 | ], 203 | }; 204 | } else if (name === "create_coinbase_mpc_wallet") { 205 | const { mpcAddress } = await queryMpcWallet(); 206 | if (!mpcAddress) { 207 | const newMpcAddress = await createMPCWallet() 208 | return { 209 | content: [ 210 | { 211 | type: "text", 212 | text: `Your Coinbase MPC wallet address has been successfully created (${newMpcAddress}). Now please transfer USDC to MPC wallet, and you can later use it to transfer funds to others without fees.`, 213 | }, 214 | ], 215 | }; 216 | } 217 | return { 218 | content: [ 219 | { 220 | type: "text", 221 | text: `You already have an address, which is ${mpcAddress}`, 222 | }, 223 | ], 224 | }; 225 | } 226 | else { 227 | throw new Error(`Unknown tool: ${name}`); 228 | } 229 | } catch (error) { 230 | if (error instanceof z.ZodError) { 231 | throw new Error( 232 | `Invalid arguments: ${error.errors 233 | .map((e) => `${e.path.join(".")}: ${e.message}`) 234 | .join(", ")}` 235 | ); 236 | } 237 | throw error; 238 | } 239 | }); 240 | 241 | // Start the server 242 | async function main() { 243 | const transport = new StdioServerTransport(); 244 | await server.connect(transport); 245 | console.error("Free USDC transfer MCP Server running on stdio"); 246 | } 247 | 248 | main().catch((error) => { 249 | console.error("Fatal error in main():", error); 250 | process.exit(1); 251 | }); 252 | --------------------------------------------------------------------------------