From 93addb0320a4be10a3ceb358678498b40cb3a46c Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 05:31:44 +0600 Subject: [PATCH 01/11] chore: configure publishConfig and files for npm registry --- package.json | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a576605..2d78714 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,23 @@ { "name": "aura-courier-mcp", - "version": "2.0.0", - "description": "Universal Bangladesh Courier MCP Server (Steadfast, Pathao & Fraud Engine) for Claude, Antigravity, Cursor & AI Agents", + "version": "2.1.0", + "description": "Universal Bangladesh Courier MCP Server (Steadfast, Pathao, RedX, Paperfly & Fraud AI) for Claude, Antigravity, Cursor & n8n AI Agents", "main": "dist/index.js", "type": "module", "bin": { "aura-courier-mcp": "./dist/index.js" }, + "files": [ + "dist", + "README.md", + "package.json" + ], + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc", + "prepublishOnly": "npm run build", "start": "node dist/index.js", "dev": "tsx src/index.ts" }, @@ -18,13 +27,20 @@ "courier", "steadfast", "pathao", + "redx", + "paperfly", "bangladesh", "antigravity", "claude", - "ecommerce" + "ecommerce", + "ai-agent" ], "author": "Aura Agentic AI ", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/auraajenticai/aura-courier-mcp.git" + }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", "axios": "^1.7.9", From 003f5590599d5d4c3d9ea34d034dcd055ff6f6c7 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 00:58:31 +0000 Subject: [PATCH 02/11] server: remote HTTP MCP endpoint (Streamable HTTP /mcp) + Node Dockerfile; landing served at / --- Dockerfile | 23 +- README.md | 109 ++---- package-lock.json | 962 ++++++++++++++++++++++++++++++++++------------ package.json | 16 +- src/config.ts | 55 +-- src/http.ts | 138 +++++++ src/index.ts | 180 +-------- src/registry.ts | 56 +-- src/server.ts | 135 +++++++ src/types.ts | 15 +- 10 files changed, 1082 insertions(+), 607 deletions(-) create mode 100644 src/http.ts mode change 100644 => 100755 src/index.ts create mode 100644 src/server.ts diff --git a/Dockerfile b/Dockerfile index 3b5434e..a5772eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index 029eec7..23062c4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package-lock.json b/package-lock.json index c73fdb1..ae6a54c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,25 @@ { "name": "aura-courier-mcp", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aura-courier-mcp", - "version": "2.0.0", + "version": "2.1.0", "license": "MIT", "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" }, "bin": { "aura-courier-mcp": "dist/index.js" }, "devDependencies": { + "@types/express": "^4.17.21", "@types/node": "^22.13.0", "tsx": "^4.19.2", "typescript": "^5.7.3" @@ -517,6 +519,367 @@ } } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", @@ -527,14 +890,61 @@ "undici-types": "~6.21.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -585,6 +995,12 @@ } } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -604,40 +1020,69 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, "node_modules/bytes": { @@ -691,16 +1136,15 @@ } }, "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.6" } }, "node_modules/content-type": { @@ -722,13 +1166,10 @@ } }, "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.6", @@ -796,6 +1237,16 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -961,43 +1412,46 @@ } }, "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "peer": true, "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 18" + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", @@ -1023,6 +1477,21 @@ "express": ">= 4.11" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1046,26 +1515,38 @@ "license": "BSD-3-Clause" }, "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -1102,27 +1583,6 @@ "node": ">= 6" } }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1133,12 +1593,12 @@ } }, "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/fsevents": { @@ -1379,53 +1839,63 @@ } }, "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "engines": { - "node": ">=18" - }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/ms": { @@ -1435,32 +1905,12 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/object-assign": { @@ -1524,14 +1974,10 @@ } }, "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" }, "node_modules/pkce-challenge": { "version": "5.0.1", @@ -1565,9 +2011,9 @@ } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -1581,16 +2027,12 @@ } }, "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { @@ -1633,6 +2075,36 @@ "node": ">= 18" } }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1640,48 +2112,57 @@ "license": "MIT" }, "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { @@ -1821,34 +2302,16 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/typescript": { @@ -1881,6 +2344,15 @@ "node": ">= 0.8" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 2d78714..26d808c 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/config.ts b/src/config.ts index 50c88a9..98a3a84 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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; - 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), - }, }; } diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..e1d57c0 --- /dev/null +++ b/src/http.ts @@ -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; + 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 = {}; + +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( + `Aura Courier MCP` + + `` + + `

🚚 Aura Courier MCP

Live remote MCP endpoint for Bangladesh couriers — Steadfast & Pathao.

` + + `

Connect your AI to POST /mcp and pass your courier keys as headers (x-steadfast-api-key, x-steadfast-secret-key) or query params (?steadfast_key=…&steadfast_secret=…).

` + + `

By Aura Ajentic AI · docs & setup

` + ); + } + }); +}); + +app.listen(PORT, () => { + console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`); +}); diff --git a/src/index.ts b/src/index.ts old mode 100644 new mode 100755 index 85ee8de..6f5c01b --- a/src/index.ts +++ b/src/index.ts @@ -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) => { diff --git a/src/registry.ts b/src/registry.ts index 72eccc7..26324c1 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -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 = 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); } } diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..9e3ead4 --- /dev/null +++ b/src/server.ts @@ -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; +} diff --git a/src/types.ts b/src/types.ts index 687aa8e..b06e1f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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; }>; } From 49c02b60b84096d627b38c6d82ba44870e6dc0c8 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 01:25:36 +0000 Subject: [PATCH 03/11] feat: RedX courier adapter (create + track, area auto-resolve); v2.2.0 --- package.json | 5 +- src/adapters/redx.ts | 118 ++++++++++++++++++++++++------------------- src/config.ts | 12 +++++ src/http.ts | 5 +- src/registry.ts | 4 ++ src/server.ts | 10 ++-- src/types.ts | 5 ++ 7 files changed, 102 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 26d808c..56b7f91 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aura-courier-mcp", - "version": "2.1.0", - "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.", + "version": "2.2.0", + "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao & RedX 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": { @@ -27,6 +27,7 @@ "courier", "steadfast", "pathao", + "redx", "bangladesh", "antigravity", "claude", diff --git a/src/adapters/redx.ts b/src/adapters/redx.ts index ede2d64..f0e590f 100644 --- a/src/adapters/redx.ts +++ b/src/adapters/redx.ts @@ -2,29 +2,28 @@ import axios, { AxiosInstance } from "axios"; import { CourierAdapter } from "./base.js"; import { BalanceResponse, - LocationResolutionResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse, } from "../types.js"; -export class RedXAdapter implements CourierAdapter { +export class RedxAdapter implements CourierAdapter { courierName: SupportedCourier = "redx"; private client: AxiosInstance; - private apiToken: string; private enabled: boolean; + private pickupStoreId: string; - constructor(apiToken: string, baseUrl: string = "https://openapi.redx.com.bd/v1.0.0-beta") { - this.apiToken = apiToken; + constructor(apiToken: string, baseUrl: string, pickupStoreId = "") { this.enabled = Boolean(apiToken); + this.pickupStoreId = pickupStoreId; this.client = axios.create({ baseURL: baseUrl, headers: { - Authorization: `Bearer ${apiToken}`, + "API-ACCESS-TOKEN": `Bearer ${apiToken}`, "Content-Type": "application/json", }, - timeout: 10000, + timeout: 15000, }); } @@ -32,26 +31,66 @@ export class RedXAdapter implements CourierAdapter { return this.enabled; } + // RedX's create endpoint needs a numeric delivery_area_id. If the caller did + // not pass one, best-effort resolve it from the address via GET /areas. + private async resolveDeliveryArea(req: ParcelCreateRequest): Promise<{ id: number; name: string }> { + if (req.delivery_area_id) { + return { id: Number(req.delivery_area_id), name: req.delivery_area || "" }; + } + const res = await this.client.get("/areas"); + const areas: Array<{ id: number; name: string }> = res.data?.areas || []; + const addr = (req.recipient_address || "").toLowerCase(); + let best: { id: number; name: string } | null = null; + for (const a of areas) { + const base = String(a.name).split("(")[0].trim().toLowerCase(); + if (base && addr.includes(base)) { + const bestLen = best ? String(best.name).split("(")[0].trim().length : 0; + if (base.length > bestLen) best = a; + } + } + if (!best) { + throw new Error( + "RedX: could not match a delivery area from the address. Pass 'delivery_area_id' explicitly (look it up via RedX /areas)." + ); + } + return { id: best.id, name: best.name }; + } + async createParcel(req: ParcelCreateRequest): Promise { if (!this.enabled) { - throw new Error("RedX Courier credentials (API Token) are not configured."); + throw new Error("RedX credentials (API access token) are not configured."); } - const payload = { + const area = await this.resolveDeliveryArea(req); + const weightGrams = Math.max(1, Math.round((req.item_weight ?? 0.5) * 1000)); + const declaredValue = String(req.value ?? req.cod_amount ?? 0); + + const payload: Record = { customer_name: req.recipient_name, customer_phone: req.recipient_phone, - delivery_area: req.recipient_address, - delivery_area_id: 1, // Default auto-resolved area + delivery_area: area.name || req.delivery_area || "", + delivery_area_id: area.id, customer_address: req.recipient_address, merchant_invoice_id: req.invoice, - cash_collection_amount: req.cod_amount, - parcel_weight: (req.item_weight || 0.5) * 1000, // RedX uses grams + cash_collection_amount: String(req.cod_amount ?? 0), + parcel_weight: weightGrams, instruction: req.note || "Aura AI automated dispatch", - value: req.cod_amount || 500, + value: declaredValue, }; + const storeId = req.pickup_store_id || this.pickupStoreId; + if (storeId) payload.pickup_store_id = Number(storeId); + if (req.item_type) { + payload.parcel_details_json = [ + { name: req.item_type, category: req.item_category || "general", value: Number(req.value ?? req.cod_amount ?? 0) }, + ]; + } - const response = await this.client.post("/parcels", payload); - const trackingId = response.data.tracking_id || response.data.parcel_id; + const response = await this.client.post("/parcel", payload); + const data = response.data; + const trackingId = data?.tracking_id; + if (!trackingId) { + throw new Error(`RedX API error: ${JSON.stringify(data?.message || data)}`); + } return { success: true, @@ -59,58 +98,35 @@ export class RedXAdapter implements CourierAdapter { tracking_code: trackingId, consignment_id: trackingId, invoice: req.invoice, - status: "ready_for_pickup", + status: "pickup-pending", cod_amount: req.cod_amount, - delivery_fee: response.data.delivery_fee || 60, created_at: new Date().toISOString(), - raw_response: response.data, + raw_response: data, }; } async trackParcel(trackingCode: string): Promise { if (!this.enabled) { - throw new Error("RedX Courier credentials are not configured."); + throw new Error("RedX credentials are not configured."); } - - const response = await this.client.get(`/parcels/track/${encodeURIComponent(trackingCode)}`); + const response = await this.client.get(`/parcel/track/${encodeURIComponent(trackingCode)}`); const data = response.data; - + const events: Array<{ message_en?: string; message_bn?: string; time?: string }> = data?.tracking || []; + const latest = events[events.length - 1]; return { success: true, courier: "redx", tracking_code: trackingCode, - status: data.tracking?.status || "in_transit", - timeline: (data.tracking?.history || []).map((h: any) => ({ - status: h.status, - time: h.created_at, - note: h.message, - })), + status: latest?.message_en || "unknown", + updated_at: latest?.time || new Date().toISOString(), + timeline: events.map((e) => ({ status: e.message_en || "", time: e.time || "", note: e.message_bn })), raw_response: data, }; } async getBalance(): Promise { - return { - success: true, - courier: "redx", - current_balance: 0, - raw_response: { message: "RedX payouts are disbursed directly to connected merchant bank accounts." }, - }; - } - - async getLocations(cityName?: string): Promise { - const response = await this.client.get("/areas"); - const areas = response.data.areas || []; - - return { - success: true, - courier: "redx", - locations: areas.map((a: any) => ({ - city_id: a.id, - city_name: a.name, - zone_name: a.zone_name, - area_name: a.name, - })), - }; + throw new Error( + "RedX does not expose a merchant balance endpoint via its public API — check your balance in the RedX merchant panel." + ); } } diff --git a/src/config.ts b/src/config.ts index 98a3a84..a4792ee 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,6 +18,12 @@ export interface CourierConfig { baseUrl: string; enabled: boolean; }; + redx: { + apiToken: string; + baseUrl: string; + pickupStoreId: string; + enabled: boolean; + }; } export type EnvSource = Record; @@ -50,5 +56,11 @@ export function loadConfig(src: EnvSource = process.env): CourierConfig { baseUrl: src.PATHAO_BASE_URL || "https://api-hermes.pathao.com", enabled: Boolean(pathaoClientId && pathaoClientSecret), }, + redx: { + apiToken: src.REDX_API_TOKEN || "", + baseUrl: src.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta", + pickupStoreId: src.REDX_PICKUP_STORE_ID || "", + enabled: Boolean(src.REDX_API_TOKEN), + }, }; } diff --git a/src/http.ts b/src/http.ts index e1d57c0..6e2006b 100644 --- a/src/http.ts +++ b/src/http.ts @@ -32,6 +32,9 @@ function keysFromRequest(req: Request): EnvSource { 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"), + REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"), + REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"), + REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"), }; } @@ -43,7 +46,7 @@ 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" + "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, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id" ); res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); res.header("Access-Control-Expose-Headers", "mcp-session-id"); diff --git a/src/registry.ts b/src/registry.ts index 26324c1..97c7bef 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -2,6 +2,7 @@ import { CourierAdapter } from "./adapters/base.js"; import { SteadfastAdapter } from "./adapters/steadfast.js"; import { PathaoAdapter } from "./adapters/pathao.js"; import { FraudRiskEngine } from "./adapters/fraud_engine.js"; +import { RedxAdapter } from "./adapters/redx.js"; import { loadConfig, CourierConfig } from "./config.js"; import { BalanceResponse, @@ -32,6 +33,9 @@ export class CourierRegistry { config.pathao.baseUrl ); this.adapters.set("pathao", pathao); + + const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId); + this.adapters.set("redx", redx); } listCouriers() { diff --git a/src/server.ts b/src/server.ts index 9e3ead4..96c6963 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,11 +16,11 @@ export const TOOLS: Tool[] = [ }, { name: "create_parcel", - description: "Book a new parcel delivery across Bangladesh (Steadfast or Pathao) with normalized response.", + description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao or RedX) 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)" }, + courier: { type: "string", enum: ["steadfast", "pathao", "redx", "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)" }, @@ -28,6 +28,10 @@ export const TOOLS: Tool[] = [ 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)" }, + item_type: { type: "string", description: "What's inside the parcel (used by RedX)" }, + value: { type: "number", description: "Declared parcel value in BDT (used by RedX; defaults to the COD amount)" }, + delivery_area_id: { type: "number", description: "RedX only: numeric delivery-area id (auto-resolved from the address if omitted)" }, + pickup_store_id: { type: "number", description: "RedX only: your pickup store id (optional)" }, }, required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"], }, @@ -39,7 +43,7 @@ export const TOOLS: Tool[] = [ 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" }, + courier: { type: "string", enum: ["steadfast", "pathao", "redx"], description: "Optional courier name if known" }, }, required: ["tracking_code"], }, diff --git a/src/types.ts b/src/types.ts index b06e1f4..3d3f035 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,11 @@ export interface ParcelCreateRequest { note?: string; item_type?: string; item_weight?: number; // in KG + item_category?: string; + value?: number | string; + delivery_area?: string; + delivery_area_id?: number | string; + pickup_store_id?: number | string; } export interface ParcelResponse { From f1a197e25169d9e9a2fa3edacd289505017fe16e Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 01:34:16 +0000 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20Paperfly=20courier=20adapter=20(c?= =?UTF-8?q?reate=20+=20track);=20v2.3.0=20=E2=80=94=20all=204=20couriers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- src/adapters/paperfly.ts | 117 ++++++++++++++++++++------------------- src/config.ts | 16 ++++++ src/http.ts | 6 +- src/registry.ts | 10 ++++ src/server.ts | 6 +- 6 files changed, 97 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 56b7f91..7ef4450 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "aura-courier-mcp", - "version": "2.2.0", - "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao & RedX 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.", + "version": "2.3.0", + "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly 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": { @@ -28,6 +28,7 @@ "steadfast", "pathao", "redx", + "paperfly", "bangladesh", "antigravity", "claude", diff --git a/src/adapters/paperfly.ts b/src/adapters/paperfly.ts index eb353ec..a705054 100644 --- a/src/adapters/paperfly.ts +++ b/src/adapters/paperfly.ts @@ -2,7 +2,6 @@ import axios, { AxiosInstance } from "axios"; import { CourierAdapter } from "./base.js"; import { BalanceResponse, - LocationResolutionResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, @@ -11,32 +10,23 @@ import { export class PaperflyAdapter implements CourierAdapter { courierName: SupportedCourier = "paperfly"; + private apiKey: string; + private username: string; + private password: string; + private storeName: string; private client: AxiosInstance; - private user: string; - private pass: string; - private key: string; private enabled: boolean; - constructor( - user: string, - pass: string, - key: string, - baseUrl: string = "https://api.paperfly.com.bd" - ) { - this.user = user; - this.pass = pass; - this.key = key; - this.enabled = Boolean(user && pass && key); - - const token = Buffer.from(`${user}:${pass}`).toString("base64"); + constructor(apiKey: string, username: string, password: string, storeName: string, baseUrl: string) { + this.apiKey = apiKey; + this.username = username; + this.password = password; + this.storeName = storeName; + this.enabled = Boolean(apiKey && storeName); this.client = axios.create({ baseURL: baseUrl, - headers: { - Authorization: `Basic ${token}`, - pap_key: key, - "Content-Type": "application/json", - }, - timeout: 10000, + timeout: 15000, + headers: { "Content-Type": "application/json" }, }); } @@ -46,66 +36,79 @@ export class PaperflyAdapter implements CourierAdapter { async createParcel(req: ParcelCreateRequest): Promise { if (!this.enabled) { - throw new Error("Paperfly Courier credentials (User / Pass / Key) are not configured."); + throw new Error("Paperfly credentials (paperflykey + store name) are not configured."); } - const payload = { - merOrderRef: req.invoice, - pickMerchantName: "Aura AI Commerce", - pickMerchantAddress: "Dhaka, Bangladesh", - custName: req.recipient_name, - custPhone: req.recipient_phone, - custAddr: req.recipient_address, - customerDistrict: "Dhaka", - customerThana: "Gulshan", - packagePrice: req.cod_amount, - max_weight: req.item_weight || 0.5, - productBrief: req.item_type || "General Ecommerce item", - deliveryOption: "regular", + merchantOrderReference: req.invoice, + storeName: this.storeName, + productBrief: req.item_type || "Product", + packagePrice: String(req.value ?? req.cod_amount ?? 0), + max_weight: String(req.item_weight ?? 0.5), + customerName: req.recipient_name, + customerAddress: req.recipient_address, + customerPhone: req.recipient_phone, }; - - const response = await this.client.post("/OrderPlacement", payload); - const trackingCode = response.data?.tracking_number || req.invoice; - + const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, { + headers: { paperflykey: this.apiKey }, + }); + const data = response.data; + const ok = data?.success; + if (!ok?.tracking_number) { + throw new Error(`Paperfly API error: ${JSON.stringify(data?.error || data?.message || data)}`); + } return { success: true, courier: "paperfly", - tracking_code: trackingCode, - consignment_id: trackingCode, + tracking_code: ok.tracking_number, + consignment_id: ok.tracking_barcode || ok.tracking_number, invoice: req.invoice, - status: "order_placed", + status: ok.message || "created", cod_amount: req.cod_amount, created_at: new Date().toISOString(), - raw_response: response.data, + raw_response: data, }; } async trackParcel(trackingCode: string): Promise { if (!this.enabled) { - throw new Error("Paperfly Courier credentials are not configured."); + throw new Error("Paperfly credentials are not configured."); } - - const response = await this.client.post("/Tracking", { - ReferenceNumber: trackingCode, - }); + if (!this.username || !this.password) { + throw new Error( + "Paperfly tracking needs your merchant-panel username & password (Basic Auth). Add them to use track_parcel." + ); + } + // Paperfly tracks by your merchant order reference (merchantOrderReference), not its tracking number. + const response = await this.client.post( + "/API-Order-Tracking", + { ReferenceNumber: trackingCode }, + { auth: { username: this.username, password: this.password } } + ); const data = response.data; - + const st = data?.success?.trackingStatus?.[0] || {}; + const stages: Array<[any, string]> = [ + [st.Delivered, "delivered"], + [st.Partial, "partial-delivery"], + [st.Returned, "returned"], + [st.PickedForDelivery, "out-for-delivery"], + [st.inTransit, "in-transit"], + [st.ReceivedAtPoint, "received-at-point"], + [st.Pick, "picked-up"], + ]; + const status = stages.find(([v]) => v && String(v).trim())?.[1] || data?.success?.message || "pending"; return { success: true, courier: "paperfly", tracking_code: trackingCode, - status: data?.Status || "in_progress", + status, updated_at: new Date().toISOString(), raw_response: data, }; } async getBalance(): Promise { - return { - success: true, - courier: "paperfly", - current_balance: 0, - raw_response: { message: "Paperfly merchant settlements processed weekly via Wingman portal." }, - }; + throw new Error( + "Paperfly does not expose a merchant balance endpoint via its public API — check the Paperfly merchant panel." + ); } } diff --git a/src/config.ts b/src/config.ts index a4792ee..e2b84ac 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,6 +24,14 @@ export interface CourierConfig { pickupStoreId: string; enabled: boolean; }; + paperfly: { + apiKey: string; + username: string; + password: string; + storeName: string; + baseUrl: string; + enabled: boolean; + }; } export type EnvSource = Record; @@ -62,5 +70,13 @@ export function loadConfig(src: EnvSource = process.env): CourierConfig { pickupStoreId: src.REDX_PICKUP_STORE_ID || "", enabled: Boolean(src.REDX_API_TOKEN), }, + paperfly: { + apiKey: src.PAPERFLY_API_KEY || "", + username: src.PAPERFLY_USERNAME || "", + password: src.PAPERFLY_PASSWORD || "", + storeName: src.PAPERFLY_STORE_NAME || "", + baseUrl: src.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd", + enabled: Boolean(src.PAPERFLY_API_KEY && src.PAPERFLY_STORE_NAME), + }, }; } diff --git a/src/http.ts b/src/http.ts index 6e2006b..e82c1c7 100644 --- a/src/http.ts +++ b/src/http.ts @@ -35,6 +35,10 @@ function keysFromRequest(req: Request): EnvSource { REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"), REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"), REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"), + PAPERFLY_API_KEY: pick("x-paperfly-api-key", "paperfly_key"), + PAPERFLY_USERNAME: pick("x-paperfly-username", "paperfly_username"), + PAPERFLY_PASSWORD: pick("x-paperfly-password", "paperfly_password"), + PAPERFLY_STORE_NAME: pick("x-paperfly-store-name", "paperfly_store"), }; } @@ -46,7 +50,7 @@ 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, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id" + "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, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id, x-paperfly-api-key, x-paperfly-username, x-paperfly-password, x-paperfly-store-name" ); res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); res.header("Access-Control-Expose-Headers", "mcp-session-id"); diff --git a/src/registry.ts b/src/registry.ts index 97c7bef..07aad08 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -3,6 +3,7 @@ import { SteadfastAdapter } from "./adapters/steadfast.js"; import { PathaoAdapter } from "./adapters/pathao.js"; import { FraudRiskEngine } from "./adapters/fraud_engine.js"; import { RedxAdapter } from "./adapters/redx.js"; +import { PaperflyAdapter } from "./adapters/paperfly.js"; import { loadConfig, CourierConfig } from "./config.js"; import { BalanceResponse, @@ -36,6 +37,15 @@ export class CourierRegistry { const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId); this.adapters.set("redx", redx); + + const paperfly = new PaperflyAdapter( + config.paperfly.apiKey, + config.paperfly.username, + config.paperfly.password, + config.paperfly.storeName, + config.paperfly.baseUrl + ); + this.adapters.set("paperfly", paperfly); } listCouriers() { diff --git a/src/server.ts b/src/server.ts index 96c6963..c7680f4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,11 +16,11 @@ export const TOOLS: Tool[] = [ }, { name: "create_parcel", - description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao or RedX) with normalized response.", + 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", "auto"], description: "Target courier or 'auto' for AI smart routing (default: auto)" }, + 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)" }, @@ -43,7 +43,7 @@ export const TOOLS: Tool[] = [ type: "object", properties: { tracking_code: { type: "string", description: "Consignment ID or tracking code" }, - courier: { type: "string", enum: ["steadfast", "pathao", "redx"], description: "Optional courier name if known" }, + courier: { type: "string", enum: ["steadfast", "pathao", "redx", "paperfly"], description: "Optional courier name if known" }, }, required: ["tracking_code"], }, From c96d8eff7eb599684843fdb2632906c658051497 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 01:45:23 +0000 Subject: [PATCH 05/11] fix(paperfly): send paperflykey + Basic Auth on both create & track; v2.3.1 --- package.json | 2 +- src/adapters/paperfly.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7ef4450..65c4291 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aura-courier-mcp", - "version": "2.3.0", + "version": "2.3.1", "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly 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", diff --git a/src/adapters/paperfly.ts b/src/adapters/paperfly.ts index a705054..3a872aa 100644 --- a/src/adapters/paperfly.ts +++ b/src/adapters/paperfly.ts @@ -22,7 +22,7 @@ export class PaperflyAdapter implements CourierAdapter { this.username = username; this.password = password; this.storeName = storeName; - this.enabled = Boolean(apiKey && storeName); + this.enabled = Boolean(apiKey && username && password); this.client = axios.create({ baseURL: baseUrl, timeout: 15000, @@ -36,7 +36,10 @@ export class PaperflyAdapter implements CourierAdapter { async createParcel(req: ParcelCreateRequest): Promise { if (!this.enabled) { - throw new Error("Paperfly credentials (paperflykey + store name) are not configured."); + throw new Error("Paperfly credentials (paperflykey + merchant username & password) are not configured."); + } + if (!this.storeName) { + throw new Error("Paperfly store name is required to create a parcel."); } const payload = { merchantOrderReference: req.invoice, @@ -50,6 +53,7 @@ export class PaperflyAdapter implements CourierAdapter { }; const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, { headers: { paperflykey: this.apiKey }, + auth: { username: this.username, password: this.password }, }); const data = response.data; const ok = data?.success; @@ -82,7 +86,7 @@ export class PaperflyAdapter implements CourierAdapter { const response = await this.client.post( "/API-Order-Tracking", { ReferenceNumber: trackingCode }, - { auth: { username: this.username, password: this.password } } + { headers: { paperflykey: this.apiKey }, auth: { username: this.username, password: this.password } } ); const data = response.data; const st = data?.success?.trackingStatus?.[0] || {}; From 33bc9e25a9d0a28f7a6a8ebe093423a7442f3800 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 07:13:09 +0000 Subject: [PATCH 06/11] chore: add MIT LICENSE file (repo license was declared in package.json only) --- LICENSE | 21 +++++++++++++++++++++ package.json | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fe9bdb8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Aura Ajentic AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package.json b/package.json index 65c4291..2f3148f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aura-courier-mcp", - "version": "2.3.1", + "version": "2.3.2", "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly 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", @@ -9,7 +9,8 @@ }, "files": [ "dist", - "README.md" + "README.md", + "LICENSE" ], "publishConfig": { "access": "public" From 39fed40bc46f76647996388aa92c78c13e026763 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 13:16:29 +0000 Subject: [PATCH 07/11] chore: add glama.json declaring maintainer (khondokartowsif171) --- glama.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 glama.json diff --git a/glama.json b/glama.json new file mode 100644 index 0000000..9cd99c7 --- /dev/null +++ b/glama.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://glama.ai/mcp/schemas/server.json", + "maintainers": [ + "khondokartowsif171" + ] +} From 10f073cd4740617fb87ec4983814482f86c4fa4c Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Wed, 2 Sep 2026 13:24:19 +0000 Subject: [PATCH 08/11] docs: README shows all 4 couriers (Steadfast/Pathao/RedX/Paperfly) + remote-URL & npx methods --- README.md | 48 ++++++++++++++++++++++++++++++++++-------------- package.json | 2 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 23062c4..894dbd3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Aura Courier MCP -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. +Universal Bangladesh courier MCP server — **Steadfast, Pathao, RedX & Paperfly** with a built-in **fraud-risk engine** — for Claude, Antigravity, Cursor, n8n and any MCP-compatible AI agent. Book and track parcels across Bangladesh by just telling your AI. By **Aura Ajentic AI** · [auraajenticai.cloud](https://auraajenticai.cloud) · ✓ Verified on Glama @@ -9,12 +9,24 @@ By **Aura Ajentic AI** · [auraajenticai.cloud](https://auraajenticai.cloud) · | 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 | +| `create_parcel` | Book a parcel (Steadfast / Pathao / RedX / Paperfly, or `auto`) | +| `track_parcel` | Track a shipment by tracking / consignment / reference id | +| `get_balance` | Merchant account balance (Steadfast / Pathao) | | `check_fraud_risk` | Delivery/return-risk score for a Bangladeshi phone number | -## Install (add to your AI's MCP config) +## Two ways to connect + +### 1) Remote URL — claude.ai web, n8n, any remote client + +Add this as a custom MCP connector and pass your courier keys as headers (or query params): + +``` +https://courier.auraajenticai.cloud/mcp +``` + +Headers: `x-steadfast-api-key`, `x-steadfast-secret-key`, `x-pathao-client-id` …, `x-redx-api-token`, `x-paperfly-api-key`, `x-paperfly-store-name`, `x-paperfly-username`, `x-paperfly-password`. + +### 2) Local (npx) — Claude Desktop, Cursor, Antigravity ```json { @@ -23,25 +35,33 @@ By **Aura Ajentic AI** · [auraajenticai.cloud](https://auraajenticai.cloud) · "command": "npx", "args": ["-y", "aura-courier-mcp@latest"], "env": { - "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" + "STEADFAST_API_KEY": "…", + "STEADFAST_SECRET_KEY": "…", + "REDX_API_TOKEN": "…", + "PAPERFLY_API_KEY": "…", + "PAPERFLY_STORE_NAME": "…", + "PAPERFLY_USERNAME": "…", + "PAPERFLY_PASSWORD": "…" } } } } ``` -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. +Add only the couriers you use — each is independent. Your keys stay on your side (env vars or request headers); they are never stored by Aura. + +## Courier notes + +- **Steadfast / Pathao** — API key + secret (Pathao also needs client id/secret + username/password). +- **RedX** — API access token (`REDX_API_TOKEN`). Delivery area is auto-resolved from the address, or pass `delivery_area_id`. +- **Paperfly** — needs **both** the `paperflykey` **and** your merchant username/password on create *and* track, plus your store name. +- `get_balance` is available for Steadfast & Pathao (RedX/Paperfly don't expose a balance API). ## Then just ask > "Book a Steadfast parcel for invoice A-1001, COD 1500 to 017XXXXXXXX." -> "Track consignment 123456." +> "Create a RedX parcel to Dhanmondi, COD 900." +> "Track consignment 20A316MOG0DI." ## License diff --git a/package.json b/package.json index 2f3148f..ae3e84f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aura-courier-mcp", - "version": "2.3.2", + "version": "2.3.3", "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly 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", From bc19b4e785e5749d8b36c410ff39b3c8814c15e5 Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Tue, 8 Sep 2026 12:37:51 +0600 Subject: [PATCH 09/11] =?UTF-8?q?README:=20submission-grade=20rewrite=20?= =?UTF-8?q?=E2=80=94=20badges,=20positioning,=20accurate=20remote+npx=20co?= =?UTF-8?q?nnect,=20all-4-courier=20status,=20install=20configs=20for=20An?= =?UTF-8?q?tigravity/Claude/Cursor,=20honest=20tool=20+=20courier=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 131 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 894dbd3..dad472c 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,58 @@ -# Aura Courier MCP +# 🚀 Aura Courier MCP — Bangladesh's First Universal Courier Model Context Protocol -Universal Bangladesh courier MCP server — **Steadfast, Pathao, RedX & Paperfly** with a built-in **fraud-risk engine** — for Claude, Antigravity, Cursor, n8n and any MCP-compatible AI agent. Book and track parcels across Bangladesh by just telling your AI. +[![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 SDK](https://img.shields.io/badge/MCP%20SDK-1.x-8b5cf6.svg)](https://modelcontextprotocol.io) +[![Couriers](https://img.shields.io/badge/Couriers-Steadfast%20%7C%20Pathao%20%7C%20RedX%20%7C%20Paperfly-46d17f.svg)](https://courier.auraajenticai.cloud) -By **Aura Ajentic AI** · [auraajenticai.cloud](https://auraajenticai.cloud) · ✓ Verified on Glama +> **One unified Model Context Protocol connector for every Bangladesh courier.** +> Book deliveries, track parcels, check merchant balances, and screen customer COD/return-fraud risk — straight from **Google Antigravity**, **Claude Desktop**, **Cursor**, **n8n AI Agents**, or any MCP-compatible client. Just tell your AI what to ship. -## Tools +🌐 **Showcase & live docs:** [courier.auraajenticai.cloud](https://courier.auraajenticai.cloud) +🏢 **Built by:** [Aura Ajentic AI](https://auraajenticai.cloud) · Khondokar Towsif (Amirul Islam Redwan) + +--- + +## ⚡ Supported couriers — four networks, one interface (all proven) + +| Courier | API | Status | +|---|---|---| +| **Steadfast** | Packzy API v1 | ✅ Proven | +| **Pathao** | Aladdin API v1 (OAuth + city/zone resolver) | ✅ Proven | +| **RedX** | OpenAPI v1 (area auto-resolver + tracking) | ✅ Proven | +| **Paperfly** | Wingman API (order + tracking) | ✅ Proven | + +> You bring each courier's own merchant credentials (env vars or request headers); Aura routes the request and normalizes the response. + +--- + +## 🧠 MCP tools | Tool | What it does | -|------|--------------| -| `list_couriers` | Show supported couriers and which credentials are active | -| `create_parcel` | Book a parcel (Steadfast / Pathao / RedX / Paperfly, or `auto`) | -| `track_parcel` | Track a shipment by tracking / consignment / reference id | -| `get_balance` | Merchant account balance (Steadfast / Pathao) | -| `check_fraud_risk` | Delivery/return-risk score for a Bangladeshi phone number | +|---|---| +| `create_parcel` | Book a parcel on Steadfast / Pathao / RedX / Paperfly (or `auto`), with one normalized response. | +| `track_parcel` | Universal tracking by tracking / consignment / reference id, across every supported courier. | +| `get_balance` | Live merchant account balance & payout info (Steadfast & Pathao). | +| `check_fraud_risk` | **BD phone COD/return-risk score** — screen a customer's number *before* you ship cash-on-delivery. Works with no courier keys. | +| `list_couriers` | Show supported couriers and which credentials are currently active. | -## Two ways to connect +--- + +## 🔌 Two ways to connect ### 1) Remote URL — claude.ai web, n8n, any remote client - -Add this as a custom MCP connector and pass your courier keys as headers (or query params): +Add a custom MCP connector and pass your courier keys as headers (or query params): ``` https://courier.auraajenticai.cloud/mcp ``` -Headers: `x-steadfast-api-key`, `x-steadfast-secret-key`, `x-pathao-client-id` …, `x-redx-api-token`, `x-paperfly-api-key`, `x-paperfly-store-name`, `x-paperfly-username`, `x-paperfly-password`. +Headers: `x-steadfast-api-key`, `x-steadfast-secret-key`, `x-pathao-client-id`, `x-pathao-client-secret`, `x-pathao-username`, `x-pathao-password`, `x-redx-api-token`, `x-paperfly-api-key`, `x-paperfly-store-name`, `x-paperfly-username`, `x-paperfly-password`. -### 2) Local (npx) — Claude Desktop, Cursor, Antigravity +### 2) Local (npx / STDIO) — Antigravity, Claude Desktop, Cursor +Add only the couriers you use — each is independent. Your keys stay on your side (env vars or request headers); **Aura never stores them**. +#### 🪐 Google Antigravity / Gemini CLI — `antigravity.json` ```json { "mcpServers": { @@ -35,34 +60,78 @@ Headers: `x-steadfast-api-key`, `x-steadfast-secret-key`, `x-pathao-client-id` "command": "npx", "args": ["-y", "aura-courier-mcp@latest"], "env": { - "STEADFAST_API_KEY": "…", - "STEADFAST_SECRET_KEY": "…", - "REDX_API_TOKEN": "…", - "PAPERFLY_API_KEY": "…", - "PAPERFLY_STORE_NAME": "…", - "PAPERFLY_USERNAME": "…", - "PAPERFLY_PASSWORD": "…" + "STEADFAST_API_KEY": "YOUR_STEADFAST_API_KEY", + "STEADFAST_SECRET_KEY": "YOUR_STEADFAST_SECRET_KEY", + "REDX_API_TOKEN": "YOUR_REDX_TOKEN", + "PAPERFLY_API_KEY": "YOUR_PAPERFLY_KEY", + "PAPERFLY_STORE_NAME": "YOUR_STORE_NAME", + "PAPERFLY_USERNAME": "YOUR_PAPERFLY_USERNAME", + "PAPERFLY_PASSWORD": "YOUR_PAPERFLY_PASSWORD" } } } } ``` -Add only the couriers you use — each is independent. Your keys stay on your side (env vars or request headers); they are never stored by Aura. +#### ⚡ Claude Desktop — `claude_desktop_config.json` & 💻 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" + } + } + } +} +``` -## Courier notes +--- -- **Steadfast / Pathao** — API key + secret (Pathao also needs client id/secret + username/password). -- **RedX** — API access token (`REDX_API_TOKEN`). Delivery area is auto-resolved from the address, or pass `delivery_area_id`. -- **Paperfly** — needs **both** the `paperflykey` **and** your merchant username/password on create *and* track, plus your store name. -- `get_balance` is available for Steadfast & Pathao (RedX/Paperfly don't expose a balance API). +## 📋 Courier notes -## Then just ask +- **Steadfast / Pathao** — API key + secret (Pathao also needs client id/secret + username/password). `get_balance` is available for these two. +- **RedX** — API access token (`REDX_API_TOKEN`); delivery area is auto-resolved from the address, or pass `delivery_area_id`. +- **Paperfly** — needs the `paperflykey` **and** your merchant username/password on both create *and* track, plus your store name. +- **`check_fraud_risk`** runs keyless — a COD gate you can call before booking anything. + +--- + +## 💬 Then just ask your AI > "Book a Steadfast parcel for invoice A-1001, COD 1500 to 017XXXXXXXX." > "Create a RedX parcel to Dhanmondi, COD 900." > "Track consignment 20A316MOG0DI." +> "Check the return risk for 018XXXXXXXX before I ship COD." -## License +--- -MIT © Aura Ajentic AI +## 🏗️ Architecture + +``` +AI agent (Antigravity / Claude / Cursor / n8n) + │ MCP (STDIO or Streamable-HTTP) + ▼ + Aura Courier MCP ──► Registry (gateway + smart router) ──► Courier adapters + ├── Steadfast (Packzy API) + ├── Pathao (Aladdin API) + ├── RedX (OpenAPI) + └── Paperfly (Wingman API) + + built-in BD phone fraud-risk engine +``` + +Keys are read per-request (headers) or per-process (env) — never persisted. + +--- + +## 📜 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 transact with Bangladeshi businesses. This MCP is the first brick. + +--- + +Licensed under **MIT** © Aura Ajentic AI. From fd70ab6bf5616af455a580e851afbbbd0598804a Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Tue, 8 Sep 2026 12:57:08 +0600 Subject: [PATCH 10/11] Directory-submission readiness: add MCP tool annotations (title + readOnlyHint/destructiveHint), /privacy policy route + README Privacy Policy section, bump to 2.3.2 --- README.md | 6 ++ dist/adapters/paperfly.d.ts | 9 ++- dist/adapters/paperfly.js | 101 ++++++++++++----------- dist/adapters/redx.d.ts | 10 +-- dist/adapters/redx.js | 107 ++++++++++++++----------- dist/config.d.ts | 16 +++- dist/config.js | 47 ++++++----- dist/http.d.ts | 1 + dist/http.js | 155 ++++++++++++++++++++++++++++++++++++ dist/index.js | 155 ++---------------------------------- dist/registry.d.ts | 3 +- dist/registry.js | 51 +++--------- dist/server.d.ts | 10 +++ dist/server.js | 123 ++++++++++++++++++++++++++++ dist/types.d.ts | 19 ++++- package.json | 2 +- src/http.ts | 30 ++++++- src/server.ts | 7 +- 18 files changed, 528 insertions(+), 324 deletions(-) create mode 100644 dist/http.d.ts create mode 100644 dist/http.js create mode 100644 dist/server.d.ts create mode 100644 dist/server.js diff --git a/README.md b/README.md index dad472c..6427257 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,12 @@ Keys are read per-request (headers) or per-process (env) — never persisted. --- +## 🔒 Privacy Policy + +Aura Courier MCP processes only what's needed to fulfil your request: your **courier credentials** (passed per-request as headers/env — **never stored** by Aura), the **order details** you send to book/track a parcel, and **phone numbers** you submit to `check_fraud_risk`. Order data is forwarded only to the courier you choose (Steadfast / Pathao / RedX / Paperfly) to complete the operation; nothing is sold or used for profiling, and credentials live only in memory for the active session. All traffic is over HTTPS. + +**Full policy:** https://courier.auraajenticai.cloud/privacy · **Contact:** ceo@auraajenticai.cloud + ## 📜 The vision > **মানুষের স্বপ্ন · এআই-এর হাত** diff --git a/dist/adapters/paperfly.d.ts b/dist/adapters/paperfly.d.ts index 4569766..874f835 100644 --- a/dist/adapters/paperfly.d.ts +++ b/dist/adapters/paperfly.d.ts @@ -2,12 +2,13 @@ import { CourierAdapter } from "./base.js"; import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js"; export declare class PaperflyAdapter implements CourierAdapter { courierName: SupportedCourier; + private apiKey; + private username; + private password; + private storeName; private client; - private user; - private pass; - private key; private enabled; - constructor(user: string, pass: string, key: string, baseUrl?: string); + constructor(apiKey: string, username: string, password: string, storeName: string, baseUrl: string); isConfigured(): boolean; createParcel(req: ParcelCreateRequest): Promise; trackParcel(trackingCode: string): Promise; diff --git a/dist/adapters/paperfly.js b/dist/adapters/paperfly.js index edf8a28..4f0b7d7 100644 --- a/dist/adapters/paperfly.js +++ b/dist/adapters/paperfly.js @@ -1,25 +1,22 @@ import axios from "axios"; export class PaperflyAdapter { courierName = "paperfly"; + apiKey; + username; + password; + storeName; client; - user; - pass; - key; enabled; - constructor(user, pass, key, baseUrl = "https://api.paperfly.com.bd") { - this.user = user; - this.pass = pass; - this.key = key; - this.enabled = Boolean(user && pass && key); - const token = Buffer.from(`${user}:${pass}`).toString("base64"); + constructor(apiKey, username, password, storeName, baseUrl) { + this.apiKey = apiKey; + this.username = username; + this.password = password; + this.storeName = storeName; + this.enabled = Boolean(apiKey && username && password); this.client = axios.create({ baseURL: baseUrl, - headers: { - Authorization: `Basic ${token}`, - pap_key: key, - "Content-Type": "application/json", - }, - timeout: 10000, + timeout: 15000, + headers: { "Content-Type": "application/json" }, }); } isConfigured() { @@ -27,59 +24,73 @@ export class PaperflyAdapter { } async createParcel(req) { if (!this.enabled) { - throw new Error("Paperfly Courier credentials (User / Pass / Key) are not configured."); + throw new Error("Paperfly credentials (paperflykey + merchant username & password) are not configured."); + } + if (!this.storeName) { + throw new Error("Paperfly store name is required to create a parcel."); } const payload = { - merOrderRef: req.invoice, - pickMerchantName: "Aura AI Commerce", - pickMerchantAddress: "Dhaka, Bangladesh", - custName: req.recipient_name, - custPhone: req.recipient_phone, - custAddr: req.recipient_address, - customerDistrict: "Dhaka", - customerThana: "Gulshan", - packagePrice: req.cod_amount, - max_weight: req.item_weight || 0.5, - productBrief: req.item_type || "General Ecommerce item", - deliveryOption: "regular", + merchantOrderReference: req.invoice, + storeName: this.storeName, + productBrief: req.item_type || "Product", + packagePrice: String(req.value ?? req.cod_amount ?? 0), + max_weight: String(req.item_weight ?? 0.5), + customerName: req.recipient_name, + customerAddress: req.recipient_address, + customerPhone: req.recipient_phone, }; - const response = await this.client.post("/OrderPlacement", payload); - const trackingCode = response.data?.tracking_number || req.invoice; + const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, { + headers: { paperflykey: this.apiKey }, + auth: { username: this.username, password: this.password }, + }); + const data = response.data; + const ok = data?.success; + if (!ok?.tracking_number) { + throw new Error(`Paperfly API error: ${JSON.stringify(data?.error || data?.message || data)}`); + } return { success: true, courier: "paperfly", - tracking_code: trackingCode, - consignment_id: trackingCode, + tracking_code: ok.tracking_number, + consignment_id: ok.tracking_barcode || ok.tracking_number, invoice: req.invoice, - status: "order_placed", + status: ok.message || "created", cod_amount: req.cod_amount, created_at: new Date().toISOString(), - raw_response: response.data, + raw_response: data, }; } async trackParcel(trackingCode) { if (!this.enabled) { - throw new Error("Paperfly Courier credentials are not configured."); + throw new Error("Paperfly credentials are not configured."); } - const response = await this.client.post("/Tracking", { - ReferenceNumber: trackingCode, - }); + if (!this.username || !this.password) { + throw new Error("Paperfly tracking needs your merchant-panel username & password (Basic Auth). Add them to use track_parcel."); + } + // Paperfly tracks by your merchant order reference (merchantOrderReference), not its tracking number. + const response = await this.client.post("/API-Order-Tracking", { ReferenceNumber: trackingCode }, { headers: { paperflykey: this.apiKey }, auth: { username: this.username, password: this.password } }); const data = response.data; + const st = data?.success?.trackingStatus?.[0] || {}; + const stages = [ + [st.Delivered, "delivered"], + [st.Partial, "partial-delivery"], + [st.Returned, "returned"], + [st.PickedForDelivery, "out-for-delivery"], + [st.inTransit, "in-transit"], + [st.ReceivedAtPoint, "received-at-point"], + [st.Pick, "picked-up"], + ]; + const status = stages.find(([v]) => v && String(v).trim())?.[1] || data?.success?.message || "pending"; return { success: true, courier: "paperfly", tracking_code: trackingCode, - status: data?.Status || "in_progress", + status, updated_at: new Date().toISOString(), raw_response: data, }; } async getBalance() { - return { - success: true, - courier: "paperfly", - current_balance: 0, - raw_response: { message: "Paperfly merchant settlements processed weekly via Wingman portal." }, - }; + throw new Error("Paperfly does not expose a merchant balance endpoint via its public API — check the Paperfly merchant panel."); } } diff --git a/dist/adapters/redx.d.ts b/dist/adapters/redx.d.ts index 7ed8fe7..662872d 100644 --- a/dist/adapters/redx.d.ts +++ b/dist/adapters/redx.d.ts @@ -1,14 +1,14 @@ import { CourierAdapter } from "./base.js"; -import { BalanceResponse, LocationResolutionResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js"; -export declare class RedXAdapter implements CourierAdapter { +import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js"; +export declare class RedxAdapter implements CourierAdapter { courierName: SupportedCourier; private client; - private apiToken; private enabled; - constructor(apiToken: string, baseUrl?: string); + private pickupStoreId; + constructor(apiToken: string, baseUrl: string, pickupStoreId?: string); isConfigured(): boolean; + private resolveDeliveryArea; createParcel(req: ParcelCreateRequest): Promise; trackParcel(trackingCode: string): Promise; getBalance(): Promise; - getLocations(cityName?: string): Promise; } diff --git a/dist/adapters/redx.js b/dist/adapters/redx.js index 63a03dc..80ad307 100644 --- a/dist/adapters/redx.js +++ b/dist/adapters/redx.js @@ -1,94 +1,111 @@ import axios from "axios"; -export class RedXAdapter { +export class RedxAdapter { courierName = "redx"; client; - apiToken; enabled; - constructor(apiToken, baseUrl = "https://openapi.redx.com.bd/v1.0.0-beta") { - this.apiToken = apiToken; + pickupStoreId; + constructor(apiToken, baseUrl, pickupStoreId = "") { this.enabled = Boolean(apiToken); + this.pickupStoreId = pickupStoreId; this.client = axios.create({ baseURL: baseUrl, headers: { - Authorization: `Bearer ${apiToken}`, + "API-ACCESS-TOKEN": `Bearer ${apiToken}`, "Content-Type": "application/json", }, - timeout: 10000, + timeout: 15000, }); } isConfigured() { return this.enabled; } + // RedX's create endpoint needs a numeric delivery_area_id. If the caller did + // not pass one, best-effort resolve it from the address via GET /areas. + async resolveDeliveryArea(req) { + if (req.delivery_area_id) { + return { id: Number(req.delivery_area_id), name: req.delivery_area || "" }; + } + const res = await this.client.get("/areas"); + const areas = res.data?.areas || []; + const addr = (req.recipient_address || "").toLowerCase(); + let best = null; + for (const a of areas) { + const base = String(a.name).split("(")[0].trim().toLowerCase(); + if (base && addr.includes(base)) { + const bestLen = best ? String(best.name).split("(")[0].trim().length : 0; + if (base.length > bestLen) + best = a; + } + } + if (!best) { + throw new Error("RedX: could not match a delivery area from the address. Pass 'delivery_area_id' explicitly (look it up via RedX /areas)."); + } + return { id: best.id, name: best.name }; + } async createParcel(req) { if (!this.enabled) { - throw new Error("RedX Courier credentials (API Token) are not configured."); + throw new Error("RedX credentials (API access token) are not configured."); } + const area = await this.resolveDeliveryArea(req); + const weightGrams = Math.max(1, Math.round((req.item_weight ?? 0.5) * 1000)); + const declaredValue = String(req.value ?? req.cod_amount ?? 0); const payload = { customer_name: req.recipient_name, customer_phone: req.recipient_phone, - delivery_area: req.recipient_address, - delivery_area_id: 1, // Default auto-resolved area + delivery_area: area.name || req.delivery_area || "", + delivery_area_id: area.id, customer_address: req.recipient_address, merchant_invoice_id: req.invoice, - cash_collection_amount: req.cod_amount, - parcel_weight: (req.item_weight || 0.5) * 1000, // RedX uses grams + cash_collection_amount: String(req.cod_amount ?? 0), + parcel_weight: weightGrams, instruction: req.note || "Aura AI automated dispatch", - value: req.cod_amount || 500, + value: declaredValue, }; - const response = await this.client.post("/parcels", payload); - const trackingId = response.data.tracking_id || response.data.parcel_id; + const storeId = req.pickup_store_id || this.pickupStoreId; + if (storeId) + payload.pickup_store_id = Number(storeId); + if (req.item_type) { + payload.parcel_details_json = [ + { name: req.item_type, category: req.item_category || "general", value: Number(req.value ?? req.cod_amount ?? 0) }, + ]; + } + const response = await this.client.post("/parcel", payload); + const data = response.data; + const trackingId = data?.tracking_id; + if (!trackingId) { + throw new Error(`RedX API error: ${JSON.stringify(data?.message || data)}`); + } return { success: true, courier: "redx", tracking_code: trackingId, consignment_id: trackingId, invoice: req.invoice, - status: "ready_for_pickup", + status: "pickup-pending", cod_amount: req.cod_amount, - delivery_fee: response.data.delivery_fee || 60, created_at: new Date().toISOString(), - raw_response: response.data, + raw_response: data, }; } async trackParcel(trackingCode) { if (!this.enabled) { - throw new Error("RedX Courier credentials are not configured."); + throw new Error("RedX credentials are not configured."); } - const response = await this.client.get(`/parcels/track/${encodeURIComponent(trackingCode)}`); + const response = await this.client.get(`/parcel/track/${encodeURIComponent(trackingCode)}`); const data = response.data; + const events = data?.tracking || []; + const latest = events[events.length - 1]; return { success: true, courier: "redx", tracking_code: trackingCode, - status: data.tracking?.status || "in_transit", - timeline: (data.tracking?.history || []).map((h) => ({ - status: h.status, - time: h.created_at, - note: h.message, - })), + status: latest?.message_en || "unknown", + updated_at: latest?.time || new Date().toISOString(), + timeline: events.map((e) => ({ status: e.message_en || "", time: e.time || "", note: e.message_bn })), raw_response: data, }; } async getBalance() { - return { - success: true, - courier: "redx", - current_balance: 0, - raw_response: { message: "RedX payouts are disbursed directly to connected merchant bank accounts." }, - }; - } - async getLocations(cityName) { - const response = await this.client.get("/areas"); - const areas = response.data.areas || []; - return { - success: true, - courier: "redx", - locations: areas.map((a) => ({ - city_id: a.id, - city_name: a.name, - zone_name: a.zone_name, - area_name: a.name, - })), - }; + throw new Error("RedX does not expose a merchant balance endpoint via its public API — check your balance in the RedX merchant panel."); } } diff --git a/dist/config.d.ts b/dist/config.d.ts index af1c188..de29f0c 100644 --- a/dist/config.d.ts +++ b/dist/config.d.ts @@ -17,14 +17,22 @@ export interface CourierConfig { redx: { apiToken: string; baseUrl: string; + pickupStoreId: string; enabled: boolean; }; paperfly: { - user: string; - pass: string; - key: string; + apiKey: string; + username: string; + password: string; + storeName: string; baseUrl: string; enabled: boolean; }; } -export declare function loadConfig(): CourierConfig; +export type EnvSource = Record; +/** + * 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 declare function loadConfig(src?: EnvSource): CourierConfig; diff --git a/dist/config.js b/dist/config.js index 9200522..4842d81 100644 --- a/dist/config.js +++ b/dist/config.js @@ -1,41 +1,44 @@ import dotenv from "dotenv"; dotenv.config(); -export function loadConfig() { - const steadfastApiKey = process.env.STEADFAST_API_KEY || ""; - const steadfastSecretKey = process.env.STEADFAST_SECRET_KEY || ""; - const pathaoClientId = process.env.PATHAO_CLIENT_ID || ""; - const pathaoClientSecret = process.env.PATHAO_CLIENT_SECRET || ""; - 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 || ""; +/** + * 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 = process.env) { + const steadfastApiKey = src.STEADFAST_API_KEY || ""; + const steadfastSecretKey = src.STEADFAST_SECRET_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), + apiToken: src.REDX_API_TOKEN || "", + baseUrl: src.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta", + pickupStoreId: src.REDX_PICKUP_STORE_ID || "", + enabled: Boolean(src.REDX_API_TOKEN), }, paperfly: { - user: paperflyUser, - pass: paperflyPass, - key: paperflyKey, - baseUrl: process.env.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd", - enabled: Boolean(paperflyUser && paperflyPass && paperflyKey), + apiKey: src.PAPERFLY_API_KEY || "", + username: src.PAPERFLY_USERNAME || "", + password: src.PAPERFLY_PASSWORD || "", + storeName: src.PAPERFLY_STORE_NAME || "", + baseUrl: src.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd", + enabled: Boolean(src.PAPERFLY_API_KEY && src.PAPERFLY_STORE_NAME), }, }; } diff --git a/dist/http.d.ts b/dist/http.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/http.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/http.js b/dist/http.js new file mode 100644 index 0000000..6f82275 --- /dev/null +++ b/dist/http.js @@ -0,0 +1,155 @@ +import express 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 } 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) { + const h = req.headers; + const q = req.query; + const pick = (header, query) => { + 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"), + REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"), + REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"), + REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"), + PAPERFLY_API_KEY: pick("x-paperfly-api-key", "paperfly_key"), + PAPERFLY_USERNAME: pick("x-paperfly-username", "paperfly_username"), + PAPERFLY_PASSWORD: pick("x-paperfly-password", "paperfly_password"), + PAPERFLY_STORE_NAME: pick("x-paperfly-store-name", "paperfly_store"), + }; +} +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, x-redx-api-token, x-redx-base-url, x-redx-pickup-store-id, x-paperfly-api-key, x-paperfly-username, x-paperfly-password, x-paperfly-store-name"); + 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 = {}; +app.post("/mcp", async (req, res) => { + const sessionId = req.headers["mcp-session-id"]; + let transport; + 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) { + 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, res) { + const sessionId = req.headers["mcp-session-id"]; + 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.3.2", sessions: Object.keys(transports).length })); +const PRIVACY_HTML = `Aura Courier MCP — Privacy Policy + +

Aura Courier MCP — Privacy Policy

+

Last updated: 8 September 2026 · Operated by Aura Ajentic AI (auraajenticai.cloud)

+

Aura Courier MCP is a Model Context Protocol server that lets an AI agent book and track parcels with Bangladeshi couriers (Steadfast, Pathao, RedX, Paperfly) and screen cash-on-delivery fraud risk. This policy explains what data it handles and how.

+

1. What we process

+
    +
  • Your courier credentials (API keys/secrets, merchant username/password) — supplied by you per request as HTTP headers or environment variables.
  • +
  • Order details you pass to book or track a parcel — recipient name, phone, address, COD amount, item info.
  • +
  • Phone numbers submitted to check_fraud_risk to compute a delivery/return-risk score.
  • +
+

2. How we use it

+

Solely to carry out the operation you request — forwarding the booking/tracking/balance call to the specific courier you select, or computing a risk score. We do not use your data for advertising or profiling.

+

3. Storage & retention

+

Your courier credentials are used only for the duration of the request and are never persisted by Aura. Requests are processed transiently; Aura does not build a long-term store of your merchant keys. Data you send is retained only as long as needed to complete the request.

+

4. Third-party sharing

+

To fulfil your request, order data is transmitted to the courier provider you choose (Steadfast, Pathao, RedX or Paperfly), each governed by its own privacy terms. We do not sell your data or share it with anyone else.

+

5. Security

+

All traffic is over HTTPS. Credentials travel as request headers and are held only in memory for the active session.

+

6. Your choices

+

You control which couriers you connect and which credentials you provide. Disconnecting the connector stops all processing.

+

7. Contact

+

Questions or requests: ceo@auraajenticai.cloud · auraajenticai.cloud

+`; +app.get("/privacy", (_req, res) => res.type("html").send(PRIVACY_HTML)); +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(`Aura Courier MCP` + + `` + + `

🚚 Aura Courier MCP

Live remote MCP endpoint for Bangladesh couriers — Steadfast & Pathao.

` + + `

Connect your AI to POST /mcp and pass your courier keys as headers (x-steadfast-api-key, x-steadfast-secret-key) or query params (?steadfast_key=…&steadfast_secret=…).

` + + `

By Aura Ajentic AI · docs & setup

`); + } + }); +}); +app.listen(PORT, () => { + console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`); +}); diff --git a/dist/index.js b/dist/index.js index 658f133..fa26315 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,159 +1,14 @@ #!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { CourierRegistry } from "./registry.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 = [ - { - 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, - 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); - return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }; - } - case "get_balance": { - const result = await registry.getBalance(String(args?.courier)); - 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) { - 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) => { console.error("Fatal error running Aura Courier MCP:", error); diff --git a/dist/registry.d.ts b/dist/registry.d.ts index fb7fd94..bb0579c 100644 --- a/dist/registry.d.ts +++ b/dist/registry.d.ts @@ -1,8 +1,9 @@ import { CourierAdapter } from "./adapters/base.js"; +import { CourierConfig } from "./config.js"; import { BalanceResponse, FraudRiskScoreResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "./types.js"; export declare class CourierRegistry { private adapters; - constructor(); + constructor(config?: CourierConfig); listCouriers(): { courier: SupportedCourier; is_configured: boolean; diff --git a/dist/registry.js b/dist/registry.js index 1c6ecdc..b3b61b4 100644 --- a/dist/registry.js +++ b/dist/registry.js @@ -1,20 +1,19 @@ 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 { RedxAdapter } from "./adapters/redx.js"; +import { PaperflyAdapter } from "./adapters/paperfly.js"; import { loadConfig } from "./config.js"; export class CourierRegistry { adapters = new Map(); - constructor() { - const config = loadConfig(); + constructor(config = loadConfig()) { const steadfast = new SteadfastAdapter(config.steadfast.apiKey, config.steadfast.secretKey, config.steadfast.baseUrl); this.adapters.set("steadfast", steadfast); const pathao = new PathaoAdapter(config.pathao.clientId, config.pathao.clientSecret, config.pathao.username, config.pathao.password, config.pathao.storeId, config.pathao.baseUrl); this.adapters.set("pathao", pathao); - const redx = new RedXAdapter(config.redx.apiToken, config.redx.baseUrl); + const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId); this.adapters.set("redx", redx); - const paperfly = new PaperflyAdapter(config.paperfly.user, config.paperfly.pass, config.paperfly.key, config.paperfly.baseUrl); + const paperfly = new PaperflyAdapter(config.paperfly.apiKey, config.paperfly.username, config.paperfly.password, config.paperfly.storeName, config.paperfly.baseUrl); this.adapters.set("paperfly", paperfly); } listCouriers() { @@ -36,7 +35,7 @@ export class CourierRegistry { courierName = req.courier; } else { - // Smart routing heuristic across all 4 couriers + // Smart routing heuristic const addr = req.recipient_address.toLowerCase(); if (addr.includes("dhaka") && (addr.includes("gulshan") || @@ -44,26 +43,10 @@ export class CourierRegistry { addr.includes("dhanmondi") || 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"; } } const adapter = this.getAdapter(courierName); @@ -73,26 +56,12 @@ export class CourierRegistry { if (courierName) { return await this.getAdapter(courierName).trackParcel(trackingCode); } - // Smart fallback across available configured adapters - const order = ["steadfast", "pathao", "redx", "paperfly"]; - let lastError = 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); } } async getBalance(courierName) { diff --git a/dist/server.d.ts b/dist/server.d.ts new file mode 100644 index 0000000..257fa58 --- /dev/null +++ b/dist/server.d.ts @@ -0,0 +1,10 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { CourierRegistry } from "./registry.js"; +export declare const TOOLS: Tool[]; +/** + * 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 declare function buildMcpServer(registry: CourierRegistry): Server; diff --git a/dist/server.js b/dist/server.js new file mode 100644 index 0000000..6a50b0c --- /dev/null +++ b/dist/server.js @@ -0,0 +1,123 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; +// Tool definitions — shared by the STDIO (npx) and HTTP (web-URL) entrypoints. +export const TOOLS = [ + { + name: "list_couriers", + annotations: { title: "List couriers", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, + description: "Show supported Bangladeshi couriers and check which credentials are active.", + inputSchema: { type: "object", properties: {}, required: [] }, + }, + { + name: "create_parcel", + annotations: { title: "Create parcel (book delivery)", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, + 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)" }, + item_type: { type: "string", description: "What's inside the parcel (used by RedX)" }, + value: { type: "number", description: "Declared parcel value in BDT (used by RedX; defaults to the COD amount)" }, + delivery_area_id: { type: "number", description: "RedX only: numeric delivery-area id (auto-resolved from the address if omitted)" }, + pickup_store_id: { type: "number", description: "RedX only: your pickup store id (optional)" }, + }, + required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"], + }, + }, + { + name: "track_parcel", + annotations: { title: "Track parcel", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, + 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", "redx", "paperfly"], description: "Optional courier name if known" }, + }, + required: ["tracking_code"], + }, + }, + { + name: "get_balance", + annotations: { title: "Get merchant balance", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, + 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", + annotations: { title: "Check COD fraud risk", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, + 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) { + const server = new Server({ name: "aura-courier-mcp", version: "2.3.2" }, { 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, + 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); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + case "get_balance": { + const result = await registry.getBalance(String(args?.courier)); + 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) { + return { + isError: true, + content: [{ type: "text", text: `Aura Courier MCP Error: ${error?.message || String(error)}` }], + }; + } + }); + return server; +} diff --git a/dist/types.d.ts b/dist/types.d.ts index 28efd04..1a5833f 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -9,6 +9,11 @@ export interface ParcelCreateRequest { note?: string; item_type?: string; item_weight?: number; + item_category?: string; + value?: number | string; + delivery_area?: string; + delivery_area_id?: number | string; + pickup_store_id?: number | string; } export interface ParcelResponse { success: boolean; @@ -50,15 +55,21 @@ export interface FraudRiskScoreResponse { recommendation: string; 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; }>; } diff --git a/package.json b/package.json index ae3e84f..2f3148f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aura-courier-mcp", - "version": "2.3.3", + "version": "2.3.2", "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao, RedX & Paperfly 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", diff --git a/src/http.ts b/src/http.ts index e82c1c7..75966c9 100644 --- a/src/http.ts +++ b/src/http.ts @@ -120,9 +120,37 @@ 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 }) + res.json({ ok: true, service: "aura-courier-mcp", version: "2.3.2", sessions: Object.keys(transports).length }) ); + +const PRIVACY_HTML = `Aura Courier MCP — Privacy Policy + +

Aura Courier MCP — Privacy Policy

+

Last updated: 8 September 2026 · Operated by Aura Ajentic AI (auraajenticai.cloud)

+

Aura Courier MCP is a Model Context Protocol server that lets an AI agent book and track parcels with Bangladeshi couriers (Steadfast, Pathao, RedX, Paperfly) and screen cash-on-delivery fraud risk. This policy explains what data it handles and how.

+

1. What we process

+
    +
  • Your courier credentials (API keys/secrets, merchant username/password) — supplied by you per request as HTTP headers or environment variables.
  • +
  • Order details you pass to book or track a parcel — recipient name, phone, address, COD amount, item info.
  • +
  • Phone numbers submitted to check_fraud_risk to compute a delivery/return-risk score.
  • +
+

2. How we use it

+

Solely to carry out the operation you request — forwarding the booking/tracking/balance call to the specific courier you select, or computing a risk score. We do not use your data for advertising or profiling.

+

3. Storage & retention

+

Your courier credentials are used only for the duration of the request and are never persisted by Aura. Requests are processed transiently; Aura does not build a long-term store of your merchant keys. Data you send is retained only as long as needed to complete the request.

+

4. Third-party sharing

+

To fulfil your request, order data is transmitted to the courier provider you choose (Steadfast, Pathao, RedX or Paperfly), each governed by its own privacy terms. We do not sell your data or share it with anyone else.

+

5. Security

+

All traffic is over HTTPS. Credentials travel as request headers and are held only in memory for the active session.

+

6. Your choices

+

You control which couriers you connect and which credentials you provide. Disconnecting the connector stops all processing.

+

7. Contact

+

Questions or requests: ceo@auraajenticai.cloud · auraajenticai.cloud

+`; + +app.get("/privacy", (_req, res) => res.type("html").send(PRIVACY_HTML)); + app.get("/", (_req, res) => { // Serve the marketing landing page; fall back to a minimal page if it's missing. res.sendFile(LANDING, (err) => { diff --git a/src/server.ts b/src/server.ts index c7680f4..a7e5e12 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,11 +11,13 @@ import { SupportedCourier } from "./types.js"; export const TOOLS: Tool[] = [ { name: "list_couriers", + annotations: { title: "List couriers", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, description: "Show supported Bangladeshi couriers and check which credentials are active.", inputSchema: { type: "object", properties: {}, required: [] }, }, { name: "create_parcel", + annotations: { title: "Create parcel (book delivery)", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao, RedX or Paperfly) with normalized response.", inputSchema: { type: "object", @@ -38,6 +40,7 @@ export const TOOLS: Tool[] = [ }, { name: "track_parcel", + annotations: { title: "Track parcel", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, description: "Track shipment delivery status across Steadfast or Pathao using Tracking Code / Consignment ID.", inputSchema: { type: "object", @@ -50,6 +53,7 @@ export const TOOLS: Tool[] = [ }, { name: "get_balance", + annotations: { title: "Get merchant balance", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, description: "Retrieve current merchant account balance and payout details from a courier.", inputSchema: { type: "object", @@ -61,6 +65,7 @@ export const TOOLS: Tool[] = [ }, { name: "check_fraud_risk", + annotations: { title: "Check COD fraud risk", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, description: "Analyze Bangladeshi customer phone number delivery history and return/fraud risk score before dispatching.", inputSchema: { type: "object", @@ -79,7 +84,7 @@ export const TOOLS: Tool[] = [ */ export function buildMcpServer(registry: CourierRegistry): Server { const server = new Server( - { name: "aura-courier-mcp", version: "2.1.0" }, + { name: "aura-courier-mcp", version: "2.3.2" }, { capabilities: { tools: {} } } ); From 8c661d0422abce7f174e471c77d13d8a8f683e0b Mon Sep 17 00:00:00 2001 From: Amirul Islam Redwan Date: Tue, 8 Sep 2026 17:31:33 +0600 Subject: [PATCH 11/11] feat: add smithery config and update mcp registry --- package-lock.json | 4 +-- smithery.yaml | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 smithery.yaml diff --git a/package-lock.json b/package-lock.json index ae6a54c..096ca6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aura-courier-mcp", - "version": "2.1.0", + "version": "2.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aura-courier-mcp", - "version": "2.1.0", + "version": "2.3.2", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.0", diff --git a/smithery.yaml b/smithery.yaml new file mode 100644 index 0000000..bfaeea7 --- /dev/null +++ b/smithery.yaml @@ -0,0 +1,77 @@ +# Smithery configuration for Aura Courier MCP +# https://smithery.ai/docs/config — how the server is launched + what config it needs. +# Every courier's keys are optional: you connect only the couriers you use, and +# `check_fraud_risk` works with no keys at all. Keys are read per-process (STDIO) or +# per-request (the remote HTTP endpoint) and are never stored by Aura. + +startCommand: + type: stdio + configSchema: + type: object + title: Aura Courier MCP configuration + description: Provide the API credentials for the Bangladeshi couriers you want to use. All fields are optional. + properties: + steadfastApiKey: + type: string + title: Steadfast API Key + steadfastSecretKey: + type: string + title: Steadfast Secret Key + pathaoClientId: + type: string + title: Pathao Client ID + pathaoClientSecret: + type: string + title: Pathao Client Secret + pathaoUsername: + type: string + title: Pathao Username + pathaoPassword: + type: string + title: Pathao Password + pathaoStoreId: + type: string + title: Pathao Store ID + redxApiToken: + type: string + title: RedX API Access Token + redxPickupStoreId: + type: string + title: RedX Pickup Store ID + paperflyApiKey: + type: string + title: Paperfly API Key + paperflyUsername: + type: string + title: Paperfly Merchant Username + paperflyPassword: + type: string + title: Paperfly Merchant Password + paperflyStoreName: + type: string + title: Paperfly Store Name + required: [] + commandFunction: |- + (config) => ({ + command: 'node', + args: ['dist/index.js'], + env: { + STEADFAST_API_KEY: config.steadfastApiKey || '', + STEADFAST_SECRET_KEY: config.steadfastSecretKey || '', + PATHAO_CLIENT_ID: config.pathaoClientId || '', + PATHAO_CLIENT_SECRET: config.pathaoClientSecret || '', + PATHAO_USERNAME: config.pathaoUsername || '', + PATHAO_PASSWORD: config.pathaoPassword || '', + PATHAO_STORE_ID: config.pathaoStoreId || '', + REDX_API_TOKEN: config.redxApiToken || '', + REDX_PICKUP_STORE_ID: config.redxPickupStoreId || '', + PAPERFLY_API_KEY: config.paperflyApiKey || '', + PAPERFLY_USERNAME: config.paperflyUsername || '', + PAPERFLY_PASSWORD: config.paperflyPassword || '', + PAPERFLY_STORE_NAME: config.paperflyStoreName || '' + } + }) + exampleConfig: + steadfastApiKey: your-steadfast-api-key + steadfastSecretKey: your-steadfast-secret-key + redxApiToken: your-redx-token