Skip to content
Conduit

mcp server

nys-openlegislation-mcp

MCP server for New York State legislation via the NYS Open Legislation API

BetaNYC/nys-openlegislation-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "nys-openlegislation-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@betanyc/nys-openlegislation-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 25, 2026

@betanyc/nys-openlegislation-mcp

An MCP server for the NYS Open Legislation API, maintained by the New York State Senate. Gives AI assistants direct access to the full NYS legislative record — bills, laws, members, committees, calendars, agendas, and transcripts — covering 150,000+ bills and resolutions dating back to 2009.

Built by BetaNYC as part of a suite of civic data MCP servers for New York.


What it covers

ResourceTools
BillsSearch, get by print number, list by session, get votes, track updates
LawsList all law bodies, browse law trees, retrieve section text
MembersList and search Senate and Assembly members
CommitteesList committees, get details and meeting history
Floor CalendarsList and get Senate floor calendars
AgendasList and get committee agendas with vote records
TranscriptsFloor session and public hearing transcripts
UpdatesAggregate change feed across all content types
SearchFull-text ElasticSearch within a content type (bills, laws, agendas, calendars, transcripts, hearings)

API key

A free API key is strongly recommended, and required for live data. Get one from the NYS Open Legislation portal and set it as the NYS_LEGISLATION_API_KEY environment variable:

  1. Register at legislation.nysenate.gov/public
  2. You'll receive an API key by email
  3. Set it as NYS_LEGISLATION_API_KEY (e.g. export NYS_LEGISLATION_API_KEY="your-api-key"), or pass it in your MCP client's env block — see Installation

Without a key, the server still starts as long as a local corpus exists, and serves it in local-only mode. Results are labelled with the corpus sync date and a note saying what refreshes them. The 5 tools with no local coverage (get_bill_votes, get_bill_updates, get_committee_meetings, get_updates, search) return a message naming the key rather than failing opaquely, and a lookup that finds nothing in the corpus says so explicitly — offline, "not in the corpus" and "does not exist" are not the same claim.

The server refuses to start only when both the key and the corpus are missing.

Bill URLs returned by this server point to the public nysenate.gov website — no login required.


Installation

Requires Node.js 20 or newer.

Use with Claude Desktop (recommended)

No global install needed. Add to your Claude Desktop config and npx handles the rest.

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "nys-openlegislation": {
      "command": "npx",
      "args": ["-y", "@betanyc/nys-openlegislation-mcp"],
      "env": {
        "NYS_LEGISLATION_API_KEY": "your-api-key-here"
      }
    }
  }
}

Restart Claude Desktop. The server starts automatically.

Global install

npm install -g @betanyc/nys-openlegislation-mcp

Then in your MCP config:

{
  "mcpServers": {
    "nys-openlegislation": {
      "command": "nys-openlegislation-mcp",
      "env": {
        "NYS_LEGISLATION_API_KEY": "your-api-key-here"
      }
    }
  }
}

Local corpus (optional)

By default the server queries the live NYS Open Legislation API on every request. If you run large workloads — bulk research, repeated searches, or offline use — you can build a local SQLite corpus that the server queries first, falling back to the API only on a cache miss.

The corpus is also what makes the server usable without an API key: with a corpus present and no key, it starts in local-only mode instead of exiting. Building the corpus in the first place does require a key (npm run sync calls the live API), so this is a "borrow a colleague's corpus" or "key expired" path, not a way to skip registration entirely. The corpus location defaults to data/corpus.db in the package and can be overridden with NYS_CORPUS_DB.

Requirements

The fetch and sync scripts (scripts/) are not included in the npm package — building a local corpus requires a git clone:

git clone https://github.com/BetaNYC/nys-openlegislation-mcp.git
cd nys-openlegislation-mcp
npm install

better-sqlite3 is an optional dependency. The server works without it — it just means every request goes to the live API. If its native build was skipped, install it explicitly with npm install better-sqlite3.

1. Fetch the initial corpus

NYS_LEGISLATION_API_KEY=your-key node scripts/fetch-data.js

Options:

FlagDefaultDescription
--start-year=YYYY2009Earliest session year to fetch
--types=bills,laws,...all typesComma-separated list: bills,laws,members,committees,agendas,calendars,transcripts
--include-law-textoffFetch full text of every law section (large — adds significant download time)
--include-transcript-textoffFetch full text of floor and hearing transcripts (very large)
--delay-ms=N150Delay between API calls in ms
--dry-runoffPrint what would be fetched without writing

The corpus lands in data/corpus.db (override the location with the NYS_CORPUS_DB environment variable — both the server and the scripts honor it). A full fetch without law or transcript text takes 2–6 hours depending on your start year and network speed. The --types flag lets you fetch only what you need.

2. Run incremental sync (nightly)

NYS_LEGISLATION_API_KEY=your-key node scripts/sync.js

Options:

FlagDefaultDescription
--from=YYYY-MM-DDTHH:MM:SSlast sync timeOverride the start datetime
--delay-ms=N150Delay between API calls in ms
--dry-runoffPrint what would be synced without writing

Cron example (nightly at 2am):

0 2 * * * NYS_LEGISLATION_API_KEY=your-key node /path/to/scripts/sync.js >> /var/log/nys-sync.log 2>&1

How local-first works

When data/corpus.db exists and better-sqlite3 is available, every tool handler queries the local DB first. If the record is found locally, it returns immediately without an API call, annotated with "source": "local corpus (synced <date>)" so staleness is visible. If the record is not found — or the local result is empty (0 items) — it falls through to the live API transparently; an empty local slice never shadows live data.

Law section text and transcript full text fall back to the API unless --include-law-text / --include-transcript-text were used during the initial fetch.

Known limitations

  • Committees go stale between full fetches. The incremental sync consumes the aggregate updates feed, which only carries AGENDA / BILL / CALENDAR / LAW content types — committee changes never appear in it. Re-run fetch-data.js --types=committees periodically (it is a quick fetch) to refresh committee data.
  • Law and transcript text are not incrementally synced; re-run fetch-data.js for those periodically.
  • Member full names come from bill sponsor records already in the corpus, filled in by npm run sync and fetch-data.js (no extra API calls). A corpus built before this change gains the members.full_name column on its next sync. A member who has never prime-sponsored a bill in the corpus has no full name locally.
  • Local results carry no total. Paginated results served from the local corpus return items and size only; the total, offsetStart, offsetEnd and limit fields come from the live API.

Tools

Every tool rejects parameters it does not declare, rather than ignoring them. Passing an undeclared key returns an error naming both the offending key and the parameters that tool accepts. This is deliberate: silently dropping a filter returns real, correctly-sourced data that answers a different question, and nothing in the response would signal it.

Bills

ToolDescription
search_billsFull-text bill search with ElasticSearch syntax, optional session year filter (omit it to search all sessions; all sessions in the local corpus, when it answers)
get_billGet a specific bill by print number (e.g. S1234) and session year
list_billsList bills introduced in a session year
get_bill_votesGet committee and floor vote records for a bill (the votes block of the bill record)
get_bill_updatesFeed of bill changes within a date range

Laws

ToolDescription
list_lawsList all NYS law bodies (Education Law, Labor Law, etc.)
get_law_treeGet table of contents for a law body
get_law_sectionGet text of a specific law section

Citation links. Law records from get_law_section, and search results of type: "laws", carry a nysenate_url field: a link a person can open on nysenate.gov. It is populated only for SECTION and ARTICLE documents, the two shapes checked in a browser on 2026-08-02 (…/laws/EDN/6233, …/laws/EDN/A125). It is null for every other type, including CHAPTER and TITLE, because their URL shape has not been verified, and a wrong link is worse than none. If the field is populated, use it. If it is null, cite the law and section in plain text. Note that www.nysenate.gov returns 403 to curl, so check a new doc type in a browser before adding it.

Members

ToolDescription
list_membersList Senate or Assembly members for a session year
get_memberGet a specific legislator by member ID; errors if the member sits in the other chamber from chamber
search_membersSearch legislators by name; every row carries chamber, and fullName where known (a locally served member who has never prime-sponsored a bill has none). Defaults to the current session. Served from the local corpus when present (works without a key)

Committees

ToolDescription
list_committeesList Senate or Assembly committees
get_committeeGet committee details — chair, schedule, location
get_committee_meetingsGet meeting history and bills considered

Calendars & Agendas

ToolDescription
list_calendarsList Senate floor calendars for a year
get_calendarGet a specific floor calendar with bill lists
list_agendasList committee agendas for a year
get_agendaGet a specific agenda with vote records

Transcripts

ToolDescription
list_floor_transcriptsList Senate floor session transcripts
get_floor_transcriptGet a specific floor session transcript
list_hearing_transcriptsList public hearing transcripts
get_hearing_transcriptGet a specific hearing transcript

Updates & Search

ToolDescription
get_updatesAggregate change feed for a date range. type selects the timestamp the range filters on (processed or published); content_type restricts to bills, agendas, calendars, or laws
searchFull-text search within one content type — bills (default), laws, agendas, calendars, transcripts, hearings. One type per call; the upstream API has no unified search endpoint. Search resolutions via type: "bills".

Example queries

Once connected to Claude Desktop, you can ask:

  • "Find NYS bills about congestion pricing introduced in the 2025 session"
  • "Get the vote record for Senate bill S1234 in the 2025 session"
  • "What committees does the NYS Senate Finance Committee oversee?"
  • "Show me the text of Section 701 of the Education Law"
  • "What NYS legislation changed this week?"
  • "Search for bills sponsored by Krueger about climate"

Session years

NYS legislative sessions run in two-year cycles beginning in odd-numbered years. The current session is 2025 (covering 2025–2026). When no session year is specified, tools default to the current session.


Data disclaimer

Results are sourced from the NYS Open Legislation API, maintained by the New York State Senate. Bill text, status, vote records, and law content reflect official legislative data but may be subject to correction or amendment. Verify critical information at legislation.nysenate.gov.


Tests

Two suites, split so that network access is a deliberate choice rather than a side effect of your environment.

npm test        # offline, always — safe to run reflexively
npm run test:live   # real calls to the NYS Open Legislation API; needs a key

npm test covers everything under test/. It never touches the network, regardless of whether NYS_LEGISLATION_API_KEY is set — that is enforced by a directory boundary (test/live/ is outside the default glob) and guarded by test/offline-by-default.test.js.

npm run test:live runs the smoke tests in test/live/, which make one read-only GET per searchable type. Without a key it fails with setup instructions rather than skipping, because reaching it means you asked for it.

This split exists because the previous arrangement gated the live test on the API key being present, which meant a machine configured to run this server also made live requests on a plain npm test. Having a key is not the same as intending to use it.


Releases

Releases are automated by .github/workflows/release.yml:

  1. Bump version in package.json in a PR (with a matching CHANGELOG.md entry) and merge to main.
  2. Tag the merge commit v<version> (e.g. git tag v2.0.1 && git push origin v2.0.1).
  3. The workflow runs the test suite, verifies the tag matches package.json, publishes to npm with provenance, and creates a GitHub Release with generated notes.

See CHANGELOG.md for version history. Publishing requires the NPM_TOKEN org secret (an npm token with publish rights to the @betanyc scope).


About BetaNYC

This project is built and maintained by BetaNYC, New York's civic technology and open-data community. We work to improve lives in New York through civic design, technology, data, and public-interest technology.

Come do civic tech with us. We run public events, meetups, and hands-on data classes throughout the year — including NYC School of Data and CityCamp NYC, and we host frequent civic-tech gatherings. See what's coming up on our events calendar.

Sustain this work. These MCP servers are free and open source. To help keep this work going and find BetaNYC's tools, please consider donating and becoming a Beta Builder.

Building on this? Tell us!

If you build something with this project, we'd love to hear about it. We can help other New Yorkers find it. BetaNYC publishes a weekly newsletter, This Week in NYC's Civic Technology and Open Data.

Related BetaNYC MCP servers

BetaNYC maintains a suite of open-source MCP servers for NYC and NYS civic data. See the full directory, with install details for each, at beta.nyc/ai-tools.

This server pairs directly with:

  • nyc-council-mcp: compare New York State legislation with its New York City legislative counterpart.

Support our work

Freedom isn't free. Support BetaNYC.

License

MIT © BetaNYC

↑↓ navigate openesc close