← Project index

DSV Tracking MCP Server

A coding challenge that turned live DSV shipment data into tools for AI agents—and led to an interview.

Outcome

I built this as a Sendify coding challenge. The team liked the solution enough to invite me to interview.

Shipment tracking as an agent tool

The brief was to make DSV shipment data available to an AI assistant. I chose MCP over a conversational wrapper so the integration would expose explicit, reusable capabilities that any compatible client could discover and call.

The result is a Go server that communicates over stdio and presents three focused tools: find shipments from a reference, fetch the full detail for a resolved shipment, and list the 21 reference formats accepted by DSV.

Claude answering a waybill question with status, route, dates, reference, and transport mode from the DSV tracking MCP server
A natural-language waybill question resolves into live, structured DSV tracking data.

A deliberately small tool contract

track_shipment
Accepts a waybill or other reference and returns matching summaries, locations, status, progress, and a stable shipment ID.
get_shipment_details
Returns goods, locations, complete event history, and per-package scans for the resolved shipment.
list_reference_types
Publishes the 21 accepted reference types and their validation patterns without an upstream call.
Transport boundary
Version one explicitly supports LAND shipments; SEA, AIR, and RAIL remain outside the verified contract.

Separating search from detail keeps each call understandable and lets an agent disambiguate multiple matches before requesting a heavier response. Responses include freshness and retrieval time, while failures use stable codes such as INVALID_INPUT, SHIPMENT_NOT_FOUND, and UPSTREAM_UNAVAILABLE.

MCP Inspector showing the DSV server's three tools and a live structured shipment search result
MCP Inspector exposes the three tools, their schemas, annotations, and the structured response.

The upstream was the hard part

DSV's public tracking page uses JSON endpoints, but they sit behind Cap.js browser checks and proof-of-work. A conventional retrying HTTP client succeeded once, then returned repeated 429 responses. Retrying harder could not fix a client the upstream had classified as automation.

The working adapter runs a long-lived Chromium process through chromedp. DSV's own page completes its browser challenge, then the adapter captures the JSON responses through the Chrome DevTools Protocol. Reusing one browser session amortizes the five-to-ten-second cold start and preserves the solved session state for later calls.

Tradeoff: the browser path costs more memory and startup time than a direct HTTP client. It is isolated behind the upstream interface so the MCP and domain layers do not depend on how DSV data is retrieved.

Turning observed data into a stable contract

The public API does not return sender or receiver names, so the server does not invent them. It returns the available place data and documents the limitation. Unknown transport modes and event codes are preserved instead of crashing, and event histories are sorted defensively because observed order is not a contractual guarantee.

Typed boundary

Upstream DTOs map into domain models before they become MCP views.

Resolved identity

Detail caching uses DSV's shipment ID because several input references can point to the same shipment.

Freshness policy

Search and active detail responses have short TTLs; delivered shipments can safely remain cached for 24 hours.

Failure behavior

Singleflight prevents duplicate fetches, stale data can cover a brief outage, and Prometheus metrics expose request behavior.

Verification, not just a demo

The repository includes fixtures for booked, dispatching, in-delivery, and delivered shipments across parcel and LTL freight. Unit tests cover mapping, chronological event ordering, cache expiry and stale fallback, MCP error contracts, and each tool's successful and failing paths.

A separate live verification harness ran both search and detail calls for all ten challenge waybills. It confirmed single- and multi-package results, empty event arrays for newly booked freight, heavy loads, and delivered shipments without missing goods blocks or unexpected response shapes.

Evidence in the repository