Skip to main content
Glama

gmaps-mcp

A free Google Maps toolkit, in pure Python, no API key. It hits Google's own internal endpoints directly — no headless browser — for business search, single-place lookup, and multi-mode travel directions. Ships as a CLI (gmaps) and as an MCP server (gmaps-mcp) so any MCP-capable coding tool can pull Google Maps data straight into a conversation.

Search and lookup extract full business profiles with asyncio concurrency and export to JSON + CSV; directions returns travel duration and distance between two places. Built to be the strong, fast, self-hosted option — see Roadmap for where this is headed.

Features

Business search & lookup

  • No API key, no paid service — direct tbm=map protobuf-over-JSON requests.

  • Full profile data per business: name, rating, categories, address (split into street/city/state/postal/country), coordinates, phone, website, operating hours, description, plus code, status (open/closed), attributes (women-led, LGBTQ+ friendly…), photos + thumbnail, derived CID, reviews link, Street View link, Google Maps link.

  • Free-endpoint limits (honest nulls): review count, price range, reservation/ordering links, owner-claimed flag and payment info are not exposed by Google's free endpoints — stored empty rather than guessed.

  • Website enrichment: crawls each business's site (deduped by domain) for email addresses and social links (Instagram, Facebook, X/Twitter, LinkedIn, TikTok, YouTube).

  • Concurrency via asyncio (parallel requests + retries + backoff).

  • Pagination, whole-country sweep on a lat/lng grid, multi-keyword passes (deduped by feature id), single-entity lookup by feature id / Google Maps URL / name.

  • JSON + CSV output with normalized fields — machine CSV for scripts, a separate human-readable summary CSV alongside it.

Directions

  • Travel duration + distance between two places: driving, bicycling, walking, transit — all real, no API key (flying is a separate Google product, not available here).

MCP server (gmaps-mcp): search, lookup and directions tools, same data.

Related MCP server: Google Maps MCP Server

Install

python3 -m venv .venv && source .venv/bin/activate
pip install -e .

Optional extras:

pip install -e ".[socks]"   # SOCKS proxy support (--proxy socks5://...)
pip install -e ".[mcp]"     # the gmaps-mcp server
pip install -e ".[dev]"     # tests (pulls in [mcp] too, see Development)

MCP server — connect it to your coding tool

gmaps-mcp runs over stdio and exposes three tools: search (keywords, optionally swept over a country/bbox grid), lookup (one place by feature id / Google Maps URL / name), and directions (travel duration between two places — all ground modes, with distance for driving/transit; see the note below).

Install the package firstpip install -e ".[mcp]" (above). The gmaps-mcp binary must already be on PATH (or callable via python -m gmaps.mcp_server) before any of the steps below will work: none of these commands install gmaps-mcp itself, they only register an already-installed binary with the tool's own config so it knows to launch it.

Claude Code — one line, no file editing:

claude mcp add gmaps -- gmaps-mcp

(add --scope user to make it available in every project, not just this one)

Codex CLI:

codex mcp add gmaps -- gmaps-mcp

or in ~/.codex/config.toml:

[mcp_servers.gmaps]
command = "gmaps-mcp"

Cursor.cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{ "mcpServers": { "gmaps": { "command": "gmaps-mcp" } } }

DeepSeek Harness (dsh) — MCP servers are mounted as patches in ~/.dsh/profiles/web/cordis.patch.yml:

- insert:
    - id: mcp-gmaps
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: gmaps
        transport: stdio
        command: gmaps-mcp

Restart dsh after saving (dsh web --dump-config | grep -A3 mcp to confirm it loaded).

OpenCode — add under mcp in your OpenCode config (opencode.json / opencode.jsonc):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "gmaps": { "type": "local", "command": ["gmaps-mcp"] }
  }
}

Hermes Agent:

hermes mcp add gmaps --command gmaps-mcp

or directly in ~/.hermes/config.yaml:

mcp_servers:
  gmaps:
    command: gmaps-mcp

Then hermes mcp test gmaps to confirm, or /reload-mcp in a running session.

Directions (travel duration + distance between two places)

gmaps --directions "ORIGIN" "DEST" (CLI) and the directions MCP tool return travel duration for driving, bicycling, walking and transit, plus real distance for driving and transit — no API key. Under the hood this queries Google's internal directions endpoint twice, in two different request shapes, because they aren't interchangeable: one gives duration for all four ground modes on a consistent, toll-averse route, but its transit entry is commonly empty; the other gives real distance + duration for driving and transit (transit's includes actual departure/arrival times), but its driving route can include tolls — a different physical route than the first shape's, confirmed live, not a rounding difference. Because of that: driving's duration always comes from the consistent (toll-averse) shape, matching bicycling/walking; driving's distance is added from the other shape only when available, flagged distance_route_may_differ: true so it's never presented as describing the exact same trip as the duration. Transit comes entirely from the distance-carrying shape (no mixing). Flying is not available — it's a separate Google Flights product, not part of this endpoint at all, and a null there says nothing about whether a flight actually exists.

gmaps --directions "Paris, France" "Zurich, Switzerland"

CLI usage

# single keyword, full Google Maps profile, save JSON+CSV (default: no website crawl)
gmaps "pizza in Berlin" --out leads

# optionally crawl each business's website for emails + social links
gmaps "pizza in Berlin" --website --out leads_with_contacts

# add emails + socials to an EXISTING results JSON (no Google re-scrape)
gmaps --enrich-file leads.json --out leads_contacts

# faster: skip place-detail enrichment (search fields only)
gmaps "pizza in Berlin" --no-enrich

# 3 pages (≈60 results) of one query
gmaps "restaurants in Berlin" --pages 3

# sweep a whole country on a 30km grid
gmaps "dentist" --country DE --spacing 30 --out germany_dentists

# long sweep: write a partial <out>.checkpoint.json every 50 cells,
# so an interruption never loses the whole run
gmaps "dentist" --country DE --spacing 30 --checkpoint-every 50 --out germany_dentists

# custom bounding box (min_lat min_lng max_lat max_lng)
gmaps "cafe" --bbox 48.0 11.0 49.0 13.0 --spacing 15

# multi-keyword: several terms in one pass, deduped by feature id
gmaps "pizza" --keywords "kebab" "italian restaurant" --out multi_food

# single-entity lookup: full profile by feature id / maps URL
gmaps --lookup "0x47a8...:0x4bff..."
# ... or top-5 candidates by name (disambiguate, then look up by fid)
gmaps --lookup "Joyería Serrano" --top-n 5

# list supported countries
gmaps --list-countries

# tune concurrency / optional proxy (SOCKS needs the [socks] extra, see Install)
gmaps "barber" --country FR --concurrency 8 --proxy socks5://127.0.0.1:1080

Run as a module if you prefer: python -m gmaps "pizza in Berlin".

Output

<out>.json — array of full records. <out>.csv — flat table with columns for every field plus one per social network. <out>.summary.csv — human-oriented table (written alongside, same rows): name first, split street/city/state/postal_code/country, compact hours plus a separate closed_days column, rounded coordinates, machine ids (query, google_maps_url) at the end. Semicolon-delimited, plain UTF-8 (no BOM): spreadsheets in comma-decimal locales (FR/DE/MA...) split CSV on ; and would show a comma file as one column; a comma file is still written by the machine export above.

Every record carries google_maps_url (a working link to the place). The machine <out>.csv layout is stable for scripts; the summary is meant to be read.

Repository structure

gmaps-mcp/
├── src/gmaps/
│   ├── cli.py           argument parsing + orchestration (the `gmaps` command)
│   ├── __main__.py       thin entry point (python -m gmaps)
│   ├── mcp_server.py     the `gmaps-mcp` entry point: search + lookup + directions tools
│   ├── config.py        shared headers, user-agent, endpoint URLs, constants
│   ├── client.py        async HTTP client (retries, circuit breaker, proxy validation)
│   ├── pb.py             protobuf field-selector construction (search, detail, directions)
│   ├── parser.py        schema-tolerant structural parser for the protobuf JSON
│   ├── models.py        Place / SearchResult dataclasses
│   ├── pipeline.py      concurrency, pagination, enrichment, grid sweeps, lookup, directions
│   ├── grid.py           country bounding boxes + lat/lng grid
│   ├── webenrich.py     website crawl for emails + socials (deduped by domain)
│   └── export.py        JSON + CSV writers (machine CSV + human summary CSV)
├── tests/                pytest suite — parser, grid, export, pipeline, features,
│                         mcp, directions; all offline (fixtures, stub clients, in-process MCP calls)
├── data/                 your scraped output lands here (gitignored)
├── .github/workflows/    CI: install + full suite on Python 3.10–3.12
├── AGENTS.md             invariants and gotchas for anyone editing this codebase
├── CONTRIBUTING.md       ground rules for contributions
├── llms.txt              machine-readable project index (llms.txt v2 spec)
├── pyproject.toml        package metadata, dependencies, entry points
├── requirements.txt      plain pip-freeze-style mirror of pyproject's deps
└── LICENSE               MIT

Disclaimer — this is gray-zone scraping

This project queries https://www.google.com/search?tbm=map and https://www.google.com/maps/preview/placeinternal, undocumented Google endpoints, not a published or licensed API. There is no API key because there is no API: this is the same request your browser makes when you use Google Maps, replayed programmatically. That distinction matters:

  • Google's Terms of Service generally prohibit automated access to their services outside of the official APIs. Using this tool may violate those terms; Google can and does rate-limit or block IPs it identifies as scraping (the client's circuit breaker exists because of this, not despite it).

  • The endpoints are unstable by design — they can change shape, require new headers, or disappear without notice, because Google owes no backward compatibility to a request format it never published.

  • Data protection law varies by jurisdiction. Business listings are generally public information, but what you may collect, store, and do with scraped personal data (an owner's name in a listing, a phone number) depends on where you and your data subjects are — GDPR, CCPA, and similar regimes may apply.

  • This is not legal advice. Review the copyright/ToS/data-protection position for your jurisdiction and use case before scraping at any scale. The authors and contributors accept no liability for how this tool is used; see LICENSE (MIT — provided as-is, no warranty).

If you need guaranteed uptime, an SLA, or unambiguous legal footing, use Google's official Places API instead — it costs money precisely because it buys you those things.

Development

See CONTRIBUTING.md for the ground rules (tests-first, fixtures from real bodies, honest nulls, flag threading).

pip install -e ".[dev]"      # install package + pytest (pulls in the mcp extra too)
python -m pytest tests/ -q   # run the test suite (72 tests, all offline)

Architecture

See Repository structure above for what each file does. AGENTS.md documents the invariants that aren't obvious from reading any single file — read it before making structural changes.

Roadmap

Search, lookup and directions all sit on the same family of Google Maps internal endpoints — the pattern for adding a capability here is consistent: find the request shape via a real browser session, confirm it live against several inputs (never trust one), write a structural parser, ship honest nulls for whatever the free endpoint doesn't expose. Natural next additions on that same pattern, roughly in order of how well-trodden the endpoint already looks from this session's exploration:

  • Geocoding / reverse geocoding — address ↔ coordinates, no full business lookup needed. Likely a thin wrapper around the same place-resolution step search/lookup already do.

  • Elevation — a single lat/lng in, a height out; small, self-contained.

  • Place photos & reviews at scalelookup already returns photo URLs and a reviews_link; a dedicated reviews tool exists at the client layer (MapsClient.reviews) but isn't wired into MCP/CLI yet.

  • Distance for bicycling/walking — currently duration-only (see Directions above); the request shape that carries distance for driving/transit doesn't carry these two modes at all, so this needs a genuinely different endpoint angle, not just a tweak.

  • Batch directions (many origins → one destination, or a matrix) — useful for "which of these N places is closest," built on top of the existing single-pair directions rather than replacing it.

None of these are promised or scheduled — this list exists so a contributor (human or agent) doesn't have to rediscover where the natural seams are. See CONTRIBUTING.md for how a new capability should be built here: tests first, fixtures from real captured bodies, honest nulls.

How it works (briefly)

Google Maps search can be queried at https://www.google.com/search?tbm=map which returns a large nested JSON array (protobuf-over-JSON) prefixed with an anti-XSSI marker. Each business is a sub-array anchored on a feature id like 0x47a8...:0x4bff.... The parser decodes the JSON once and then walks the tree, extracting fields structurally (rather than by brittle fixed indices or regexes over the raw dump), so it survives schema shifts AND JSON escaping (names with quotes or accents). Coordinates are embedded in the pb parameter to pin the map viewport, and pagination is done by incrementing the !8i offset.

Notes & risks

  • review_count is not available through the free protobuf endpoints from a residential IP: the tbm=map search response omits it, the place-detail response omits it, and the reviews endpoint returns 404 for unauthenticated requests. The scraper reports it honestly as null rather than guessing. To capture review counts, feed the feature_id/google_maps_url into a browser renderer (the count is shown on the place HTML page).

  • This works from a residential IP; Google may rate-limit heavy use. The client includes a circuit breaker (pauses after repeated 403/429/503 blockages; timeouts and other errors retry without tripping it) and capped exponential backoff. Use --concurrency/--respect-delay sensibly for very large sweeps.

  • Proxies: pass a URL string (--proxy socks5://host:1080). SOCKS additionally needs the [socks] extra; the scraper validates this up front and exits with an install hint instead of failing mid-sweep.

  • The protobuf schema changes over time; the structural parser is designed to degrade gracefully (missing fields stay null) rather than crash. Hours are parsed from locale-dependent weekday blocks (Spanish lunes and English Monday alike); descriptions only when Google actually publishes an editorial summary for the place — ad lines, internal ids and UI labels are excluded, and a missing description stays null rather than being guessed.

  • The place-detail parser is ad-proof: it skips third-party booking domains (Booking.com, Expedia, OpenTable...) for websites and takes the most-frequent phone, so ad data never leaks into a business record.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reveredyvs/gmaps-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server