MCP地图代理
一个Python应用程序,通过使用OpenAI的代理SDK将多个地图服务器(地理编码、路由、Tiles)作为代理工具公开,演示了模型上下文协议(MCP)模式。
该项目实现了一个智能的对话式地图查询助手,可以理解有关地理数据的自然语言问题,并将其动态路由到适当的服务。它展示了人工智能驱动系统设计中的高级模式,包括代理循环、多工具编排和上下文管理。
概述
该项目创建了一个智能地理助手,可以:
- 理解意图:处理关于位置、路线和地图的自然语言问题
- 智能路由:使用OpenAI的代理SDK来决定调用哪个工具
- 集成多种服务:无缝结合三个独立的地图服务:
- 地理编码服务器:地址↔ 坐标转换、POI搜索(OpenStreetMap提名) - 路由服务器:路线规划、距离矩阵、GPS轨迹匹配(OSRM) - 磁贴/元数据服务器:地图图块提供商信息和归属
- 提供丰富的响应:返回具有性能指标的结构化地理数据
该系统使用OpenAI的函数调用API为每个查询自主选择和调用正确的工具,使其完全对话和上下文感知。
建筑
┌─────────────────────────────────────────────────────┐
│ Typer CLI / Interactive Chat │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ MapAgentOrchestrator (OpenAI Agents SDK) │
│ - Agentic loop with tool calling │
│ - Intent routing via LLM │
└─────────────────────────────────────────────────────┘
↓ ↓ ↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Geocoding │ │ Routing │ │ Tiles │
│ Server │ │ Server │ │ Server │
└──────────────┘ └──────────────┘ └──────────────┘
↓ ↓ ↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Nominatim │ │ OSRM │ │ Static Data │
│ (HTTP) │ │ (HTTP) │ │ (In-memory) │
└──────────────┘ └──────────────┘ └──────────────┘特性
可用工具
地理编码(正向/反向/POI搜索)
$ python main.py query "What are the coordinates of Times Square?"
$ python main.py query "What address is at 40.7128, -74.0060?"
$ python main.py query "Find restaurants near Central Park"路由
$ python main.py query "Route from NYC to Boston by car"
$ python main.py query "Distance matrix between these 3 cities"
$ python main.py query "Match this GPS trace to roads"瓷砖供应商
$ python main.py query "List available map tile providers"
$ python main.py query "Tell me about OpenStreetMap tiles"
$ python main.py query "Attribution for CARTO Positron?"质量保证
- 单元测试:全面的pytest套件,包括3台服务器和9多种工具
- 类型安全:mypy严格模式适用于所有代码
- 代码检查:Ruff强制执行代码质量
- 覆盖:所有模块的目标为60%以上
设置
需求
- Python 3.11+
- OpenAI API密钥(集
OPENAI_API_KEY任何人) - 互联网接入Nominim和OSRM公共端点
安装
# Clone and navigate to project
cd mcp-map-agents
# Create virtual environment (using Python 3.11)
python3.11 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install -r requirements.txt配置
创建一个 .env file(可选,默认值适用于公共API):
NOMINATIM_BASE_URL=https://nominatim.openstreetmap.org
NOMINATIM_TIMEOUT_SECONDS=10
OSRM_BASE_URL=http://router.project-osrm.org
OSRM_TIMEOUT_SECONDS=15
OPENAI_API_KEY=sk-your-key-here用法
交互模式
python main.py chat
# Then ask questions naturally:
# > What's the distance from NYC to Boston by car?
# > Find hotels near the Eiffel Tower
# > List all tile providers单一查询
python main.py query "What address is at 40.7128, -74.0060?"测试
# Run all tests
pytest -v
# Run with coverage
pytest --cov=src --cov-report=html
# Specific test file
pytest tests/test_geocoding.py -v代码质量
# Lint
ruff check src
# Type checking
mypy src
# All checks (lint + type + test)
make verify # or: pytest && mypy src && ruff check src查询示例
地理编码示例
- “为我编写‘自由女神像’地理代码”
- “坐标48.8584,2.2945处是什么?”
- “在巴黎寻找博物馆”
路由示例
- “计算从波士顿到纽约的行车路线”
- “旧金山离洛杉矶有多远?”
- “从中央公园到时代广场的旅行时间”
瓷砖示例
- “支持哪些地图提供程序?”
- “我需要什么样的耐力爽肤水?”
组合示例
- “获取埃菲尔铁塔的坐标,然后找到附近的餐馆”
- “从我的地址\[地理编码\]到中央公园的路线,并显示距离”
项目结构
mcp-map-agents/
├── src/
│ ├── agents/
│ │ ├── schemas.py # Pydantic models (ToolRequest, ToolResponse, etc.)
│ │ ├── orchestrator.py # OpenAI Agents SDK integration & agentic loop
│ │ └── cli.py # Typer CLI (chat, query commands)
│ └── servers/
│ ├── geocoding/
│ │ ├── client.py # Nominatim HTTP client & geocoding logic
│ │ ├── tools.py # Tool definitions & handlers
│ │ └── __init__.py
│ ├── routing/
│ │ ├── client.py # OSRM HTTP client & routing logic
│ │ ├── tools.py # Tool definitions & handlers
│ │ └── __init__.py
│ └── tiles/
│ ├── providers.py # Tile provider metadata (6 providers)
│ ├── tools.py # Tool definitions & handlers
│ └── __init__.py
├── tests/
│ ├── test_geocoding.py # Geocoding server tests (8 tests)
│ ├── test_routing.py # Routing server tests (8 tests)
│ ├── test_tiles.py # Tiles server tests (10 tests)
│ ├── test_schemas.py # Schema validation tests
│ └── __init__.py
├── scripts/
│ └── demo.sh # Quality checks & demo script
├── main.py # Entry point
├── requirements.txt # Python dependencies
├── pyproject.toml # Project metadata & tool configs
├── mypy.ini # Type checking configuration
├── .env.example # Environment variables template
├── .gitignore # Git ignore rules
└── README.md # This file实现细节
工具注册
每个服务器通过以下方式声明工具 get_*_tools() 返回与OpenAI函数调用格式兼容的JSON模式:
- 工具名称和描述
- 参数模式(JSON模式)
- 文档字符串中的示例
代理循环
编排器使用OpenAI的代理API来:
- 接受用户查询
- 让模型决定调用哪个工具
- 执行工具并收集结果
- 返回带有端点URL和时间的最终响应
错误处理
- HTTP超时→ 优雅的错误消息
- 坐标无效→ API调用前的验证
- 未知工具→ 显式错误响应
- 所有回复包括
status,message,可选error_code
测试
覆盖
Geocoding: 8 tests (forward, reverse, POI, error cases, schema)
Routing: 8 tests (route, matrix, trace, error cases, schema)
Tiles: 10 tests (provider list, info, attribution, error cases)
---
Total: ~26 tests, 65%+ code coverage关键测试场景
- 快乐路径:有效输入返回预期的结构化响应
- 错误案例:空查询、无效坐标、找不到场景
- 架构验证:所有工具都有正确的JSON模式参数
- 服务器信息:元数据(名称、描述)符合预期
演出
- 地理编码:每次请求约500-1000ms(Nomatim public API)
- 路由:本地路由约1000-2000毫秒(OSRM公共API)
- 磁贴:\<10ms(内存数据)
- 代理编排:总计约2-3秒(包括LLM推理时间)
局限性和未来工作
- 公共API:使用免费的Nominim和OSRM端点(适用速率限制)
- MCP协议:这是MCP风格的模式,不是官方的MCP规范
- 未来:可以添加高程服务器、天气、本地搜索、离线支持
- 演出:缓存响应、批处理请求、异步池
故障排除
OSRM的“未找到路由”:
- 检查坐标是否有效(不适用于岛屿等)
- 一些偏远地区可能没有路由覆盖
提名超时:
- 公共API在高峰时段可能较慢
- 考虑将自托管用于生产环境
OpenAI API错误:
- 验证
OPENAI_API_KEY设置正确 - 检查API密钥是否已启用功能调用
许可证
该项目作为EECE 503P的教育示例提供。
参考文献
- OpenStreetMap提名
- OSRM(路由机)
- OpenAI代理SDK
- 模型上下文协议 (概念灵感)
- 派丹蒂克 -使用Python类型提示进行数据验证
- 打字机 -Python的CLI框架
