aura-courier-mcp 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ import { CourierAdapter } from "./base.js";
2
+ import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js";
3
+ export declare class RedxAdapter implements CourierAdapter {
4
+ courierName: SupportedCourier;
5
+ private client;
6
+ private enabled;
7
+ private pickupStoreId;
8
+ constructor(apiToken: string, baseUrl: string, pickupStoreId?: string);
9
+ isConfigured(): boolean;
10
+ private resolveDeliveryArea;
11
+ createParcel(req: ParcelCreateRequest): Promise<ParcelResponse>;
12
+ trackParcel(trackingCode: string): Promise<TrackingResponse>;
13
+ getBalance(): Promise<BalanceResponse>;
14
+ }
@@ -0,0 +1,111 @@
1
+ import axios from "axios";
2
+ export class RedxAdapter {
3
+ courierName = "redx";
4
+ client;
5
+ enabled;
6
+ pickupStoreId;
7
+ constructor(apiToken, baseUrl, pickupStoreId = "") {
8
+ this.enabled = Boolean(apiToken);
9
+ this.pickupStoreId = pickupStoreId;
10
+ this.client = axios.create({
11
+ baseURL: baseUrl,
12
+ headers: {
13
+ "API-ACCESS-TOKEN": `Bearer ${apiToken}`,
14
+ "Content-Type": "application/json",
15
+ },
16
+ timeout: 15000,
17
+ });
18
+ }
19
+ isConfigured() {
20
+ return this.enabled;
21
+ }
22
+ // RedX's create endpoint needs a numeric delivery_area_id. If the caller did
23
+ // not pass one, best-effort resolve it from the address via GET /areas.
24
+ async resolveDeliveryArea(req) {
25
+ if (req.delivery_area_id) {
26
+ return { id: Number(req.delivery_area_id), name: req.delivery_area || "" };
27
+ }
28
+ const res = await this.client.get("/areas");
29
+ const areas = res.data?.areas || [];
30
+ const addr = (req.recipient_address || "").toLowerCase();
31
+ let best = null;
32
+ for (const a of areas) {
33
+ const base = String(a.name).split("(")[0].trim().toLowerCase();
34
+ if (base && addr.includes(base)) {
35
+ const bestLen = best ? String(best.name).split("(")[0].trim().length : 0;
36
+ if (base.length > bestLen)
37
+ best = a;
38
+ }
39
+ }
40
+ if (!best) {
41
+ throw new Error("RedX: could not match a delivery area from the address. Pass 'delivery_area_id' explicitly (look it up via RedX /areas).");
42
+ }
43
+ return { id: best.id, name: best.name };
44
+ }
45
+ async createParcel(req) {
46
+ if (!this.enabled) {
47
+ throw new Error("RedX credentials (API access token) are not configured.");
48
+ }
49
+ const area = await this.resolveDeliveryArea(req);
50
+ const weightGrams = Math.max(1, Math.round((req.item_weight ?? 0.5) * 1000));
51
+ const declaredValue = String(req.value ?? req.cod_amount ?? 0);
52
+ const payload = {
53
+ customer_name: req.recipient_name,
54
+ customer_phone: req.recipient_phone,
55
+ delivery_area: area.name || req.delivery_area || "",
56
+ delivery_area_id: area.id,
57
+ customer_address: req.recipient_address,
58
+ merchant_invoice_id: req.invoice,
59
+ cash_collection_amount: String(req.cod_amount ?? 0),
60
+ parcel_weight: weightGrams,
61
+ instruction: req.note || "Aura AI automated dispatch",
62
+ value: declaredValue,
63
+ };
64
+ const storeId = req.pickup_store_id || this.pickupStoreId;
65
+ if (storeId)
66
+ payload.pickup_store_id = Number(storeId);
67
+ if (req.item_type) {
68
+ payload.parcel_details_json = [
69
+ { name: req.item_type, category: req.item_category || "general", value: Number(req.value ?? req.cod_amount ?? 0) },
70
+ ];
71
+ }
72
+ const response = await this.client.post("/parcel", payload);
73
+ const data = response.data;
74
+ const trackingId = data?.tracking_id;
75
+ if (!trackingId) {
76
+ throw new Error(`RedX API error: ${JSON.stringify(data?.message || data)}`);
77
+ }
78
+ return {
79
+ success: true,
80
+ courier: "redx",
81
+ tracking_code: trackingId,
82
+ consignment_id: trackingId,
83
+ invoice: req.invoice,
84
+ status: "pickup-pending",
85
+ cod_amount: req.cod_amount,
86
+ created_at: new Date().toISOString(),
87
+ raw_response: data,
88
+ };
89
+ }
90
+ async trackParcel(trackingCode) {
91
+ if (!this.enabled) {
92
+ throw new Error("RedX credentials are not configured.");
93
+ }
94
+ const response = await this.client.get(`/parcel/track/${encodeURIComponent(trackingCode)}`);
95
+ const data = response.data;
96
+ const events = data?.tracking || [];
97
+ const latest = events[events.length - 1];
98
+ return {
99
+ success: true,
100
+ courier: "redx",
101
+ tracking_code: trackingCode,
102
+ status: latest?.message_en || "unknown",
103
+ updated_at: latest?.time || new Date().toISOString(),
104
+ timeline: events.map((e) => ({ status: e.message_en || "", time: e.time || "", note: e.message_bn })),
105
+ raw_response: data,
106
+ };
107
+ }
108
+ async getBalance() {
109
+ throw new Error("RedX does not expose a merchant balance endpoint via its public API — check your balance in the RedX merchant panel.");
110
+ }
111
+ }
package/dist/config.d.ts CHANGED
@@ -14,5 +14,17 @@ export interface CourierConfig {
14
14
  baseUrl: string;
15
15
  enabled: boolean;
16
16
  };
17
+ redx: {
18
+ apiToken: string;
19
+ baseUrl: string;
20
+ pickupStoreId: string;
21
+ enabled: boolean;
22
+ };
17
23
  }
18
- export declare function loadConfig(): CourierConfig;
24
+ export type EnvSource = Record<string, string | undefined>;
25
+ /**
26
+ * Build a CourierConfig from a key/value source.
27
+ * Defaults to process.env (used by the STDIO/npx entrypoint); the HTTP
28
+ * entrypoint passes a per-request map built from that client's headers/query.
29
+ */
30
+ export declare function loadConfig(src?: EnvSource): CourierConfig;
package/dist/config.js CHANGED
@@ -1,25 +1,36 @@
1
1
  import dotenv from "dotenv";
2
2
  dotenv.config();
3
- export function loadConfig() {
4
- const steadfastApiKey = process.env.STEADFAST_API_KEY || "";
5
- const steadfastSecretKey = process.env.STEADFAST_SECRET_KEY || "";
6
- const pathaoClientId = process.env.PATHAO_CLIENT_ID || "";
7
- const pathaoClientSecret = process.env.PATHAO_CLIENT_SECRET || "";
3
+ /**
4
+ * Build a CourierConfig from a key/value source.
5
+ * Defaults to process.env (used by the STDIO/npx entrypoint); the HTTP
6
+ * entrypoint passes a per-request map built from that client's headers/query.
7
+ */
8
+ export function loadConfig(src = process.env) {
9
+ const steadfastApiKey = src.STEADFAST_API_KEY || "";
10
+ const steadfastSecretKey = src.STEADFAST_SECRET_KEY || "";
11
+ const pathaoClientId = src.PATHAO_CLIENT_ID || "";
12
+ const pathaoClientSecret = src.PATHAO_CLIENT_SECRET || "";
8
13
  return {
9
14
  steadfast: {
10
15
  apiKey: steadfastApiKey,
11
16
  secretKey: steadfastSecretKey,
12
- baseUrl: process.env.STEADFAST_BASE_URL || "https://portal.packzy.com/api/v1",
17
+ baseUrl: src.STEADFAST_BASE_URL || "https://portal.packzy.com/api/v1",
13
18
  enabled: Boolean(steadfastApiKey && steadfastSecretKey),
14
19
  },
15
20
  pathao: {
16
21
  clientId: pathaoClientId,
17
22
  clientSecret: pathaoClientSecret,
18
- username: process.env.PATHAO_USERNAME || "",
19
- password: process.env.PATHAO_PASSWORD || "",
20
- storeId: process.env.PATHAO_STORE_ID || "",
21
- baseUrl: process.env.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
23
+ username: src.PATHAO_USERNAME || "",
24
+ password: src.PATHAO_PASSWORD || "",
25
+ storeId: src.PATHAO_STORE_ID || "",
26
+ baseUrl: src.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
22
27
  enabled: Boolean(pathaoClientId && pathaoClientSecret),
23
28
  },
29
+ redx: {
30
+ apiToken: src.REDX_API_TOKEN || "",
31
+ baseUrl: src.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta",
32
+ pickupStoreId: src.REDX_PICKUP_STORE_ID || "",
33
+ enabled: Boolean(src.REDX_API_TOKEN),
34
+ },
24
35
  };
25
36
  }
package/dist/http.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/http.js ADDED
@@ -0,0 +1,126 @@
1
+ import express from "express";
2
+ import { randomUUID } from "node:crypto";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
+ import { CourierRegistry } from "./registry.js";
6
+ import { loadConfig } from "./config.js";
7
+ import { buildMcpServer } from "./server.js";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ const PORT = Number(process.env.PORT || 8080);
11
+ // Marketing landing (index.html) sits at the repo root, one level above dist/.
12
+ const LANDING = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "index.html");
13
+ // Pull this client's courier keys from request headers or query params.
14
+ // Captured once per session (on the initialize request) and bound to that session's server.
15
+ function keysFromRequest(req) {
16
+ const h = req.headers;
17
+ const q = req.query;
18
+ const pick = (header, query) => {
19
+ const hv = h[header];
20
+ const v = (Array.isArray(hv) ? hv[0] : hv) ?? q[query];
21
+ return v ? String(v) : undefined;
22
+ };
23
+ return {
24
+ STEADFAST_API_KEY: pick("x-steadfast-api-key", "steadfast_key"),
25
+ STEADFAST_SECRET_KEY: pick("x-steadfast-secret-key", "steadfast_secret"),
26
+ STEADFAST_BASE_URL: pick("x-steadfast-base-url", "steadfast_base_url"),
27
+ PATHAO_CLIENT_ID: pick("x-pathao-client-id", "pathao_client_id"),
28
+ PATHAO_CLIENT_SECRET: pick("x-pathao-client-secret", "pathao_client_secret"),
29
+ PATHAO_USERNAME: pick("x-pathao-username", "pathao_username"),
30
+ PATHAO_PASSWORD: pick("x-pathao-password", "pathao_password"),
31
+ PATHAO_STORE_ID: pick("x-pathao-store-id", "pathao_store_id"),
32
+ PATHAO_BASE_URL: pick("x-pathao-base-url", "pathao_base_url"),
33
+ REDX_API_TOKEN: pick("x-redx-api-token", "redx_token"),
34
+ REDX_BASE_URL: pick("x-redx-base-url", "redx_base_url"),
35
+ REDX_PICKUP_STORE_ID: pick("x-redx-pickup-store-id", "redx_pickup_store_id"),
36
+ };
37
+ }
38
+ const app = express();
39
+ app.use(express.json({ limit: "1mb" }));
40
+ // Allow browser-based and cross-origin MCP clients.
41
+ app.use((req, res, next) => {
42
+ res.header("Access-Control-Allow-Origin", "*");
43
+ 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");
44
+ res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
45
+ res.header("Access-Control-Expose-Headers", "mcp-session-id");
46
+ if (req.method === "OPTIONS") {
47
+ res.sendStatus(204);
48
+ return;
49
+ }
50
+ next();
51
+ });
52
+ // Active sessions: sessionId -> transport (each bound to one client's keys).
53
+ const transports = {};
54
+ app.post("/mcp", async (req, res) => {
55
+ const sessionId = req.headers["mcp-session-id"];
56
+ let transport;
57
+ if (sessionId && transports[sessionId]) {
58
+ // Existing session — reuse its server (already holds this client's keys).
59
+ transport = transports[sessionId];
60
+ }
61
+ else if (!sessionId && isInitializeRequest(req.body)) {
62
+ // New session — capture THIS client's courier keys now.
63
+ const registry = new CourierRegistry(loadConfig(keysFromRequest(req)));
64
+ const server = buildMcpServer(registry);
65
+ transport = new StreamableHTTPServerTransport({
66
+ sessionIdGenerator: () => randomUUID(),
67
+ onsessioninitialized: (sid) => {
68
+ transports[sid] = transport;
69
+ },
70
+ });
71
+ transport.onclose = () => {
72
+ if (transport.sessionId)
73
+ delete transports[transport.sessionId];
74
+ };
75
+ await server.connect(transport);
76
+ }
77
+ else {
78
+ res.status(400).json({
79
+ jsonrpc: "2.0",
80
+ error: { code: -32000, message: "Bad Request: missing or invalid session. Send an initialize request first." },
81
+ id: null,
82
+ });
83
+ return;
84
+ }
85
+ try {
86
+ await transport.handleRequest(req, res, req.body);
87
+ }
88
+ catch (err) {
89
+ if (!res.headersSent) {
90
+ res.status(500).json({
91
+ jsonrpc: "2.0",
92
+ error: { code: -32603, message: `Internal error: ${err?.message || String(err)}` },
93
+ id: null,
94
+ });
95
+ }
96
+ }
97
+ });
98
+ // GET (server->client SSE stream) and DELETE (end session) for an existing session.
99
+ async function handleSessionRequest(req, res) {
100
+ const sessionId = req.headers["mcp-session-id"];
101
+ if (!sessionId || !transports[sessionId]) {
102
+ res.status(400).send("Invalid or missing session ID");
103
+ return;
104
+ }
105
+ await transports[sessionId].handleRequest(req, res);
106
+ }
107
+ app.get("/mcp", handleSessionRequest);
108
+ app.delete("/mcp", handleSessionRequest);
109
+ app.get("/health", (_req, res) => res.json({ ok: true, service: "aura-courier-mcp", version: "2.1.0", sessions: Object.keys(transports).length }));
110
+ app.get("/", (_req, res) => {
111
+ // Serve the marketing landing page; fall back to a minimal page if it's missing.
112
+ res.sendFile(LANDING, (err) => {
113
+ if (err && !res.headersSent) {
114
+ res
115
+ .type("html")
116
+ .send(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Aura Courier MCP</title>` +
117
+ `<style>body{font-family:system-ui,sans-serif;background:#0b0f1a;color:#e6ecff;margin:0;display:grid;place-items:center;min-height:100vh}.b{max-width:640px;padding:40px;text-align:center}h1{font-size:28px;margin:0 0 10px}code{background:#141c30;padding:2px 8px;border-radius:6px;color:#7dd3fc;font-size:13px}a{color:#7dd3fc}p{line-height:1.6;color:#9fb0d0}</style></head>` +
118
+ `<body><div class="b"><h1>🚚 Aura Courier MCP</h1><p>Live remote MCP endpoint for Bangladesh couriers — Steadfast &amp; Pathao.</p>` +
119
+ `<p>Connect your AI to <code>POST /mcp</code> and pass your courier keys as headers (<code>x-steadfast-api-key</code>, <code>x-steadfast-secret-key</code>) or query params (<code>?steadfast_key=…&amp;steadfast_secret=…</code>).</p>` +
120
+ `<p>By <a href="https://auraajenticai.cloud">Aura Ajentic AI</a> · <a href="https://courier.auraajenticai.cloud">docs &amp; setup</a></p></div></body></html>`);
121
+ }
122
+ });
123
+ });
124
+ app.listen(PORT, () => {
125
+ console.log(`Aura Courier MCP (HTTP) listening on :${PORT} — POST /mcp`);
126
+ });
package/dist/index.js CHANGED
@@ -1,159 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
3
  import { CourierRegistry } from "./registry.js";
6
- const registry = new CourierRegistry();
7
- const server = new Server({
8
- name: "aura-courier-mcp",
9
- version: "2.0.0",
10
- }, {
11
- capabilities: {
12
- tools: {},
13
- },
14
- });
15
- // Define Tools exactly matching Glama.ai & Extended Specs
16
- const TOOLS = [
17
- {
18
- name: "list_couriers",
19
- description: "Show supported Bangladeshi couriers and check which credentials are active.",
20
- inputSchema: {
21
- type: "object",
22
- properties: {},
23
- required: [],
24
- },
25
- },
26
- {
27
- name: "create_parcel",
28
- description: "Book a new parcel delivery across Bangladesh (Steadfast or Pathao) with normalized response.",
29
- inputSchema: {
30
- type: "object",
31
- properties: {
32
- courier: {
33
- type: "string",
34
- enum: ["steadfast", "pathao", "auto"],
35
- description: "Target courier or 'auto' for AI smart routing (default: auto)",
36
- },
37
- invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
38
- recipient_name: { type: "string", description: "Customer full name" },
39
- recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
40
- recipient_address: { type: "string", description: "Delivery address (Thana, District, Street)" },
41
- cod_amount: { type: "number", description: "Cash on delivery amount in BDT (0 if prepaid)" },
42
- note: { type: "string", description: "Special instructions for delivery rider" },
43
- item_weight: { type: "number", description: "Parcel weight in KG (default: 0.5)" },
44
- },
45
- required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"],
46
- },
47
- },
48
- {
49
- name: "track_parcel",
50
- description: "Track shipment delivery status across Steadfast or Pathao using Tracking Code / Consignment ID.",
51
- inputSchema: {
52
- type: "object",
53
- properties: {
54
- tracking_code: { type: "string", description: "Consignment ID or tracking code" },
55
- courier: {
56
- type: "string",
57
- enum: ["steadfast", "pathao"],
58
- description: "Optional courier name if known",
59
- },
60
- },
61
- required: ["tracking_code"],
62
- },
63
- },
64
- {
65
- name: "get_balance",
66
- description: "Retrieve current merchant account balance and payout details from a courier.",
67
- inputSchema: {
68
- type: "object",
69
- properties: {
70
- courier: {
71
- type: "string",
72
- enum: ["steadfast", "pathao"],
73
- description: "Courier provider to check balance for",
74
- },
75
- },
76
- required: ["courier"],
77
- },
78
- },
79
- {
80
- name: "check_fraud_risk",
81
- description: "Analyze Bangladeshi customer phone number delivery history and return/fraud risk score before dispatching.",
82
- inputSchema: {
83
- type: "object",
84
- properties: {
85
- phone: { type: "string", description: "11-digit Bangladeshi phone number to evaluate" },
86
- },
87
- required: ["phone"],
88
- },
89
- },
90
- ];
91
- server.setRequestHandler(ListToolsRequestSchema, async () => {
92
- return { tools: TOOLS };
93
- });
94
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
95
- const { name, arguments: args } = request.params;
96
- try {
97
- switch (name) {
98
- case "list_couriers": {
99
- const couriers = registry.listCouriers();
100
- return {
101
- content: [{ type: "text", text: JSON.stringify(couriers, null, 2) }],
102
- };
103
- }
104
- case "create_parcel": {
105
- const result = await registry.createParcel({
106
- courier: args?.courier,
107
- invoice: String(args?.invoice),
108
- recipient_name: String(args?.recipient_name),
109
- recipient_phone: String(args?.recipient_phone),
110
- recipient_address: String(args?.recipient_address),
111
- cod_amount: Number(args?.cod_amount || 0),
112
- note: args?.note ? String(args?.note) : undefined,
113
- item_weight: args?.item_weight ? Number(args?.item_weight) : 0.5,
114
- });
115
- return {
116
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
117
- };
118
- }
119
- case "track_parcel": {
120
- const result = await registry.trackParcel(String(args?.tracking_code), args?.courier);
121
- return {
122
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
123
- };
124
- }
125
- case "get_balance": {
126
- const result = await registry.getBalance(String(args?.courier));
127
- return {
128
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
129
- };
130
- }
131
- case "check_fraud_risk": {
132
- const result = registry.checkFraudRisk(String(args?.phone));
133
- return {
134
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
135
- };
136
- }
137
- default:
138
- throw new Error(`Unknown tool: ${name}`);
139
- }
140
- }
141
- catch (error) {
142
- return {
143
- isError: true,
144
- content: [
145
- {
146
- type: "text",
147
- text: `Aura Courier MCP Error: ${error.message || String(error)}`,
148
- },
149
- ],
150
- };
151
- }
152
- });
4
+ import { buildMcpServer } from "./server.js";
5
+ // STDIO / npx entrypoint (Claude Desktop, Cursor, Antigravity).
6
+ // Credentials come from environment variables (STEADFAST_API_KEY, PATHAO_*).
153
7
  async function run() {
8
+ const server = buildMcpServer(new CourierRegistry());
154
9
  const transport = new StdioServerTransport();
155
10
  await server.connect(transport);
156
- console.error("Aura Courier MCP v2.0 running on STDIO");
11
+ console.error("Aura Courier MCP v2.1 running on STDIO");
157
12
  }
158
13
  run().catch((error) => {
159
14
  console.error("Fatal error running Aura Courier MCP:", error);
@@ -1,8 +1,9 @@
1
1
  import { CourierAdapter } from "./adapters/base.js";
2
+ import { CourierConfig } from "./config.js";
2
3
  import { BalanceResponse, FraudRiskScoreResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "./types.js";
3
4
  export declare class CourierRegistry {
4
5
  private adapters;
5
- constructor();
6
+ constructor(config?: CourierConfig);
6
7
  listCouriers(): {
7
8
  courier: SupportedCourier;
8
9
  is_configured: boolean;
package/dist/registry.js CHANGED
@@ -1,15 +1,17 @@
1
1
  import { SteadfastAdapter } from "./adapters/steadfast.js";
2
2
  import { PathaoAdapter } from "./adapters/pathao.js";
3
3
  import { FraudRiskEngine } from "./adapters/fraud_engine.js";
4
+ import { RedxAdapter } from "./adapters/redx.js";
4
5
  import { loadConfig } from "./config.js";
5
6
  export class CourierRegistry {
6
7
  adapters = new Map();
7
- constructor() {
8
- const config = loadConfig();
8
+ constructor(config = loadConfig()) {
9
9
  const steadfast = new SteadfastAdapter(config.steadfast.apiKey, config.steadfast.secretKey, config.steadfast.baseUrl);
10
10
  this.adapters.set("steadfast", steadfast);
11
11
  const pathao = new PathaoAdapter(config.pathao.clientId, config.pathao.clientSecret, config.pathao.username, config.pathao.password, config.pathao.storeId, config.pathao.baseUrl);
12
12
  this.adapters.set("pathao", pathao);
13
+ const redx = new RedxAdapter(config.redx.apiToken, config.redx.baseUrl, config.redx.pickupStoreId);
14
+ this.adapters.set("redx", redx);
13
15
  }
14
16
  listCouriers() {
15
17
  return Array.from(this.adapters.entries()).map(([name, adapter]) => ({
@@ -0,0 +1,10 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
3
+ import { CourierRegistry } from "./registry.js";
4
+ export declare const TOOLS: Tool[];
5
+ /**
6
+ * Build a fully-wired MCP Server bound to a given CourierRegistry.
7
+ * The registry carries the credentials — for STDIO it comes from env,
8
+ * for HTTP it is built per-request from that client's keys.
9
+ */
10
+ export declare function buildMcpServer(registry: CourierRegistry): Server;
package/dist/server.js ADDED
@@ -0,0 +1,118 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ // Tool definitions — shared by the STDIO (npx) and HTTP (web-URL) entrypoints.
4
+ export const TOOLS = [
5
+ {
6
+ name: "list_couriers",
7
+ description: "Show supported Bangladeshi couriers and check which credentials are active.",
8
+ inputSchema: { type: "object", properties: {}, required: [] },
9
+ },
10
+ {
11
+ name: "create_parcel",
12
+ description: "Book a new parcel delivery across Bangladesh (Steadfast, Pathao or RedX) with normalized response.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ courier: { type: "string", enum: ["steadfast", "pathao", "redx", "auto"], description: "Target courier or 'auto' for AI smart routing (default: auto)" },
17
+ invoice: { type: "string", description: "Unique order invoice number (e.g. INV-1002)" },
18
+ recipient_name: { type: "string", description: "Customer full name" },
19
+ recipient_phone: { type: "string", description: "11-digit Bangladeshi mobile number (e.g. 017XXXXXXXX)" },
20
+ recipient_address: { type: "string", description: "Delivery address (Thana, District, Street)" },
21
+ cod_amount: { type: "number", description: "Cash on delivery amount in BDT (0 if prepaid)" },
22
+ note: { type: "string", description: "Special instructions for delivery rider" },
23
+ item_weight: { type: "number", description: "Parcel weight in KG (default: 0.5)" },
24
+ item_type: { type: "string", description: "What's inside the parcel (used by RedX)" },
25
+ value: { type: "number", description: "Declared parcel value in BDT (used by RedX; defaults to the COD amount)" },
26
+ delivery_area_id: { type: "number", description: "RedX only: numeric delivery-area id (auto-resolved from the address if omitted)" },
27
+ pickup_store_id: { type: "number", description: "RedX only: your pickup store id (optional)" },
28
+ },
29
+ required: ["invoice", "recipient_name", "recipient_phone", "recipient_address", "cod_amount"],
30
+ },
31
+ },
32
+ {
33
+ name: "track_parcel",
34
+ description: "Track shipment delivery status across Steadfast or Pathao using Tracking Code / Consignment ID.",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ tracking_code: { type: "string", description: "Consignment ID or tracking code" },
39
+ courier: { type: "string", enum: ["steadfast", "pathao", "redx"], description: "Optional courier name if known" },
40
+ },
41
+ required: ["tracking_code"],
42
+ },
43
+ },
44
+ {
45
+ name: "get_balance",
46
+ description: "Retrieve current merchant account balance and payout details from a courier.",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ courier: { type: "string", enum: ["steadfast", "pathao"], description: "Courier provider to check balance for" },
51
+ },
52
+ required: ["courier"],
53
+ },
54
+ },
55
+ {
56
+ name: "check_fraud_risk",
57
+ description: "Analyze Bangladeshi customer phone number delivery history and return/fraud risk score before dispatching.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ phone: { type: "string", description: "11-digit Bangladeshi phone number to evaluate" },
62
+ },
63
+ required: ["phone"],
64
+ },
65
+ },
66
+ ];
67
+ /**
68
+ * Build a fully-wired MCP Server bound to a given CourierRegistry.
69
+ * The registry carries the credentials — for STDIO it comes from env,
70
+ * for HTTP it is built per-request from that client's keys.
71
+ */
72
+ export function buildMcpServer(registry) {
73
+ const server = new Server({ name: "aura-courier-mcp", version: "2.1.0" }, { capabilities: { tools: {} } });
74
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
75
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
76
+ const { name, arguments: args } = request.params;
77
+ try {
78
+ switch (name) {
79
+ case "list_couriers":
80
+ return { content: [{ type: "text", text: JSON.stringify(registry.listCouriers(), null, 2) }] };
81
+ case "create_parcel": {
82
+ const result = await registry.createParcel({
83
+ courier: args?.courier,
84
+ invoice: String(args?.invoice),
85
+ recipient_name: String(args?.recipient_name),
86
+ recipient_phone: String(args?.recipient_phone),
87
+ recipient_address: String(args?.recipient_address),
88
+ cod_amount: Number(args?.cod_amount || 0),
89
+ note: args?.note ? String(args?.note) : undefined,
90
+ item_weight: args?.item_weight ? Number(args?.item_weight) : 0.5,
91
+ });
92
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
93
+ }
94
+ case "track_parcel": {
95
+ const result = await registry.trackParcel(String(args?.tracking_code), args?.courier);
96
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
97
+ }
98
+ case "get_balance": {
99
+ const result = await registry.getBalance(String(args?.courier));
100
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
101
+ }
102
+ case "check_fraud_risk": {
103
+ const result = registry.checkFraudRisk(String(args?.phone));
104
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
105
+ }
106
+ default:
107
+ throw new Error(`Unknown tool: ${name}`);
108
+ }
109
+ }
110
+ catch (error) {
111
+ return {
112
+ isError: true,
113
+ content: [{ type: "text", text: `Aura Courier MCP Error: ${error?.message || String(error)}` }],
114
+ };
115
+ }
116
+ });
117
+ return server;
118
+ }
package/dist/types.d.ts CHANGED
@@ -9,6 +9,11 @@ export interface ParcelCreateRequest {
9
9
  note?: string;
10
10
  item_type?: string;
11
11
  item_weight?: number;
12
+ item_category?: string;
13
+ value?: number | string;
14
+ delivery_area?: string;
15
+ delivery_area_id?: number | string;
16
+ pickup_store_id?: number | string;
12
17
  }
13
18
  export interface ParcelResponse {
14
19
  success: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aura-courier-mcp",
3
- "version": "2.1.0",
4
- "description": "Universal Bangladesh courier MCP server — Steadfast & Pathao with a built-in fraud-risk engine — for Claude, Antigravity, Cursor & n8n AI agents",
3
+ "version": "2.2.0",
4
+ "description": "Universal Bangladesh courier MCP server — Steadfast, Pathao & RedX with a built-in fraud-risk engine — for Claude, Antigravity, Cursor & n8n AI agents. Runs as a local npx (STDIO) server or a remote HTTP endpoint.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -18,6 +18,7 @@
18
18
  "build": "tsc",
19
19
  "prepublishOnly": "npm run build",
20
20
  "start": "node dist/index.js",
21
+ "start:http": "node dist/http.js",
21
22
  "dev": "tsx src/index.ts"
22
23
  },
23
24
  "keywords": [
@@ -26,6 +27,7 @@
26
27
  "courier",
27
28
  "steadfast",
28
29
  "pathao",
30
+ "redx",
29
31
  "bangladesh",
30
32
  "antigravity",
31
33
  "claude",
@@ -39,12 +41,14 @@
39
41
  "url": "git+https://github.com/auraajenticai/aura-courier-mcp.git"
40
42
  },
41
43
  "dependencies": {
42
- "@modelcontextprotocol/sdk": "^1.6.0",
44
+ "@modelcontextprotocol/sdk": "^1.12.0",
43
45
  "axios": "^1.7.9",
44
46
  "dotenv": "^16.4.7",
47
+ "express": "^4.21.2",
45
48
  "zod": "^3.24.2"
46
49
  },
47
50
  "devDependencies": {
51
+ "@types/express": "^4.17.21",
48
52
  "@types/node": "^22.13.0",
49
53
  "tsx": "^4.19.2",
50
54
  "typescript": "^5.7.3"