colleag-mcp-ups
# colleag-mcp-ups
An [MCP](https://modelcontextprotocol.io) server for **UPS shipping**, running
against **your own UPS account**: rate shopping with negotiated prices,
live tracking (with an interactive [MCP Apps](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp)
card), and landed-cost estimates for international shipments.
Built by [Colleag.ai](https://colleag.ai), a CargoBeacon AB company. Works with any MCP host —
Colleag.ai, Claude Desktop, VS Code, or your own client.
> **Read-only by design (v0.1).** This release quotes, tracks and estimates —
> it never books shipments or spends money. Side-effecting tools (booking,
> pickup scheduling) will follow once host-side human-in-the-loop
> confirmation conventions are settled; a host, not a connector, should own
> the "are you sure?" step for actions that cost money.
## Tools
| Tool | What it does |
|---|---|
| `get_rates` | Compare every available UPS service on a lane (or price one `service_code`) with negotiated account prices and estimated delivery dates |
| `track_shipment` | Current status, delivery estimate and scan history for a tracking number. Ships an MCP Apps tracking card for hosts that support the `io.modelcontextprotocol/ui` extension |
| `landed_cost` | Duties, VAT and brokerage estimate for an international shipment (per-commodity lines, incoterm-aware) |
## Setup
1. Create an app on [developer.ups.com](https://developer.ups.com) (*"I want
to integrate UPS technology into my business"*) and enable the Rating,
Tracking and Landed Cost products. This gives you a Client ID and Secret
tied to your UPS account.
2. Configure environment variables:
```bash
UPS_CLIENT_ID=... # required
UPS_CLIENT_SECRET=... # required, secret
UPS_ACCOUNT_NUMBER=... # required — your 6-character shipper number
UPS_ENVIRONMENT=test # 'test' (CIE sandbox, default) or 'production'
SHIP_FROM_NAME="Acme AB" # optional defaults for the quoting origin
SHIP_FROM_ADDRESS="Industrigatan 1"
SHIP_FROM_CITY=Stockholm
SHIP_FROM_POSTAL_CODE="112 46"
SHIP_FROM_COUNTRY=SE
SHIP_FROM_PHONE="+468..."
```
3. Run:
```bash
uv run colleag-mcp-ups # stdio (default)
MCP_TRANSPORT=streamable-http uv run colleag-mcp-ups # HTTP
```
### Claude Desktop
```json
{
"mcpServers": {
"ups": {
"command": "uvx",
"args": ["colleag-mcp-ups"],
"env": {
"UPS_CLIENT_ID": "...",
"UPS_CLIENT_SECRET": "...",
"UPS_ACCOUNT_NUMBER": "...",
"UPS_ENVIRONMENT": "test"
}
}
}
}
```
### Try it without credentials
`npx @modelcontextprotocol/inspector uv run colleag-mcp-ups` lists the tools
and the `ui://colleag-mcp-ups/tracking-card` resource; calls will return a
structured configuration error until UPS credentials are set.
## Notes
- **Access tokens** are cached per `expires_in` (UPS cut lifetimes to 1 h in
April 2026 — never hardcode refresh intervals).
- **Rate limits** are not published by UPS; the server surfaces HTTP 429 as a
structured `UPS_RATE_LIMITED` error for the host to back off on.
- **Tracking retention**: UPS purges tracking data after ~120 days; keep your
own shipment history if you need longer memory.
- The MCP Apps tracking card is rendered by the host in a sandboxed iframe;
hosts without the UI extension simply use the JSON tool result.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 3 tools
Each tool targets a completely distinct UPS workflow: tracking an existing shipment, getting carrier rates, and estimating import costs. There is no overlap or realistic chance of selecting the wrong tool for a task.
track_shipment and get_rates follow a clear verb_noun pattern in snake_case. landed_cost is still readable and consistent in style, but it breaks the imperative verb pattern, making it a minor deviation.
Three tools is a well-scoped count for this focused UPS visibility and cost-estimation server. Each tool covers a meaningful capability with no redundant entries.
The set covers the apparent purpose of tracking and cost estimation well: track an existing shipment, compare rates, and estimate landed cost. Shipping execution operations like creating or canceling a label are absent, but that seems outside the server's deliberate scope.