Skip to main content
Glama
mboya

Safaricom Daraja MCP Server

by mboya

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

Related MCP server: Daraja MCP

πŸ“‹ 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.

  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.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mboya/daraja-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server