MCP Server¶
The InstaPayBill API exposes a Model Context Protocol server so AI agents can query platform data directly — wallet balances, transactions, plans, billers and customers — without hand-rolling HTTP calls.
The MCP server is mounted on the same Express application as the REST API,
at https://api.instapaybill.com/mcp.
Transport¶
StreamableHTTPServerTransport, configured for stateless JSON responses:
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // stateless
enableJsonResponse: true, // JSON, not SSE
});
No session id is issued and no SSE stream is kept open, so the server scales horizontally without sticky sessions.
The server rewrites the Accept header if needed, so clients that only send
one of application/json or text/event-stream still work:
if (!hasJson || !hasSse) {
req.headers.accept = [accept, 'application/json', 'text/event-stream']
.filter(Boolean).join(', ');
}
The bundled MCP README is out of date
mcp/README.md and mcp/SUMMARY.md describe an older SSE design
(GET /mcp/sse, POST /mcp/message) with 22 tools in 7 domain files.
The live implementation is mcp/index.js with Streamable HTTP on
app.all('/mcp') and 50 tools in 50 files. Use mcp/index.js as the
source of truth.
Authentication¶
Two layers.
Layer 1 — X-API-Key¶
Every non-GET request must carry the shared service key:
GET requests pass through unauthenticated — that is what makes
/mcp/health and /mcp/tools publicly readable.
A missing or wrong key returns 401:
Layer 2 — a user Bearer token for tools/call¶
tools/call requests additionally require a user-scoped JWT:
Without it:
The token is verified with HS256 against AGENT_TOKEN_SECRET (falling back to
ACCESS_TOKEN_SECRET), with issuer AGENT_TOKEN_ISSUER (default
api.instapaybill.com). It must carry a string sub.
Issuing a user token¶
Exchange the service key and a customer email for a user access token.
curl -X POST https://api.instapaybill.com/api/v1/agent/token \
-H "Content-Type: application/json" \
-d '{
"service_key": "<MCP_API_KEY>",
"email": "[email protected]"
}'
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
The token payload:
{
"sub": "<userId>",
"email": "[email protected]",
"role": "user",
"isAdmin": false,
"type": "mcp-user-access"
}
| Failure | Response |
|---|---|
Missing service_key or email |
300 — "service_key and email are required" |
Wrong service_key |
401 — "Unauthorized: invalid service key" |
| Unknown email | 404 — "User not found" |
Tokens expire after AGENT_TOKEN_EXPIRES_IN_SECONDS (default 3600 seconds).
Discovery¶
| Method | Path | Auth | Returns |
|---|---|---|---|
GET |
/mcp/health |
None | { status, service, version, totalTools, tools } |
GET |
/mcp/tools |
None | [{ name, description }] |
{
"status": "ok",
"service": "utilities-mcp-server",
"version": "1.0.0",
"totalTools": 50,
"tools": [{ "name": "get_wallet_balance", "description": "…" }]
}
Calling the server¶
curl -X POST https://api.instapaybill.com/mcp \
-H "X-API-Key: <MCP_API_KEY>" \
-H "Authorization: Bearer <user_access_token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_wallet_balance",
"arguments": { "userId": "<userId>" }
}
}'
The response is JSON-RPC with the tool result as a single text content block:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "{\n \"success\": true,\n \"data\": { … }\n}" }
]
}
}
The tool result is JSON-stringified inside the text block, so a client must parse the text to get structured data.
Tool catalogue¶
50 tools, each a separate class in mcp/tools/. The registered name is
declared in the file and is what you pass as params.name.
Wallet¶
| Tool | Purpose |
|---|---|
get_wallet_balance |
All wallet balances for a user |
get_wallet_data |
The wallet record |
get_wallet_history |
Wallet ledger entries |
get_wallet_user |
The wallet's owner |
get_wallet_stat |
Wallet statistics |
get_wallet_bonus_history |
Bonus history |
get_wallet_funding |
Funding history |
get_wallet_manual_history |
Manual funding history |
Transactions¶
| Tool | Purpose |
|---|---|
get_transactions |
Transaction list with filters |
get_transaction_details |
A single transaction |
get_transaction_by_reference |
Look up by reference |
get_transactions_stat |
Transaction analytics |
find_transactions_stat |
Search transaction stats |
get_data_transactions |
Data transactions |
get_key_patron_transactions_stat |
High-value customer stats |
Profit and analytics¶
| Tool | Purpose |
|---|---|
get_transactions_profit_stat |
Profit statistics |
get_transaction_profit_comparison |
Profit comparison |
get_transaction_analytics_by_product |
Per-product analytics |
get_manual_approval |
Manual approval queue |
Users¶
| Tool | Purpose |
|---|---|
get_user_details |
A user's profile |
get_users |
User list |
get_users_by_email |
Look up by email |
get_user_total_amount |
Total transacted per user |
get_referrals |
A user's referrals |
get_user_context |
A user's stored context |
get_user_contexts_batch |
Context for many users |
Data plans and catalogue¶
| Tool | Purpose |
|---|---|
get_normal_data_plans |
Active data plans |
get_transformed_data_plans |
Data plans in agent shape |
get_data_plans_by_network |
Plans for one network |
get_available_networks |
Networks with active plans |
get_cables_plan |
Cable plans |
get_cables_type |
Cable types |
get_e_lectricity_disco |
Electricity discos |
get_education_plans |
Education exam plans |
get_category |
Product categories |
get_biller |
Biller records |
get_billers_balance |
Biller balances |
Validation¶
| Tool | Purpose |
|---|---|
validate_cables_i_u_c |
Validate a smart card |
validate_e_lectricity_meter_no |
Validate a meter number |
Notifications¶
| Tool | Purpose |
|---|---|
get_all_notifications |
User notifications |
get_admin_notifications |
Admin notifications |
get_user_notification_admin |
Notification admin view |
Automation and upgrades¶
| Tool | Purpose |
|---|---|
get_auto_buy_list |
Scheduled auto-buy jobs |
get_become_an_agent_top_user |
Top agent-approval candidates |
get_upgrade_details |
Agent upgrade request details |
Miscellaneous¶
| Tool | Purpose |
|---|---|
get_settings |
Platform settings |
get_gateways |
Payment gateways |
get_card_saved |
Saved cards |
get_active_rooms |
Active voice-assistant rooms |
get_room_details |
A single room |
Three tool names are misspelled — use them verbatim
The names contain a stray capital E from the original class names:
get_e_lectricity_disco(notget_electricity_disco)validate_e_lectricity_meter_no(notvalidate_electricity_meter_no)validate_cables_i_u_c(notvalidate_cables_iuc)
These are the registered names. A tools/call with the "corrected" name
fails with an unknown-tool error.
How tools execute¶
Each tool calls a service function directly, not the HTTP endpoint. It synthesises a request/response pair:
const mockReq = { query: { … }, user: { isAdmin: true, _id: 'admin' }, userAuth: 'admin' };
const mockRes = { status: (code) => ({ json: (data) => { … } }), json: (data) => { … } };
await serviceFn(mockReq, mockRes);
The MCP surface is fully privileged
Every tool executes with isAdmin: true and userAuth: 'admin'. There is
no per-tool permission check and no scoping to the token's sub.
The X-API-Key is the real control point. Anyone holding it can read any
user's data through these tools. Treat it as a production secret, issue
user tokens only for accounts you intend to expose, and never ship the
service key to a client.
Note that the user token identifies who is asking but does not limit what they can read.
Tool schemas¶
Each tool declares a Zod schema, which becomes the MCP inputSchema. Two
schema styles are used:
- Object schemas — most tools. Arguments are a named object.
- String schemas — a few tools take a bare string argument.
The Accept header must include both application/json and
text/event-stream for strict MCP clients; the server normalises it either
way.
Errors¶
The MCP layer uses its own error shape, not the REST envelope:
A tool that throws returns a JSON-RPC error object.
Related¶
- Authentication — the REST credential model for comparison