Skip to main content
Glama
trevorquinn

Supply Chain Disruption Monitor

by trevorquinn

Supply Chain Disruption Monitor

An MCP server exposing supply chain intelligence tools, with a PydanticAI agent that synthesizes them to answer disruption questions.

Demo scenario: "What disruptions could affect shipments from Shanghai to Rotterdam right now?"

The agent calls multiple tools, synthesizes the results, and produces a structured risk assessment — demonstrating that the value is in the reasoning across sources, not any single data lookup.

Built as a self-training project and portfolio artifact.


Tech stack

Layer

Choice

Notes

MCP server

Python MCP SDK (mcp)

FastMCP decorator API, stdio transport

Agent framework

PydanticAI

MCPToolset + StdioTransport to wire agent → server

LLM

qwen2.5:7b via Ollama

Local, reliable tool calling

Vessel data

AISStream.io

Free WebSocket AIS feed

Weather

Open-Meteo

Free, no API key

News

NewsAPI

Free tier (100 req/day)

Port congestion

Mocked

Realistic synthetic data


Related MCP server: ShippingRates

Setup

1. Prerequisites

  • Python 3.11+

  • uvpip install uv or brew install uv

  • Ollama — for the local LLM

2. Install dependencies

cd supply-chain-disruption-monitor
uv sync

3. Pull the model

ollama pull qwen2.5:7b

4. Configure API keys

cp .env.example .env

Edit .env and fill in:

  • AISSTREAM_API_KEY — Free at aisstream.io. Provides real-time vessel positions via WebSocket AIS feed.

  • NEWS_API_KEY — Free at newsapi.org. 100 requests/day on the free tier.

The weather tool (Open-Meteo) needs no key.


Running

MCP server (standalone — for development / MCP Inspector)

uv run mcp dev server.py

This opens the MCP Inspector in your browser so you can call tools interactively.

Agent only

Note: agent.py is a sample MCP client, not part of the server itself. It plays the same role Claude Desktop, Codex, or any other MCP client would — it launches server.py over stdio, runs the agent loop against a local model (Ollama), and synthesizes the tool results. The server is the deliverable; the agent is just one interchangeable consumer of it. It imports nothing from server.py and talks to it purely over the MCP protocol, so you can swap in any other MCP client without touching the server.

# Default query: Shanghai to Rotterdam risk assessment
uv run agent.py

# Custom query
uv run agent.py "What risks affect container ships transiting the Red Sea?"

Full demo (tool outputs + agent synthesis)

uv run demo.py              # tools + agent
uv run demo.py --tools-only # just raw tool outputs
uv run demo.py --agent-only # just the agent synthesis

MCP tools

Tool

Source

Returns

list_major_ports(region)

Static

Major ports by region — use this first to identify route waypoints

get_port_weather(port_name)

Open-Meteo

Current conditions + 24h forecast, wind speed in knots, operational impact

get_vessel_positions(region)

AISStream.io

Live vessel snapshot: MMSI, name, position, speed, nav status

search_disruption_news(query, days)

NewsAPI

Recent headlines + high-signal flag (strikes, attacks, blockages)

get_port_congestion(port_name)

Mocked

Utilization %, queue depth, wait hours, trend, advisory

The agent synthesizes across all five — there is no single assess_route_risk tool. The multi-tool reasoning is the point.


Add this server to Claude Desktop

Add to your Claude Desktop claude_desktop_config.json:

{
  "mcpServers": {
    "supply-chain-monitor": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/supply-chain-disruption-monitor", "python", "server.py"],
      "env": {
        "AISSTREAM_API_KEY": "your_key_here",
        "NEWS_API_KEY": "your_key_here"
      }
    }
  }
}

Project structure

supply-chain-disruption-monitor/
├── server.py           # FastMCP server — all 5 tools
├── agent.py            # Sample MCP client (PydanticAI) — interchangeable with Claude Desktop, Codex, etc.
├── demo.py             # Demo script (tool outputs + agent synthesis)
├── tools/
│   ├── ports.py        # Static port data + coordinates (27 major ports)
│   ├── weather.py      # Open-Meteo integration
│   ├── vessels.py      # AISStream.io WebSocket integration
│   ├── news.py         # NewsAPI integration
│   └── congestion.py   # Mocked port congestion (realistic synthetic data)
├── pyproject.toml
├── .env.example
└── README.md

Design notes

Why mock port congestion? Live data (MarineTraffic, FreightWaves) requires enterprise subscriptions (£100+/month). Mocking gives full demo control — a "Shanghai congestion spike" can be shown without paywall friction. The tool interface is identical to what a real API would return.

Why AISStream.io? It's a free, real-time WebSocket AIS feed. A vessel actually moving through the South China Sea is a better demo moment than a static mock.

Why qwen2.5:7b? Reliable tool-calling behavior at a size that runs well on consumer hardware (16 GB RAM). Swap in any model with Ollama support by changing OLLAMA_MODEL in .env.

Why PydanticAI? PydanticAI co-maintains the official Python MCP SDK, so using their stack is thematically coherent. MCPServerStdio is the native path for agent → MCP server wiring.

Available Tools

5 tools
get_port_congestionA

Get current congestion metrics for a port: vessel queue, wait times, capacity utilization, trend, and operational advisory.

Data is realistically mocked (live data requires MarineTraffic or FreightWaves enterprise subscription). The interface mirrors what a production integration would return.

Args: port_name: Port common name or UN/LOCODE

Returns: dict with capacity_utilization_pct, vessels_queued, estimated_wait_hours, trend_vs_yesterday, severity, and advisory

ParametersJSON Schema
NameRequiredDescriptionDefault
port_nameYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It transparently discloses that data is 'realistically mocked' and that live data requires a subscription, which is a critical behavioral trait. It also lists return fields and notes that the interface mirrors production, going beyond typical read-only disclosures.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a clear one-sentence summary, followed by a well-organized 'Args' and 'Returns' structure. Every sentence adds value, and the mock-data note and field list are essential.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description provides a complete list of return keys and a clear parameter description. It also explains the mocking limitation, leaving no critical information missing for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description fully compensates by stating 'port_name: Port common name or UN/LOCODE', adding meaning beyond the bare string type. This gives the agent precise guidance on accepted formats.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource: 'Get current congestion metrics for a port: vessel queue, wait times, capacity utilization, trend, and operational advisory.' This clearly differentiates it from sibling tools like get_port_weather or get_vessel_positions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for retrieving congestion metrics but does not explicitly state when to use it over alternatives or provide exclusions. The mock-data note is useful but not a usage guideline. It would benefit from naming siblings, e.g., 'For weather conditions, use get_port_weather.'

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_port_weatherA

Get current weather conditions and 24-hour forecast for a port.

Weather is sourced from Open-Meteo (free, no API key required). Pay attention to wind speed in knots — values above 25 kn affect large vessel operations; above 40 kn typically suspends port activity.

Args: port_name: Port common name (e.g. "Rotterdam") or UN/LOCODE (e.g. "NLRTM")

Returns: dict with current conditions, wind speed, operational impact assessment, and hourly 24h forecast

ParametersJSON Schema
NameRequiredDescriptionDefault
port_nameYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses the data source (Open-Meteo, free, no API key), mentions that wind speed is in knots and provides operational thresholds, and describes the return payload. This adds meaningful behavioral context beyond what the tool name alone implies.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and well-organized: purpose, source, wind-speed advisory, args, and returns. Every sentence adds value, and the structure is easy to scan. No redundant or filler text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given one parameter, no output schema, and no annotations, the description covers the essential details: input format, sources, key thresholds, and return contents. It doesn't specify timezone or update frequency, but these are not critical for a weather-forecast tool and the description is sufficiently complete for effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description fully compensates. It explains exactly what port_name accepts: common name or UN/LOCODE, with examples. This is precise and leaves no ambiguity for the single parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves current weather and a 24-hour forecast for a port. It uses a specific verb (get) and resource (port weather), and this is distinct from sibling tools that handle port lists, vessel positions, disruptions, and congestion.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when this tool is useful, especially for assessing port operations based on wind speed thresholds. It doesn't explicitly mention alternatives or exclusions, but the context is clear enough. The wind speed thresholds provide actionable guidance on operational impact.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_vessel_positionsA

Get live vessel positions from AIS transponder data for a shipping region.

Uses AISStream.io free WebSocket feed (requires AISSTREAM_API_KEY in .env). Listens for ~8 seconds and returns a snapshot of vessels in the region.

Named regions this tool recognizes (not specific to any one route — use whichever apply to the route you're assessing): "South China Sea", "East China Sea", "Strait of Malacca", "Indian Ocean", "Red Sea", "Suez Canal", "Persian Gulf", "Mediterranean", "English Channel", "North Sea", "Taiwan Strait", "East Asia", "Northwest Europe", "North America West", "North America East"

Args: region: Named region — see list above, or call list_major_ports() for context max_vessels: Maximum vessels to return (default 20)

Returns: dict with vessel_count, underway/anchored breakdown, and per-vessel details (MMSI, name, position, speed, course, nav status)

ParametersJSON Schema
NameRequiredDescriptionDefault
regionYes
max_vesselsNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full responsibility and excels: it discloses the AISStream.io WebSocket source, the required AISSTREAM_API_KEY in .env, the ~8-second listening time, and that it returns a snapshot. These are critical operational details not visible in the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured into source, region list, args, and returns. The region list is long but necessary for the agent to know valid inputs, and every sentence adds value with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, so the description must explain return values. It does so explicitly: dict with vessel_count, underway/anchored breakdown, and per-vessel details like MMSI, name, position, speed, course, and nav status. Combined with prerequisites and latency, the agent has everything needed to invoke and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must explain parameters. It fully documents the region parameter with all valid named regions and suggests list_major_ports() for context, and it explains max_vessels with its default. This adds significant meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it gets live vessel positions from AIS transponder data for a shipping region, using a specific verb and resource. This is distinct from sibling tools like get_port_congestion or get_port_weather, so no confusion arises.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use the tool, tying it to route assessment and listing named regions. It also references list_major_ports() for context, but it doesn't explicitly state when not to use the tool or directly name alternatives for the same task.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_major_portsA

List major container ports, optionally filtered by region.

This is the grounding tool — call it first when you need to know which ports are relevant to a route or region before calling other tools.

Args: region: Optional region filter. Available regions: East Asia, Southeast Asia, South Asia, Middle East, Red Sea, Northwest Europe, Mediterranean, North America West, North America East. Leave empty to list all ports.

Returns: dict with 'region', 'port_count', and 'ports' list

ParametersJSON Schema
NameRequiredDescriptionDefault
regionNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden. It discloses the return format (dict with region, port_count, ports) and the region options, but does not explicitly state whether the operation is read-only or discuss potential side effects; however, the 'list' action implies safety.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a summary line, a usage note, and clear Args/Returns sections. It is slightly verbose but each part adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one optional parameter and no output schema, the description covers purpose, usage, parameters, and return structure. It is complete for the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description compensates fully. It explains the region parameter, lists all valid values, and instructs that empty returns all ports.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists major container ports with an optional region filter. It distinguishes itself from sibling tools (weather, vessel positions, etc.) by being the grounding tool for port discovery.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to call this tool first when needing to know which ports are relevant to a route or region before calling other tools. This provides clear context, though it does not name specific alternatives or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_disruption_newsA

Search recent news for supply chain disruption signals.

Uses NewsAPI (free tier, 100 req/day — requires NEWS_API_KEY in .env). Returns articles with a 'flagged_high_signal' list highlighting those mentioning strikes, storms, blockages, attacks, or sanctions.

If the query names a known port or shipping region, results are filtered to articles that actually mention that place — this prevents e.g. a Red Sea query returning unrelated Strait of Hormuz coverage just because both mention "attack". Check the 'location_filter' and 'filtered_out' fields in the response to see if/how this applied. Lead each query with the exact place name for the filter to engage.

Example query shapes — lead with the place actually relevant to your route, not necessarily these:

  • " port delay" (e.g. "Rotterdam port delay")

  • " attack blockage" (e.g. "Red Sea attack blockage")

  • " disruption" (e.g. "Suez Canal disruption")

  • "container shipping freight rates" (no location — not filtered)

Args: query: Search query string, ideally leading with a specific place name days: Days back to search (default 7, max 30 on free tier)

Returns: dict with articles list, flagged_high_signal subset, and location_filter/filtered_out showing whether results were filtered

ParametersJSON Schema
NameRequiredDescriptionDefault
daysNo
queryYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully carries the burden and does so thoroughly: it discloses the NewsAPI dependency, rate limit (100 req/day), required API key, the high-signal flagging logic, and the location-filtering behavior with response fields (location_filter, filtered_out). This is rich behavioral context well beyond the raw tool name.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is somewhat long but front-loaded with the main purpose, followed by useful details, examples, and parameter explanations. Every section earns its place; the example query shapes are slightly repetitive but add value. It is not as tight as the two-sentence get_calls example but still well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema and no annotations, the description explains the return structure (articles, flagged_high_signal, location_filter/filtered_out) and mentions rate limits. It does not discuss empty-result behavior or error cases, but for a search tool this is adequate coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, but the description fully compensates by explaining both parameters: 'query' should lead with a place name (with examples), and 'days' gets a default and max value. This adds meaning the schema lacks entirely.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action and resource: 'Search recent news for supply chain disruption signals.' It clearly distinguishes from sibling tools (list_major_ports, get_port_weather, get_vessel_positions, get_port_congestion) by focusing on news, not port/weather/vessel data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete usage guidance: asks users to lead queries with the exact place name for location filtering to engage, and gives example query shapes. It also explains when the filter is NOT applied. It lacks explicit alternatives/exclusions, but the sibling tools are clearly different, minimizing the need for such exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 5 tool updatesv0.1.0
    • First observedget_port_congestion
    • First observedget_port_weather
    • First observedget_vessel_positions
    • First observedlist_major_ports
    • First observedsearch_disruption_news

TDQS

A4.6/5.0

Scored across 5 tools

Disambiguation5/5

Each tool targets a distinct data source or aspect: port catalog, weather, vessel positions, news, and congestion. There is no overlap in purpose, and the descriptions clarify when to use each.

Naming Consistency5/5

All tool names use a consistent verb_noun pattern in snake_case: list_major_ports, get_port_weather, get_vessel_positions, search_disruption_news, get_port_congestion. The verbs are appropriate for each action, and the naming is predictable.

Tool Count5/5

Five tools is a well-scoped count for a supply chain disruption monitor. Each tool covers a distinct capability without redundancy, and the set is neither too thin nor too heavy.

Completeness4/5

The tool set covers the core workflow: grounding via port list, then checking weather, vessel positions, news, and congestion. Minor gaps like historical trends or a combined risk assessment exist, but agents can work around them.

Maintenance

ActivitySlowing
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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/trevorquinn/supply-chain-disruption-monitor'

If you have feedback or need assistance with the MCP directory API, please join our Discord server