ShipSmart-MCP
Provides shipping tools for DHL integration, including address validation and rate preview capabilities through the DHL shipping provider.
Provides shipping tools for FedEx integration, including address validation and rate preview capabilities through the FedEx shipping provider.
Provides shipping tools for UPS integration, including address validation and rate preview capabilities through the UPS shipping provider.
Provides shipping tools for USPS integration, including address validation and rate preview capabilities through the USPS shipping provider.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ShipSmart-MCPvalidate address 123 Main St San Francisco CA 94105"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ShipSmart — MCP Tool Server (mcp)
The platform's sandboxed tool boundary: a standalone Model Context Protocol server exposing a least-privilege, read-only, JSON-Schema- validated tool registry. It is the only component that touches external carrier APIs — and by construction the place that can never write, book, or move money: register a write tool and the service refuses to boot.
Single source of truth for tool behavior across the platform:
ShipSmart-API calls this server
over MCP/HTTP instead of implementing tools in-process.
Stack: FastAPI 0.135.3 · Python 3.13 (async) · uv · pydantic-settings · httpx · JSON Schema Draft 2020-12 · FedEx / UPS / USPS / DHL adapters (+ mock)
Live: GET /health (service +
registered tool count) · GET /
discovery (Render free tier — cold start ~30–60 s).
Metric convention: structural counts (tools, tests, endpoints) are facts verified against source; latency/availability figures are (target) budgets, never measured production metrics.
Table of contents
Related MCP server: one-mcp
The ShipSmart ecosystem
One of six sibling repositories — clone them as siblings of this directory.
All six are also mirrored together in
ShipSmart — the umbrella repository
that snapshots each component at a pinned commit (see its COMPONENTS.yml).
Repo | Role | Stack |
React SPA — search-first UI | React 19, Vite, TS | |
Java system of record — single Postgres writer, AI trust boundary | Spring Boot 3.4, Java 17 | |
Python AI layer — RAG, guardrails, agents, streaming | FastAPI, Python 3.13 | |
ShipSmart-MCP (this repo) | Read-only MCP tool server — 6 tools, 4+1 carrier adapters | FastAPI + MCP |
Supabase schema, RLS, WORM ledger, edge functions | Supabase, Deno | |
Cross-repo contracts + evals + e2e | Python 3.13, pytest |
Architecture (HLD)
Figure 1 — container/component view. Every call crosses the guard/audit/ integrity plane before any carrier adapter runs; the registry's JSON Schemas power both discovery and enforcement.
flowchart TB
API["ShipSmart-API (agent / RAG / concierge)"] -->|"MCP over HTTP + X-MCP-Api-Key"| APP
subgraph APP["FastAPI app (app/main.py)"]
AUTH["require_api_key dependency"]
EP["GET / · GET /health · POST /tools/list · POST /tools/call"]
INV["_enforce_read_only(registry) at lifespan"]
end
subgraph REG["ToolRegistry (app/tools)"]
T1[validate_address]
T2[get_quote_preview]
T3[calculate_dimensional_weight]
T4[estimate_package_profile]
T5[parse_address]
T6[check_restricted_items]
end
subgraph CTL["Cross-cutting controls"]
JS["JSON Schema 2020-12 validate_input (additionalProperties=false)"]
TG["tool_guard: SSRF egress allowlist + per-caller scopes"]
TA["tool_audit: append-only, args-hashed"]
IN["integrity: sha256 descriptor checksums"]
end
subgraph PROV["Provider adapters (Strategy)"]
P0["MockProvider (keyless default)"]
P1[FedExProvider]
P2[UPSProvider]
P3[USPSProvider]
P4[DHLProvider]
end
CARR["Carrier REST APIs (sandbox base URLs)"]
APP --> REG
REG --> CTL
REG --> PROV
PROV -->|"httpx, explicit timeouts"| CARRPatterns: Adapter/Strategy (one ShippingProvider port, five adapters) ·
Registry (discovery + lookup) · least privilege as a boot invariant · Guard
(egress + caller scopes) · content-addressable integrity (descriptor checksums).
The headline invariant
READ_ONLY_TOOL_ALLOWLIST is a frozenset, and _enforce_read_only()
runs at startup:
Refusing to start: non-read-only tool(s) registered: [...].
ShipSmart-MCP serves read/preview tools only; writes, bookings, and
money movement belong to the Java Orchestrator.Figure 2 — least privilege as a boot-time property. A developer who registers a write tool can't even start the service — the mistake fails loudly in dev and CI, never silently in production.
flowchart TB
S[Service starting] --> B["_build_registry()"]
B --> E["_enforce_read_only(registry)"]
E --> C{"served tools ⊆ READ_ONLY_TOOL_ALLOWLIST?"}
C -->|yes| UP["App serves traffic"]
C -->|no| X["raise RuntimeError — refuse to start"]
X --> MSG["writes, bookings, money movement belong to the Java Orchestrator"]HTTP contract
Endpoint | Purpose |
| discovery/info |
| service + registered tool count |
| MCP discovery — full JSON Schemas per tool |
| execute one tool (auth + validation + guard + audit) |
X-MCP-Api-Key (shared key) gates the tool routes — this is an internal
service, called by the API, never by browsers.
Anatomy of a tool call
Figure 3 — /tools/call happy path: five controls in order.
sequenceDiagram
participant C as Caller (ShipSmart-API)
participant A as FastAPI (require_api_key)
participant R as ToolRegistry
participant G as tool_guard
participant P as Provider adapter
participant AU as tool_audit
C->>A: POST /tools/call (X-MCP-Api-Key)
A->>R: lookup(tool)
R->>R: validate_input (JSON Schema 2020-12)
R->>G: is_authorized(caller, tool)? egress allowed?
G-->>R: allowed
R->>P: execute(args)
P->>P: httpx call, explicit timeout
P-->>R: read-only result
R->>AU: record(tool, caller, request_id, args_hash, status, latency_ms)
R-->>C: typed resultFigure 4 — SSRF denial: blocked before any network I/O. The same guard denies any non-allowlisted host and every private/loopback/link-local IP literal (the cloud metadata-endpoint attack).
sequenceDiagram
participant T as Tool execution
participant G as tool_guard
T->>G: assert_egress_allowed("http://169.254.169.254/...")
G->>G: _is_public? link-local IP literal detected
G--xT: EgressDeniedError (no request ever leaves)Figure 5 — schema rejection & descriptor drift: both fail closed.
sequenceDiagram
participant C as Caller
participant R as Registry
participant I as integrity
C->>R: tools/call with unexpected field
R--xC: validation error (additionalProperties=false, before execute())
C->>I: verify_descriptors(registry, expected_checksums)
I-->>C: drift list (changed / added / removed descriptors)
Note over I: sha256 over name + description + input schemaFigure 6 — tool-call lifecycle (state machine). Every terminal state — success, failure, or denial — passes through the audit before the response leaves.
stateDiagram-v2
[*] --> Received: POST /tools/call
Received --> Authenticated: X-MCP-Api-Key ok
Received --> Rejected: bad or missing key (401)
Authenticated --> Validated: JSON Schema ok
Authenticated --> Rejected: unknown or invalid args
Validated --> Guarded: caller in scope AND egress allowed
Validated --> Denied: off-scope or EgressDenied
Guarded --> Executing: adapter call (timeout armed)
Executing --> Succeeded: carrier or pure result
Executing --> Failed: timeout or carrier error
Succeeded --> Audited: args-hashed record
Failed --> Audited: error_class recorded
Denied --> Audited
Audited --> [*]Tools
Tool | Kind | Honest semantics |
| carrier-backed | deliverability / normalization |
| carrier-backed | preview pricing — never bookable |
| pure | billable = max(actual, L·W·H ÷ divisor) |
| pure | labelled profile → estimated dims, |
| pure | freeform → components + rule-derived confidence; reports missing parts, never guesses |
| corpus-backed | allowed/warning/prohibited + source; advisory-only — never asserts "cleared" |
Object design (OOD)
Figure 7 — class model. Tools depend on the provider port, never a concrete carrier — adding a carrier is a new adapter; the tool layer is untouched.
classDiagram
class Tool {
<<abstract>>
+name
+description
+parameters
+schema() JSONSchema2020_12
+validate_input(args)
+execute(args)
}
Tool <|-- ValidateAddress
Tool <|-- GetQuotePreview
Tool <|-- CalculateDimensionalWeight
Tool <|-- EstimatePackageProfile
Tool <|-- ParseAddress
Tool <|-- CheckRestrictedItems
class ToolRegistry {
+list_tools()
+get(name)
}
ToolRegistry o-- Tool
class ShippingProvider {
<<interface>>
+validate_address()
+quote_preview()
}
ShippingProvider <|.. MockProvider
ShippingProvider <|.. FedExProvider
ShippingProvider <|.. UPSProvider
ShippingProvider <|.. USPSProvider
ShippingProvider <|.. DHLProvider
GetQuotePreview ..> ShippingProvider
ValidateAddress ..> ShippingProvider
class ToolGuard {
+allowed_hosts()
+check_egress(url)
+is_authorized(caller, tool)
}
class ToolAudit {
+args_hash(args) sha256
+record(...)
}Security layers & threat model
Five independent controls stack on every call:
Shared-key auth —
require_api_key(X-MCP-Api-Key).SSRF egress allowlist — allowlisted carrier hosts only; private/loopback/link-local IP literals denied before any I/O.
Per-caller tool scopes — off-scope tools are denied, not merely hidden.
Descriptor integrity — sha256 checksums make registry drift detectable.
Append-only, args-hashed audit — full observability, no PII store.
Threat | Control |
Unauthenticated access | shared-key |
SSRF / metadata endpoint | egress allowlist + private-IP denial |
Caller privilege creep | per-caller tool scopes |
Poisoned tool descriptors (supply chain) | sha256 checksums + |
PII in audit | args-hash, never raw args |
Write capability smuggled in | frozen allowlist + boot-time |
The audit trail
Figure 8 — one PII-safe record per call. Raw arguments (which may carry addresses) are never stored — only their hash, which still allows exact-call correlation.
flowchart LR
CALL["tools/call"] --> H["args_hash = sha256(args)"]
H --> REC["record: tool · version · caller · request_id · args_hash · status · error_class · latency_ms"]
REC --> LOG[("append-only tool audit")]
LOG --> Q["what did the tool layer do, for whom, how fast = a query"]Performance & availability
(This service is stateless — it owns no database, so there is no entity-relationship model here; the platform's ER story lives in ShipSmart-Infra.)
Latency budget (target):
Tool | Validation | Carrier RTT | Total (target) |
pure tools (×3) | ~2 ms | — | < 10 ms |
validate_address | ~2 ms | 300–1500 ms | < 2000 ms |
get_quote_preview | ~2 ms | 300–1800 ms | < 2000 ms |
xychart-beta
title "Tool latency budget in ms (target)"
x-axis ["pure-tools", "validate_address", "quote_preview"]
y-axis "ms (target)" 0 --> 2000
bar [10, 1200, 1800]Availability & degradation (coded behaviors, facts):
Failure | Behavior |
Carrier timeout | explicit httpx timeout → typed error, |
No carrier keys | MockProvider default — fully functional, keyless |
Bad caller key | 401 at |
Off-scope caller | denied by |
Availability 99.5% (target); probes: GET /health (the tool count doubles
as a registry-drift canary) and GET / discovery.
Deployment topology
Figure 9 — production layout. Called only by the API; /docs off in prod;
sandbox carrier base URLs by default.
flowchart LR
A["Render: shipsmart-api-python"] -->|X-MCP-Api-Key| M["Render: shipsmart-mcp (this)"]
M --> C1["FedEx sandbox"]
M --> C2["UPS sandbox"]
M --> C3["USPS sandbox"]
M --> C4["DHL sandbox"]
OPS[Operator] -.->|"GET /health — tool count"| MRunning locally
uv sync
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8001
curl localhost:8001/health # {"service":"shipsmart-mcp","tools":N}Keyless by default (SHIPPING_PROVIDER=mock). Set carrier credentials via env
to hit sandboxes.
Configuration
Env | Effect |
| shared key for |
|
|
carrier | sandbox-by-default adapters |
Tests
uv run pytest # 94 tests — keyless (mock provider), fast
uv run ruff check .The API↔MCP tool-policy contract is additionally asserted by ShipSmart-Test in CI.
License
See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nia194/ShipSmart-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server