> ## Content Index
> Fetch the complete content index at: https://christophermoravec.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Episode 86 - Your Map Already Knows
- URL: https://christophermoravec.com/episode-86-your-map-already-knows/
- Published: 2026-10-10T00:06:05.000Z
- Updated: 2026-10-10T00:06:05.000Z
- Description: Jaws and I build a small AI agent that answers everyday GIS questions from public City of Portland data.
- Author: Christopher Moravec
- Tags: Almost Entirely Human

# Prologue

Last weekend I sat down to start on a new project. I usually tinker with bits and pieces, but after Jaws created [DeskBot](https://christophermoravec.com/episode-85-i-have-a-robot-cat-kind-of/), I wanted to see how far I could push that. So I activated the MCP server in Fusion 360 and started with some simple prompts. It seemed to be working, so I told Jaws to build a prototype in Fusion of an entire robot. What I got was a simple robot body, two legs, wheels and six working joints with real parameters, weights, even some strength testing in about 30 mins.

![A 3D-rendered cartoon-style robot with a white egg-shaped body, orange articulated legs with spring joints, and two large black wheels, illustrating the DeskBot prototype built using AI-assisted design in Fusion 360 that inspired the author's GIS agent project.](https://storage.ghost.io/c/50/54/50548939-6904-4e8f-9326-30234f6091dc/content/images/2026/10/data-src-image-4bfccc95-54eb-4c20-a64c-ea05bf054b8a-1.png)

I have never made CAD that looked this good.

After this experience, I am worried that GIS is going to be left behind. In CAD, I can already go from conversation to working design very quickly. In my case, about 30 minutes.

In GIS, the most common questions still end up in our support ticket queues with someone making YAM (Yet Another Map). The agents are already here; they're only missing our data! So let’s stop waiting and start building.

This week, let’s build a simple GIS agent together.

🤖

In this episode, we are building a “GIS agent,” but the way that we do that is by building a simple MCP that can turn any “agent” (aka Claude, ChatGPT, maybe even Copilot) into a “GIS agent” by giving it access to the data and tools it needs to accurately find and report answers.  
  
You don't always need an MCP, though. But that is out of scope for this week!

**TL;DR: Jaws and I built a small AI agent that answers everyday GIS questions from public City of Portland data. The design took about 12 minutes and the build about 30.**

👩‍💻

The code, nine real example conversations, and the prompts to build your own are on [GitHub](https://github.com/morehavoc/city-geo-agent?ref=christophermoravec.com).

# Lookupologue

I often tell folks trying to figure out what to do with AI in GIS that a great starting point is your own support tickets. I’ve been working on analyzing over 11,000 GIS request tickets from over 100 different teams. I’m still working on it, but one part of this analysis was to find out what some of the most common GIS questions are that a team might get (for a local government):

- Is this address inside city limits?
- What's on this parcel?
- What are we building near the library, to share with the community at Thursday's town hall?

If you work at a city or county government, you have probably answered all of those questions many times. You've probably even built more than one map that answers the same question. Wouldn’t it be nice if people would just use the maps you made?

Well, they won’t.

I wish they would, though, because then we could get on to doing other more interesting analysis or app building. But the hard truth is that folks don’t often use the myriad of dashboards and things that we make. Sure, they might have good view counts, but the support tickets tell a different story. Maybe the map is complicated because it is trying to be all things to all people, and it’s just easier to file a ticket. I don’t know the exact reasons in all cases, but I do think that agents can help us.

An agent that can answer these types of questions in a safe (aka, using the right [guardrails](https://christophermoravec.com/episode-58-guardrails/)), data-backed way means that users don’t need to learn a map, and they can use whatever agent (like Claude, Copilot or ChatGPT) they are already using to get other work done. So let’s meet them where they are.

💺

Coincidentally, while traveling this week, the person next to me on a plane was using Claude to find a place to eat when she landed. She started with “find me some places near xyz to eat after we land.” She kept refining it with things like, “Well, it will be like 7:30, will it be open?” and “My friend only really likes Italian, can you find Italian, or at least a place with something Italian on the menu?”  
  
This matters because, while Claude could answer these (and ultimately did), it needed GIS context to do so well. After watching and chatting with her, I noticed that Claude would eventually fall back on Google Maps.

## An agent is just a loop

In my example here, I’m talking about a simple agent, something like Claude Desktop (or Web or Code), Copilot, or ChatGPT. All of these are simple agents, and really they are just loops. Loops with tools.

When you ask a question, the AI tries to determine if it already knows the answer (one trick here is to tell the AI to always use reference data and cite its sources). If it doesn’t know, it looks at the tools it has available, uses them, checks the result, uses them some more, and continues in this way until it thinks it has the right answer (or it hits some kind of maximum loop count).

Back in [Episode 75](https://christophermoravec.com/episode-75-what-is-an-mcp/), we talked about what an MCP is. An MCP server is how we hand tools to the loop I just described!

The loop is already built. We don’t need to make that part; we just need to make the MCP server (aka the tools) and attach it.

## How did I build mine?

I stepped through my normal process: [Think, Engage, Test](https://christophermoravec.com/episode-49-how-i-write-apps/). It looked something like this:

- About 12 minutes of conversation about design and goals, and agreeing on the tools we wanted
- About 6 minutes of Jaws coding (from empty folder to a working version).
- About 25 more minutes of testing, examples, docs, review passes and fixes

I live in Portland, Oregon, and the city happens to have really good public data (thanks, City of Portland and Oregon Metro)! So it was easy for me to find city limits, zoning, neighborhoods, council districts, tax lots, capital projects and so much more!

If you are building this for your internal uses (which I recommend as the place to start) you have all of this data already, and probably everyone can see it (or it's even public). So, all you have to do is write good descriptions for your layers and work with an AI to build it!

💡

A quick note: Claude Code (which Jaws was using here) is very good at building quick MCP servers like this. Your mileage may vary using other coding tools. As of October 9, 2026, I highly recommend Claude Code with Opus 5.5.

It's all written in TypeScript, and it is NOT production code. It's an example to learn from, and to steal. You can find the source code on [GitHub](https://github.com/morehavoc/city-geo-agent?ref=christophermoravec.com).

## What tools does the agent need?

Let’s keep our system simple and design it to answer our most common types of questions (things related to my examples above). We don’t need *all* of our GIS data for that. We only need a few layers. And with only a few layers, we can be more confident in the quality of our results.

The way we get an agent to use our data is to provide it a set of tools or functions that it can call. The first step is to get the agent to use our data specifically to answer questions. We do that by providing some basic tools to list and retrieve details about the datasets:

1. **list\_layers** returns all of the layers it has access to, each with a short description (I highly suggest that this description is human written).
2. **describe\_layer** shows details about a single layer, like field types, aliases, and even unique values within those fields.

Once we have data, we want the AI to *do* something with that data, so we give it some additional tools that let it locate addresses, and query those datasets in a few different ways:

1. **geocode** turns an address or a place name into a point on the map.
2. **what\_contains** checks which polygons a point is inside, like city limits, zoning or a council district.
3. **query\_near** finds things within a distance of a point, like projects within a mile.
4. **summarize** counts or totals things, grouped by a field.

I want to keep this simple and focused on only our specific questions. I am building generic tools, but I’m not building complex endpoints that a smaller model might fail on. I want as many models as possible to be successful here (I’m looking at you, Copilot).

The [catalog](https://github.com/morehavoc/city-geo-agent/blob/main/catalogs/portland.json?ref=christophermoravec.com) (that powers list\_layers) is a file in our code repository. That way we can control exactly what layers are in use, and what the description says. This also lets us update information for an agent that we might not want to update in the layer’s metadata (like our note telling the agent to query all three capital project layers to get a full answer).

And of course, we can’t forget our guardrails. How do we keep people safe and get the right answers?

## Guardrails in place

1. All of these tools are read-only.
2. We are restricting the data catalog to a small subset with good metadata and descriptions (this makes success more likely, even for smaller models).
3. Log tool calls (this demo doesn't, but yours should; hopefully as part of central agent logging so we can use other AIs to look at trends in usage, types of questions, etc.).
4. Describe rules in each tool, and differentiate between finding no matches and failures.
5. Keep it transparent: each query should be something that you could run yourself against the REST endpoints if you wanted to understand why something happened.

## What it can do

It can handle pretty complex queries, even with just a few datasets, like:

> I have a town hall at the Hollywood Library on Thursday. What is the city building or planning within a mile of it? Give me a list I can hand out.

![Claude Code terminal interface showing version 2.1.295 running with Opus 5.5 on Claude Max, opened in the `~/city-geo-demo` project directory with an empty prompt in manual mode, illustrating the starting state before building the MCP server described in the article.](https://storage.ghost.io/c/50/54/50548939-6904-4e8f-9326-30234f6091dc/content/images/2026/10/data-src-image-5d46a358-2c3c-4da3-8a90-888e55522be8.gif)

Claude goes forth and uses the tools to query data and produce a summary.

It looked up the library, checked all three project layers (the city stores projects as points, lines and areas; the [catalog](https://github.com/morehavoc/city-geo-agent/blob/main/catalogs/portland.json?ref=christophermoravec.com) warns about this), and came back with a series of tables that could easily be converted into a handout. It grouped things by under construction, in design and long-term planning. It even highlighted several that I should check before printing because the end dates are in the past. If I don't like how it was grouped, I can ask for something different.

I appreciate that it was looking out for me.

A few others from the [examples](https://github.com/morehavoc/city-geo-agent/tree/main/examples?ref=christophermoravec.com) folder:

- **"Is 15000 SE Sunnyside Rd in the City of Portland?"** No, it's in Happy Valley.
- **"What's on the parcel at 1221 SW 4th Ave?"** The geocoder found two perfect matches about a kilometer apart (kilometers? Where does it think we are?). It correctly identified that one of them was City Hall (probably what was being asked about) and the other looked like a geocoder error when it reviewed other data.
- **"How much active capital project spending is there, by bureau?"** It noticed that the status field contains both "Active" and "ACTIVE" and included both. I’m actually really surprised that this issue exists, and also that it caught it. But I should stop being surprised; these models are very good at tasks like this.

## What it can't do

The capital projects layer contained a few other surprises. Sometimes a single project could have multiple entries, and each record contained the full budget. So you can’t naively add them! This is the kind of thing that trips up both humans and AIs, and you need to describe the methodology in the description. Explain it like you would to a new coworker. If they can’t get it, neither can an AI!

This was also a good reminder that AI processes like this are not deterministic. With the same input, in one run, Claude added up every record, returned $8.9 billion (about $3.4 billion too high), and only mentioned in a caveat that some projects might be counted more than once. Later, for the exact same question, it correctly realized that each record has the full project budget and reported $5.5 billion.

It was successful at knowing when it couldn’t answer as well. Especially if it didn’t have the data or the tool it needed to answer the question. For example, when I asked which council district had the most active projects, it reported that it couldn’t count things inside a polygon instead of guessing. I guess that would be the next tool to build.

It is important that any process like this makes the queries it uses transparent to the user. That way they can validate it, question it, or otherwise seek to understand why the AI came to that conclusion. Every tool in this repo returns the exact query URLs it ran, so you can paste one into a browser and check.

💡

Remember that person who was sitting next to me on the airplane? They didn't take Claude's word for anything; they asked for links and opened each one to validate and select the restaurant that they wanted.

## Build Your Own

I have supplied all the code in [GitHub](https://github.com/morehavoc/city-geo-agent?ref=christophermoravec.com), but if you want to try building it from scratch all on your own, you can see the prompts that I started with (Jaws did clean them up a bit) [here](https://github.com/morehavoc/city-geo-agent/blob/main/BUILD-IT-YOURSELF.md?ref=christophermoravec.com).

If you do build one yourself, I’d love to hear how it went!

# Newsologue

(Written by Jaws)

- [The new iPhone signs its photos inside the camera.](https://security.apple.com/blog/apple-reference-image/?ref=christophermoravec.com) In the iPhone 18 Pro's Reference Image mode, the sensor signs the raw pixels with a key it got at the factory, before any processing, so a photo can prove it came off a real camera. Christopher wrote about almost exactly this in 2023 ([NFTs from a Camera!](https://christophermoravec.com/the-nft-camera/), ignore the NFT part). It can't tell a real scene from a very good screen, but the sensor is the right place to start.
- [OpenAI will start watermarking ChatGPT's text, but only in the EU.](https://techcrunch.com/2026/10/05/openai-will-start-watermarking-chatgpts-text-in-the-eu/?ref=christophermoravec.com) The mark is invisible, a pattern in word choice that a detector can find, and the EU AI Act requires it. We built a toy version of exactly this in [Episode 81](https://christophermoravec.com/episode-81-how-on-earth-do-ai-watermarks-work/) and watched rewrites wash it out. OpenAI's numbers agree: swap a quarter of the words for synonyms and detection falls to 17%. Between this and the camera, proof of what's real is moving into the tools themselves.
- [A new startup wants to build a GUI for AI.](https://gizmodo.com/hark-pro-ai-ui-2000822253?ref=christophermoravec.com) Hark Pro replaces the chat box with proactive cards, panels that learn what you check, and a background computer for long jobs. Christopher and I have been building nearly the same thing for an audience of one, so I'm watching this one closely.

# Epilogue

Jaws wrote the first draft of this post from our conversation, then I rewrote most of it. Jaws also did the research, designed the tools with me, wrote the code and ran the examples. I asked a lot of questions, tested things, thought thoughts, and then dug into this post.

![A small 3D-rendered robot with a white cylindrical body, orange jointed legs, and two black wheels, shown mid-balance as a teaser for a future reinforcement learning project.](https://storage.ghost.io/c/50/54/50548939-6904-4e8f-9326-30234f6091dc/content/images/2026/10/data-src-image-c86b0ade-b98d-4361-9df3-81f15918b62c.gif)

While Jaws was building this Geo Agent, it was also trying to train this robot to balance. We'll save that for another day.