potal-mcp-server 1.4.0 → 1.4.1

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.
Files changed (2) hide show
  1. package/build/index.js +126 -43
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -7,15 +7,15 @@
7
7
  * for cross-border purchases across 240 countries and territories.
8
8
  *
9
9
  * Tools (9):
10
- * - calculate_landed_cost: Calculate total cost for international purchases
11
- * - classify_product: AI-powered HS code classification
10
+ * - calculate_landed_cost: Calculate total cost for international purchases (with 9-field classify support)
11
+ * - classify_product: HS code classification via v3.3 GRI pipeline (9-field input)
12
12
  * - check_restrictions: Import restriction & compliance check
13
- * - screen_shipment: Comprehensive pre-shipment screening (cost + compliance)
13
+ * - screen_shipment: Comprehensive pre-shipment screening (cost + compliance, 9-field classify)
14
14
  * - screen_denied_party: Sanctions/denied-party screening (OFAC SDN + CSL, 21K entries)
15
15
  * - lookup_fta: Free Trade Agreement lookup (63 FTAs)
16
16
  * - list_supported_countries: Get all supported countries with tax info
17
17
  * - generate_document: Generate trade documents (CI/PL/C/O)
18
- * - compare_countries: Compare TLC across multiple destination countries
18
+ * - compare_countries: Compare TLC across multiple destination countries (with 9-field classify)
19
19
  */
20
20
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
21
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -23,11 +23,11 @@ import { z } from "zod";
23
23
  // ─── Configuration ──────────────────────────────────────────
24
24
  const POTAL_API_BASE = "https://www.potal.app/api/v1";
25
25
  const API_KEY = process.env.POTAL_API_KEY || "";
26
- const USER_AGENT = "potal-mcp-server/1.3.0";
26
+ const USER_AGENT = "potal-mcp-server/1.4.1";
27
27
  // ─── Server Instance ────────────────────────────────────────
28
28
  const server = new McpServer({
29
29
  name: "potal",
30
- version: "1.3.0",
30
+ version: "1.4.1",
31
31
  });
32
32
  async function callPotalApi(endpoint, method = "GET", body) {
33
33
  const url = `${POTAL_API_BASE}${endpoint}`;
@@ -110,7 +110,8 @@ server.tool("calculate_landed_cost", "Calculate the total landed cost for a prod
110
110
  "Returns a detailed breakdown of import duties, VAT/GST, customs fees, and " +
111
111
  "the final total price the buyer will pay. Supports 240 countries and territories. " +
112
112
  "Includes China CBEC tax, Mexico IEPS, Brazil cascading tax, India IGST, " +
113
- "and processing fees for 12 countries.", {
113
+ "and processing fees for 12 countries. " +
114
+ "When providing productName for auto-classification, include material for accurate HS code results.", {
114
115
  price: z.number().describe("Product price in USD. Example: 49.99"),
115
116
  origin: z
116
117
  .string()
@@ -132,10 +133,22 @@ server.tool("calculate_landed_cost", "Calculate the total landed cost for a prod
132
133
  .string()
133
134
  .optional()
134
135
  .describe("Name of the product for automatic HS Code classification. Examples: Cotton T-Shirt, Laptop, Running Shoes"),
136
+ material: z
137
+ .string()
138
+ .optional()
139
+ .describe("Primary material — needed for accurate HS classification. Examples: cotton, leather, steel, polyester, rubber"),
135
140
  productCategory: z
136
141
  .string()
137
142
  .optional()
138
143
  .describe("Product category for classification. Examples: electronics, apparel, footwear, accessories, food"),
144
+ processing: z
145
+ .string()
146
+ .optional()
147
+ .describe("Manufacturing/processing method. Examples: woven, knitted, forged, molded"),
148
+ composition: z
149
+ .string()
150
+ .optional()
151
+ .describe("Material composition. Example: '100% cotton', '70% polyester 30% cotton'"),
139
152
  hsCode: z
140
153
  .string()
141
154
  .optional()
@@ -151,16 +164,28 @@ server.tool("calculate_landed_cost", "Calculate the total landed cost for a prod
151
164
  ],
152
165
  };
153
166
  }
154
- const result = await callPotalApi("/calculate", "POST", {
167
+ const body = {
155
168
  price: params.price,
156
169
  origin: params.origin,
157
170
  destinationCountry: params.destinationCountry,
158
- shippingPrice: params.shippingPrice,
159
- zipcode: params.zipcode,
160
- productName: params.productName,
161
- productCategory: params.productCategory,
162
- hsCode: params.hsCode,
163
- });
171
+ };
172
+ if (params.shippingPrice !== undefined)
173
+ body.shippingPrice = params.shippingPrice;
174
+ if (params.zipcode)
175
+ body.zipcode = params.zipcode;
176
+ if (params.productName)
177
+ body.productName = params.productName;
178
+ if (params.material)
179
+ body.material = params.material;
180
+ if (params.productCategory)
181
+ body.productCategory = params.productCategory;
182
+ if (params.processing)
183
+ body.processing = params.processing;
184
+ if (params.composition)
185
+ body.composition = params.composition;
186
+ if (params.hsCode)
187
+ body.hsCode = params.hsCode;
188
+ const result = await callPotalApi("/calculate", "POST", body);
164
189
  if (!result.success) {
165
190
  return {
166
191
  content: [
@@ -183,11 +208,19 @@ server.tool("calculate_landed_cost", "Calculate the total landed cost for a prod
183
208
  };
184
209
  });
185
210
  // ─── Tool: classify_product ──────────────────────────────────
186
- server.tool("classify_product", "Classify a product into an HS (Harmonized System) code for customs. " +
187
- "Uses AI-powered classification with keyword matching fallback. " +
188
- "Returns HS code, description, chapter, and confidence level.", {
211
+ server.tool("classify_product", "Classify a product into an HS (Harmonized System) code for customs using the v3.3 GRI pipeline. " +
212
+ "Supports 9-field input for maximum accuracy. material is REQUIRED for the v3.3 pipeline — " +
213
+ "without it, classification will fail with 400 error. " +
214
+ "Returns HS code, description, confidence, decision path, and GRI rules applied.", {
189
215
  productName: z.string().describe("Product name or description. Example: 'cotton t-shirt', 'laptop computer', 'running shoes'"),
190
- productCategory: z.string().optional().describe("Product category hint. Example: 'apparel', 'electronics', 'footwear'"),
216
+ material: z.string().describe("Primary material — REQUIRED for v3.3 pipeline. Example: 'cotton', 'leather', 'steel', 'polyester', 'rubber', 'wood', 'glass'"),
217
+ productCategory: z.string().optional().describe("Product category for classification. Example: 'apparel', 'electronics', 'footwear', 'kitchenware', 'toys'"),
218
+ originCountry: z.string().optional().describe("Country of origin (ISO 2-letter code). Example: 'CN', 'DE', 'US', 'JP'"),
219
+ description: z.string().optional().describe("Detailed product description for better accuracy. Example: 'Men's woven dress shirt with long sleeves and button front'"),
220
+ processing: z.string().optional().describe("Manufacturing/processing method. Example: 'woven', 'knitted', 'forged', 'molded', 'printed', 'assembled'"),
221
+ composition: z.string().optional().describe("Material composition with percentages. Example: '100% cotton', '70% polyester 30% cotton', '18/8 stainless steel'"),
222
+ weightSpec: z.string().optional().describe("Product weight specification. Example: '500g', '2kg', '150gsm'"),
223
+ price: z.number().optional().describe("Unit price in USD. Used for price-break HS rules (e.g. 'valued over $X'). Example: 49.99"),
191
224
  hsCode: z.string().optional().describe("Known HS code to override classification. Example: '6109.10'"),
192
225
  }, async (params) => {
193
226
  if (!API_KEY) {
@@ -195,11 +228,27 @@ server.tool("classify_product", "Classify a product into an HS (Harmonized Syste
195
228
  content: [{ type: "text", text: "❌ POTAL API key not configured." }],
196
229
  };
197
230
  }
198
- const result = await callPotalApi("/classify", "POST", {
231
+ const body = {
199
232
  productName: params.productName,
200
- productCategory: params.productCategory,
201
- hsCode: params.hsCode,
202
- });
233
+ material: params.material,
234
+ };
235
+ if (params.productCategory)
236
+ body.category = params.productCategory;
237
+ if (params.originCountry)
238
+ body.origin_country = params.originCountry;
239
+ if (params.description)
240
+ body.description = params.description;
241
+ if (params.processing)
242
+ body.processing = params.processing;
243
+ if (params.composition)
244
+ body.composition = params.composition;
245
+ if (params.weightSpec)
246
+ body.weight_spec = params.weightSpec;
247
+ if (params.price !== undefined)
248
+ body.price = params.price;
249
+ if (params.hsCode)
250
+ body.hsCode = params.hsCode;
251
+ const result = await callPotalApi("/classify", "POST", body);
203
252
  if (!result.success) {
204
253
  return {
205
254
  content: [{ type: "text", text: `❌ Classification failed: ${result.error}` }],
@@ -273,11 +322,15 @@ server.tool("check_restrictions", "Check import restrictions for a product in a
273
322
  // ─── Tool: screen_shipment ──────────────────────────────────
274
323
  server.tool("screen_shipment", "Comprehensive shipment screening: calculates landed cost, checks restrictions, " +
275
324
  "and identifies trade remedies — all in one call. Use this for a complete " +
276
- "pre-shipment compliance and cost analysis.", {
325
+ "pre-shipment compliance and cost analysis. Include material for accurate HS classification.", {
277
326
  price: z.number().describe("Product price in USD"),
278
327
  origin: z.string().length(2).describe("Origin country ISO2"),
279
328
  destinationCountry: z.string().length(2).describe("Destination country ISO2"),
280
329
  productName: z.string().describe("Product name for classification"),
330
+ material: z.string().optional().describe("Primary material — needed for accurate classification. Example: 'cotton', 'steel', 'leather'"),
331
+ productCategory: z.string().optional().describe("Product category. Example: 'apparel', 'electronics'"),
332
+ processing: z.string().optional().describe("Processing method. Example: 'woven', 'forged'"),
333
+ composition: z.string().optional().describe("Material composition. Example: '100% cotton'"),
281
334
  shippingPrice: z.number().optional().describe("Shipping cost in USD"),
282
335
  hsCode: z.string().optional().describe("Known HS code"),
283
336
  }, async (params) => {
@@ -286,16 +339,27 @@ server.tool("screen_shipment", "Comprehensive shipment screening: calculates lan
286
339
  content: [{ type: "text", text: "❌ POTAL API key not configured." }],
287
340
  };
288
341
  }
342
+ const calcBody = {
343
+ price: params.price,
344
+ origin: params.origin,
345
+ destinationCountry: params.destinationCountry,
346
+ productName: params.productName,
347
+ };
348
+ if (params.material)
349
+ calcBody.material = params.material;
350
+ if (params.productCategory)
351
+ calcBody.productCategory = params.productCategory;
352
+ if (params.processing)
353
+ calcBody.processing = params.processing;
354
+ if (params.composition)
355
+ calcBody.composition = params.composition;
356
+ if (params.shippingPrice !== undefined)
357
+ calcBody.shippingPrice = params.shippingPrice;
358
+ if (params.hsCode)
359
+ calcBody.hsCode = params.hsCode;
289
360
  // Run calculate and restrictions in parallel
290
361
  const [calcResult, restrictResult] = await Promise.all([
291
- callPotalApi("/calculate", "POST", {
292
- price: params.price,
293
- origin: params.origin,
294
- destinationCountry: params.destinationCountry,
295
- productName: params.productName,
296
- shippingPrice: params.shippingPrice,
297
- hsCode: params.hsCode,
298
- }),
362
+ callPotalApi("/calculate", "POST", calcBody),
299
363
  callPotalApi("/restrictions", "POST", {
300
364
  hsCode: params.hsCode || "",
301
365
  destinationCountry: params.destinationCountry,
@@ -414,16 +478,27 @@ server.tool("lookup_fta", "Look up Free Trade Agreements between two countries.
414
478
  };
415
479
  }
416
480
  const d = result.data?.data || result.data;
481
+ const ftaInfo = d.fta || d;
482
+ const hasFta = ftaInfo.applicable || ftaInfo.hasFta || ftaInfo.hasFTA || false;
417
483
  const lines = [
418
484
  "## FTA Lookup Result\n",
419
485
  `- **Route**: ${params.origin} → ${params.destination}`,
420
- `- **FTA Found**: ${d.hasFTA ? '✅ Yes' : '❌ No FTA'}`,
486
+ `- **FTA Found**: ${hasFta ? '✅ Yes' : '❌ No FTA'}`,
421
487
  ];
422
- if (d.hasFTA && d.agreements) {
423
- for (const fta of Array.isArray(d.agreements) ? d.agreements : [d.agreements]) {
424
- lines.push(`- **Agreement**: ${fta.name || fta.agreement || 'N/A'}`);
425
- if (fta.preferentialRate !== undefined) {
426
- lines.push(`- **Preferential Rate**: ${fta.preferentialRate}%`);
488
+ if (hasFta) {
489
+ lines.push(`- **Agreement**: ${ftaInfo.name || d.fta?.name || 'N/A'}`);
490
+ lines.push(`- **Code**: ${ftaInfo.code || d.fta?.code || 'N/A'}`);
491
+ if (ftaInfo.preferentialMultiplier !== undefined && ftaInfo.preferentialMultiplier !== null) {
492
+ const pctReduction = ((1 - ftaInfo.preferentialMultiplier) * 100).toFixed(0);
493
+ lines.push(`- **Preferential Rate**: ${ftaInfo.preferentialMultiplier === 0 ? 'Duty-free (0%)' : `${pctReduction}% reduction`}`);
494
+ }
495
+ if (d.rulesOfOrigin) {
496
+ lines.push(`\n### Rules of Origin`);
497
+ lines.push(`- **Certificate Type**: ${d.rulesOfOrigin.certificateType || 'N/A'}`);
498
+ if (d.rulesOfOrigin.rules?.length) {
499
+ for (const rule of d.rulesOfOrigin.rules) {
500
+ lines.push(`- ${rule.criterion || rule.type}: ${rule.description || rule.note || ''}`);
501
+ }
427
502
  }
428
503
  }
429
504
  }
@@ -490,8 +565,8 @@ server.tool("generate_document", "Generate trade documents for a shipment: Comme
490
565
  }
491
566
  const result = await callPotalApi("/documents", "POST", {
492
567
  type: args.documentType,
493
- exporter: args.exporterName,
494
- importer: args.importerName,
568
+ exporter: { name: args.exporterName, country: args.originCountry },
569
+ importer: { name: args.importerName, country: args.destinationCountry },
495
570
  origin: args.originCountry,
496
571
  destination: args.destinationCountry,
497
572
  items: [{
@@ -516,11 +591,13 @@ server.tool("generate_document", "Generate trade documents for a shipment: Comme
516
591
  // ─── Tool: compare_countries ────────────────────────────────
517
592
  server.tool("compare_countries", "Compare Total Landed Cost across multiple destination countries for the same product. " +
518
593
  "Useful for finding the cheapest import route or comparing duty rates between countries. " +
519
- "Returns a side-by-side comparison of costs.", {
594
+ "Returns a side-by-side comparison of costs. Include material for accurate classification.", {
520
595
  productName: z.string().describe("Product name/description"),
521
596
  price: z.number().describe("Product price in USD"),
522
597
  originCountry: z.string().describe("Origin/export country (ISO 2-letter code, e.g. 'CN')"),
523
598
  destinationCountries: z.array(z.string()).describe("List of destination countries to compare (ISO 2-letter codes, e.g. ['US', 'GB', 'DE', 'JP'])"),
599
+ material: z.string().optional().describe("Primary material for accurate classification. Example: 'cotton', 'steel'"),
600
+ productCategory: z.string().optional().describe("Product category. Example: 'apparel', 'electronics'"),
524
601
  hsCode: z.string().optional().describe("HS code (6+ digits)"),
525
602
  shippingPrice: z.number().optional().describe("Shipping cost in USD (default: 0)"),
526
603
  }, async (args) => {
@@ -531,14 +608,20 @@ server.tool("compare_countries", "Compare Total Landed Cost across multiple dest
531
608
  }
532
609
  const results = [];
533
610
  for (const country of args.destinationCountries.slice(0, 10)) {
534
- const result = await callPotalApi("/calculate", "POST", {
611
+ const body = {
535
612
  price: args.price,
536
613
  shippingPrice: args.shippingPrice || 0,
537
614
  origin: args.originCountry,
538
615
  destinationCountry: country,
539
616
  productName: args.productName,
540
- hsCode: args.hsCode || undefined,
541
- });
617
+ };
618
+ if (args.material)
619
+ body.material = args.material;
620
+ if (args.productCategory)
621
+ body.productCategory = args.productCategory;
622
+ if (args.hsCode)
623
+ body.hsCode = args.hsCode;
624
+ const result = await callPotalApi("/calculate", "POST", body);
542
625
  if (result.success && result.data) {
543
626
  results.push({ country, data: result.data });
544
627
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "potal-mcp-server",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "mcpName": "io.github.soulmaten7/potal",
5
5
  "description": "Calculate total landed costs for cross-border commerce. 240 countries, 257M+ tariff records, 131K government tariff schedules, 1.36M product-HS mappings, 63 FTAs, 9-field 100% HS Code accuracy (GRI Pipeline), sanctions screening (21K+ entries). MCP server for Claude, Cursor, and any MCP-compatible AI.",
6
6
  "type": "module",