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 = `
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.
check_fraud_risk to compute a delivery/return-risk score.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.
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.
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.
All traffic is over HTTPS. Credentials travel as request headers and are held only in memory for the active session.
You control which couriers you connect and which credentials you provide. Disconnecting the connector stops all processing.
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(`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=…).