places-new-mcp
README.md
# places-new-mcp
A small MCP server and CLI over **Google Places API (New)**, **Geocoding API v4** and the **Routes API**.
Every call sends a field mask, answers come back as compact JSON, identical calls are cached for the session,
and each session has a hard upstream call budget.
## The problem
Google moved the old Places API, Directions API and Distance Matrix API to Legacy on March 1, 2025. Legacy
services cannot be enabled in new Cloud projects, so new agents have to use Places API (New) and the Routes API.
Those APIs bill by the fields you ask for, and returning every field is both the most expensive SKU and a wall of JSON
for an agent. This lab gives an agent eight read-only tools with cheap defaults, a tier ceiling and a call budget.
## Quickstart
```bash
bun install --frozen-lockfile
# Keyless: replay synthetic responses (invented places, real response shapes; only the demo queries answer)
bun run src/cli.ts geocode "123 Sample St, Sampletown, EX 00000" --fixtures
bun run src/cli.ts text-search "coffee near Example Market Hall, Sampletown" --limit 5 --fixtures
# Live: put a key in .env (see .env.example), then
bun run src/cli.ts details <placeId> --fields displayName,rating,regularOpeningHours
bun run src/cli.ts route --from "1600 Amphitheatre Parkway, Mountain View, CA" --to "place_id:<id>" --mode WALK
# MCP server (stdio)
bun run src/cli.ts serve # live
bun run src/cli.ts serve --fixtures # keyless demo
```
MCP client config (Claude Code `.mcp.json`, Cursor `mcp.json`):
```json
{
"mcpServers": {
"places": {
"command": "bun",
"args": ["run", "/path/to/places-new-mcp/src/cli.ts", "serve"],
"env": { "GOOGLE_MAPS_API_KEY": "${GOOGLE_MAPS_API_KEY}", "PLACES_MCP_BUDGET": "25" }
}
}
}
```
Restrict the key in Google Cloud to Places API (New) (`places.googleapis.com`), Geocoding API and Routes API
(`routes.googleapis.com`). Our live run used a project with `geocoding-backend.googleapis.com` (console name
"Geocoding API") enabled; we did not confirm whether Geocoding v4 is metered under a different service name.
## Tools
| Tool | Google API | Default field mask bills |
| --- | --- | --- |
| `places_text_search` | Places API (New) `places:searchText` | Text Search Pro |
| `places_nearby_search` | Places API (New) `places:searchNearby` | Nearby Search Pro (no Essentials tier exists) |
| `place_details` | Places API (New) `places/{id}` | Place Details Pro |
| `places_autocomplete` | Places API (New) `places:autocomplete` | Autocomplete Requests; returns a `sessionToken` to reuse on `place_details` |
| `geocode_address` | Geocoding API v4 `geocode/address/{address}` | Geocoding |
| `reverse_geocode` | Geocoding API v4 `geocode/location/{lat},{lng}` | Geocoding |
| `compute_route` | Routes API `directions/v2:computeRoutes` | Compute Routes Essentials (traffic-aware: Pro, two-wheeler: Enterprise) |
| `session_status` | none | free: calls used, remaining, cache hits, refused, calls per SKU |
Cost controls:
- **Field masks everywhere.** `X-Goog-FieldMask` on every request; a transport refuses to send one without it.
`*` is refused. Place fields come from a curated list (27 fields) with the tier Google's docs assign to each
(`src/fields.ts`). The priciest field sets the SKU, and the tool reports it in `meta.sku`.
- **Tier ceiling.** `PLACES_MCP_MAX_TIER` (default `enterprise`) refuses Enterprise + Atmosphere fields unless raised.
- **Budget.** `PLACES_MCP_BUDGET` (default 25) upstream calls per session; past it, tools return `budget_exceeded`
without touching the network.
- **Cache.** In-memory, per session, `PLACES_MCP_CACHE_TTL_S` (default 60 s, `0` disables). Cache hits are free.
- **Compact JSON.** Flat `{id, name, address, lat, lng, type, mapsUrl, rating, ratings, price, phone, website,
openNow, hours}`; empty keys dropped; `OPERATIONAL` status dropped; reverse geocode trimmed to 5 results.
## Measured
Keyless run of the real MCP server (in-memory MCP client). **Keyless numbers come from synthetic responses**
(`fixtures/synthetic/`): invented values in the same JSON shape (key paths, nesting, status codes) as real Google
responses. `tests/shape.test.ts` checks that shape against real responses recorded locally on 2026-10-21 (kept in the
gitignored `.recorded-local/`, never published). Byte counts depend on the invented values, so read them as
illustrative. The proof against the real APIs is the live smoke test (`tests/live.test.ts`: text search, geocode,
route, plus a cache hit), which passed with a key on 2026-10-21.
Produced by `bun run report`; full output in `reports/places-new-mcp.md` and `reports/places-new-mcp.json`.
<!-- measured:start -->
| Step | Tool | Billed SKU (from field mask) | Mask fields | Response bytes (synthetic, masked) | Compact bytes | Smaller by |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| text-search-default | `places_text_search` | Text Search Pro | 7 | 1863 | 1238 | 33.5% |
| nearby-cafes | `places_nearby_search` | Nearby Search Pro | 5 | 1356 | 736 | 45.7% |
| details-default | `place_details` | Place Details Pro | 6 | 355 | 240 | 32.4% |
| details-enterprise | `place_details` | Place Details Enterprise | 9 | 1134 | 437 | 61.5% |
| autocomplete | `places_autocomplete` | Autocomplete Requests | 6 | 1715 | 1271 | 25.9% |
| geocode | `geocode_address` | Geocoding (v4) | 5 | 239 | 206 | 13.8% |
| reverse-geocode | `reverse_geocode` | Geocoding (v4) | 5 | 2976 | 963 | 67.6% |
| route-drive | `compute_route` | Compute Routes Essentials | 7 | 184 | 154 | 16.3% |
| **total** | | | | 9822 | 5245 | 46.6% |
- reverse-geocode: the synthetic response has 14 results (as many as the real one did); the tool keeps the top 5 by default (client-side trim, same bill).
- Same text search repeated in-session: served from cache = **true** (no upstream call, no budget used).
- `fields: ["*"]`: **field_mask_refused** before any network call.
- `editorialSummary` (Enterprise + Atmosphere) with the default max tier `enterprise`: **field_mask_refused**.
- Invalid place id (synthetic copy of the Google 400 shape): **google_INVALID_ARGUMENT**.
- Session after the run: 9 budgeted calls of 25 (served by the synthetic transport, no network), 1 cache hit(s), 2 refused.
- Budget 2, four different calls: ok, ok, budget_exceeded, budget_exceeded.
<!-- measured:end -->
## What we verified about the idea's claims
Checked on 2026-10-21 against Google's own pages (each showed "Last updated 2026-10-20 UTC"):
| Claim | Result | Source |
| --- | --- | --- |
| Legacy Places API closed to new projects | **Confirmed.** "Legacy services are not available in new Cloud projects but remain fully supported for existing projects." Places API legacy date: March 1, 2025. | [Legacy products and features](https://developers.google.com/maps/legacy) |
| ...from Oct 5, 2026 | **Not confirmed.** Google's legacy date is March 1, 2025. Neither the legacy page, the [deprecations page](https://developers.google.com/maps/deprecations) nor the [Places release notes](https://developers.google.com/maps/documentation/places/web-service/release-notes) has an October 2026 entry. | same |
| Existing projects keep working, at least 12 months notice | **Confirmed.** "we will provide at least a 12-month notice prior to the decommission"; no decommission date set. | [Legacy products and features](https://developers.google.com/maps/legacy) |
| Directions / Distance Matrix replaced by Routes API | **Confirmed.** Both Legacy since March 1, 2025, replacement Routes API (`directions/v2:computeRoutes`, `distanceMatrix/v2:computeRouteMatrix`). | [Legacy page](https://developers.google.com/maps/legacy), [Routes migration guide](https://developers.google.com/maps/documentation/routes/migrate-routes) |
| "Geocoding v4" | **Confirmed it exists.** Preview April 30, 2025; GA March 30, 2026. Endpoints under `geocode.googleapis.com/v4/geocode/...`, field mask via `X-Goog-FieldMask`. Our live calls to `/v4/geocode/address` and `/v4/geocode/location` returned HTTP 200. | [Geocoding release notes](https://developers.google.com/maps/documentation/geocoding/release-notes), [v4 overview](https://developers.google.com/maps/documentation/geocoding/geocoding-v4-overview), [Choose fields](https://developers.google.com/maps/documentation/geocoding/choose-fields) |
| "Field agents still scrape Maps HTML; few ship a clean MCP" | **Not verified.** Market claim; we did not measure it. | none |
## Limits
- **Caching and Google's terms.** Google's [Places policies](https://developers.google.com/maps/documentation/places/web-service/policies)
say "You must not pre-fetch, cache, or store Places API content beyond the allowed exceptions"; place IDs are exempt.
The cache here is in-memory, per process, never written to disk and expires after 60 s by default, meant to absorb an
agent repeating the same call in one task. Check the Maps Service Terms for your use; set `PLACES_MCP_CACHE_TTL_S=0`
to turn it off.
- **No Google content in the repo.** Keyless tests and the demo use synthetic responses with invented values. Real
responses are recorded only to `.recorded-local/` (gitignored) for the shape check. In fixture mode only the demo
requests answer, and their answers describe fictional places.
- **Attribution.** If you show place content to people, Google requires Maps attribution and author credit for
photos/reviews. Photos and reviews are not exposed by this server.
- **SKU labels are estimates.** Tiers are copied from Google's docs on 2026-10-21 for the 27 supported fields. Routes tier
logic covers traffic-aware (Pro) and two-wheeler (Enterprise) only. We did not verify the Geocoding API v4 SKU name.
The bill in Google Cloud is the source of truth. No prices are quoted here.
- **Budget scope.** One budget per process/MCP connection, not per key or per day. Use Cloud quotas and budget alerts
for hard limits.
- **Not included:** Route Matrix, Places photos, reviews, Address Validation, Geocoding v4 `SearchDestinations`
(Experimental/Preview), and Gemini summaries (an MCP client is already an LLM; summarizing compact JSON again
would add cost).
- **Legacy APIs are never called.** No `maps.googleapis.com/maps/api/...` endpoint is used; a test enforces this for the synthetic fixtures.
## Development
```bash
bun run check # install, skills layout, biome, tsc, tests, report, bun audit, gitleaks
bun test # live smoke runs only if GOOGLE_MAPS_API_KEY is set (3 calls); PLACES_MCP_SKIP_LIVE=1 skips it
bun run fixtures:synthetic # regenerate fixtures/synthetic (keyless, deterministic)
bun run record # record real responses to gitignored .recorded-local/ (9 field-masked calls); enables tests/shape.test.ts
```
## Not affiliated
Not affiliated with, endorsed by, or sponsored by Google LLC. Google Maps Platform, Places API and Routes API are
trademarks of Google LLC.
---
Built by [SkilledDragon LLC](https://skilleddragon.com). Questions: hello@skilleddragon.com. Apache-2.0 (see LICENSE and NOTICE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues