Skip to main content
Glama

STPT MCP

CI

An unofficial, standalone Model Context Protocol server for STPT public transport in Timișoara, Romania. It exposes live arrivals, date-aware schedule fallback, service alerts, vehicle positions, and an offline stop/route index to MCP clients over stdio or Streamable HTTP.

This project is not affiliated with, endorsed by, or operated by STPT or the Timișoara municipality. Upstream data is used with caching and conservative request limits. Do not use it for safety-critical decisions.

In action

An assistant answering "when does the 14 get to Piata Marasti?" with the stpt MCP tools

Ask in plain language. The assistant calls search_stops to resolve the stop, then get_arrivals for the departures, and answers with the next bus. The same tools work from any MCP client — Claude, Hermes, Cursor, or an agent of your own.

Related MCP server: @cyanheads/onebusaway-mcp-server

Highlights

  • Seven read-only MCP tools with typed output schemas.

  • Live-first arrivals with date- and shape-aware schedule fallback, explicit provenance, and last-known-good stale data.

  • Bounded TTL caches with single-flight refresh. Fifty concurrent calls to one stop produce one live upstream request, not fifty.

  • Bounded upstream concurrency, timeouts, retries with jitter, Retry-After support, and response-size limits.

  • Bundled GTFS-derived network snapshot, so stop/route discovery works offline.

  • stdio and Streamable HTTP through the official MCP Python SDK.

  • Secure HTTP defaults: loopback bind, DNS-rebinding protection, Host/Origin validation, body limits, bounded sessions/concurrency, optional bearer auth, and built-in OAuth 2.1 sign-in for hosted clients.

  • Deterministic test suite with real MCP client tests for stdio and HTTP.

Requirements

  • Python 3.11 or newer.

  • uv for development (optional for end users).

  • Network access to STPT endpoints for live data. Discovery tools work offline.

Install

From PyPI, with one command:

pip install stpt-mcp
stpt-mcp --version

Or run it without installing anything permanently, using uv:

uvx stpt-mcp --version

From a checkout:

uv sync --all-groups
uv run stpt-mcp --version

Or from a wheel/sdist:

python -m venv .venv
. .venv/bin/activate
pip install dist/stpt_mcp-0.3.7-py3-none-any.whl
stpt-mcp --version

stpt-mcp with no subcommand is equivalent to stpt-mcp serve and defaults to stdio.

Quick start

stdio

uv run stpt-mcp serve --transport stdio

The simplest MCP client configuration lets uvx fetch the package from PyPI on first use:

{
  "mcpServers": {
    "stpt": {
      "command": "uvx",
      "args": ["stpt-mcp", "serve", "--transport", "stdio"]
    }
  }
}

A minimal MCP client configuration using an installed console script:

{
  "mcpServers": {
    "stpt": {
      "command": "stpt-mcp",
      "args": ["serve", "--transport", "stdio"]
    }
  }
}

From a checkout without installing the script:

{
  "mcpServers": {
    "stpt": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/stpt-mcp",
        "stpt-mcp",
        "serve",
        "--transport",
        "stdio"
      ]
    }
  }
}

The server writes logs to stderr and never writes protocol data to stdout.

Streamable HTTP

uv run stpt-mcp serve --transport http
# endpoint: http://127.0.0.1:8000/mcp
# health:   http://127.0.0.1:8000/healthz

Generate a bearer token for any non-local deployment:

export STPT_MCP_AUTH_TOKEN="$(stpt-mcp token)"
stpt-mcp serve --transport http

Clients then send Authorization: Bearer <token>. /healthz stays unauthenticated.

For hosted clients that support OAuth, set a public origin and a sign-in password instead of handing out a header token:

export STPT_MCP_PUBLIC_URL=https://mcp.example.com
export STPT_MCP_OAUTH_PASSWORD='replace-with-a-long-random-password'
stpt-mcp serve --transport http

Claude's custom connector can then use Sign in now with Register automatically. See OAuth sign-in for the reverse-proxy paths and connector steps.

Minimal client configuration:

{
  "mcpServers": {
    "stpt": {
      "url": "http://127.0.0.1:8000/mcp"
    }
  }
}

HTTP is bound to 127.0.0.1 by default. Binding elsewhere requires an explicit STPT_MCP_ALLOWED_HOSTS allowlist. If you expose the server beyond localhost, also set STPT_MCP_AUTH_TOKEN and terminate TLS at a reverse proxy. For a complete public deployment, see Public HTTPS, OAuth sign-in, Deployment, and Security.

Tools

Tool

Purpose

search_stops

Search stops by ID, name, or line (diacritic-insensitive).

get_stop

Stop coordinates, lines, directions, destinations, neighbors.

list_routes

Routes with mode, category, and colors.

get_arrivals

Live arrivals, with schedule fallback and line/limit filters.

get_alerts

Service alerts with active-period and stop/line filters.

get_vehicles

Vehicle positions with observation timestamps and staleness.

get_service_status

Readiness, version, feed metadata, cache statistics.

Every result is a typed envelope. Data-source problems are returned as status: "partial" or "error" with a structured error, not a traceback:

{
  "status": "partial",
  "error": {
    "code": "upstream_http_error",
    "message": "Live arrivals were unavailable; schedule data is being used.",
    "retryable": true,
    "upstream": "live arrivals"
  },
  "provenance": {
    "source": "schedule",
    "fetched_at": "2026-10-21T10:30:00Z",
    "observed_at": null,
    "is_stale": false,
    "data_age_seconds": 0.0,
    "fallback_reason": "live_unavailable",
    "warnings": ["Live arrivals were unavailable; trying the schedule."]
  },
  "stop_id": "59",
  "stop_name": "Piața Sfânta Maria",
  "line_filter": null,
  "lookahead_minutes": 180,
  "count": 9,
  "arrivals": []
}

arrivals is abbreviated above. Full schemas are generated from the Pydantic models and exposed through MCP. See Tools for arguments, semantics, error codes, and more examples.

Configuration

Configuration is read from environment variables; a few common values also have CLI flags. Defaults are safe for local use.

Variable

Default

Purpose

STPT_MCP_TRANSPORT

stdio

stdio or http.

STPT_MCP_HOST

127.0.0.1

HTTP bind address.

STPT_MCP_PORT

8000

HTTP port.

STPT_MCP_PATH

/mcp

Streamable HTTP path.

STPT_MCP_LOG_LEVEL

INFO

Python log level.

STPT_MCP_AUTH_TOKEN

unset

Optional static bearer token (16+ chars).

STPT_MCP_ALLOWED_HOSTS

loopback

Required for non-loopback binds.

STPT_MCP_ALLOWED_ORIGINS

loopback

Browser origins allowed by the SDK.

STPT_MCP_MAX_REQUEST_BODY_SIZE

1048576

HTTP request body limit in bytes.

STPT_MCP_MAX_CONCURRENT_HTTP_REQUESTS

100

HTTP concurrency cap.

TTLs, stale windows, upstream timeouts, retry policy, and cache bounds are also configurable. See Configuration for the complete table.

How data selection works

  1. A fresh cached arrival snapshot for the stop is returned with no request.

  2. Otherwise live predictions are fetched. They are used only if their freshness is verified against the real clock: the feed's implied clock must match it, must not stand still across fetches, and must move with real time.

  3. If live is empty, unavailable, stale, or unverified, the departures come from the bundled GTFS timetable for the local service date (source: "schedule", live: false). No website is contacted for this.

  4. If the refresh fails, last-known-good data may be served inside the stale window.

  5. If no usable data remains, the tool returns a structured error. Stale or unverified live predictions are never returned as arrivals, because frozen minutes counted from now look like real departures.

provenance.source identifies live, schedule, or network data; provenance.is_stale and data_age_seconds quantify freshness; fallback_reason explains why live data was not used. Live arrivals expose no upstream observation timestamp, so observed_at is the fetch time for live arrivals and null for schedules.

Development

uv sync --all-groups
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytest
uv run pytest --cov --cov-report=term-missing
uv run bandit -c pyproject.toml -r src
uv run pip-audit
uv build

The default suite never contacts live STPT services. It uses deterministic fixtures, injected clocks, httpx.MockTransport, and loopback fake upstreams. An opt-in live smoke test is available:

STPT_MCP_RUN_NETWORK_TESTS=1 uv run pytest -m network

See Testing, Architecture, and Contributing.

The same STPT data, exposed for different runtimes. If one of these fits your setup, the others probably do too.

Project

Runtime

Use it for

STPT MCP (this repo)

Any MCP client

Talking to your transport data

STPT Transit

Home Assistant

Automations, sensors, and dashboards

Inky pHAT

Raspberry Pi

A glanceable e-ink departures display

All three are unofficial and are not affiliated with STPT.

Limitations

  • Upstream endpoints are unofficial public interfaces and may change without notice.

  • The bundled network snapshot and timetable are point-in-time GTFS-derived artifacts that end on 2026-12-31; after that the schedule fallback reports schedule_unavailable until a release regenerates them. The feed version and service range are exposed by get_service_status.

  • STPT's live arrivals feed carries no timestamps. Freshness is inferred from its display times, so the first fetch of a stop after a restart cannot prove the feed is running.

  • Each process has its own in-memory cache; multi-worker deployments do not share entries.

  • OAuth is single-owner and keeps tokens in memory; a restart requires clients to sign in again. It is not a multi-user identity provider.

  • Live arrivals do not carry an upstream observation timestamp.

Author

Created and maintained by Vlad Ohai (GitHub).

License and attribution

MIT licensed, © 2026 Vlad Ohai. STPT data remains the property of its respective owners. See LICENSE and NOTICE. OpenStreetMap-derived coordinates, if present, are © OpenStreetMap contributors under the ODbL.

Available Tools

7 tools
get_alertsGet STPT service alertsA
Read-onlyIdempotent

Get current or historical STPT service alerts. Filters by stop ID and/or line when supplied. Active periods are evaluated in Europe/Bucharest.

NameRequiredDescriptionDefault
lineNoOptional line filter.
limitNoMaximum number of alerts.
stop_idNoOptional STPT stop ID.
active_onlyNoReturn only alerts active at the current instant.

Output Schema

NameRequiredDescription
lineNo
countYes
errorNoStructured failure, if any.
alertsNo
statusYesOverall result status.
stop_idNo
provenanceNoSource and freshness metadata.
active_onlyYes

TDQS

A3.8/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, openWorld, and non-destructive, so the safety profile is covered. The description adds genuinely useful non-obvious context: that active-period evaluation happens in Europe/Bucharest and that both current and historical alerts are returned. It stops short of noting data freshness or rate limits.

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?

Two tight sentences, front-loaded with the core action, and the timezone note earns its place as non-obvious behavior. No filler.

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

Completeness4/5

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

With an output schema, full schema coverage, and rich annotations, the description covers what the agent needs to call it correctly. Only the sibling relationship to get_service_status is unaddressed, which is a minor gap.

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 four parameters are already documented. The description reinforces that line and stop_id are optional filters ('when supplied') and adds timezone context for active_only, but supplies no format or semantics the schema lacks. Baseline 3 applies.

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

Purpose4/5

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

States a specific verb and resource ('Get ... STPT service alerts') and clarifies the current-vs-historical scope. It is distinguishable from most siblings like get_arrivals or get_vehicles, though it never explicitly contrasts with get_service_status, which is the nearest sibling and could be confused with it.

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

Usage Guidelines3/5

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

'Filters by stop ID and/or line when supplied' implies how to narrow results but gives no explicit when-to-use guidance or exclusions relative to get_service_status or get_arrivals. The agent must infer that this is the tool for alert text as opposed to arrival/status data.

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

get_arrivalsGet STPT arrivalsA
Read-onlyIdempotent

Get upcoming arrivals at an STPT stop. Live data is used only when its freshness is verified; otherwise departures come from the bundled GTFS timetable. Check provenance.freshness, provenance.fallback_reason, and each arrival's live flag.

NameRequiredDescriptionDefault
lineNoOptional line filter such as '2', 'E3', or 'M32'.
limitNoMaximum number of arrivals to return.
stop_idYesNumeric STPT stop ID.

Output Schema

NameRequiredDescription
countYes
errorNoStructured failure, if any.
statusYesOverall result status.
stop_idYes
arrivalsNo
stop_nameYes
provenanceNoSource and freshness metadata.
line_filterNo
lookahead_minutesNo

TDQS

A3.9/5.0
Behavior4/5

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

Annotations already cover read-only/idempotent safety, and the description adds genuinely new behavior: live data only when freshness is verified, GTFS fallback otherwise, and which fields reveal provenance. That is meaningful context beyond the structured hints, though it doesn't mention rate limits or ordering guarantees.

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?

Three tight sentences, front-loaded with the purpose, then the data-source caveat, then the fields to check. No sentence is redundant.

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?

An output schema exists so return values need not be explained, yet the description helpfully points at the exact provenance fields an agent should inspect. Nothing necessary for correct invocation 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%, so stop_id, line, and limit are fully documented in the schema. The description adds no syntax, format, or defaulting detail beyond that, so the baseline 3 applies.

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

Purpose4/5

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

States a specific verb and resource ('Get upcoming arrivals at an STPT stop'), which clearly separates it from get_stop, get_vehicles, and the other siblings. It doesn't explicitly name an alternative tool to route away to, but the resource is unambiguous.

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

Usage Guidelines3/5

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

The description implies the use case (fetching arrivals for a stop) but gives no explicit when-to-use vs when-not guidance and never references sibling tools like get_stop or get_vehicles. The fallback explanation is behavioral rather than selection guidance.

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

get_service_statusGet STPT MCP service statusA
Read-onlyIdempotent

Report server readiness, version, bundled network feed metadata, and bounded cache statistics. Contains no secrets.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
errorNoStructured failure, if any.
readyYes
cachesNo
statusYesOverall result status.
versionYes
timezoneYes
live_feedNoFreshness of the live arrivals feed. Null until a stop has been fetched.
provenanceNoSource and freshness metadata.
started_atNo
current_timeYes
network_feed_versionYes
network_generated_atYes
network_service_rangeNo

TDQS

A4.3/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, non-destructive, and openWorld, so the safety profile is covered. The description adds genuine context beyond them: that the response includes bundled feed metadata and 'bounded' cache statistics, plus the assurance 'Contains no secrets,' which matters for an externally exposed status endpoint.

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?

Two short sentences with no filler, and the primary payload (what is reported) is front-loaded before the safety note.

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?

An output schema exists, so return-value details need not be in the description, and the annotations cover the safety contract. For a zero-parameter read-only status tool, nothing an agent needs in order to call it is missing.

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

Parameters4/5

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

The tool takes zero parameters, so the baseline is 4; there is nothing for the description to disambiguate. It correctly avoids inventing parameter semantics that do not exist.

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?

Uses a specific verb ('Report') with a concrete list of resources: readiness, version, bundled network feed metadata, and bounded cache statistics. This is unmistakably a service-health tool and cannot be confused with any of the transit-data siblings such as get_vehicles or get_alerts.

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

Usage Guidelines3/5

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

The stated contents imply a health/diagnostics use case, but the description never says when to call it, whether it should be polled, or how it relates to the sibling tools. Since no sibling covers the same ground, the missing guidance is less costly, leaving it at implied-only usage.

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

get_stopGet STPT stop detailsA
Read-onlyIdempotent

Get coordinates, serving lines, directions, terminals, and neighboring stops for a numeric STPT stop ID.

NameRequiredDescriptionDefault
stop_idYesNumeric STPT stop ID.

Output Schema

NameRequiredDescription
stopNo
errorNoStructured failure, if any.
statusYesOverall result status.
provenanceNoSource and freshness metadata.

TDQS

A3.6/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false, so the safety profile is fully covered. The description adds useful scope information by naming the returned facets (including neighboring stops, a non-obvious extra), but says nothing about behavior on invalid IDs or how fresh/complete the data is.

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?

One tightly packed sentence: the action leads, the returned fields are enumerated, and the input constraint trails. Zero filler.

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

Completeness4/5

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

With an output schema covering return shape and annotations covering safety, the description is nearly complete. The only residual gap is how an agent should obtain a valid stop ID (e.g., via search_stops), which would round it out.

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% and the single parameter is documented as 'Numeric STPT stop ID.' The description restates the same constraint without adding format, range, or sourcing guidance, so baseline 3 applies.

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

Purpose4/5

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

States a specific verb+resource ('Get ... stop details') and enumerates exactly what is returned: coordinates, serving lines, directions, terminals, and neighboring stops, keyed by a numeric STPT stop ID. This distinguishes it well from siblings like get_arrivals, though it does not explicitly contrast it with search_stops.

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

Usage Guidelines3/5

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

Usage is only implied by the 'numeric STPT stop ID' phrasing — an agent can infer it is the lookup-by-ID tool versus search_stops' query-based discovery, but no when/when-not or alternative is named. No prerequisites or failure conditions for an unknown ID are mentioned.

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

get_vehiclesGet STPT vehicle positionsA
Read-onlyIdempotent

Get current STPT vehicle positions, optionally filtered by line. Each vehicle includes its upstream observation timestamp and staleness flag.

NameRequiredDescriptionDefault
lineNoOptional line filter.
limitNoMaximum number of vehicles.

Output Schema

NameRequiredDescription
countYes
errorNoStructured failure, if any.
statusYesOverall result status.
vehiclesNo
provenanceNoSource and freshness metadata.
line_filterNo

TDQS

A4/5.0
Behavior4/5

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

Annotations already establish this as a safe, read-only, idempotent operation, so the description doesn't need to cover safety. It adds useful behavioral context by disclosing that each vehicle carries an upstream observation timestamp and staleness flag, which tells the agent how to interpret data freshness. It does not mention pagination or limit behavior, but that is a minor gap given the annotations and output schema.

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?

Two concise sentences, front-loaded with the purpose and followed by the optional filter and data-quality note. No filler or redundancy.

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

Completeness4/5

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

The tool is simple: two optional parameters, full schema coverage, annotations covering safety and openness, and an output schema that documents return values. The description is complete enough for correct invocation. It could be improved by noting when to prefer this over get_arrivals or other siblings, but that is outside the core completeness of this definition.

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 both the 'line' filter and 'limit' cap are already documented in the schema. The description only restates the optional line filter and adds no syntax, format, or default details beyond the schema. Baseline 3 is appropriate.

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 description states a specific verb ('Get') and resource ('current STPT vehicle positions'), making it immediately distinguishable from siblings like search_stops, list_routes, and get_arrivals. No tautology or ambiguity.

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

Usage Guidelines3/5

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

It implies usage through 'current' positions and an optional line filter, but gives no explicit when-to-use/when-not guidance, no alternatives among the sibling tools, and no prerequisites. The reader must infer the role of this tool from the resource name alone.

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

list_routesList STPT routesB
Read-onlyIdempotent

List STPT tram, trolleybus, bus, metropolitan, express, and vaporetto routes from the bundled GTFS-derived network snapshot.

NameRequiredDescriptionDefault
modeNoOptional mode filter.

Output Schema

NameRequiredDescription
countYes
errorNoStructured failure, if any.
routesNo
statusYesOverall result status.
provenanceNoSource and freshness metadata.

TDQS

B3/5.0
Behavior3/5

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

Annotations already declare read-only, idempotent, non-destructive behavior, so the safety profile is covered. The description adds one genuinely useful fact beyond that: results come from a static bundled GTFS-derived snapshot rather than live data. It says nothing about result size, ordering, or pagination.

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?

A single front-loaded sentence with the resource and data source in the opening clause. It is terse and free of filler, though the mode list it adds is both redundant with the schema and inaccurate.

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

Completeness3/5

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

An output schema exists, so return-value explanation is not required, and there are no required parameters. What is missing is any guidance on filtering semantics or on the static-vs-live distinction's implications, plus the inaccurate mode list, leaving the definition only minimally sufficient.

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

Parameters2/5

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

Schema coverage is 100% for the single optional `mode` parameter, which sets a baseline of 3, but the description names modes ("metropolitan, express") that are not valid values in the VehicleMode enum (tram, trolleybus, bus, vaporetto, unknown). This misleads the agent rather than adding meaning, so it scores below baseline.

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

Purpose4/5

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

States a specific verb and resource ("List STPT ... routes") and scopes it to the bundled GTFS-derived snapshot, which clearly separates it from siblings like search_stops or get_vehicles. It does not explicitly name an alternative, so it stops short of full sibling differentiation.

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

Usage Guidelines2/5

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

No statement of when to call this versus search_stops, get_stop, or get_service_status, and no guidance on when the mode filter should be applied. The agent must infer usage entirely from the name and the mode enumeration.

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

search_stopsSearch STPT stopsA
Read-onlyIdempotent

Search the bundled STPT network by stop ID, stop name, or line. Uses diacritic-insensitive matching. Returns up to 50 stops.

NameRequiredDescriptionDefault
limitNoMaximum number of stops to return.
queryYesStop code, stop name fragment, or line number.

Output Schema

NameRequiredDescription
countYes
errorNoStructured failure, if any.
queryYes
stopsNo
statusYesOverall result status.
provenanceNoSource and freshness metadata.

TDQS

A4/5.0
Behavior4/5

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

Annotations already declare read-only, idempotent, non-destructive and open-world. The description adds genuine behavioral detail beyond them: diacritic-insensitive matching and a hard result cap of 50, both of which affect how an agent should query and interpret results.

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?

Three short sentences with no filler, and the core action and scope are front-loaded before the matching and result-count details.

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

Completeness4/5

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

With an output schema present and full parameter documentation, the description covers what is needed to call the tool correctly. The only omission is how the tool relates to the sibling get_stop for exact-ID lookups.

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 both query and limit are already documented with examples and bounds. The description's mention of 'up to 50 stops' and the match types only restates schema-level facts, so the baseline of 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?

Specific verb (Search) plus resource (STPT stops) and explicit scope ('bundled STPT network'). It also names the three match keys, so an agent can immediately tell this apart from get_stop (single-stop lookup) and list_routes.

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

Usage Guidelines3/5

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

The description implies usage through the set of searchable keys (stop ID, name, line), which lets an agent infer when to reach for it. However, it never explicitly says when to prefer it over the sibling get_stop, nor any exclusions or prerequisites.

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. 7 tool updatesv0.3.7
    • Changedget_alerts5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
    • Changedget_arrivals5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
    • Changedget_service_status7 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • addedOutput schema / $defs / LiveFeedStatus
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "assessed_at": {
        +      "description": "When that fetch was assessed, in UTC.",
        +      "format": "date-time",
        +      "title": "Assessed At",
        +      "type": "string"
        +    },
        +    "clock_unchanged_seconds": {
        +      "description": "How long that stop's feed clock has stood still while real time advanced.",
        +      "minimum": 0,
        +      "title": "Clock Unchanged Seconds",
        +      "type": "number"
        +    },
        +    "freshness": {
        +      "$ref": "#/$defs/Freshness"
        +    },
        +    "recent_stops": {
        +      "additionalProperties": {
        +        "type": "integer"
        +      },
        +      "description": "Freshness of every stop fetched in the last 15 minutes, counted.",
        +      "propertyNames": {
        +        "$ref": "#/$defs/Freshness"
        +      },
        +      "title": "Recent Stops",
        +      "type": "object"
        +    },
        +    "skew_seconds": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "default": null,
        +      "description": "The feed's implied clock minus the real clock, in seconds. Positive means the feed runs ahead. Null when the feed carried no clock reference.",
        +      "title": "Skew Seconds"
        +    },
        +    "stop_id": {
        +      "description": "Stop of the most recent live arrivals fetch.",
        +      "title": "Stop Id",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "stop_id",
        +    "assessed_at",
        +    "freshness",
        +    "clock_unchanged_seconds"
        +  ],
        +  "title": "LiveFeedStatus",
        +  "type": "object"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
      • addedOutput schema / properties / live_feed
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/LiveFeedStatus"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Freshness of the live arrivals feed. Null until a stop has been fetched."
        +}
    • Changedget_stop5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
    • Changedget_vehicles5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
    • Changedlist_routes5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
    • Changedsearch_stops5 fields changed
      • addedOutput schema / $defs / Freshness
        Added value: +{
        +  "enum": [
        +    "fresh",
        +    "stale",
        +    "unverified"
        +  ],
        +  "title": "Freshness",
        +  "type": "string"
        +}
      • changedOutput schema / $defs / Provenance / properties / data_age_seconds / description
        Previous value: -"Age of the source/cache entry when returned."New value: +"Age of the upstream observation when returned, in seconds. Null when the age cannot be measured."
      • addedOutput schema / $defs / Provenance / properties / freshness
        Added value: +{
        +  "$ref": "#/$defs/Freshness",
        +  "default": "unverified",
        +  "description": "fresh: an upstream observation time was checked against this server's clock and is within tolerance. stale: the check failed (old, clock-skewed, or frozen data) or a cache entry past its TTL is served. unverified: the data carries no upstream clock reference (schedules, the network snapshot, alerts), so it cannot be confirmed as current."
        +}
      • changedOutput schema / $defs / Provenance / properties / is_stale / description
        Previous value: -"True when the stale cache policy supplied the data."New value: +"True when stale data is being returned, whether from cache or upstream."
      • changedOutput schema / $defs / Provenance / properties / observed_at / description
        Previous value: -"Upstream observation timestamp. Live arrivals expose no timestamp, so this is the fetch time; schedules describe future service and leave it null; vehicles use their upstream timestamp."New value: +"Upstream observation timestamp in UTC. Live arrivals derive it from the feed's own display times and remaining minutes; it is null when the feed carries no clock reference or is rejected as stale. Schedules describe future service and leave it null; vehicles use their upstream timestamp."
  2. 7 tool updatesv0.2.0
    • First observedget_alerts
    • First observedget_arrivals
    • First observedget_service_status
    • First observedget_stop
    • First observedget_vehicles
    • First observedlist_routes
    • First observedsearch_stops

TDQS

A3.9/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct transit object or concern: stop search, stop detail, route listing, arrivals, service status, alerts, and vehicle positions. Boundaries are clear even where arrivals and vehicle positions both relate to live transit data.

Naming Consistency5/5

All tool names use a consistent snake_case verb_noun convention: search_stops, get_stop, list_routes, get_arrivals, get_service_status, get_alerts, get_vehicles. No ambiguous or inconsistent naming styles appear.

Tool Count5/5

Seven tools is well-scoped for a transit MCP server, covering discovery, detail, arrivals, alerts, vehicles, and health without bloat. Each tool has a clear role.

Completeness4/5

The surface covers core transit workflows: finding stops, viewing routes and stop details, checking arrivals, alerts, and vehicle positions. Minor gaps exist, such as explicit route-detail or trip-planning operations, but core use cases are supported.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    MCP server for Chelaile realtime public transit data in China, including bus and metro arrivals, vehicle positions, nearby stops, timetables, and route planning. It runs locally over stdio with no login or account configuration required.
    15
    199 npm
    23
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Private MCP server adapter for RedTransporteAPI, providing read-only access to Santiago de Chile public transit data including stops, routes, real-time iBus/RED predictions, and RAPTOR journey planning, secured with deny-by-default bearer token authentication over stdio or streamable HTTP.
    15
    1
    GPL 3.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables querying real-time BART and SF Muni transit data, including departures, trip planning, fares, advisories, routes, alerts, vehicle positions, and schedules, from any MCP-compatible client.
    1
    MIT