maps-fetcher-mcp
# maps-fetcher-mcp
MCP server + CLI: fetch any Google Maps place by link — full business
metadata plus ALL gallery photos and videos at max resolution.
## Install
```bash
pip install maps-fetcher-mcp # or: uv pip install maps-fetcher-mcp
maps-fetcher-setup # one-time: browser download (~120MB)
```
## Use as an MCP server (any MCP client)
```json
{
"mcpServers": {
"maps_fetcher": {
"command": "maps-fetcher-mcp"
}
}
}
```
Works in: Hermes Agent, Claude Desktop, Cursor, opencode, Cline, Zed —
anything that speaks MCP (stdio).
## Use as a CLI
```bash
maps-fetcher "https://maps.app.goo.gl/XXXX" # everything
maps-fetcher "https://maps.app.goo.gl/XXXX" --include info # metadata only (~20s)
maps-fetcher "https://maps.app.goo.gl/XXXX" --include photos
maps-fetcher "https://maps.app.goo.gl/XXXX" --include videos
maps-fetcher --list # what's been fetched
```
## Tools exposed (MCP)
| Tool | Purpose | Typical time |
|---|---|---|
| `get_place_info` | metadata only: name, phone, rating, address, hours, coords | ~20s |
| `get_place_media` | media only (`kind`: photos / videos / both) | 40-220s |
| `fetch_maps_place` | one-shot everything (`include`: all/info/photos/videos/media) | 60-220s |
| `list_maps_places` | previously fetched places | instant |
Same place re-fetched within 24h reuses the scrape (~2s).
## What it handles
- Short links resolved + canonicalized; search links rejected cleanly
- Video tiles disguised as photos; DASH/HLS manifests muxed with audio
- Expired URLs, avatar junk, motion-blur frames, perceptual duplicates
(quarantined, not deleted), gapless file numbering
- Nondeterministic gallery lazy-loading: scrape retried and merged
- Bot-wall detection (reCAPTCHA / unusual-traffic) with clean errors;
stealth browser via Patchright, plain Playwright fallback
- Security: google-host allowlist for all media downloads, per-file and
per-place size caps, decompression-bomb guard, no secrets
## Output
```
<root>/<place-slug>/
photos/photo_01.jpg ... videos/video_01.mp4 ... quarantine/
info_pw.json contact_sheet.jpg
```
Default root: `/mnt/d/Projects/maps-data-fetcher/places` (WSL layout;
override with `output_dir` or `MAPS_FETCHER_ROOT`).
## Limits
Unofficial scraping — good for batch collection at low volume. Google
markup changes can break it (fixes usually small). Not for high-volume
production; use the paid Google Places API for that. CAPTCHAs are
detected and reported, not auto-solved.
## Dev
Source layout: `maps_fetcher/core.py` (engine), `mcp_server.py` (MCP
tools), `cli.py` (CLI), `setup.py` (browser install helper).
TDQS
Scored across 4 tools
There is significant overlap: fetch_maps_place with include='info' does exactly what get_place_info does, and with include='media' does what get_place_media does. While the descriptions clarify the intended use cases, an agent may struggle to pick the right tool since fetch_maps_place is a superset. The tools are not clearly distinct in purpose.
The naming mixes conventions: 'get_place_info' and 'get_place_media' follow a get_place_N pattern, but 'fetch_maps_place' uses a different verb (fetch) and noun order, and 'list_maps_places' uses plural. The pattern is not consistent across all tools, though the tools are readable.
At 4 tools, the server is well-scoped for a maps fetcher. However, the presence of get_place_info and get_place_media is redundant given fetch_maps_place can achieve the same via its include parameter, suggesting the count could be trimmed to 2–3 tools without losing functionality.
The core lifecycle for fetching Google Maps place data is covered: retrieving metadata, media, or both, plus listing previously fetched places. There are no obvious dead ends for the stated purpose. Missing delete/update operations are not essential for a fetcher, so the surface feels complete.