OpenCityRecord
Server Details
City council meetings, transcripts, votes, and documents for U.S. cities, with citations.
- Status
- Healthy
- Uptime
- 100.0% over the last 24 hours
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- ChatGPT
- –
- Claude
- –
TDQS
Scored across 15 tools
Most tools are clearly scoped: application_timeline vs issue_timeline, get_meeting vs get_transcript_window, and track_position vs search_by_official are explicitly contrasted. The main overlap is the generic `search` tool, which duplicates the combined retrieval role of search_meetings/search_documents; descriptions mitigate this by saying to prefer the specialized tools, but it is still a redundant entry point.
All names use snake_case, and some families are consistent (search_*, get_*, *_timeline). However, the surface mixes verb-first names with bare nouns and noun phrases (`fetch`, `application_timeline`, `issue_timeline`, `whats_changed`), so the convention is readable but not fully predictable.
At 15 tools, the set is at the upper end of a reasonable range for a rich civic-records domain, and most tools map to distinct jobs. The count is slightly inflated by having both a generic `search` and several specialized search tools.
The surface covers discovery (find_city), meeting and transcript retrieval, document search, official rosters, voting records, decision search, project and issue timelines, upcoming meetings, and recent changes. CRUD-style gaps are not relevant here; for a read-only civic records server, the lifecycle from locating a city to reading a transcript, decision, or official record is well covered.
Publisher details
- Operator
- OpenCityRecord · Publisher source
- Operator website
- https://opencityrecord.org
- Vendor relationship
- First-party
- Documentation
- https://opencityrecord.org/mcp
- Trust center
- Not available
- Restrictions
- Requires an OpenCityRecord account; the free account covers one city.
Available Tools
15 toolsapplication_timelineDevelopment application timelineARead-onlyIdempotentInspect
How a development project moved through the city — a rezone, subdivision, site plan, conditional-use permit, or apartment/commercial build. Returns the application's lifecycle across meetings (pending → under-review → hearing → approved/denied), oldest first, with the status recorded at each step.
WHEN: 'What's the status of the Silo Park development?', 'How did rezone X move through the council?'. WHEN NOT: For issues/policy (not a specific project) use issue_timeline; for quotes use search_meetings.
Pass city whenever you pass q. A project or retailer name often matches applications in several cities, and without a city you will get application: null plus a candidates list to choose from rather than an answer about the wrong city. Check candidates even on a resolved application: one name can cover more than one project in the same city.
EXAMPLES: { "q": "Silo Park", "city": "lehi-ut" } — ALWAYS pass city with q { "application_id": "" }
Inputs: q, city, limit, application_id. Returns: timeline, candidates, application
fetchFetch a full transcript or documentARead-onlyIdempotentInspect
Retrieve the full record behind a search result: the complete meeting transcript (for meeting:… ids) or the full document text (for doc:… ids). Long records are truncated at a safe length with a marker.
WHEN: After the search tool, to read a result in full before answering or citing. WHEN NOT: Ids are opaque — never construct one by hand; only use ids returned by search.
EXAMPLES: { "id": "meeting:lehi-ut:1f0c…" } { "id": "doc:lehi-ut:1f0c…:42" }
A not-found error means that specific id is stale or unindexed — re-run search for fresh ids; it is not an access restriction.
Inputs: id (required). Returns: id, url, text, title, +1 more
find_cityFind the covered government for a placeARead-onlyIdempotentInspect
Which city or county government OpenCity covers for a place name or a street address, and whether it is in this account's cities. Returns the city_slug the other tools take. Discovery only: it returns no meeting records and never changes which cities this account can query.
WHEN: the user names a place or gives an address and you do not have its slug ('my council is in Lehi', 'I live at 153 N 100 E, Lehi'), or asks whether OpenCity covers somewhere. WHEN NOT: the slug is already known or in the cities list above — call the tool you need directly.
An address can be inside both a city and a county government; both come back. may_serve marks a town or township in the address's county whose boundary is not known: say it may serve the address, not that it does. When in_your_plan is false, do not call other tools for that city; tell the user it is covered but outside their account.
EXAMPLES: { "name": "Lehi, UT" } { "name": "Utah County" } { "address": "153 N 100 E, Lehi, UT 84043" }
Inputs: name, state, address. Returns: address, matches, plan_note, nearest_covered
get_meetingWhat happened at a meetingARead-onlyIdempotentInspect
One meeting as structured data: headline and summary, agenda items (with video jump links), every decision with its outcome and roll call, public comment (count and unnamed summaries), and the meeting's documents. No transcript.
WHEN: 'What happened at Tuesday's council meeting?', 'What did they decide on item 5.3?', or after any tool returns a meeting_id you want to explain. Also accepts an OpenCity meeting URL the user pasted. WHEN NOT: For the exact words spoken, use get_transcript_window. For decisions across many meetings, use whats_changed.
Decisions follow whats_changed's rules: outcome is at the item's stage ('Recommended approval' is not final), tally counts positions on the ITEM, and is withheld when votes_complete is false. Public commenters are never named.
EXAMPLES: { "meeting": "ad1f24aa-ee04-4ce3-bd71-ab6410429d6d" } { "meeting": "https://lehi.openutah.org/meetings/2026-10-13-city-council-ad1f24aa-ee04-4ce3-bd71-ab6410429d6d" }
Inputs: meeting (required). Returns: meeting, redacted, decisions, documents, +2 more
get_transcript_windowRead the transcript around a momentARead-onlyIdempotentInspect
The passage of a meeting transcript surrounding a given moment — what was said before and after, with speakers and timestamps. Pass the meeting_id and start_seconds from a search_meetings hit.
WHEN: ALWAYS, before quoting or reasoning from a search_meetings hit. Search returns individual segments, and a segment is often a fragment — one real hit was the entirety of "does not have a contract with FLOC". The answer is in the passage around it, not in the segment. WHEN NOT: To find something, use search_meetings first — this tool has no query and only reads a meeting you already located. For a whole document, use fetch.
Prefer this over fetch for transcripts: fetch truncates at 40,000 characters from the START of a meeting, so on a three-hour council meeting anything after roughly the first hour is missing entirely — including most late-agenda votes.
EXAMPLES: { "meeting_id": "977675c0-...", "around_seconds": 5113 } { "meeting_id": "977675c0-...", "around_seconds": 5113, "before_seconds": 300, "after_seconds": 300 }
Inputs: meeting_id (required), around_seconds (required), limit, after_seconds, +1 more. Returns: window, segments, city_slug, truncated, +6 more
issue_timelineIssue lifecycle timelineARead-onlyIdempotentInspect
How a local issue developed over time — a zoning change, a housing or density fight, a tax or fee, a road, park, or police matter. Returns the chronological lifecycle of the issue across meetings (raised → discussed → voted), oldest first, each entry linking to the meeting.
WHEN: 'How has the short-term-rental issue progressed?', 'When was X first discussed and what happened since?'. WHEN NOT: For what a specific person said, use search_by_official; for how they voted, use track_position. For a one-off keyword search, use search_meetings.
Pass city whenever you pass q. The same phrase names a different fight in every city, and without a city the match is ambiguous — you will get issue: null plus a candidates list to choose from rather than a confident answer about the wrong city. Check candidates even on a resolved issue: 'short-term rentals' matches both a citywide ordinance and one subdivision's area-plan amendment.
EXAMPLES: { "q": "short-term rentals", "city": "lehi-ut" } — ALWAYS pass city with q { "issue_id": "" }
Inputs: q, city, limit, issue_id. Returns: issue, timeline, candidates
list_officialsList elected officialsARead-onlyIdempotentInspect
Who serves on a city council — mayors and council members — looked up by city, state, or name.
Returns the CURRENT ELECTED roster by default: people in office right now, no appointed board members, no staff. Answer 'who represents me' straight from this — no need to hedge about whether someone still holds the seat. Each row carries role, status, and term dates.
WHEN: First stop for 'who represents…' questions, and the prerequisite for any search_by_official / track_position call (both need a UUID this tool returns). WHEN NOT: For voting records use track_position; for quotes use search_by_official, with an id from here.
Set include_former for historical questions ('who was mayor in 2024?') or when reading an older meeting whose speakers have since left office — an official_id you got elsewhere may belong to someone no longer seated. Set include_appointed for planning commissioners and city staff.
EXAMPLES: { "city": "lehi-ut" } — the sitting Lehi council { "city": "lehi-ut", "include_former": true } — plus everyone who has left office { "state": "UT", "name_contains": "binns" } — find 'Paul Binns' across Utah { "city": "lehi-ut" } then pivot to track_position with one of the returned ids
Inputs: city, limit, state, name_contains, +2 more. Returns: total, officials, citiesQueried
searchSearch city meetings and documentsARead-onlyIdempotentInspect
Anything about a city council, mayor, ordinance, zoning case, development permit, meeting agenda, or municipal budget. Combined keyword search across public city-government meeting transcripts AND meeting documents (agendas, minutes, packets, ordinances). Returns results with opaque ids — pass an id to the fetch tool for the full transcript or document text.
OpenCity URLs in the query are understood as scope, not text: a city site (taylorsville.openutah.org) narrows to that city, a state site (utahrecord.org, myopentexas.org) narrows to that state, and a full meeting URL resolves to that exact meeting. A bare URL with no other words returns that scope's most recent activity.
WHEN: General civic questions ('what has the city said about X?') from clients that auto-route through search/fetch (e.g. ChatGPT connectors), and whenever the user pastes an opencity/openutah/opentexas-style URL. WHEN NOT: If search_meetings / search_documents / list_officials are available to you, prefer them — they take filters (city, meeting type, doc type) and return structured fields this tool flattens away.
EXAMPLES: { "query": "short-term rental ordinance" } { "query": "parking taylorsville.openutah.org" } — 'parking', scoped to Taylorsville { "query": "utahrecord.org" } — recent activity across Utah cities
Empty results mean no records matched the query — never interpret them as missing access or permissions; retry with broader keywords instead.
Inputs: query (required). Returns: notice, results
search_by_officialSearch what an official said (paid)ARead-onlyIdempotentInspect
Retrieve every transcript segment a specific elected official has spoken. Optional q narrows within their segments. Returns their speaking history, newest first — what they argued, asked, and moved, in their own words.
WHEN: 'What has Councilmember X said about Y', their reasoning on an issue, or a verbatim quote with a jump-to timestamp. WHEN NOT: For how they voted, use track_position — that reads the recorded roll call (yes/no/abstain per decision). Transcripts capture discussion, not the tally, so compiling a voting record from these segments systematically under-reports and will miss votes entirely. If you don't yet have their UUID, call list_officials first. For non-elected speakers (staff, public commenters), use search_meetings.
EXAMPLES: { "official_id": "", "q": "housing" } { "official_id": "" } — full speaking history newest-first
REQUIRES: a paid plan (Resident and above). Free callers get an upgrade hint, no data.
Inputs: official_id (required), q, limit, cities. Returns: hits, total, citiesQueried
search_decisionsSearch recorded motions, votes and amountsARead-onlyIdempotentInspect
What a body actually DECIDED, from the approved minutes — the motion as recorded, who moved it, the roll-call tally, and any dollar figure attached. This is the structured record, not people talking: use it whenever the question is what was approved, denied, tabled, or how much something cost.
WHEN: 'Which cities approved Flock Safety cameras?', 'Show me every contract over $100k in Texas this year', 'Where did accessory-dwelling ordinances stall?', 'What did Lehi vote on last month?'. WHEN NOT: For what someone SAID, use search_meetings. For one official's record across items, use track_position. For a project's lifecycle, use application_timeline.
TWO THINGS THAT WILL MISLEAD YOU IF IGNORED:
• Report outcome, not result. A passed motion TO TABLE means the item did NOT advance; 161 published motions are exactly that. outcome already combines motion_kind with result.
• amount_deferred: true means the money was named inside a motion to delay or table. Never describe it as spending.
• EMPTY IS NOT "NOTHING WAS DECIDED". Motions come only from minutes we have extracted, which lag the meeting and are missing for many cities. Read coverage / coverage_note before answering, and fall back to search_documents for the minutes or agenda.
• unofficial_decisions are decisions heard in the RECORDING of a meeting where no official motion covers that item — usually because its minutes are not published yet. They are NOT the official record: say "according to the meeting recording" and repeat the row's record_note (it says whether the minutes are unpublished or simply have no matching motion), never present their vote_count as an official roll call, and never count them into total. Verify a specific one with get_transcript_window at its timestamp_seconds.
q matches whole words, so 'Flock' will not return a person named Flocke. Every row carries a citation_url pointing at that exact motion.
EXAMPLES: { "q": "Flock Safety" } { "q": "accessory dwelling", "outcome": "tabled" } { "min_amount_usd": 100000, "basis": ["award"], "since": "2026-01-01" }
Inputs: q, basis, limit, match, +5 more. Returns: total, blocked, summary, approved, +9 more
search_documentsSearch meeting documentsARead-onlyIdempotentInspect
What the city put in writing — agendas, minutes, staff reports, meeting packets, attachments, ordinances, and resolutions. Keyword search across extracted document text. Use this when the value is in the written record, not the spoken transcript.
WHEN: Looking for ordinance language, budget tables, agenda text, staff reports, attached PDFs.
WHEN NOT: For what was said in the meeting, use search_meetings. For metadata-only queries ('what meetings happened last week?'), search_meetings with q on a placeholder works better.
EXAMPLES: { "q": "ADU ordinance", "cities": ["lehi-ut"] } { "q": "FY2027 budget", "cities": ["lehi-ut"] } { "q": "developer presentation Main Street" }
hits: [] means no document text matched — simplify the query and retry; never report it as missing access to a city.
Inputs: q (required), limit, cities, doc_types. Returns: hits, total, citiesQueried
search_meetingsSearch meeting transcriptsARead-onlyIdempotentInspect
What was actually said out loud at a city council meeting, planning-commission hearing, or any other public city-government meeting — about zoning, housing, the city budget, taxes, permits, ordinances, roads, parks, police, or public comment. Keyword search across transcript segments; each hit includes speaker, snippet, timestamp, and — when the speaker matches an elected official — an official_id UUID.
WHEN: 'What did people say about X?', 'When was Y last discussed?', or any query where the value is what was spoken in a meeting. WHEN NOT: For one person's complete speaking history, use search_by_official (better signal, no false positives from name collisions). For ordinance text, packets, or attached PDFs, use search_documents.
For 'recent' or 'this year' questions pass since — the corpus runs back years, and without a window old material competes with new. For 'what did OFFICIALS say' pass officials_only — it keeps segments whose speaker matched the city roster (elected, appointed or senior staff), because everyone else is unnamed by design and their quotes cannot be cited to a person.
EXAMPLES: { "q": "short-term rentals", "cities": ["lehi-ut"] } { "q": "housing affordability", "cities": ["lehi-ut"], "since": "2026-01-01", "officials_only": true } { "q": "downtown parking", "cities": ["lehi-ut"], "meeting_types": ["city_council"] } { "q": "developer Main Street" } — last meetings across allowed cities, newest first
hits: [] means no transcript segments matched — simplify the query and retry; never report it as missing access to a city.
Inputs: q (required), limit, since, until, +3 more. Returns: hits, total, citiesQueried
track_positionTrack an official's position over timeARead-onlyIdempotentInspect
The voting record of one elected official. Their actual recorded roll-call votes (yes/no/abstain, per decision, with the decision title) plus expressed stances, in chronological order, with cited quotes, jump-to timestamps, and detected position reversals (flips).
This is the ONLY tool that returns how someone voted. topic and the since/until date window are all optional — omit them all for a complete record.
WHEN: any question about how a named official voted — 'How has Councilmember X voted over the last couple months?' (pass since/until, no topic), 'How has X voted on housing?' (pass topic), 'Has the mayor changed position on short-term rentals?'. WHEN NOT: For what they said rather than how they voted, use search_by_official. For a topic across everyone (not one person) use whats_changed or search_meetings; to find the official_id first, use list_officials.
Do NOT try to reconstruct a voting record from transcript segments — transcripts capture discussion, not the tally, so that reliably under-reports. Votes live here.
PAID plan required (attributes positions to a named official).
EXAMPLES: { "official_id": "", "since": "2026-06-01" } — recent voting record, all topics { "official_id": "", "topic": "housing" } { "official_id": "" } — the complete record
Inputs: official_id (required), city, limit, since, +2 more. Returns: topic, summary, official, trajectory
upcoming_meetingsUpcoming meetings and agendasARead-onlyIdempotentInspect
What a city has scheduled next — council, planning commission and board meetings in the coming days, with start time, location, how to comment, and the agenda where one is posted (headline and items when summarized).
WHEN: 'What's on the agenda next week?', 'When does the planning commission meet?', 'Is the council voting on X soon?'. WHEN NOT: For what already happened, use whats_changed or search_meetings. For the full text of a posted agenda, use search_documents or open agenda_url.
Most meetings are posted before their agenda: agenda_status: 'not_posted' means the city has not published it yet, not that the meeting has no business. A cancelled meeting is listed so you can say so.
EXAMPLES: { "city": "lehi-ut" } { "city": "lehi-ut", "days": 30, "meeting_types": ["planning"] }
Inputs: city, days, limit, meeting_types. Returns: period, meetings, truncated
whats_changedWhat changed since a dateARead-onlyIdempotentInspect
What the city council actually decided recently — the changeset of council activity in a city since a date: decisions taken (with tallies where a roll call was recorded), issues discussed, development-application status changes, and — on paid plans — per-member votes and stance reversals. Free plans get the aggregated changeset without member attribution (redacted: true). Most decisions are unanimous voice votes with roll_call: false and no tally — they are still decisions; report them.
WHEN: 'What did the council do in the last two weeks?', 'Any movement on development since June?'. WHEN NOT: For one person or one issue over time, use track_position / issue_timeline.
EXAMPLES: { "city": "lehi-ut", "since": "2026-06-21" }
Inputs: city (required), since (required), limit. Returns: period, redacted, decisions, truncated, +4 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
Related MCP Connectors
Search US local-government meetings: AI summaries, transcripts, and adopted plan documents.
Public records for every city: permits, alerts, wildfires, laws and funding, linked to the source.
Council/county meeting transcripts with agenda timestamps + official votes, $0.008/audio min.
The public record of local government: what your officials actually say and do, word for word.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceOpen & historical US government bid solicitations from city/county portals — updated daily.147 npm2MIT
- AlicenseNot gradedqualityBmaintenanceEnables searching and browsing municipal resolutions, meetings, votes, and member voting records through tools for full-text and semantic search, details, statistics, and attendance/voting history.MIT
- AlicenseNot gradedqualityDmaintenanceProvides programmatic access to municipal codes, ordinances, and zoning regulations from thousands of US cities and counties through the Municode digital library, enabling search, navigation, and retrieval of local government legal documents.6MIT
- FlicenseNot gradedqualityDmaintenanceA read-only MCP server for querying Los Angeles City legislative data - Council Files, votes, member activity, and Neighborhood Council engagement - through parameterized tools without raw SQL.1-
Glama MCP Gateway
Add one secure layer between your agents and this server.