diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 807f866eb..e8d6cf73f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,7 @@ agentkit/ │ ├── langchain-privy-chatbot/ │ ├── langchain-solana-chatbot/ │ ├── langchain-twitter-chatbot/ +│ ├── model-context-protocol-cdp-server/ │ └── vercel-ai-sdk-cdp-chatbot/ ├── python/ │ ├── coinbase-agentkit/ diff --git a/README.md b/README.md index e205d5ba2..87ab62246 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ agentkit/ │ ├── langchain-privy-chatbot/ │ ├── langchain-solana-chatbot/ │ ├── langchain-twitter-chatbot/ +│ ├── model-context-protocol-cdp-server/ │ └── vercel-ai-sdk-cdp-chatbot/ ├── python/ │ ├── coinbase-agentkit/ diff --git a/typescript/examples/model-context-protocol-cdp-server/.eslintrc.json b/typescript/examples/model-context-protocol-cdp-server/.eslintrc.json new file mode 100644 index 000000000..91571ba7a --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": ["../../.eslintrc.base.json"] +} diff --git a/typescript/examples/model-context-protocol-cdp-server/.prettierignore b/typescript/examples/model-context-protocol-cdp-server/.prettierignore new file mode 100644 index 000000000..ed251d161 --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/.prettierignore @@ -0,0 +1,8 @@ +docs/ +dist/ +build/ +coverage/ +.github/ +src/client +**/**/*.json +*.md diff --git a/typescript/examples/model-context-protocol-cdp-server/.prettierrc b/typescript/examples/model-context-protocol-cdp-server/.prettierrc new file mode 100644 index 000000000..ffb416b74 --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid", + "printWidth": 100, + "proseWrap": "never" +} diff --git a/typescript/examples/model-context-protocol-cdp-server/README.md b/typescript/examples/model-context-protocol-cdp-server/README.md new file mode 100644 index 000000000..7fe7cb5cb --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/README.md @@ -0,0 +1,59 @@ +# CDP AgentKit Model Context Protocol Examples - Chatbot Typescript + +This example demonstrates an agent setup as a server for Model Context Protocol, allowing Claude Desktop access to the full set of CDP AgentKit actions. + +## Ask the chatbot to engage in the Web3 ecosystem! + +- "Transfer a portion of your ETH to a random address" +- "What is the price of BTC?" +- "Deploy an NFT that will go super viral!" +- "Deploy an ERC-20 token with total supply 1 billion" + +## Prerequisites + +### Checking Node Version + +Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running: + +```bash +node --version +``` + +If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm): + +```bash +nvm install node +``` + +This will automatically install and use the latest version of Node. + +### API Keys + +You'll need the following API key: +- [CDP API Key](https://portal.cdp.coinbase.com/access/api) + +You'll need to configure the Claude desktop config file with your CDP API keys. Copy the contents from `claude_desktop_config.json` to your Claude desktop config file and update the following: + +1. Update the `args` path to match the location of your built index.js file +2. Set your CDP API keys in the `env` section: + - "CDP_API_KEY_NAME" + - "CDP_API_KEY_PRIVATE_KEY" + +Then, navigate to the `claude_desktop_config.json` file found in your Claude Desktop apps' settings and update it's contents to the contents of our `claude_desktop_config.json` file. + +## Running the example + +From the root directory, run: + +```bash +npm install +npm run build +``` + +This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-model-context-protocol` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `npm run build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example. + +To use the chatbot, simply open Claude desktop after configuring your API keys. The MCP server will run automatically when you interact with Claude. + +## License + +Apache-2.0 diff --git a/typescript/examples/model-context-protocol-cdp-server/claude_desktop_config.json b/typescript/examples/model-context-protocol-cdp-server/claude_desktop_config.json new file mode 100644 index 000000000..366b1bfae --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/claude_desktop_config.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "agentkit": { + "command": "node", + "args": [ + "/Users/YOUR_USER/YOUR_DIRECTORIES/agentkit/typescript/examples/model-context-protocol-cdp-server/dist/index.js" + ], + "env": { + "CDP_API_KEY_NAME": "YOUR_CDP_API_KEY_NAME", + "CDP_API_KEY_PRIVATE_KEY": "YOUR_CDP_API_KEY_PRIVATE_KEY" + } + } + } +} diff --git a/typescript/examples/model-context-protocol-cdp-server/index.ts b/typescript/examples/model-context-protocol-cdp-server/index.ts new file mode 100644 index 000000000..236393bf7 --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/index.ts @@ -0,0 +1,136 @@ +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { getMcpTools } from "@coinbase/agentkit-model-context-protocol"; +import { + AgentKit, + CdpWalletProvider, + wethActionProvider, + walletActionProvider, + erc20ActionProvider, + erc721ActionProvider, + cdpApiActionProvider, + cdpWalletActionProvider, + pythActionProvider, +} from "@coinbase/agentkit"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; + +/** + * Validates that required environment variables are set + */ +function validateEnvironment(): void { + const requiredVars = ["CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"]; + const missingVars = requiredVars.filter(varName => !process.env[varName]); + + if (missingVars.length > 0) { + console.error("Error: Required environment variables are not set:"); + missingVars.forEach(varName => { + console.error(`${varName}=your_${varName.toLowerCase()}_here`); + }); + process.exit(1); + } + + if (!process.env.NETWORK_ID) { + console.warn("Warning: NETWORK_ID not set, defaulting to base-sepolia testnet"); + } +} + +/** + * This function creates a new server instance with the capabilities to handle MCP requests. + * It configures the CDP Wallet Provider with the provided API keys and network ID. + * It then initializes the AgentKit with the configured wallet provider and action providers. + * + * @returns {Promise} The initialized MCP server + */ +async function initializeServer() { + try { + // Create server instance with capabilities + const server = new Server( + { + name: "cdp-agentkit", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + }, + ); + + // Configure CDP Wallet Provider + const config = { + apiKeyName: process.env.CDP_API_KEY_NAME!, + apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY!, + networkId: process.env.NETWORK_ID || "base-sepolia", + }; + + const walletProvider = await CdpWalletProvider.configureWithWallet(config); + + // Initialize AgentKit + const agentkit = await AgentKit.from({ + walletProvider, + actionProviders: [ + wethActionProvider(), + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + erc721ActionProvider(), + cdpApiActionProvider({ + apiKeyName: config.apiKeyName, + apiKeyPrivateKey: config.apiKeyPrivateKey, + }), + cdpWalletActionProvider({ + apiKeyName: config.apiKeyName, + apiKeyPrivateKey: config.apiKeyPrivateKey, + }), + ], + }); + + // Get MCP tools from AgentKit + const { tools, toolHandler } = await getMcpTools(agentkit); + + // Set up request handlers + server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools, + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async request => { + try { + return await toolHandler(request.params.name, request.params.arguments); + } catch (error) { + console.error(`Error executing tool ${request.params.name}:`, error); + throw new Error(`Tool ${request.params.name} failed: ${error}`); + } + }); + + return server; + } catch (error) { + console.error("Failed to initialize server:", error); + throw error; + } +} + +/** + * Main function to run the MCP server + */ +async function main() { + validateEnvironment(); + + try { + const server = await initializeServer(); + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("CDP AgentKit MCP Server running on stdio"); + } catch (error) { + console.error("Fatal error:", error); + process.exit(1); + } +} + +if (require.main === module) { + main().catch(error => { + console.error("Fatal error in main():", error); + process.exit(1); + }); +} diff --git a/typescript/examples/model-context-protocol-cdp-server/package.json b/typescript/examples/model-context-protocol-cdp-server/package.json new file mode 100644 index 000000000..dc3e9e50d --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/package.json @@ -0,0 +1,28 @@ +{ + "name": "@coinbase/cdp-model-context-protocol-server", + "description": "Coinbase AgentKit Example Model Context Protocol Server", + "version": "1.0.0", + "private": true, + "author": "Coinbase Inc.", + "license": "Apache-2.0", + "bin": { + "agentkit": "./dist/index.js" + }, + "scripts": { + "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"", + "lint": "eslint -c .eslintrc.json *.ts", + "lint:fix": "eslint -c .eslintrc.json *.ts --fix", + "format": "prettier --write \"**/*.{ts,js,cjs,json,md}\"", + "format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"" + }, + "dependencies": { + "@coinbase/agentkit": "^0.2.3", + "@coinbase/agentkit-model-context-protocol": "^0.1.0", + "@modelcontextprotocol/sdk": "^1.6.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "nodemon": "^3.1.0", + "ts-node": "^10.9.2" + } +} diff --git a/typescript/examples/model-context-protocol-cdp-server/tsconfig.json b/typescript/examples/model-context-protocol-cdp-server/tsconfig.json new file mode 100644 index 000000000..a37da3664 --- /dev/null +++ b/typescript/examples/model-context-protocol-cdp-server/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "preserveSymlinks": true, + "outDir": "./dist", + "rootDir": ".", + "module": "Node16" + }, + "include": ["*.ts"] +} diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 23aa3700a..cfd40db82 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -19,7 +19,8 @@ "examples/langchain-privy-chatbot", "examples/langchain-solana-chatbot", "examples/langchain-smart-wallet-chatbot", - "examples/vercel-ai-sdk-cdp-chatbot" + "examples/vercel-ai-sdk-cdp-chatbot", + "examples/model-context-protocol-cdp-server" ], "devDependencies": { "@changesets/changelog-github": "^0.5.1", @@ -467,6 +468,71 @@ "ts-node": "^10.9.2" } }, + "examples/model-context-protocol-cdp-chatbot": { + "name": "@coinbase/cdp-model-context-protocol-chatbot-example", + "version": "1.0.0", + "extraneous": true, + "license": "Apache-2.0", + "dependencies": { + "@coinbase/agentkit": "^0.2.3", + "@coinbase/agentkit-model-context-protocol": "^0.1.0", + "@modelcontextprotocol/sdk": "^1.6.1", + "zod": "^3.22.4" + }, + "bin": { + "agentkit": "dist/index.js" + }, + "devDependencies": { + "nodemon": "^3.1.0", + "ts-node": "^10.9.2" + } + }, + "examples/model-context-protocol-cdp-server": { + "name": "@coinbase/cdp-model-context-protocol-server", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@coinbase/agentkit": "^0.2.3", + "@coinbase/agentkit-model-context-protocol": "^0.1.0", + "@modelcontextprotocol/sdk": "^1.6.1", + "zod": "^3.22.4" + }, + "bin": { + "agentkit": "dist/index.js" + }, + "devDependencies": { + "nodemon": "^3.1.0", + "ts-node": "^10.9.2" + } + }, + "examples/model-context-protocol-cdp-server/node_modules/@coinbase/agentkit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@coinbase/agentkit/-/agentkit-0.2.3.tgz", + "integrity": "sha512-BnmkQgvoz73RgXNBiqNOfk6/dio1of3oJftzMpZNBfJYHnnXWEXEkXrXefVRjVfEjWVb3cKDSHZ0mAyFQjrqoA==", + "dependencies": { + "@coinbase/coinbase-sdk": "^0.20.0", + "@jup-ag/api": "^6.0.39", + "@privy-io/server-auth": "^1.18.4", + "@solana/spl-token": "^0.4.12", + "@solana/web3.js": "^1.98.0", + "md5": "^2.3.0", + "reflect-metadata": "^0.2.2", + "twitter-api-v2": "^1.18.2", + "viem": "^2.22.16", + "zod": "^3.23.8" + } + }, + "examples/model-context-protocol-cdp-server/node_modules/@coinbase/agentkit-model-context-protocol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@coinbase/agentkit-model-context-protocol/-/agentkit-model-context-protocol-0.1.0.tgz", + "integrity": "sha512-Bx7zThE3moeIGqc6fwp1Y3Oph1IFG36clvV0ohxvAbUfIyJj7knT7puTFeuodSOLvljhrLnwQ3TVTXQNY0HBcA==", + "dependencies": { + "@coinbase/agentkit": "^0.2.3", + "@modelcontextprotocol/sdk": "^1.6.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.24.3" + } + }, "examples/vercel-ai-sdk-cdp-chatbot": { "name": "@coinbase/cdp-vercel-ai-sdk-chatbot-example", "version": "1.0.0", @@ -1595,6 +1661,10 @@ "resolved": "examples/langchain-cdp-chatbot", "link": true }, + "node_modules/@coinbase/cdp-model-context-protocol-server": { + "resolved": "examples/model-context-protocol-cdp-server", + "link": true + }, "node_modules/@coinbase/cdp-vercel-ai-sdk-chatbot-example": { "resolved": "examples/vercel-ai-sdk-cdp-chatbot", "link": true @@ -20246,6 +20316,47 @@ "zod": "^3.22.4" } }, + "@coinbase/cdp-model-context-protocol-server": { + "version": "file:examples/model-context-protocol-cdp-server", + "requires": { + "@coinbase/agentkit": "^0.2.3", + "@coinbase/agentkit-model-context-protocol": "^0.1.0", + "@modelcontextprotocol/sdk": "^1.6.1", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "zod": "^3.22.4" + }, + "dependencies": { + "@coinbase/agentkit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@coinbase/agentkit/-/agentkit-0.2.3.tgz", + "integrity": "sha512-BnmkQgvoz73RgXNBiqNOfk6/dio1of3oJftzMpZNBfJYHnnXWEXEkXrXefVRjVfEjWVb3cKDSHZ0mAyFQjrqoA==", + "requires": { + "@coinbase/coinbase-sdk": "^0.20.0", + "@jup-ag/api": "^6.0.39", + "@privy-io/server-auth": "^1.18.4", + "@solana/spl-token": "^0.4.12", + "@solana/web3.js": "^1.98.0", + "md5": "^2.3.0", + "reflect-metadata": "^0.2.2", + "twitter-api-v2": "^1.18.2", + "viem": "^2.22.16", + "zod": "^3.23.8" + } + }, + "@coinbase/agentkit-model-context-protocol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@coinbase/agentkit-model-context-protocol/-/agentkit-model-context-protocol-0.1.0.tgz", + "integrity": "sha512-Bx7zThE3moeIGqc6fwp1Y3Oph1IFG36clvV0ohxvAbUfIyJj7knT7puTFeuodSOLvljhrLnwQ3TVTXQNY0HBcA==", + "requires": { + "@coinbase/agentkit": "^0.2.3", + "@modelcontextprotocol/sdk": "^1.6.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.24.3" + } + } + } + }, "@coinbase/cdp-vercel-ai-sdk-chatbot-example": { "version": "file:examples/vercel-ai-sdk-cdp-chatbot", "requires": { diff --git a/typescript/package.json b/typescript/package.json index e79cd84be..917658a1d 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -14,7 +14,8 @@ "examples/langchain-privy-chatbot", "examples/langchain-solana-chatbot", "examples/langchain-smart-wallet-chatbot", - "examples/vercel-ai-sdk-cdp-chatbot" + "examples/vercel-ai-sdk-cdp-chatbot", + "examples/model-context-protocol-cdp-server" ], "packageManager": "npm@8.9.0", "scripts": {