warimcp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -0
- package/.gitkeep +0 -0
- package/Dockerfile +14 -0
- package/NPM_SETUP.md +76 -0
- package/README.md +151 -0
- package/RECOVERY.md +62 -0
- package/TECHNICAL.md +82 -0
- package/backup.sh +55 -0
- package/docker-compose.yml +45 -0
- package/package.json +41 -0
- package/src/index.js +33 -0
- package/src/providers/cinetpay.js +73 -0
- package/src/providers/hub2.js +21 -0
- package/src/providers/papss.js +21 -0
- package/src/providers/wave.js +42 -0
- package/src/queue.js +42 -0
- package/src/router.js +31 -0
- package/src/tools.js +84 -0
- package/src/webhook.js +12 -0
package/.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# WariMCP — Environment Variables
|
|
2
|
+
# Copy this file to .env and fill in your credentials
|
|
3
|
+
|
|
4
|
+
# CinetPay (Phase 1)
|
|
5
|
+
CINETPAY_API_KEY=
|
|
6
|
+
CINETPAY_SITE_ID=
|
|
7
|
+
|
|
8
|
+
# Wave (Phase 1)
|
|
9
|
+
WAVE_API_KEY=
|
|
10
|
+
|
|
11
|
+
# Hub2 / Ecobank unified gateway (Phase 2)
|
|
12
|
+
HUB2_API_KEY=
|
|
13
|
+
|
|
14
|
+
# PAPSS Pan-African (Phase 3)
|
|
15
|
+
PAPSS_API_KEY=
|
|
16
|
+
|
|
17
|
+
# Server config
|
|
18
|
+
WARIMCP_PORT=3000
|
|
19
|
+
WARIMCP_ENV=development
|
package/.gitkeep
ADDED
|
File without changes
|
package/Dockerfile
ADDED
package/NPM_SETUP.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# WariMCP — npm Publish Setup
|
|
2
|
+
|
|
3
|
+
## One-Time Setup
|
|
4
|
+
|
|
5
|
+
### Step 1 — Create an npm Account
|
|
6
|
+
1. Go to https://www.npmjs.com/signup
|
|
7
|
+
2. Create an account with:
|
|
8
|
+
- Username: (your preferred handle, e.g. `bigabou`)
|
|
9
|
+
- Email: your main email
|
|
10
|
+
- Password: strong, unique
|
|
11
|
+
3. Verify your email from the confirmation message
|
|
12
|
+
|
|
13
|
+
### Step 2 — Enable 2FA (Required for Publishing)
|
|
14
|
+
1. Log into npmjs.com
|
|
15
|
+
2. Click your avatar → **Account** → **Two-Factor Authentication**
|
|
16
|
+
3. Choose **Auth and Writes** (required for publish)
|
|
17
|
+
4. Scan the QR code with Google Authenticator or Authy
|
|
18
|
+
5. Save your backup codes securely
|
|
19
|
+
|
|
20
|
+
### Step 3 — Log In from Terminal
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd ~/automation/projects/warimcp
|
|
24
|
+
npm login
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You'll be prompted for:
|
|
28
|
+
- Username
|
|
29
|
+
- Password
|
|
30
|
+
- Email
|
|
31
|
+
- OTP (from your authenticator app)
|
|
32
|
+
|
|
33
|
+
Verify login:
|
|
34
|
+
```bash
|
|
35
|
+
npm whoami
|
|
36
|
+
# should return your npm username
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 4 — Publish the Package
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm publish --access=public
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> **Note:** The package name `warimcp` was confirmed available as of March 1, 2026.
|
|
46
|
+
|
|
47
|
+
### Step 5 — Verify on npm
|
|
48
|
+
|
|
49
|
+
Visit: https://www.npmjs.com/package/warimcp
|
|
50
|
+
|
|
51
|
+
You should see the package live within ~2 minutes.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Future Publishes
|
|
56
|
+
|
|
57
|
+
Once logged in, Claude Code can handle all future publishes automatically:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Bump version (patch / minor / major)
|
|
61
|
+
npm version patch # 0.0.1 → 0.0.2
|
|
62
|
+
npm version minor # 0.0.1 → 0.1.0
|
|
63
|
+
|
|
64
|
+
# Publish
|
|
65
|
+
npm publish --access=public
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or just tell Claude: "Publish warimcp version X.Y.Z" and it will handle the version bump and publish.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Important Notes
|
|
73
|
+
|
|
74
|
+
- Do NOT run `npm publish` without bumping the version first — npm will reject duplicate versions
|
|
75
|
+
- The `--access=public` flag is required for the first publish of a scoped or new package
|
|
76
|
+
- npm sessions expire — re-run `npm login` if you get auth errors
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# WariMCP
|
|
2
|
+
|
|
3
|
+
> **The AI-native payment layer for West Africa**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/warimcp)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/Bigabou007-dev/warimcp/blob/main/CONTRIBUTING.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
WariMCP is an **MCP (Model Context Protocol) server** that gives AI agents and LLMs a unified, production-ready interface to all major West African payment rails.
|
|
14
|
+
|
|
15
|
+
Instead of integrating CinetPay, Wave, Hub2/Ecobank, and PAPSS one by one — WariMCP exposes them all through a single, consistent API that any Claude, GPT, or custom AI agent can call natively.
|
|
16
|
+
|
|
17
|
+
**Built for:**
|
|
18
|
+
- AI agents that need to initiate, track, or refund payments in West Africa
|
|
19
|
+
- Developers building fintech products across UEMOA and pan-African corridors
|
|
20
|
+
- Agencies that need a white-label payment middleware layer
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Supported Payment Rails
|
|
25
|
+
|
|
26
|
+
| Provider | Region | Coverage | Phase |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| **CinetPay** | UEMOA | Orange Money, MTN, Wave, Moov, Visa/MC | Phase 1 |
|
|
29
|
+
| **Wave** | Côte d'Ivoire, Sénégal | Wave mobile wallet | Phase 1 |
|
|
30
|
+
| **Hub2 / Ecobank** | UEMOA (unified) | 200M+ mobile wallets, single API | Phase 2 |
|
|
31
|
+
| **PAPSS** | Pan-African | CI ↔ Kenya corridor, 160+ banks, local currency | Phase 3 |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install warimcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Environment Variables
|
|
42
|
+
|
|
43
|
+
Copy `.env.example` and fill in your credentials:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cp .env.example .env
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```env
|
|
50
|
+
CINETPAY_API_KEY=your_cinetpay_api_key
|
|
51
|
+
CINETPAY_SITE_ID=your_cinetpay_site_id
|
|
52
|
+
WAVE_API_KEY=your_wave_api_key
|
|
53
|
+
HUB2_API_KEY=your_hub2_api_key
|
|
54
|
+
PAPSS_API_KEY=your_papss_api_key
|
|
55
|
+
WARIMCP_PORT=3000
|
|
56
|
+
WARIMCP_ENV=development
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### As an MCP Server (Claude / Cursor / Windsurf)
|
|
64
|
+
|
|
65
|
+
Add to your `.claude.json` or MCP config:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"warimcp": {
|
|
71
|
+
"command": "node",
|
|
72
|
+
"args": ["/path/to/warimcp/src/index.js"],
|
|
73
|
+
"env": {
|
|
74
|
+
"CINETPAY_API_KEY": "...",
|
|
75
|
+
"CINETPAY_SITE_ID": "..."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### As a Node.js Module
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const warimcp = require('warimcp');
|
|
86
|
+
|
|
87
|
+
const result = await warimcp.initiatePayment({
|
|
88
|
+
provider: 'cinetpay',
|
|
89
|
+
amount: 5000,
|
|
90
|
+
currency: 'XOF',
|
|
91
|
+
customer: { name: 'Kofi Atta', phone: '+2250700000000' },
|
|
92
|
+
description: 'Invoice #001',
|
|
93
|
+
return_url: 'https://yourapp.com/payment/callback'
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## MCP Tools
|
|
100
|
+
|
|
101
|
+
WariMCP exposes the following tools to AI agents:
|
|
102
|
+
|
|
103
|
+
| Tool | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `initiate_payment` | Start a payment via any supported rail |
|
|
106
|
+
| `check_status` | Poll the status of a transaction |
|
|
107
|
+
| `refund` | Issue a full or partial refund |
|
|
108
|
+
| `list_transactions` | List recent transactions with filters |
|
|
109
|
+
| `generate_payment_link` | Create a shareable payment link |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Roadmap
|
|
114
|
+
|
|
115
|
+
### Phase 1 — CinetPay + Wave (March 2026)
|
|
116
|
+
- [x] Project scaffold + MCP server
|
|
117
|
+
- [ ] CinetPay provider: `initiate_payment`, `check_status`, `refund`
|
|
118
|
+
- [ ] Wave provider: `initiate_payment`, `check_status`
|
|
119
|
+
- [ ] Webhook handler (payment confirmation)
|
|
120
|
+
- [ ] Master log / queue for Always-On reliability
|
|
121
|
+
- [ ] npm publish (`npm install warimcp`)
|
|
122
|
+
|
|
123
|
+
### Phase 2 — Hub2 / Ecobank Unified Gateway (60 days)
|
|
124
|
+
- [ ] Hub2 provider integration (single API → full UEMOA)
|
|
125
|
+
- [ ] Unified routing layer (auto-select best rail by country/wallet)
|
|
126
|
+
- [ ] `list_transactions` across providers
|
|
127
|
+
- [ ] Hosted dashboard (transaction monitor)
|
|
128
|
+
|
|
129
|
+
### Phase 3 — PAPSS Pan-African Corridor (Q3 2026)
|
|
130
|
+
- [ ] PAPSS integration (CI ↔ Kenya + 160 banks)
|
|
131
|
+
- [ ] Multi-currency settlement engine
|
|
132
|
+
- [ ] Enterprise middleware API (AI Bookkeeper, Bulk Payment Agent)
|
|
133
|
+
- [ ] Partnership program (Hub2, CinetPay, Orange Ventures)
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Contributing
|
|
138
|
+
|
|
139
|
+
PRs, issues, and discussions are welcome. Please open an issue before submitting major changes.
|
|
140
|
+
|
|
141
|
+
1. Fork the repo
|
|
142
|
+
2. Create your branch: `git checkout -b feat/your-feature`
|
|
143
|
+
3. Commit your changes: `git commit -m 'feat: add your feature'`
|
|
144
|
+
4. Push: `git push origin feat/your-feature`
|
|
145
|
+
5. Open a Pull Request
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](LICENSE) — Built by [Bigabou](https://github.com/Bigabou007-dev) in Abidjan, Côte d'Ivoire.
|
package/RECOVERY.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# WariMCP — Recovery Protocol
|
|
2
|
+
|
|
3
|
+
**Target:** Restore WariMCP to a fresh host in under 30 minutes.
|
|
4
|
+
|
|
5
|
+
## Prerequisites on Fresh Host
|
|
6
|
+
- Ubuntu 22.04+
|
|
7
|
+
- Docker + Docker Compose installed
|
|
8
|
+
- Nginx Proxy Manager running with `npm_proxy` network
|
|
9
|
+
|
|
10
|
+
## Step-by-Step Restore
|
|
11
|
+
|
|
12
|
+
### Step 1 — Restore Files (5 min)
|
|
13
|
+
```bash
|
|
14
|
+
# From backup archive (gdrive:vps-backups)
|
|
15
|
+
rclone copy gdrive:vps-backups/warimcp/latest.tar.gz /tmp/
|
|
16
|
+
cd /tmp && tar -xzf latest.tar.gz
|
|
17
|
+
mv warimcp ~/automation/projects/
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Step 2 — Restore Environment (2 min)
|
|
21
|
+
```bash
|
|
22
|
+
cd ~/automation/projects/warimcp
|
|
23
|
+
# .env is NOT in the archive — restore from your password manager or secure vault
|
|
24
|
+
cp /path/to/secure/.env .env
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Step 3 — Restore Database (10 min)
|
|
28
|
+
```bash
|
|
29
|
+
# Start only the DB container first
|
|
30
|
+
docker compose up -d warimcp_db
|
|
31
|
+
|
|
32
|
+
# Wait for postgres to be ready
|
|
33
|
+
sleep 10
|
|
34
|
+
|
|
35
|
+
# Restore from backup dump
|
|
36
|
+
docker compose exec -T warimcp_db psql -U warimcp warimcp < /tmp/warimcp_db_backup.sql
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 4 — Start Service (3 min)
|
|
40
|
+
```bash
|
|
41
|
+
docker compose up -d
|
|
42
|
+
docker compose logs -f warimcp # confirm healthy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 5 — Verify NPM Route (2 min)
|
|
46
|
+
- Log into Nginx Proxy Manager dashboard
|
|
47
|
+
- Confirm `warimcp` proxy host points to container:3000 on npm_proxy network
|
|
48
|
+
- Test payment status endpoint
|
|
49
|
+
|
|
50
|
+
### Step 6 — Queue Reconciliation (5 min)
|
|
51
|
+
```bash
|
|
52
|
+
# Check the master log for any in-flight transactions at time of failure
|
|
53
|
+
cat ~/automation/projects/warimcp/logs/queue.log | tail -50
|
|
54
|
+
# Re-process any PENDING transactions manually if needed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Total Estimated Time: ~27 minutes
|
|
58
|
+
|
|
59
|
+
## Contacts
|
|
60
|
+
- CinetPay support: support@cinetpay.com
|
|
61
|
+
- Wave developer support: developers@wave.com
|
|
62
|
+
- Hub2: developers@hub2.io
|
package/TECHNICAL.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# WariMCP — Technical Documentation
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
WariMCP is a Node.js MCP (Model Context Protocol) server that exposes West African payment rails to AI agents. It runs as a containerized service behind Nginx Proxy Manager with an embedded PostgreSQL database for transaction logging.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
AI Agent / LLM
|
|
9
|
+
│
|
|
10
|
+
▼
|
|
11
|
+
WariMCP MCP Server (Express + MCP SDK)
|
|
12
|
+
│
|
|
13
|
+
├── router.js → Selects the correct payment provider
|
|
14
|
+
├── tools.js → MCP tool definitions
|
|
15
|
+
├── queue.js → Master log / Always-On queue
|
|
16
|
+
│
|
|
17
|
+
├── providers/
|
|
18
|
+
│ ├── cinetpay.js → CinetPay UEMOA gateway (Phase 1)
|
|
19
|
+
│ ├── wave.js → Wave mobile wallet (Phase 1)
|
|
20
|
+
│ ├── hub2.js → Hub2/Ecobank unified (Phase 2)
|
|
21
|
+
│ └── papss.js → PAPSS pan-African (Phase 3)
|
|
22
|
+
│
|
|
23
|
+
└── webhook.js → Inbound payment confirmations
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Deployment
|
|
27
|
+
|
|
28
|
+
### Prerequisites
|
|
29
|
+
- Docker + Docker Compose
|
|
30
|
+
- Nginx Proxy Manager (shared `npm_proxy` network)
|
|
31
|
+
- `.env` file with all required keys
|
|
32
|
+
|
|
33
|
+
### First-Time Setup
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 1. Clone the repo
|
|
37
|
+
git clone https://github.com/Bigabou007-dev/warimcp.git
|
|
38
|
+
cd warimcp
|
|
39
|
+
|
|
40
|
+
# 2. Configure environment
|
|
41
|
+
cp .env.example .env
|
|
42
|
+
nano .env # fill in API keys
|
|
43
|
+
|
|
44
|
+
# 3. Add DB password to .env
|
|
45
|
+
echo "DB_PASSWORD=$(openssl rand -hex 16)" >> .env
|
|
46
|
+
|
|
47
|
+
# 4. Start services
|
|
48
|
+
docker compose up -d
|
|
49
|
+
|
|
50
|
+
# 5. Verify
|
|
51
|
+
docker compose logs -f warimcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### NPM Proxy Manager Route
|
|
55
|
+
- Container name: `warimcp`
|
|
56
|
+
- Internal port: `3000`
|
|
57
|
+
- Route via NPM — do NOT expose this port publicly
|
|
58
|
+
|
|
59
|
+
## Maintenance
|
|
60
|
+
|
|
61
|
+
### Logs
|
|
62
|
+
```bash
|
|
63
|
+
docker compose logs -f warimcp
|
|
64
|
+
docker compose logs -f warimcp_db
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Updates
|
|
68
|
+
```bash
|
|
69
|
+
git pull
|
|
70
|
+
docker compose up -d --build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Restart
|
|
74
|
+
```bash
|
|
75
|
+
docker compose restart warimcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Security
|
|
79
|
+
- Service runs as non-root (`USER node`)
|
|
80
|
+
- Zero public ports — all traffic via NPM internal network
|
|
81
|
+
- API keys stored in `.env` only, never hardcoded
|
|
82
|
+
- DB credentials auto-generated at setup
|
package/backup.sh
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# WariMCP — Backup Script
|
|
4
|
+
# Usage: ./backup.sh [--dry-run]
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
DRY_RUN=false
|
|
9
|
+
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
|
10
|
+
|
|
11
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
12
|
+
BACKUP_DIR="/tmp/warimcp_backup_${TIMESTAMP}"
|
|
13
|
+
ARCHIVE="/tmp/warimcp_${TIMESTAMP}.tar.gz"
|
|
14
|
+
REMOTE="gdrive:vps-backups/warimcp"
|
|
15
|
+
|
|
16
|
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
|
17
|
+
|
|
18
|
+
log "WariMCP backup starting — dry-run: $DRY_RUN"
|
|
19
|
+
|
|
20
|
+
mkdir -p "$BACKUP_DIR"
|
|
21
|
+
|
|
22
|
+
log "Dumping database..."
|
|
23
|
+
if $DRY_RUN; then
|
|
24
|
+
log "[DRY-RUN] Would run: docker compose exec warimcp_db pg_dump -U warimcp warimcp"
|
|
25
|
+
else
|
|
26
|
+
docker compose -f "$(dirname "$0")/docker-compose.yml" exec -T warimcp_db \
|
|
27
|
+
pg_dump -U warimcp warimcp > "${BACKUP_DIR}/warimcp_db_${TIMESTAMP}.sql"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
log "Archiving code..."
|
|
31
|
+
if $DRY_RUN; then
|
|
32
|
+
log "[DRY-RUN] Would archive: $(dirname "$0") -> $ARCHIVE"
|
|
33
|
+
else
|
|
34
|
+
tar -czf "$ARCHIVE" \
|
|
35
|
+
--exclude=".env" \
|
|
36
|
+
--exclude="node_modules" \
|
|
37
|
+
--exclude=".git" \
|
|
38
|
+
-C "$(dirname "$(realpath "$0")")" . \
|
|
39
|
+
-C "$BACKUP_DIR" .
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
log "Syncing to remote..."
|
|
43
|
+
if $DRY_RUN; then
|
|
44
|
+
log "[DRY-RUN] Would run: rclone copy $ARCHIVE $REMOTE/"
|
|
45
|
+
else
|
|
46
|
+
rclone copy "$ARCHIVE" "$REMOTE/" --progress
|
|
47
|
+
rclone copy "${BACKUP_DIR}/warimcp_db_${TIMESTAMP}.sql" "$REMOTE/" --progress
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
log "Cleaning up temp files..."
|
|
51
|
+
if ! $DRY_RUN; then
|
|
52
|
+
rm -rf "$BACKUP_DIR" "$ARCHIVE"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
log "Backup complete."
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
version: "3.9"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
warimcp:
|
|
5
|
+
build: .
|
|
6
|
+
container_name: warimcp
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
env_file: .env
|
|
9
|
+
networks:
|
|
10
|
+
- npm_proxy
|
|
11
|
+
deploy:
|
|
12
|
+
resources:
|
|
13
|
+
limits:
|
|
14
|
+
cpus: "0.25"
|
|
15
|
+
memory: 256M
|
|
16
|
+
reservations:
|
|
17
|
+
cpus: "0.10"
|
|
18
|
+
memory: 128M
|
|
19
|
+
depends_on:
|
|
20
|
+
- warimcp_db
|
|
21
|
+
|
|
22
|
+
warimcp_db:
|
|
23
|
+
image: postgres:16-alpine
|
|
24
|
+
container_name: warimcp_db
|
|
25
|
+
restart: unless-stopped
|
|
26
|
+
environment:
|
|
27
|
+
POSTGRES_USER: warimcp
|
|
28
|
+
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
29
|
+
POSTGRES_DB: warimcp
|
|
30
|
+
volumes:
|
|
31
|
+
- warimcp_db_data:/var/lib/postgresql/data
|
|
32
|
+
networks:
|
|
33
|
+
- npm_proxy
|
|
34
|
+
deploy:
|
|
35
|
+
resources:
|
|
36
|
+
limits:
|
|
37
|
+
cpus: "0.25"
|
|
38
|
+
memory: 256M
|
|
39
|
+
|
|
40
|
+
volumes:
|
|
41
|
+
warimcp_db_data:
|
|
42
|
+
|
|
43
|
+
networks:
|
|
44
|
+
npm_proxy:
|
|
45
|
+
external: true
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "warimcp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "WariMCP — AI-native payment MCP server for West Africa. Unified gateway for CinetPay, Wave, Hub2/Ecobank, and PAPSS payment rails.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/index.js",
|
|
8
|
+
"dev": "node --watch src/index.js",
|
|
9
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"payments",
|
|
14
|
+
"west-africa",
|
|
15
|
+
"cinetpay",
|
|
16
|
+
"wave",
|
|
17
|
+
"fintech",
|
|
18
|
+
"africa",
|
|
19
|
+
"nodejs",
|
|
20
|
+
"uemoa",
|
|
21
|
+
"papss"
|
|
22
|
+
],
|
|
23
|
+
"author": "Bigabou",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/Bigabou007-dev/warimcp.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/Bigabou007-dev/warimcp#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Bigabou007-dev/warimcp/issues"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"axios": "^1.7.0",
|
|
38
|
+
"dotenv": "^16.4.0",
|
|
39
|
+
"express": "^4.18.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require("dotenv").config();
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const { tools } = require("./tools");
|
|
4
|
+
const { processToolCall } = require("./router");
|
|
5
|
+
const { logQueue } = require("./queue");
|
|
6
|
+
const { handleWebhook } = require("./webhook");
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
app.use(express.json());
|
|
10
|
+
|
|
11
|
+
app.post("/mcp", async (req, res) => {
|
|
12
|
+
const { tool, input } = req.body;
|
|
13
|
+
if (!tool || !input) return res.status(400).json({ error: "tool and input required" });
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
logQueue("RECEIVED", tool, input);
|
|
17
|
+
const result = await processToolCall(tool, input);
|
|
18
|
+
logQueue("COMPLETED", tool, result);
|
|
19
|
+
res.json({ result });
|
|
20
|
+
} catch (err) {
|
|
21
|
+
logQueue("FAILED", tool, { error: err.message });
|
|
22
|
+
res.status(500).json({ error: err.message });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
app.get("/tools", (_req, res) => res.json({ tools }));
|
|
27
|
+
|
|
28
|
+
app.post("/webhook/:provider", handleWebhook);
|
|
29
|
+
|
|
30
|
+
app.get("/health", (_req, res) => res.json({ status: "ok", ts: new Date().toISOString() }));
|
|
31
|
+
|
|
32
|
+
const PORT = process.env.WARIMCP_PORT || 3000;
|
|
33
|
+
app.listen(PORT, () => console.log(`WariMCP listening on port ${PORT}`));
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://api-checkout.cinetpay.com/v2";
|
|
4
|
+
|
|
5
|
+
function headers() {
|
|
6
|
+
return { "Content-Type": "application/json" };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function credentials() {
|
|
10
|
+
return {
|
|
11
|
+
apikey: process.env.CINETPAY_API_KEY,
|
|
12
|
+
site_id: process.env.CINETPAY_SITE_ID
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function initiatePayment({ amount, currency, customer, description, return_url, metadata }) {
|
|
17
|
+
const transaction_id = `CP_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
|
|
18
|
+
|
|
19
|
+
const payload = {
|
|
20
|
+
...credentials(),
|
|
21
|
+
transaction_id,
|
|
22
|
+
amount,
|
|
23
|
+
currency: currency || "XOF",
|
|
24
|
+
description: description || "WariMCP Payment",
|
|
25
|
+
customer_name: customer.name,
|
|
26
|
+
customer_surname: "",
|
|
27
|
+
customer_email: customer.email || "",
|
|
28
|
+
customer_phone_number: customer.phone,
|
|
29
|
+
customer_address: "",
|
|
30
|
+
customer_city: "",
|
|
31
|
+
customer_country: "CI",
|
|
32
|
+
customer_state: "CI",
|
|
33
|
+
customer_zip_code: "00000",
|
|
34
|
+
return_url: return_url || "",
|
|
35
|
+
notify_url: `${process.env.WARIMCP_WEBHOOK_URL || ""}/webhook/cinetpay`,
|
|
36
|
+
channels: "ALL",
|
|
37
|
+
metadata: JSON.stringify(metadata || {})
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const { data } = await axios.post(`${BASE_URL}/payment`, payload, { headers: headers() });
|
|
41
|
+
return { transaction_id, ...data };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function checkStatus(transaction_id) {
|
|
45
|
+
const { data } = await axios.post(`${BASE_URL}/payment/check`, {
|
|
46
|
+
...credentials(),
|
|
47
|
+
transaction_id
|
|
48
|
+
}, { headers: headers() });
|
|
49
|
+
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function refund(transaction_id, amount, reason) {
|
|
54
|
+
throw new Error("CinetPay refunds require manual processing via CinetPay dashboard.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listTransactions({ limit = 20, offset = 0 } = {}) {
|
|
58
|
+
throw new Error("CinetPay list_transactions not yet implemented.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function generatePaymentLink({ amount, currency, description, expires_at, metadata }) {
|
|
62
|
+
const result = await initiatePayment({
|
|
63
|
+
amount,
|
|
64
|
+
currency,
|
|
65
|
+
customer: { name: "Customer", phone: "" },
|
|
66
|
+
description,
|
|
67
|
+
metadata
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { payment_url: result?.data?.payment_url, transaction_id: result.transaction_id };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { initiatePayment, checkStatus, refund, listTransactions, generatePaymentLink };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
async function initiatePayment(input) {
|
|
2
|
+
throw new Error("Hub2 provider coming in Phase 2.");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
async function checkStatus(transaction_id) {
|
|
6
|
+
throw new Error("Hub2 provider coming in Phase 2.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function refund(transaction_id, amount, reason) {
|
|
10
|
+
throw new Error("Hub2 provider coming in Phase 2.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function listTransactions(filters) {
|
|
14
|
+
throw new Error("Hub2 provider coming in Phase 2.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function generatePaymentLink(input) {
|
|
18
|
+
throw new Error("Hub2 provider coming in Phase 2.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { initiatePayment, checkStatus, refund, listTransactions, generatePaymentLink };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
async function initiatePayment(input) {
|
|
2
|
+
throw new Error("PAPSS provider coming in Phase 3.");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
async function checkStatus(transaction_id) {
|
|
6
|
+
throw new Error("PAPSS provider coming in Phase 3.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function refund(transaction_id, amount, reason) {
|
|
10
|
+
throw new Error("PAPSS provider coming in Phase 3.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function listTransactions(filters) {
|
|
14
|
+
throw new Error("PAPSS provider coming in Phase 3.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function generatePaymentLink(input) {
|
|
18
|
+
throw new Error("PAPSS provider coming in Phase 3.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { initiatePayment, checkStatus, refund, listTransactions, generatePaymentLink };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://api.wave.com/v1";
|
|
4
|
+
|
|
5
|
+
function headers() {
|
|
6
|
+
return {
|
|
7
|
+
"Authorization": `Bearer ${process.env.WAVE_API_KEY}`,
|
|
8
|
+
"Content-Type": "application/json"
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function initiatePayment({ amount, currency, customer, description, return_url, metadata }) {
|
|
13
|
+
const payload = {
|
|
14
|
+
currency: currency || "XOF",
|
|
15
|
+
amount: String(amount),
|
|
16
|
+
error_url: return_url || "",
|
|
17
|
+
success_url: return_url || "",
|
|
18
|
+
client_reference: `WAVE_${Date.now()}`
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const { data } = await axios.post(`${BASE_URL}/checkout/sessions`, payload, { headers: headers() });
|
|
22
|
+
return { transaction_id: data.id, payment_url: data.wave_launch_url, ...data };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function checkStatus(transaction_id) {
|
|
26
|
+
const { data } = await axios.get(`${BASE_URL}/checkout/sessions/${transaction_id}`, { headers: headers() });
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function refund(transaction_id, amount, reason) {
|
|
31
|
+
throw new Error("Wave refunds not yet implemented.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function listTransactions({ limit = 20, offset = 0 } = {}) {
|
|
35
|
+
throw new Error("Wave list_transactions not yet implemented.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function generatePaymentLink({ amount, currency, description, metadata }) {
|
|
39
|
+
return initiatePayment({ amount, currency, customer: { name: "Customer", phone: "" }, description, metadata });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { initiatePayment, checkStatus, refund, listTransactions, generatePaymentLink };
|
package/src/queue.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const LOG_FILE = path.join(__dirname, "../logs/queue.log");
|
|
5
|
+
|
|
6
|
+
function logQueue(status, tool, data) {
|
|
7
|
+
const entry = JSON.stringify({
|
|
8
|
+
ts: new Date().toISOString(),
|
|
9
|
+
status,
|
|
10
|
+
tool,
|
|
11
|
+
data
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
fs.appendFileSync(LOG_FILE, entry + "\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readQueue() {
|
|
18
|
+
if (!fs.existsSync(LOG_FILE)) return [];
|
|
19
|
+
|
|
20
|
+
return fs.readFileSync(LOG_FILE, "utf8")
|
|
21
|
+
.split("\n")
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map(line => {
|
|
24
|
+
try { return JSON.parse(line); }
|
|
25
|
+
catch { return null; }
|
|
26
|
+
})
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getPending() {
|
|
31
|
+
const entries = readQueue();
|
|
32
|
+
const pending = new Map();
|
|
33
|
+
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (entry.status === "RECEIVED") pending.set(entry.data?.transaction_id, entry);
|
|
36
|
+
if (entry.status === "COMPLETED" || entry.status === "FAILED") pending.delete(entry.data?.transaction_id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [...pending.values()];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { logQueue, readQueue, getPending };
|
package/src/router.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const cinetpay = require("./providers/cinetpay");
|
|
2
|
+
const wave = require("./providers/wave");
|
|
3
|
+
const hub2 = require("./providers/hub2");
|
|
4
|
+
const papss = require("./providers/papss");
|
|
5
|
+
|
|
6
|
+
const providers = { cinetpay, wave, hub2, papss };
|
|
7
|
+
|
|
8
|
+
async function processToolCall(tool, input) {
|
|
9
|
+
const provider = input.provider || "cinetpay";
|
|
10
|
+
|
|
11
|
+
if (!providers[provider]) {
|
|
12
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch (tool) {
|
|
16
|
+
case "initiate_payment":
|
|
17
|
+
return providers[provider].initiatePayment(input);
|
|
18
|
+
case "check_status":
|
|
19
|
+
return providers[provider].checkStatus(input.transaction_id);
|
|
20
|
+
case "refund":
|
|
21
|
+
return providers[provider].refund(input.transaction_id, input.amount, input.reason);
|
|
22
|
+
case "list_transactions":
|
|
23
|
+
return providers[provider].listTransactions(input);
|
|
24
|
+
case "generate_payment_link":
|
|
25
|
+
return providers[provider].generatePaymentLink(input);
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unknown tool: ${tool}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { processToolCall };
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const tools = [
|
|
2
|
+
{
|
|
3
|
+
name: "initiate_payment",
|
|
4
|
+
description: "Start a payment transaction via a supported West African payment rail.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
required: ["provider", "amount", "currency", "customer"],
|
|
8
|
+
properties: {
|
|
9
|
+
provider: { type: "string", enum: ["cinetpay", "wave", "hub2", "papss"] },
|
|
10
|
+
amount: { type: "number", description: "Amount in smallest currency unit" },
|
|
11
|
+
currency: { type: "string", description: "ISO 4217 currency code, e.g. XOF, CFA" },
|
|
12
|
+
customer: {
|
|
13
|
+
type: "object",
|
|
14
|
+
required: ["name", "phone"],
|
|
15
|
+
properties: {
|
|
16
|
+
name: { type: "string" },
|
|
17
|
+
phone: { type: "string" },
|
|
18
|
+
email: { type: "string" }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
description: { type: "string" },
|
|
22
|
+
return_url: { type: "string" },
|
|
23
|
+
metadata: { type: "object" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "check_status",
|
|
29
|
+
description: "Check the status of an existing payment transaction.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
required: ["transaction_id", "provider"],
|
|
33
|
+
properties: {
|
|
34
|
+
transaction_id: { type: "string" },
|
|
35
|
+
provider: { type: "string", enum: ["cinetpay", "wave", "hub2", "papss"] }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "refund",
|
|
41
|
+
description: "Issue a full or partial refund for a completed transaction.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
required: ["transaction_id", "provider"],
|
|
45
|
+
properties: {
|
|
46
|
+
transaction_id: { type: "string" },
|
|
47
|
+
provider: { type: "string", enum: ["cinetpay", "wave", "hub2", "papss"] },
|
|
48
|
+
amount: { type: "number", description: "Partial refund amount. Omit for full refund." },
|
|
49
|
+
reason: { type: "string" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "list_transactions",
|
|
55
|
+
description: "List recent transactions with optional filters.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
provider: { type: "string", enum: ["cinetpay", "wave", "hub2", "papss", "all"] },
|
|
60
|
+
status: { type: "string", enum: ["pending", "completed", "failed", "refunded"] },
|
|
61
|
+
limit: { type: "number", default: 20 },
|
|
62
|
+
offset: { type: "number", default: 0 }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "generate_payment_link",
|
|
68
|
+
description: "Generate a shareable payment link for a customer.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
required: ["provider", "amount", "currency"],
|
|
72
|
+
properties: {
|
|
73
|
+
provider: { type: "string", enum: ["cinetpay", "wave", "hub2"] },
|
|
74
|
+
amount: { type: "number" },
|
|
75
|
+
currency: { type: "string" },
|
|
76
|
+
description: { type: "string" },
|
|
77
|
+
expires_at: { type: "string", description: "ISO 8601 expiry datetime" },
|
|
78
|
+
metadata: { type: "object" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
module.exports = { tools };
|
package/src/webhook.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { logQueue } = require("./queue");
|
|
2
|
+
|
|
3
|
+
async function handleWebhook(req, res) {
|
|
4
|
+
const { provider } = req.params;
|
|
5
|
+
const payload = req.body;
|
|
6
|
+
|
|
7
|
+
logQueue("WEBHOOK", provider, payload);
|
|
8
|
+
|
|
9
|
+
res.json({ received: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { handleWebhook };
|