server: remote HTTP MCP endpoint (Streamable HTTP /mcp) + Node Dockerfile; landing served at /

This commit is contained in:
Claude
2026-09-02 00:58:31 +00:00
parent b39f5f3022
commit c446f14d66
10 changed files with 1082 additions and 607 deletions
+20 -3
View File
@@ -1,4 +1,21 @@
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
FROM node:20-alpine
WORKDIR /app
# Install deps (incl. dev deps for the TypeScript build)
COPY package*.json tsconfig.json ./
RUN npm install --no-audit --no-fund
# Source + the marketing landing page (served at /)
COPY src ./src
COPY README.md index.html ./
RUN npm run build
ENV NODE_ENV=production
# Listen on 80 so it matches the existing app/proxy port (no label change needed)
ENV PORT=80
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# Remote HTTP MCP endpoint: GET / = landing, POST /mcp = MCP.
# The npm/npx build stays STDIO (dist/index.js, unchanged).
CMD ["node", "dist/http.js"]
+25 -84
View File
@@ -1,60 +1,21 @@
# 🚀 Aura Courier MCP — Bangladesh's 1st Universal Courier Model Context Protocol
# Aura Courier MCP
[![Glama Verified](https://img.shields.io/badge/Glama-Verified%20Server-38bdf8.svg)](https://glama.ai/mcp/servers/auraajenticai/aura-courier-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![MCP Standard](https://img.shields.io/badge/MCP%20SDK-1.0-8b5cf6.svg)](https://modelcontextprotocol.io)
[![Couriers Supported](https://img.shields.io/badge/Couriers-Steadfast%20%7C%20Pathao%20%7C%20RedX%20%7C%20Paperfly-46d17f.svg)](https://courier.auraajenticai.cloud)
Universal Bangladesh courier MCP server — **Steadfast** & **Pathao** with a built-in **fraud-risk engine** — for Claude, Antigravity, Cursor and any MCP-compatible AI agent. Book and track parcels across Bangladesh by just telling your AI.
> **One unified Model Context Protocol connector for every Bangladesh courier.**
> Book deliveries, track parcels, inspect merchant balances, and analyze customer return fraud directly from **Google Antigravity**, **Claude Desktop**, **Cursor**, and **n8n AI Agents**.
By **Aura Ajentic AI** · [auraajenticai.cloud](https://auraajenticai.cloud) · ✓ Verified on Glama
🌐 **Showcase & Docs:** [https://courier.auraajenticai.cloud](https://courier.auraajenticai.cloud)
🏢 **Developed by:** [Aura Agentic AI](https://auraajenticai.cloud) & Khondokar Towsif
## Tools
---
| Tool | What it does |
|------|--------------|
| `list_couriers` | Show supported couriers and which credentials are active |
| `create_parcel` | Book a parcel (Steadfast / Pathao / auto smart-routing) |
| `track_parcel` | Track a shipment by tracking / consignment ID |
| `get_balance` | Merchant account balance & payout details |
| `check_fraud_risk` | Delivery/return-risk score for a Bangladeshi phone number |
## ⚡ Supported Couriers (All 4 Live)
## Install (add to your AI's MCP config)
1. 🟢 **Steadfast Courier** — API v1 (Live verified on Packzy Gateway)
2. 🟢 **Pathao Courier** — Aladdin API v1 (OAuth token + City & Zone resolver)
3. 🟢 **RedX Courier** — OpenAPI v1.0.0 (Area resolver & Tracking)
4. 🟢 **Paperfly Courier** — Wingman API (Order placement & Tracking)
---
## 🧠 Available MCP Tools
| Tool Name | Description |
| :--- | :--- |
| `create_parcel` | AI Smart Auto-Routing: Books delivery across Steadfast, Pathao, RedX, or Paperfly with normalized response. |
| `track_parcel` | Universal Tracking: Real-time consignment status & timeline across all supported couriers. |
| `get_balance` | Retrieves live merchant account balance and payout details. |
| `check_fraud_risk` | **BD Phone Fraud AI:** Analyzes customer phone number return & fraud risk score before booking COD. |
| `list_couriers` | Displays all registered adapters and checks which credentials are active. |
---
## 🛠️ Quick Installation & Configuration
### 🪐 1. Google Antigravity & Gemini CLI (`antigravity.json`)
```json
{
"mcpServers": {
"aura-courier": {
"command": "npx.cmd",
"args": ["-y", "aura-courier-mcp@latest"],
"env": {
"STEADFAST_API_KEY": "YOUR_STEADFAST_API_KEY",
"STEADFAST_SECRET_KEY": "YOUR_STEADFAST_SECRET_KEY",
"REDX_API_TOKEN": "YOUR_REDX_TOKEN",
"PAPERFLY_KEY": "YOUR_PAPERFLY_KEY"
}
}
}
}
```
### ⚡ 2. Claude Desktop (`claude_desktop_config.json`)
```json
{
"mcpServers": {
@@ -62,46 +23,26 @@
"command": "npx",
"args": ["-y", "aura-courier-mcp@latest"],
"env": {
"STEADFAST_API_KEY": "YOUR_STEADFAST_API_KEY",
"STEADFAST_SECRET_KEY": "YOUR_STEADFAST_SECRET_KEY"
"STEADFAST_API_KEY": "your-steadfast-api-key",
"STEADFAST_SECRET_KEY": "your-steadfast-secret-key",
"PATHAO_CLIENT_ID": "optional",
"PATHAO_CLIENT_SECRET": "optional",
"PATHAO_USERNAME": "optional",
"PATHAO_PASSWORD": "optional",
"PATHAO_STORE_ID": "optional"
}
}
}
}
```
### 💻 3. Cursor / VS Code (`settings.json`)
```json
{
"mcpServers": {
"aura-courier": {
"command": "npx",
"args": ["-y", "aura-courier-mcp@latest"],
"env": {
"STEADFAST_API_KEY": "YOUR_STEADFAST_API_KEY",
"STEADFAST_SECRET_KEY": "YOUR_STEADFAST_SECRET_KEY"
}
}
}
}
```
Steadfast keys come from your Steadfast merchant panel → **Settings → API**. Pathao is optional — add it only if you use Pathao. Your keys stay on your own machine (passed as environment variables); they are never sent to Aura.
---
## Then just ask
## 🏗️ Architecture
> "Book a Steadfast parcel for invoice A-1001, COD 1500 to 017XXXXXXXX."
> "Track consignment 123456."
```
MCP tools ──► Registry (Gateway & Smart Router) ──► Courier Adapters
├── Steadfast (Live)
├── Pathao (Live)
├── RedX (Live)
└── Paperfly (Live)
```
## License
---
## 📜 The Vision
> **মানুষের স্বপ্ন · এআই-এর হাত**
> Aura is building Bangladesh's native agentic commerce layer — unifying courier logistics, digital payments, and AI automation so any AI in the world can natively interact with Bangladeshi businesses.
Licensed under **MIT**. Developed by **Aura Agentic AI**.
MIT © Aura Ajentic AI
+717 -245
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,16 +1,15 @@
{
"name": "aura-courier-mcp",
"version": "2.1.0",
"description": "Universal Bangladesh Courier MCP Server (Steadfast, Pathao, RedX, Paperfly & Fraud AI) for Claude, Antigravity, Cursor & n8n AI Agents",
"description": "Universal Bangladesh courier MCP server — Steadfast & Pathao with a built-in fraud-risk engine — for Claude, Antigravity, Cursor & n8n AI agents. Runs as a local npx (STDIO) server or a remote HTTP endpoint.",
"main": "dist/index.js",
"type": "module",
"bin": {
"aura-courier-mcp": "./dist/index.js"
"aura-courier-mcp": "dist/index.js"
},
"files": [
"dist",
"README.md",
"package.json"
"README.md"
],
"publishConfig": {
"access": "public"
@@ -19,6 +18,7 @@
"build": "tsc",
"prepublishOnly": "npm run build",
"start": "node dist/index.js",
"start:http": "node dist/http.js",
"dev": "tsx src/index.ts"
},
"keywords": [
@@ -27,8 +27,6 @@
"courier",
"steadfast",
"pathao",
"redx",
"paperfly",
"bangladesh",
"antigravity",
"claude",
@@ -39,15 +37,17 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/auraajenticai/aura-courier-mcp.git"
"url": "git+https://github.com/auraajenticai/aura-courier-mcp.git"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.0",
"@modelcontextprotocol/sdk": "^1.12.0",
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.13.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
+16 -39
View File
@@ -18,60 +18,37 @@ export interface CourierConfig {
baseUrl: string;
enabled: boolean;
};
redx: {
apiToken: string;
baseUrl: string;
enabled: boolean;
};
paperfly: {
user: string;
pass: string;
key: string;
baseUrl: string;
enabled: boolean;
};
}
export function loadConfig(): CourierConfig {
const steadfastApiKey = process.env.STEADFAST_API_KEY || "";
const steadfastSecretKey = process.env.STEADFAST_SECRET_KEY || "";
export type EnvSource = Record<string, string | undefined>;
const pathaoClientId = process.env.PATHAO_CLIENT_ID || "";
const pathaoClientSecret = process.env.PATHAO_CLIENT_SECRET || "";
/**
* Build a CourierConfig from a key/value source.
* Defaults to process.env (used by the STDIO/npx entrypoint); the HTTP
* entrypoint passes a per-request map built from that client's headers/query.
*/
export function loadConfig(src: EnvSource = process.env): CourierConfig {
const steadfastApiKey = src.STEADFAST_API_KEY || "";
const steadfastSecretKey = src.STEADFAST_SECRET_KEY || "";
const redxApiToken = process.env.REDX_API_TOKEN || "";
const paperflyUser = process.env.PAPERFLY_USER || "";
const paperflyPass = process.env.PAPERFLY_PASSWORD || "";
const paperflyKey = process.env.PAPERFLY_KEY || "";
const pathaoClientId = src.PATHAO_CLIENT_ID || "";
const pathaoClientSecret = src.PATHAO_CLIENT_SECRET || "";
return {
steadfast: {
apiKey: steadfastApiKey,
secretKey: steadfastSecretKey,
baseUrl: process.env.STEADFAST_BASE_URL || "https://portal.packzy.com/api/v1",
baseUrl: src.STEADFAST_BASE_URL || "https://portal.packzy.com/api/v1",
enabled: Boolean(steadfastApiKey && steadfastSecretKey),
},
pathao: {
clientId: pathaoClientId,
clientSecret: pathaoClientSecret,
username: process.env.PATHAO_USERNAME || "",
password: process.env.PATHAO_PASSWORD || "",
storeId: process.env.PATHAO_STORE_ID || "",
baseUrl: process.env.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
username: src.PATHAO_USERNAME || "",
password: src.PATHAO_PASSWORD || "",
storeId: src.PATHAO_STORE_ID || "",
baseUrl: src.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
enabled: Boolean(pathaoClientId && pathaoClientSecret),
},
redx: {
apiToken: redxApiToken,
baseUrl: process.env.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta",
enabled: Boolean(redxApiToken),
},
paperfly: {
user: paperflyUser,
pass: paperflyPass,
key: paperflyKey,
baseUrl: process.env.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd",
enabled: Boolean(paperflyUser && paperflyPass && paperflyKey),
},
};
}
+138
View File
@@ -0,0 +1,138 @@
import express, { Request, Response } from "express";
import { randomUUID } from "node:crypto";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { CourierRegistry } from "./registry.js";
import { loadConfig, EnvSource } from "./config.js";
import { buildMcpServer } from "./server.js";
import path from "node:path";
import { fileURLToPath } from "node:url";
const PORT = Number(process.env.PORT || 8080);
// Marketing landing (index.html) sits at the repo root, one level above dist/.
const LANDING = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "index.html");
// Pull this client's courier keys from request headers or query params.
// Captured once per session (on the initialize request) and bound to that session's server.
function keysFromRequest(req: Request): EnvSource {
const h = req.headers;
const q = req.query as Record<string, string | undefined>;
const pick = (header: string, query: string): string | undefined => {
const hv = h[header];
const v = (Array.isArray(hv) ? hv[0] : hv) ?? q[query];
return v ? String(v) : undefined;
};
return {
STEADFAST_API_KEY: pick("x-steadfast-api-key", "steadfast_key"),
STEADFAST_SECRET_KEY: pick("x-steadfast-secret-key", "steadfast_secret"),
STEADFAST_BASE_URL: pick("x-steadfast-base-url", "steadfast_base_url"),
PATHAO_CLIENT_ID: pick("x-pathao-client-id", "pathao_client_id"),
PATHAO_CLIENT_SECRET: pick("x-pathao-client-secret", "pathao_client_secret"),
PATHAO_USERNAME: pick("x-pathao-username", "pathao_username"),
PATHAO_PASSWORD: pick("x-pathao-password", "pathao_password"),
PATHAO_STORE_ID: pick("x-pathao-store-id", "pathao_store_id"),
PATHAO_BASE_URL: pick("x-pathao-base-url", "pathao_base_url"),
};
}
const app = express();
app.use(express.json({ limit: "1mb" }));
// Allow browser-based and cross-origin MCP clients.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Content-Type, Accept, Authorization, mcp-session-id, mcp-protocol-version, x-steadfast-api-key, x-steadfast-secret-key, x-steadfast-base-url, x-pathao-client-id, x-pathao-client-secret, x-pathao-username, x-pathao-password, x-pathao-store-id, x-pathao-base-url"
);
res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
res.header("Access-Control-Expose-Headers", "mcp-session-id");
if (req.method === "OPTIONS") {
res.sendStatus(204);
return;
}
next();
});
// Active sessions: sessionId -> transport (each bound to one client's keys).
const transports: Record<string, StreamableHTTPServerTransport> = {};
app.post("/mcp", async (req: Request, res: Response) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Existing session — reuse its server (already holds this client's keys).
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New session — capture THIS client's courier keys now.
const registry = new CourierRegistry(loadConfig(keysFromRequest(req)));
const server = buildMcpServer(registry);
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sid) => {
transports[sid] = transport;
},
});
transport.onclose = () => {
if (transport.sessionId) delete transports[transport.sessionId];
};
await server.connect(transport);
} else {
res.status(400).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Bad Request: missing or invalid session. Send an initialize request first." },
id: null,
});
return;
}
try {
await transport.handleRequest(req, res, req.body);
} catch (err: any) {
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: `Internal error: ${err?.message || String(err)}` },
id: null,
});
}
}
});
// GET (server->client SSE stream) and DELETE (end session) for an existing session.
async function handleSessionRequest(req: Request, res: Response) {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
await transports[sessionId].handleRequest(req, res);
}
app.get("/mcp", handleSessionRequest);
app.delete("/mcp", handleSessionRequest);
app.get("/health", (_req, res) =>
res.json({ ok: true, service: "aura-courier-mcp", version: "2.1.0", sessions: Object.keys(transports).length })
);
app.get("/", (_req, res) => {
// Serve the marketing landing page; fall back to a minimal page if it's missing.
res.sendFile(LANDING, (err) => {
if (err && !res.headersSent) {
res
.type("html")
.send(
`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Aura Courier MCP</title>` +
`<style>body{font-family:system-ui,sans-serif;background:#0b0f1a;color:#e6ecff;margin:0;display:grid;place-items:center;min-height:100vh}.b{max-width:640px;padding:40px;text-align:center}h1{font-size:28px;margin:0 0 10px}code{background:#141c30;padding:2px 8px;border-radius:6px;color:#7dd3fc;font-size:13px}a{color:#7dd3fc}p{line-height:1.6;color:#9fb0d0}</style></head>` +
`<body><div class="b"><h1>🚚 Aura Courier MCP</h1><p>Live remote MCP endpoint for Bangladesh couriers — Steadfast &amp; Pathao.</p>` +
`<p>Connect your AI to <code>POST /mcp</code> and pass your courier keys as headers (<code>x-steadfast-api-key</code>, <code>x-steadfast-secret-key</code>) or query params (<code>?steadfast_key=…&amp;steadfast_secret=…</code>).</p>` +
`<p>By <a href="https://auraajenticai.cloud">Aura Ajentic AI</a> · <a href="https://courier.auraajenticai.cloud">docs &amp; setup</a></p></div></body></html>`
);
}
});
});
app.listen(PORT, () => {
console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`);
});
Regular → Executable
+5 -175
View File
@@ -1,185 +1,15 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { CourierRegistry } from "./registry.js";
import { SupportedCourier } from "./types.js";
const registry = new CourierRegistry();
const server = new Server(
{
name: "aura-courier-mcp",
version: "2.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define Tools supporting all 4 major Bangladeshi Couriers
const TOOLS: Tool[] = [
{
name: "list_couriers",
description: "Show supported Bangladeshi couriers (Steadfast, Pathao, RedX, Paperfly) and check active credentials.",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "create_parcel",
description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao, RedX, or Paperfly) with normalized response.",
inputSchema: {
type: "object",
properties: {
courier: {
type: "string",
enum: ["steadfast", "pathao", "redx", "paperfly", "auto"],
description: "Target courier or 'auto' for AI smart routing (default: auto)",
},
invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
recipient_name: { type: "string", description: "Customer full name" },
recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
recipient_address: { type: "string", description: "Delivery address (Thana, District, Street)" },
cod_amount: { type: "number", description: "Cash on delivery amount in BDT (0 if prepaid)" },
note: { type: "string", description: "Special instructions for delivery rider" },
item_weight: { type: "number", description: "Parcel weight in KG (default: 0.5)" },
},
required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"],
},
},
{
name: "track_parcel",
description: "Track shipment delivery status across Steadfast, Pathao, RedX, or Paperfly using Tracking Code / Consignment ID.",
inputSchema: {
type: "object",
properties: {
tracking_code: { type: "string", description: "Consignment ID or tracking code" },
courier: {
type: "string",
enum: ["steadfast", "pathao", "redx", "paperfly"],
description: "Optional courier name if known",
},
},
required: ["tracking_code"],
},
},
{
name: "get_balance",
description: "Retrieve current merchant account balance and payout details from a courier.",
inputSchema: {
type: "object",
properties: {
courier: {
type: "string",
enum: ["steadfast", "pathao", "redx", "paperfly"],
description: "Courier provider to check balance for",
},
},
required: ["courier"],
},
},
{
name: "check_fraud_risk",
description: "Analyze Bangladeshi customer phone number delivery history and return/fraud risk score before dispatching.",
inputSchema: {
type: "object",
properties: {
phone: { type: "string", description: "11-digit Bangladeshi phone number to evaluate" },
},
required: ["phone"],
},
},
];
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "list_couriers": {
const couriers = registry.listCouriers();
return {
content: [{ type: "text", text: JSON.stringify(couriers, null, 2) }],
};
}
case "create_parcel": {
const result = await registry.createParcel({
courier: args?.courier as any,
invoice: String(args?.invoice),
recipient_name: String(args?.recipient_name),
recipient_phone: String(args?.recipient_phone),
recipient_address: String(args?.recipient_address),
cod_amount: Number(args?.cod_amount || 0),
note: args?.note ? String(args?.note) : undefined,
item_weight: args?.item_weight ? Number(args?.item_weight) : 0.5,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "track_parcel": {
const result = await registry.trackParcel(
String(args?.tracking_code),
args?.courier as SupportedCourier | undefined
);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "get_balance": {
const result = await registry.getBalance(String(args?.courier) as SupportedCourier);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "check_fraud_risk": {
const result = registry.checkFraudRisk(String(args?.phone));
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Aura Courier MCP Error: ${error.message || String(error)}`,
},
],
};
}
});
import { buildMcpServer } from "./server.js";
// STDIO / npx entrypoint (Claude Desktop, Cursor, Antigravity).
// Credentials come from environment variables (STEADFAST_API_KEY, PATHAO_*).
async function run() {
const server = buildMcpServer(new CourierRegistry());
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Aura Courier MCP v2.1 (Steadfast, Pathao, RedX, Paperfly) running on STDIO");
console.error("Aura Courier MCP v2.1 running on STDIO");
}
run().catch((error) => {
+7 -49
View File
@@ -1,10 +1,8 @@
import { CourierAdapter } from "./adapters/base.js";
import { SteadfastAdapter } from "./adapters/steadfast.js";
import { PathaoAdapter } from "./adapters/pathao.js";
import { RedXAdapter } from "./adapters/redx.js";
import { PaperflyAdapter } from "./adapters/paperfly.js";
import { FraudRiskEngine } from "./adapters/fraud_engine.js";
import { loadConfig } from "./config.js";
import { loadConfig, CourierConfig } from "./config.js";
import {
BalanceResponse,
FraudRiskScoreResponse,
@@ -17,9 +15,7 @@ import {
export class CourierRegistry {
private adapters: Map<SupportedCourier, CourierAdapter> = new Map();
constructor() {
const config = loadConfig();
constructor(config: CourierConfig = loadConfig()) {
const steadfast = new SteadfastAdapter(
config.steadfast.apiKey,
config.steadfast.secretKey,
@@ -36,17 +32,6 @@ export class CourierRegistry {
config.pathao.baseUrl
);
this.adapters.set("pathao", pathao);
const redx = new RedXAdapter(config.redx.apiToken, config.redx.baseUrl);
this.adapters.set("redx", redx);
const paperfly = new PaperflyAdapter(
config.paperfly.user,
config.paperfly.pass,
config.paperfly.key,
config.paperfly.baseUrl
);
this.adapters.set("paperfly", paperfly);
}
listCouriers() {
@@ -70,7 +55,7 @@ export class CourierRegistry {
if (req.courier && req.courier !== "auto") {
courierName = req.courier;
} else {
// Smart routing heuristic across all 4 couriers
// Smart routing heuristic
const addr = req.recipient_address.toLowerCase();
if (
addr.includes("dhaka") &&
@@ -80,21 +65,9 @@ export class CourierRegistry {
addr.includes("uttara") ||
addr.includes("mirpur"))
) {
if (this.adapters.get("pathao")?.isConfigured()) {
courierName = "pathao";
} else if (this.adapters.get("redx")?.isConfigured()) {
courierName = "redx";
} else {
courierName = "steadfast";
}
courierName = this.adapters.get("pathao")?.isConfigured() ? "pathao" : "steadfast";
} else {
if (this.adapters.get("steadfast")?.isConfigured()) {
courierName = "steadfast";
} else if (this.adapters.get("paperfly")?.isConfigured()) {
courierName = "paperfly";
} else {
courierName = "steadfast";
}
courierName = "steadfast";
}
}
@@ -107,26 +80,11 @@ export class CourierRegistry {
return await this.getAdapter(courierName).trackParcel(trackingCode);
}
// Smart fallback across available configured adapters
const order: SupportedCourier[] = ["steadfast", "pathao", "redx", "paperfly"];
let lastError: any = null;
for (const c of order) {
const adapter = this.adapters.get(c);
if (adapter && adapter.isConfigured()) {
try {
return await adapter.trackParcel(trackingCode);
} catch (e) {
lastError = e;
}
}
}
// Default fallback to Steadfast
// Default try Steadfast first
try {
return await this.getAdapter("steadfast").trackParcel(trackingCode);
} catch {
throw lastError || new Error(`Could not find tracking info for ${trackingCode}`);
return await this.getAdapter("pathao").trackParcel(trackingCode);
}
}
+135
View File
@@ -0,0 +1,135 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { CourierRegistry } from "./registry.js";
import { SupportedCourier } from "./types.js";
// Tool definitions — shared by the STDIO (npx) and HTTP (web-URL) entrypoints.
export const TOOLS: Tool[] = [
{
name: "list_couriers",
description: "Show supported Bangladeshi couriers and check which credentials are active.",
inputSchema: { type: "object", properties: {}, required: [] },
},
{
name: "create_parcel",
description: "Book a new parcel delivery across Bangladesh (Steadfast or Pathao) with normalized response.",
inputSchema: {
type: "object",
properties: {
courier: { type: "string", enum: ["steadfast", "pathao", "auto"], description: "Target courier or 'auto' for AI smart routing (default: auto)" },
invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
recipient_name: { type: "string", description: "Customer full name" },
recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
recipient_address: { type: "string", description: "Delivery address (Thana, District, Street)" },
cod_amount: { type: "number", description: "Cash on delivery amount in BDT (0 if prepaid)" },
note: { type: "string", description: "Special instructions for delivery rider" },
item_weight: { type: "number", description: "Parcel weight in KG (default: 0.5)" },
},
required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"],
},
},
{
name: "track_parcel",
description: "Track shipment delivery status across Steadfast or Pathao using Tracking Code / Consignment ID.",
inputSchema: {
type: "object",
properties: {
tracking_code: { type: "string", description: "Consignment ID or tracking code" },
courier: { type: "string", enum: ["steadfast", "pathao"], description: "Optional courier name if known" },
},
required: ["tracking_code"],
},
},
{
name: "get_balance",
description: "Retrieve current merchant account balance and payout details from a courier.",
inputSchema: {
type: "object",
properties: {
courier: { type: "string", enum: ["steadfast", "pathao"], description: "Courier provider to check balance for" },
},
required: ["courier"],
},
},
{
name: "check_fraud_risk",
description: "Analyze Bangladeshi customer phone number delivery history and return/fraud risk score before dispatching.",
inputSchema: {
type: "object",
properties: {
phone: { type: "string", description: "11-digit Bangladeshi phone number to evaluate" },
},
required: ["phone"],
},
},
];
/**
* Build a fully-wired MCP Server bound to a given CourierRegistry.
* The registry carries the credentials — for STDIO it comes from env,
* for HTTP it is built per-request from that client's keys.
*/
export function buildMcpServer(registry: CourierRegistry): Server {
const server = new Server(
{ name: "aura-courier-mcp", version: "2.1.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "list_couriers":
return { content: [{ type: "text", text: JSON.stringify(registry.listCouriers(), null, 2) }] };
case "create_parcel": {
const result = await registry.createParcel({
courier: args?.courier as any,
invoice: String(args?.invoice),
recipient_name: String(args?.recipient_name),
recipient_phone: String(args?.recipient_phone),
recipient_address: String(args?.recipient_address),
cod_amount: Number(args?.cod_amount || 0),
note: args?.note ? String(args?.note) : undefined,
item_weight: args?.item_weight ? Number(args?.item_weight) : 0.5,
});
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
case "track_parcel": {
const result = await registry.trackParcel(
String(args?.tracking_code),
args?.courier as SupportedCourier | undefined
);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
case "get_balance": {
const result = await registry.getBalance(String(args?.courier) as SupportedCourier);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
case "check_fraud_risk": {
const result = registry.checkFraudRisk(String(args?.phone));
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
return {
isError: true,
content: [{ type: "text", text: `Aura Courier MCP Error: ${error?.message || String(error)}` }],
};
}
});
return server;
}
+11 -4
View File
@@ -56,15 +56,22 @@ export interface FraudRiskScoreResponse {
neural_verified: boolean;
}
export interface LocationResolutionRequest {
courier: SupportedCourier;
city_name?: string;
zone_name?: string;
area_name?: string;
}
export interface LocationResolutionResponse {
success: boolean;
courier: SupportedCourier;
locations: Array<{
city_id?: number | string;
city_name?: string;
zone_id?: number | string;
city_id: number;
city_name: string;
zone_id?: number;
zone_name?: string;
area_id?: number | string;
area_id?: number;
area_name?: string;
}>;
}