mcp-town-explorer
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-town-explorer Compare schools in Winchester and Lexington"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-town-explorer
A learning project that makes every MCP boundary visible: host, client, server, transport, discovery, invocation. The trick that makes the boundaries legible is one server, two hosts: a no-LLM host and an LLM host talk to the exact same server with zero server changes.
What MCP is
The Model Context Protocol (MCP) is a standard way for an application (the host) to let a language model use external tools and data through a uniform interface. The host embeds an MCP client that speaks JSON-RPC to one or more MCP servers; each server advertises tools (callable functions) and resources (readable content) with machine-readable schemas. The point is decoupling: any MCP-speaking host can use any MCP server without custom glue, because discovery ("what tools exist?") and invocation ("call this tool with these arguments") are standardized.
Related MCP server: Contract Review Demo MCP Server
Architecture
Which file is which:
Host:
v1_cli/host.pyandv2_llm/host.py. The host owns the user interaction, and in V2 owns the LLM and the validation logic.Client:
ClientSessionfrom the MCP SDK, constructed inside each host. It is not hand-written JSON-RPC; the SDK provides it.Server:
server/server.py. Built with FastMCP; exposes the tools and resource. Neither host imports it -- they only launch it as a subprocess and speak the protocol.
For a deeper walk-through of the MCP plumbing (transport, client, handshake, schemas, result shapes), see docs/notes.md.
Setup
Uses uv. Python is pinned to 3.12.
uv syncRun V1 (no LLM, no API key)
Argv selects the tool; the model is never involved. This isolates the protocol from model behavior.
uv run python v1_cli/host.py list-tools # print the raw advertised JSON schema
uv run python v1_cli/host.py housing Winchester
uv run python v1_cli/host.py distance Winchester
uv run python v1_cli/host.py schools Lexington
uv run python v1_cli/host.py safety Woburn
uv run python v1_cli/host.py resource Winchester # read the town://{town} resource
uv run python v1_cli/host.py --verbose housing Winchester # log the protocol lifecycleThe error path, deliberately shown:
uv run python v1_cli/host.py housing Nowhere
# -> ERROR from server: Unknown town: 'Nowhere'. Known towns: ...
# exits non-zero; the server error surfaces across the protocol rather than being swallowedRun V2 (adds the LLM)
Natural language in. The model sees the tool schemas, proposes a call, the host validates it, the client executes it, the result is fed back, and the model explains. The loop repeats (capped at 5 iterations) until the model stops requesting tools.
Put OPENAI_API_KEY in a .env file at the repo root (it is git-ignored); the
host loads it automatically via python-dotenv. Exporting the variable works too.
# .env at the repo root contains: OPENAI_API_KEY=sk-... (loaded automatically)
uv run python v2_llm/host.py "Compare schools in Winchester and Lexington"
uv run python v2_llm/host.py --show-tokens "How safe is Woburn?" # print tool-schema token cost
# or export it instead of using .env:
export OPENAI_API_KEY=sk-...
uv run python v2_llm/host.py "How safe is Woburn?"The validation step is the reason V2 exists.
Before any execution the host checks the proposed tool name against an allowlist and, for tools that take a town, checks that the town exists in the dataset; a rejected call is logged ([REJECTED] ...) and never reaches the server.
The exact line where a model proposal becomes an execution is commented in v2_llm/host.py.
Provenance
This dataset describes real Massachusetts towns, but it is stitched together from several sources of differing years and methodologies. It is a demo for teaching MCP architecture. Do not use it for any real decision (buying a home, choosing a school district, judging safety).
Sources, one per column:
column | source |
| Zillow Home Value Index (ZHVI), town level |
| GreatSchools, rounded average of the town's public schools (GreatSchools publishes no single district number) |
| NeighborhoodScout, incidents per 1,000 (2024 FBI-derived vintage) |
| computed: haversine from the town's US Census/Wikipedia centroid to Boston City Hall (42.3601, -71.0589) |
| US Census (2020 decennial or ACS estimate) |
Per-column source URLs, vintages, retrieval notes, and known caveats are recorded in PROVENANCE.md.
The habit is the lesson: record where every number came from, even in a teaching dataset.
What this does NOT demonstrate
Deployment. stdio transport means the server is a local subprocess the host spawns. There is no network service, no container, no host/port.
Auth. No authentication or authorization between host and server. The V2 "permission boundary" is application-level input validation, not identity or access control.
Remote transport. No HTTP/SSE/streamable transport. Everything is local stdio.
Multi-user / concurrency. One host, one server subprocess, one user, one request at a time.
Available Tools
4 toolsget_distanceC
Straight-line distance from one town to Boston, in miles.
Inputs: town (required).
get_housingB
Median home price for one town, in USD.
Inputs: town (required).
get_safetyB
Crime figures for one town: violent crime rate as incidents per 1,000 residents.
Inputs: town (required).
get_schoolsB
School rating for one town (GreatSchools district rating, 1-10).
Inputs: town (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
Each tool targets a distinct domain dimension: housing, transportation, education, and public safety. There is no overlap between the four tools, and their purposes are clearly separable by the resource type they query.
All tools follow a consistent get_[noun] pattern (get_housing, get_distance, get_schools, get_safety). The only minor deviation is that 'distance' refers to a relationship (distance to Boston) while the others refer to town attributes, but the naming is otherwise uniform and predictable.
Four tools is a borderline-low count. For a town explorer covering four quality-of-life dimensions (housing, commute, schools, safety), it's coherent but thin—a full explorer might also include taxes, weather, demographics, or amenities.
The four tools cover major town-comparison dimensions (housing, distance, schools, crime), but several obvious gaps exist: no property-tax data, no median income, no demographics or unemployment figures. An agent comparing towns would likely need more than these four metrics for a thorough assessment.
Maintenance
Related MCP Connectors
An MCP server for deep research or task groups
Read-only MCP server for Sandwich aging-parent care resources and cost data.
Related MCP Servers
- FlicenseCqualityDmaintenanceA demo MCP server with tools for getting weather via wttr.in and executing read-only SQLite queries.10-
- AlicenseNot gradedqualityCmaintenanceA public-safe demo MCP server for learning how to package Tools, Resources, and Prompts, using fictional contract text and local demo rules only.MIT
- FlicenseCqualityDmaintenanceA simple MCP server demonstrating resources, tools, and prompts using a local task list. It enables reading, creating, and analyzing tasks through MCP.1-
- AlicenseNot gradedqualityDmaintenanceA demonstration MCP server that enables read-only SQL queries and schema exploration of a synthetic IaaS database through the Metabase API.MIT