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/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/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/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" + ] +} diff --git a/package-lock.json b/package-lock.json index c73fdb1..096ca6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,25 @@ { "name": "aura-courier-mcp", - "version": "2.0.0", + "version": "2.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aura-courier-mcp", - "version": "2.0.0", + "version": "2.3.2", "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/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/types.ts b/src/types.ts index e643cef..15dad06 100644 --- a/src/types.ts +++ b/src/types.ts @@ -78,6 +78,13 @@ export interface LocationResolutionRequest { area_name?: string; } +export interface LocationResolutionRequest { + courier: SupportedCourier; + city_name?: string; + zone_name?: string; + area_name?: string; +} + export interface LocationResolutionResponse { success: boolean; courier: SupportedCourier;