sl-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sl-mcpWhen are the next departures from Stockholm Central?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
sl-mcp
MCP server that lets AI assistants (Claude, Cursor, VS Code) answer questions about Stockholm public transport, using SL's open APIs.
Work in progress.
Tools
Tool | Description |
| TODO |
| TODO |
| TODO |
Related MCP server: TFL MCP Server for Poke
Getting started
Requires Node.js 20 or later.
npm install
npm run build
npm test
npm run inspect # opens the MCP Inspector against the built serverProject structure
src/
index.ts entry point: serves the server over stdio
server.ts createServer(): builds the server and registers the tools
tools/ one file per tool (schema, description, handler)
result.ts helpers for text and error results
sl/
client.ts SlClient: calls SL's APIs and maps responses to our types
types.ts Stop, Departure, Disruption
lib/
cache.ts TtlCache
test/
harness.ts connects a real MCP client to the server in memory
fixtures/ recorded API responsesThe entry point only wires up stdio. Everything else is built by createServer(), so tests can
connect a real MCP client over an in-memory transport without touching stdin/stdout. The tools
depend on SlClient, which takes a fetch function, so tests run on recorded responses instead
of the live API.
Design decisions
TODO
Data
Data from SL's open APIs via Trafiklab.
Available Tools
3 toolsfind_stopFind stopDRead-only
TODO
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | TODO | |
| query | Yes | TODO: describe for the model, e.g. part of a stop name |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=true, so the safety profile is partly covered structurally, but the description adds nothing beyond that — no note on matching behavior, ambiguity handling, or result ordering. The placeholder text contributes zero behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The single word "TODO" is a placeholder rather than a concise description. It is not merely terse — it conveys no information and leaves the definition unfinished.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter open-world search tool with no output schema, the description should explain what is searched, what is returned, and how limit affects results. None of that is present; the definition is incomplete at every level.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema declares two parameters with types, a default of 5, and a 1-10 bound for limit, but both parameter descriptions are themselves "TODO" placeholders, including the required query. The tool description adds no meaning on top, so an agent learns nothing about what query accepts (substring, exact name, ID?).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description is the literal placeholder "TODO", so it states no verb, resource, or scope. The only signal of purpose comes from the tool name and the sibling names get_departures/get_disruptions, not from the description itself.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no mention of alternatives such as get_departures, and no indication of what a valid query looks like. An agent must guess entirely from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_departuresGet departuresDRead-only
TODO
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | TODO | |
| siteId | Yes | TODO: the id returned by find_stop | |
| transportMode | No | TODO |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=true, but the description adds zero behavioral context beyond them — no mention of real-time data sourcing, rate limits, freshness, or failure modes when a stop has no service. Since the description is a placeholder, it contributes nothing to the already-thin annotation set.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
"TODO" is a placeholder, not a concise description. Brevity here reflects missing content rather than disciplined editing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a three-parameter, open-world query tool with no output schema, the description should explain what a departure record contains and how results are ordered or truncated by limit. None of that is present, leaving the definition unusable for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is nominally 100%, but every parameter description is itself "TODO" except the bare hint that siteId is "the id returned by find_stop". The description adds no semantics for limit, transportMode, or siteId, so an agent gets only structural cues (enum values, default 10, max 20) without meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description is the literal string "TODO", so it states nothing about what the tool does. Only the name and title hint at fetching departures, and no scope, format, or distinction from siblings like get_disruptions is given.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no exclusions, and no mention of the sibling tools find_stop or get_disruptions. An agent must infer the entire usage context from the tool name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_disruptionsGet disruptionsDRead-only
TODO
| Name | Required | Description | Default |
|---|---|---|---|
| line | No | TODO: e.g. a line number like 14 | |
| transportMode | No | TODO |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=true, so the safety profile is known, but the description adds nothing beyond that — no indication of data freshness, coverage area, pagination, or what a disruption record contains. A placeholder string contributes zero behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
"TODO" is short but not concise — there is no content to be front-loaded or structured. It is an unfilled template rather than a description.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter query tool with no output schema, the description must carry the full explanatory burden, and it carries none. An agent has no basis for supplying `line`, choosing a `transportMode`, or interpreting results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description provides no parameter meaning at all. Although the coverage metric reports 100%, both schema descriptions are themselves "TODO" placeholders, so an agent still learns nothing about the expected format of `line` or what filtering by `transportMode` does. Nothing compensates for the effectively empty schema documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description is the literal placeholder string "TODO", which conveys no verb, resource, or scope. An agent cannot tell from this text what get_disruptions returns or how it differs from siblings like get_departures.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool, when not to, or how it relates to find_stop and get_departures. The placeholder text offers no conditions or alternatives.
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.
3 tool updates
v0.1.0- First observed
find_stop - First observed
get_departures - First observed
get_disruptions
TDQS
Scored across 3 tools
The three names imply fairly different targets (a stop lookup, departure boards, and disruption feeds), but all descriptions are literally 'TODO', so an agent has no guidance on parameters or the relationship between them. In particular, whether get_departures takes a stop id from find_stop or accepts free-text is undefined, which invites misselection.
All names use a consistent snake_case verb_noun pattern (find_stop, get_departures, get_disruptions). Minor deviation between 'find' and 'get' verbs is the only inconsistency, and it still reads naturally.
Three tools is thin for a public-transport server; a stop lookup, departures, and disruptions are a reasonable minimal core, but the surface feels under-scoped for real transit queries. It is borderline rather than clearly wrong.
The set covers the basics of locating a stop, seeing departures, and checking disruptions, but there is no journey/route planning, no nearby-stop search, and no way to resolve a line or destination — all likely gaps for transit workflows. Without descriptions it is impossible to confirm the existing three are sufficient.
Maintenance
Related MCP Connectors
Stockholm transit: realtime departures, trip planning, disruptions and personal commute status.
Belgian trains for AI agents: live journeys, departures, delays, disruptions. Free, no key.
Search award flights and cash fares, optimize points, and predict fares inside ChatGPT and Claude.
Connects AI assistants to TrackingTime for querying time entries, projects, tasks, customers, and team data using natural language.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI assistants to query Prague's public transit departures, air quality, parking availability, and other city data via the Golemio open data API.12GPL 3.0
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to access real-time Transport for London data, including tube/bus arrivals, line status, journey planning, and disruptions.1-
- AlicenseAqualityDmaintenanceAn 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.61MIT
- AlicenseAqualityCmaintenanceEnables 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.2MIT