From 6cb57c9dc6e032883d858b223dc7549db14bace2 Mon Sep 17 00:00:00 2001 From: Vishal Jain Date: Fri, 13 Jun 2025 09:53:43 +0100 Subject: [PATCH] Fix Twilio MCP integration with baked-in credentials - Add proper type and env sections to mcp-config.json - Remove dynamic MCP add command, use pre-configured MCP - Bake .env credentials into Docker image at build time - Remove runtime .env volume mount - true one-time setup - Auto-rebuild image when .env file changes - Export Twilio env vars for MCP server subprocess - Remove conflicting .mcp.json file --- .env.example | 20 +++++------ .gitignore | 4 ++- Dockerfile | 11 ++++-- README.md | 73 ++++++++++++++++++++++++++++++++++++---- config/mcp-config.json | 10 +++++- scripts/claude-docker.sh | 42 +++++++++++++++++------ scripts/setup-env.sh | 51 ++++++++++++++++++++++++++++ scripts/startup.sh | 26 +++++++++++--- test-twilio.js | 60 +++++++++++++++++++++++++++++++++ 9 files changed, 262 insertions(+), 35 deletions(-) create mode 100644 scripts/setup-env.sh create mode 100755 test-twilio.js diff --git a/.env.example b/.env.example index 5ae32b7..86784df 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,12 @@ -# ABOUTME: Environment variables for Claude Docker -# ABOUTME: Copy this to ~/.claude-docker/.env and fill in your values +# Copy this file to .env and fill in your credentials +# The .env file will be baked into the Docker image during build -# Optional: Anthropic API key (only needed if not using subscription auth) -# ANTHROPIC_API_KEY=sk-ant-your_anthropic_api_key_here +# Required for Claude Code +ANTHROPIC_API_KEY=your_anthropic_api_key_here -# Optional: Twilio credentials for SMS notifications via MCP -# TWILIO_ACCOUNT_SID=your_twilio_sid_here -# TWILIO_API_KEY=your_twilio_api_key_here -# TWILIO_API_SECRET=your_twilio_api_secret_here -# TWILIO_FROM_NUMBER=+1234567890 # Your Twilio phone number -# TWILIO_TO_NUMBER=+1234567890 # Your personal phone to receive SMS \ No newline at end of file +# Optional: Twilio credentials for SMS notifications +TWILIO_ACCOUNT_SID=your_twilio_account_sid +TWILIO_API_KEY=your_twilio_api_key +TWILIO_API_SECRET=your_twilio_api_secret +TWILIO_FROM_NUMBER=+1234567890 +TWILIO_TO_NUMBER=+0987654321 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a8795d..9531807 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ data/ # Temporary files *.tmp *.temp -~* \ No newline at end of file +~* +# Environment file with credentials +.env diff --git a/Dockerfile b/Dockerfile index 96fa5de..75f5242 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,11 @@ RUN apt-get update && apt-get install -y \ sudo \ && rm -rf /var/lib/apt/lists/* -# Create a non-root user -RUN useradd -m -s /bin/bash claude-user && \ +# Create a non-root user with matching host UID/GID +ARG USER_UID=1000 +ARG USER_GID=1000 +RUN groupadd -g $USER_GID claude-user && \ + useradd -m -s /bin/bash -u $USER_UID -g $USER_GID claude-user && \ echo "claude-user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers # Create app directory @@ -38,6 +41,10 @@ COPY config/mcp-config.json /app/config/ COPY scripts/startup.sh /app/ RUN chmod +x /app/startup.sh +# Copy .env file during build to bake credentials into the image +# This enables one-time setup - no need for .env in project directories +COPY .env /app/.env + # Set proper ownership RUN chown -R claude-user:claude-user /app /home/claude-user diff --git a/README.md b/README.md index ddeb120..a4b8d5f 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,26 @@ A Docker container setup for running Claude Code with full autonomous permission ## Quick Start -1. **Clone and install:** +1. **Clone the repository:** ```bash git clone https://github.com/VishalJ99/claude-docker.git cd claude-docker - ./scripts/install.sh ``` 2. **Configure your API keys:** ```bash - # Edit ~/.claude-docker/.env with your keys + # Copy the example file + cp .env.example .env + + # Edit .env with your credentials + nano .env + ``` + + Add your credentials to the `.env` file: + ```bash ANTHROPIC_API_KEY=your_anthropic_key - # For Twilio MCP integration: + # For Twilio MCP integration (optional): TWILIO_ACCOUNT_SID=your_twilio_sid TWILIO_API_KEY=your_twilio_api_key TWILIO_API_SECRET=your_twilio_api_secret @@ -32,13 +39,60 @@ A Docker container setup for running Claude Code with full autonomous permission TWILIO_TO_NUMBER=your_phone_number ``` + > **Important**: The `.env` file will be baked into the Docker image during build. This means: + > - Your credentials are embedded in the image + > - You can use the image from any directory without needing the .env file + > - Keep your image secure since it contains your credentials + > **Note**: Twilio MCP requires API Key/Secret instead of Auth Token. Create API keys in your Twilio Console under Account → API keys & tokens. -3. **Use from any project directory:** +3. **Build and install:** + ```bash + ./scripts/install.sh + ``` + + This will: + - Build the Docker image with your credentials baked in + - Install the `claude-docker` command to your PATH + +4. **Use from any project directory:** ```bash claude-docker ``` +## Usage Patterns + +### One-Time Setup Per Project +For the best experience, run `claude-docker` once per project and leave it running: + +1. **Start Claude Docker:** + ```bash + cd your-project + claude-docker + ``` + +2. **Detach from the session (keep it running):** + - **Mac/Linux**: `Ctrl + P`, then `Ctrl + Q` + - Hold Control key, press P, then Q while still holding Control + - Container keeps running in background + +3. **Reattach when needed:** + ```bash + docker ps # Find your container ID + docker attach claude-docker-session # Reattach to the session + ``` + +4. **Stop when done with project:** + ```bash + docker stop claude-docker-session + ``` + +This workflow gives you: +- ✅ Persistent authentication (login once per machine) +- ✅ Persistent project context (one session per project) +- ✅ Perfect file permissions between host and container +- ✅ No repeated setup or authentication + ## Features ### 🤖 Full Autonomy @@ -60,6 +114,7 @@ A Docker container setup for running Claude Code with full autonomous permission - Login once, use forever - authentication tokens persist across sessions - No need to re-authenticate every time you start claude-docker - Credentials stored securely in `~/.claude-docker/claude-home` +- Automatic UID/GID mapping ensures perfect file permissions between host and container ### 🐳 Clean Environment - Each session runs in fresh Docker container @@ -91,8 +146,12 @@ claude-docker/ ## Configuration -The setup creates `~/.claude-docker/` with: -- `.env` - API keys and configuration +During build, the `.env` file from the claude-docker directory is baked into the image: +- Credentials are embedded at `/app/.env` inside the container +- No need to manage .env files in each project +- The image contains everything needed to run + +The setup creates `~/.claude-docker/` in your home directory with: - `claude-home/` - Persistent Claude authentication and settings - `config/` - MCP server configuration diff --git a/config/mcp-config.json b/config/mcp-config.json index 745d860..568e180 100644 --- a/config/mcp-config.json +++ b/config/mcp-config.json @@ -1,6 +1,7 @@ { "mcpServers": { "twilio": { + "type": "stdio", "command": "npx", "args": [ "-y", @@ -10,7 +11,14 @@ "twilio_api_v2010", "--tags", "Api20100401Message" - ] + ], + "env": { + "TWILIO_ACCOUNT_SID": "${TWILIO_ACCOUNT_SID}", + "TWILIO_API_KEY": "${TWILIO_API_KEY}", + "TWILIO_API_SECRET": "${TWILIO_API_SECRET}", + "TWILIO_FROM_NUMBER": "${TWILIO_FROM_NUMBER}", + "TWILIO_TO_NUMBER": "${TWILIO_TO_NUMBER}" + } } } } \ No newline at end of file diff --git a/scripts/claude-docker.sh b/scripts/claude-docker.sh index dc2eeec..833be03 100755 --- a/scripts/claude-docker.sh +++ b/scripts/claude-docker.sh @@ -24,18 +24,42 @@ if [ ! -d "$CURRENT_DIR/.claude" ]; then echo "✓ Claude configuration created" fi -# Check if .env file exists in user's home claude-docker directory -ENV_FILE="$HOME/.claude-docker/.env" -if [ ! -f "$ENV_FILE" ]; then +# Check if .env exists in claude-docker directory for building +ENV_FILE="$PROJECT_ROOT/.env" +if [ -f "$ENV_FILE" ]; then + echo "✓ Found .env file with credentials" +else echo "⚠️ No .env file found at $ENV_FILE" - echo "Please create it with your API keys. See .env.example for reference." - exit 1 + echo " Twilio MCP features will be unavailable." + echo " To enable: create .env in claude-docker directory with your credentials" fi -# Build the Docker image if it doesn't exist +# Check if we need to rebuild the image +NEED_REBUILD=false + if ! docker images | grep -q "claude-docker"; then - echo "Building Claude Docker image..." - docker build -t claude-docker:latest "$PROJECT_ROOT" + echo "Building Claude Docker image with your user permissions..." + NEED_REBUILD=true +elif ! docker image inspect claude-docker:latest | grep -q "USER_UID.*$(id -u)" 2>/dev/null; then + echo "Rebuilding Claude Docker image to match your user permissions..." + NEED_REBUILD=true +elif [ -f "$ENV_FILE" ]; then + # Check if .env is newer than the Docker image + IMAGE_CREATED=$(docker inspect -f '{{.Created}}' claude-docker:latest 2>/dev/null) + if [ -n "$IMAGE_CREATED" ]; then + IMAGE_TIMESTAMP=$(date -d "$IMAGE_CREATED" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%S" "${IMAGE_CREATED%%.*}" +%s 2>/dev/null) + ENV_TIMESTAMP=$(stat -c %Y "$ENV_FILE" 2>/dev/null || stat -f %m "$ENV_FILE" 2>/dev/null) + + if [ -n "$IMAGE_TIMESTAMP" ] && [ -n "$ENV_TIMESTAMP" ] && [ "$ENV_TIMESTAMP" -gt "$IMAGE_TIMESTAMP" ]; then + echo "⚠️ .env file has been updated since last build" + echo " Rebuilding to include new credentials..." + NEED_REBUILD=true + fi + fi +fi + +if [ "$NEED_REBUILD" = true ]; then + docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t claude-docker:latest "$PROJECT_ROOT" fi # Ensure the claude-home directory exists @@ -45,8 +69,6 @@ mkdir -p "$HOME/.claude-docker/claude-home" echo "Starting Claude Code in Docker..." docker run -it --rm \ -v "$CURRENT_DIR:/workspace" \ - -v "$ENV_FILE:/app/.env:ro" \ - -v "$HOME/.claude-docker/config:/app/.claude:rw" \ -v "$HOME/.claude-docker/claude-home:/home/claude-user/.claude:rw" \ --workdir /workspace \ --name claude-docker-session \ diff --git a/scripts/setup-env.sh b/scripts/setup-env.sh new file mode 100644 index 0000000..b935466 --- /dev/null +++ b/scripts/setup-env.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# ABOUTME: Setup script to create ~/.claude-docker/.env with Twilio credentials +# ABOUTME: Run this once on your host machine to configure Twilio for all projects + +CLAUDE_DOCKER_DIR="$HOME/.claude-docker" +ENV_FILE="$CLAUDE_DOCKER_DIR/.env" + +# Create directory if it doesn't exist +mkdir -p "$CLAUDE_DOCKER_DIR" + +# Check if .env already exists +if [ -f "$ENV_FILE" ]; then + echo "⚠️ $ENV_FILE already exists!" + read -p "Do you want to update it? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 0 + fi +fi + +echo "Setting up Twilio credentials for Claude Docker..." +echo "You'll need your Twilio account information." +echo + +# Collect Twilio credentials +read -p "Enter your Twilio Account SID: " TWILIO_ACCOUNT_SID +read -p "Enter your Twilio API Key: " TWILIO_API_KEY +read -sp "Enter your Twilio API Secret: " TWILIO_API_SECRET +echo +read -p "Enter your Twilio phone number (with country code, e.g., +1234567890): " TWILIO_FROM_NUMBER +read -p "Enter the phone number to receive SMS (with country code): " TWILIO_TO_NUMBER + +# Create .env file +cat > "$ENV_FILE" << EOF +# Twilio credentials for Claude Docker +TWILIO_ACCOUNT_SID=$TWILIO_ACCOUNT_SID +TWILIO_API_KEY=$TWILIO_API_KEY +TWILIO_API_SECRET=$TWILIO_API_SECRET +TWILIO_FROM_NUMBER=$TWILIO_FROM_NUMBER +TWILIO_TO_NUMBER=$TWILIO_TO_NUMBER +EOF + +# Set restrictive permissions +chmod 600 "$ENV_FILE" + +echo +echo "✅ Twilio credentials saved to $ENV_FILE" +echo "These credentials will be available to all Claude Docker sessions." +echo +echo "To test, run claude-docker from any project directory and use:" +echo " node /workspace/test-twilio.js" \ No newline at end of file diff --git a/scripts/startup.sh b/scripts/startup.sh index 610bbf2..5d4edf1 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -3,10 +3,22 @@ # ABOUTME: Configures environment and starts Claude Code with Twilio MCP integration # Load environment variables from .env if it exists +# Use the .env file baked into the image at build time if [ -f /app/.env ]; then + echo "Loading credentials from baked-in .env file" set -a source /app/.env 2>/dev/null || true set +a + + # Export Twilio variables for MCP server + export TWILIO_ACCOUNT_SID + export TWILIO_API_KEY + export TWILIO_API_SECRET + export TWILIO_FROM_NUMBER + export TWILIO_TO_NUMBER +else + echo "WARNING: No .env file found in image. Twilio features will be unavailable." + echo "To enable Twilio: create .env in claude-docker directory before building the image." fi # Configure Claude Code to use the MCP server @@ -20,11 +32,17 @@ else echo "Your login will be saved for future sessions" fi -# Start Claude Code with permissions bypass -echo "Starting Claude Code..." -if [ -n "$TWILIO_ACCOUNT_SID" ] && [ -n "$TWILIO_API_KEY" ]; then - echo "Twilio MCP integration enabled" +# Verify Twilio MCP configuration +if [ -n "$TWILIO_ACCOUNT_SID" ] && [ -n "$TWILIO_API_KEY" ] && [ -n "$TWILIO_API_SECRET" ]; then + echo "Twilio MCP pre-configured with:" + echo " - Account SID: ${TWILIO_ACCOUNT_SID:0:10}..." + echo " - From Number: $TWILIO_FROM_NUMBER" + echo " - To Number: $TWILIO_TO_NUMBER" + echo " - MCP Config: $CLAUDE_MCP_CONFIG" else echo "No Twilio credentials found, MCP features will be unavailable" fi + +# Start Claude Code with permissions bypass +echo "Starting Claude Code..." exec claude --dangerously-skip-permissions "$@" \ No newline at end of file diff --git a/test-twilio.js b/test-twilio.js new file mode 100755 index 0000000..73266bd --- /dev/null +++ b/test-twilio.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +// Test script to verify Twilio SMS functionality +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_API_SECRET; +const apiKey = process.env.TWILIO_API_KEY; +const fromNumber = process.env.TWILIO_FROM_NUMBER; +const toNumber = process.env.TWILIO_TO_NUMBER; + +console.log('Twilio Test Configuration:'); +console.log(`Account SID: ${accountSid?.substring(0, 10)}...`); +console.log(`API Key: ${apiKey?.substring(0, 10)}...`); +console.log(`From: ${fromNumber}`); +console.log(`To: ${toNumber}`); + +// Using Twilio REST API directly +const https = require('https'); + +const auth = Buffer.from(`${apiKey}:${authToken}`).toString('base64'); +const data = new URLSearchParams({ + To: toNumber, + From: fromNumber, + Body: 'MCP is working! This is a test message from Claude Docker.' +}); + +const options = { + hostname: 'api.twilio.com', + port: 443, + path: `/2010-04-01/Accounts/${accountSid}/Messages.json`, + method: 'POST', + headers: { + 'Authorization': `Basic ${auth}`, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.toString().length + } +}; + +const req = https.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + if (res.statusCode === 201) { + console.log('\n✅ SMS sent successfully!'); + const response = JSON.parse(body); + console.log(`Message SID: ${response.sid}`); + console.log(`Status: ${response.status}`); + } else { + console.error('\n❌ Failed to send SMS'); + console.error(`Status: ${res.statusCode}`); + console.error(`Response: ${body}`); + } + }); +}); + +req.on('error', (e) => { + console.error(`Problem with request: ${e.message}`); +}); + +req.write(data.toString()); +req.end(); \ No newline at end of file