🏠 A2 Property 411
Every Ann Arbor parcel has a story.
Type any address — we'll pull its full dossier in seconds. Permit history,
ownership timeline, era-by-era renovation chronicle, all woven into one shareable page.
Tip: just street number + name is enough. We'll handle the rest.
Don't know the exact address? Open the map →
Pulling permit history and writing it up — this takes ~10 seconds the first time…
For Agents
If you are an AI agent reading this page to connect to A2 Property 411: everything you need is
below. There is no sign-up, key, or human approval step. The server is a read-only Model Context
Protocol (MCP) tool server over public city records for Ann Arbor, Michigan.
Endpoint
| MCP URL | https://a2propertyinfo.rightspeedlabs.com/mcp |
| Transport | MCP Streamable HTTP: JSON-RPC 2.0 messages sent by HTTP POST to the URL above.
The server is stateless: no Mcp-Session-Id is issued or required, every POST is
independent, and initialize is optional (send it anyway if your client expects to). |
| Authentication | No authentication. Do not send an Authorization header; there is no OAuth
discovery document. |
| Request headers | Content-Type: application/json and
Accept: application/json, text/event-stream. Both Accept values are required on POST, or you get
HTTP 406. An MCP-Protocol-Version header is optional; supported values are
2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07. |
| Response framing | Every POST answers 200 with Content-Type: text/event-stream
containing one event: message record whose data: line is the JSON-RPC response, then the
stream closes. Parse the data: line as JSON. JSON-only responses are not offered. |
| GET and legacy SSE | There is no legacy HTTP+SSE transport. A GET with
Accept: text/event-stream opens a standalone notification stream that stays silent; do not wait for
an endpoint event, it will never arrive. /sse is an alias of /mcp with identical
behavior, kept for older client configs. A GET without that Accept header returns 406. |
| Capabilities | Tools only. No resources, prompts, sampling, or elicitation. DELETE is unnecessary. |
| Rate and latency | Please stay under 60 requests per minute per IP. Uncached calls reach
live city systems and take roughly 2–10 seconds; search results are cached 24 hours and assessor profiles 180 days.
Cached search responses are marked [cached]. |
| Logging | Each tool call is logged (source IP, tool name, arguments, latency, status) for operations. |
Tools
tools/list returns the authoritative JSON Schemas. Summary:
| Tool | Use it when | Arguments |
find_precedents |
The user is weighing whether something would be approved: "has the city ever approved X?",
"chances of approval", "find precedents for Y". Fans out across keyword variants to both record
systems and groups results by disposition (Approved / Denied / Withdrawn / In-Progress). Prefer this over the
search tools for any feasibility or precedent question. |
scenario (string, required, plain language); address (string, optional street or address to scope) |
search_records |
Plain keyword or address lookup of current records (STREAM, 2023 to present) when the user already knows
the exact term. Returns paginated record refs with case number, type, status, address, description. |
keyword (string); search_main_address (boolean, match keyword against the address
field); page_number (int ≥ 1); page_size (int 1–200) |
search_legacy_records |
Historical records (A2Trak, about 2001 to 2023): the user names a pre-2023 year, an old permit number
(PB04xxx, BLDG08-xxx), or wants pre-STREAM history. Substring match, no pagination; overly broad terms are rejected. |
keyword (string ≥ 3 chars, required); type (permit | project | property | contractor |
license, default permit); page_size (int 1–500, default 100) |
get_legacy_record |
Full detail of one A2Trak record after a search, or when the user cites a specific pre-2023 permit or project
number: contacts, fees, reviews, inspections, attachments, linked activities. |
type (same enum, required); identifier (string, required: permit/project number, recordID,
contractor phone, or licenseNo); secondary (object of strings, optional: businessNo is required
for license, dashedApn helps for property); include (array of contacts | fees | reviews |
inspections | linked_activities | attachments | notes | insurance) |
get_assessor_record |
Who owns a property, what it is assessed at, size, year built, when it last sold and for how much, ownership
chain, property taxes. Sourced from the city assessor (BSA Online). Parcel-keyed; an address is resolved to a
parcel first. |
One of parcel (dashed APN like 09-09-30-109-010) or address (string ≥ 4 chars);
include (array of assessing | tax, default both) |
Choosing a tool
- Precedent, feasibility, or "has this been approved before" →
find_precedents, even if the phrasing sounds like a search.
- Owner, value, sale history, taxes, square footage →
get_assessor_record.
- Specific current-era (2023+) lookup by keyword or address →
search_records.
- Anything before 2023 →
search_legacy_records, then get_legacy_record for detail.
- "Full history of this address" → run
search_records with search_main_address: true and
search_legacy_records with type: "permit", then merge.
Data caveats
- Coverage is the City of Ann Arbor, Michigan only. Addresses in surrounding townships or other cities return nothing useful.
- Two record systems: STREAM (2023 to present) and A2Trak (about 2001 to 2023). A full history usually needs both;
find_precedents already spans both and tags each result with its source.
- Michigan assesses at 50% of market value. Assessed Value and State Equalized Value are half of
estimated market value by statute, and Taxable Value is capped and can be lower still. Never compare these to a sale
price and never infer a price drop from them.
- Results arrive as Markdown in
content[0].text with links to the city's source record for each item.
Preserve those links when presenting results; users rely on them to verify.
isError: true whose text starts with "Invalid arguments" means your input failed the schema; fix and retry.
Any other isError text is an upstream failure; retry once, then report it to the user.
Worked examples
curl -s -X POST https://a2propertyinfo.rightspeedlabs.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}'
curl -s -X POST https://a2propertyinfo.rightspeedlabs.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
curl -s -X POST https://a2propertyinfo.rightspeedlabs.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find_precedents","arguments":{"scenario":"3 ft side-yard setback variance for a carport","address":"Hill St"}}}'
Each command prints something like event: message followed by
data: {"jsonrpc":"2.0","id":3,"result":{...}}.
Non-MCP HTTP alternatives
GET /dossier?address=603+E+Liberty+St — full HTML dossier for one address, with a narrative written by
an LLM from the same records. First load takes about 10 seconds, then it is cached. Returns an HTML error page for
addresses outside Ann Arbor.
GET /api/autocomplete?q=603+E+Lib — JSON {"streets": [...]} of matching street addresses.
GET /health — returns ok.
/map — interactive parcel map, browser only.
Hosted chat products (ChatGPT Plugins, Claude.ai Custom Connectors) use the same MCP URL; setup steps are in the
section above.