Skip to main content
Glama

sl-mcp

CI Release License: MIT sl-mcp MCP server score

An MCP server for Stockholm public transport (SL). Connect it to Claude or another MCP client and ask about departures, disruptions and trips in plain Swedish or English.

När går nästa tunnelbana från Slussen mot Ropsten?
Är det några störningar på röda linjen?
How do I get from Odenplan to Arlanda?
Hur tar jag mig från Sundbyberg till Götgatan 76?

It uses SL's open APIs, which don't need an API key.

An answer looks like this:

Gå 08:31 från Sundbybergs centrum, så är du framme på Götgatan 76 kl 08:58.

  1. 08:31 Tunnelbana 10 mot Kungsträdgården från spår 2. Åk 7 stationer till T-Centralen.

  2. 08:47 Byt till tunnelbana 17 mot Skarpnäck från spår 4. Åk 3 stationer till Medborgarplatsen.

  3. 08:52–08:58 Gå ut via uppgång Folkungagatan Götgatan och gå 6 min till Götgatan 76.

Use it without installing

A hosted server runs on Cloudflare Workers. No API key or login is needed:

https://sl-mcp.miltonlindblad.workers.dev/mcp

Add it as a custom connector in claude.ai or ChatGPT, or in any MCP client that supports Streamable HTTP. In Claude Code:

claude mcp add --transport http sl https://sl-mcp.miltonlindblad.workers.dev/mcp

Related MCP server: TFL MCP Server for Poke

Tools

  • find_stop: search for a stop by name, typos are fine, and get its id

  • get_departures: live departures from a stop with platform or bus stop letter, delays and cancellations. Filter by mode, line or destination

  • get_disruptions: announced disruptions such as track work, broken lifts and replacement buses, by line, mode or metro colour ("grön linje")

  • plan_trip: up to 3 trips between stops or street addresses, leaving or arriving at a given time. Each ride shows platform, stops, crowding and delay, and each walk shows minutes and which station exit to use. You can ask for only some modes (e.g. boat) and for fewest changes or least walking

Prompts

Ready-made questions to pick in your client, e.g. as slash commands:

  • trip: how to get from one place to another, optionally arriving by a given time

  • departures: the next departures from a stop

Install

Requires Node.js 20 or later.

git clone https://github.com/Hashtagsmile/sl-mcp.git
cd sl-mcp
npm install
npm run build

Claude Code

claude mcp add sl -- node /path/to/sl-mcp/dist/index.js

Claude Desktop

Add this to claude_desktop_config.json and restart Claude Desktop:

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

Development

npm run dev       # compile on save
npm test          # run the tests
npm run inspect   # try the tools in the MCP Inspector

The tests use recorded API responses in test/fixtures/, so they don't call SL.

Deploy to Cloudflare Workers

src/worker.ts serves the same tools over HTTP at /mcp.

npx wrangler dev      # run it locally on http://localhost:8787/mcp
npx wrangler deploy   # deploy to your Cloudflare account

Limitations

  • SL returns at most 3 departures per line and direction.

  • Disruption messages are in Swedish only.

  • There is no "nearest stop" search, since SL has no keyless API for it.

  • SL can't plan step-free trips. For a wheelchair, the tools suggest least walking and checking for broken lifts.

  • Addresses work, business names don't: "Götgatan 76" works, "Sellpy" doesn't.

  • Departures reach at most 3 hours ahead. For later, use plan_trip with a time.

Data

Data from SL via Trafiklab.

License

MIT

Available Tools

4 tools
find_stopFind stopA
Read-only
Inspect

Find Stockholm public transport (SL) stops by name and return their ids, names and transport modes. Use it to get the siteId for get_departures, or a stop id for plan_trip. Not for street addresses: pass those to plan_trip as text instead. A name can match several stops ('Arlanda' gives the train station and bus stops): pick by name and modes, or ask the user. Typos are fine, abbreviations are not: write 'Tekniska högskolan', not 'KTH'. Returns one line per stop: ': , ()'.

NameRequiredDescriptionDefault
limitNoMaximum number of stops to return
queryYesStop or station name, e.g. 'Slussen' or 'Arlanda'. Typos are fine, abbreviations are not.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint/openWorldHint annotations, it discloses fuzzy matching behavior ('typos are fine, abbreviations are not'), that a query can return several distinct stops, how to disambiguate, and the exact one-line-per-stop return shape. These are behavioral traits an agent could not infer from the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and return values, then usage routing, then the negative case, then ambiguity handling, then output shape. Every sentence carries distinct information; there is no filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter lookup with no output schema, the description supplies the missing return format ('<id>: <town>, <name> (<modes>)'), the disambiguation policy, and the sibling routing. Nothing an agent needs to call it correctly and interpret results is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already documents both 'query' (including the typo/abbreviation note) and 'limit' with its bounds and default. The description reinforces query semantics and hints that results may be multiple, but adds no new naming syntax or formatting details beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Find Stockholm public transport (SL) stops by name') plus what is returned (ids, names, transport modes). It clearly distinguishes itself from siblings by naming get_departures and plan_trip as consumers of the resulting ids.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: use this to obtain a siteId for get_departures or a stop id for plan_trip, and explicitly excludes street addresses ('pass those to plan_trip as text instead'). It also gives a resolution strategy for ambiguous multi-match results (pick by name/modes or ask the user).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_departuresGet departuresA
Read-only
Inspect

List live departures from one SL stop, soonest first: line, mode, final destination, direction (1 or 2), platform or bus stop letter, minutes or clock time, delay, cancellation and SL's notes. Use it for 'when does the next X leave from Y'. To get from A to B, use plan_trip; for announced track work or broken lifts, use get_disruptions. Real-time data from SL: at most 3 departures per line and direction, up to 3 hours ahead. towards matches the final destination only: for a station on the way (e.g. 'mot T-Centralen' from Slussen), leave it out and pick the direction from the results. An unknown siteId gives no departures, not an error.

NameRequiredDescriptionDefault
lineNoOnly this line number, e.g. '14' or '43X'
limitNoMaximum number of departures
siteIdYesStop id from find_stop
minutesNoHow many minutes ahead to look, at most 180
towardsNoOnly departures whose final destination contains this, e.g. 'Ropsten'
transportModeNoOnly this mode

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover read-only and open-world, but the description adds real behavioral traits: real-time SL data capped at 3 departures per line and direction, a 3-hour horizon, and the important edge case that an unknown siteId returns an empty list rather than an error. It stops short of covering error modes, rate limiting, or what live-vs-scheduled mixing looks like, so it is strong but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and return payload, then routing, then data limits and edge cases — a sound order with no filler. The final `towards` guidance is a long compound sentence that could be split for readability, which keeps it off a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates by enumerating returned fields, the 3-per-line/direction cap, and the time horizon. Combined with the sibling routing and the empty-result caveat, an agent has everything needed to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does add real meaning beyond the schema for `towards` (final destination only, with the 'mot T-Centralen' workaround), which lifts it slightly, but the other five parameters are left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List live departures from one SL stop, soonest first') and then enumerates exactly what comes back: line, mode, final destination, direction, platform/bus stop letter, time, delay, cancellation, notes. It explicitly names the siblings it is not (plan_trip, get_disruptions), so an agent can route without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete use case ('when does the next X leave from Y') and names both alternatives with the condition that selects them: plan_trip for A-to-B, get_disruptions for track work and broken lifts. It also supplies a negative rule for the `towards` filter, telling the agent to omit it and read the direction from results rather than guessing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_disruptionsGet disruptionsA
Read-only
Inspect

List disruptions SL has announced: track work, closed entrances, broken lifts and replacement buses, with the lines affected and until when. Use it to check a line or the stops of a trip before travelling, e.g. lifts for a wheelchair. For live delays and cancelled departures, use get_departures instead. The text is in Swedish, so translate it if needed, and is cut at 800 characters. Results are cached for 1 minute, as SL asks. Leave both filters out to list everything.

NameRequiredDescriptionDefault
linesNoOnly these line numbers, e.g. ['13', '14'], or metro colours, e.g. ['grön linje']
transportModeNoOnly this mode

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint and openWorldHint; the description goes well beyond them by disclosing that the text is Swedish, truncated at 800 characters, and server-cached for 1 minute. These are exactly the traits an agent needs to interpret results correctly and avoid spurious retries.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what the tool returns, followed by when to use it and the behavioral caveats; nearly every clause earns its place. Slightly dense as a single run-on paragraph, but no filler sentences.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description still tells the agent what a result contains (affected lines, until when), what language and length to expect, and how fresh it is. For a two-parameter read-only lookup, nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already documents both parameters with examples and an enum, so the description only adds the omission semantics ("Leave both filters out to list everything"). Baseline 3 is appropriate since the schema carries the parameter meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("List disruptions SL has announced") and enumerates the content covered: track work, closed entrances, broken lifts, replacement buses, affected lines, and validity window. An agent can immediately distinguish this from find_stop, get_departures, and plan_trip.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete scenario ("check a line or the stops of a trip before travelling, e.g. lifts for a wheelchair") and explicitly names the alternative for the adjacent case: "For live delays and cancelled departures, use get_departures instead." It also clarifies the default behavior when filters are omitted, which prevents unnecessary parameter guessing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

plan_tripPlan tripA
Read-only
Inspect

Plan trips by SL public transport between two stops or street addresses, leaving or arriving at a given time. Returns up to 3 trips, plus walking all the way when that takes at most 30 min. Each ride lists line, direction, platform, stops, crowding and delay; each walk lists minutes and the station exit (uppgång). Use it for 'how do I get to X' and 'when must I leave'; for departures from one stop, use get_departures. Addresses are fuzzy matched, so the result starts with what each one matched. Times are Stockholm time. Unknown stops, the same stop twice and times outside the timetable give a clear error. SL cannot plan step-free trips: for a wheelchair or pram, use prefer 'least walking' and check get_disruptions for broken lifts at the stops.

NameRequiredDescriptionDefault
toYesDestination: a stop id from find_stop, or a street address
fromYesStart: a stop id from find_stop, or a street address like 'Götgatan 76, Stockholm'
timeNoStockholm time as YYYY-MM-DDTHH:MM. Leave out to leave now.
modesNoOnly these modes, e.g. ['boat'] for 'by boat'. Leave out for all.
preferNoWhat to optimise for: fastest (default), fewest changes or least walkingfastest
arriveByNotrue: arrive by `time`. false: leave at `time`.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/openWorld annotations, the description discloses result cardinality (up to 3 trips), a non-obvious fallback (walking all the way when under 30 min), the per-item fields returned and the fuzzy-match echo behaviour. It also states the error surface (unknown stops, identical stops, out-of-timetable times) and a real capability limit (SL cannot plan step-free trips), which no annotation conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The primary purpose and the sibling exclusion are front-loaded, and the denser return-shape sentence earns its space because there is no output schema. It is a long, dense block with a slightly tangential accessibility coda, but nearly every clause carries information an agent would otherwise have to discover by trial.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 6-parameter routing tool with no output schema, the description covers input interpretation (stops vs fuzzy-matched addresses), output shape (rides vs walks and their fields), failure modes and a domain limitation. Nothing an agent needs in order to call it correctly and interpret the response is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all six parameters are already documented, including the Stockholm-time format and prefer's default. The description reinforces a few meanings (Stockholm time, prefer 'least walking' as the accessibility workaround) but adds no new syntax or semantic detail about from/to, modes, or arriveBy, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb (plan), the resource (trips), the exact scope (between two stops or street addresses) and the time semantics (leave or arrive at a given time). It is immediately distinguishable from get_departures because of the origin/destination requirement.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It names the concrete user intents it serves ('how do I get to X', 'when must I leave') and explicitly routes single-stop departure queries to get_departures. It also gives a conditional alternative path for step-free travel (use prefer 'least walking' plus get_disruptions), which is when-to-use guidance for a whole class of requests.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.2.0
    • Changedfind_stop3 fields changed
      • changedInput schema / properties / limit / description
        Previous value: -"TODO"New value: +"Maximum number of stops to return"
      • changedInput schema / properties / query / description
        Previous value: -"TODO: describe for the model, e.g. part of a stop name"New value: +"Stop or station name, e.g. 'Slussen' or 'Arlanda'. Typos are fine, abbreviations are not."
      • addedInput schema / properties / query / minLength
        Added value: +1
    • Changedget_departures8 fields changed
      • changedInput schema / properties / limit / description
        Previous value: -"TODO"New value: +"Maximum number of departures"
      • changedInput schema / properties / limit / maximum
        Previous value: -20New value: +30
      • addedInput schema / properties / line
        Added value: +{
        +  "description": "Only this line number, e.g. '14' or '43X'",
        +  "type": "string"
        +}
      • addedInput schema / properties / minutes
        Added value: +{
        +  "default": 60,
        +  "description": "How many minutes ahead to look, at most 180",
        +  "maximum": 180,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • changedInput schema / properties / siteId / description
        Previous value: -"TODO: the id returned by find_stop"New value: +"Stop id from find_stop"
      • addedInput schema / properties / towards
        Added value: +{
        +  "description": "Only departures whose final destination contains this, e.g. 'Ropsten'",
        +  "type": "string"
        +}
      • changedInput schema / properties / transportMode / description
        Previous value: -"TODO"New value: +"Only this mode"
      • changedInput schema / properties / transportMode / enum
        Previous value: -[
        -  "METRO",
        -  "BUS",
        -  "TRAIN",
        -  "TRAM",
        -  "SHIP"
        -]New value: +[
        +  "METRO",
        +  "BUS",
        +  "TRAIN",
        +  "TRAM",
        +  "SHIP",
        +  "FERRY"
        +]
    • Changedget_disruptions4 fields changed
      • removedInput schema / properties / line
        Removed value: -{
        -  "description": "TODO: e.g. a line number like 14",
        -  "type": "string"
        -}
      • addedInput schema / properties / lines
        Added value: +{
        +  "description": "Only these line numbers, e.g. ['13', '14'], or metro colours, e.g. ['grön linje']",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • changedInput schema / properties / transportMode / description
        Previous value: -"TODO"New value: +"Only this mode"
      • changedInput schema / properties / transportMode / enum
        Previous value: -[
        -  "METRO",
        -  "BUS",
        -  "TRAIN",
        -  "TRAM",
        -  "SHIP"
        -]New value: +[
        +  "METRO",
        +  "BUS",
        +  "TRAIN",
        +  "TRAM",
        +  "SHIP",
        +  "FERRY"
        +]
    • Addedplan_trip
  2. 3 tool updatesv0.1.0
    • First observedfind_stop
    • First observedget_departures
    • First observedget_disruptions

TDQS

A4.7/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a distinct role: stop lookup, live departures, announced disruptions, or door-to-door trip planning. Descriptions explicitly cross-reference alternatives and clarify potentially confusing cases such as delays versus track work.

Naming Consistency5/5

All four tools use a consistent snake_case verb_noun pattern. The verbs get, find, and plan accurately reflect their respective operations.

Tool Count5/5

Four tools are well matched to a focused Stockholm public-transit server. Each earns its place without introducing overlapping or narrowly redundant operations.

Completeness5/5

The set supports the core travel workflow from resolving stops to checking departures, planning journeys, and reviewing disruptions. It also accommodates street addresses and accessibility-related checks, leaving no obvious dead ends for its read-only purpose.

Maintenance

ActivityNo data
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that exposes the Deutsche Bahn public transport API to any MCP-compatible client (Claude Desktop, Cursor, Cline, Continue, etc.). Five tools cover station search, departures, journey planning, trip details, and nearby stations.
    6
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI agents to search for stops and stations across all Norwegian public transport and to fetch real-time departure data — planned versus expected times, delays, platform, operator, and disruption messages — with no API key required. Covers bus, tram, metro, train, and ferry nationwide through Entur's open APIs, with filtering by line, destination, and future departure time.
    2
    MIT