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: {} } } );