Safaricom Daraja MCP Server

Safaricom Daraja MCP Server

A Model Context Protocol (MCP) server that integrates Safaricom's M-PESA Daraja API with Claude, enabling natural language payment processing and real-time transaction notifications.

Category
Visit Server

README

Safaricom Daraja MCP Server

A Model Context Protocol (MCP) server that integrates Safaricom's M-PESA Daraja API with Claude, enabling natural language payment processing and real-time transaction notifications.

🌟 Features

  • STK Push Payments: Initiate M-PESA payment requests through natural language
  • Real-time Callbacks: Automatic payment notification handling with Flask server
  • Payment Tracking: Store and query payment history with read/unread status
  • Natural Language Interface: Interact with M-PESA through Claude conversations
  • Sandbox Testing: Full support for Daraja sandbox environment
  • Automated Testing: Comprehensive test suite for validation

πŸ“‹ Table of Contents

Prerequisites

  • Python 3.10+ installed on your system
  • Daraja API Account - Register at developer.safaricom.co.ke
  • ngrok (optional, for testing callbacks) - Download from ngrok.com
  • Claude Desktop (optional, for MCP integration)

Installation

1. Clone the Repository

# Clone the repository
git clone https://github.com/mboya/daraja-mcp.git
cd daraja-mcp

# Or if you already have the repository, navigate to it
cd daraja-mcp

2. Set Up Virtual Environment

# Create virtual environment
python3 -m venv venv

# Activate virtual environment
# macOS/Linux:
source venv/bin/activate

# Windows:
venv\Scripts\activate

You should see (venv) in your terminal prompt.

3. Install Dependencies

# Install all required packages from requirements.txt
pip install -r requirements.txt

This will install:

  • mcp - Model Context Protocol server
  • requests - HTTP library for API calls
  • flask - Web framework for callback server
  • python-dotenv - Environment variable management
  • gunicorn - WSGI HTTP server (for production deployment)

4. Configure Environment Variables

Create a .env file in the project root directory. The repository includes a .env.example file as a template.

Quick Setup:

# Copy the example file
cp .env.example .env

# Edit the .env file with your actual credentials
# Use your preferred text editor (nano, vim, code, etc.)
nano .env

Then replace the placeholder values with your actual Daraja API credentials (see Getting Daraja Credentials).

Configuration

Environment Variables

Copy .env.example to .env and fill in your credentials. Key variables:

Variable Purpose
DARAJA_CONSUMER_KEY / DARAJA_CONSUMER_SECRET OAuth (all APIs)
DARAJA_SHORTCODE / DARAJA_PASSKEY STK Push
DARAJA_INITIATOR / DARAJA_SECURITY_CREDENTIAL B2C, B2B, balance, status, reversal
DARAJA_ENV sandbox or production
PUBLIC_URL HTTPS base URL Safaricom can reach (ngrok locally, Railway in prod)
CALLBACK_HOST 127.0.0.1 for local dev (avoids macOS bind errors)
CALLBACK_PORT Default 3000
IOT_* IoT SIM Portal (Phase 5) β€” see .env.example

Never commit .env. For production, set variables in your hosting dashboard (Railway, etc.).

Getting Daraja Credentials

1. Register on Daraja Portal

  1. Visit developer.safaricom.co.ke
  2. Create an account
  3. Verify your email

2. Create an App

  1. Navigate to "My Apps" β†’ "Create New App"
  2. Select APIs:
    • Lipa Na M-PESA Online
    • M-PESA Express (STK Push)
  3. Submit your app
  4. Get your credentials:
    • Consumer Key
    • Consumer Secret
    • Passkey (in app details)

3. Sandbox Test Credentials

For testing, use these sandbox values:

  • Business Short Code: 174379 (default sandbox)
  • Passkey: Check your app details on Daraja portal
  • Test Phone Numbers: 254708374149 (check Daraja docs for updated test numbers)
  • Test PIN: Varies by sandbox version (usually simulated automatically)

4. Production Credentials

  1. Test thoroughly in sandbox
  2. Apply for production access through Daraja portal
  3. Complete KYC and business verification
  4. Receive production credentials
  5. Update .env with production values and set DARAJA_ENV=production

Usage

Run the server

Scenario Command Notes
Claude Desktop (local) python server.py stdio MCP + Flask callbacks on CALLBACK_PORT
HTTP / cloud / Railway gunicorn server_http:app --bind 0.0.0.0:$PORT See Procfile; exposes /mcp/tools, /mcp/call_tool, all callback routes
Local HTTP smoke test python server_http.py Same routes as production, without gunicorn

Verify the server:

curl http://127.0.0.1:3000/health

Both server.py and server_http.py share the same callback routes and MCP tools via daraja_core.py. server.py talks to Claude over stdio and runs Flask in a background thread; server_http.py is a single Flask app for deployment.

For real Safaricom callbacks in sandbox, set PUBLIC_URL to an HTTPS URL (ngrok locally). See Callback setup.

Then configure Claude Desktop.

Testing

All tests are in test_daraja.py.

Command What it runs
python test_daraja.py Full E2E (local): env, OAuth, MCP, webhooks, optional live API probes
python test_daraja.py --platform CI-safe: no live Safaricom calls (GitHub Actions)
python test_daraja.py --quick Auth + HTTP + callback smoke
python test_daraja.py --callbacks All 17 webhook routes + MCP storage/retrieval
python test_daraja.py --callbacks --public Above + POST webhooks through ngrok
python test_daraja.py --live Live sandbox: ngrok, real STK, IMSI, IoT
python test_daraja.py --b2c Live B2C payout + ngrok callback only
python test_daraja.py --all Everything: platform tests + live STK/B2C/IoT via ngrok (no skips)

Exit code 0 on pass, 1 on failure. Warnings (e.g. IMSI sandbox 404) do not fail CI.

Optional: DARAJA_TEST_PHONE (default 254708374149), DARAJA_B2C_TEST_AMOUNT (default 10).

GitHub Actions CI

Workflow: .github/workflows/ci.yml (push/PR to main, workflow_dispatch).

Job Command / check Secrets
compile compileall + MCP tool count No
platform test_daraja.py --platform (Python 3.10–3.12) No
webhooks test_daraja.py --callbacks No
gunicorn-smoke gunicorn server_http:app + curl health/MCP/callback No
live-sandbox test_daraja.py --live Daraja + optional NGROK_AUTHTOKEN

The live-sandbox job runs on workflow_dispatch or push to main; it runs live tests only when DARAJA_CONSUMER_KEY and DARAJA_CONSUMER_SECRET repository secrets are set (otherwise the job skips those steps).

Integrating with Claude Desktop

1. Locate Configuration File

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

2. Add MCP Server Configuration

{
  "mcpServers": {
    "daraja": {
      "command": "/absolute/path/to/daraja-mcp/venv/bin/python",
      "args": ["/absolute/path/to/daraja-mcp/server.py"],
      "env": {
        "DARAJA_CONSUMER_KEY": "your_consumer_key",
        "DARAJA_CONSUMER_SECRET": "your_consumer_secret",
        "DARAJA_SHORTCODE": "174379",
        "DARAJA_PASSKEY": "your_passkey",
        "DARAJA_ENV": "sandbox",
        "CALLBACK_PORT": "3000",
        "PUBLIC_URL": "https://your-ngrok-url.ngrok.io"
      }
    }
  }
}

Important:

  • Use absolute paths (not relative)
  • Use virtual environment's Python: venv/bin/python
  • Update PUBLIC_URL with your ngrok HTTPS URL

3. Restart Claude Desktop

Completely quit and reopen Claude Desktop to load the MCP server.

4. Verify Integration

In Claude Desktop, ask:

"Is the Daraja callback server working?"

Claude should respond with server status information.

Claude Desktop and Railway

This section explains how Claude Desktop relates to a Railway deployment. Read this before pointing PUBLIC_URL at Railway while running Claude locally.

Two servers, two transports

File Runs on Protocol Typical use
server.py Your Mac/PC stdio MCP Claude Desktop (spawns local process)
server_http.py Railway / VPS HTTP (/mcp/tools, /mcp/call_tool) Production callbacks, curl, your own backend

Claude Desktop does not connect to Railway as a native MCP server today. It expects a local command (server.py), not https://your-app.railway.app/mcp/call_tool.

Railway’s HTTP endpoints are a REST-style tool API, not the full MCP stream Claude Desktop uses over stdio (or remote MCP URL/SSE).

Callbacks must hit the same process as Claude

STK payments and async callbacks are stored in memory in the running server process.

Setup Works for Claude chat + payment history?
Claude β†’ local server.py, PUBLIC_URL β†’ ngrok β†’ same machine Yes
Claude β†’ local server.py, PUBLIC_URL β†’ Railway No β€” Safaricom posts to Railway; Claude’s local process never sees those callbacks
No Claude; only Railway server_http.py Yes for HTTP/callbacks; use /mcp/call_tool, not Claude Desktop
Claude on Railway Not supported without adding remote MCP transport to server_http.py

Rule: The host in PUBLIC_URL must be the same instance that handles MCP tools and stores payments.

Recommended setups

A β€” Claude Desktop + local server (recommended for chatting with Claude)

  1. Configure Claude with server.py (see Integrating with Claude Desktop).
  2. Run ngrok (or similar): ngrok http 3000.
  3. Set PUBLIC_URL in Claude’s env to the ngrok HTTPS URL (or in .env if you load it locally).
  4. Skip Railway for Claude, or use Railway only for unrelated HTTP integrations.

B β€” Railway only (no Claude Desktop)

  1. Deploy via Procfile (gunicorn server_http:app).
  2. Set Railway variables (see Railway Deployment), especially:
    PUBLIC_URL=https://your-service.up.railway.app
    
  3. Verify: curl https://your-service.up.railway.app/health
  4. Call tools with HTTP:
    curl -X POST https://your-service.up.railway.app/mcp/call_tool \
      -H "Content-Type: application/json" \
      -d '{"name":"get_notification_summary","arguments":{}}'
    
  5. Register Safaricom callback URLs using your Railway domain (see Callback routes).

C β€” Railway for callbacks + Claude (common mistake)

Setting Claude’s local PUBLIC_URL to Railway looks convenient but breaks payment tracking in Claude: STK/B2C results land on Railway, while get_recent_payments reads the local store.

Use setup A instead, or setup B without Claude until shared storage or remote MCP is implemented.

Railway env vars (Claude-free deployment)

Variable Value
DARAJA_* Daraja credentials (same as local)
PUBLIC_URL https://<your-service>.up.railway.app (no trailing slash)
DARAJA_ENV sandbox or production
Do not set CALLBACK_HOST Railway sets $PORT; gunicorn binds 0.0.0.0:$PORT

Initiator and IoT variables are optional; see .env.example.

Available Tools

Once configured, Claude can use these tools:

1. stk_push

Initiate an STK Push payment request.

Example:

"Send a payment request for 500 KES to 0712345678 for order #INV-001"

Parameters:

  • phone_number - Customer phone (254XXXXXXXXX or 07XXXXXXXX)
  • amount - Amount in KES (minimum 1)
  • account_reference - Reference like invoice/order number
  • transaction_desc - Description of transaction

2. stk_query

Check the status of a payment request.

Example:

"Check the status of checkout request ws_CO_12345"

Parameters:

  • checkout_request_id - ID returned from STK push

3. get_recent_payments

View recent payment notifications.

Example:

"Show me the last 10 payments"

Parameters:

  • limit - Number of payments to retrieve (default: 10, max: 50)

4. get_payment_details

Get details of a specific payment.

Example:

"Show me details for receipt QAR7I8K3LM"

Parameters:

  • checkout_request_id - Or -
  • mpesa_receipt - M-PESA receipt number

5. mark_payment_read

Mark a notification as read.

Example:

"Mark payment ws_CO_12345 as read"

6. get_notification_summary

Get summary of all notifications.

Example:

"How many unread payments do I have?"

7. get_callback_status

Check if callback server is running.

Example:

"Is the callback server working?"

Additional tools (B2C, B2B, C2B, Ratiba, Pull, IMSI, IoT) are listed under API coverage vs Postman.

Phase 1 β€” B2C, B2B, C2B, balance, status, reversal

Tool Description
b2c_payment Pay customer (B2C) β€” needs initiator credentials
b2pochi_payment B2Pochi wallet payout
b2b_payment Business-to-business transfer
c2b_register_url Register validation/confirmation URLs
c2b_simulate Sandbox C2B payment simulation
account_balance Query paybill balance (async callback)
transaction_status Query by M-PESA transaction ID
reversal Reverse a transaction
get_recent_callbacks List B2C/B2B/C2B/balance/reversal callbacks
get_access_token Debug OAuth token

Callback Setup

Safaricom requires HTTPS callback URLs reachable from the internet. Localhost works for simulated webhooks (python test_daraja.py --callbacks); use a tunnel or deployed host for real STK/B2C results.

Automated tests can start ngrok for you (--live, --b2c, --all).

Why You Need ngrok (or Similar Tunneling Service)

The Problem:

  • M-PESA Daraja API requires HTTPS callbacks (not HTTP)
  • Safaricom's servers need to reach your callback endpoint from the internet
  • Your local development server (localhost:3000) is not accessible from the internet
  • Firewalls and NAT prevent external access to your local machine

The Solution: ngrok creates a secure tunnel that:

  • βœ… Exposes your local server to the internet via HTTPS
  • βœ… Provides a public URL that Safaricom can reach
  • βœ… Automatically handles SSL/TLS encryption
  • βœ… Allows real-time testing without deploying to production
  • βœ… Shows all incoming requests in a web interface for debugging

How It Works:

Safaricom Servers β†’ ngrok HTTPS URL β†’ ngrok Tunnel β†’ Your Local Server (localhost:3000)

Local Testing with ngrok

1. Install ngrok

# macOS
brew install ngrok

# Linux (using snap)
sudo snap install ngrok

# Windows
# Download from https://ngrok.com/download
# Or use Chocolatey: choco install ngrok

# Or download directly from https://ngrok.com/download

Sign up for free: Visit ngrok.com and create an account to get your authtoken.

2. Authenticate ngrok (First Time Only)

ngrok config add-authtoken YOUR_AUTHTOKEN_HERE

3. Start ngrok Tunnel

# Forward HTTPS traffic to your local port 3000
ngrok http 3000

Output:

Session Status                online
Account                       Your Name (Plan: Free)
Version                       3.x.x
Region                        United States (us)
Latency                       45ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://abc123.ngrok.io -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Important: Copy the Forwarding HTTPS URL (e.g., https://abc123.ngrok.io)

4. Update Configuration

Update PUBLIC_URL in your .env file:

PUBLIC_URL=https://abc123.ngrok.io

Or update Claude Desktop config with the ngrok URL.

Note: Free ngrok URLs change each time you restart ngrok. For a static URL, upgrade to a paid plan or use ngrok's reserved domains feature.

5. Restart Server

# Stop the server (Ctrl+C)
# Restart with new PUBLIC_URL
python server.py

6. Verify ngrok is Working

Check ngrok web interface:

  • Open http://localhost:4040 in your browser
  • You'll see all requests being forwarded through ngrok
  • Useful for debugging callback issues

Test the tunnel:

# Test health endpoint through ngrok
curl https://abc123.ngrok.io/health

# Should return:
# {"status":"healthy","callback_url":"https://abc123.ngrok.io/mpesa/callback",...}

7. Keep ngrok Running

Important: Keep the ngrok terminal window open while testing. If you close it, the tunnel stops and Safaricom won't be able to reach your callback endpoint.

Pro Tip: Run ngrok in a separate terminal or use a process manager like tmux or screen:

# Using tmux
tmux new -s ngrok
ngrok http 3000
# Press Ctrl+B then D to detach (keeps running in background)

ngrok Alternatives

If you prefer other tunneling services:

  • Cloudflare Tunnel (cloudflared) - Free, no account needed for basic use

    cloudflared tunnel --url http://localhost:3000
    
  • localtunnel - Simple npm-based tunnel

    npx localtunnel --port 3000
    
  • serveo - SSH-based tunnel (no installation)

    ssh -R 80:localhost:3000 serveo.net
    

However, ngrok is recommended because:

  • Most reliable and stable
  • Best documentation and community support
  • Web interface for request inspection
  • Easy to use and configure

Production Callback Setup

For production, deploy to a server with:

  1. Public HTTPS endpoint (SSL certificate required)
  2. Static IP or domain name
  3. Firewall rules allowing incoming HTTPS traffic
  4. Monitoring and logging

Popular options:

  • AWS EC2 with Elastic IP
  • DigitalOcean Droplet
  • Heroku with SSL
  • Google Cloud Run
  • Railway (recommended - see deployment guide below)

Example nginx configuration:

server {
    listen 443 ssl;
    server_name api.yourdomain.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location /mpesa/ {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Railway Deployment (Quick Start)

Railway is an excellent choice for deploying this MCP server because it:

  • βœ… Provides HTTPS endpoints automatically
  • βœ… Handles SSL certificates
  • βœ… Easy environment variable configuration
  • βœ… Automatic deployments from Git
  • βœ… Free tier available for testing

Railway Deployment Steps

  1. Create Railway Account

  2. Create New Project

    • Click "New Project"
    • Select "Deploy from GitHub repo" (or upload code)
  3. Configure Environment Variables In Railway dashboard, add these environment variables:

    DARAJA_CONSUMER_KEY=your_consumer_key
    DARAJA_CONSUMER_SECRET=your_consumer_secret
    DARAJA_SHORTCODE=174379
    DARAJA_PASSKEY=your_passkey
    DARAJA_ENV=sandbox
    CALLBACK_PORT=3000
    PUBLIC_URL=https://your-app-name.railway.app
    

    Note: On Railway, the app is served by gunicorn which binds to 0.0.0.0:$PORT automatically; you don't need to set CALLBACK_HOST.

  4. Deploy

    • Railway will automatically detect Procfile and railway.json
    • The Procfile uses server_http.py with gunicorn
    • Railway will build and deploy automatically
  5. Get Your Public URL

    • Railway provides a public HTTPS URL (e.g., https://your-app.railway.app)
    • Update PUBLIC_URL environment variable with this URL
    • Railway will restart the service automatically
  6. Verify Deployment

    # Test health endpoint
    curl https://your-app.railway.app/health
    
    # Should return:
    # {"status":"healthy","callback_url":"https://your-app.railway.app/mpesa/callback",...}
    

Important Notes:

  • Railway automatically provides HTTPS, so no ngrok needed in production
  • The PUBLIC_URL must match your Railway app URL exactly
  • Use server_http.py (configured in Procfile) for Railway deployments
  • Railway handles port binding automatically via $PORT environment variable

Troubleshooting

Startup

Issue Fix
Can't assign requested address (macOS) Set CALLBACK_HOST=127.0.0.1 in .env
Port 3000 in use lsof -i :3000 then kill process, or change CALLBACK_PORT
.env not loading cp .env.example .env; verify with python -c "from dotenv import load_dotenv; load_dotenv(); import os; print(os.getenv('DARAJA_CONSUMER_KEY'))"
Health check fails Ensure server is running; curl http://127.0.0.1:3000/health

Common Issues

1. "Failed to get access token"

Causes:

  • Invalid Consumer Key or Secret
  • Wrong environment (sandbox vs production)
  • Network connectivity issues

Solutions:

# Test authentication manually
python -c "
from dotenv import load_dotenv
import os, base64, requests
load_dotenv()
key = os.getenv('DARAJA_CONSUMER_KEY')
secret = os.getenv('DARAJA_CONSUMER_SECRET')
auth = base64.b64encode(f'{key}:{secret}'.encode()).decode()
r = requests.get('https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
                 headers={'Authorization': f'Basic {auth}'})
print(r.json())
"

2. "Callback server not responding"

Solutions:

# Check if port 3000 is available
lsof -i :3000

# Kill any process using the port
kill -9 <PID>

# Restart server
python server.py

3. "MCP server not found in Claude"

Solutions:

  • Verify config file path is correct
  • Use absolute paths in configuration
  • Ensure virtual environment Python path is correct
  • Check Claude Desktop logs: Help β†’ View Logs
  • Restart Claude Desktop completely

4. "No callbacks received"

Solutions:

  • Verify ngrok is running: curl https://your-url.ngrok.io/health
  • Check PUBLIC_URL environment variable
  • Ensure ngrok URL is HTTPS (required by Safaricom)
  • View ngrok request logs: http://localhost:4040
  • Check firewall settings

5. "Invalid phone number"

Solutions:

  • Use format: 254XXXXXXXXX (not +254 or 07XX)
  • Sandbox: Use test numbers from Daraja portal
  • Remove spaces, dashes, or special characters

Debug Commands

# Check server process
ps aux | grep server.py

# Test callback health
curl http://localhost:3000/health

# Test ngrok forwarding
curl https://your-ngrok-url.ngrok.io/health

# View Python errors
tail -f server.log

# Check Claude logs
# macOS: ~/Library/Logs/Claude/
# Windows: %APPDATA%\Claude\logs\

Getting Help

  1. Check Daraja API documentation: developer.safaricom.co.ke/Documentation
  2. Review ngrok request inspector: http://localhost:4040
  3. Check Claude Desktop logs
  4. Verify all environment variables are set correctly
  5. Test each component independently

Security Best Practices

1. Credential Management

  • βœ… Never commit credentials to version control
  • βœ… Use .env files with .gitignore
  • βœ… Rotate credentials regularly
  • βœ… Use different credentials for sandbox and production
  • βœ… Store production secrets in secure vaults (AWS Secrets Manager, etc.)

2. Network Security

  • βœ… Use HTTPS for all callbacks (required by Safaricom)
  • βœ… Implement webhook signature verification
  • βœ… Restrict callback endpoint to Safaricom IPs
  • βœ… Use firewall rules to limit access
  • βœ… Enable rate limiting

3. Application Security

  • βœ… Validate all input data
  • βœ… Sanitize phone numbers and amounts
  • βœ… Implement request logging
  • βœ… Add authentication for sensitive operations
  • βœ… Use environment-specific configurations

4. Data Privacy

  • βœ… Don't log sensitive data (PINs, full card numbers)
  • βœ… Mask phone numbers in logs
  • βœ… Implement data retention policies
  • βœ… Comply with data protection regulations
  • βœ… Encrypt data at rest and in transit

5. Monitoring

  • βœ… Set up error alerting
  • βœ… Monitor callback success rates
  • βœ… Track failed transactions
  • βœ… Log all API calls
  • βœ… Implement health checks

Production Deployment

Pre-deployment Checklist

  • [ ] Thoroughly tested in sandbox environment
  • [ ] Obtained production credentials from Daraja
  • [ ] Set up production server with SSL/TLS
  • [ ] Configured firewall and security groups
  • [ ] Implemented proper logging and monitoring
  • [ ] Set up error alerting
  • [ ] Documented deployment process
  • [ ] Created backup and recovery plan
  • [ ] Tested with small amounts first
  • [ ] Configured auto-restart on failure

Deployment Steps

1. Prepare Server

# Update system
sudo apt update && sudo apt upgrade -y

# Install Python
sudo apt install python3.10 python3.10-venv -y

# Install nginx (for reverse proxy)
sudo apt install nginx -y

# Install supervisor (for process management)
sudo apt install supervisor -y

2. Deploy Application

# Create application directory
sudo mkdir -p /opt/daraja-mcp
sudo chown $USER:$USER /opt/daraja-mcp
cd /opt/daraja-mcp

# Clone or copy application files
# Set up virtual environment
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Create production .env
nano .env
# Add production credentials

3. Configure Supervisor

Create /etc/supervisor/conf.d/daraja-mcp.conf:

[program:daraja-mcp]
command=/opt/daraja-mcp/venv/bin/python /opt/daraja-mcp/server.py
directory=/opt/daraja-mcp
user=www-data
autostart=true
autorestart=true
stderr_logfile=/var/log/daraja-mcp/error.log
stdout_logfile=/var/log/daraja-mcp/access.log
environment=PRODUCTION="true"

4. Configure nginx

Create /etc/nginx/sites-available/daraja-mcp:

server {
    listen 80;
    server_name api.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
    
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

5. Start Services

# Reload supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start daraja-mcp

# Enable and restart nginx
sudo ln -s /etc/nginx/sites-available/daraja-mcp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

# Check status
sudo supervisorctl status daraja-mcp
curl https://api.yourdomain.com/health

Monitoring and Maintenance

# View logs
sudo tail -f /var/log/daraja-mcp/error.log

# Restart service
sudo supervisorctl restart daraja-mcp

# Check resource usage
htop

# Monitor nginx access
sudo tail -f /var/log/nginx/access.log

Postman collection (API reference)

The repo includes Safaricom’s official Postman collection as the source-of-truth checklist for Daraja endpoints:

File Safaricom APIs.postman_collection.json
Postman link Safaricom APIs on Postman
Sandbox base https://sandbox.safaricom.co.ke
Production base https://api.safaricom.co.ke

Use this collection when adding MCP tools: each request maps to a Daraja API product. Coverage vs this server is summarized in API coverage below.

API coverage vs Postman

Status Count Notes
Phase 1 (MCP tools) 10 STK, B2C, B2Pochi, B2B, C2B register/simulate, balance, status, reversal
Phase 2 (MCP tools) 2 M-PESA Ratiba standing orders (paybill, buy goods)
Phase 3 (MCP tools) 3 Pull API register/query, SFC Verify org lookup
Phase 4 (MCP tools) 2 IMSI / SWAP CheckATI
Phase 5 (MCP tools) 14 IoT SIM Portal messaging and SIM operations
Helpers 6 Payments, async callbacks, summary, access token, callback status
Postman coverage Complete All 35 collection requests have MCP tools

Initiator APIs need DARAJA_INITIATOR_NAME, DARAJA_INITIATOR, and DARAJA_SECURITY_CREDENTIAL in .env.

Callback routes (prefix with your HTTPS PUBLIC_URL):

Route Purpose
/mpesa/callback STK Push
/mpesa/b2c/result, /mpesa/b2c/timeout B2C / B2Pochi
/mpesa/b2b/result, /mpesa/b2b/timeout B2B
/mpesa/c2b/validation, /mpesa/c2b/confirmation C2B
/mpesa/accountbalance/result, .../timeout Account balance
/mpesa/transactionstatus/result, .../timeout Transaction status
/mpesa/reversal/result, .../timeout Reversal
/mpesa/ratiba/callback M-PESA Ratiba standing order events
/mpesa/pull/callback Pull API transaction notifications

Phase 2 β€” M-PESA Ratiba (standing orders)

Tool Description
ratiba_standing_order_paybill Schedule recurring paybill payments from a customer
ratiba_standing_order_buy_goods Schedule recurring till/buy-goods payments

Frequency codes: 1 once, 2 daily, 3 weekly, 4 monthly, 5 bi-monthly, 6 quarterly, 7 half-year, 8 yearly (or aliases like monthly, weekly).

Dates: start_date and end_date as yyyymmdd (e.g. 20260101).

Phase 3 β€” Pull API & SFC Verify

Tool Description
pull_register Register pull callback (request_type: Pull or Cancellation)
pull_query Fetch transactions for a datetime range
sfcverify_query_org_info Look up party/org by identifier type and value

Pull query dates: YYYY-MM-DD H:MM:SS (e.g. 2024-08-04 8:36:00).

Phase 4 β€” IMSI / SWAP

Tool Endpoint
imsi_check_ati POST /imsi/v1/checkATI
imsi_swap_check_ati POST /imsi/v2/checkATI

Sandbox note: 404 Not found usually means the IMSI product is not enabled on your Daraja app, or the test MSISDN is not provisioned for IMSI. Enable IMSI CheckATI in the developer portal, confirm sandbox test numbers, and set DARAJA_TEST_PHONE if needed.

Phase 5 β€” IoT SIM Portal

Tool Purpose
iot_search_messages Search messages
iot_filter_messages Filter by date/status
iot_delete_message_thread Delete thread
iot_get_all_messages List messages
iot_send_single_message Send SMS to SIM
iot_delete_message Delete message by ID
iot_all_sims List SIMs in VPN group
iot_query_lifecycle_status SIM lifecycle
iot_query_customer_info Customer info
iot_sim_activation Activate SIM
iot_get_activation_trends Activation trends
iot_rename_asset Rename asset
iot_get_location_info Location info
iot_suspend_unsuspend_sub Suspend/unsuspend

API Reference

Daraja API Endpoints

Authentication

GET https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials
Authorization: Basic <base64(consumer_key:consumer_secret)>

STK Push

POST https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "BusinessShortCode": "174379",
  "Password": "<base64(shortcode+passkey+timestamp)>",
  "Timestamp": "20240108143022",
  "TransactionType": "CustomerPayBillOnline",
  "Amount": 100,
  "PartyA": "254712345678",
  "PartyB": "174379",
  "PhoneNumber": "254712345678",
  "CallBackURL": "https://your-domain.com/callback",
  "AccountReference": "Order123",
  "TransactionDesc": "Payment for Order123"
}

STK Query

POST https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "BusinessShortCode": "174379",
  "Password": "<base64(shortcode+passkey+timestamp)>",
  "Timestamp": "20240108143022",
  "CheckoutRequestID": "ws_CO_08012024123456789"
}

MCP Server Endpoints

Health Check

GET http://localhost:3000/health

Response:
{
  "status": "healthy",
  "environment": "sandbox",
  "callback_url": "http://localhost:3000/mpesa/callback",
  "callback_urls": { "stk": "...", "b2c_result": "...", "ratiba": "...", "pull": "..." },
  "unread_payments": 0,
  "unread_callbacks": 0
}

M-PESA Callback

POST http://localhost:3000/mpesa/callback
Content-Type: application/json

{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "29115-34620561-1",
      "CheckoutRequestID": "ws_CO_08012024123456789",
      "ResultCode": 0,
      "ResultDesc": "The service request is processed successfully.",
      "CallbackMetadata": {
        "Item": [
          {"Name": "Amount", "Value": 100},
          {"Name": "MpesaReceiptNumber", "Value": "QAR7I8K3LM"},
          {"Name": "TransactionDate", "Value": 20240108143022},
          {"Name": "PhoneNumber", "Value": 254712345678}
        ]
      }
    }
  }
}

Project Structure

daraja-mcp/
β”œβ”€β”€ venv/                       # Virtual environment (not in git)
β”œβ”€β”€ daraja_core.py              # Shared Daraja client, callbacks, MCP tools
β”œβ”€β”€ daraja_iot.py               # IMSI and IoT SIM Portal APIs (Phases 4–5)
β”œβ”€β”€ server.py                   # MCP server for local Claude Desktop (stdio)
β”œβ”€β”€ server_http.py              # MCP server for cloud deployment (HTTP)
β”œβ”€β”€ test_daraja.py             # Unified test suite (see Testing)
β”œβ”€β”€ .github/workflows/ci.yml   # CI: platform, webhooks, gunicorn, optional live
β”œβ”€β”€ Procfile                   # Railway: gunicorn server_http:app
β”œβ”€β”€ railway.json               # Railway platform configuration
β”œβ”€β”€ .env.example               # Environment template (copy to .env)
β”œβ”€β”€ requirements.txt           # Python dependencies
β”œβ”€β”€ README.md                  # This file
└── Safaricom APIs.postman_collection.json  # Postman reference (optional local copy)
File Use
server.py Claude Desktop (stdio)
server_http.py Production / HTTP MCP + callbacks
daraja_core.py Daraja client, callbacks, core MCP tools
daraja_iot.py IMSI + IoT SIM Portal tools
test_daraja.py All automated tests

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Setup

Follow Installation, then run python test_daraja.py --platform before opening a PR.

License

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

Acknowledgments

Support

Changelog

v1.0.0 (2024-01-08)

  • Initial release
  • STK Push implementation
  • Real-time callback handling
  • Payment notification storage
  • Automated testing suite
  • Claude Desktop integration
  • Comprehensive documentation

Made with ❀️ for the M-PESA ecosystem

For questions or support, please open an issue on GitHub or contact the maintainers.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured