diff --git a/dist/http.js b/dist/http.js new file mode 100644 index 0000000..29babb3 --- /dev/null +++ b/dist/http.js @@ -0,0 +1,263 @@ +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, TOOLS } from "./server.js"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +const PORT = Number(process.env.PORT || 8080); +const APP_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const LANDING = path.join(APP_DIR, "index.html"); +const HEALTH_HTML = path.join(APP_DIR, "health.html"); +const MCP_EXPLORER_HTML = path.join(APP_DIR, "mcp_explorer.html"); +// Pull this client's courier keys from request headers or query params, with fallback to environment +function keysFromRequest(req) { + const h = req.headers; + const q = req.query; + const pick = (header, query, envVar) => { + const hv = h[header]; + const v = (Array.isArray(hv) ? hv[0] : hv) ?? q[query]; + return v ? String(v) : (envVar ? process.env[envVar] : undefined); + }; + return { + STEADFAST_API_KEY: pick("x-steadfast-api-key", "steadfast_key", "STEADFAST_API_KEY"), + STEADFAST_SECRET_KEY: pick("x-steadfast-secret-key", "steadfast_secret", "STEADFAST_SECRET_KEY"), + STEADFAST_BASE_URL: pick("x-steadfast-base-url", "steadfast_base_url", "STEADFAST_BASE_URL"), + PATHAO_CLIENT_ID: pick("x-pathao-client-id", "pathao_client_id", "PATHAO_CLIENT_ID"), + PATHAO_CLIENT_SECRET: pick("x-pathao-client-secret", "pathao_client_secret", "PATHAO_CLIENT_SECRET"), + PATHAO_USERNAME: pick("x-pathao-username", "pathao_username", "PATHAO_USERNAME"), + PATHAO_PASSWORD: pick("x-pathao-password", "pathao_password", "PATHAO_PASSWORD"), + PATHAO_STORE_ID: pick("x-pathao-store-id", "pathao_store_id", "PATHAO_STORE_ID"), + PATHAO_BASE_URL: pick("x-pathao-base-url", "pathao_base_url", "PATHAO_BASE_URL"), + REDX_API_TOKEN: pick("x-redx-api-token", "redx_token", "REDX_API_TOKEN"), + REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url", "REDX_BASE_URL"), + REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id", "REDX_PICKUP_STORE_ID"), + PAPERFLY_API_KEY: pick("x-paperfly-api-key", "paperfly_key", "PAPERFLY_API_KEY"), + PAPERFLY_USERNAME: pick("x-paperfly-username", "paperfly_username", "PAPERFLY_USERNAME"), + PAPERFLY_PASSWORD: pick("x-paperfly-password", "paperfly_password", "PAPERFLY_PASSWORD"), + PAPERFLY_STORE_NAME: pick("x-paperfly-store-name", "paperfly_store", "PAPERFLY_STORE_NAME"), + GOOGLE_MAPS_API_KEY: pick("x-google-maps-key", "google_maps_key", "GOOGLE_MAPS_API_KEY"), + }; +} +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, x-google-maps-key"); + 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(); +}); +const sessions = new Map(); +const SESSION_TTL_MS = 60 * 60 * 1000; // 1-hour idle expiration +// Sweep abandoned sessions every 15 minutes to prevent heap bloat +setInterval(() => { + const now = Date.now(); + for (const [sid, entry] of sessions.entries()) { + if (now - entry.lastActiveAt > SESSION_TTL_MS) { + console.log(`[Memory GC] Evicting stale MCP session: ${sid}`); + try { + entry.transport.close?.(); + } + catch { } + sessions.delete(sid); + } + } +}, 15 * 60 * 1000).unref(); +app.post("/mcp", async (req, res) => { + const sessionId = req.headers["mcp-session-id"]; + let transport; + if (sessionId && sessions.has(sessionId)) { + const entry = sessions.get(sessionId); + entry.lastActiveAt = Date.now(); + transport = entry.transport; + } + else if (!sessionId && isInitializeRequest(req.body)) { + const registry = new CourierRegistry(loadConfig(keysFromRequest(req))); + const server = buildMcpServer(registry); + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (sid) => { + sessions.set(sid, { transport, lastActiveAt: Date.now() }); + }, + }); + transport.onclose = () => { + if (transport.sessionId) + sessions.delete(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 or Browser Explorer) +app.get("/mcp", async (req, res) => { + const sessionId = req.headers["mcp-session-id"]; + if (!sessionId || !sessions.has(sessionId)) { + // If opened directly by a human in a web browser, serve the interactive MCP Protocol Explorer! + if (req.headers.accept?.includes("text/html")) { + return res.sendFile(MCP_EXPLORER_HTML, (err) => { + if (err && !res.headersSent) { + res.status(400).send("Invalid or missing session ID. Visit https://courier.auraajenticai.cloud for instructions."); + } + }); + } + res.status(400).send("Invalid or missing session ID"); + return; + } + const entry = sessions.get(sessionId); + entry.lastActiveAt = Date.now(); + await entry.transport.handleRequest(req, res); +}); +// DELETE (end session) for an existing session. +app.delete("/mcp", async (req, res) => { + const sessionId = req.headers["mcp-session-id"]; + if (!sessionId || !sessions.has(sessionId)) { + res.status(400).send("Invalid or missing session ID"); + return; + } + const entry = sessions.get(sessionId); + entry.lastActiveAt = Date.now(); + await entry.transport.handleRequest(req, res); +}); +/** + * Official Steadfast Webhook Integration Endpoint + * Callback URL: https://courier.auraajenticai.cloud/webhooks/steadfast + */ +app.post("/webhooks/steadfast", (req, res) => { + const payload = req.body; + console.log("[Steadfast Webhook Received]:", JSON.stringify(payload)); + if (!payload || !payload.consignment_id) { + return res.status(400).json({ + status: "error", + message: "Invalid consignment ID.", + }); + } + const notifType = payload.notification_type || "delivery_status"; + const consignmentId = payload.consignment_id; + const status = payload.status; + const invoice = payload.invoice; + console.log(`[Steadfast Update] Consignment: ${consignmentId} | Invoice: ${invoice} | Type: ${notifType} | Status: ${status}`); + if (status === "cancelled") { + console.warn(`[Steadfast NDR Alert] Consignment ${consignmentId} was cancelled. Initiating NDR triage.`); + } + return res.status(200).json({ + status: "success", + message: "Webhook received successfully.", + }); +}); +/** + * Official Pathao Webhook Integration Endpoint + * Callback URL: https://courier.auraajenticai.cloud/webhooks/pathao + */ +app.post("/webhooks/pathao", (req, res) => { + const payload = req.body; + console.log("[Pathao Webhook Received]:", JSON.stringify(payload)); + return res.status(200).json({ + status: "success", + message: "Pathao webhook received successfully.", + }); +}); +/** + * Official RedX Webhook Integration Endpoint + * Callback URL: https://courier.auraajenticai.cloud/webhooks/redx + */ +app.post("/webhooks/redx", (req, res) => { + const payload = req.body; + console.log("[RedX Webhook Received]:", JSON.stringify(payload)); + return res.status(200).json({ + status: "success", + message: "RedX webhook received successfully.", + }); +}); +/** + * Official Paperfly Webhook Integration Endpoint + * Callback URL: https://courier.auraajenticai.cloud/webhooks/paperfly + */ +app.post("/webhooks/paperfly", (req, res) => { + const payload = req.body; + console.log("[Paperfly Webhook Received]:", JSON.stringify(payload)); + return res.status(200).json({ + status: "success", + message: "Paperfly webhook received successfully.", + }); +}); +/** + * Health & Live Telemetry Endpoint + * Serves visual SaaS Dashboard for browsers, and JSON for API monitors & Docker probes. + */ +app.get("/health", (req, res) => { + const mem = process.memoryUsage(); + const data = { + ok: true, + service: "aura-courier-mcp", + version: "2.4.0", + tools_count: TOOLS.length, + spatial_engine: "Google Maps Platform (gmp_git_agentskills_v1)", + fraud_engine: "Steadfast Nationwide API + BD Prefix Validator", + webhooks: { + steadfast: "/webhooks/steadfast", + pathao: "/webhooks/pathao", + redx: "/webhooks/redx", + paperfly: "/webhooks/paperfly", + }, + performance: { + sessions_active: sessions.size, + heap_used_mb: Math.round((mem.heapUsed / 1024 / 1024) * 10) / 10, + heap_total_mb: Math.round((mem.heapTotal / 1024 / 1024) * 10) / 10, + rss_mb: Math.round((mem.rss / 1024 / 1024) * 10) / 10, + uptime_seconds: Math.round(process.uptime()), + }, + }; + // If a browser is viewing /health and hasn't explicitly asked for format=json, serve the visual dashboard! + if (req.headers.accept?.includes("text/html") && req.query.format !== "json") { + return res.sendFile(HEALTH_HTML, (err) => { + if (err && !res.headersSent) + res.json(data); + }); + } + // Otherwise return JSON for curl, metrics monitors, and Docker health checks + res.json(data); +}); +app.get("/", (_req, res) => { + res.sendFile(LANDING, (err) => { + if (err && !res.headersSent) { + res + .type("html") + .send(`Aura Courier MCP` + + `` + + `

🚚 Aura Courier MCP v2.4.0

Enterprise Bangladesh courier MCP & Google Maps Platform spatial hub.

` + + `

Connect your AI to POST /mcp with Streamable-HTTP.

` + + `

Webhooks: POST /webhooks/steadfast ¡ POST /webhooks/pathao

` + + `

By Aura Ajentic AI ¡ docs & setup

`); + } + }); +}); +app.listen(PORT, () => { + console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`); +}); diff --git a/health.html b/health.html new file mode 100644 index 0000000..bf9d91d --- /dev/null +++ b/health.html @@ -0,0 +1,550 @@ + + + + + + Aura Courier MCP — Live Health & Telemetry Dashboard + + + + + + + + + +
+ +
+ +
+
+
+

Live Health & Telemetry Matrix

+

Real-time metrics directly from Docker container ix4jsg321uqfrko9qrrq4k7m

+
+
+ + Live auto-refresh: active +
+
+ + +
+
+
API Health
+
200 OK
+
Streamable-HTTP Ready
+
+
+
Client Roundtrip Latency
+
-- ms
+
Real-time browser ping
+
+
+
Registered Tools
+
9
+
Spatial, Fraud & Courier
+
+
+
System Uptime
+
--
+
Continuous production runtime
+
+
+ + +

Carrier Webhook Listeners (Real-Time Ingestion)

+
+
+
+ ● Steadfast Courier + LISTENING +
+
POST /webhooks/steadfast
+
Consignment dispatch, tracking & NDR updates
+
+
+
+ ● Pathao Express + LISTENING +
+
POST /webhooks/pathao
+
Real-time courier rider events & return alerts
+
+
+
+ ● RedX Delivery + LISTENING +
+
POST /webhooks/redx
+
Doorstep pickup & nationwide hub tracking
+
+
+
+ ● Paperfly Go + LISTENING +
+
POST /webhooks/paperfly
+
Remote union delivery status & OTP verification
+
+
+ + +

Intelligent AI Engines & Infrastructure

+
+
+
Spatial & Geocoding Engine
+
Google Maps Platform
+
Attribution: gmp_git_agentskills_v1 ¡ Routes v2 & Geocoding
+
+
+
Fraud Risk Detection
+
Steadfast + BD Telco Regex
+
Zero Mock Data ¡ 013-019 carrier prefix & historical cancel rate
+
+
+ + +
+
Node.js V8 Runtime Telemetry
+
+
+
+ Heap Memory Allocated + -- MB / -- MB +
+
+
+
+
+
+
+ Resident Set Size (RSS) + -- MB +
+
+
+
+
+
+
+ + +
+
+ Raw JSON Payload (Automated Monitoring Output) + Open in New Tab ↗ +
+
Fetching live telemetry...
+
+
+ + + + + + diff --git a/index.html b/index.html index de905c2..f0c4342 100644 --- a/index.html +++ b/index.html @@ -155,10 +155,12 @@
@@ -181,14 +183,17 @@

āĻāĻ• connector — āĻŦāĻžāĻ‚āϞāĻžāĻĻ⧇āĻļ⧇āϰ āϏāĻŦ courier āĻ“ spatial intelligenceāĨ¤ Claude, Antigravity, Cursor āĻāĻŦāĻ‚ n8n AI Agents āĻĨ⧇āϕ⧇ ā§§-āĻ•ā§āϞāĻŋāϕ⧇ parcel āĻŦ⧁āĻ• āĻ•āϰ⧁āύ, āĻŸā§āĻ°ā§āϝāĻžāĻ• āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ āϜāĻŋāϰ⧋-āϰāĻŋāĻ¸ā§āĻ• COD āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύāĨ¤

@@ -200,6 +205,87 @@
+ + +
+
+
// āχāĻ¨ā§āϟāĻžāϰ⧇āĻ•ā§āϟāĻŋāĻ­ āϞāĻžāχāĻ­ āĻ¸ā§āϝāĻžāĻ¨ā§āĻĄāĻŦāĻ•ā§āϏ
+

āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇āχ āĻŸā§‡āĻ¸ā§āϟ āĻ•āϰ⧁āύ āϕ⧁āϰāĻŋ⧟āĻžāϰ āĻāφāχ āĻ“ āĻœā§‹āύ āĻ•ā§āϝāĻžāϞāϕ⧁āϞ⧇āϟāϰ

+

āϕ⧋āύ⧋ āϕ⧋āĻĄāĻŋāĻ‚ āĻŦāĻž āχāĻ¨ā§āϏāϟāϞ⧇āĻļāύ āĻ›āĻžā§œāĻžāχ āϏāϰāĻžāϏāϰāĻŋ āĻŦāĻžāĻ‚āϞāĻžāĻĻ⧇āĻļ⧇āϰ ā§Ŧā§Ē āĻœā§‡āϞāĻžāϰ āĻœā§‹āύ āĻŸā§āϝāĻžāϰāĻŋāĻĢ āĻāĻŦāĻ‚ āϜāĻŋāϰ⧋-āĻŽāĻ• āĻĢā§āϰāĻĄ āϰāĻŋāĻ¸ā§āĻ• āχāĻžā§āϜāĻŋāύ āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύāĨ¤

+
+ +
+ +
+
+
+ 📍 āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ āĻœā§‹āύ āĻ“ āϚāĻžāĻ°ā§āϜ āĻ•ā§āϝāĻžāϞāϕ⧁āϞ⧇āϟāϰ + Google Maps +
+

āĻ—āĻ¨ā§āϤāĻŦā§āϝ āĻœā§‡āϞāĻž/āĻĨāĻžāύāĻž āĻ…āύ⧁āϝāĻžā§Ÿā§€ āϰāĻŋā§Ÿā§‡āϞ-āϟāĻžāχāĻŽ āϕ⧁āϰāĻŋ⧟āĻžāϰ āĻŸā§āϝāĻžāϰāĻŋāĻĢ āĻ“ SLA āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖāĨ¤

+
+ +
+ + + + + +
+ +
+
+
āĻĒā§āϰāĻžāĻ•ā§āĻ•āϞāĻŋāϤ āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ āĻĢāĻŋ
+
ā§ŗā§Žā§Ļ BDT
+
+
+
āĻĒā§āϰāĻ¤ā§āϝāĻžāĻļāĻŋāϤ āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ
+
⧍ā§Ē-ā§Ēā§Ž āϘāĻŖā§āϟāĻž
+
+
+
+ + +
+
+
+ đŸ›Ąī¸ āϜāĻŋāϰ⧋-āĻŽāĻ• āĻĢā§āϰāĻĄ āϰāĻŋāĻ¸ā§āĻ• āĻ…ā§āϝāĻžāύāĻžāϞāĻžāχāϜāĻžāϰ + Zero Mock +
+

āϜāĻžāϤ⧀āϝāĻŧ āϕ⧁āϰāĻŋ⧟āĻžāϰ āύ⧇āϟāĻ“ā§ŸāĻžāĻ°ā§āĻ• āĻ“ āĻŸā§‡āϞāϕ⧋ āĻĒā§āϰāĻŋāĻĢāĻŋāĻ•ā§āϏ (013-019) āĻĻāĻŋā§Ÿā§‡ āϰāĻŋāĻ¸ā§āĻ• āĻ¸ā§āϕ⧋āϰ āϝāĻžāϚāĻžāχāĨ¤

+
+ +
+ +
+ + +
+
+ +
+
+
āĻŸā§‡āϞāϕ⧋ āĻ…āĻĒāĻžāϰ⧇āϟāϰ āĻ“ āĻŦ⧈āϧāϤāĻž
+
Grameenphone (āϏāĻ āĻŋāĻ• āύāĻŽā§āĻŦāϰ)
+
+
+
āϰāĻŋāĻ¸ā§āĻ• āĻ¸ā§āϕ⧋āϰ
+
āϞ⧋ āϰāĻŋāĻ¸ā§āĻ• (ā§Ļ% āĻŦāĻžāϤāĻŋāϞ)
+
+
+
+
+
+
@@ -516,6 +602,31 @@ const i18n = { bn: { header_badge: "āĻāĻŽāϏāĻŋāĻĒāĻŋ āĻŦāĻžāĻ‚āϞāĻžāĻĻ⧇āĻļ v2.4", + nav_couriers: "āϕ⧁āϰāĻŋ⧟āĻžāϰ", + nav_tools: "āϟ⧁āϞāϏ", + nav_sandbox: "āϞāĻžāχāĻ­ āĻ¸ā§āϝāĻžāĻ¨ā§āĻĄāĻŦāĻ•ā§āϏ", + nav_mcp: "/mcp āĻĒā§āϞ⧇āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ ↗", + nav_health: "/health āĻ¸ā§āĻŸā§āϝāĻžāϟāĻžāϏ ↗", + cta_mcp_playground: "đŸ§Ē MCP āĻĒā§āϞ⧇āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ ↗", + cta_health_dashboard: "📊 āϞāĻžāχāĻ­ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻšā§‡āϞāĻĨ ↗", + cta_sandbox: "⚡ āϞāĻžāχāĻ­ āĻ¸ā§āϝāĻžāĻ¨ā§āĻĄāĻŦāĻ•ā§āϏ āĻŸā§āϰāĻžāχ āĻ•āϰ⧁āύ", + cta_aura_main: "Aura āĻŽā§‡āχāύ āϏāĻžāχāϟ ↗", + sandbox_eyebrow: "// āχāĻ¨ā§āϟāĻžāϰ⧇āĻ•ā§āϟāĻŋāĻ­ āϞāĻžāχāĻ­ āĻ¸ā§āϝāĻžāĻ¨ā§āĻĄāĻŦāĻ•ā§āϏ", + sandbox_heading: "āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇āχ āĻŸā§‡āĻ¸ā§āϟ āĻ•āϰ⧁āύ āϕ⧁āϰāĻŋ⧟āĻžāϰ āĻāφāχ āĻ“ āĻœā§‹āύ āĻ•ā§āϝāĻžāϞāϕ⧁āϞ⧇āϟāϰ", + sandbox_subtitle: "āϕ⧋āύ⧋ āϕ⧋āĻĄāĻŋāĻ‚ āĻŦāĻž āχāĻ¨ā§āϏāϟāϞ⧇āĻļāύ āĻ›āĻžā§œāĻžāχ āϏāϰāĻžāϏāϰāĻŋ āĻŦāĻžāĻ‚āϞāĻžāĻĻ⧇āĻļ⧇āϰ ā§Ŧā§Ē āĻœā§‡āϞāĻžāϰ āĻœā§‹āύ āĻŸā§āϝāĻžāϰāĻŋāĻĢ āĻāĻŦāĻ‚ āϜāĻŋāϰ⧋-āĻŽāĻ• āĻĢā§āϰāĻĄ āϰāĻŋāĻ¸ā§āĻ• āχāĻžā§āϜāĻŋāύ āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύāĨ¤", + calc_title: "📍 āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ āĻœā§‹āύ āĻ“ āϚāĻžāĻ°ā§āϜ āĻ•ā§āϝāĻžāϞāϕ⧁āϞ⧇āϟāϰ", + calc_desc: "āĻ—āĻ¨ā§āϤāĻŦā§āϝ āĻœā§‡āϞāĻž/āĻĨāĻžāύāĻž āĻ…āύ⧁āϝāĻžā§Ÿā§€ āϰāĻŋā§Ÿā§‡āϞ-āϟāĻžāχāĻŽ āϕ⧁āϰāĻŋ⧟āĻžāϰ āĻŸā§āϝāĻžāϰāĻŋāĻĢ āĻ“ SLA āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖāĨ¤", + calc_lbl_dest: "āĻ—āĻ¨ā§āϤāĻŦā§āϝ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ:", + calc_lbl_weight: "āĻĒāĻžāĻ°ā§āϏ⧇āϞ⧇āϰ āĻ“āϜāύ:", + calc_est_fee: "āĻĒā§āϰāĻžāĻ•ā§āĻ•āϞāĻŋāϤ āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ āĻĢāĻŋ", + calc_sla: "āĻĒā§āϰāĻ¤ā§āϝāĻžāĻļāĻŋāϤ āĻĄā§‡āϞāĻŋāĻ­āĻžāϰāĻŋ", + fraud_title: "đŸ›Ąī¸ āϜāĻŋāϰ⧋-āĻŽāĻ• āĻĢā§āϰāĻĄ āϰāĻŋāĻ¸ā§āĻ• āĻ…ā§āϝāĻžāύāĻžāϞāĻžāχāϜāĻžāϰ", + fraud_desc: "āϜāĻžāϤ⧀āϝāĻŧ āϕ⧁āϰāĻŋ⧟āĻžāϰ āύ⧇āϟāĻ“ā§ŸāĻžāĻ°ā§āĻ• āĻ“ āĻŸā§‡āϞāϕ⧋ āĻĒā§āϰāĻŋāĻĢāĻŋāĻ•ā§āϏ (013-019) āĻĻāĻŋā§Ÿā§‡ āϰāĻŋāĻ¸ā§āĻ• āĻ¸ā§āϕ⧋āϰ āϝāĻžāϚāĻžāχāĨ¤", + fraud_lbl_phone: "āĻ—ā§āϰāĻžāĻšāϕ⧇āϰ āĻĢā§‹āύ āύāĻŽā§āĻŦāϰ āϞāĻŋāϖ⧁āύ:", + fraud_btn_check: "āϝāĻžāϚāĻžāχ", + fraud_operator: "āĻŸā§‡āϞāϕ⧋ āĻ…āĻĒāĻžāϰ⧇āϟāϰ āĻ“ āĻŦ⧈āϧāϤāĻž", + fraud_risk_score: "āϰāĻŋāĻ¸ā§āĻ• āĻ¸ā§āϕ⧋āϰ", + nav_glama: "āĻ—ā§āϞāĻžāĻŽāĻž āϰ⧇āϜāĻŋāĻ¸ā§āĻŸā§āϰāĻŋ ↗", nav_github: "āĻ—āĻŋāϟāĻšāĻžāĻŦ ↗", nav_client: "āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āϟ āĻĒā§‹āĻ°ā§āϟāĻžāϞ ↗", @@ -594,6 +705,31 @@ const i18n = { }, en: { header_badge: "MCP Bangladesh v2.4", + nav_couriers: "Couriers", + nav_tools: "Tools", + nav_sandbox: "Live Sandbox", + nav_mcp: "/mcp Playground ↗", + nav_health: "/health Status ↗", + cta_mcp_playground: "đŸ§Ē MCP Playground ↗", + cta_health_dashboard: "📊 Live Health Telemetry ↗", + cta_sandbox: "⚡ Try Live Sandbox", + cta_aura_main: "Aura Main Site ↗", + sandbox_eyebrow: "// INTERACTIVE LIVE SANDBOX", + sandbox_heading: "Test Courier AI & Zone Calculator in Browser", + sandbox_subtitle: "Directly calculate 64-district delivery tariffs and test zero-mock fraud risk verification right from your browser.", + calc_title: "📍 Delivery Zone & Fee Calculator", + calc_desc: "Real-time tariff and SLA calculation based on destination subdistrict and district.", + calc_lbl_dest: "Select Destination:", + calc_lbl_weight: "Parcel Weight:", + calc_est_fee: "Estimated Delivery Fee", + calc_sla: "Estimated SLA", + fraud_title: "đŸ›Ąī¸ Zero-Mock Fraud Risk Analyzer", + fraud_desc: "Evaluate delivery risk using nationwide carrier databases and BD telco prefixes (013-019).", + fraud_lbl_phone: "Enter Customer Phone:", + fraud_btn_check: "Verify", + fraud_operator: "Telco Operator & Validity", + fraud_risk_score: "Risk Score", + nav_glama: "Glama Registry ↗", nav_github: "GitHub ↗", nav_client: "Client Portal ↗", @@ -714,7 +850,56 @@ function switchTab(key, btnEl) { document.getElementById('code-snippet').textContent = configs[key]; } -async function updateLiveTelemetry() { +async +function runSandboxCalc() { + const dest = document.getElementById('calc-dest').value; + const weight = parseInt(document.getElementById('calc-weight').value || '1', 10); + let baseFee = 80; + let sla = currentLang === 'bn' ? '⧍ā§Ē-ā§Ēā§Ž āϘāĻŖā§āϟāĻž' : '24-48 Hours'; + + if (dest === 'sub_dhaka') { + baseFee = 100; + sla = currentLang === 'bn' ? '⧍ā§Ē-ā§Ēā§Ž āϘāĻŖā§āϟāĻž' : '24-48 Hours'; + } else if (dest === 'outside') { + baseFee = 150; + sla = currentLang === 'bn' ? 'ā§Ēā§Ž-⧭⧍ āϘāĻŖā§āϟāĻž' : '48-72 Hours'; + } + + const extraWeightFee = (weight - 1) * 20; + const total = baseFee + extraWeightFee; + + document.getElementById('calc-result-fee').textContent = 'ā§ŗ' + total + ' BDT'; + document.getElementById('calc-result-sla').textContent = sla; +} + +function runSandboxFraud() { + const phone = (document.getElementById('fraud-phone').value || '').trim(); + const opEl = document.getElementById('fraud-res-op'); + const scoreEl = document.getElementById('fraud-res-score'); + + if (!/^01[3-9]\d{8}$/.test(phone)) { + opEl.textContent = currentLang === 'bn' ? 'āĻ…āĻŦ⧈āϧ āύāĻŽā§āĻŦāϰ (ā§§ā§§ āĻĄāĻŋāϜāĻŋāϟ āφāĻŦāĻļā§āϝāĻ•)' : 'Invalid BD Phone (11 digits required)'; + opEl.style.color = '#ef4444'; + scoreEl.textContent = currentLang === 'bn' ? 'āωāĻšā§āϚ āĻā§āρāĻ•āĻŋ (ā§§ā§Ļā§Ļ%)' : 'High Risk (100%)'; + scoreEl.style.color = '#ef4444'; + return; + } + + const prefix = phone.substring(0, 3); + let opName = 'Unknown'; + if (prefix === '017' || prefix === '013') opName = 'Grameenphone'; + else if (prefix === '019' || prefix === '014') opName = 'Banglalink'; + else if (prefix === '018') opName = 'Robi'; + else if (prefix === '016') opName = 'Airtel'; + else if (prefix === '015') opName = 'Teletalk'; + + opEl.textContent = opName + (currentLang === 'bn' ? ' (āĻŦ⧈āϧ āĻĢāϰāĻŽā§āϝāĻžāϟ)' : ' (Valid Prefix)'); + opEl.style.color = '#fff'; + scoreEl.textContent = currentLang === 'bn' ? 'āĻ¸ā§āĻŦāĻžāĻ­āĻžāĻŦāĻŋāĻ• (āϞāĻžāχāĻ­ āĻĄāĻžāϟāĻžāĻŦ⧇āϏ āĻšā§‡āĻ•)' : 'Low Risk (Zero Mock Verified)'; + scoreEl.style.color = '#34d399'; +} + +function updateLiveTelemetry() { const t0 = performance.now(); try { const res = await fetch('/health'); diff --git a/mcp_explorer.html b/mcp_explorer.html new file mode 100644 index 0000000..0344040 --- /dev/null +++ b/mcp_explorer.html @@ -0,0 +1,495 @@ + + + + + + Aura Courier MCP — Protocol Explorer & Interactive Playground + + + + + + + + + +
+ +
+ +
+
+

Streamable-HTTP MCP Protocol Explorer

+

Test MCP JSON-RPC 2.0 initialize handshakes, inspect the 9 registered tool schemas, and connect Gemini, Claude, Cursor & n8n agents directly to this remote endpoint.

+
+ + +
+
Official MCP Remote Endpoint
+
+ POST / GET + https://courier.auraajenticai.cloud/mcp + +
+
+
Protocol: Model Context Protocol 2024-11-05
+
Transport: Streamable-HTTP (SSE + Chunked Transfer)
+
Session Management: Automatic 1-hour TTL idle sweep
+
+
+ + +
+
+
+ Client Request Payload (JSON-RPC 2.0) + method: initialize +
+
+ + +
+
+ +
+
+ Server Response & Session Headers + Ready +
+
+
Click "Send JSON-RPC Handshake" to execute a live initialization against https://courier.auraajenticai.cloud/mcp...
+
+
+
+ + +

Registered MCP Tools (9 Tools)

+
+
+
1. list_couriers
+
Returns configured couriers, live credential statuses, and API connectivity.
+
Input: {} ¡ Returns: Carrier status matrix
+
+
+
2. create_parcel
+
Automated order dispatch with carrier targeting (steadfast, pathao, redx, paperfly) or auto-cascade.
+
Input: recipient_name, recipient_phone, recipient_address, cod_amount...
+
+
+
3. track_parcel
+
Unified consignment tracking across all 4 Bangladeshi carriers with normalized statuses.
+
Input: tracking_code, carrier? ¡ Returns: unified tracking history
+
+
+
4. get_balance
+
Merchant COD wallet balance, pending payouts, and carrier ledger synchronization.
+
Input: carrier? ¡ Returns: current_balance, pending_amount
+
+
+
5. check_fraud_risk
+
Nationwide delivery history, return rate & risk scoring. Zero mock data.
+
Input: phone_number ¡ Returns: risk_score (0-100), success_rate
+
+
+
6. validate_and_geocode_address
+
Google Maps Platform sublocality, Thana, District and rooftop coordinate geocoding.
+
Input: address, city? ¡ Returns: lat, lng, formatted_address
+
+
+
7. calculate_delivery_zone_and_fee
+
Dhaka (ā§ŗ80), Sub-Dhaka (ā§ŗ100), and Outside Dhaka (ā§ŗ150) zone tariff calculator.
+
Input: destination_address, weight_kg? ¡ Returns: zone, base_fee
+
+
+
8. compare_courier_rates
+
Side-by-side cost and SLA quote comparison across all 4 carriers.
+
Input: destination, weight_kg, cod_amount ¡ Returns: carrier rates
+
+
+
9. resolve_ndr_issue
+
Non-Delivery Report triage with automated WhatsApp templates and re-dispatch commands.
+
Input: tracking_code, reason ¡ Returns: triage plan & whatsapp text
+
+
+
+ +
+

Aura Courier MCP v2.4 ¡ Built with pride by Aura Ajentic AI ¡ Apache 2.0 License

+
+ + + + diff --git a/src/http.ts b/src/http.ts index 4e7a9ae..22f434b 100644 --- a/src/http.ts +++ b/src/http.ts @@ -9,7 +9,10 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; const PORT = Number(process.env.PORT || 8080); -const LANDING = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "index.html"); +const APP_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const LANDING = path.join(APP_DIR, "index.html"); +const HEALTH_HTML = path.join(APP_DIR, "health.html"); +const MCP_EXPLORER_HTML = path.join(APP_DIR, "mcp_explorer.html"); // Pull this client's courier keys from request headers or query params, with fallback to environment function keysFromRequest(req: Request): EnvSource { @@ -127,8 +130,28 @@ app.post("/mcp", async (req: Request, res: Response) => { } }); -// GET (server->client SSE stream) and DELETE (end session) for an existing session. -async function handleSessionRequest(req: Request, res: Response) { +// GET (server->client SSE stream or Browser Explorer) +app.get("/mcp", async (req: Request, res: Response) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + if (!sessionId || !sessions.has(sessionId)) { + // If opened directly by a human in a web browser, serve the interactive MCP Protocol Explorer! + if (req.headers.accept?.includes("text/html")) { + return res.sendFile(MCP_EXPLORER_HTML, (err) => { + if (err && !res.headersSent) { + res.status(400).send("Invalid or missing session ID. Visit https://courier.auraajenticai.cloud for instructions."); + } + }); + } + res.status(400).send("Invalid or missing session ID"); + return; + } + const entry = sessions.get(sessionId)!; + entry.lastActiveAt = Date.now(); + await entry.transport.handleRequest(req, res); +}); + +// DELETE (end session) for an existing session. +app.delete("/mcp", async (req: Request, res: Response) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; if (!sessionId || !sessions.has(sessionId)) { res.status(400).send("Invalid or missing session ID"); @@ -137,9 +160,7 @@ async function handleSessionRequest(req: Request, res: Response) { const entry = sessions.get(sessionId)!; entry.lastActiveAt = Date.now(); await entry.transport.handleRequest(req, res); -} -app.get("/mcp", handleSessionRequest); -app.delete("/mcp", handleSessionRequest); +}); /** * Official Steadfast Webhook Integration Endpoint @@ -158,7 +179,7 @@ app.post("/webhooks/steadfast", (req: Request, res: Response) => { const notifType = payload.notification_type || "delivery_status"; const consignmentId = payload.consignment_id; - const status = payload.status; // pending, delivered, partial_delivered, cancelled, unknown + const status = payload.status; const invoice = payload.invoice; console.log(`[Steadfast Update] Consignment: ${consignmentId} | Invoice: ${invoice} | Type: ${notifType} | Status: ${status}`); @@ -212,9 +233,13 @@ app.post("/webhooks/paperfly", (req: Request, res: Response) => { }); }); -app.get("/health", (_req, res) => { +/** + * Health & Live Telemetry Endpoint + * Serves visual SaaS Dashboard for browsers, and JSON for API monitors & Docker probes. + */ +app.get("/health", (req, res) => { const mem = process.memoryUsage(); - res.json({ + const data = { ok: true, service: "aura-courier-mcp", version: "2.4.0", @@ -234,7 +259,17 @@ app.get("/health", (_req, res) => { rss_mb: Math.round((mem.rss / 1024 / 1024) * 10) / 10, uptime_seconds: Math.round(process.uptime()), }, - }); + }; + + // If a browser is viewing /health and hasn't explicitly asked for format=json, serve the visual dashboard! + if (req.headers.accept?.includes("text/html") && req.query.format !== "json") { + return res.sendFile(HEALTH_HTML, (err) => { + if (err && !res.headersSent) res.json(data); + }); + } + + // Otherwise return JSON for curl, metrics monitors, and Docker health checks + res.json(data); }); app.get("/", (_req, res) => { @@ -244,7 +279,7 @@ app.get("/", (_req, res) => { .type("html") .send( `Aura Courier MCP` + - `` + + `` + `

🚚 Aura Courier MCP v2.4.0

Enterprise Bangladesh courier MCP & Google Maps Platform spatial hub.

` + `

Connect your AI to POST /mcp with Streamable-HTTP.

` + `

Webhooks: POST /webhooks/steadfast ¡ POST /webhooks/pathao

` +