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.
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
- Installation
- Configuration
- Getting Daraja Credentials
- Usage
- Testing
- Integrating with Claude Desktop
- Claude Desktop and Railway
- Available Tools
- Callback Setup
- Troubleshooting
- Security Best Practices
- Production Deployment
- API coverage vs Postman
- API Reference
- Project Structure
- Contributing
- License
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 serverrequests- HTTP library for API callsflask- Web framework for callback serverpython-dotenv- Environment variable managementgunicorn- 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
- Visit developer.safaricom.co.ke
- Create an account
- Verify your email
2. Create an App
- Navigate to "My Apps" β "Create New App"
- Select APIs:
- Lipa Na M-PESA Online
- M-PESA Express (STK Push)
- Submit your app
- 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
- Test thoroughly in sandbox
- Apply for production access through Daraja portal
- Complete KYC and business verification
- Receive production credentials
- Update
.envwith production values and setDARAJA_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)
- Configure Claude with
server.py(see Integrating with Claude Desktop). - Run ngrok (or similar):
ngrok http 3000. - Set
PUBLIC_URLin Claudeβsenvto the ngrok HTTPS URL (or in.envif you load it locally). - Skip Railway for Claude, or use Railway only for unrelated HTTP integrations.
B β Railway only (no Claude Desktop)
- Deploy via
Procfile(gunicorn server_http:app). - Set Railway variables (see Railway Deployment), especially:
PUBLIC_URL=https://your-service.up.railway.app - Verify:
curl https://your-service.up.railway.app/health - 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":{}}' - 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 numbertransaction_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:
- Public HTTPS endpoint (SSL certificate required)
- Static IP or domain name
- Firewall rules allowing incoming HTTPS traffic
- 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
-
Create Railway Account
- Visit railway.app
- Sign up with GitHub/GitLab
-
Create New Project
- Click "New Project"
- Select "Deploy from GitHub repo" (or upload code)
-
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.appNote: On Railway, the app is served by gunicorn which binds to
0.0.0.0:$PORTautomatically; you don't need to setCALLBACK_HOST. -
Deploy
- Railway will automatically detect
Procfileandrailway.json - The
Procfileusesserver_http.pywith gunicorn - Railway will build and deploy automatically
- Railway will automatically detect
-
Get Your Public URL
- Railway provides a public HTTPS URL (e.g.,
https://your-app.railway.app) - Update
PUBLIC_URLenvironment variable with this URL - Railway will restart the service automatically
- Railway provides a public HTTPS URL (e.g.,
-
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_URLmust match your Railway app URL exactly - Use
server_http.py(configured inProcfile) for Railway deployments - Railway handles port binding automatically via
$PORTenvironment 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
- Check Daraja API documentation: developer.safaricom.co.ke/Documentation
- Review ngrok request inspector: http://localhost:4040
- Check Claude Desktop logs
- Verify all environment variables are set correctly
- Test each component independently
Security Best Practices
1. Credential Management
- β Never commit credentials to version control
- β
Use
.envfiles 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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
- Safaricom Daraja API - M-PESA API platform
- Anthropic MCP - Model Context Protocol
- Flask - Web framework for callbacks
- ngrok - Secure tunneling for local development
Support
- Documentation: developer.safaricom.co.ke/Documentation
- Daraja Support: support@safaricom.co.ke
- MCP Documentation: modelcontextprotocol.io
- Issues: GitHub Issues
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
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.