#
tokens: 2532/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── assets
│   └── demo.gif
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
build/

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Twilio MCP Server

A Model Context Protocol (MCP) server that enables Claude and other AI assistants to send SMS and MMS messages using Twilio.

## Demo

![Demo](./assets/demo.gif)

## Features

- Send SMS messages 📱
- Pre-built prompts for common messaging scenarios 📝
- Secure handling of Twilio credentials 🔒

## Requirements

- Node.js >= 18
  - If you need to update Node.js, we recommend using `nvm` (Node Version Manager):
    ```bash
    nvm install 18.14.2
    nvm alias default 18.14.2
    ```
  - If you encounter any errors in Claude Desktop, try running the following command in your terminal to verify the installation:
    ```bash
    npx -y @yiyang.1i/sms-mcp-server
    ```

## Configuration

The server requires three environment variables:

- `ACCOUNT_SID`: Your Twilio account SID
- `AUTH_TOKEN`: Your Twilio auth token
- `FROM_NUMBER`: Your Twilio phone number (in E.164 format, e.g., +11234567890)

### Claude Desktop Configuration

To use this server with Claude Desktop, add the following to your configuration file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`

**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "twilio": {
      "command": "npx",
      "args": [
        "-y",
        "@yiyang.1i/sms-mcp-server"
      ],
      "env": {
        "ACCOUNT_SID": "your_account_sid",
        "AUTH_TOKEN": "your_auth_token",
        "FROM_NUMBER": "your_twilio_number"
      }
    }
  }
}
```
After that, restart Claude Desktop to reload the configuration. 
If connected, you should see Twilio under the 🔨 menu.

## Example Interactions with Claude

Here are some natural ways to interact with the server through Claude:

1. Simple SMS:
```
Send a text message to the number +11234567890 saying "Don't forget about dinner tonight!"
```

2. Creative SMS:
```
Write a haiku about autumn and send it to my number +11234567890
```

## Important Notes

1. **Phone Number Format**: All phone numbers must be in E.164 format (e.g., +11234567890)
2. **Rate Limits**: Be aware of your Twilio account's rate limits and pricing
3. **Security**: Keep your Twilio credentials secure and never commit them to version control

## Troubleshooting

Common error messages and solutions:

1. "Phone number must be in E.164 format"
   - Make sure the phone number starts with "+" and the country code

2. "Invalid credentials"
   - Double-check your ACCOUNT_SID and AUTH_TOKEN. You can copy them from the [Twilio Console](https://console.twilio.com)

## Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Security

Please do not include any sensitive information (like phone numbers or Twilio credentials) in GitHub issues or pull requests.
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@yiyang.1i/sms-mcp-server",
  "version": "2025.2.24",
  "description": "A Model Context Protocol (MCP) server that enables Claude and other AI assistants to send SMS and MMS messages using Twilio.",
  "license": "MIT",
  "author": "Yiyang Li",
  "type": "module",
  "bin": {
    "sms-mcp-server": "build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x build/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.5.0",
    "twilio": "^5.4.5",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^22",
    "shx": "^0.3.4",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import twilio from "twilio";

// Environment variables validation
const requiredEnvVars = ["ACCOUNT_SID", "AUTH_TOKEN", "FROM_NUMBER"];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    console.error(`Error: ${envVar} environment variable is required`);
    process.exit(1);
  }
}

// Initialize Twilio client
const client = twilio(process.env.ACCOUNT_SID, process.env.AUTH_TOKEN);

// Create MCP server
const server = new McpServer({
    name: "twilio-sms",
    version: "1.0.0",
});

server.prompt(
  "send-greeting",
  {
    to: z.string().describe("Recipient's phone number in E.164 format (e.g., +11234567890)"),
    occasion: z.string().describe("The occasion for the greeting (e.g., birthday, holiday)")
  },
  ({ to, occasion }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please write a warm, personalized greeting for ${occasion} and send it as a text message to ${to}. Make it engaging and friendly.`
      }
    }]
  })
);

server.prompt(
  "send-haiku",
  {
    theme: z.string().describe("The theme of the haiku"),
    to: z.string().describe("Recipient's phone number in E.164 format (e.g., +11234567890)")
  },
  ({ to, theme }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please write a warm, personalized greeting for ${theme} and send it as a text message to ${to}. Make it engaging and friendly.`
      }
    }]
  })
);


// Add send message tool
server.tool(
  "send-message",
  "Send an SMS message via Twilio",
  {
    to: z.string().describe("Recipient phone number in E.164 format (e.g., +11234567890)"),
    message: z.string().describe("Message content to send")
  },
  async ({ to, message }) => {
    try {
      // Validate phone number format
      if (!to.startsWith("+")) {
        return {
          content: [{
            type: "text",
            text: "Error: Phone number must be in E.164 format (e.g., +11234567890)"
          }],
          isError: true
        };
      }

      // Send message via Twilio
      const response = await client.messages.create({
        body: message,
        from: process.env.FROM_NUMBER,
        to: to
      });

      return {
        content: [{
          type: "text",
          text: `Message sent successfully! Message SID: ${response.sid}`
        }]
      };
    } catch (error) {
      console.error("Error sending message:", error);
      return {
        content: [{
          type: "text",
          text: `Error sending message: ${error instanceof Error ? error.message : "Unknown error"}`
        }],
        isError: true
      };
    }
  }
);

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Twilio SMS MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});
```