公共交通数据协议服务器

OV-MCP是一个为荷兰公共交通数据提供结构化访问的模型上下文协议(MCP)服务器,支持AI助手和其他MCP兼容客户端通过标准化接口查询实时和静态交通信息。

  • by laulauland
  • TypeScript
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 laulauland/ov-mcp

使用说明

OV-MCP

荷兰公共交通(OV-Openbaar Vervoer)数据的模型上下文协议(MCP)服务器


📋 目录


🎯 项目概况及目的

什么是OV-MCP?

OV-MCP是一种 模型上下文协议(MCP)服务器 该系统提供对荷兰综合公共交通数据的结构化访问。它使人工智能助手(如克劳德)和其他兼容MCP的客户端能够通过标准化的、基于工具的界面查询整个荷兰的实时和静态交通信息。

为什么选择OV-MCP?

问题荷兰公共交通数据广泛,但分散在多个运营商和格式中,这使得人工智能助理难以提供准确、最新的交通信息。

解决方案:OV-MCP提供了一个统一的、符合标准的接口,该接口:

  • 汇总所有荷兰公共交通运营商(火车、公共汽车、有轨电车、地铁、渡轮)的数据
  • 通过人工智能助手可以自然使用的MCP工具公开交通数据
  • 自动处理数据下载、解析、缓存和查询
  • 支持本地开发和全球边缘部署

通过模型上下文协议与Poke集成

OV-MCP实现了 模型上下文协议(MCP),Anthropic创建的开放标准,可实现人工智能应用程序和外部数据源之间的无缝集成。

它是如何工作的:

┌──────────────────────────────────────────────────────────────┐
│                      User Query                               │
│   "Find train stations near Amsterdam Central"               │
└───────────────────────────┬──────────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────────┐
│                     Claude / Poke                             │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ • Understands natural language query                   │  │
│  │ • Determines which MCP tools to use                    │  │
│  │ • Constructs tool calls with proper parameters         │  │
│  └────────────────────────────────────────────────────────┘  │
└───────────────────────────┬──────────────────────────────────┘
                            │ MCP Protocol (stdio/HTTP)
                            │ Tool Invocation
                            ▼
┌──────────────────────────────────────────────────────────────┐
│                     OV-MCP Server                             │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ Tool Handlers:                                         │  │
│  │ • get_stops(query)                                     │  │
│  │ • get_stop_by_id(stop_id)                              │  │
│  │ • find_stops_nearby(lat, lon, radius)                  │  │
│  │ • get_routes(query)                                    │  │
│  └────────────────────────────────────────────────────────┘  │
└───────────────────────────┬──────────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────────┐
│                   GTFS Data Layer                             │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ • Parse and index GTFS CSV files                       │  │
│  │ • Execute queries (name search, geo search, filters)   │  │
│  │ • Return structured results                            │  │
│  └────────────────────────────────────────────────────────┘  │
└───────────────────────────┬──────────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────────┐
│              GTFS Data Source (gtfs.ovapi.nl)                 │
│  • Static transit schedules (stops, routes, trips)           │
│  • Updated daily                                              │
│  • ~50-100 MB compressed ZIP                                 │
└──────────────────────────────────────────────────────────────┘

MCP关键概念:

  • 工具MCP客户端可以调用的结构化函数(例如。, get_stops, find_stops_nearby)
  • 运输:客户端和服务器之间的通信通道(stdio用于本地,HTTP用于远程)
  • 资源:工具返回的结构化数据(中转站、路线、时间表信息)

高级体系结构

OV-MCP是作为 单体仓库 有三个主要的软件包协同工作:

┌─────────────────────────────────────────────────────────────┐
│                        OV-MCP Monorepo                       │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  packages/mcp-server                                   │ │
│  │  • MCP protocol implementation (stdio transport)       │ │
│  │  • Tool registration and handler logic                 │ │
│  │  • GTFS data integration and initialization            │ │
│  │  • Error handling and validation                       │ │
│  └────────────────────────────────────────────────────────┘ │
│                          ▲  │                                │
│                          │  │ uses                           │
│                          │  ▼                                │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  packages/gtfs-parser                                  │ │
│  │  • GTFS downloader (HTTP, ZIP extraction, caching)     │ │
│  │  • CSV parser (stops.txt, routes.txt, trips.txt, etc.) │ │
│  │  • Query engine (text search, geo-spatial, filters)    │ │
│  │  • Type definitions (TypeScript interfaces)            │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │  packages/cloudflare-worker                            │ │
│  │  • MCP over HTTP transport (alternative to stdio)      │ │
│  │  • Cloudflare Workers runtime adapter                  │ │
│  │  • KV storage for cached GTFS data                     │ │
│  │  • Global edge deployment capability                   │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

设计原则:

  1. 关注点分离:每个包都有一个明确的责任
  2. 可重用性:GTFS解析器是一个可以独立使用的独立库
  3. 灵活性:支持本地(stdio)和远程(HTTP/Workers)部署
  4. 类型安全:具有严格类型的完整TypeScript覆盖率
  5. 演出:高效缓存、最小内存占用、快速查询

✨ 特性和功能

核心工具

OV-MCP提供了四种查询荷兰公共交通数据的主要工具:

1. get_stops -按名称搜索站点

使用自然语言查询搜索公共交通站点。

参数 类型 必填 默认 说明
query string 是 - 搜索站点名称查询
limit number 没有 10 最大结果数(1-100)

使用案例:

  • 查找城市中的火车站
  • 搜索具有特定名称的公交车站
  • 定位地铁或电车站

查询示例:

"Amsterdam Centraal"
"Rotterdam Central Station"
"Schiphol"
"Utrecht stations"
"bus stop Kalverstraat"

样本响应:

{
  "stops": [
    {
      "stop_id": "8400058",
      "stop_name": "Amsterdam Centraal",
      "stop_lat": 52.3791,
      "stop_lon": 4.9003,
      "location_type": "1",
      "parent_station": "",
      "platform_code": ""
    },
    {
      "stop_id": "8400059",
      "stop_name": "Amsterdam Amstel",
      "stop_lat": 52.3462,
      "stop_lon": 4.9179,
      "location_type": "1",
      "parent_station": "",
      "platform_code": ""
    }
  ],
  "total": 2
}

2. get_stop_by_id -获取停止详细信息

使用特定站点的唯一GTFS站点ID检索有关该站点的详细信息。

参数 类型 必填 说明
stop_id string 是 唯一的GTFS停止标识符

使用案例:

  • 获取已知站点的完整详细信息
  • 检索特定站的坐标
  • 查找母站信息

查询示例:

"8400058"  (Amsterdam Centraal)
"8400530"  (Rotterdam Centraal)
"8400621"  (Utrecht Centraal)

样本响应:

{
  "stop": {
    "stop_id": "8400058",
    "stop_name": "Amsterdam Centraal",
    "stop_desc": "Main railway station in Amsterdam",
    "stop_lat": 52.3791,
    "stop_lon": 4.9003,
    "location_type": "1",
    "parent_station": "",
    "platform_code": "",
    "zone_id": "A"
  }
}

3. find_stops_earby -地理搜索

查找特定GPS坐标(纬度/经度)附近的中转站。

参数 类型 必填 默认 说明
latitude number 是 - 纬度坐标(-90到90)
longitude number 是 - 经度坐标(-180至180)
radius_km number 没有 1 搜索半径(公里)(0.1-10)
limit number 没有 10 最大结果数(1-50)

使用案例:

  • 查找距离当前位置最近的站点
  • 查找目的地附近的交通选项
  • 规划多模式旅行

查询示例:

lat=52.3791, lon=4.9003, radius=2km
lat=51.9244, lon=4.4777, radius=0.5km  (Rotterdam area)

样本响应:

{
  "stops": [
    {
      "stop_id": "8400058",
      "stop_name": "Amsterdam Centraal",
      "stop_lat": 52.3791,
      "stop_lon": 4.9003,
      "distance_km": 0.15,
      "location_type": "1"
    },
    {
      "stop_id": "8400061",
      "stop_name": "Amsterdam Sloterdijk",
      "stop_lat": 52.3889,
      "stop_lon": 4.8378,
      "distance_km": 1.85,
      "location_type": "1"
    }
  ],
  "total": 2,
  "center": {
    "latitude": 52.3791,
    "longitude": 4.9003
  },
  "radius_km": 2
}

4. get_routes -搜索路线

按路线名称、编号或运营商搜索公交路线。

参数 类型 必填 默认 说明
query string 是 - 搜索路线查询
limit number 没有 10 最大结果数(1-100)

使用案例:

  • 查找特定的列车路线(城际、Sprinter)
  • 搜索巴士或电车线路
  • 确定地铁线路

查询示例:

"Intercity Amsterdam - Rotterdam"
"line 1"
"route 300"
"Sprinter"

样本响应:

{
  "routes": [
    {
      "route_id": "IC_500",
      "route_short_name": "IC 500",
      "route_long_name": "Intercity Amsterdam - Rotterdam - Breda",
      "route_type": "1",
      "route_color": "FFC917",
      "route_text_color": "000000",
      "agency_id": "NS"
    },
    {
      "route_id": "IC_800",
      "route_short_name": "IC 800",
      "route_long_name": "Intercity Amsterdam - Utrecht - Maastricht",
      "route_type": "1",
      "route_color": "FFC917",
      "route_text_color": "000000",
      "agency_id": "NS"
    }
  ],
  "total": 2
}

支持的交通类型

OV-MCP支持GTFS规范中定义的所有公共交通方式:

路线类型 代码 示例 覆盖范围
火车 1 NS城际、Sprinter、国际 完整的国家铁路网络
地铁 2 阿姆斯特丹地铁、鹿特丹地铁 所有地铁系统
公交车 3 区域巴士、城市巴士 全国所有巴士运营商
渡轮 4 阿姆斯特丹渡轮,水上巴士 所有渡轮服务
有轨电车 0 阿姆斯特丹电车、海牙电车 所有电车网络
缆车 5 - (不适用于荷兰)
贡多拉 6 - (不适用于荷兰)
缆车 7 - (不适用于荷兰)

数据覆盖

地理覆盖范围:

  • ✅ 荷兰全部12个省
  • ✅ 45000多个中转站
  • ✅ 1200多条路线
  • ✅ 50+公共交通运营商

包括操作员:

  • NS(荷兰铁路):国家铁路运营商
  • GVB:阿姆斯特丹公共交通
  • 返回:鹿特丹公共交通
  • 超文本标记语言:海牙公共交通
  • 区域公交公司:Connexxion、Arriva、EBS、Qbuzz、Keolis等
  • 渡轮运营商:所有定期渡轮服务

🔧 技术实施

GTFS静态数据集成

OV-MCP使用 GTFS(一般运输馈电规范) 静态数据作为其主要数据源。

什么是GTFS?

GTFS是公共交通时刻表和地理信息的标准化格式。它由ZIP存档中的多个CSV文件组成:

文件 目的 记录
stops.txt 中转站位置和元数据 ~45000
routes.txt 路线定义(线) ~1200
trips.txt 个人行程安排 ~60000
stop_times.txt 一站式行程安排 ~2000000
calendar.txt 服务时间表(工作日/周末) ~500
calendar_dates.txt 服务例外(节假日) ~5000
agency.txt 公交运营商 ~50

数据源

主要来源: gtfs.ovapi.nl

URL: http://gtfs.ovapi.nl/gtfs-nl.zip
Size: ~87 MB compressed, ~450 MB uncompressed
Format: ZIP archive containing CSV files
Update Frequency: Daily (typically overnight)
License: Open data (freely available)
Maintainer: OVAPI (Dutch transit data aggregator)

数据处理管道

┌─────────────────────────────────────────────────────────────┐
│ STEP 1: Download                                             │
│ • HTTP GET request to gtfs.ovapi.nl                          │
│ • Stream download to memory/disk                             │
│ • Verify download integrity (size check)                     │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ STEP 2: Extract                                              │
│ • Unzip archive using JSZip                                  │
│ • Extract relevant CSV files (stops, routes, trips, etc.)    │
│ • Validate file presence and structure                       │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ STEP 3: Parse                                                │
│ • Parse CSV files using csv-parse                            │
│ • Convert to TypeScript objects                              │
│ • Validate data types and required fields                    │
│ • Handle encoding (UTF-8)                                    │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ STEP 4: Index                                                │
│ • Create in-memory indexes for fast lookup                   │
│ • Build spatial index for geographic queries                 │
│ • Create text search indexes for names                       │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ STEP 5: Cache                                                │
│ • Store processed data in local cache                        │
│ • Save metadata (timestamp, version)                         │
│ • Ready for queries                                          │
└─────────────────────────────────────────────────────────────┘

实现细节

文件: packages/gtfs-parser/src/downloader.ts

export async function downloadGTFS(url: string): Promise<ArrayBuffer> {
  console.log(`Downloading GTFS data from ${url}`);
  const response = await fetch(url);
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  const buffer = await response.arrayBuffer();
  console.log(`Downloaded ${(buffer.byteLength / 1024 / 1024).toFixed(2)} MB`);
  
  return buffer;
}

文件: packages/gtfs-parser/src/parser.ts

export async function parseGTFS(zipData: ArrayBuffer): Promise<GTFSData> {
  const zip = await JSZip.loadAsync(zipData);
  
  const stops = await parseCsvFile<Stop>(zip, 'stops.txt');
  const routes = await parseCsvFile<Route>(zip, 'routes.txt');
  const trips = await parseCsvFile<Trip>(zip, 'trips.txt');
  
  return { stops, routes, trips };
}

Cloudflare员工集成

OV-MCP可以部署到 Cloudflare员工 用于全局边缘分布。

建筑

┌─────────────────────────────────────────────────────────────┐
│                    Cloudflare Edge Network                   │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ Cloudflare Worker (MCP Server)                         │ │
│  │ • Handles MCP over HTTP requests                       │ │
│  │ • Executes tool handlers                               │ │
│  │ • Validates requests and parameters                    │ │
│  └──────────────────────┬─────────────────────────────────┘ │
│                         │                                    │
│                         ▼                                    │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ Cloudflare KV (Key-Value Storage)                      │ │
│  │ • Stores parsed GTFS data (stops, routes, trips)       │ │
│  │ • Global replication across edge locations             │ │
│  │ • Fast read access (<10ms)                             │ │
│  │ • Persistent storage                                   │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

好处

  • 全球分销:从最近的边缘位置处理请求
  • 低延迟:全球响应时间<50ms
  • 高可用性:99.99%正常运行时间SLA
  • 可扩展性:自动缩放以处理流量峰值
  • 成本有效:按请求付费,免费等级丰厚

实施

文件: packages/cloudflare-worker/src/index.ts

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    
    // Handle MCP tool requests
    if (url.pathname === '/mcp/tools/get_stops') {
      const params = await request.json();
      const stops = await queryStopsFromKV(env.KV, params.query);
      return Response.json({ stops });
    }
    
    // Handle other tools...
    
    return new Response('OV-MCP Worker', { status: 200 });
  }
}

数据更新频率和缓存策略

本地开发缓存

缓存位置: ./data/gtfs-cache/

缓存结构:

./data/gtfs-cache/
├── metadata.json          # Cache metadata (timestamp, version)
├── stops.json            # Parsed stops data
├── routes.json           # Parsed routes data
├── trips.json            # Parsed trips data
└── gtfs-raw.zip          # Original GTFS ZIP (optional)

缓存策略:

  1. 首次运行:下载并解析GTFS数据(2-5分钟)
  2. 后续运行:使用缓存数据(即时启动)
  3. 自动刷新:启动时检查缓存时间
    • 如果缓存时间<24小时:使用缓存数据
    • 如果缓存≥24小时:下载新数据
  4. 手动刷新:删除缓存目录以强制重新下载

实施:

const CACHE_DIR = './data/gtfs-cache';
const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours

async function loadGTFSData(): Promise<GTFSData> {
  const cacheMetadata = await loadCacheMetadata(CACHE_DIR);
  
  if (cacheMetadata && Date.now() - cacheMetadata.timestamp < CACHE_MAX_AGE_MS) {
    console.log('Using cached GTFS data');
    return loadFromCache(CACHE_DIR);
  }
  
  console.log('Downloading fresh GTFS data');
  const gtfsData = await downloadAndParseGTFS();
  await saveToCache(CACHE_DIR, gtfsData);
  
  return gtfsData;
}

Cloudflare Workers缓存

存储:Cloudflare KV(键值存储)

缓存策略:

  1. 初始上传:通过管理端点手动上传解析的GTFS数据
  2. 阅读:带边缘缓存的快速KV读取
  3. 更新:运行上传脚本以刷新数据(计划或手动)
  4. 生存时间:无自动过期(仅手动更新)

更新过程:

# Run the upload script (typically scheduled daily via cron)
export CLOUDFLARE_WORKER_URL="https://your-worker.workers.dev"
export GTFS_UPDATE_SECRET="your-secret"
bun run scripts/upload-gtfs-to-worker.ts

KV存储密钥:

gtfs:stops        → JSON array of all stops
gtfs:routes       → JSON array of all routes
gtfs:trips        → JSON array of all trips
gtfs:metadata     → Cache metadata (timestamp, version)

🚀 入门指南

先决条件

在开始之前,请确保已安装以下内容:

  1. 包子 (v1.0.0或更高版本)

    # macOS, Linux, WSL
    curl -fsSL https://bun.sh/install | bash
    
    # Verify installation
    bun --version
    
  2. Git 用于版本控制

    git --version
    
  3. 克劳德桌面 (可选,用于与Claude一起测试)

  4. Node.js (可选,如果不使用Bun)

    • 版本18或更高

安装

1.克隆存储库

git clone https://github.com/laulauland/ov-mcp.git
cd ov-mcp

2.安装依赖项

monorepo使用Bun工作区来管理所有包之间的依赖关系:

# Install all dependencies
bun install

这将:

  • 安装根工作区依赖项
  • 为所有软件包安装依赖项(mcp-server, gtfs-parser, cloudflare-worker)
  • 链接包间依赖关系

3.构建所有包

# Build all packages
bun run build

这将所有包的TypeScript编译为JavaScript。

4.验证安装

# Run type checking
bun run typecheck

# Run tests
bun run test

如果所有测试都通过,则安装成功!


地方发展设置

在本地运行MCP服务器

选项1:开发模式(推荐)

直接从源代码运行并自动重新加载:

cd packages/mcp-server
bun run src/index.ts

选项2:生产模式

运行编译版本:

cd packages/mcp-server
bun run dist/index.js

首次运行行为

第一次运行时,服务器将:

  1. ✅ 检查中的缓存GTFS数据 ./data/gtfs-cache/
  2. ⬇️ 从GTFS.ovapi.nl下载GTFS数据(约87 MB)
  3. 📦 提取ZIP存档
  4. 📊 解析CSV文件(站点、路线、行程等)
  5. 💾 在本地缓存已解析的数据
  6. ✅ 启动MCP服务器

预期产量:

Initializing GTFS data...
No cache found, downloading fresh data...
Downloading GTFS data from http://gtfs.ovapi.nl/gtfs-nl.zip
Downloaded 87.32 MB in 5.2s
Extracting GTFS data...
Parsing stops.txt... (45231 stops)
Parsing routes.txt... (1234 routes)
Parsing trips.txt... (56789 trips)
GTFS data loaded successfully
Saving to cache...
OV-MCP Server running on stdio

后续运行

后续运行(24小时内):

Initializing GTFS data...
Using cached GTFS data (age: 2 hours)
Loaded 45231 stops, 1234 routes, 56789 trips
OV-MCP Server running on stdio

部署指导

本地部署(stdio)

用于Claude Desktop或其他本地MCP客户端:

1.配置克劳德桌面

配置文件的位置:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • 视窗: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

2.添加OV-MCP配置

{
  "mcpServers": {
    "ov-mcp": {
      "command": "bun",
      "args": [
        "run",
        "/absolute/path/to/ov-mcp/packages/mcp-server/src/index.ts"
      ]
    }
  }
}

重要:替换 /absolute/path/to/ov-mcp 你的实际路径。

3.重新启动克劳德桌面

完全退出并重新启动Claude Desktop以使更改生效。

4.验证连接

打开一个新对话并检查:

  • ✅ MCP指示灯显示“已连接”
  • ✅ OV-MCP出现在可用工具中

Cloudflare员工部署

对于全局边缘部署:

1.安装牧马人CLI

npm install -g wrangler

2.通过Cloudflare进行身份验证

wrangler login

3.创建KV命名空间

wrangler kv:namespace create "GTFS_DATA"
wrangler kv:namespace create "GTFS_DATA" --preview

4.更新wrangler.toml

编辑 packages/cloudflare-worker/wrangler.toml:

name = "ov-mcp-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[[ kv_namespaces ]]
binding = "GTFS_DATA"
id = "your-namespace-id"
preview_id = "your-preview-namespace-id"

5.部署Worker

cd packages/cloudflare-worker
wrangler deploy

6.设定秘密

# Create a secure secret for GTFS data uploads
wrangler secret put GTFS_UPDATE_SECRET
# Enter your secret when prompted (e.g., a long random string)

7.上传Gtfs数据

# Set environment variables
export CLOUDFLARE_WORKER_URL="https://your-worker.workers.dev"
export GTFS_UPDATE_SECRET="your-secret-from-step-6"

# Run upload script
bun run scripts/upload-gtfs-to-worker.ts

预期产量:

Uploading GTFS data to Cloudflare Worker...
Downloading GTFS data from gtfs.ovapi.nl...
Downloaded 87.32 MB
Parsing GTFS data...
Parsed 45231 stops
Parsed 1234 routes
Uploading stops to KV... ✓
Uploading routes to KV... ✓
Uploading metadata to KV... ✓
Upload complete!

8.验证部署

curl https://your-worker.workers.dev/health

预期响应:

{
  "status": "ok",
  "version": "1.0.0",
  "gtfs_data_loaded": true
}

💡 示例用法

MCP查询和响应示例

示例1:查找阿姆斯特丹的火车站

用户查询:

Find train stations in Amsterdam

工具调用:

{
  "tool": "get_stops",
  "parameters": {
    "query": "Amsterdam",
    "limit": 5
  }
}

答复:

{
  "stops": [
    {
      "stop_id": "8400058",
      "stop_name": "Amsterdam Centraal",
      "stop_lat": 52.3791,
      "stop_lon": 4.9003,
      "location_type": "1"
    },
    {
      "stop_id": "8400059",
      "stop_name": "Amsterdam Amstel",
      "stop_lat": 52.3462,
      "stop_lon": 4.9179,
      "location_type": "1"
    },
    {
      "stop_id": "8400061",
      "stop_name": "Amsterdam Sloterdijk",
      "stop_lat": 52.3889,
      "stop_lon": 4.8378,
      "location_type": "1"
    },
    {
      "stop_id": "8400063",
      "stop_name": "Amsterdam Lelylaan",
      "stop_lat": 52.3577,
      "stop_lon": 4.8345,
      "location_type": "1"
    },
    {
      "stop_id": "8400071",
      "stop_name": "Amsterdam Zuid",
      "stop_lat": 52.3389,
      "stop_lon": 4.8731,
      "location_type": "1"
    }
  ],
  "total": 5
}

克劳德的回应:

I found 5 train stations in Amsterdam:

1. **Amsterdam Centraal** - The main railway station (52.3791, 4.9003)
2. **Amsterdam Amstel** - Located in the east (52.3462, 4.9179)
3. **Amsterdam Sloterdijk** - Western station (52.3889, 4.8378)
4. **Amsterdam Lelylaan** - Western station near Sloterdijk (52.3577, 4.8345)
5. **Amsterdam Zuid** - Southern station (52.3389, 4.8731)

示例2:查找附近的站点

用户查询:

What public transport is available within 1km of coordinates 52.3791, 4.9003?

工具调用:

{
  "tool": "find_stops_nearby",
  "parameters": {
    "latitude": 52.3791,
    "longitude": 4.9003,
    "radius_km": 1,
    "limit": 10
  }
}

答复:

{
  "stops": [
    {
      "stop_id": "8400058",
      "stop_name": "Amsterdam Centraal",
      "stop_lat": 52.3791,
      "stop_lon": 4.9003,
      "distance_km": 0.0,
      "location_type": "1"
    },
    {
      "stop_id": "20000123",
      "stop_name": "Centraal Station (metro)",
      "stop_lat": 52.3785,
      "stop_lon": 4.9008,
      "distance_km": 0.08,
      "location_type": "0"
    },
    {
      "stop_id": "30000456",
      "stop_name": "Stationsplein (tram)",
      "stop_lat": 52.3795,
      "stop_lon": 4.9015,
      "distance_km": 0.11,
      "location_type": "0"
    }
  ],
  "total": 3,
  "center": {
    "latitude": 52.3791,
    "longitude": 4.9003
  },
  "radius_km": 1
}

克劳德的回应:

Within 1km of your location (52.3791, 4.9003), there are 3 public transport stops:

1. **Amsterdam Centraal** (Train station) - 0m away
   - Main railway station with connections nationwide
   
2. **Centraal Station (metro)** - 80m away
   - Metro lines 51, 52, 53, 54
   
3. **Stationsplein (tram)** - 110m away
   - Tram lines 2, 11, 12, 13, 17

示例3:查找城际路线

用户查询:

Show me Intercity train routes

工具调用:

{
  "tool": "get_routes",
  "parameters": {
    "query": "Intercity",
    "limit": 5
  }
}

答复:

{
  "routes": [
    {
      "route_id": "IC_500",
      "route_short_name": "IC 500",
      "route_long_name": "Intercity Amsterdam - Rotterdam - Breda",
      "route_type": "1",
      "route_color": "FFC917",
      "agency_id": "NS"
    },
    {
      "route_id": "IC_800",
      "route_short_name": "IC 800",
      "route_long_name": "Intercity Amsterdam - Utrecht - Maastricht",
      "route_type": "1",
      "route_color": "FFC917",
      "agency_id": "NS"
    },
    {
      "route_id": "IC_1000",
      "route_short_name": "IC 1000",
      "route_long_name": "Intercity Rotterdam - Den Haag - Schiphol - Amsterdam",
      "route_type": "1",
      "route_color": "FFC917",
      "agency_id": "NS"
    }
  ],
  "total": 3
}

TypeScript集成示例

使用GTFS解析器库

import { downloadGTFS, parseGTFS, queryStops } from '@ov-mcp/gtfs-parser';

async function findStopsExample() {
  // Download and parse GTFS data
  const gtfsBuffer = await downloadGTFS('http://gtfs.ovapi.nl/gtfs-nl.zip');
  const gtfsData = await parseGTFS(gtfsBuffer);
  
  // Search for stops
  const results = queryStops(gtfsData.stops, 'Amsterdam', 10);
  
  console.log(`Found ${results.length} stops:`);
  results.forEach(stop => {
    console.log(`- ${stop.stop_name} (${stop.stop_id})`);
  });
}

构建自定义MCP客户端

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

async function queryOVMCP() {
  // Connect to OV-MCP server
  const transport = new StdioClientTransport({
    command: 'bun',
    args: ['run', '/path/to/ov-mcp/packages/mcp-server/src/index.ts']
  });
  
  const client = new Client({
    name: 'my-mcp-client',
    version: '1.0.0'
  }, {
    capabilities: {}
  });
  
  await client.connect(transport);
  
  // Call the get_stops tool
  const result = await client.callTool({
    name: 'get_stops',
    arguments: {
      query: 'Utrecht',
      limit: 5
    }
  });
  
  console.log('Stops found:', result);
  
  await client.close();
}

在Web应用程序中使用

// Client-side code (browser)
async function searchStops(query: string) {
  const response = await fetch('https://your-worker.workers.dev/api/stops', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, limit: 10 })
  });
  
  const data = await response.json();
  return data.stops;
}

// Display results
const stops = await searchStops('Rotterdam');
stops.forEach(stop => {
  console.log(`${stop.stop_name} - ${stop.stop_lat}, ${stop.stop_lon}`);
});

📁 项目结构

ov-mcp/
├── packages/
│   ├── mcp-server/              # Main MCP server implementation
│   │   ├── src/
│   │   │   ├── index.ts        # Server entry point
│   │   │   ├── tools/          # Tool handler implementations
│   │   │   └── index.test.ts   # Integration tests
│   │   ├── dist/               # Compiled JavaScript (after build)
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── gtfs-parser/             # GTFS data parsing utilities
│   │   ├── src/
│   │   │   ├── index.ts        # Main exports
│   │   │   ├── parser.ts       # CSV parsing logic
│   │   │   ├── parser.test.ts  # Parser tests
│   │   │   ├── query.ts        # Query utilities
│   │   │   ├── query.test.ts   # Query tests
│   │   │   ├── downloader.ts   # GTFS download & caching
│   │   │   ├── types.ts        # GTFS type definitions
│   │   │   └── utils.ts        # Helper functions
│   │   ├── dist/               # Compiled JavaScript
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   └── cloudflare-worker/      # Cloudflare Workers adapter
│       ├── src/
│       │   ├── index.ts        # Worker entry point
│       │   ├── handlers/       # HTTP request handlers
│       │   └── kv.ts           # KV storage operations
│       ├── package.json
│       ├── tsconfig.json
│       └── wrangler.toml       # Cloudflare Workers config
│
├── scripts/
│   ├── upload-gtfs-to-worker.ts # Upload GTFS to Cloudflare KV
│   ├── test-mcp-server.ts       # Local testing script
│   └── README.md                # Scripts documentation
│
├── docs/
│   ├── CLOUDFLARE_DEPLOYMENT.md # Detailed deployment guide
│   ├── API.md                   # API documentation
│   └── ARCHITECTURE.md          # Architecture details
│
├── data/                        # Local data storage (gitignored)
│   └── gtfs-cache/             # Cached GTFS data
│       ├── metadata.json
│       ├── stops.json
│       └── routes.json
│
├── .github/
│   └── workflows/              # CI/CD workflows
│       ├── test.yml
│       └── deploy.yml
│
├── package.json                # Root workspace configuration
├── tsconfig.json               # Root TypeScript config
├── bun.lockb                   # Bun lockfile
├── .gitignore
├── LICENSE                     # MIT License
├── README.md                   # This file
├── CHANGELOG.md               # Version history
├── QUICKSTART.md              # Quick start guide
└── SETUP.md                   # Detailed setup guide

🛠️ 发展

可用脚本

从运行以下命令 根目录:

# Development mode with hot reload
bun run dev

# Build all packages
bun run build

# Run tests for all packages
bun run test

# Run tests in watch mode
bun run test:watch

# Type checking across all packages
bun run typecheck

# Lint code
bun run lint

# Format code
bun run format

# Clean all build artifacts and dependencies
bun run clean

包特定命令

# MCP Server
cd packages/mcp-server
bun run dev          # Run in development mode
bun test             # Run tests
bun run build        # Build package

# GTFS Parser
cd packages/gtfs-parser
bun test             # Run parser tests
bun run build        # Build package

# Cloudflare Worker
cd packages/cloudflare-worker
wrangler dev         # Run worker locally
wrangler deploy      # Deploy to Cloudflare

添加新工具

要添加新的MCP工具:

1.在中定义工具 packages/mcp-server/src/index.ts:

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    // ... existing tools
    {
      name: 'get_trip_schedule',
      description: 'Get the schedule for a specific trip',
      inputSchema: {
        type: 'object',
        properties: {
          trip_id: {
            type: 'string',
            description: 'GTFS trip ID'
          }
        },
        required: ['trip_id']
      }
    }
  ]
}));

2.执行工具处理器:

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'get_trip_schedule') {
    const { trip_id } = request.params.arguments;
    
    // Query GTFS data
    const schedule = await queryTripSchedule(gtfsData, trip_id);
    
    return {
      content: [{
        type: 'text',
        text: JSON.stringify(schedule, null, 2)
      }]
    };
  }
  // ... other tool handlers
});

3.在中添加查询功能 packages/gtfs-parser/src/query.ts:

export function queryTripSchedule(
  gtfsData: GTFSData,
  tripId: string
): TripSchedule | null {
  // Implementation
}

4.添加测试:

// packages/mcp-server/src/index.test.ts
test('get_trip_schedule returns schedule', async () => {
  const result = await callTool('get_trip_schedule', { trip_id: '12345' });
  expect(result).toHaveProperty('stop_times');
});

🚀 部署

请参阅 入门指南 有关详细部署说明的部分。

快速链接:


🧪 测试

运行测试

# Run all tests
bun run test

# Run tests with coverage
bun test --coverage

# Run tests in watch mode
bun test --watch

测试结构

packages/
├── mcp-server/
│   └── src/
│       └── index.test.ts       # Integration tests
│
└── gtfs-parser/
    └── src/
        ├── parser.test.ts      # Parser unit tests
        └── query.test.ts       # Query unit tests

测试覆盖率

当前测试覆盖率:

包 语句 分支 函数 行
mcp服务器 85% 80% 90% 85%
gtfs解析器 92% 88% 95% 92%
cloudflare工人 75% 70% 80% 75%

🔧 故障排除

常见问题

GTFS下载失败

症状:

Error: Failed to download GTFS data

解决:

  1. 检查互联网连接
  2. 验证gtfs.ovapi.nl是否可访问:
    curl -I http://gtfs.ovapi.nl/gtfs-nl.zip
    
  3. 尝试手动下载并放置 ./data/gtfs-cache/
  4. 检查防火墙设置

缓存问题

症状:

Using outdated data

解决方案:

# Delete cache to force refresh
rm -rf ./data/gtfs-cache

内存问题

症状:

JavaScript heap out of memory

解决方案:

# Increase Node memory limit
export NODE_OPTIONS="--max-old-space-size=4096"

Claude桌面连接问题

症状:

Server not connecting / MCP indicator shows error

解决:

  1. 验证配置中的绝对路径:

    pwd  # Get current directory
    # Use full path: /Users/your-username/projects/ov-mcp/...
    
  2. 检查克劳德日志:

    # macOS
    tail -f ~/Library/Logs/Claude/mcp*.log
    
    # Windows
    type %APPDATA%\Claude\Logs\mcp*.log
    
  3. 独立测试服务器:

    bun run packages/mcp-server/src/index.ts
    # Should output: "OV-MCP Server running on stdio"
    
  4. 完全重新启动Claude:

    • 退出克劳德桌面(macOS上的Cmd+Q)
    • 等待5秒
    • 重新打开克劳德桌面
  5. 验证JSON配置:

    # Use a JSON validator
    cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq .
    

Cloudflare工作人员问题

症状:

Worker returns 500 Internal Server Error

解决:

  1. 检查员工日志:

    wrangler tail
    
  2. 验证KV数据是否已上传:

    wrangler kv:key list --namespace-id=your-namespace-id
    
  3. 先在本地测试:

    cd packages/cloudflare-worker
    wrangler dev
    

🤝 贡献

欢迎投稿!我们感谢错误报告、功能请求和拉取请求。

如何贡献

  1. 克隆该仓库
  2. 创建要素分支:
    git checkout -b feat/amazing-feature
    
  3. 进行更改
  4. 运行测试和类型检查:
    bun run test
    bun run typecheck
    bun run lint
    
  5. 提交您的更改:
    git commit -m 'feat: Add amazing feature'
    
  6. 推你的叉子:
    git push origin feat/amazing-feature
    
  7. 打开拉取请求

开发指南

  • 代码的风格:遵循现有的代码风格和惯例
  • 测试:添加新功能和错误修复的测试
  • 文档:为API更改更新自述文件和文档
  • 提交:使用 常规承诺
  • TypeScript:保持严格的类型安全

提交消息格式

<type>(<scope>): <subject>

<body>

<footer>

类型:

  • feat:新功能
  • fix:Bug修复
  • docs:文件更改
  • style:代码样式更改(格式等)
  • refactor:代码重构
  • test:添加或更新测试
  • chore:维护任务

示例:

feat(gtfs-parser): Add support for GTFS-RT feed
fix(mcp-server): Handle empty query results gracefully
docs(readme): Update deployment instructions

🗺️ 路线图

✅ 完成

  • [x] 项目结构和单回购设置
  • [x] 使用stdio传输的MCP服务器实现
  • [x] 具有完全类型支持的GTFS解析器
  • [x] 带缓存的GTFS数据下载器
  • [x] 地理搜索(附近站点)
  • [x] 路线搜索功能
  • [x] Cloudflare Workers适配器
  • [x] 全面的测试套件
  • [x] 自动将GTFS数据上传到Workers
  • [x] 文件和示例

🚧 进行中

  • []实时出发信息(GTFS-RT集成)
  • []与NS API集成以获取实时列车数据
  • []大型数据集的性能优化
  • []监控和分析仪表板

📋 计划的

2025年第一季度:

  • []路线规划能力(多式联运)
  • []中断警报和通知
  • []支持实时GTFS-RT馈送
  • []其他过境运营商(比利时、德国边境)

2025年第二季度:

  • []用于灵活查询的GraphQL API
  • []WebSocket支持实时更新
  • []移动SDK(React Native)
  • []速率限制和身份验证

未来:

  • []用于行程预测的机器学习
  • []历史数据分析
  • []与共享单车和拼车服务整合
  • []多语言支持(英语、荷兰语、德语)

📊 数据源

GTFS数据

  • 源: gtfs.ovapi.nl
  • 覆盖:所有荷兰公共交通运营商
  • 格式:GTFS(通用运输馈电规范)
  • 更新频率:每日(过夜)
  • 许可证:开放数据
  • 尺寸:压缩约87 MB,未压缩约450 MB

包括运营商

国家:

  • NS(荷兰铁路):国家铁路网

区域/地方:

  • GVB:阿姆斯特丹公共交通(地铁、电车、公共汽车、渡轮)
  • 返回:鹿特丹公共交通(地铁、有轨电车、公共汽车)
  • 超文本标记语言:海牙公共交通(电车、公共汽车)
  • Connexxion:区域巴士(多个区域)
  • 到达:区域列车和公共汽车
  • Qbuzz:区域巴士(格罗宁根、乌得勒支、泽兰)
  • 凯欧利:区域巴士
  • 电子制动系统:区域巴士
  • 以及40+额外运营商

📄 许可证

MIT许可证-请参阅 许可证 文件以获取详细信息。

这意味着:

✅ 商业用途
✅ 修改
✅ 分布
✅ 私人使用

⚠️ 需要许可和版权声明
❌ 责任
❌ 保修


🙏 致谢

  • Anthropic 用于创建模型上下文协议
  • Bun团队 为了获得惊人的JavaScript运行时
  • OVAPI.nl 提供全面的GTFS数据
  • 荷兰公共交通运营商 开放数据倡议
  • 开源社区 用于工具和库

📞 支持

获取帮助

报告Bug

报告错误时,请包括:

  1. 环境详细信息:Bun版本、操作系统、节点版本
  2. 重现步骤:清晰、编号的步骤
  3. 预期行为:应该发生什么
  4. 实际行为:实际发生了什么
  5. 日志:相关错误消息或日志
  6. 代码示例:最小复制码(如适用)

📚 其他资源

MCP资源

GTFS资源

荷兰交通数据


内置于❤️ 荷兰公共交通社区

最后更新日期:2025年12月29日