You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tighten bare z.string() inputs to their valid-form unions across the tools so
garbage is rejected at the schema layer while every legitimate form still
validates, mirroring the website /api/mcp surface identically (FAULT 13):
- unit_converter: from/to enums of the supported unit codes (+ chargeable_kg /
freight_tonnes as conversion targets).
- hs_code_lookup: code ^\d{2,6}$, section Roman numeral I-XXI, query >= 2.
- incoterms_lookup: code 3-letter Incoterm.
- uk_duty_calculator: incoterm enum (commodity/origin regexes aligned to website).
- airline_lookup: iata ^[A-Za-z0-9]{2}$, icao ^[A-Za-z]{3}$ (from length checks).
- unlocode_lookup: function -> function_type enum (port|airport|rail|road|icd|
border); code/country regexes aligned to website.
- container/uld/vehicle selectors: require >= 2 chars (kept permissive code-or-slug).
No tool-count or tool-list change (still 19). Minor bump: advertised input
schemas changed + the unlocode function -> function_type rename.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,22 @@
1
1
# Changelog
2
2
3
+
## 2.5.0 — 2026-06-19
4
+
5
+
### Changed
6
+
7
+
-**Loose-schema tightening across the tool inputs — garbage is now rejected at the schema layer while every legitimate input form still validates.** Several tools advertised a bare `z.string()` where the description promised a specific format, so malformed values passed validation and only failed (or silently returned nothing) downstream. Each is now constrained to exactly the forms its underlying data/logic accepts, with a clear error message, without narrowing any feature:
8
+
-**`unit_converter`** — `from` is now an enum of the 17 supported unit codes (weight / volume / length); `to` is those plus the freight targets `chargeable_kg` and `freight_tonnes`. Unknown units are rejected with the valid set surfaced in `tools/list`.
9
+
-**`hs_code_lookup`** — `code` must be 2–6 digits, `section` a Roman numeral (I–XXI), `query` ≥ 2 chars.
10
+
-**`incoterms_lookup`** — `code` must be a 3-letter Incoterm code (any case).
11
+
-**`uk_duty_calculator`** — `incoterm` is now the 11-value Incoterms enum (commodity_code `^\d{6,10}$` and origin_country `^[A-Za-z]{2}$` were already tight; regex form aligned with the website surface).
12
+
-**`airline_lookup`** — `iata``^[A-Za-z0-9]{2}$`, `icao``^[A-Za-z]{3}$` (upgraded from length checks); the 2-char IATA / 3-char ICAO / 3-digit AWB-prefix / free-form country+query union is fully preserved.
13
+
-**`unlocode_lookup`** — the function filter is now **`function_type`** (an enum of `port | airport | rail | road | icd | border`), renamed from the previous bare-string `function` to match the website MCP surface and validated as an enum; `code` / `country` regexes aligned to the website (`^[A-Za-z0-9]{5}$` / `^[A-Za-z]{2}$`). **Note:** callers that passed `function` must switch to `function_type`.
14
+
-**`container_lookup` / `uld_lookup` / `vehicle_lookup`** — the slug/code selector now requires ≥ 2 chars; these accept a code **or** slug from a fixed reference set, so they stay permissive (the handler returns a clear "not found" for unknown values) rather than being narrowed to a brittle enum.
15
+
16
+
### Notes
17
+
18
+
-**No tool-count or tool-list change — still 19 tools, same names.** This is a FAULT 13 fix-once-mirror-everywhere release: the website `/api/mcp` surface ([`app/api/mcp/[transport]/route.ts`](https://github.com/SoapyRED/freighttools)) was tightened to the identical schemas in the same change, so `tools/list` and input validation match across both surfaces. `serverInfo.version` reads dynamically from `package.json`, now `2.5.0`. Minor bump because the advertised input schemas changed (stricter validation + the `unlocode_lookup``function` → `function_type` rename).
section: z.string().optional().describe('Browse by section (Roman numeral, e.g., "II")'),
296
+
query: z.string().min(2,'Search term must be at least 2 characters').optional().describe('Search by product description (min 2 chars)'),
297
+
code: z.string().regex(/^\d{2,6}$/,'HS code must be 2–6 digits').optional().describe('Exact HS code lookup (2-6 digits)'),
298
+
section: z.string().regex(/^[ivxIVX]{1,5}$/,'Section must be a Roman numeral I–XXI').optional().describe('Browse by section (Roman numeral I–XXI, e.g., "II")'),
value: z.number().describe('The value to convert'),
396
-
from: z.string().describe('Source unit code (e.g., "kg", "cbm", "cm")'),
397
-
to: z.string().describe('Target unit code (e.g., "lbs", "cuft", "inches")'),
396
+
from: z.enum(['kg','lbs','oz','tonnes','short_tons','long_tons','cbm','cuft','cuin','litres','gal_us','gal_uk','cm','inches','m','feet','mm']).describe('Source unit — weight (kg, lbs, oz, tonnes, short_tons, long_tons), volume (cbm, cuft, cuin, litres, gal_us, gal_uk) or length (cm, inches, m, feet, mm)'),
397
+
to: z.enum(['kg','lbs','oz','tonnes','short_tons','long_tons','cbm','cuft','cuin','litres','gal_us','gal_uk','cm','inches','m','feet','mm','chargeable_kg','freight_tonnes']).describe('Target unit — any source unit, plus the freight targets chargeable_kg and freight_tonnes (only valid from cbm)'),
@@ -476,9 +476,9 @@ Use this tool when you need to:
476
476
477
477
schema: z.object({
478
478
query: z.string().min(2,'Query must be at least 2 characters').optional().describe('Search by location name (e.g., "rotterdam", "heathrow")'),
479
-
code: z.string().length(5,'UN/LOCODE must be exactly 5 characters (2-letter country + 3-char location)').regex(/^[A-Z0-9]{5}$/i,'UN/LOCODE must be alphanumeric').optional().describe('Exact UN/LOCODE lookup (e.g., "GBLHR", "NLRTM")'),
480
-
country: z.string().length(2,'Country must be a 2-letter ISO code').regex(/^[A-Z]{2}$/i,'Country must be a 2-letter ISO code (e.g., "GB", "NL")').optional().describe('Filter by country code (e.g., "GB", "NL")'),
481
-
function: z.string().optional().describe('Filter by function: port, airport, rail, road, icd, border'),
479
+
code: z.string().regex(/^[A-Za-z0-9]{5}$/,'UN/LOCODE must be 5 characters: 2-letter country + 3-char location (e.g. "GBLHR")').optional().describe('Exact UN/LOCODE lookup (e.g., "GBLHR", "NLRTM")'),
480
+
country: z.string().regex(/^[A-Za-z]{2}$/,'Country must be a 2-letter ISO code (e.g. "GB", "NL")').optional().describe('Filter by country code (e.g., "GB", "NL")'),
481
+
function_type: z.enum(['port','airport','rail','road','icd','border']).optional().describe('Filter by location function'),
@@ -509,11 +509,11 @@ Use this tool when you need to:
509
509
510
510
schema: z.object({
511
511
commodity_code: z.string().regex(/^\d{6,10}$/,'Commodity code must be 6–10 digits').describe('HS/tariff code (6–10 digits, e.g., "847989")'),
512
-
origin_country: z.string().length(2,'Origin country must be a 2-letter ISO code').regex(/^[A-Z]{2}$/i,'Origin country must be a 2-letter ISO code (e.g., "CN", "DE")').describe('ISO 2-letter origin country code (e.g., "CN", "DE")'),
512
+
origin_country: z.string().regex(/^[A-Za-z]{2}$/,'Origin country must be a 2-letter ISO code (e.g., "CN", "DE")').describe('ISO 2-letter origin country code (e.g., "CN", "DE")'),
513
513
customs_value: z.number().positive().describe('Goods value in GBP'),
514
514
freight_cost: z.number().optional().describe('Freight cost in GBP (added to CIF value)'),
515
515
insurance_cost: z.number().optional().describe('Insurance cost in GBP (added to CIF value)'),
@@ -604,7 +604,7 @@ Use this tool when you need to:
604
604
- Find aircraft-compatible ULDs`,
605
605
606
606
schema: z.object({
607
-
type: z.string().optional().describe('ULD code (e.g., "AKE", "PMC"). Omit to list all.'),
607
+
type: z.string().min(2,'ULD type must be at least 2 characters').optional().describe('ULD code (e.g., "AKE", "PMC") or slug (e.g., "ake-ld3"). Omit to list all.'),
608
608
category: z.enum(['container','pallet','special']).optional().describe('Filter by category'),
609
609
deck: z.enum(['lower','main']).optional().describe('Filter by deck position'),
610
610
}).strict(),
@@ -632,7 +632,7 @@ Use this tool when you need to:
632
632
- Check payload limits for heavy shipments`,
633
633
634
634
schema: z.object({
635
-
slug: z.string().optional().describe('Vehicle slug (e.g., "standard-curtainsider"). Omit to list all.'),
635
+
slug: z.string().min(2,'Vehicle slug must be at least 2 characters').optional().describe('Vehicle slug (e.g., "standard-curtainsider"). Omit to list all.'),
636
636
category: z.enum(['articulated','rigid','van']).optional().describe('Filter by category'),
637
637
region: z.enum(['EU','US']).optional().describe('Filter by region'),
0 commit comments