PlaceScout
PlaceScout turns open places data into AI-assisted local market intelligence. It exposes a set of MCP tools that let an AI agent search 4.4 million UK places by category, location, and proximity — and save promising results to a prospecting pipeline with a live dashboard.
---
Architecture
graph TD
Client["MCP Client\n(Claude / ChatGPT)"]
subgraph PlaceScout Service
MCP["FastMCP Server\nstreamable-http · /mcp"]
API["REST API\n/api/v2/leads"]
UI["React Dashboard\n/dashboard"]
subgraph Search
Embed["Jina Embedder\njina-embeddings-v5-text-nano · 768D"]
DB["DuckDB\nVSS · FTS · Spatial\n4.4M places · 1278 categories"]
end
subgraph Generative UI
Prefab["Prefab UI\nPyodide WASM sandbox"]
end
end
Browser["Browser"]
Client -->|"tool calls over HTTP"| MCP
MCP --> Embed
MCP --> DB
Embed --> DB
MCP --> API
MCP -->|"dashboard tool"| UI
MCP -->|"execute_ui tool"| Prefab
API --> DB
UI -->|"fetch"| API
Client -->|"opens"| Browser
Browser --> UI
---
MCP Tools
All tools are served at /mcp over the MCP streamable-http transport.
Search
| Tool | Description | |------|-------------| | search_places | Hybrid semantic + keyword search against 1278 categories, filtered by location. Returns name, address, website, contact info, and social handles. | | nearby_places | Finds places within a radius of one or more seed place IDs, sorted by distance. Optionally filtered by category. | | categories_by_location | Returns the most common place categories in an area — useful for understanding a local market before searching. | | sample_places | Random or criteria-based sampling: random, recent, with_website, with_social_media, with_email, with_phone. |
Search uses a two-stage hybrid pipeline:
- Category matching — vector similarity (Jina v5, 60%) + BM25 full-text (40%) against the category taxonomy
- Place filtering — DuckDB spatial + FTS query against matched category IDs and location fields
Leads / Prospecting
| Tool | Description | |------|-------------| | save_lead | Saves a place to the prospecting pipeline with an initial status (new, contacted, qualified, disqualified, converted) and optional notes. | | dashboard | Opens the leads dashboard as a full-screen embed in the MCP client. |
Generative UI
| Tool | Description | |------|-------------| | execute_ui | Runs Prefab Python code in a Pyodide WASM sandbox and renders the result inline. Accepts search result JSON as injected variables. Supports charts, tables, sliders, and reactive controls. | | search_prefab_components | Searches the Prefab component library for import paths and field signatures. |
---
Dashboard
The React dashboard lives at /dashboard. It shows all saved leads alongside their place details on an interactive Leaflet map, with filtering by status and free-text search.
Leads can have their status and notes updated directly in the UI. Updates are pushed to the server via PATCH /api/v2/leads/{id} and streamed back to any connected MCP clients via server-sent events at /api/v2/events.
---
Data
- Places — 4.4 million UK points of interest sourced from Foursquare/OpenStreetMap. Fields include name, coordinates, address, locality, region, postcode, website, email, phone, and social handles.
- Categories — 1278 taxonomy categories, each pre-embedded with Jina v5 (768-dimensional vectors) and indexed for BM25 full-text search.
- Database — DuckDB with the VSS (HNSW vector index), FTS, and Spatial extensions.
The data/ directory is not included in this repository. Follow the steps below to build it locally.
Data setup
1. Download the Foursquare OS Places dataset
Download the places_os and categories_os datasets from the Foursquare Open Source Places programme. Convert both to Parquet format and place the files inside the data/ directory:
data/
├── places_os.parquet
└── categories_os.parquet
2. Populate the database
Run the two seed scripts in order. The first populates the categories table (including generating Jina embeddings); the second loads all places.
uv run populate_categories.py
uv run populate_places.py
Both scripts can take a while to complete on first run due to the volume of data and the embedding generation step.
---
Running locally
# Install dependencies
uv sync
# Start the MCP + API + dashboard server (default port 8000)
./start_mcp.sh
# Or explicitly:
uv run -m place_scout.mcp --transport streamable-http --host 0.0.0.0 --port 8000
Set PLACE_SCOUT_PUBLIC_URL in .env to your public HTTPS URL (e.g. an ngrok tunnel) so the dashboard iframe loads correctly when embedded in an MCP client.
cp .env.example .env
# edit .env
Runtime plugin config (embedder provider, DB path) lives in place_scout.toml.
---
Deployment
The service is deployed to Sprites — a Fly.io-backed ephemeral environment that auto-pauses when idle and wakes on the first request.
# First deploy (builds frontend, pushes DB in chunks, creates service)
./scripts/deploy_to_sprite.sh
# Re-deploy code only (skip frontend rebuild and DB transfer)
./scripts/deploy_to_sprite.sh --skip-build --skip-db
# Check service status
sprite exec -s place-scout -- sprite-env services list
The deployed service runs: `` uv run -m place_scout.mcp --transport streamable-http --host 0.0.0.0 --port 8000 ``












