# Directory Structure ``` ├── .gitignore ├── assets │ └── demo.gif ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | build/ 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Twilio MCP Server 2 | 3 | A Model Context Protocol (MCP) server that enables Claude and other AI assistants to send SMS and MMS messages using Twilio. 4 | 5 | ## Demo 6 | 7 |  8 | 9 | ## Features 10 | 11 | - Send SMS messages 📱 12 | - Pre-built prompts for common messaging scenarios 📝 13 | - Secure handling of Twilio credentials 🔒 14 | 15 | ## Requirements 16 | 17 | - Node.js >= 18 18 | - If you need to update Node.js, we recommend using `nvm` (Node Version Manager): 19 | ```bash 20 | nvm install 18.14.2 21 | nvm alias default 18.14.2 22 | ``` 23 | - If you encounter any errors in Claude Desktop, try running the following command in your terminal to verify the installation: 24 | ```bash 25 | npx -y @yiyang.1i/sms-mcp-server 26 | ``` 27 | 28 | ## Configuration 29 | 30 | The server requires three environment variables: 31 | 32 | - `ACCOUNT_SID`: Your Twilio account SID 33 | - `AUTH_TOKEN`: Your Twilio auth token 34 | - `FROM_NUMBER`: Your Twilio phone number (in E.164 format, e.g., +11234567890) 35 | 36 | ### Claude Desktop Configuration 37 | 38 | To use this server with Claude Desktop, add the following to your configuration file: 39 | 40 | **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` 41 | 42 | **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 43 | 44 | ```json 45 | { 46 | "mcpServers": { 47 | "twilio": { 48 | "command": "npx", 49 | "args": [ 50 | "-y", 51 | "@yiyang.1i/sms-mcp-server" 52 | ], 53 | "env": { 54 | "ACCOUNT_SID": "your_account_sid", 55 | "AUTH_TOKEN": "your_auth_token", 56 | "FROM_NUMBER": "your_twilio_number" 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | After that, restart Claude Desktop to reload the configuration. 63 | If connected, you should see Twilio under the 🔨 menu. 64 | 65 | ## Example Interactions with Claude 66 | 67 | Here are some natural ways to interact with the server through Claude: 68 | 69 | 1. Simple SMS: 70 | ``` 71 | Send a text message to the number +11234567890 saying "Don't forget about dinner tonight!" 72 | ``` 73 | 74 | 2. Creative SMS: 75 | ``` 76 | Write a haiku about autumn and send it to my number +11234567890 77 | ``` 78 | 79 | ## Important Notes 80 | 81 | 1. **Phone Number Format**: All phone numbers must be in E.164 format (e.g., +11234567890) 82 | 2. **Rate Limits**: Be aware of your Twilio account's rate limits and pricing 83 | 3. **Security**: Keep your Twilio credentials secure and never commit them to version control 84 | 85 | ## Troubleshooting 86 | 87 | Common error messages and solutions: 88 | 89 | 1. "Phone number must be in E.164 format" 90 | - Make sure the phone number starts with "+" and the country code 91 | 92 | 2. "Invalid credentials" 93 | - Double-check your ACCOUNT_SID and AUTH_TOKEN. You can copy them from the [Twilio Console](https://console.twilio.com) 94 | 95 | ## Contributing 96 | 97 | Contributions are welcome! Please read our contributing guidelines before submitting pull requests. 98 | 99 | ## License 100 | 101 | This project is licensed under the MIT License - see the LICENSE file for details. 102 | 103 | ## Security 104 | 105 | Please do not include any sensitive information (like phone numbers or Twilio credentials) in GitHub issues or pull requests. ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@yiyang.1i/sms-mcp-server", 3 | "version": "2025.2.24", 4 | "description": "A Model Context Protocol (MCP) server that enables Claude and other AI assistants to send SMS and MMS messages using Twilio.", 5 | "license": "MIT", 6 | "author": "Yiyang Li", 7 | "type": "module", 8 | "bin": { 9 | "sms-mcp-server": "build/index.js" 10 | }, 11 | "files": [ 12 | "build" 13 | ], 14 | "scripts": { 15 | "build": "tsc && shx chmod +x build/*.js", 16 | "prepare": "npm run build", 17 | "watch": "tsc --watch" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "1.5.0", 21 | "twilio": "^5.4.5", 22 | "zod": "^3.24.2" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^22", 26 | "shx": "^0.3.4", 27 | "typescript": "^5.3.3" 28 | } 29 | } 30 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 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 twilio from "twilio"; 6 | 7 | // Environment variables validation 8 | const requiredEnvVars = ["ACCOUNT_SID", "AUTH_TOKEN", "FROM_NUMBER"]; 9 | for (const envVar of requiredEnvVars) { 10 | if (!process.env[envVar]) { 11 | console.error(`Error: ${envVar} environment variable is required`); 12 | process.exit(1); 13 | } 14 | } 15 | 16 | // Initialize Twilio client 17 | const client = twilio(process.env.ACCOUNT_SID, process.env.AUTH_TOKEN); 18 | 19 | // Create MCP server 20 | const server = new McpServer({ 21 | name: "twilio-sms", 22 | version: "1.0.0", 23 | }); 24 | 25 | server.prompt( 26 | "send-greeting", 27 | { 28 | to: z.string().describe("Recipient's phone number in E.164 format (e.g., +11234567890)"), 29 | occasion: z.string().describe("The occasion for the greeting (e.g., birthday, holiday)") 30 | }, 31 | ({ to, occasion }) => ({ 32 | messages: [{ 33 | role: "user", 34 | content: { 35 | type: "text", 36 | text: `Please write a warm, personalized greeting for ${occasion} and send it as a text message to ${to}. Make it engaging and friendly.` 37 | } 38 | }] 39 | }) 40 | ); 41 | 42 | server.prompt( 43 | "send-haiku", 44 | { 45 | theme: z.string().describe("The theme of the haiku"), 46 | to: z.string().describe("Recipient's phone number in E.164 format (e.g., +11234567890)") 47 | }, 48 | ({ to, theme }) => ({ 49 | messages: [{ 50 | role: "user", 51 | content: { 52 | type: "text", 53 | text: `Please write a warm, personalized greeting for ${theme} and send it as a text message to ${to}. Make it engaging and friendly.` 54 | } 55 | }] 56 | }) 57 | ); 58 | 59 | 60 | // Add send message tool 61 | server.tool( 62 | "send-message", 63 | "Send an SMS message via Twilio", 64 | { 65 | to: z.string().describe("Recipient phone number in E.164 format (e.g., +11234567890)"), 66 | message: z.string().describe("Message content to send") 67 | }, 68 | async ({ to, message }) => { 69 | try { 70 | // Validate phone number format 71 | if (!to.startsWith("+")) { 72 | return { 73 | content: [{ 74 | type: "text", 75 | text: "Error: Phone number must be in E.164 format (e.g., +11234567890)" 76 | }], 77 | isError: true 78 | }; 79 | } 80 | 81 | // Send message via Twilio 82 | const response = await client.messages.create({ 83 | body: message, 84 | from: process.env.FROM_NUMBER, 85 | to: to 86 | }); 87 | 88 | return { 89 | content: [{ 90 | type: "text", 91 | text: `Message sent successfully! Message SID: ${response.sid}` 92 | }] 93 | }; 94 | } catch (error) { 95 | console.error("Error sending message:", error); 96 | return { 97 | content: [{ 98 | type: "text", 99 | text: `Error sending message: ${error instanceof Error ? error.message : "Unknown error"}` 100 | }], 101 | isError: true 102 | }; 103 | } 104 | } 105 | ); 106 | 107 | // Start server 108 | async function main() { 109 | const transport = new StdioServerTransport(); 110 | await server.connect(transport); 111 | console.error("Twilio SMS MCP Server running on stdio"); 112 | } 113 | 114 | main().catch((error) => { 115 | console.error("Fatal error in main():", error); 116 | process.exit(1); 117 | }); ```