批量附近搜索MCP服务器
一个优化的模型上下文协议(MCP)服务器,用于使用谷歌的API查找到不同地点的距离。该服务器采用FastMCP构建,通过并发API调用和缓存提供智能批处理,可将成本降低50-80%。
特性
- 批处理:并行搜索多个位置和特征类型
- 智能高速缓存:两层缓存(地理编码+位置)减少了冗余的API调用
- 可选字段选择:Claude只能请求每个任务所需的字段
- 部分故障处理:即使某些位置失败,也会返回成功的结果
- 成本优化:并行执行+缓存=节省50-80%的成本
- 易于集成:与Claude Desktop无缝协作
提供的工具
1. distance_matrix
使用Google Distance Matrix API计算多个出发地-目的地对之间的距离和旅行时间。
最适合:比较与已知目的地的通勤时间
2. nearby_search
从一个位置查找附近多种类型的地点。
最适合:探索一个地址周围的设施
3. batch_nearby_search (⚡ 优化)
并行查找多个位置的附近位置-这是主要的优化工具。
最适合:比较多个社区或物业的设施
安装
先决条件
- Python 3.10或更高版本
- 启用了以下API的Google Maps API密钥:
- 地点API(新增) - 距离矩阵API - 地理编码API
- 紫外线 (推荐)或pip
设置步骤
- 克隆存储库
git clone
cd batch-nearby-search-mcp- 安装依赖项
紫外线(推荐):
uv pip install -e .使用pip:
pip install -e .- 设置环境变量
cp .env.example .env编辑 .env 并添加您的Google Maps API密钥:
GOOGLE_MAPS_API_KEY=your-api-key-here- 获取Google Maps API密钥
- 首选 谷歌云控制台 - 创建新项目或选择现有项目 - 启用所需的API:Places API(New)、Distance Matrix API、Geocoding API - 创建凭据(API密钥) - 启用计费(API使用需要)
Claude桌面配置
将此添加到您的Claude Desktop配置文件中:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
视窗: %APPDATA%/Claude/claude_desktop_config.json
Linux: ~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"batch-nearby-search": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/batch-nearby-search-mcp",
"run",
"batch-nearby-search"
],
"env": {
"GOOGLE_MAPS_API_KEY": "your-api-key-here"
}
}
}
}重要:替换 /absolute/path/to/batch-nearby-search-mcp 使用系统上的实际路径。
更新配置后:
- 保存文件
- 完全重新启动克劳德桌面
- 查找MCP图标以验证连接
用法示例
示例1:比较邻居
问克劳德:
I'm looking at houses in these locations:
- 1600 Amphitheatre Parkway, Mountain View, CA
- 1 Apple Park Way, Cupertino, CA
- 1 Hacker Way, Menlo Park, CA
For each location, find the nearest:
- Park (with rating)
- Grocery store (with address and rating)
- Coffee shop (with rating)
Show me a comparison.克劳德将使用 batch_nearby_search 以并行高效地搜索所有位置。
示例2:距离分析
问克劳德:
Calculate driving distances from my address (123 Main St, City, State) to:
- San Francisco Airport
- Stanford University
- Downtown San Jose
Show me distances and travel times.克劳德将使用 distance_matrix 对于这个请求。
示例3:单点勘探
问克劳德:
I'm staying at 456 Market St, San Francisco, CA.
Find nearby restaurants, cafes, and gyms within walking distance (500 meters).
Include ratings and addresses.克劳德将使用 nearby_search 对于这个请求。
示例4:批量附近搜索(详细)
此示例显示 精确结构 打电话来 batch_nearby_search 直接,包括如何组织结果。
您正在搜索的内容:
- 3个位置(地址和坐标混合)
- 每个地点有2种功能类型(公园、杂货店)
- 包括可选字段(评级、地址)
工具调用结构:
{
"locations": [
{"address": "1600 Amphitheatre Parkway, Mountain View, CA"},
{"address": "1 Apple Park Way, Cupertino, CA"},
{"lat": 37.4849, "lng": -122.1477}
],
"feature_types": ["park", "grocery_store"],
"radius_meters": 2000,
"include_fields": ["rating", "address"],
"format": "json"
}如何组织结果:
结果已分组 先按位置,然后按特征类型:
results[0] (first location: "1600 Amphitheatre...")
├── location_index: 0
├── coordinates: {lat: 37.4220, lng: -122.0841}
├── features:
│ ├── "park": [
│ │ {name: "Charleston Park", distance_meters: 450, rating: 4.5, ...},
│ │ {name: "Shoreline Park", distance_meters: 890, rating: 4.7, ...}
│ │ ]
│ └── "grocery_store": [
│ {name: "Whole Foods", distance_meters: 1200, rating: 4.2, ...}
│ ]
└── status: "success"
results[1] (second location: "1 Apple Park...")
├── location_index: 1
├── features:
│ ├── "park": [...]
│ └── "grocery_store": [...]
└── status: "success"
results[2] (third location: coordinates)
└── ... (same structure)摘要信息:
{
"summary": {
"total_locations": 3,
"successful": 3,
"partial": 0,
"failed": 0,
"total_places_found": 15
}
}要点:
- 每个位置都有自己的入口
results[] - 在每个位置内,结果按以下方式分组
feature_type - 支持部分故障-如果一种功能类型失败,其他功能类型仍会返回
- API调用总数=3个位置×2个功能类型=6个并行请求
文本格式输出:
=== Location 0: 1600 Amphitheatre Parkway, Mountain View, CA ===
Coordinates: (37.4220, -122.0841)
Status: success
Feature: park
- "Charleston Park" 450m [rating: 4.5, address: "123 Charleston Rd"]
- "Shoreline Park" 890m [rating: 4.7, address: "3070 N Shoreline Blvd"]
Feature: grocery_store
- "Whole Foods Market" 1200m [rating: 4.2, address: "2580 California St"]
=== Location 1: 1 Apple Park Way, Cupertino, CA ===
...限制和陷阱:
- 最多20个地点 每个请求(验证强制执行此操作)
- 最多10种特征类型 每个请求
- API调用=位置×特征类型(例如,10个位置×5个类型=50个调用)
- 结果的大小可能有所不同-某些位置可能没有某些类型的结果
- 部分故障得到妥善处理-检查
status每个位置的字段
性能和成本
API成本(截至2024年)
- 地点API附近搜索每1000个请求32美元
- 距离矩阵API:每1000个元素5美元
- 地理编码API:每1 000个请求5美元
成本优化示例
无缓存:
- 5个位置×3个功能=15个API调用
- 成本:约0.48美元
- 时间:~30-45秒(连续)
使用此服务器(并行+缓存):
- 第一次查询:15个API调用,约0.48美元,约3-5秒
- 重复查询:0个API调用(缓存),$0.00,<1秒
- 节省:随着时间的推移,节省50-80%
批量建议
- 最优:5-15个位置×2-5个功能类型(25-75个API调用)
- 最大:20个位置×10个功能类型(200个API调用,约6.40美元)
配置选项
环境变量(设置于 .env):
# Required
GOOGLE_MAPS_API_KEY=your-api-key-here
# Optional - adjust caching
GEOCODING_CACHE_SIZE=1000 # Default: 1000
PLACES_CACHE_SIZE=500 # Default: 500
PLACES_CACHE_TTL=3600 # Default: 3600 (1 hour)
# Optional - rate limiting
MAX_CONCURRENT_REQUESTS=10 # Default: 10 (recommended)常见场所类型
使用这些 feature_types 参数。您可以指定单个地点类型或整个类别:
个人场所类型:
- 便利设施:
park,gym,library,hospital,pharmacy - 饮食:
restaurant,cafe,bar,grocery_store,supermarket - 中转:
bus_station,subway_station,train_station,airport - 服务:
atm,bank,gas_station,post_office - 教育:
school,university
类别名称 (搜索类别中的所有类型):
food_drink-所有餐厅、咖啡馆、酒吧等。sports-所有健身房、健身中心、体育场等。health_wellness-所有医院、药店、医生等。shopping-所有商店、商场、超市等。entertainment_recreation-所有公园、剧院、博物馆等。- 还有更多!使用
list_place_types()查看所有类别。
完整列表: 谷歌地点类型
可选字段和输出格式
输出格式
所有工具均支持 format 参数:
format="text"(默认):返回人类可读的日志格式,显示每行中的每个位置format="json":返回结构化JSON数据以供编程使用
日志格式输出示例:
- 123 Main St (37.7749, -122.4194) "Starbucks" 250 meters [rating: 4.5]
- 123 Main St (37.7749, -122.4194) "Blue Bottle Coffee" 450 meters [rating: 4.2]可选字段
使用时 nearby_search 或 batch_nearby_search,您可以指定要包含哪些可选字段:
可用字段:
rating-平均评分(0-5)user_ratings_total-评级数量address-格式化地址phone_number-电话号码website-网站URLprice_level-价格水平(0-4)opening_hours-营业时间信息types-场所类型列表
默认 (如果未指定):仅 name, place_id,以及 distance_meters 被退回。
了解工具响应
本节解释了每个工具如何组织其结果,以便您确切地知道会发生什么。
batch_nearby_search 响应结构
结果已整理 首先按位置,然后按每个位置内的特征类型:
{
"results": [
{
"location_index": 0,
"location": {"address": "1600 Amphitheatre Parkway, Mountain View, CA"},
"coordinates": {"lat": 37.4220, "lng": -122.0841},
"features": {
"park": [
{"name": "Charleston Park", "distance_meters": 450, "place_id": "ChIJ...", ...},
{"name": "Shoreline Park", "distance_meters": 890, "place_id": "ChIJ...", ...}
],
"grocery_store": [
{"name": "Whole Foods", "distance_meters": 1200, "place_id": "ChIJ...", ...}
]
},
"status": "success"
},
{
"location_index": 1,
"location": {"lat": 37.4849, "lng": -122.1477},
"coordinates": {"lat": 37.4849, "lng": -122.1477},
"features": {
"park": [...],
"grocery_store": [...]
},
"status": "success"
}
],
"summary": {
"total_locations": 2,
"successful": 2,
"partial": 0,
"failed": 0,
"total_places_found": 8
}
}要点:
- 层级:
results[location_index].features[feature_type][place_index] - 状态值:
- "success":所有功能类型都返回了结果 - "partial":一些功能类型成功,一些失败(请参见 errors 现场) - "error":所有要素类型均失败或地理编码失败
- 部分失败:如果搜索3个特征类型,其中1个失败,您仍然会得到其他2个的结果
- 空结果:特征类型可以有一个空数组
[]如果找不到地方
nearby_search 响应结构
类似于批处理,但适用于单个位置:
{
"location": {"lat": 37.4220, "lng": -122.0841},
"features": {
"park": [
{"name": "Charleston Park", "distance_meters": 450, ...}
],
"cafe": [
{"name": "Blue Bottle Coffee", "distance_meters": 320, ...}
]
},
"summary": {
"total_feature_types": 2,
"total_places_found": 5,
"radius_meters": 5000
}
}要点:
- 层级:
features[feature_type][place_index] - 结果按特征类型分组
- 每种特征类型都是一个按距离排序的位置数组
distance_matrix 响应结构
{
"results": [
{
"origin": "1600 Amphitheatre Parkway, Mountain View, CA",
"destination": "1 Apple Park Way, Cupertino, CA",
"distance_meters": 15420,
"duration_seconds": 1260,
"status": "OK"
}
],
"summary": {
"total_pairs": 1,
"mode": "driving",
"api_calls": 1
}
}要点:
- 每个起点-终点对都有自己的结果
- 3个出发地×2个目的地=6个结果
status可以是"OK","NOT_FOUND","ZERO_RESULTS"等等。
geocode 和 reverse_geocode 响应结构
{
"results": [
{
"address": "Times Square, NYC",
"formatted_address": "Manhattan, NY 10036, USA",
"lat": 40.7580,
"lng": -73.9855,
"status": "success"
}
],
"summary": {
"total_addresses": 1,
"successful": 1,
"failed": 0
}
}要点:
- 支持批量地理编码(一次多个地址)
- 每个结果都有一个
status字段:"success"或"error" - 失败的地理编码仍然会返回成功地址的部分结果
验证警告
当你提供无效的地点类型时,你会看到这样的警告:
Validation: 2 of 3 place types are valid. Proceeding with: park, grocery_store
Invalid types:
- 'grocrey_store' is not valid. Did you mean: grocery_store, convenience_store, supermarket?这意味着:
- 搜索将 进行 具有有效类型(公园、杂货店)
- 跳过无效类型,但您会收到更正建议
- 无需重试-您仍然会得到有效类型的结果
故障排除
“API密钥无效”
- 检查你的
.env文件包含正确的API密钥 - 验证Google Cloud控制台中是否启用了API
- 确保为您的项目启用计费
“请求太多”或速率限制错误
- 减少
MAX_CONCURRENT_REQUESTS在.env(尝试5而不是10) - 检查您的Google Cloud配额限制
Claude Desktop未显示工具
- 验证配置文件路径是否正确
- 检查JSON语法(无尾随逗号)
- 确保项目的路径是绝对的,而不是相对的
- 检查日志:
tail -f ~/Library/Logs/Claude/mcp*.log(macOS) - 完全重新启动克劳德桌面
未找到结果
- 检查一下
radius_meters不太小(试试2000-5000) - 验证
feature_type是有效的Google地点类型 - 尝试更广泛的类型(例如,“餐厅”而不是“sushi_restaurant”)
发展
运行测试
# Install dev dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=batch_nearby_search --cov-report=html代码格式化
# Format code
black src/ tests/
# Lint
ruff check src/ tests/建筑
项目结构
batch-nearby-search-mcp/
├── src/batch_nearby_search/
│ ├── server.py # FastMCP server with @mcp.tool decorators
│ ├── models.py # Pydantic models for validation
│ ├── google_client.py # Google API wrapper (async + caching)
│ ├── cache.py # Two-tier caching (LRU + TTL)
│ └── utils.py # Helper functions
├── tests/ # Test suite
├── IMPL_PLAN.md # Detailed implementation plan
├── CLAUDE.md # Quick reference for AI assistants
└── README.md # This file关键设计模式
- 并发API调用:用途
asyncio.gather()并行化请求 - 速率限制:基于语义,尊重Google API配额
- 两层缓存:
- 地理编码缓存(LRU,无限期)-地址不变 - 位置缓存(TTL,1小时)-位置可能会随时间而变化
- 部分故障处理:即使某些位置失败,也会返回成功的结果
- 可选字段选择:减少响应规模和API成本
许可证
麻省理工学院
贡献
欢迎投稿!请打开问题或拉取请求。
支持
对于问题或疑问:
- 检查 故障排除 章节
- 查看登录
~/Library/Logs/Claude/mcp*.log(macOS) - 在GitHub上打开一个问题
致谢
内置:
- FastMCP -高级MCP框架
- 谷歌地图Python客户端
- 派丹蒂克 -数据验证
