Tifa
Provides access to Transport for London's open data on London Underground, Elizabeth line, London Overground and DLR line statuses, including live status, historical disruption episodes, causes, period statistics, and coverage information.
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., "@TifaWhich London rail lines are disrupted right now?"
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.
Tifa
Tifa keeps the history of London's rail disruptions. Every minute it records the status of every London Underground, Elizabeth line, London Overground and DLR line from TfL's open data, turns it into disruption episodes (which line, what kind, when it started and ended, what TfL said, and what caused it), and serves the live status and the history to everyone through an open REST API and a remote MCP server for AI assistants.
TfL publishes only the status right now. Tifa remembers it, so you can ask how often the Central line had a signal failure last month, how much of last week the network ran a good service, or what was disrupted on the night the clocks went back.
Live service: https://tifa-api-dh52427ooa-ew.a.run.app
API docs: https://tifa-api-dh52427ooa-ew.a.run.app/docs (interactive), docs/api.md
MCP:
https://tifa-api-dh52427ooa-ew.a.run.app/mcp, see docs/mcp.md
Powered by TfL Open Data. Contains data from Transport for London, re-used under TfL's open data terms (based on the Open Government Licence v2.0). Tifa is not affiliated with or endorsed by Transport for London. It uses no TfL logos or roundels. Please credit "Powered by TfL Open Data" when you show data you got from Tifa; every API response and MCP result carries that line.
Quick start
# what is disrupted right now
curl -s https://tifa-api-dh52427ooa-ew.a.run.app/v1/status | jq '.data.lines[] | select(.good | not) | {name, status, reasons}'
# the Central line's unplanned disruptions in October, with their causes
curl -s 'https://tifa-api-dh52427ooa-ew.a.run.app/v1/disruptions?line=central&from=2026-10-01&to=2026-10-31&planned=false' \
| jq '.data.episodes[] | {start, end, status, cause, reason}'
# how the network did over the last 30 days
curl -s 'https://tifa-api-dh52427ooa-ew.a.run.app/v1/stats?span=30d&end=now' | jq '.data | {good_pct, unplanned, planned, most_disrupted}'Add Tifa to Claude Code (or any MCP client; docs/mcp.md covers the others):
claude mcp add --transport http tifa https://tifa-api-dh52427ooa-ew.a.run.app/mcpRelated MCP server: TFL MCP Server for Poke
What the data means
The full description is in docs/data-model.md. In short:
Episodes. An episode is a run of polls that saw the same line with the same status severity, planned or not. It starts at the first poll that saw it and ends at the first poll that did not, so times are accurate to about a minute. Good Service, No Issues and Service Closed (the end of the day) are never episodes. While an episode is open its
endisnull.The GAP rule. When no poll succeeded for more than 15 minutes (
GAP= 900 s), nobody was watching. Every episode open at the last poll before the gap ends there, markedend_estimated: true, and anything still going on starts again at the first poll after it. Shorter outages change nothing.A gap is not good service. Time Tifa did not watch counts neither as good service nor as disruption. Every percentage is of watched time;
/v1/coverageshows what was watched, the gaps, and each failed poll with its reason. Check it before reading a quiet period as a good one.Categories. Each severity falls in one category:
delays(minor and severe delays, reduced service),suspended(suspended, part suspended),closure(closed, planned and part closures, not running),other(special service, diversions, bus service and the rest) andinfo(information, no step-free access).infoepisodes are listed but never counted as disruption.Causes. Each episode's cause is read from TfL's own reason text by 16 rules (signal failure, faulty train, track fault, power failure, faulty equipment, customer incident, obstruction, staff shortage, strike, police or security, fire alert, weather, engineering works, train cancellations, operational restrictions, Network Rail). Planned works are engineering unless they say otherwise. When nothing matches, the cause is
null: unknown, and shown as such.Time. Times are UTC (ISO 8601 in the API, epoch seconds in month payloads). Months are UTC calendar months. Days and hours shown to people are London time, so a London day can be 23, 24 or 25 hours long.
Endpoints
All GET, open CORS, JSON in a {"data": ..., "meta": ...} envelope with the attribution in meta. Details,
parameters and examples: docs/api.md.
Route | What |
| Every line's status at the last poll, and the episodes open now |
| What changed since a cursor: for keeping a copy in sync |
| The lines; one line in depth (now, 24 hours to 90 days, recent episodes, hours of the day) |
| The months index; one month's episodes and watched time |
| Episodes filtered by line, time, category, cause and planned, paged |
| Figures for 1, 7, 30, 90 or 365 days, against the period before |
| When Tifa was watching: spans, gaps, polls and failed polls per day |
| Sources, categories, causes, licence and the fair-use limits |
| The last poll's age and the service's health |
| Interactive documentation and the OpenAPI document |
| The MCP server (streamable HTTP, stateless, JSON responses) |
Every answer from a route, errors included, has an ETag (send If-None-Match for a 304), Cache-Control set
per route, and gzip when asked. Refusals by the fair-use limits (429, and 503 for the service-wide limits) and other
errors the service raises before reaching a route (405, 413, 500) are no-store and carry no ETag.
MCP
The MCP server at /mcp has seven read-only tools over the same data as the REST API: list_lines,
current_status, line_status, disruptions, period_stats, coverage and causes. No key or sign-in is
needed. Setup for Claude Code, the Claude apps, VS Code, Cursor and others, and the tool reference, are in
docs/mcp.md.
Fair use
Tifa is free to use and runs on a small budget, so requests are shared out:
Per client address (IPv6 by /64): a bucket of 60 tokens, refilled at 1 per second. A request costs 1, an uncached changes, disruptions, stats or coverage query 5, an MCP tool call 3. Each address may also receive 1.25 MiB an hour (an eighth of the service's allowance), so ask for gzip and send
If-None-Match. Over either: 429 withRetry-After.For the whole service: 20 requests a second (bursts to 100), 10 uncached computations a second and 10 MiB of responses an hour. Over: 503 with
Retry-After(60 seconds, or longer while the hourly allowance recovers).Responses carry
RateLimit-Limit,RateLimit-Remaining,RateLimit-ResetandRateLimit-Policy.If traffic still runs away, a guard closes the public API for a while (more than 9,000 requests in 5 minutes: 30 minutes, doubling on repeats up to a day; more than 36,000 in an hour: 2 hours; the month's estimated cost over $2, or the budget spent: until the 1st). Recording never stops.
Polling once a minute is plenty: the data changes at most once a minute. Use If-None-Match, and /v1/changes to
keep a copy.
Sources and licences
Source | What | Published |
| Tifa's own live recording of TfL's line status, from its first poll | yes |
| TfL's freedom-of-information release of its status records, 2020 to February 2025 | no |
Historic data from TfL's FOI release is not published. Information released under the Freedom of Information Act
is released to the person who asked: the Act gives access, not a licence to re-use or publish, and TfL's release
grants no such permission. So the API publishes only Tifa's own recording (TIFA_PUBLIC_SOURCES=a, the default
everywhere), and tools/import_foi.py refuses to run unless told --i-have-reuse-permission. If TfL grants re-use
in writing, the history back to 2020 can be added.
Code: MIT (LICENSE).
Data: TfL open data, under TfL's open data terms (based on the Open Government Licence v2.0), with the attribution "Powered by TfL Open Data". Tifa is not affiliated with or endorsed by Transport for London.
Privacy
No accounts, no keys, no cookies, no analytics, no tracking.
The REST API,
/openapi.jsonand/mcpload nothing from anywhere else. The interactive docs page,/docs, is Swagger UI: when you open it in a browser, your browser fetches Swagger UI's script and stylesheet (an exact, pinned version) from the jsDelivr CDN, so the CDN sees your address too. Nothing else on the page asks a third party. Read docs/api.md or/openapi.jsoninstead to avoid that.Your IP address is used only for the per-client rate limit. It is held in the API's memory (at most 10,000 addresses, the least recently seen dropped first) and never written to the database or to Tifa's own logs.
Cloud Run's request logs for responses below HTTP 500 are excluded from Cloud Logging, so ordinary requests leave no log entry with your address. Request logs of server errors (5xx) are kept for diagnosis, as Cloud Logging records them.
The fair-use guard reads only aggregate request counts.
Tifa's TfL app key is kept in Secret Manager and never logged.
How it works
Cloud Scheduler (every minute, UTC) ──► tifa-poller (private)
├─ GET TfL line status
├─ check the answer, observe, advance: one Firestore transaction
└─ guard: Cloud Monitoring every 2 minutes; can close tifa-api
Budget alerts ─► Pub/Sub ─push─────────► tifa-poller /internal/budget
Anyone ─────────────────────────────────► tifa-api (public): /v1/*, /mcp, /docs, /health
served from memory, refreshed from Firestore every 30 sTwo Cloud Run services from one image (
TIFA_ROLE=api|poller), so a flood on the public API can never stall recording. The poller is private (IAM); the API is public and scales to zero.Each minute slot is recorded at most once, in one transaction, so retries are harmless. TfL's answer is checked before anything changes, so a broken answer cannot close every episode.
The API serves a copy held in memory, so database reads do not grow with traffic.
Self-hosting
Everything needed to run your own Tifa on Google Cloud is in this repository; nothing identifying a real deployment
is. Tifa can share an existing project: every resource it creates is named tifa-* and labelled app=tifa, its CI
identity may update only its two services, and its budget counts only its own resources.
cp infra/env.example infra/.envand fill it in (gitignored; never commit it).infra/bootstrap.sh --dry-run, theninfra/bootstrap.sh: APIs, Firestore, Artifact Registry, service accounts, Workload Identity Federation for GitHub Actions, the TfL key secret, Pub/Sub and a paused Scheduler job.In the GitHub repository's settings, set the Actions variables the bootstrap prints (
--githubsets them withgh), and create the environmentproduction, limited to themainbranch. The Deploy workflow runs in that environment, so without it every deploy from CI fails.Optionally register for a free TfL app key on TfL's API portal and store it with
infra/bootstrap.sh tfl-key(a hidden prompt), then setTFL_APP_KEY_VERSIONto the version it prints. Without a key, Tifa polls anonymously.infra/first-deploy.sh: builds the image and creates both services with your own credentials, and lets CI update them (and nothing else).infra/post-deploy.sh: who may call what, the budget and its push, the log exclusion, then smoke tests. From then on every push tomaindeploys through.github/workflows/deploy.yml.Resume the Scheduler job, and recording starts.
docs/operations.md has the details: settings, deploying, rotating the key, the guard and the budget, rolling back and troubleshooting. The services' settings are environment variables, listed in docs/contracts.md.
Cost
Expected, in US dollars a month, assuming the free tiers are already used by something else:
Item | Expected |
Poller: 43,800 polls a month of about 3.5 s at 0.25 vCPU | 1.06 |
API: about 100,000 requests | 0.36 |
Cloud Scheduler: one job | 0.10 |
Egress, Artifact Registry, Secret Manager | 0.17 |
Firestore, Logging, Monitoring, Pub/Sub (inside their free tiers) | 0 |
Total | about 1.70 |
Under abuse, the fair-use limits, the guard and a monthly budget (£3.75, about $5, by default) hold the worst case to about the budget. Nothing is ever deleted and billing is never detached; recording carries on while the public API is closed.
Development
uv sync # Python 3.12+; the image uses 3.13
uv run pytest -q # the whole suite; Firestore emulator tests run when FIRESTORE_EMULATOR_HOST is set
uv run ruff check .
TIFA_ROLE=api uv run tifa # the API on http://localhost:8080 with an empty in-memory storetests/integration/ runs three simulated days of minute polls (including the night the clocks go back) and the
night a month ends through the real poller into an in-memory store, and reads them back through the REST API, the
MCP server, clients keeping a copy in sync and the operator tools.
Documentation
docs/api.md: the REST API
docs/mcp.md: the MCP server
docs/data-model.md: episodes, gaps, categories, causes, months and the period figures
docs/operations.md: setting up, deploying and running it
docs/contracts.md: the internal interfaces
Available Tools
7 toolscausesCausesARead-onlyIdempotent
The causes Tifa puts disruptions down to (read from TfL's reason text), as codes with labels, in the order the rules are tried. The codes are what disruptions takes as cause; a disruption with a null cause is unknown (cause=unknown finds those).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, closed-world and non-destructive, so safety is covered. The description adds genuinely non-obvious behavior: results are ordered by the sequence in which rules are tried, values come from TfL's raw reason text, and a null cause means unknown. It doesn't say anything about whether the list is static or changes with TfL data updates, which would help an agent decide on caching.
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?
Two sentences, both carrying content: one defines the output and ordering, the other connects it to `disruptions` and handles the null case. It's front-loaded with the core definition. The phrasing 'puts disruptions down to' is informal internal jargon that costs a little parse time versus 'attributes disruptions to'.
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?
With no parameters, no output schema and annotations covering the safety profile, the remaining burden is describing what comes back and how to use it — and the description does both, including ordering and the null/unknown convention. An agent has everything needed to call it correctly and interpret 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 tool takes zero parameters, so there is nothing for the description to disambiguate and the baseline of 4 applies. The description does clarify the semantics of the related `cause` value consumed elsewhere, which is a useful adjunct but not a parameter explanation.
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 names a specific resource (the cause codes Tifa derives from TfL reason text) and states its shape (codes with labels) and ordering (rule-trial order). It distinguishes itself from siblings by tying directly to the `cause` field of `disruptions`. It lacks an explicit verb like 'list' or 'returns', relying on a noun phrase, which slightly blunts the statement of purpose.
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?
It gives a clear use context: these codes are what `disruptions` accepts as `cause`, and it explains the edge case that a null cause is unknown and `cause=unknown` finds those records. What's missing is an explicit statement of when to call this versus `disruptions` (e.g. 'call this first to discover valid filter values'), so the routing is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
coverageCoverageARead-onlyIdempotent
When Tifa was watching, over a time range (default: the 7 days to now; at most 90 days): the spans each source covered, the share of the range watched, the gaps nothing watched, and per UTC day the polls recorded (of 1440) and the polls that failed. Check it before reading a quiet period as a good one.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | End of the range (exclusive): an ISO 8601 time, epoch seconds, or a London date YYYY-MM-DD (to the end of that day, so the day is included). Default: now. | |
| from | No | Start of the range: an ISO 8601 time (UTC when it has no offset), epoch seconds (a whole number, or the same as text), or a London date YYYY-MM-DD (from the start of that day). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), yet the description still adds substantive behavior: the default window is the last 7 days and the queryable range is capped at 90 days, a constraint absent from the schema. Per-day granularity and the 1440-polls-per-day denominator are also disclosed. That is real added context above the annotations.
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?
One sentence that front-loads the time scope and then lists outputs in a tight enumerated series; every clause carries content. The opening 'When Tifa was watching' is the one piece of jargon a reader must infer a meaning for.
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?
With no output schema, the description effectively serves as the return-value contract by naming each reported metric, which is exactly what an agent needs to decide whether to call it. It omits pagination/shape details and cost, but for a two-optional-parameter read-only report it is close to sufficient.
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 description coverage is 100%, so the per-field format details (ISO 8601, epoch seconds, London dates) need no repeating. The description nonetheless adds the effective default range (7 days to now) and the 90-day maximum, which the schema does not state. Modest but genuine value beyond the structured fields.
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 enumerates the specific payload: per-source spans, share of range watched, uncovered gaps, and per-UTC-day poll counts (of 1440) and failures. That is far more concrete than the bare name 'coverage'. It never differentiates itself from siblings such as period_stats or current_status, and the phrase 'When Tifa was watching' is unexplained shorthand, so it falls short of a 5.
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?
Gives a crisp use condition: 'Check it before reading a quiet period as a good one' – i.e. use this to validate whether silence means no data versus no events. It offers no explicit when-not or named alternative among list_lines/period_stats, so it is clear guidance without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
current_statusStatus nowARead-onlyIdempotent
The status of every line right now, refreshed from TfL each minute: status text, worst severity, TfL's reasons, whether it is good service and whether a problem is live or planned works; lines with live problems first, then planned works, then good service. Also the disruptions open now (with their start times and causes), the time of the last poll, and stale: true when there has been no poll for over 5 minutes (or the history cannot be read), so the status may be out of date.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Only lines of this mode. Omit for every line. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent, yet the description adds substantial behavior beyond them: a one-minute refresh cadence from TfL, the ordering of results (live problems, planned works, good service), and the precise meaning of `stale` including the five-minute threshold and the history-unreadable fallback. That staleness contract is exactly the kind of context an agent needs to decide whether to trust the answer.
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?
Front-loaded with the core purpose, and every clause (ordering, stale definition, disruption payload) contributes information an agent would otherwise have to discover empirically. It is a dense run-on sentence spanning several semicolon-delimited ideas, which slightly hurts scannability but not substance.
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?
With no output schema, the description must describe the return shape, and it does: status text, worst severity, TfL reasons, good-service boolean, live-vs-planned flag, open disruptions with start times and causes, last poll time, and the stale flag. Nothing an agent needs in order to interpret the response is missing.
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 description coverage is 100% and the single `mode` parameter is fully documented with an enum, so the schema carries the load. The description's 'every line' hints at the omitted-mode default but adds no new semantics beyond the schema, which is the expected baseline here.
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 gives a specific verb-and-resource picture: it returns the current status of every line plus open disruptions, the last poll time, and a stale flag. The phrase 'every line' implicitly separates it from the per-line sibling line_status, but no sibling is named explicitly, so an agent must infer the boundary rather than read it.
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?
'Right now' and the stale flag imply this is the snapshot tool to use for present-moment questions, but there is no explicit when-to-use versus line_status, disruptions, or period_stats. The exclusion logic is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
disruptionsSearch disruptionsARead-onlyIdempotent
Recorded disruptions overlapping a time range (default: the 30 days to now; at most 366 days), oldest first. One row per spell of one severity on one line: start, end (null while it is still going on), minutes, severity, status text, category, planned or not, cause and TfL's reason, and whether the end is estimated (recording was paused). Filter by line, category, cause and planned. total is how many match; when next_cursor is not null, call again with it (and the same other arguments) for the next page.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | End of the range (exclusive): an ISO 8601 time, epoch seconds, or a London date YYYY-MM-DD (to the end of that day, so the day is included). Default: now. | |
| from | No | Start of the range: an ISO 8601 time (UTC when it has no offset), epoch seconds (a whole number, or the same as text), or a London date YYYY-MM-DD (from the start of that day). | |
| line | No | Only this line. A line id from list_lines, such as central, elizabeth, northern, dlr or weaver. | |
| cause | No | Only this cause (codes and labels from causes); unknown: no cause found. | |
| limit | No | Most rows to return. | |
| cursor | No | next_cursor from the previous call, for the next page. | |
| planned | No | true: planned works only; false: unplanned only. Omit for both. | |
| category | No | Only this kind: delays, suspended, closure, other or info (information only). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive, closed-world, and the description still adds real behavioral context beyond them: default and maximum time window, sort order, null end while an incident is ongoing, the 'estimated end' flag meaning recording was paused, and how pagination works. Pagination guidance ('call again with next_cursor and the same other arguments') is exactly the kind of behavioral detail annotations cannot convey.
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?
Front-loaded with the core question (what counts as a row) and then constraints, filtering, and pagination in that order. The middle enumeration of returned fields is long but earns its place since there is no output schema; only mild density cost.
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?
With no output schema available, the description takes on the responsibility of describing row shape (start, end, minutes, severity, status, category, planned, cause, estimated end) plus envelope fields total and next_cursor. Combined with the complete parameter schema, an agent has everything needed to call and page correctly.
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 description coverage is 100% — every parameter already documents format, defaults, enums, and cross-references to list_lines and causes. The description's 'filter by line, category, cause and planned' restates the schema rather than adding syntax or constraints beyond it, so the baseline 3 applies.
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?
States the specific resource and scope: recorded disruptions overlapping a time range, with the exact grain of a result row (one spell of one severity on one line). An agent can tell this apart from current-status siblings because the description makes clear it is historical/recorded overlap search, not a live snapshot.
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?
Gives clear operating context: default window (30 days to now), hard cap of 366 days, oldest-first ordering, and filter dimensions. It does not explicitly say when to prefer this over current_status or line_status, so it stops short of full sibling routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
line_statusOne lineARead-onlyIdempotent
One line in depth: its disruptions open now; the number of disruptions, minutes disrupted and share of watched time disrupted over the last 24 hours, 7, 30 and 90 days; its latest disruptions with TfL's reasons and their causes; and the London hours of the day at which its unplanned disruptions start.
| Name | Required | Description | Default |
|---|---|---|---|
| line | Yes | A line id from list_lines, such as central, elizabeth, northern, dlr or weaver. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false and openWorldHint=false, so the safety profile is covered. The description adds genuinely useful behavioral context beyond that: exactly which metric families and time windows are computed and that reasons/causes are TfL-sourced. It stops short of mentioning cost, latency, or data freshness.
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?
It is a single dense sentence, but the scope ('One line in depth') is front-loaded and every clause enumerates a distinct return category rather than padding. Slightly run-on, but no sentence is wasted.
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?
With no output schema and only one parameter, the description carries the burden of explaining what comes back, and it does so across all the metric groups and time windows. It does not describe response shape or ordering, which is a minor residual gap for an agent parsing the result.
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?
There is a single required parameter with 100% schema description coverage; the schema already states it is a line id from list_lines with concrete examples. The description adds nothing about the line argument, so the baseline 3 applies.
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 uses a specific verb ('One line in depth') and enumerates the exact resource scope: open disruptions, 24h/7/30/90-day disruption metrics, latest disruptions with TfL reasons and causes, and hour-of-day onset patterns. It is clearly differentiated from list_lines by being per-line and 'in depth,' though it never names the sibling tools it supersedes.
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?
The 'in depth' framing implies this is the detailed per-line drill-down versus the aggregate siblings (current_status, period_stats), but no explicit when-to-use or when-not guidance is given. An agent must infer that it should reach for this tool after list_lines resolves a line id, which is only hinted at via the schema's reference to list_lines.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_linesList linesARead-onlyIdempotent
Every line Tifa records, with its id, name and mode (tube, elizabeth-line, overground or dlr) and the lines it shares track with. The ids are what line_status, disruptions and period_stats take.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, closed-world, non-destructive, so the safety profile is fully covered. The description adds the useful detail that results are exhaustive ('every line') and what fields come back, but says nothing about ordering, pagination, or freshness. Useful context beyond annotations, but thin.
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?
Two sentences, zero waste: the first enumerates the payload, the second explains the downstream use of the ids. Front-loaded with the core identification.
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?
There is no output schema, so the description must carry the return-value burden — and it does, naming the exact fields and the mode values. For a zero-parameter, annotation-covered read tool, nothing an agent needs is missing.
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?
Zero parameters, so the baseline of 4 applies; there is nothing the description could add on this dimension. The description correctly doesn't waste words on inputs.
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?
States a specific verb (list) and resource (every line Tifa records) and enumerates the returned fields: id, name, mode, and shared-track lines. The mode enumeration (tube, elizabeth-line, overground, dlr) makes it distinguishable from the status-oriented siblings at a glance.
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?
Explicitly says the ids returned are what line_status, disruptions and period_stats take, which strongly implies this tool is the discovery/prerequisite step before those. It stops short of an explicit 'call this first / use when you need line ids' directive, so it lands at 4 rather than 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
period_statsPeriod figuresARead-onlyIdempotent
Figures for a period, for the network or one line: watched coverage, share of time with good service, unplanned and planned disruption counts, line-time disrupted, the change against the period before (when both were at least 80% watched), the most disrupted line, the longest unplanned disruption, the busiest day, the top causes, per-day (or per-week) bins and a per-line table. period names the period and gives prev and next: the end to pass for the period before or after. Unlike the other tools, times here (t0, t1, p0, starts and ends, bins, unwatched spans) are epoch seconds (UTC) as numbers; only period.first is ISO 8601.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | now (the period ends now), or a London date YYYY-MM-DD the period ends with. | now |
| line | No | Only this line; omit for the network. A line id from list_lines, such as central, elizabeth, northern, dlr or weaver. | |
| span | Yes | Length of the period: 1d, 7d, 30d, 90d or 365d. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive, closed-world behavior. The description adds meaningful context beyond that: times are epoch seconds here unlike other tools, change comparison requires both periods at least 80% watched, and results include per-day/per-week bins and a per-line table.
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?
Front-loaded with the core purpose and metrics, then the time-unit caveat. It is dense and somewhat run-on, but every clause conveys useful information for a complex aggregate tool.
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?
With no output schema, the description carries the return-value burden and does so by enumerating the figures, bins, and per-line table. It also clarifies the unusual epoch-seconds convention and the 80% watched caveat, making it sufficiently complete for an agent to use the tool correctly.
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 description coverage is 100%, so the three parameters are already well documented. The description adds a useful note that the returned `period` object exposes `prev` and `next` ends to pass when walking adjacent periods, but it does not materially deepen the input parameter semantics beyond the schema.
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?
Clearly states this returns period figures for the network or a single line, and enumerates the metrics in the result. It distinguishes itself from siblings mainly through bundling coverage/causes/disruptions into one aggregate and by the 'Unlike the other tools' time-format note, though it does not explicitly name which sibling to use instead.
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?
Usage is implied: omit `line` for network-wide figures, supply `line` for one line, and use `end`/`span` to choose the period. However, there is no explicit guidance on when to prefer this over `coverage`, `causes`, `disruptions`, or `line_status`.
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.
7 tool updates
v0.1.0- First observed
causes - First observed
coverage - First observed
current_status - First observed
disruptions - First observed
line_status - First observed
list_lines - First observed
period_stats
TDQS
Scored across 7 tools
Each tool has a distinct scope: list_lines and causes are reference lookups, current_status is a network-wide now snapshot, line_status is a per-line deep dive, disruptions is a paged record query, and period_stats/coverage are aggregate analytics. The main risk is overlap between line_status (which includes recent disruptions and per-line counts) and period_stats (which can also produce a per-line table), plus the near-pair naming current_status vs line_status. Descriptions do enough to steer an agent, but the boundary between per-line stats and period stats requires careful reading.
All names are lowercase snake_case, which is consistent, but the grammatical pattern is mixed: list_lines is verb_noun, current_status is adjective_noun, line_status is noun_noun, and disruptions/coverage/causes are bare nouns. The verb-style tools imply actions while the noun-style ones are queries, so an agent cannot infer the calling convention from the name alone. Still readable and domain-conventional.
Seven tools is well within the ideal range for a single-domain read-only analytics server. Each one earns its place: two reference lookups, one live snapshot, one per-line detail, one raw record query, one period aggregation and one data-quality checker. No obvious redundancy or filler.
The surface covers the lifecycle of observed disruption data well: enumerating lines and causes, live status, historical records with filtering and pagination, period comparisons, and coverage/staleness checks. Minor gaps remain — there is no way to fetch a single disruption by id, no manual poll/refresh trigger, and no direct way to explore inter-line (shared track) correlation despite list_lines exposing that information.
Maintenance
Related MCP Connectors
Read-only airport delay, weather, and 24h forecast tools for AI assistants. Airport-level only.
AI-callable tools for API mocking, testing, monitoring, security, and automation.
Travel tools for AI agents: plan and edit real trips, search stays and tours, import travel videos.
Geocoding, routing, isochrones, traffic, weather, and place search for AI agents. 19 MCP tools.
Related MCP Servers
- AlicenseCqualityDmaintenanceProvides real-time access to Transport for London data, enabling users to check tube line statuses, get disruption details, and plan journeys between London locations. Uses the official TfL Unified API to deliver live transport information through AI assistants.38 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to access real-time Transport for London data, including tube/bus arrivals, line status, journey planning, and disruptions.1-
- AlicenseBqualityDmaintenanceProvides real-time Transport for London journey data, including routes, alerts, and disruptions, allowing AI assistants to search journeys and get station information.13 npm4ISC
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to query live Transport for London data, including service status, arrivals, journey planning, bike points, crowding, and fares.1MIT