使用说明
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 │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
设计原则:
- 关注点分离:每个包都有一个明确的责任
- 可重用性:GTFS解析器是一个可以独立使用的独立库
- 灵活性:支持本地(stdio)和远程(HTTP/Workers)部署
- 类型安全:具有严格类型的完整TypeScript覆盖率
- 演出:高效缓存、最小内存占用、快速查询
✨ 特性和功能
核心工具
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)
缓存策略:
- 首次运行:下载并解析GTFS数据(2-5分钟)
- 后续运行:使用缓存数据(即时启动)
- 自动刷新:启动时检查缓存时间
- 如果缓存时间<24小时:使用缓存数据
- 如果缓存≥24小时:下载新数据
- 手动刷新:删除缓存目录以强制重新下载
实施:
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(键值存储)
缓存策略:
- 初始上传:通过管理端点手动上传解析的GTFS数据
- 阅读:带边缘缓存的快速KV读取
- 更新:运行上传脚本以刷新数据(计划或手动)
- 生存时间:无自动过期(仅手动更新)
更新过程:
# 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)
🚀 入门指南
先决条件
在开始之前,请确保已安装以下内容:
-
包子 (v1.0.0或更高版本)
# macOS, Linux, WSL curl -fsSL https://bun.sh/install | bash # Verify installation bun --version -
Git 用于版本控制
git --version -
克劳德桌面 (可选,用于与Claude一起测试)
- 下载自 Claude.ai
-
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
首次运行行为
第一次运行时,服务器将:
- ✅ 检查中的缓存GTFS数据
./data/gtfs-cache/ - ⬇️ 从GTFS.ovapi.nl下载GTFS数据(约87 MB)
- 📦 提取ZIP存档
- 📊 解析CSV文件(站点、路线、行程等)
- 💾 在本地缓存已解析的数据
- ✅ 启动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
解决:
- 检查互联网连接
- 验证gtfs.ovapi.nl是否可访问:
curl -I http://gtfs.ovapi.nl/gtfs-nl.zip - 尝试手动下载并放置
./data/gtfs-cache/ - 检查防火墙设置
缓存问题
症状:
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
解决:
-
验证配置中的绝对路径:
pwd # Get current directory # Use full path: /Users/your-username/projects/ov-mcp/... -
检查克劳德日志:
# macOS tail -f ~/Library/Logs/Claude/mcp*.log # Windows type %APPDATA%\Claude\Logs\mcp*.log -
独立测试服务器:
bun run packages/mcp-server/src/index.ts # Should output: "OV-MCP Server running on stdio" -
完全重新启动Claude:
- 退出克劳德桌面(macOS上的Cmd+Q)
- 等待5秒
- 重新打开克劳德桌面
-
验证JSON配置:
# Use a JSON validator cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq .
Cloudflare工作人员问题
症状:
Worker returns 500 Internal Server Error
解决:
-
检查员工日志:
wrangler tail -
验证KV数据是否已上传:
wrangler kv:key list --namespace-id=your-namespace-id -
先在本地测试:
cd packages/cloudflare-worker wrangler dev
🤝 贡献
欢迎投稿!我们感谢错误报告、功能请求和拉取请求。
如何贡献
- 克隆该仓库
- 创建要素分支:
git checkout -b feat/amazing-feature - 进行更改
- 运行测试和类型检查:
bun run test bun run typecheck bun run lint - 提交您的更改:
git commit -m 'feat: Add amazing feature' - 推你的叉子:
git push origin feat/amazing-feature - 打开拉取请求
开发指南
- 代码的风格:遵循现有的代码风格和惯例
- 测试:添加新功能和错误修复的测试
- 文档:为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
报告错误时,请包括:
- 环境详细信息:Bun版本、操作系统、节点版本
- 重现步骤:清晰、编号的步骤
- 预期行为:应该发生什么
- 实际行为:实际发生了什么
- 日志:相关错误消息或日志
- 代码示例:最小复制码(如适用)
📚 其他资源
MCP资源
GTFS资源
荷兰交通数据
内置于❤️ 荷兰公共交通社区
最后更新日期:2025年12月29日