⚠️ Alpha, v0.1.0. Early code, shared now so we can find out what breaks. Tool names and response shapes may change without notice, so please don't build anything important on it yet. Tell us what's wrong
MCP, set in white on a blue circle
BetaNYC · civic tech, open source

mta-mcp

Before every BetaNYC event, we check the trains and elevators on the way there, so nobody who uses a wheelchair gets sent to a station whose elevators are out. We want to know whether the train we're telling people to take is running that day, at that station. Checking by hand is slow and easy to get wrong, so we built an MCP server that lets an AI assistant like Claude do it.

You ask something like "Is the 6 train running to 68 St–Hunter College on Saturday, October 3?" It reads MTA's service alerts and elevator outage feeds and tells you what affects that station on that date. It's an unofficial tool. BetaNYC is not the MTA.

When to use something else

If your question is "is the L running right now," use mta.info or any transit app. They're free and official, and they show what's happening now.

This server is for planning ahead: a future date, a specific station, and an answer you can put in the email to everyone coming. For live arrivals inside an AI assistant, try one of the other NYC subway servers further down.

Each example below is labeled. Verified 2026-09-21 means we ran that call live that day. Saved feed means we ran it against copies of MTA's feeds saved in the repo, dated on the panel. Illustrative shows a call Claude might make, not one we ran.

When an alert means more trains

An alert can mention your station because service there got better.

"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.
Steps 4 and 5 are judgment calls, and getting step 4 wrong gives you a false alarm.
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
The express-to-local alert tags 68 St and doesn't count as a disruption, because running local means more trains stop there. The second alert names no stations, so the tool won't say your station is clear. It reports a disruption and names the alert that caused it.

Counting any mention as a disruption would flag 68 St here, because the alert that names it adds service. This server reads MTA's alert_type to tell the two apart.

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.
This works if you already know the subway's station names.
mta-mcp
match_count
4
unambiguous
false
stop_idroutes
1161
2252, 3
6214, 5, 6, 6X
A15A, B, C, D
The tool returns all four matches instead of picking one. 193 of 496 stations share a name with another. Pass route_id to narrow it, or pick a stop_id.
This answer comes from a station list built from MTA's static GTFS that ships with the repo, so it changes only when MTA renames a station.

Elevators and the way around

"Are any elevators out at 161 St–Yankee Stadium on the 4 train on Saturday, September 26?" Saved feed
The call
get_accessibility_outages({
  "stop_id": "414",
  "route_id": "4",
  "date": "2026-09-26"
})
What came back, trimmed

Elevator feed saved 2026-09-17. Station ADA and equipment inventory snapshots pulled 2026-09-22. Only the last of five outages is shown, and MTA's alternate route text is cut short.

{
  "date": "2026-09-26",
  "query": "161 St-Yankee Stadium",
  "stop_id": "414",
  "route_id": "4",
  "station_accessibility": { "status": "fully_accessible", "mta_notes": null },
  "outage_count": 5,
  "date_caveats": [],
  "outages": [
    …four more elevators…
    {
      "station": "161 St-Yankee Stadium",
      "trainno": "B/D/4",
      "equipment": "EL133",
      "equipmenttype": "EL",
      "serving": "mezzanine to Manhattan-bound 4 platform",
      "ADA": "Y",
      "outagedate": "11/06/2024 09:00:00 AM",
      "estimatedreturntoservice": "09/30/2026 11:59:00 PM",
      "reason": "Capital Replacement",
      "matched_by": "name",
      "match_note": "MTA's equipment inventory places EL133 at 161 St-Yankee Stadium (D11), another station in this complex. The outage feed's station name fits this station too, so it is listed here.",
      "inventory": {
        "stop_ids": ["D11"],
        "ada_compliant": "YES",
        "redundant_elevator": "-",
        "alternative_route": "To access street level: Take a Manhattan-bound
          4 to 125 St. Use elevators to transfer to a Bronx-bound 6. Exit at
          Hunts Point Av and transfer to a Washington Heights-bound Bx6-SBS
          bus to E 161 St and River Av. …"
      }
    }
  ]
}
What Claude can tell you from it
MTA lists this station as fully accessible. On this saved feed, five of its elevators are out, three of them with an estimated return of January 31, 2027, so the ADA status alone can't tell you the station is usable. Each outage carries MTA's own alternate route. The one above sends riders to the 6 train between 125 St and Hunts Point Av, and the saved alerts feed shows no 6 train on that stretch that weekend. The return dates are MTA's estimates. Check MTA's elevator and escalator status page before you send anyone.

How to use it

Once it's installed, you ask Claude a question in plain English, and Claude picks a tool and fills in the arguments. It works best when the question names a train, a station, and a date.

Try asking

Illustrative Claude chooses the tool, so its call may differ a little from the one shown.

You askThe tool Claude would likely call
"Check the 6 train and the elevators at 68 St–Hunter College for October 3. It's for our event." check_route_on_date
{"route_id": "6", "date": "2026-10-03", "station": "68 St-Hunter College", "include_accessibility": true}
"What's happening on the 6 train this weekend?" get_service_alerts, once per day
{"route_id": "6", "date": "2026-09-26"}
"Our flyer says 125 St. Which station is that?" resolve_station
{"query": "125 St"}
Four stations match, so it asks you which one.
"What elevator outages are scheduled on the 7 train?" get_accessibility_outages
{"route_id": "7", "upcoming": true}

Worked example: a travel note for an event

"We're at 68 St–Hunter College on Saturday, September 19. Is the 6 train OK, and are any elevators out?" Saved feed
The call
check_route_on_date({
  "route_id": "6",
  "date": "2026-09-19",
  "stop_id": "628",
  "include_accessibility": true
})
What came back, trimmed

Alerts feed timestamped 2026-09-16, elevator feed saved 2026-09-17, station ADA snapshot pulled 2026-09-22. Some fields are cut.

{
  "route_id": "6",
  "date": "2026-09-19",
  "disrupted": false,
  "station": {
    "stop_id": "628",
    "stop_name": "68 St-Hunter College",
    "routes": ["4", "6", "6X"],
    "accessibility": { "status": "fully_accessible", "mta_notes": null }
  },
  "station_level_detail": true,
  "alert_count": 1,
  "alerts": [{
    "alert_type": "Planned - Part Suspended",
    "effect": "reduced",
    "header_text": "No [6] between Hunts Point Av and 125 St",
    "human_readable_active_period": "Sep 18 - Oct 19, Fri 9:30 PM to Mon 5:00 AM",
    "affected_stops": [
      { "stop_id": "614", "stop_name": "Longwood Av" },
      …five more Bronx stations…
    ],
    "affects_this_station": false,
    "relevant_to_station": false
  }],
  "accessibility_outages": {
    "outage_count": 0,
    "outages": [],
    "note": "… No outage is listed here. A missing outage is not proof
      the station is usable: the feed can lag, and an outage MTA hasn't
      entered won't show. Confirm at
      https://www.mta.info/elevator-escalator-status before travel."
  },
  "disclaimer": "Unofficial. BetaNYC is not the MTA …"
}
What Claude can tell you from it
The one alert on the 6 that weekend is in the Bronx, between Hunts Point Av and 125 St, and MTA didn't tag 68 St, so disrupted is false for this station. Guests coming from the Bronx on the 6 would still hit the gap, and MTA's alert text says free shuttle buses cover it. MTA lists the station as fully accessible and listed no elevator outage there that day. The outage feed can lag, so that isn't proof the elevators work. A flyer should still tell people to check mta.info before traveling.

Every parameter and every field in every answer is in the tool reference. The elevator, escalator, and ADA data, and where it falls short, are in docs/accessibility.md.

Running it

1. Clone and build

You need Node 18 or newer, and no API key.

git clone https://github.com/BetaNYC/mta-mcp.git
cd mta-mcp
npm install    # also builds

2. Point your AI app at the build

For Claude Code, in a project's .mcp.json:

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

For Claude Desktop, in claude_desktop_config.json. Claude Desktop doesn't expand ${HOME}, so use the full path to your copy:

{
  "mcpServers": {
    "mta-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/mta-mcp/dist/index.js"]
    }
  }
}

3. Ask a question

Restart the app and ask something from Try asking, like "Check the 6 train and the elevators at 68 St–Hunter College for October 3."

Using Claude Code and want to use fewer tokens per conversation? The same tools also come as a skill, with no MCP server to run. See skills/README.md.

What it can't do yet

Not thisWhy
"When is the next 6 train?" No arrivals or trip updates, for the subway or buses. The subway arrival feeds are protobuf-only, and Bus Time needs an API key. The servers below do live subway arrivals.
"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.
"Can I get step-free to the platform today?" Only in part. You get the station's ADA status and any elevator outages MTA has listed, with its alternate route. The ADA status is a dated snapshot and the outage feed can lag, so an empty answer doesn't mean the station is usable. Check MTA's elevator and escalator status page.
"Is my station definitely fine?" MTA tags stations only when it considers the change significant. Every response carries station_level_detail, which is true only when every matching alert named its stations. When it's false, a clean answer for your station isn't a guarantee.

What we haven't settled

Want bus or LIRR alerts, or found something wrong? Open an issue.

Other NYC subway MCP servers

For most subway questions, one of these is a better fit. Several are further along than ours, and all of them do live arrivals.

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.

Use this one when your question has a date in it. It checks a future day at one station, and it tells service that got worse there from service that got better.

Why you have to build it yourself

Our other seven MCP servers install from npm. This one runs from a local build because of MTA's data feed terms, which say users of an app have to get MTA's data from a non-MTA server. A published package would have every user's machine fetch straight from api-endpoint.mta.info, with no server of ours in between. MTA's wording and our reasoning are in docs/terms-compliance.md.

Community

Your questionWhere it goes
A tool returns the wrong shape, a schema is wrong, this server crashes Our issue tracker
For an alert-matching question, include the entity_id from the response so we can find the alert in the raw feed.
A feed field is undocumented, an alert looks wrong, MTA's API behaves oddly MTA Developer Google Group
MTA points developers to this public group. You'll get a better answer there than in our tracker, and it helps everyone else building on the same feeds.

Build with us

BetaNYC builds and maintains this project. We're New York's civic technology and open data community, and we work to improve lives in New York through civic design, technology, and data. Our members, BetaBuilders, talk through tools like this one on the BetaNYC Discord while they're still rough.

Attending a class or registering for a flagship event earns 90 days of access at no cost. Giving $5 a month gets you in too, and funds School of Data, Mapping for Equity, Open Data Week, CityCamp NYC, and the weekly newsletter. If cost is the obstacle, say so. Learn more about BetaBuilders.

The newsletter, public events, classes, and this server don't require a membership. More about us: who we are, our work, the events calendar, NYC School of Data, CityCamp NYC, and github.com/BetaNYC, where our seven npm-published MCP servers live. If these tools are useful to you, please donate.

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 are at nymta/gtfs-documentation.

We wrote most of this server and this page with Claude. Panels labeled Verified or Saved feed show real calls. The panel labeled Illustrative does not.