DEV Community

Positioning Stages
Positioning Stages

Posted on

Schema Defaults Can Quietly Rewrite a Freight Quote: Smoke-Testing a Hosted MCP Server

A freight-rating tool exposed over MCP looks easy to wire into an agent: point the client at a URL, let it discover the tools, and let the model call quote_shipping_rates. The risk is not that the call fails. The risk is that it succeeds with inputs the model never supplied. This post walks through a shell smoke test of JTLGO's public commerce MCP server and focuses on one detail that matters for any typed tool: optional parameters with defaults, and how to catch them in the response.

The endpoint and the handshake

JTLGO publishes two connector URLs against one server. The public one, https://jtlgo.com/api/mcp, exposes six read-only tools and needs no sign-in. The account URL adds four account-scoped tools behind OAuth, started by the client only when account data is needed. JTLGO's own setup page for the public and account connectors has JSON and TOML config snippets for common clients.

The transport is JSON-RPC over HTTP POST. A plain GET is rejected with 405 on purpose; a GET returning metadata would be a second, undocumented interface. The request needs an Accept header naming both JSON and the event-stream type:

curl -s -X POST https://jtlgo.com/api/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.1"}}}'
Enter fullscreen mode Exit fullscreen mode

When this was run on 11 November 2026, the server answered with protocol version 2025-06-18 and serverInfo of jtlgo-commerce-mcp version 1.1.0. A tools/list call then returned the six public tools: search_products, quote_shipping_rates, track_shipment, get_product_detail, human_handoff and get_mcp_config, each annotated with readOnlyHint: true.

Read the schema before you trust the tool

tools/list is cheap and usually served from a static schema, so it proves reachability, not correctness. Its real value is the input schema. For quote_shipping_rates, the schema returned on the test date had two required fields, weight (kg, minimum 0.01) and countryCode (ISO alpha-2). Everything else was optional:

  • length, width, height in cm, described as defaulting to 20, 20 and 15.
  • goodsType, an enum of general, battery, liquid, powder and sensitive.
  • description, optional text used to infer goodsType.
  • limit, the number of top routes to return, 1 to 20, default 3.

That shape is reasonable for a human-facing calculator. For an agent, it creates a trap. A model that only knows "12 kg to the US" can make a valid call with two arguments, and the server will fill the box size in for it. JTLGO's longer article on which tools a freight MCP server should expose and why dimensions matter states the underlying rule: any quote tool that accepts weight without dimensions is guessing, because for almost every mode the billable figure is volumetric.

Two calls that differ only by the box

The test made two calls with the same weight and destination. The first passed real dimensions:

{"jsonrpc":"2.0","id":4,"method":"tools/call",
 "params":{"name":"quote_shipping_rates",
  "arguments":{"weight":12,"countryCode":"US","length":40,"width":30,"height":25,"goodsType":"general"}}}
Enter fullscreen mode Exit fullscreen mode

The second passed only weight and countryCode. Both returned ok: true and a list of routes. The difference was in the echoed request block: the second response showed length: 20, width: 20, height: 15 and goodsType: "general" — values the caller never sent. Each route also carried actualWeight, volumeWeight and chargeableWeight. With the 40 × 30 × 25 cm box the volumetric figure on the routes was 5; with the defaulted box it was 1. At 12 kg actual weight the chargeable weight was 12 in both cases, so the headline result happened not to change.

Swap in a light, bulky product and it would. That is the failure mode: an agent that omits dimensions gets a confident, well-formed answer for a smaller box than the real one. Every response also carried a notice that estimated rates are for reference only and that final pricing depends on actual cargo details and JTLGO's manual confirmation, which matches the company's position that final freight is confirmed after warehouse measurement.

Guardrails worth putting in the client

Three checks are cheap to add on the agent side:

  1. Require what the server defaults. In the tool-use prompt or a wrapper, refuse to call quote_shipping_rates without all three dimensions. If the user has none, ask for packed carton size rather than letting the default stand in.
  2. Diff the echo. Compare the request block in the response with the arguments you sent. Any field present in the echo but absent from your call was defaulted; surface it to the user.
  3. Classify the cargo explicitly. Pass goodsType deliberately. Batteries, liquids and powders are separate classes in the enum, and restricted cargo is exactly where a rating engine may return nothing.

For the last case the server provides human_handoff. JTLGO's guidance is that when confidence is low — restricted commodities, unusual lanes — the correct behaviour is to hand off rather than extrapolate. get_mcp_config returns a support block with email and WhatsApp contacts and a message asking for product links, destination, package weight and dimensions, and delivery deadline. The same engine powers the human-facing Rate & Ship calculator, which is a useful cross-check when an agent result looks odd.

Scope to state in your agent's instructions

JTLGO describes the connector's scope plainly: it quotes China-origin express, air and ocean options with billable weight calculated from the dimensions supplied, and it does not rate domestic truckload movements inside the destination country. track_shipment accepts up to 10 numbers per call. Put both facts in the system prompt so the model does not promise an LTL or FTL quote the tool cannot produce.

Questions about the connector or a specific shipment can go to sales@jtlgo.com.

FAQ

Do I need an API key for the public tools?
No. The public URL needs no sign-in. Account tools use an OAuth flow started by the client.

Why does my GET request fail?
The endpoint accepts JSON-RPC over POST only; GET is rejected with 405 by design.

How do I know if a dimension was defaulted?
Compare the request object in the response with the arguments you sent.

Does the server quote US domestic LTL?
No. It covers China-origin export routes, not domestic truckload rating in the destination country.

Top comments (0)