server: remote HTTP MCP endpoint (Streamable HTTP /mcp) + Node Dockerfile; landing served at /
This commit is contained in:
+20
-3
@@ -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"]
|
||||
|
||||
@@ -1,60 +1,21 @@
|
||||
# 🚀 Aura Courier MCP — Bangladesh's 1st Universal Courier Model Context Protocol
|
||||
# Aura Courier MCP
|
||||
|
||||
[](https://glama.ai/mcp/servers/auraajenticai/aura-courier-mcp)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://modelcontextprotocol.io)
|
||||
[](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
|
||||
|
||||
Generated
+717
-245
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -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
@@ -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
@@ -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 & 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=…&steadfast_secret=…</code>).</p>` +
|
||||
`<p>By <a href="https://auraajenticai.cloud">Aura Ajentic AI</a> · <a href="https://courier.auraajenticai.cloud">docs & setup</a></p></div></body></html>`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`);
|
||||
});
|
||||
Regular → Executable
+5
-175
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user