potal-mcp-server 1.3.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.
Files changed (3) hide show
  1. package/README.md +149 -0
  2. package/build/index.js +592 -0
  3. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # POTAL MCP Server
2
+
3
+ Calculate the total landed cost of cross-border commerce directly in Claude, Cursor, and any MCP-compatible AI. Get instant breakdowns of import duties, taxes (VAT/GST), customs fees, and shipping for 240 countries.
4
+
5
+ **113M+ tariff records | 240 countries | 63 FTAs | AI HS classification | Sanctions screening**
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx potal-mcp-server
11
+ ```
12
+
13
+ ## Tools (9)
14
+
15
+ ### `calculate_landed_cost`
16
+ Calculate the total cost for a product being shipped internationally.
17
+
18
+ **Parameters:**
19
+ - `price` (required) — Product price in USD
20
+ - `origin` (required) — Origin country ISO code (e.g., `CN`, `US`, `DE`)
21
+ - `destinationCountry` (required) — Destination country ISO code
22
+ - `shippingPrice` — Shipping cost in USD (default: 0)
23
+ - `zipcode` — Required for US destinations (state sales tax)
24
+ - `productName` — Product name for HS Code classification
25
+ - `productCategory` — Category: electronics, apparel, footwear, etc.
26
+ - `hsCode` — Harmonized System code if known
27
+
28
+ ### `classify_product`
29
+ AI-powered HS code classification. Provide a product name and get the harmonized system code.
30
+
31
+ **Parameters:**
32
+ - `productName` (required) — Product name or description
33
+ - `category` — Product category for improved accuracy
34
+
35
+ ### `check_restrictions`
36
+ Check import restrictions and compliance requirements for a product in a destination country.
37
+
38
+ **Parameters:**
39
+ - `hsCode` (required) — HS code of the product
40
+ - `destinationCountry` (required) — Destination country ISO code
41
+ - `originCountry` — Origin country ISO code
42
+
43
+ ### `screen_shipment`
44
+ Comprehensive pre-shipment screening — combines cost calculation with compliance checks (restrictions, sanctions, denied parties).
45
+
46
+ **Parameters:**
47
+ - `price` (required) — Product price in USD
48
+ - `origin` (required) — Origin country ISO code
49
+ - `destinationCountry` (required) — Destination country ISO code
50
+ - `productName` — Product name
51
+ - `consigneeName` — Consignee name for denied-party screening
52
+ - `shippingPrice` — Shipping cost in USD
53
+
54
+ ### `screen_denied_party`
55
+ Screen a name against sanctions and denied-party lists (OFAC SDN + CSL, 21K+ entries).
56
+
57
+ **Parameters:**
58
+ - `name` (required) — Entity or individual name to screen
59
+ - `country` — Country for narrowing results
60
+
61
+ ### `lookup_fta`
62
+ Look up Free Trade Agreement benefits between two countries (63 FTAs covered).
63
+
64
+ **Parameters:**
65
+ - `originCountry` (required) — Origin country ISO code
66
+ - `destinationCountry` (required) — Destination country ISO code
67
+ - `hsCode` — HS code to check specific preferential rates
68
+
69
+ ### `list_supported_countries`
70
+ Get all 240 supported countries with VAT/GST rates, duty rates, and de minimis thresholds.
71
+
72
+ ### `generate_document`
73
+ Generate trade documents: Commercial Invoice (CI), Packing List (PL), Certificate of Origin (C/O).
74
+
75
+ **Parameters:**
76
+ - `documentType` (required) — `commercial_invoice`, `packing_list`, or `certificate_of_origin`
77
+ - `shipmentData` (required) — Shipment details (origin, destination, items, etc.)
78
+
79
+ ### `compare_countries`
80
+ Compare total landed costs across multiple destination countries for the same product.
81
+
82
+ **Parameters:**
83
+ - `price` (required) — Product price in USD
84
+ - `origin` (required) — Origin country ISO code
85
+ - `destinations` (required) — Array of destination country ISO codes
86
+ - `productName` — Product name
87
+ - `shippingPrice` — Shipping cost in USD
88
+
89
+ ## Setup
90
+
91
+ ### 1. Get your API Key
92
+ Sign up at [potal.app](https://potal.app) and get your API key.
93
+
94
+ ### 2. Configure Claude Desktop
95
+
96
+ Edit your Claude Desktop config file:
97
+
98
+ **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
99
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "potal": {
105
+ "command": "npx",
106
+ "args": ["-y", "potal-mcp-server"],
107
+ "env": {
108
+ "POTAL_API_KEY": "your_api_key_here"
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### 3. Restart Claude Desktop
116
+
117
+ After saving the config, restart Claude Desktop. You should see 9 POTAL tools available.
118
+
119
+ ## Usage Examples
120
+
121
+ Once connected, ask Claude:
122
+
123
+ - "How much will a $50 T-shirt from China cost me in the US including duties?"
124
+ - "Classify 'wireless bluetooth earbuds' and give me the HS code"
125
+ - "Check if there are any import restrictions for HS 8471 going to Brazil"
126
+ - "Screen a shipment: $200 leather bag from Italy to New York"
127
+ - "Screen 'Acme Corp' against sanctions lists"
128
+ - "Are there any FTA benefits between Korea and the US for HS 610510?"
129
+ - "Compare import costs for a $500 laptop from China to US, UK, Germany, and Japan"
130
+ - "Generate a commercial invoice for my shipment"
131
+ - "What countries are supported and their VAT rates?"
132
+
133
+ **Multilingual:**
134
+ - "日本にイタリアの革靴を送ると関税はいくらですか?"
135
+ - "Was kostet ein $100-Produkt aus China nach Deutschland mit Zoll?"
136
+ - "이 가방을 중국에서 한국으로 보내면 관세 포함 총 얼마야?"
137
+ - "从美国买一个$200的包寄到中国,总费用是多少?"
138
+
139
+ ## Environment Variables
140
+
141
+ | Variable | Required | Description |
142
+ |----------|----------|-------------|
143
+ | `POTAL_API_KEY` | Yes | Your POTAL API key (pk_live_ or sk_live_) |
144
+
145
+ ## About POTAL
146
+
147
+ POTAL is the infrastructure for global commerce — providing Total Landed Cost calculations for cross-border transactions. 240 countries, 113M+ tariff records, 63 FTAs, sanctions screening, and AI-powered HS classification.
148
+
149
+ Website: [potal.app](https://potal.app)
package/build/index.js ADDED
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * POTAL MCP Server
4
+ *
5
+ * Provides Total Landed Cost calculation tools for Claude.
6
+ * Calculates import duties, taxes (VAT/GST), customs fees, and shipping
7
+ * for cross-border purchases across 240 countries and territories.
8
+ *
9
+ * Tools (9):
10
+ * - calculate_landed_cost: Calculate total cost for international purchases
11
+ * - classify_product: AI-powered HS code classification
12
+ * - check_restrictions: Import restriction & compliance check
13
+ * - screen_shipment: Comprehensive pre-shipment screening (cost + compliance)
14
+ * - screen_denied_party: Sanctions/denied-party screening (OFAC SDN + CSL, 21K entries)
15
+ * - lookup_fta: Free Trade Agreement lookup (63 FTAs)
16
+ * - list_supported_countries: Get all supported countries with tax info
17
+ * - generate_document: Generate trade documents (CI/PL/C/O)
18
+ * - compare_countries: Compare TLC across multiple destination countries
19
+ */
20
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { z } from "zod";
23
+ // ─── Configuration ──────────────────────────────────────────
24
+ const POTAL_API_BASE = "https://www.potal.app/api/v1";
25
+ const API_KEY = process.env.POTAL_API_KEY || "";
26
+ const USER_AGENT = "potal-mcp-server/1.3.0";
27
+ // ─── Server Instance ────────────────────────────────────────
28
+ const server = new McpServer({
29
+ name: "potal",
30
+ version: "1.3.0",
31
+ });
32
+ async function callPotalApi(endpoint, method = "GET", body) {
33
+ const url = `${POTAL_API_BASE}${endpoint}`;
34
+ const headers = {
35
+ "User-Agent": USER_AGENT,
36
+ "X-API-Key": API_KEY,
37
+ "Content-Type": "application/json",
38
+ };
39
+ try {
40
+ const options = { method, headers };
41
+ if (body && method === "POST") {
42
+ options.body = JSON.stringify(body);
43
+ }
44
+ const response = await fetch(url, options);
45
+ if (!response.ok) {
46
+ const errorText = await response.text();
47
+ return {
48
+ success: false,
49
+ error: `API error (${response.status}): ${errorText}`,
50
+ };
51
+ }
52
+ const data = await response.json();
53
+ return { success: true, data: data };
54
+ }
55
+ catch (err) {
56
+ return {
57
+ success: false,
58
+ error: `Network error: ${err instanceof Error ? err.message : String(err)}`,
59
+ };
60
+ }
61
+ }
62
+ // ─── Format Helpers ─────────────────────────────────────────
63
+ function formatCurrency(amount) {
64
+ return `$${amount.toFixed(2)}`;
65
+ }
66
+ function formatBreakdown(data) {
67
+ const d = data.data ? data.data : data;
68
+ const lines = [];
69
+ lines.push("## Total Landed Cost Breakdown\n");
70
+ if (d.productPrice !== undefined) {
71
+ lines.push(`- **Product Price**: ${formatCurrency(Number(d.productPrice))}`);
72
+ }
73
+ if (d.shippingCost !== undefined && Number(d.shippingCost) > 0) {
74
+ lines.push(`- **Shipping**: ${formatCurrency(Number(d.shippingCost))}`);
75
+ }
76
+ if (d.importDuty !== undefined && Number(d.importDuty) > 0) {
77
+ lines.push(`- **Import Duty**: ${formatCurrency(Number(d.importDuty))}`);
78
+ }
79
+ if (d.vat !== undefined && Number(d.vat) > 0) {
80
+ lines.push(`- **VAT/GST**: ${formatCurrency(Number(d.vat))}`);
81
+ }
82
+ if (d.mpf !== undefined && Number(d.mpf) > 0) {
83
+ lines.push(`- **Merchandise Processing Fee**: ${formatCurrency(Number(d.mpf))}`);
84
+ }
85
+ if (d.salesTax !== undefined && Number(d.salesTax) > 0) {
86
+ lines.push(`- **Sales Tax**: ${formatCurrency(Number(d.salesTax))}`);
87
+ }
88
+ lines.push("");
89
+ if (d.totalLandedCost !== undefined) {
90
+ lines.push(`### **Total: ${formatCurrency(Number(d.totalLandedCost))}**`);
91
+ }
92
+ if (d.isDutyFree) {
93
+ lines.push("\n> ✅ This item qualifies for **duty-free** treatment.");
94
+ }
95
+ // Detailed breakdown array if available
96
+ if (Array.isArray(d.breakdown) && d.breakdown.length > 0) {
97
+ lines.push("\n### Detailed Breakdown\n");
98
+ for (const item of d.breakdown) {
99
+ const b = item;
100
+ let line = `- ${b.label || "Fee"}: ${formatCurrency(Number(b.amount || 0))}`;
101
+ if (b.note)
102
+ line += ` _(${b.note})_`;
103
+ lines.push(line);
104
+ }
105
+ }
106
+ return lines.join("\n");
107
+ }
108
+ // ─── Tool: calculate_landed_cost ────────────────────────────
109
+ server.tool("calculate_landed_cost", "Calculate the total landed cost for a product being shipped internationally. " +
110
+ "Returns a detailed breakdown of import duties, VAT/GST, customs fees, and " +
111
+ "the final total price the buyer will pay. Supports 240 countries and territories. " +
112
+ "Includes China CBEC tax, Mexico IEPS, Brazil cascading tax, India IGST, " +
113
+ "and processing fees for 12 countries.", {
114
+ price: z.number().describe("Product price in USD. Example: 49.99"),
115
+ origin: z
116
+ .string()
117
+ .length(2)
118
+ .describe("Origin country as 2-letter ISO code. Examples: CN (China), DE (Germany), JP (Japan), IT (Italy), GB (United Kingdom)"),
119
+ destinationCountry: z
120
+ .string()
121
+ .length(2)
122
+ .describe("Destination country as 2-letter ISO code. Examples: US, GB, DE, CA, AU, JP, FR, KR"),
123
+ shippingPrice: z
124
+ .number()
125
+ .optional()
126
+ .describe("Shipping cost in USD. Default: 0. Example: 8.50"),
127
+ zipcode: z
128
+ .string()
129
+ .optional()
130
+ .describe("Destination ZIP/postal code. Recommended for US destinations to calculate state sales tax. Example: 10001 (New York), 90210 (Beverly Hills)"),
131
+ productName: z
132
+ .string()
133
+ .optional()
134
+ .describe("Name of the product for automatic HS Code classification. Examples: Cotton T-Shirt, Laptop, Running Shoes"),
135
+ productCategory: z
136
+ .string()
137
+ .optional()
138
+ .describe("Product category for classification. Examples: electronics, apparel, footwear, accessories, food"),
139
+ hsCode: z
140
+ .string()
141
+ .optional()
142
+ .describe("If known, the HS Code for precise duty calculation. Example: 6109.10 (cotton t-shirts)"),
143
+ }, async (params) => {
144
+ if (!API_KEY) {
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: "❌ POTAL API key is not configured. Please set the POTAL_API_KEY environment variable.\n\nGet your API key at: https://www.potal.app",
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ const result = await callPotalApi("/calculate", "POST", {
155
+ price: params.price,
156
+ origin: params.origin,
157
+ destinationCountry: params.destinationCountry,
158
+ shippingPrice: params.shippingPrice,
159
+ zipcode: params.zipcode,
160
+ productName: params.productName,
161
+ productCategory: params.productCategory,
162
+ hsCode: params.hsCode,
163
+ });
164
+ if (!result.success) {
165
+ return {
166
+ content: [
167
+ {
168
+ type: "text",
169
+ text: `❌ Calculation failed: ${result.error}`,
170
+ },
171
+ ],
172
+ isError: true,
173
+ };
174
+ }
175
+ const formatted = formatBreakdown(result.data);
176
+ return {
177
+ content: [
178
+ {
179
+ type: "text",
180
+ text: formatted,
181
+ },
182
+ ],
183
+ };
184
+ });
185
+ // ─── 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.", {
189
+ 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'"),
191
+ hsCode: z.string().optional().describe("Known HS code to override classification. Example: '6109.10'"),
192
+ }, async (params) => {
193
+ if (!API_KEY) {
194
+ return {
195
+ content: [{ type: "text", text: "❌ POTAL API key not configured." }],
196
+ };
197
+ }
198
+ const result = await callPotalApi("/classify", "POST", {
199
+ productName: params.productName,
200
+ productCategory: params.productCategory,
201
+ hsCode: params.hsCode,
202
+ });
203
+ if (!result.success) {
204
+ return {
205
+ content: [{ type: "text", text: `❌ Classification failed: ${result.error}` }],
206
+ isError: true,
207
+ };
208
+ }
209
+ const d = result.data?.data || result.data;
210
+ const lines = [
211
+ "## HS Code Classification\n",
212
+ `- **Product**: ${params.productName}`,
213
+ `- **HS Code**: ${d.hsCode || 'Unknown'}`,
214
+ `- **Description**: ${d.description || d.hsDescription || 'N/A'}`,
215
+ `- **Chapter**: ${d.chapter || (d.hsCode ? d.hsCode.substring(0, 2) : 'N/A')}`,
216
+ `- **Classification Source**: ${d.classificationSource || d.source || 'N/A'}`,
217
+ ];
218
+ return {
219
+ content: [{ type: "text", text: lines.join("\n") }],
220
+ };
221
+ });
222
+ // ─── Tool: check_restrictions ───────────────────────────────
223
+ server.tool("check_restrictions", "Check import restrictions for a product in a destination country. " +
224
+ "Returns prohibited items, required permits, watched items, and carrier restrictions. " +
225
+ "Essential for compliance screening before shipping.", {
226
+ hsCode: z.string().describe("HS code to check restrictions for. Example: '9302' (firearms), '3004' (pharmaceuticals)"),
227
+ destinationCountry: z.string().length(2).describe("Destination country ISO2 code. Example: 'US', 'DE', 'JP'"),
228
+ productName: z.string().optional().describe("Product name for auto-classification if no HS code."),
229
+ }, async (params) => {
230
+ if (!API_KEY) {
231
+ return {
232
+ content: [{ type: "text", text: "❌ POTAL API key not configured." }],
233
+ };
234
+ }
235
+ const result = await callPotalApi("/restrictions", "POST", {
236
+ hsCode: params.hsCode,
237
+ destinationCountry: params.destinationCountry,
238
+ productName: params.productName,
239
+ });
240
+ if (!result.success) {
241
+ return {
242
+ content: [{ type: "text", text: `❌ Restriction check failed: ${result.error}` }],
243
+ isError: true,
244
+ };
245
+ }
246
+ const d = result.data?.data || result.data;
247
+ const lines = [
248
+ "## Import Restriction Check\n",
249
+ `- **HS Code**: ${d.hsCode || params.hsCode}`,
250
+ `- **Destination**: ${d.destinationCountry || params.destinationCountry}`,
251
+ `- **Has Restrictions**: ${d.hasRestrictions ? '⚠️ Yes' : '✅ No'}`,
252
+ `- **Prohibited**: ${d.isProhibited ? '🚫 YES' : 'No'}`,
253
+ ];
254
+ if (d.isWatched) {
255
+ lines.push(`- **Watch List**: ⚠️ Item is on a watch list`);
256
+ }
257
+ if (Array.isArray(d.restrictedCarriers) && d.restrictedCarriers.length > 0) {
258
+ lines.push(`- **Carrier Restrictions**: ${d.restrictedCarriers.join(', ')}`);
259
+ }
260
+ if (Array.isArray(d.restrictions) && d.restrictions.length > 0) {
261
+ lines.push("\n### Restrictions Found\n");
262
+ for (const r of d.restrictions) {
263
+ lines.push(`- **[${(r.severity || '').toUpperCase()}]** ${r.category}: ${r.description}`);
264
+ if (r.requiredDocuments?.length) {
265
+ lines.push(` - Required: ${r.requiredDocuments.join(', ')}`);
266
+ }
267
+ }
268
+ }
269
+ return {
270
+ content: [{ type: "text", text: lines.join("\n") }],
271
+ };
272
+ });
273
+ // ─── Tool: screen_shipment ──────────────────────────────────
274
+ server.tool("screen_shipment", "Comprehensive shipment screening: calculates landed cost, checks restrictions, " +
275
+ "and identifies trade remedies — all in one call. Use this for a complete " +
276
+ "pre-shipment compliance and cost analysis.", {
277
+ price: z.number().describe("Product price in USD"),
278
+ origin: z.string().length(2).describe("Origin country ISO2"),
279
+ destinationCountry: z.string().length(2).describe("Destination country ISO2"),
280
+ productName: z.string().describe("Product name for classification"),
281
+ shippingPrice: z.number().optional().describe("Shipping cost in USD"),
282
+ hsCode: z.string().optional().describe("Known HS code"),
283
+ }, async (params) => {
284
+ if (!API_KEY) {
285
+ return {
286
+ content: [{ type: "text", text: "❌ POTAL API key not configured." }],
287
+ };
288
+ }
289
+ // Run calculate and restrictions in parallel
290
+ 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
+ }),
299
+ callPotalApi("/restrictions", "POST", {
300
+ hsCode: params.hsCode || "",
301
+ destinationCountry: params.destinationCountry,
302
+ productName: params.productName,
303
+ }),
304
+ ]);
305
+ const lines = ["## Shipment Screening Report\n"];
306
+ // Cost section
307
+ if (calcResult.success && calcResult.data) {
308
+ lines.push(formatBreakdown(calcResult.data));
309
+ const d = calcResult.data?.data || calcResult.data;
310
+ if (d.tradeRemedies?.hasRemedies) {
311
+ lines.push("\n### ⚠️ Trade Remedies Apply");
312
+ for (const m of d.tradeRemedies.measures || []) {
313
+ lines.push(`- **${m.type}**: +${(m.dutyRate * 100).toFixed(1)}% — ${m.title}`);
314
+ }
315
+ }
316
+ if (d.usAdditionalTariffs?.hasAdditionalTariffs) {
317
+ lines.push("\n### ⚠️ US Additional Tariffs");
318
+ if (d.usAdditionalTariffs.section301)
319
+ lines.push(`- ${d.usAdditionalTariffs.section301.note}`);
320
+ if (d.usAdditionalTariffs.section232)
321
+ lines.push(`- ${d.usAdditionalTariffs.section232.note}`);
322
+ }
323
+ if (d.confidenceScore !== undefined) {
324
+ lines.push(`\n**Confidence Score**: ${(d.confidenceScore * 100).toFixed(0)}%`);
325
+ }
326
+ }
327
+ else {
328
+ lines.push(`❌ Cost calculation failed: ${calcResult.error}`);
329
+ }
330
+ // Restrictions section
331
+ lines.push("\n---\n### Compliance Check\n");
332
+ if (restrictResult.success && restrictResult.data) {
333
+ const r = restrictResult.data?.data || restrictResult.data;
334
+ if (r.isProhibited) {
335
+ lines.push("🚫 **PROHIBITED** — This item cannot be imported to the destination country.");
336
+ }
337
+ else if (r.hasRestrictions) {
338
+ lines.push("⚠️ **Restrictions apply** — See details below:");
339
+ for (const rest of r.restrictions || []) {
340
+ lines.push(`- [${(rest.severity || '').toUpperCase()}] ${rest.category}: ${rest.description}`);
341
+ }
342
+ }
343
+ else {
344
+ lines.push("✅ No import restrictions found.");
345
+ }
346
+ }
347
+ else {
348
+ lines.push("⚠️ Restriction check unavailable.");
349
+ }
350
+ return {
351
+ content: [{ type: "text", text: lines.join("\n") }],
352
+ };
353
+ });
354
+ // ─── Tool: screen_denied_party ───────────────────────────────
355
+ server.tool("screen_denied_party", "Screen a person or entity against sanctions and denied-party lists. " +
356
+ "Checks OFAC SDN, BIS Entity List, CSL, and other databases (21,301 entries " +
357
+ "from 19 sources). Essential for trade compliance.", {
358
+ name: z.string().describe("Person or entity name to screen. Example: 'Huawei Technologies'"),
359
+ country: z.string().optional().describe("Country ISO2 code to narrow results. Example: 'CN'"),
360
+ minScore: z.number().optional().describe("Minimum match score 0.5-1.0. Default: 0.8"),
361
+ }, async (params) => {
362
+ if (!API_KEY) {
363
+ return {
364
+ content: [{ type: "text", text: "❌ POTAL API key not configured." }],
365
+ };
366
+ }
367
+ const result = await callPotalApi("/screening", "POST", {
368
+ name: params.name,
369
+ country: params.country,
370
+ minScore: params.minScore,
371
+ });
372
+ if (!result.success) {
373
+ return {
374
+ content: [{ type: "text", text: `❌ Screening failed: ${result.error}` }],
375
+ isError: true,
376
+ };
377
+ }
378
+ const d = result.data?.data || result.data;
379
+ const lines = [
380
+ "## Denied Party Screening Result\n",
381
+ `- **Name Screened**: ${params.name}`,
382
+ `- **Has Matches**: ${d.hasMatches ? '⚠️ YES' : '✅ No matches'}`,
383
+ `- **Status**: ${d.overallStatus || (d.hasMatches ? 'ALERT' : 'CLEAR')}`,
384
+ ];
385
+ if (d.hasMatches && Array.isArray(d.results)) {
386
+ lines.push("\n### Matches Found\n");
387
+ for (const m of d.results.slice(0, 10)) {
388
+ lines.push(`- **${m.matchedName}** (Score: ${m.score}) — List: ${m.list}, Source: ${m.source || 'N/A'}`);
389
+ }
390
+ }
391
+ return {
392
+ content: [{ type: "text", text: lines.join("\n") }],
393
+ };
394
+ });
395
+ // ─── Tool: lookup_fta ────────────────────────────────────────
396
+ server.tool("lookup_fta", "Look up Free Trade Agreements between two countries. " +
397
+ "Covers 63 FTAs including USMCA, RCEP, CPTPP, KORUS, EU-UK TCA. " +
398
+ "Returns preferential duty rates if an FTA applies.", {
399
+ origin: z.string().length(2).describe("Origin country ISO2 code. Example: 'KR'"),
400
+ destination: z.string().length(2).describe("Destination country ISO2 code. Example: 'US'"),
401
+ hsCode: z.string().optional().describe("HS code for product-specific FTA rate. Example: '6109.10'"),
402
+ }, async (params) => {
403
+ if (!API_KEY) {
404
+ return {
405
+ content: [{ type: "text", text: "❌ POTAL API key not configured." }],
406
+ };
407
+ }
408
+ const query = `?origin=${params.origin}&destination=${params.destination}${params.hsCode ? `&hsCode=${params.hsCode}` : ''}`;
409
+ const result = await callPotalApi(`/fta${query}`, "GET");
410
+ if (!result.success) {
411
+ return {
412
+ content: [{ type: "text", text: `❌ FTA lookup failed: ${result.error}` }],
413
+ isError: true,
414
+ };
415
+ }
416
+ const d = result.data?.data || result.data;
417
+ const lines = [
418
+ "## FTA Lookup Result\n",
419
+ `- **Route**: ${params.origin} → ${params.destination}`,
420
+ `- **FTA Found**: ${d.hasFTA ? '✅ Yes' : '❌ No FTA'}`,
421
+ ];
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}%`);
427
+ }
428
+ }
429
+ }
430
+ return {
431
+ content: [{ type: "text", text: lines.join("\n") }],
432
+ };
433
+ });
434
+ // ─── Tool: list_supported_countries ─────────────────────────
435
+ server.tool("list_supported_countries", "Get a list of all 240 supported countries with their VAT/GST rates, " +
436
+ "average duty rates, de minimis thresholds, and currency information. " +
437
+ "Use this to check if a country is supported or to compare tax rates.", {}, async () => {
438
+ if (!API_KEY) {
439
+ return {
440
+ content: [
441
+ {
442
+ type: "text",
443
+ text: "❌ POTAL API key is not configured. Please set the POTAL_API_KEY environment variable.\n\nGet your API key at: https://www.potal.app",
444
+ },
445
+ ],
446
+ };
447
+ }
448
+ const result = await callPotalApi("/countries", "GET");
449
+ if (!result.success) {
450
+ return {
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: `❌ Failed to fetch countries: ${result.error}`,
455
+ },
456
+ ],
457
+ isError: true,
458
+ };
459
+ }
460
+ return {
461
+ content: [
462
+ {
463
+ type: "text",
464
+ text: JSON.stringify(result.data, null, 2),
465
+ },
466
+ ],
467
+ };
468
+ });
469
+ // ─── Tool: generate_document ────────────────────────────────
470
+ server.tool("generate_document", "Generate trade documents for a shipment: Commercial Invoice (CI), " +
471
+ "Packing List (PL), or Certificate of Origin (C/O). " +
472
+ "Returns a structured JSON document ready for export/PDF generation.", {
473
+ documentType: z.enum(["commercial_invoice", "packing_list", "certificate_of_origin"])
474
+ .describe("Type of trade document to generate"),
475
+ exporterName: z.string().describe("Exporter/seller company name"),
476
+ importerName: z.string().describe("Importer/buyer company name"),
477
+ originCountry: z.string().describe("Country of origin (ISO 2-letter code, e.g. 'CN')"),
478
+ destinationCountry: z.string().describe("Destination country (ISO 2-letter code, e.g. 'US')"),
479
+ productName: z.string().describe("Product name/description"),
480
+ hsCode: z.string().optional().describe("HS code (6+ digits)"),
481
+ quantity: z.number().describe("Number of units"),
482
+ unitPrice: z.number().describe("Price per unit in USD"),
483
+ weight: z.number().optional().describe("Total weight in kg"),
484
+ currency: z.string().optional().describe("Currency code (default: USD)"),
485
+ }, async (args) => {
486
+ if (!API_KEY) {
487
+ return {
488
+ content: [{ type: "text", text: "❌ POTAL API key is not configured." }],
489
+ };
490
+ }
491
+ const result = await callPotalApi("/documents", "POST", {
492
+ type: args.documentType,
493
+ exporter: args.exporterName,
494
+ importer: args.importerName,
495
+ origin: args.originCountry,
496
+ destination: args.destinationCountry,
497
+ items: [{
498
+ description: args.productName,
499
+ hsCode: args.hsCode || undefined,
500
+ quantity: args.quantity,
501
+ unitPrice: args.unitPrice,
502
+ weight: args.weight || undefined,
503
+ }],
504
+ currency: args.currency || "USD",
505
+ });
506
+ if (!result.success) {
507
+ return {
508
+ content: [{ type: "text", text: `❌ Document generation failed: ${result.error}` }],
509
+ isError: true,
510
+ };
511
+ }
512
+ return {
513
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
514
+ };
515
+ });
516
+ // ─── Tool: compare_countries ────────────────────────────────
517
+ server.tool("compare_countries", "Compare Total Landed Cost across multiple destination countries for the same product. " +
518
+ "Useful for finding the cheapest import route or comparing duty rates between countries. " +
519
+ "Returns a side-by-side comparison of costs.", {
520
+ productName: z.string().describe("Product name/description"),
521
+ price: z.number().describe("Product price in USD"),
522
+ originCountry: z.string().describe("Origin/export country (ISO 2-letter code, e.g. 'CN')"),
523
+ destinationCountries: z.array(z.string()).describe("List of destination countries to compare (ISO 2-letter codes, e.g. ['US', 'GB', 'DE', 'JP'])"),
524
+ hsCode: z.string().optional().describe("HS code (6+ digits)"),
525
+ shippingPrice: z.number().optional().describe("Shipping cost in USD (default: 0)"),
526
+ }, async (args) => {
527
+ if (!API_KEY) {
528
+ return {
529
+ content: [{ type: "text", text: "❌ POTAL API key is not configured." }],
530
+ };
531
+ }
532
+ const results = [];
533
+ for (const country of args.destinationCountries.slice(0, 10)) {
534
+ const result = await callPotalApi("/calculate", "POST", {
535
+ price: args.price,
536
+ shippingPrice: args.shippingPrice || 0,
537
+ origin: args.originCountry,
538
+ destinationCountry: country,
539
+ productName: args.productName,
540
+ hsCode: args.hsCode || undefined,
541
+ });
542
+ if (result.success && result.data) {
543
+ results.push({ country, data: result.data });
544
+ }
545
+ else {
546
+ results.push({ country, error: result.error || "Unknown error" });
547
+ }
548
+ }
549
+ // Format comparison
550
+ const lines = [];
551
+ lines.push(`## Country Comparison: ${args.productName}\n`);
552
+ lines.push(`**Price**: ${formatCurrency(args.price)} | **Origin**: ${args.originCountry}\n`);
553
+ lines.push("| Country | Duty | VAT/GST | Total Landed Cost | Duty Rate |");
554
+ lines.push("|---------|------|---------|-------------------|-----------|");
555
+ for (const r of results) {
556
+ if (r.error) {
557
+ lines.push(`| ${r.country} | - | - | Error: ${r.error} | - |`);
558
+ continue;
559
+ }
560
+ const d = r.data?.data ? r.data.data : r.data || {};
561
+ const duty = Number(d.importDuty || 0);
562
+ const vat = Number(d.vat || d.salesTax || 0);
563
+ const total = Number(d.totalLandedCost || 0);
564
+ const dutyRate = d.dutyRate !== undefined ? `${(Number(d.dutyRate) * 100).toFixed(1)}%` : '-';
565
+ lines.push(`| ${r.country} | ${formatCurrency(duty)} | ${formatCurrency(vat)} | ${formatCurrency(total)} | ${dutyRate} |`);
566
+ }
567
+ // Find cheapest
568
+ const validResults = results.filter(r => r.data);
569
+ if (validResults.length > 1) {
570
+ const cheapest = validResults.reduce((min, r) => {
571
+ const d = r.data?.data ? r.data.data : r.data || {};
572
+ const total = Number(d.totalLandedCost || Infinity);
573
+ const minD = min.data?.data ? min.data.data : min.data || {};
574
+ const minTotal = Number(minD.totalLandedCost || Infinity);
575
+ return total < minTotal ? r : min;
576
+ });
577
+ lines.push(`\n> 💡 **Cheapest route**: ${cheapest.country}`);
578
+ }
579
+ return {
580
+ content: [{ type: "text", text: lines.join("\n") }],
581
+ };
582
+ });
583
+ // ─── Start Server ───────────────────────────────────────────
584
+ async function main() {
585
+ const transport = new StdioServerTransport();
586
+ await server.connect(transport);
587
+ console.error("POTAL MCP Server running on stdio");
588
+ }
589
+ main().catch((error) => {
590
+ console.error("Fatal error:", error);
591
+ process.exit(1);
592
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "potal-mcp-server",
3
+ "version": "1.3.0",
4
+ "description": "Calculate total landed costs for cross-border commerce. 240 countries, 113M+ tariff records, 63 FTAs, AI HS classification, sanctions screening. MCP server for Claude, Cursor, and any MCP-compatible AI.",
5
+ "type": "module",
6
+ "bin": {
7
+ "potal-mcp-server": "./build/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc && chmod 755 build/index.js",
11
+ "start": "node build/index.js",
12
+ "dev": "tsx src/index.ts"
13
+ },
14
+ "files": ["build"],
15
+ "keywords": [
16
+ "mcp",
17
+ "claude",
18
+ "potal",
19
+ "landed-cost",
20
+ "cross-border",
21
+ "duties",
22
+ "taxes",
23
+ "import",
24
+ "tariff",
25
+ "customs",
26
+ "vat",
27
+ "hs-code",
28
+ "trade",
29
+ "commerce",
30
+ "ucp"
31
+ ],
32
+ "homepage": "https://potal.app",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/soulmaten7/potal"
36
+ },
37
+ "author": "POTAL",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.12.0",
41
+ "zod": "^3.24.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "tsx": "^4.19.0",
46
+ "typescript": "^5.7.0"
47
+ }
48
+ }