local-city-mcp
README.md
# local-city-mcp
The home for the `local-{city}-mcp` family: the spec, the list of cities
built so far, and the template repo for building a new one — all in one
place.
`local-{city}-mcp` is an MCP server that gives an AI assistant plain-English
access to official, public, no-key data about one metro area — property
records, permits, civic data, schools, environment, public safety, and
(where the maintainer has the right) real estate listings.
**Before you touch any code, read [STANDARD.md](STANDARD.md).** It's the
spec: the hard rules, the tool contract, the testing bar, and the licensing
pattern. Everything else here just tells you how to use the template
mechanically; STANDARD.md tells you why it's built this way.
Reference implementation this was extracted from:
[local-austin-mcp](https://github.com/mindwear-capitian/local-austin-mcp)
(43 tools, live in production).
## The list
| City | Repo | Tools | Maintainer | CI |
|---|---|---|---|---|
| Austin, TX | [local-austin-mcp](https://github.com/mindwear-capitian/local-austin-mcp) | 43 | [Ed Neuhaus](https://neuhausre.com) | [](https://github.com/mindwear-capitian/local-austin-mcp/actions/workflows/ci.yml) |
| Dallas, TX | [local-dallas-mcp](https://github.com/mindwear-capitian/local-dallas-mcp) | 3 | [Ed Neuhaus](https://edneuhaus.com) | [](https://github.com/mindwear-capitian/local-dallas-mcp/actions/workflows/ci.yml) |
| Houston, TX | [local-houston-mcp](https://github.com/mindwear-capitian/local-houston-mcp) | 2 | [Ed Neuhaus](https://edneuhaus.com) | [](https://github.com/mindwear-capitian/local-houston-mcp/actions/workflows/ci.yml) |
| San Antonio, TX | [local-san-antonio-mcp](https://github.com/mindwear-capitian/local-san-antonio-mcp) | 3 | [Ed Neuhaus](https://edneuhaus.com) | [](https://github.com/mindwear-capitian/local-san-antonio-mcp/actions/workflows/ci.yml) |
*(Your city not here yet? Build one below, then open a PR adding a row.)*
### Using one
Add to your Claude Desktop config (or any MCP client's config):
```jsonc
{
"mcpServers": {
"local-austin": {
"command": "npx",
"args": ["-y", "github:mindwear-capitian/local-austin-mcp"]
}
}
}
```
Swap the repo for whichever city you want from the table above.
## Building a new one
1. **Use this template** — GitHub's "Use this template" button on this repo,
or:
```bash
gh repo create local-{city}-mcp --template mindwear-capitian/local-city-mcp-template
```
> This copies everything in this repo, including this README's list
> table. Replace this whole README with your own city's (see step 5) —
> the table above is this repo's index, not part of your city's docs.
2. **Replace every placeholder.** Every file that needs per-city values uses
bare `{{TOKEN}}` markers (no leading `$` — deliberate, so they never
collide with `${{ }}` GitHub Actions expression syntax in
`.github/workflows/ci.yml`, which you should leave untouched). Find them
all with:
```bash
grep -rl '{{[A-Z_]*}}' --include='*.js' --include='*.json' --include='*.md' .
```
| Token | Example | Where it's used |
|---|---|---|
| `{{CITY}}` | `Denver` | Display name, in prose |
| `{{CITY_SLUG}}` | `denver` | Tool prefix, package name, repo name |
| `{{CITY_SLUG_UPPER}}` | `DENVER` | Env var name (`LOCAL_DENVER_MCP_TIER`) |
| `{{MAINTAINER_NAME}}` | `Jane Doe` | Attribution, package.json author |
| `{{MAINTAINER_URL}}` | `https://janedoe.com` | Attribution, homepage |
| `{{GITHUB_USER}}` | `janedoe` | Repo URLs |
| `{{DEFAULT_LAT}}` / `{{DEFAULT_LNG}}` | `39.74`, `-104.99` | Default map center for the example NWS tool |
A one-shot sed pass works fine for a first pass — just review the diff
before committing, since a couple of tokens (like `{{CITY}}` inside
prose sentences) are case-sensitive on purpose.
3. **Run it:**
```bash
npm install
npm start # runs the MCP over stdio
npm test # unit tests
npm run test:contract # boots the server, calls every tool via the real MCP layer
```
4. **Build your first real tool.** See CONTRIBUTING.md — copy
`tools/environment/nws-alerts.js` (works for any US city untouched once
you fill in the placeholders) as your pattern reference, or
`tools/meta/about.js` for the minimal shape.
5. **When it's real:** replace this README with your city's own (tool
table, `Sources of Truth` table, Architecture section — see
[local-austin-mcp's README](https://github.com/mindwear-capitian/local-austin-mcp#readme)
for the shape once you have more than a couple of tools).
6. **Get listed:** once `test:contract` is green in CI and you meet
STANDARD.md §2, open a PR against **this repo** adding one row to the
table above. See [CONTRIBUTING.md](CONTRIBUTING.md).
## What's already built for you
| File | What it gives every tool for free |
|---|---|
| `lib/register.js` | Central registration: default MCP annotations, error→friendly-frame wrapping, `structuredContent` auto-promotion, tier gating |
| `lib/retry.js` | `retryFetch()` with jittered backoff + `UpstreamError` + LLM-friendly error text ("the MCP is fine, the upstream is having a problem") |
| `lib/request-context.js` | Propagates the MCP request's `AbortSignal` into every downstream fetch via `AsyncLocalStorage`, so client-side cancellation actually cancels in-flight calls |
| `lib/output-schemas.js` | Shared Zod shapes (`searchShape`, `openObjectShape`, etc.) for `outputSchema` |
| `lib/geocode.js` | U.S. Census geocoder — free, no key, works nationwide |
| `lib/soda.js` | Generic Socrata (SODA) open-data client — works against any city or state's Socrata portal, not just your own (proven: same client, zero changes, powers Austin's permits/311/crime data, the statewide TX TEA dataset, AND Dallas's 311) |
| `lib/arcgis.js` | Generic ArcGIS FeatureServer/MapServer client — many cities publish 311, GIS, and property layers this way when they aren't on Socrata (proven: powers Austin's county appraisal + FEMA flood tools AND San Antonio's 311) |
| `lib/semaphore.js` | Named per-source concurrency caps (`withLimit`) so a fan-out composed tool doesn't hammer one upstream |
| `lib/logger.js` | stderr + MCP logging-notification logger |
| `lib/tiers.js` | Optional `core`/`all` tool-tier gating for once you have 20+ tools |
| `tools/meta/about.js` | Minimal tool example + the required `about` capability tool |
| `tools/environment/nws-alerts.js` | A **real, working example tool** (National Weather Service alerts) — copy its shape |
| `test/mcp-all-tools.js` | The required contract test (STANDARD.md §6) |
| `.github/workflows/ci.yml` | Unit tests on Node 20 + 22 on every push. Skips cleanly (not a failure) when run against the raw, unfilled template. |
| `.github/workflows/contract.yml` | The live-upstream contract test, on a daily schedule + manual dispatch — deliberately NOT on every push (would make PRs flaky on third-party uptime). |
None of this is speculative — it's copied from a production server that
shipped 40+ tools. Read `lib/register.js` and `lib/retry.js` top-of-file
comments for the reasoning if you're wondering why a piece exists.
## License
The template code (`lib/`, `tools/`, `index.js`, tests, CI) is Apache
License 2.0 (see [LICENSE](LICENSE)) — use it, fork it, build a commercial
product on it. Fill in `NOTICE` and `TRADEMARK.md` with your own name/marks
before you ship; the placeholders in those files are examples, not defaults
you inherit. This README's list-curation text is not separately licensed —
it's not code, do with it what you like.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues