⚠️ Alpha, v0.1.0 — early code, shared to find out what breaks. Tool shapes can change without notice. Tell us what's wrong
MCP, set in white on a blue circle
BetaNYC · civic tech, open source

mta-mcp

An MCP server that answers one question well: is the subway line we recommend disrupted on that date, at that station?

Every demo below is badged. Verified 2026-09-21 means that exact call was run and that is what came back. Illustrative means it shows the shape of an answer, not live values.
MTA's feed moves. A verified panel is a photograph, not a window.

Start by conceding the easy case

If your question is "is the L running right now," close this page. Open mta.info or any transit app. They are free, official, and faster than anything here. This tool would be a worse answer.

It earns its place on a narrower question, and the narrowness is the point: a date in the future, a specific station, and a yes or no you are willing to put in an email to 3,000 people. That question is tedious by hand and easy to get confidently wrong.

Simple first: which station did you mean?

"Which station is 125 St?" Verified 2026-09-21
By hand
  1. Search "125 St subway station."
  2. Get four different stations back, on four unrelated lines.
  3. Work out which one your event is near.
  4. Find its GTFS id if you need to check anything programmatically.
Answerable, but you have to already know the system.
mta-mcp
match_count
4
unambiguous
false
stop_idroutes
1161
2252, 3
6214, 5, 6, 6X
A15A, B, C, D
It refuses to guess. 193 of 496 stations share a name with another. Pass route_id to narrow it, or pick a stop_id.
Why this one never goes stale. resolve_station reads a station list generated from MTA's static GTFS and committed to the repo. It makes no network call, so this panel will read the same next year unless MTA renames a station.

Harder: "affected" does not mean "worse"

This is the demo worth your time. It is a real trap, and the obvious implementation walks straight into it.

"Is the 4 train disrupted at 68 St–Hunter College on Sat Sep 26?" Verified 2026-09-21
By hand
  1. Open mta.info/alerts. Filter to the 4, set the date to 09/26.
  2. Read the results. Route names render as images, so a copy-paste or a screen reader loses which train each alert is about.
  3. Find "4 runs local between 125 St and Grand Central." Your station is on that stretch, so it is mentioned.
  4. Decide what "mentioned" means. Is local service better or worse for you?
  5. Notice a second alert with no station list at all, and decide whether it matters.
Two judgment calls, and step 4 is where people get it wrong.
mta-mcp
disrupted
true
alert_count
2
station_level_detail
false
alerteffectcounts as disruption
Planned - Express to Local
tags your station
added_at_local_stopsno
Planned - Stops Skipped
names no stations
reducedyes
Both behaviors, one answer. The express-to-local alert tags 68 St and is not counted as a disruption, because running local means more trains stop there. The second alert names no stations, so the tool will not claim your station is clear. It fails toward caution and says which alert caused it.
The trap, stated plainly. A check that asks "is my station mentioned in any alert?" reports a disruption here that does not exist. The alert mentioning 68 St is added service. Every existing NYC subway MCP we surveyed collapses this into one generic "service change" bucket. Classification is on MTA's alert_type, never on station mention.

What it cannot do

Knowing which questions a tool cannot answer is part of using it.

Not thisWhy
"When is the next 6 train?" No arrivals or trip updates. Those are protobuf-only feeds and would pull in three custom .proto files for a feature this tool does not need.
"Is the M15 bus delayed?" Bus Time needs an API key and is out of scope. Bus alerts are in the same keyless family and could be added.
"Are Times Sq and Port Authority connected?" Stations linked only by transfer, under different names, are not modeled. 60 such pairs exist. Asking about one name will not surface alerts filed against the other.
"Is my station definitely fine?" MTA tags stations only when it considers the change significant. Every response carries station_level_detail so a quiet answer is never mistaken for a guarantee.

Why this is not on npm

BetaNYC publishes seven MCP servers to npm. This one is deliberately not among them, and the reason is worth more than the code.

MTA's data feed terms, term 1:

"In developing your app, you will provide that the MTA data feed is available to others only from a non-MTA server. Accordingly, you will download and store the MTA data feed on a non-MTA server which users of your app will access in order to obtain data. MTA prohibits the development of an app that would make the data available to others directly from MTA's server(s)."

A published package makes every installer's machine fetch straight from api-endpoint.mta.info, with no server of ours in between. There is a tempting counter-argument, that each user is simply fetching for themselves. It does not survive the clause's own wording: the non-MTA server is defined as the one "which users of your app will access in order to obtain data." It names the exact party the counter-argument tries to carve out.

So package.json sets "private": true, and the decision is enforced in code rather than in a comment somebody later deletes.

The same terms bind the output. No claim that the data is accurate, complete, or timely. A visible as-of timestamp on anything cached. And MTA route bullets are licensed intellectual property, separate from the free data terms. So this page and every response write "the 6 train" rather than reproducing a route indicator. The circle at the top of this page is not one: there is no MCP train, and no MTA mark is reproduced in it.

Running it

⚠️ Read this before you install it

This is v0.1.0 alpha, in active development, and it has not been used by anyone outside BetaNYC. We are sharing it this early on purpose, to find out what breaks. Tool names, parameters, and response shapes can change without notice. Do not build anything you care about on it yet.

Found something wrong? Open an issue. That is the point of putting it out this early.

Clone and build. Node 20 or newer. No API key is needed for any feed it uses.

git clone https://github.com/BetaNYC/mta-mcp.git
cd mta-mcp
npm install
npm run build

Then point an MCP client at the build. For Claude Code, in .mcp.json:

{
  "mcpServers": {
    "mta-mcp": {
      "command": "node",
      "args": ["${HOME}/Code/mta-mcp/dist/index.js"]
    }
  }
}

Being gentle with a public feed

MTA publishes no rate limit, no refresh cadence, and no cache headers, so politeness cannot be negotiated with the server. Six mechanisms impose it instead, all on by default: a 60-second response cache, single-flight de-duplication, a process-wide one-second floor between requests, bounded retry that honors Retry-After, a 15-second timeout, and a User-Agent that identifies the project.

There is no background polling, no prefetch, no warm-up fetch. It fetches only when a tool is called. The test suite makes zero network requests, so continuous integration never touches MTA at all.

What we could not verify

Other NYC subway MCP servers

This is not the only one, and for most questions it is not the one you want. Several of these are further along, and all of them do live arrivals, which this server does not do at all. If your question is "when is the next train," start here rather than with us.

ProjectLanguageWhat it does that this one does not
where_is_my_train_mcp
sasabasara · also on Smithery
TypeScript The broadest of these. Live arrivals with crowding where available, fuzzy station search, elevator status, transfers, and nearby-station lookup by latitude and longitude.
nyc-subway-mcp-server
hardparking
TypeScript Real-time arrivals, live vehicle positions, and a line-status overview. Runs over HTTP as well as stdio.
metro-mcp
Aarekaz
TypeScript Covers DC Metro as well as NYC. Anonymous, read-only, deployed on Cloudflare Workers, so there is nothing to install.
mta-mcp
nkasmanoff · same name as ours, no relation
Python Next-train arrivals by station and direction. Small and readable, which makes it a good first thing to look at if you are learning how these fit together.

What this server does that they do not: take a date. Every one of the above answers about now. None of them accepts a future date, and none of them separates service that got worse from service that got better at a station. That gap is the only reason this exists.

Community

Three different questions, three different places. Sending one to the wrong place mostly wastes your time, so here is the routing.

Your questionWhere it goes
A tool returns the wrong shape, a schema is wrong, this server crashes Our issue tracker
Especially while this is alpha. Include the entity_id from the response if it is an alert-matching question; it makes the alert findable in the raw feed.
A feed field is undocumented, an alert looks wrong, MTA's API behaves oddly MTA Developer Google Group
MTA has no dedicated developer support team but actively monitors this group. Asking there rather than in our tracker gets you a better answer and helps everyone else building against the same feeds. It is also where we would take the two open questions in "what we could not verify" above.
You want to build civic tech in New York with other people BetaBuilders
BetaNYC's community, below.

BetaBuilders

In BetaNYC's own words, "a BetaBuilder is a New Yorker who sustains the city's civic infrastructure." The BetaNYC Discord is where classes, fellowships, and open data work get planned, where projects and job openings get posted, and where a tool like this one gets argued about before it is any good.

Membership is earned rather than bought, and there are several doors:

Most of it never goes behind a membership. The weekly newsletter, public events, classes, every open data resource, and all the research stay free. That includes this server. Membership buys the room, not the work.

Read the full thing and pick a door →

Credits

Data from the MTA service alerts feed, free to use under MTA's terms. Station names from MTA's published static GTFS. MTA's own implementation notes live at nymta/gtfs-documentation, which is sparse but is the only first-party source for how these feeds actually behave.

About BetaNYC

This 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.

Where to lookWhat is there
About BetaNYC Who we are, the team, and how the organization is structured.
Our work Programs, research, and the open-data work this tool exists to support.
Events calendar What is coming up. Attending a class is also one of the doors into BetaBuilders.
NYC School of Data Our annual open-data conference, free and open to the public.
CityCamp NYC The unconference where New Yorkers and city government work on civic problems together. Getting people there is the reason this server was built.
github.com/BetaNYC Everything else we publish, including seven MCP servers for NYC civic data that are on npm.

These tools are free and open source. If you want to help keep the work going, donating is the most direct way.

Link note. Every outbound link on this page was HTTP-checked on 2026-09-21. The mta.info and claude.ai links return 403 to a command-line request because of bot protection, not because they are dead; both were confirmed in a real browser. MTA redirects www.mta.info to mta.info, so the bare form is used here.

AI disclosure. This server was largely written with Claude, and this page with it too. Every response shape shown here was observed in a real call rather than invented, which is the repository's own standing rule and the reason the badges exist.