@cyanheads/un-comtrade-mcp-server
Access UN Comtrade international merchandise and services trade statistics โ country lookups, HS commodity search, bilateral trade flows, balances, rankings, and data availability โ via MCP. STDIO or Streamable HTTP.
9 Tools โข 2 Resources
Overview
International merchandise and services trade statistics from UN Comtrade. Resolve country and HS commodity codes, fetch bilateral trade flows and services trade, and compute balances, partner/commodity rankings, and data availability from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
| Tool | Description |
|---|
comtrade_lookup_countries | Resolve country and area names to Comtrade M49 numeric codes. |
comtrade_search_commodities | Find HS commodity codes by keyword, description, or code prefix. |
comtrade_list_service_categories | List EBOPS 2010 service trade categories by keyword or parent code. |
comtrade_get_trade_flows | Fetch bilateral trade flow records โ value, quantity, and weight per period/commodity/partner. |
comtrade_get_trade_balance | Compute a country's trade balance (exports minus imports) across one or more periods. |
comtrade_get_top_partners | Rank trading partners by trade value for a reporter, commodity, and flow direction. |
comtrade_get_top_commodities | Rank commodity categories by trade value for a reporter and flow direction. |
comtrade_get_data_availability | Check which reporter/period/classification combinations have published data. |
comtrade_get_services_trade | Fetch international trade-in-services data (EBOPS 2010). |
Resources
| Resource | Description |
|---|
comtrade://countries | Complete country/area code list with M49 codes, ISO identifiers, and reporter validity. |
comtrade://hs-classification/{level} | Top-level HS commodity hierarchy at chapter, heading, or subheading level. |
Both resources are also reachable via tools โ comtrade_lookup_countries and comtrade_search_commodities cover the same data with keyword search.
Capability reference
- Accepts a partial or full country name, or an ISO alpha-2/alpha-3 code;
role filters to "reporter", "partner", or "any" (default)
validAsReporter flag on each match โ regional groupings (e.g. World, EU) are valid partners but not valid reporters
include_groups (default true) toggles regional/economic groupings in the results
- Reference data loads from UN static files at startup โ no subscription key required, instant response
- Free-text keyword, partial description, or code-prefix search;
classification selects HS (combined dataset, default) or a specific edition H0โH6
aggr_level narrows to 2 (chapter), 4 (heading), or 6 (subheading); omit for all levels
- Up to 200 results per call (
limit, default 50); truncated: true when matches exceed the limit
recommendedQueryCode on each result โ the best code to pass as cmd_code in trade queries
- Lists EBOPS 2010 service categories, optionally filtered by keyword or
parent_code
- Up to 500 results per call (
limit, default 100); truncated: true when matches exceed the limit
- Returned
id is the service_code for comtrade_get_services_trade
- Requires
reporter_code, flow_code (M import / X export / RX re-export / RM re-import), and 1โ12 period values (YYYY or YYYYMM)
partner_code: 0 aggregates all partners (World total); omit for a per-partner breakdown
cmd_code[] accepts up to 20 HS codes; omit or pass "TOTAL" for cross-commodity totals
- Free-tier cap of 500 records per call;
truncated: true plus a truncationHint when the cap is hit
isReported flags directly-reported rows vs. UN-estimated/aggregated ones; joins country and commodity descriptions from the startup reference cache
- Runs export (
X) and import (M) fetches in parallel per period, then computes the balance locally
- Returns
balanceUsd (signed), exportsUsd, importsUsd, and coverageRatio (exports / imports) per period
- Optional
cmd_code[] (up to 20) restricts the balance to specific commodities
mirrorCaveat on every response โ the balance reflects this reporter's own values, not the mirror partner's
- Fetches the full per-partner breakdown for one
reporter_code + flow_code (M/X) + single period, then sorts locally by value
- Optional single
cmd_code; omit for total merchandise trade
- Returns up to
limit partners (max 50, default 10), each with rank, primaryValueUsd, and sharePercent
truncated: true when the underlying fetch hit the 500-record cap
- Ranks commodity categories for one
reporter_code + flow_code + single period
aggr_level selects 2 (HS chapter, default) or 4 (heading); optional partner_code scopes to one bilateral relationship
- Returns up to
limit categories (max 50, default 10), each with rank, primaryValueUsd, and sharePercent
truncated: true when the underlying fetch hit the 500-record cap
- All filters optional โ
reporter_code, period, freq (A/M, default A), type_code (C goods / S services, default C), classification (default HS)
- Returns per-dataset
totalRecords and publicationDate; omit all filters to browse the full availability index
- Annual data typically publishes 3โ12 months after the reference year โ call this before querying recent periods
- Same bilateral shape as
comtrade_get_trade_flows: reporter_code, flow_code (M/X), 1โ12 period values, optional partner_code (0 for all partners combined)
service_code filters to one EBOPS category; resolve it with comtrade_list_service_categories
- Free-tier cap of 500 records per call;
truncated: true plus a truncationHint when the cap is hit
- Services trade has more limited country and period coverage than goods trade
comtrade://countries resource
- Complete country/area list as
application/json โ M49 code, ISO identifiers, validAsReporter, and isGroup
- Loaded from the same startup reference cache
comtrade_lookup_countries searches
comtrade://hs-classification/{level} resource
level path param accepts 2 (chapters), 4 (headings), or 6 (subheadings); any other value throws a validation error
- Returns each code's
description, parent, and isLeaf; full leaf enumeration is too large to inject โ use comtrade_search_commodities for keyword search
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Comtrade-specific:
- Reference data (country codes, HS hierarchy, EBOPS categories) loaded from UN static files at startup โ keyword search with no per-request fetches
- Authenticated (
data/v1/get) and public preview (public/v1/preview) endpoints โ tools fall back to the preview endpoint when no subscription key is set
- Parallel sub-request execution in workflow tools (
comtrade_get_trade_balance runs export and import fetches concurrently)
- Retry with exponential backoff on transient failures, honoring an upstream
Retry-After header when present
- Description enrichment โ joins country and HS/EBOPS names from the reference cache, covering the preview endpoint's omission of
*Desc fields
Agent-friendly output:
truncated: true plus a recovery hint on any response capped at the 500-record free-tier limit
isReported flag on trade flow records distinguishes directly-reported values from UN-estimated/aggregated rows
validAsReporter on country lookups prevents constructing invalid queries with partner-only area codes
mirrorCaveat on trade-balance output surfaces the methodological caveat without parsing error text
Getting started
Prerequisites: A UN Comtrade subscription key is optional but recommended. Without one, tools fall back to the public preview endpoint (500 records/call, lower rate limit). With a free-tier key you get the same record cap but higher request headroom.
Add the following to your MCP client configuration file.
{
"mcpServers": {
"un-comtrade-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/un-comtrade-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"COMTRADE_SUBSCRIPTION_KEY": "your-key-here"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"un-comtrade-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/un-comtrade-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"COMTRADE_SUBSCRIPTION_KEY": "your-key-here"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"un-comtrade-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "COMTRADE_SUBSCRIPTION_KEY=your-key-here",
"ghcr.io/cyanheads/un-comtrade-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 COMTRADE_SUBSCRIPTION_KEY=... bun run start:http
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+). Development and Docker images use Bun 1.4.0.
- Optional: a UN Comtrade subscription key for full API access. Without one, all tools fall back to the public preview endpoint (500 records/call). Reference/lookup tools (
comtrade_lookup_countries, comtrade_search_commodities, comtrade_list_service_categories) never require a key โ they query static UN reference files.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/un-comtrade-mcp-server.git
- Navigate into the directory:
cd un-comtrade-mcp-server
- Install dependencies:
- Configure environment:
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
| Variable | Description | Default |
|---|
COMTRADE_SUBSCRIPTION_KEY | Azure API Management subscription key from comtradedeveloper.un.org. Without it, tools use the public preview endpoint (500-record cap). | โ |
COMTRADE_API_BASE_URL | Override the Comtrade API base URL. | https://comtradeapi.un.org |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path. | /mcp |
MCP_PUBLIC_URL | Public origin override for reverse-proxy deployments. | โ |
MCP_SESSION_MODE | HTTP session posture: auto, stateful, or stateless. No tool asks the caller for input mid-handler, so src/index.ts declares stateless and every deployment surface restates it. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
bun run rebuild
bun run start:stdio
bun run start:http
-
Run checks and tests:
bun run devcheck
bun run test
bun run lint:mcp
Docker
docker build -t un-comtrade-mcp-server .
docker run --rm -e COMTRADE_SUBSCRIPTION_KEY=your-key -p 3010:3010 un-comtrade-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/un-comtrade-mcp-server. OpenTelemetry peer dependencies are installed by default โ build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|
src/index.ts | createApp() entry point โ registers tools, resources, and inits services. |
src/config | Environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Nine tools across reference resolution, trade data, and workflow aggregations. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Country list and HS hierarchy resources. |
src/services/comtrade-data | ComtradeDataService โ authenticated/preview request builder with retry and key fallback. |
src/services/comtrade-reference | ComtradeReferenceService โ reference data loader (countries, HS, EBOPS), startup cache, keyword search. |
src/services/comtrade-meta | ComtradeMetaService โ data availability and dataset metadata endpoints. |
tests/ | Unit and integration tests mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches โ no
try/catch in tool logic
- Use
ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
- Register new tools and resources via the barrels in
src/mcp-server/*/definitions/index.ts
- Wrap external API calls: validate raw โ normalize to domain type โ return output schema; never fabricate missing fields
- Reference data (countries, HS codes) joins must pull from the startup cache, not per-request fetches
Data license
The UN Comtrade license agreement (ยง5) prohibits redistributing data without prior written UN permission. Connect with your own Comtrade subscription key.
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 โ see LICENSE for details.