Demo script — Council District [NN] ([MEMBER NAME])

TEMPLATE. Copy this file to district-[NN].md and fill every [BRACKETED] placeholder. A run-it-live script for showing BetaNYC’s grounded AI + NYC/NYS open data MCPs to Council Member [MEMBER NAME] and staff. District [NN] covers [NEIGHBORHOODS].

Companion to the general ../user-journeys.md catalog, framed for a Council audience: their district’s data, their own legislation, their discretionary funding, their oversight.


Before you start (2 min)

Opening line:

“I’m going to ask an AI the questions your office fields every week — but this one is wired into the actual city and state record. Watch where the numbers come from.”


Act 1 — Your district, right now (3 min)

Prompt:

“What were the top 311 complaint types in Council District [NN] over the last 30 days?”

What to say: “Live 311 data through BetaNYC’s 311 MCP — the same complaints your constituents file every day, summarized in one question.”


Act 2 — Grounded vs. ungrounded (2 min)

Prompt:

“What legislation has Council Member [MEMBER NAME] introduced in the last year, and where is each bill in the process?”

What to say: “An ordinary chatbot will invent bill numbers or give you last term’s list. This one reads the Council’s own Legistar system — current sponsors, current status, real Intro numbers you can cite.”


Act 3 — Your legislation meets your funding (4 min)

The act a Council audience cares about most. Make it about them.

Prompt (step one — the allocation):

“Show the FY[YYYY] NYC Council discretionary awards designated by council member [SURNAME].”

Prompt (step two — the check):

“Now check NYC Checkbook for FY[YYYY] spending paid to [LARGEST RECIPIENT ORG].”

What to say: “It just moved from where the money is allocated — the discretionary budget — to where it’s actually spent, in Checkbook. That’s the reconciliation your office does by hand, in one question.”

Look for a Tax Levy Elected Officials budget code in the Checkbook results. That is discretionary money appearing as an actual disbursement, and calling it out by name is the strongest moment available in this act.

⚠️ Ask by member, never by district number. The budget tool (search_disclosure_awards, the Council’s discretionary funding disclosure) keys on the member and has no district filter; it rejects council_district by name. Use council_member_id (the Legistar person ID) for exactly one person. Quote the Cleared total, with Pending as a smaller second number: the disclosure is a later snapshot than the adopted budget’s Schedule C, so it includes designations made after adoption, each marked Cleared or Pending. See the parameter table in the presenter-notes section below.


Act 4 — An issue you’re working on, sourced (4 min)

Ask the office for a live priority, or pre-fill one here.

Prompt:

“For Council District [NN] and the issue of [ISSUE]: pull recent 311 data that quantifies it, find any related NYC Council legislation and upcoming hearings, and check NYS Open Legislation for a state bill on the same topic. Give me a short, sourced brief.”

What to say: “Every line traces to an official source — city data, the Council’s own system, and Albany’s. If staff challenges a number, the agent shows where it came from. Grounded, not improvised.”


Closing (1 min)

“BetaNYC built these connectors and guides in the open. Any office can point its own AI at the real city and state record instead of trusting it to remember. If there’s a question your team asks constantly, that’s exactly the kind of user journey we want to add.”

Leave-behind links:


Presenter notes (verified [YYYY-MM-DD])

Dry-run every prompt above against the live MCPs before the meeting, and record what actually happened here. A script without verified notes is a draft, not a demo. Delete this instruction line once you’ve filled the section in.

✅ Fixed 2026-07-21 — the BetaNYC MCPs now reject unknown parameters

This used to be the most dangerous thing in this document. All seven servers accepted parameters that were not in their schema, silently dropped them, and returned unfiltered results with no error — real, correctly formatted data answering a different question. A guessed parameter name produced plausible garbage and nothing in the response revealed it.

Now shipped. As of 2026-07-21 every BetaNYC server rejects an undeclared parameter, and six of the seven also name the parameters they do accept. (nyc-checkbook-mcp is the exception: outside search_contracts it returns zod’s bare “Unrecognized key(s)” JSON with no accepted-parameter list. If you demo a refusal deliberately, use the budget or charter server.) Minimum versions carrying the fix:

nyc-budget-mcp 2.0.0 (the fix arrived in 1.3.0, but ⚠️ 2.0.0, 2026-10-18, is a breaking change and the minimum to use today: the award tool is now search_disclosure_awards; search_awards and the other Schedule C tools are gone) · nyc-council-mcp 2.5.0 · nyc-checkbook-mcp 1.4.0 · nyc-record-mcp 1.1.0 · nyc-311-mcp 1.1.0 · nyc-charter-laws-rules 0.2.0 · nys-openlegislation-mcp 2.3.0

Verified live against the published packages. A wrong parameter name now produces, e.g.:

search does not accept 'council_district'. Accepted parameters: query, corpus, limit.
Unrecognized parameters are rejected rather than ignored: dropping one would return
real, correctly formatted results for a different question.

Confirm your versions before presenting — run /mcp-update, and remember these are consumed via npx -y, so a stale npx cache can still serve an old build. If you are on an older version, the silent-drop behavior above is still live and the table below is a safety requirement rather than a convenience.

This is worth demoing deliberately. Asking for something the tool cannot do, and having it refuse by name, is a stronger trust argument than any successful query. Socrata is a third-party server and is NOT covered — its rows below still drop silently.

Verified-correct parameter names (re-verified 2026-07-21):

Task Correct parameter Do not use If you get it wrong
Discretionary awards by member council_member_id="[LEGISTAR ID]" (exact), or council_member="[SURNAME]" (substring) council_district, sponsor rejected by name (fixed)
Checkbook payments to an org payee_name="[ORG NAME]" vendor rejected by name (fixed)
Socrata aggregate query separate select / where / group / order / limit a single soql blob ⚠️ still silently ignored — third-party
311 by council district council_district='04' — zero-padded string '4' ⚠️ still returns zero rows silently — this is a data-value trap, not a parameter one, so the fix does not catch it

⚠️ limit can truncate silently — now in get_voting_record, not the budget tool

The budget tool used to compute its total over only the rows it returned, which put two understated dollar figures into finished demo scripts in July 2026. search_disclosure_awards (budget MCP 2.0.0) reports true totals over every match, whatever the limit.

get_voting_record is where the trap lives now. It defaults to 50 rows, newest first, and returns no total. A July 2026 script reported “46 matters, two sessions” for a member who had 459 by October; the 50-row default is the likely cause. The rule: pass limit=1000 or higher, dedupe by file_number, and treat returned count == limit as truncated until you re-run higher. A returned count that equals the limit is not a result, it is a warning.

Note the strict-parameter fix does not help here. limit is a valid parameter receiving a valid value; there is nothing for a schema to reject. Schema strictness catches wrong names, never wrong numbers.

⚠️ 311 counts are a moving 30-day window

Any “N complaints in the last 30 days” figure is perishable by construction and will not reproduce on a later date. That is not an error — it is the query. Re-run it the morning of the meeting and read the fresh number, rather than reciting a figure verified weeks earlier.

⚠️ Two more silent traps, both verified 2026-07-21

Zero-padding. In the 311 dataset (erm2-nwe9), council districts are zero-padded strings. council_district='4' returns zero rows with no error; '04' returns thousands. This affects districts 1–9 only — a two-digit district will never reveal the bug, so a script that worked for District 10 will fail silently for District 4.

Surname substring collisions — the tool now tells you. council_member matches as a substring, so council_member="Powers", fiscal_year=2026 returns both Keith Powers (7741) and Selvena Brooks-Powers (7796). Budget MCP 2.0.0 names every person matched and splits their totals. Read that note before quoting any figure, state the fiscal year, and use council_member_id for exactly one person. Background: New-York-City-Budget#38.

Also verified — do not misread these as findings

Carried-forward gotchas — confirm these still hold on your dry run

Figures and their provenance

Record every number you plan to say out loud, and how you got it:

Claim How verified
[figure] [tool + parameters]

Also record what you did not verify. If you are unsure of the district’s neighborhood composition, say so here rather than reciting a list to a room that knows the district better than you do.


Backup prompts (if one falls flat)

If asked “can it be wrong?” — Yes, and that’s the honest answer: the agent can misread, or a dataset can be stale. The safeguard is that every answer is traceable to a source you can check, which an ungrounded chatbot can’t offer. Grounding doesn’t make it infallible; it makes it accountable.