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.
- package/README.md +149 -0
- package/build/index.js +592 -0
- 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
|
+
}
|