merge: synchronize production SaaS release with GitHub and Glama maintainer metadata
This commit is contained in:
+20
-3
@@ -1,4 +1,21 @@
|
|||||||
FROM nginx:alpine
|
FROM node:20-alpine
|
||||||
COPY index.html /usr/share/nginx/html/index.html
|
|
||||||
|
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
|
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"]
|
||||||
|
|||||||
Vendored
+5
-4
@@ -2,12 +2,13 @@ import { CourierAdapter } from "./base.js";
|
|||||||
import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js";
|
import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js";
|
||||||
export declare class PaperflyAdapter implements CourierAdapter {
|
export declare class PaperflyAdapter implements CourierAdapter {
|
||||||
courierName: SupportedCourier;
|
courierName: SupportedCourier;
|
||||||
|
private apiKey;
|
||||||
|
private username;
|
||||||
|
private password;
|
||||||
|
private storeName;
|
||||||
private client;
|
private client;
|
||||||
private user;
|
|
||||||
private pass;
|
|
||||||
private key;
|
|
||||||
private enabled;
|
private enabled;
|
||||||
constructor(user: string, pass: string, key: string, baseUrl?: string);
|
constructor(apiKey: string, username: string, password: string, storeName: string, baseUrl: string);
|
||||||
isConfigured(): boolean;
|
isConfigured(): boolean;
|
||||||
createParcel(req: ParcelCreateRequest): Promise<ParcelResponse>;
|
createParcel(req: ParcelCreateRequest): Promise<ParcelResponse>;
|
||||||
trackParcel(trackingCode: string): Promise<TrackingResponse>;
|
trackParcel(trackingCode: string): Promise<TrackingResponse>;
|
||||||
|
|||||||
Vendored
+56
-45
@@ -1,25 +1,22 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
export class PaperflyAdapter {
|
export class PaperflyAdapter {
|
||||||
courierName = "paperfly";
|
courierName = "paperfly";
|
||||||
|
apiKey;
|
||||||
|
username;
|
||||||
|
password;
|
||||||
|
storeName;
|
||||||
client;
|
client;
|
||||||
user;
|
|
||||||
pass;
|
|
||||||
key;
|
|
||||||
enabled;
|
enabled;
|
||||||
constructor(user, pass, key, baseUrl = "https://api.paperfly.com.bd") {
|
constructor(apiKey, username, password, storeName, baseUrl) {
|
||||||
this.user = user;
|
this.apiKey = apiKey;
|
||||||
this.pass = pass;
|
this.username = username;
|
||||||
this.key = key;
|
this.password = password;
|
||||||
this.enabled = Boolean(user && pass && key);
|
this.storeName = storeName;
|
||||||
const token = Buffer.from(`${user}:${pass}`).toString("base64");
|
this.enabled = Boolean(apiKey && username && password);
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL: baseUrl,
|
baseURL: baseUrl,
|
||||||
headers: {
|
timeout: 15000,
|
||||||
Authorization: `Basic ${token}`,
|
headers: { "Content-Type": "application/json" },
|
||||||
pap_key: key,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
isConfigured() {
|
isConfigured() {
|
||||||
@@ -27,59 +24,73 @@ export class PaperflyAdapter {
|
|||||||
}
|
}
|
||||||
async createParcel(req) {
|
async createParcel(req) {
|
||||||
if (!this.enabled) {
|
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 = {
|
const payload = {
|
||||||
merOrderRef: req.invoice,
|
merchantOrderReference: req.invoice,
|
||||||
pickMerchantName: "Aura AI Commerce",
|
storeName: this.storeName,
|
||||||
pickMerchantAddress: "Dhaka, Bangladesh",
|
productBrief: req.item_type || "Product",
|
||||||
custName: req.recipient_name,
|
packagePrice: String(req.value ?? req.cod_amount ?? 0),
|
||||||
custPhone: req.recipient_phone,
|
max_weight: String(req.item_weight ?? 0.5),
|
||||||
custAddr: req.recipient_address,
|
customerName: req.recipient_name,
|
||||||
customerDistrict: "Dhaka",
|
customerAddress: req.recipient_address,
|
||||||
customerThana: "Gulshan",
|
customerPhone: req.recipient_phone,
|
||||||
packagePrice: req.cod_amount,
|
|
||||||
max_weight: req.item_weight || 0.5,
|
|
||||||
productBrief: req.item_type || "General Ecommerce item",
|
|
||||||
deliveryOption: "regular",
|
|
||||||
};
|
};
|
||||||
const response = await this.client.post("/OrderPlacement", payload);
|
const response = await this.client.post("/merchant/api/service/new_order_v2.php", payload, {
|
||||||
const trackingCode = response.data?.tracking_number || req.invoice;
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "paperfly",
|
courier: "paperfly",
|
||||||
tracking_code: trackingCode,
|
tracking_code: ok.tracking_number,
|
||||||
consignment_id: trackingCode,
|
consignment_id: ok.tracking_barcode || ok.tracking_number,
|
||||||
invoice: req.invoice,
|
invoice: req.invoice,
|
||||||
status: "order_placed",
|
status: ok.message || "created",
|
||||||
cod_amount: req.cod_amount,
|
cod_amount: req.cod_amount,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
raw_response: response.data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async trackParcel(trackingCode) {
|
async trackParcel(trackingCode) {
|
||||||
if (!this.enabled) {
|
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", {
|
if (!this.username || !this.password) {
|
||||||
ReferenceNumber: trackingCode,
|
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 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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "paperfly",
|
courier: "paperfly",
|
||||||
tracking_code: trackingCode,
|
tracking_code: trackingCode,
|
||||||
status: data?.Status || "in_progress",
|
status,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
raw_response: data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async getBalance() {
|
async getBalance() {
|
||||||
return {
|
throw new Error("Paperfly does not expose a merchant balance endpoint via its public API — check the Paperfly merchant panel.");
|
||||||
success: true,
|
|
||||||
courier: "paperfly",
|
|
||||||
current_balance: 0,
|
|
||||||
raw_response: { message: "Paperfly merchant settlements processed weekly via Wingman portal." },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
-5
@@ -1,14 +1,14 @@
|
|||||||
import { CourierAdapter } from "./base.js";
|
import { CourierAdapter } from "./base.js";
|
||||||
import { BalanceResponse, LocationResolutionResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js";
|
import { BalanceResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "../types.js";
|
||||||
export declare class RedXAdapter implements CourierAdapter {
|
export declare class RedxAdapter implements CourierAdapter {
|
||||||
courierName: SupportedCourier;
|
courierName: SupportedCourier;
|
||||||
private client;
|
private client;
|
||||||
private apiToken;
|
|
||||||
private enabled;
|
private enabled;
|
||||||
constructor(apiToken: string, baseUrl?: string);
|
private pickupStoreId;
|
||||||
|
constructor(apiToken: string, baseUrl: string, pickupStoreId?: string);
|
||||||
isConfigured(): boolean;
|
isConfigured(): boolean;
|
||||||
|
private resolveDeliveryArea;
|
||||||
createParcel(req: ParcelCreateRequest): Promise<ParcelResponse>;
|
createParcel(req: ParcelCreateRequest): Promise<ParcelResponse>;
|
||||||
trackParcel(trackingCode: string): Promise<TrackingResponse>;
|
trackParcel(trackingCode: string): Promise<TrackingResponse>;
|
||||||
getBalance(): Promise<BalanceResponse>;
|
getBalance(): Promise<BalanceResponse>;
|
||||||
getLocations(cityName?: string): Promise<LocationResolutionResponse>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+62
-45
@@ -1,94 +1,111 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
export class RedXAdapter {
|
export class RedxAdapter {
|
||||||
courierName = "redx";
|
courierName = "redx";
|
||||||
client;
|
client;
|
||||||
apiToken;
|
|
||||||
enabled;
|
enabled;
|
||||||
constructor(apiToken, baseUrl = "https://openapi.redx.com.bd/v1.0.0-beta") {
|
pickupStoreId;
|
||||||
this.apiToken = apiToken;
|
constructor(apiToken, baseUrl, pickupStoreId = "") {
|
||||||
this.enabled = Boolean(apiToken);
|
this.enabled = Boolean(apiToken);
|
||||||
|
this.pickupStoreId = pickupStoreId;
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL: baseUrl,
|
baseURL: baseUrl,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiToken}`,
|
"API-ACCESS-TOKEN": `Bearer ${apiToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
timeout: 10000,
|
timeout: 15000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
isConfigured() {
|
isConfigured() {
|
||||||
return this.enabled;
|
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) {
|
async createParcel(req) {
|
||||||
if (!this.enabled) {
|
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 = {
|
const payload = {
|
||||||
customer_name: req.recipient_name,
|
customer_name: req.recipient_name,
|
||||||
customer_phone: req.recipient_phone,
|
customer_phone: req.recipient_phone,
|
||||||
delivery_area: req.recipient_address,
|
delivery_area: area.name || req.delivery_area || "",
|
||||||
delivery_area_id: 1, // Default auto-resolved area
|
delivery_area_id: area.id,
|
||||||
customer_address: req.recipient_address,
|
customer_address: req.recipient_address,
|
||||||
merchant_invoice_id: req.invoice,
|
merchant_invoice_id: req.invoice,
|
||||||
cash_collection_amount: req.cod_amount,
|
cash_collection_amount: String(req.cod_amount ?? 0),
|
||||||
parcel_weight: (req.item_weight || 0.5) * 1000, // RedX uses grams
|
parcel_weight: weightGrams,
|
||||||
instruction: req.note || "Aura AI automated dispatch",
|
instruction: req.note || "Aura AI automated dispatch",
|
||||||
value: req.cod_amount || 500,
|
value: declaredValue,
|
||||||
};
|
};
|
||||||
const response = await this.client.post("/parcels", payload);
|
const storeId = req.pickup_store_id || this.pickupStoreId;
|
||||||
const trackingId = response.data.tracking_id || response.data.parcel_id;
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "redx",
|
courier: "redx",
|
||||||
tracking_code: trackingId,
|
tracking_code: trackingId,
|
||||||
consignment_id: trackingId,
|
consignment_id: trackingId,
|
||||||
invoice: req.invoice,
|
invoice: req.invoice,
|
||||||
status: "ready_for_pickup",
|
status: "pickup-pending",
|
||||||
cod_amount: req.cod_amount,
|
cod_amount: req.cod_amount,
|
||||||
delivery_fee: response.data.delivery_fee || 60,
|
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
raw_response: response.data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async trackParcel(trackingCode) {
|
async trackParcel(trackingCode) {
|
||||||
if (!this.enabled) {
|
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 data = response.data;
|
||||||
|
const events = data?.tracking || [];
|
||||||
|
const latest = events[events.length - 1];
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
courier: "redx",
|
courier: "redx",
|
||||||
tracking_code: trackingCode,
|
tracking_code: trackingCode,
|
||||||
status: data.tracking?.status || "in_transit",
|
status: latest?.message_en || "unknown",
|
||||||
timeline: (data.tracking?.history || []).map((h) => ({
|
updated_at: latest?.time || new Date().toISOString(),
|
||||||
status: h.status,
|
timeline: events.map((e) => ({ status: e.message_en || "", time: e.time || "", note: e.message_bn })),
|
||||||
time: h.created_at,
|
|
||||||
note: h.message,
|
|
||||||
})),
|
|
||||||
raw_response: data,
|
raw_response: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async getBalance() {
|
async getBalance() {
|
||||||
return {
|
throw new Error("RedX does not expose a merchant balance endpoint via its public API — check your balance in the RedX merchant panel.");
|
||||||
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,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+12
-4
@@ -17,14 +17,22 @@ export interface CourierConfig {
|
|||||||
redx: {
|
redx: {
|
||||||
apiToken: string;
|
apiToken: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
pickupStoreId: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
paperfly: {
|
paperfly: {
|
||||||
user: string;
|
apiKey: string;
|
||||||
pass: string;
|
username: string;
|
||||||
key: string;
|
password: string;
|
||||||
|
storeName: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export declare function loadConfig(): CourierConfig;
|
export type EnvSource = Record<string, string | undefined>;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|||||||
Vendored
+25
-22
@@ -1,41 +1,44 @@
|
|||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
export function loadConfig() {
|
/**
|
||||||
const steadfastApiKey = process.env.STEADFAST_API_KEY || "";
|
* Build a CourierConfig from a key/value source.
|
||||||
const steadfastSecretKey = process.env.STEADFAST_SECRET_KEY || "";
|
* Defaults to process.env (used by the STDIO/npx entrypoint); the HTTP
|
||||||
const pathaoClientId = process.env.PATHAO_CLIENT_ID || "";
|
* entrypoint passes a per-request map built from that client's headers/query.
|
||||||
const pathaoClientSecret = process.env.PATHAO_CLIENT_SECRET || "";
|
*/
|
||||||
const redxApiToken = process.env.REDX_API_TOKEN || "";
|
export function loadConfig(src = process.env) {
|
||||||
const paperflyUser = process.env.PAPERFLY_USER || "";
|
const steadfastApiKey = src.STEADFAST_API_KEY || "";
|
||||||
const paperflyPass = process.env.PAPERFLY_PASSWORD || "";
|
const steadfastSecretKey = src.STEADFAST_SECRET_KEY || "";
|
||||||
const paperflyKey = process.env.PAPERFLY_KEY || "";
|
const pathaoClientId = src.PATHAO_CLIENT_ID || "";
|
||||||
|
const pathaoClientSecret = src.PATHAO_CLIENT_SECRET || "";
|
||||||
return {
|
return {
|
||||||
steadfast: {
|
steadfast: {
|
||||||
apiKey: steadfastApiKey,
|
apiKey: steadfastApiKey,
|
||||||
secretKey: steadfastSecretKey,
|
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),
|
enabled: Boolean(steadfastApiKey && steadfastSecretKey),
|
||||||
},
|
},
|
||||||
pathao: {
|
pathao: {
|
||||||
clientId: pathaoClientId,
|
clientId: pathaoClientId,
|
||||||
clientSecret: pathaoClientSecret,
|
clientSecret: pathaoClientSecret,
|
||||||
username: process.env.PATHAO_USERNAME || "",
|
username: src.PATHAO_USERNAME || "",
|
||||||
password: process.env.PATHAO_PASSWORD || "",
|
password: src.PATHAO_PASSWORD || "",
|
||||||
storeId: process.env.PATHAO_STORE_ID || "",
|
storeId: src.PATHAO_STORE_ID || "",
|
||||||
baseUrl: process.env.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
|
baseUrl: src.PATHAO_BASE_URL || "https://api-hermes.pathao.com",
|
||||||
enabled: Boolean(pathaoClientId && pathaoClientSecret),
|
enabled: Boolean(pathaoClientId && pathaoClientSecret),
|
||||||
},
|
},
|
||||||
redx: {
|
redx: {
|
||||||
apiToken: redxApiToken,
|
apiToken: src.REDX_API_TOKEN || "",
|
||||||
baseUrl: process.env.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta",
|
baseUrl: src.REDX_BASE_URL || "https://openapi.redx.com.bd/v1.0.0-beta",
|
||||||
enabled: Boolean(redxApiToken),
|
pickupStoreId: src.REDX_PICKUP_STORE_ID || "",
|
||||||
|
enabled: Boolean(src.REDX_API_TOKEN),
|
||||||
},
|
},
|
||||||
paperfly: {
|
paperfly: {
|
||||||
user: paperflyUser,
|
apiKey: src.PAPERFLY_API_KEY || "",
|
||||||
pass: paperflyPass,
|
username: src.PAPERFLY_USERNAME || "",
|
||||||
key: paperflyKey,
|
password: src.PAPERFLY_PASSWORD || "",
|
||||||
baseUrl: process.env.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd",
|
storeName: src.PAPERFLY_STORE_NAME || "",
|
||||||
enabled: Boolean(paperflyUser && paperflyPass && paperflyKey),
|
baseUrl: src.PAPERFLY_BASE_URL || "https://api.paperfly.com.bd",
|
||||||
|
enabled: Boolean(src.PAPERFLY_API_KEY && src.PAPERFLY_STORE_NAME),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
Vendored
+5
-150
@@ -1,159 +1,14 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
import { CourierRegistry } from "./registry.js";
|
import { CourierRegistry } from "./registry.js";
|
||||||
const registry = new CourierRegistry();
|
import { buildMcpServer } from "./server.js";
|
||||||
const server = new Server({
|
// STDIO / npx entrypoint (Claude Desktop, Cursor, Antigravity).
|
||||||
name: "aura-courier-mcp",
|
// Credentials come from environment variables (STEADFAST_API_KEY, PATHAO_*).
|
||||||
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)}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
async function run() {
|
async function run() {
|
||||||
|
const server = buildMcpServer(new CourierRegistry());
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
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) => {
|
run().catch((error) => {
|
||||||
console.error("Fatal error running Aura Courier MCP:", error);
|
console.error("Fatal error running Aura Courier MCP:", error);
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
import { CourierAdapter } from "./adapters/base.js";
|
import { CourierAdapter } from "./adapters/base.js";
|
||||||
|
import { CourierConfig } from "./config.js";
|
||||||
import { BalanceResponse, FraudRiskScoreResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "./types.js";
|
import { BalanceResponse, FraudRiskScoreResponse, ParcelCreateRequest, ParcelResponse, SupportedCourier, TrackingResponse } from "./types.js";
|
||||||
export declare class CourierRegistry {
|
export declare class CourierRegistry {
|
||||||
private adapters;
|
private adapters;
|
||||||
constructor();
|
constructor(config?: CourierConfig);
|
||||||
listCouriers(): {
|
listCouriers(): {
|
||||||
courier: SupportedCourier;
|
courier: SupportedCourier;
|
||||||
is_configured: boolean;
|
is_configured: boolean;
|
||||||
|
|||||||
Vendored
+10
-41
@@ -1,20 +1,19 @@
|
|||||||
import { SteadfastAdapter } from "./adapters/steadfast.js";
|
import { SteadfastAdapter } from "./adapters/steadfast.js";
|
||||||
import { PathaoAdapter } from "./adapters/pathao.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 { FraudRiskEngine } from "./adapters/fraud_engine.js";
|
||||||
|
import { RedxAdapter } from "./adapters/redx.js";
|
||||||
|
import { PaperflyAdapter } from "./adapters/paperfly.js";
|
||||||
import { loadConfig } from "./config.js";
|
import { loadConfig } from "./config.js";
|
||||||
export class CourierRegistry {
|
export class CourierRegistry {
|
||||||
adapters = new Map();
|
adapters = new Map();
|
||||||
constructor() {
|
constructor(config = loadConfig()) {
|
||||||
const config = loadConfig();
|
|
||||||
const steadfast = new SteadfastAdapter(config.steadfast.apiKey, config.steadfast.secretKey, config.steadfast.baseUrl);
|
const steadfast = new SteadfastAdapter(config.steadfast.apiKey, config.steadfast.secretKey, config.steadfast.baseUrl);
|
||||||
this.adapters.set("steadfast", steadfast);
|
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);
|
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);
|
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);
|
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);
|
this.adapters.set("paperfly", paperfly);
|
||||||
}
|
}
|
||||||
listCouriers() {
|
listCouriers() {
|
||||||
@@ -36,7 +35,7 @@ export class CourierRegistry {
|
|||||||
courierName = req.courier;
|
courierName = req.courier;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Smart routing heuristic across all 4 couriers
|
// Smart routing heuristic
|
||||||
const addr = req.recipient_address.toLowerCase();
|
const addr = req.recipient_address.toLowerCase();
|
||||||
if (addr.includes("dhaka") &&
|
if (addr.includes("dhaka") &&
|
||||||
(addr.includes("gulshan") ||
|
(addr.includes("gulshan") ||
|
||||||
@@ -44,26 +43,10 @@ export class CourierRegistry {
|
|||||||
addr.includes("dhanmondi") ||
|
addr.includes("dhanmondi") ||
|
||||||
addr.includes("uttara") ||
|
addr.includes("uttara") ||
|
||||||
addr.includes("mirpur"))) {
|
addr.includes("mirpur"))) {
|
||||||
if (this.adapters.get("pathao")?.isConfigured()) {
|
courierName = this.adapters.get("pathao")?.isConfigured() ? "pathao" : "steadfast";
|
||||||
courierName = "pathao";
|
|
||||||
}
|
|
||||||
else if (this.adapters.get("redx")?.isConfigured()) {
|
|
||||||
courierName = "redx";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
courierName = "steadfast";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.adapters.get("steadfast")?.isConfigured()) {
|
courierName = "steadfast";
|
||||||
courierName = "steadfast";
|
|
||||||
}
|
|
||||||
else if (this.adapters.get("paperfly")?.isConfigured()) {
|
|
||||||
courierName = "paperfly";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
courierName = "steadfast";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const adapter = this.getAdapter(courierName);
|
const adapter = this.getAdapter(courierName);
|
||||||
@@ -73,26 +56,12 @@ export class CourierRegistry {
|
|||||||
if (courierName) {
|
if (courierName) {
|
||||||
return await this.getAdapter(courierName).trackParcel(trackingCode);
|
return await this.getAdapter(courierName).trackParcel(trackingCode);
|
||||||
}
|
}
|
||||||
// Smart fallback across available configured adapters
|
// Default try Steadfast first
|
||||||
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
|
|
||||||
try {
|
try {
|
||||||
return await this.getAdapter("steadfast").trackParcel(trackingCode);
|
return await this.getAdapter("steadfast").trackParcel(trackingCode);
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw lastError || new Error(`Could not find tracking info for ${trackingCode}`);
|
return await this.getAdapter("pathao").trackParcel(trackingCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getBalance(courierName) {
|
async getBalance(courierName) {
|
||||||
|
|||||||
Vendored
+10
@@ -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;
|
||||||
Vendored
+123
@@ -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;
|
||||||
|
}
|
||||||
Vendored
+15
-4
@@ -9,6 +9,11 @@ export interface ParcelCreateRequest {
|
|||||||
note?: string;
|
note?: string;
|
||||||
item_type?: string;
|
item_type?: string;
|
||||||
item_weight?: number;
|
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 {
|
export interface ParcelResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -50,15 +55,21 @@ export interface FraudRiskScoreResponse {
|
|||||||
recommendation: string;
|
recommendation: string;
|
||||||
neural_verified: boolean;
|
neural_verified: boolean;
|
||||||
}
|
}
|
||||||
|
export interface LocationResolutionRequest {
|
||||||
|
courier: SupportedCourier;
|
||||||
|
city_name?: string;
|
||||||
|
zone_name?: string;
|
||||||
|
area_name?: string;
|
||||||
|
}
|
||||||
export interface LocationResolutionResponse {
|
export interface LocationResolutionResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
courier: SupportedCourier;
|
courier: SupportedCourier;
|
||||||
locations: Array<{
|
locations: Array<{
|
||||||
city_id?: number | string;
|
city_id: number;
|
||||||
city_name?: string;
|
city_name: string;
|
||||||
zone_id?: number | string;
|
zone_id?: number;
|
||||||
zone_name?: string;
|
zone_name?: string;
|
||||||
area_id?: number | string;
|
area_id?: number;
|
||||||
area_name?: string;
|
area_name?: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://glama.ai/mcp/schemas/server.json",
|
||||||
|
"maintainers": [
|
||||||
|
"khondokartowsif171"
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+717
-245
File diff suppressed because it is too large
Load Diff
Regular → Executable
+5
-175
@@ -1,185 +1,15 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
|
||||||
CallToolRequestSchema,
|
|
||||||
ListToolsRequestSchema,
|
|
||||||
Tool,
|
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
import { CourierRegistry } from "./registry.js";
|
import { CourierRegistry } from "./registry.js";
|
||||||
import { SupportedCourier } from "./types.js";
|
import { buildMcpServer } from "./server.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)}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// STDIO / npx entrypoint (Claude Desktop, Cursor, Antigravity).
|
||||||
|
// Credentials come from environment variables (STEADFAST_API_KEY, PATHAO_*).
|
||||||
async function run() {
|
async function run() {
|
||||||
|
const server = buildMcpServer(new CourierRegistry());
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
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) => {
|
run().catch((error) => {
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ export interface LocationResolutionRequest {
|
|||||||
area_name?: string;
|
area_name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocationResolutionRequest {
|
||||||
|
courier: SupportedCourier;
|
||||||
|
city_name?: string;
|
||||||
|
zone_name?: string;
|
||||||
|
area_name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LocationResolutionResponse {
|
export interface LocationResolutionResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
courier: SupportedCourier;
|
courier: SupportedCourier;
|
||||||
|
|||||||
Reference in New Issue
Block a user