One of the best ways to understand a technology is to build something with it. For this post, we built an NYC Neighborhood Finder — a conversational app where you describe what you're looking for in a neighborhood and an AI agent recommends specific ones, highlighted on an interactive map. But it's not just relying on general knowledge of New York City — it's actively looking up real-world businesses and addresses using Mapbox APIs to provide a better answer. This is known as location grounding, and this post breaks down how it works when building agentic apps with Mapbox.
To keep API costs manageable, the app isn't publicly hosted, but it's a quick local setup. You'll just need your Mapbox Access Token and an Anthropic API key. Find this app in our public tools and demos repo.
It's a demo, not a production app, but it's a useful one. Neighborhood search is a good test case because it mixes two kinds of knowledge: the general ("Park Slope has a great brunch scene") and the specific ("is there actually a Home Depot within 15 minutes of here?"). LLMs are good at the first kind and unreliable at the second. That gap is what makes it an interesting problem to solve — and a clear illustration of why grounding matters.
The core point of this post isn't the neighborhood finder itself. It's the pattern: use an LLM for what it's good at, and reach for real location data when the answer needs to be verifiably correct.
The Demo
The app is a split-panel interface — chat on the left, map on the right. You describe what you're looking for in plain language, and the agent uses Mapbox tools to research real locations and travel times before recommending neighborhoods highlighted as boundary polygons on the map.
One deliberate choice: instead of dropping pins, we highlight full neighborhood boundary polygons. The neighborhood boundary polygons come from the nyc-neighborhood-boundaries dataset which has 300+ hand-curated polygons covering all five boroughs (also used in the NYC Neighborhoods App)
This kind of query is a good stress test: "within 15 minutes drive of a Home Depot" is a concrete, verifiable requirement that the model can't answer from training data alone. It needs real store locations and real travel time calculations. That's exactly what the Mapbox MCP server provides.
Technical Setup
Frontend
The frontend is a React app built with Vite. The two main external libraries are:
Mapbox GL JS handles all map rendering. The map loads a GeoJSON dataset of NYC neighborhood boundaries (300+ polygons, one per neighborhood). When the agent recommends neighborhoods, the app highlights them using Mapbox's layer filter and match paint expressions to color each region independently. Label markers with neighborhood names are placed at polygon centroids using Mapbox Marker elements.
@ai-sdk/react provides the useChat hook, which manages the SSE connection to the backend, streams message updates into React state, and exposes the full message history including tool call parts. As the agent works, tool call status updates stream in and are rendered in the chat as a live log — users can see each Mapbox API call happening in real time.
The frontend also fetches the neighborhood GeoJSON directly from GitHub on load, building a name-keyed index used to enrich agent recommendations with summary text and Wikipedia links.
Server
The backend is a small Express server (~85 lines). There are two key external integrations.
The Mapbox MCP Server is spawned as a child process on startup using StdioClientTransport from the MCP SDK. The server uses the Model Context Protocol to discover and call tools — it calls listTools() once at boot to get the full catalog (28 tools), then wraps each one in an AI SDK dynamicTool so they participate in the streaming UI and appear in the chat status log. The execute function for each tool is a simple proxy: it forwards the model's arguments to mcpClient.callTool() and returns the text response.
// --- Mapbox MCP setup ---
const mcpClient = new Client({ name: 'nyc-neighborhood-finder', version: '1.0.0' })
await mcpClient.connect(new StdioClientTransport({
command: 'npx',
args: ['-y', '@mapbox/mcp-server'],
env: { ...process.env, MAPBOX_ACCESS_TOKEN },
}))
// Convert MCP tools to AI SDK format (dynamicTool so they appear in the UI stream)
const mapboxTools = Object.fromEntries(
mcpToolList.map(t => [
t.name,
dynamicTool({
description: t.description ?? '',
parameters: jsonSchema(t.inputSchema),
execute: async (args) => {
const result = await mcpClient.callTool({ name: t.name, arguments: args })
return result.content.map(c => c.type === 'text' ? c.text : JSON.stringify(c)).join('\n')
},
}),
])
)
mapboxTools is then combined with locally-defined tools, then all tools are passed along to streamText() for use with the AI Agent.
The Vercel AI SDK (streamText from the ai package) handles the entire agentic loop. Rather than manually managing tool calls and multi-turn conversation state, streamText with stopWhen: stepCountIs(25) runs up to 25 steps autonomously — Claude decides which tools to call, the server executes them, results go back to Claude, and the loop continues until Claude produces a final response with no more tool calls.
The show_neighborhoods Custom Tool
Most tools in the system come from Mapbox MCP. But there's one tool the server defines directly: show_neighborhoods.
show_neighborhoods: tool({
description: 'Display recommended neighborhoods on the map as highlighted regions with info cards in the chat. Call this whenever you have neighborhood suggestions.',
parameters: z.object({
neighborhoods: z.array(z.object({
name: z.string().describe('Exact neighborhood name as it appears in NYC'),
borough: z.string().describe('Borough (manhattan, brooklyn, queens, bronx, staten_island)'),
reason: z.string().describe("One or two sentences on why this neighborhood fits the user's criteria."),
}).strict()),
}),
execute: async () => 'Neighborhoods displayed on map.',
})
The execute function is intentionally a no-op — it just returns a confirmation string. The real work happens on the frontend. Because @ai-sdk/react streams the full message state including tool call inputs, the frontend can watch for tool-show_neighborhoods parts, parse the neighborhoods array out of the input, look up each name in the GeoJSON index, assign colors, and drive both the map highlighting and the neighborhood cards in chat — all without any extra API roundtrip.
This is a useful pattern for agentic UIs: use tool calls not just to fetch data, but as structured signals to the frontend to trigger UI updates.
The Grounding Loop
The system prompt is where the real strategy lives:
You are a knowledgeable and friendly NYC neighborhood guide. Your job is to help users find NYC neighborhoods that match their lifestyle, vibe, and requirements.
You have deep knowledge of all five boroughs — Manhattan, Brooklyn, Queens, the Bronx, and Staten Island — including their neighborhoods' characters, amenities, demographics, price ranges, transit access, dining scenes, nightlife, parks, and general atmosphere.
When a user asks about specific amenities (grocery stores, gyms, cafes, parks, transit stations, etc.), use the Mapbox tools to look up real locations before making claims. search_and_geocode_tool is good for specific brands (e.g. "Whole Foods"), category_search_tool for generic types (e.g. "gym"). Always bias searches toward NYC using coordinates near 40.7128,-74.0060 or more specific if you know the coordinates for a neighborhood you are researching. ground_location_tool answers questions about what is near a location: neighborhood context, nearby POIs by category, and travel-time reachability and may be a good starting point for understanding a neighborhood's amenities.
When a user describes what they're looking for, respond conversationally with a brief paragraph, then ALWAYS call the show_neighborhoods tool with your specific recommendations. Use the tool even if you need more information — show your best guesses so far and ask follow-up questions in your text.
Be specific and opinionated. Use neighborhood names exactly as they appear in NYC (e.g. "West Village", "Astoria", "Park Slope", "Fordham Heights").
The prompt draws a deliberate line: trust the model's general knowledge for culture, vibes, and character, but require it to verify specifics with tools before making claims. This is the grounding principle in practice.
Here's how that plays out with the Mapbox MCP tools in a real query like someone apartment-hunting in New York City "I need to be within 15 minutes driving of a Home Depot, and I want a budget gym within walking distance":
search_and_geocode_tool — backed by the Mapbox Search Box API, this finds specific businesses by name and returns their coordinates. The agent uses this to locate Home Depot and Lowe's stores across the five boroughs, getting real addresses and lat/lng positions rather than guessing.
isochrone_tool — backed by the Mapbox Isochrone API, this generates a polygon representing all areas reachable within a given travel time from a point. The agent runs this for each hardware store location with a 15-minute driving profile, producing actual reachability zones that it can reason about spatially.
category_search_tool — backed by the Mapbox Search Box API's category endpoints, this finds businesses by type (gyms, jazz clubs, cafes, parks) within a bounding area. Rather than asserting "Astoria has good budget gyms," the agent looks them up, finds how many are within walking distance, and uses that to inform its recommendation.
ground_location_tool — this is a higher-level tool that takes a coordinate and a query, and returns a rich context object: what neighborhood it's in, nearby POIs by category, and travel-time reachability. The agent uses this to quickly characterize candidate neighborhoods without chaining many individual tool calls.
The result of this loop is that by the time the agent calls show_neighborhoods, its recommendations are backed by actual data. It may have used an isochrone to confirm that a place the user wanted to be near actually overlaps a given neighborhood, verified that specific types of businesses exist within a neighborhood, or excluded a neighborhood entirely because the grounding work didn't turn up good results there. Claude's training knowledge fills in the texture — history, culture, what streets feel like — but the concrete criteria are verified against reality.
Wrapping Up
This app is built on two external services and one access token.
The Anthropic API provides Claude, which brings broad world knowledge, natural language understanding, and the reasoning ability to synthesize many tool results into coherent neighborhood recommendations.
The Mapbox MCP Server provides the location intelligence layer — geocoding, business search, isochrones, directions, travel time matrices, and more — all accessible to the model as tools through a standard protocol. The same Mapbox access token that powers the MCP server also authenticates the Mapbox GL JS map in the browser, so there's no additional credential management.
You can find the full source code for this example at mapbox/public-tools-and-demos.
Supporting resources:
If you're building an agentic app and your users are asking questions that have real-world, geographic answers — where things are, how far apart they are, what's reachable in a given time — the Mapbox MCP Server gives your model the tools to answer those questions accurately. The pattern is straightforward: let the model do what it's good at (reasoning, language, general knowledge), and reach for Mapbox when the answer needs to be grounded in the real world.




Top comments (0)