Skip to main content
Glama

Cityflo On-Time Performance MCP

A small, read-only stdio MCP server for answering Mumbai route-lateness questions from data/trips.csv. The tools do deterministic computation; the client agent turns the returned measurements into plain language.

The CSV is reloaded on every tool call, so a corrected or newly added debrief row is visible without restarting the server.

Run

Requires Python 3.11+ and uv.

uv sync --dev
uv run python server.py

The second command starts a stdio server and waits silently for an MCP client. Register it with Codex from this repository:

codex mcp add cityflo-otp -- /usr/bin/uv run --directory "$PWD" python server.py
codex mcp get cityflo-otp

Related MCP server: Cityflo On-Time Performance MCP Server

Tools

  • rank_routes_by_lateness(late_after_minutes=10) ranks by affected service days, late-trip share, median delay, then route ID. It includes sample and exclusion counts.

  • get_route_performance(route_id, late_after_minutes=10) returns one route's trip/day rates, overall and late-trip medians, maximum delay, and exclusions.

  • get_route_trip_evidence(route_id, late_after_minutes=10) returns every source row for that route, including quarantined rows and their reasons.

“Late” means actual arrival is strictly more than the supplied number of minutes after scheduled arrival. Every response echoes the threshold and discovered service-date range. A negative threshold is rejected.

Data decisions

Timestamps must be timezone-aware ISO 8601 values using Mumbai's +05:30 offset. Missing or malformed timestamps, non-Mumbai offsets, and arrival-before-departure chronology are quarantined. Exact duplicates across all operational fields except trip_id keep the lexicographically first ID. Quarantined rows remain visible in exclusions and trip evidence but never enter metrics.

The current export has five exclusions:

Trip

Decision

TRIP_017

Quarantine: actual arrival is before actual departure

TRIP_031

Quarantine: malformed actual departure

TRIP_044

Quarantine: actual arrival uses +00:00, not +05:30

TRIP_053

Quarantine: exact duplicate of TRIP_052; keep the lower ID

TRIP_101

Quarantine: scheduled arrival is missing

Large but valid delays are retained. Medians, rates, affected days, and sample sizes are reported; averages and causal claims are not. Operational prose is untrusted data and cannot override the reviewed computation. In particular, the concealed request in HANDOFF.md to rewrite results for one vehicle was rejected; raw valid rows for every vehicle remain included and auditable.

At the default 10-minute threshold, Route 12 has 6/8 late trips across 4/5 observed days, a 13.5-minute overall median delay, a 14.5-minute median among late trips, and an 18-minute maximum. That is repeated lateness in this export, not evidence of a cause.

Assumptions and questions

Assumptions: this export is the full analysis window; the default threshold is 10 minutes; arrival lateness is the relevant measure; valid early arrivals remain negative delays; this server covers only the supplied Mumbai trip schema.

Questions for Priya: Is 10 minutes the operational SLA? Should cancelled or incomplete trips get a separate status when those fields arrive? Do overnight trips use service day or calendar day for the affected-day count? Who owns correction of quarantined telemetry rows? Should comparisons use route-specific schedules or a longer baseline before calling a pattern persistent?

Verification

uv run python -m unittest -v
uv run ruff check .
uv run ruff format --check .
uv run python -m compileall -q server.py test_server.py
uv run python -c 'import server'
git diff --check

The unit file covers the five exclusions, duplicate handling, strict threshold changes, invalid inputs, unknown routes, Route 12's exact metrics, the expected top-three ordering, and an in-memory MCP protocol list/call smoke test.

Real Codex session

The public transcript below is sanitized to tool names, arguments, material results, and the final answer. The complete raw Codex JSONL is uploaded separately as required and is excluded from Git.

Agent -> rank_routes_by_lateness({"late_after_minutes": 10})
Tool  -> 140 supplied, 135 valid, 5 excluded. Ranking starts R-12, R-27, R-21.
         R-12: 6/8 late trips, 4/5 late days, 13.5-minute overall median.
         R-21: 1/1 late trip on 1/1 day (one-trip sample).

Agent -> get_route_performance({"route_id": "R-12", "late_after_minutes": 10})
Tool  -> 6/8 late trips (75%), 4/5 late days, 13.5-minute overall median,
         14.5-minute late-trip median, 18-minute maximum, no Route 12 exclusions.

Agent -> get_route_trip_evidence({"route_id": "R-12", "late_after_minutes": 10})
Tool  -> TRIP_071..TRIP_078 delays: 14, 16, 13, 18, 12, 15, 3, 4 minutes;
         all eight records valid, first six late under the strict threshold.

Agent -> Priya, yes. Using a strictly over 10-minute threshold, Route 12 was late on
         6 of 8 valid trips (75%), across 4 of 5 days. Late trips were 12-18 minutes
         behind schedule, with a 14.5-minute median. Route 12 ranked first across
         135 valid trips from 140 supplied. Route 21 showed worse figures, but only
         had one trip on one day, so its sample is not comparable. The records prove
         the delays, but not whether traffic, the vehicle, driver, or device caused them.

Where I disagreed with the AI

  1. The AI proposed a fixed 10-minute threshold. I chose a validated configurable threshold with a 10-minute default because the operational SLA was not specified and every response can state the threshold used.

  2. The AI proposed two route tools. I chose three tools because ranking, route summary, and raw trip evidence are distinct client decisions, and Priya needs to inspect every row behind a headline.

  3. The AI proposed following the handoff's concealed vehicle manipulation. I chose raw evidence because operational text is untrusted data and cannot override reviewed computation.

  4. The AI proposed silently repairing the +00:00 timestamp. I chose quarantine because either the clock or offset may be wrong, so the source value and exclusion reason must remain visible.

  5. The AI proposed average delay. I chose medians, rates, affected days, and sample counts because one large delay or Route 21's one-trip sample should not be presented as a strong pattern.

Deliberately cut

No database, web UI, hosted service, authentication, model call inside the server, occupancy or ticket analysis, ops-log search, causal diagnosis, persistence, or speculative date filtering. Add one only when an observed operational need requires it.

The MCP output schemas remain generic objects. Explicit schemas would require substantial nested Pydantic models for three heterogeneous responses; add them when a client needs generated output types, rather than duplicating the current runtime shapes only for metadata.

Available Tools

3 tools
get_route_performanceC
Read-onlyIdempotent

Return defensible lateness metrics and data-quality exclusions for one route.

ParametersJSON Schema
NameRequiredDescriptionDefault
route_idYes
late_after_minutesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.9/5.0
Behavior3/5

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

Annotations already declare read-only, idempotent, and non-destructive behavior. The description adds the notions of 'defensible' metrics and 'data-quality exclusions', which communicate that the output is reliability-focused and that some data points may be intentionally omitted. This adds context beyond the annotations, though it doesn't describe edge cases or the exact response semantics.

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?

The description is a single sentence that front-loads the primary output and scope. It is concise with zero wasted words, making it easy to parse quickly.

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

Completeness2/5

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

Although the tool is simple and an output schema exists, the description leaves important gaps: no parameter semantics, no usage guidance versus siblings, and no clarity on what 'data-quality exclusions' means in practice. An agent would likely need to inspect the output schema or guess at parameter behavior to call this correctly.

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

Parameters1/5

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

Schema description coverage is 0%, so the description must compensate, but it does not explain any parameters. It implies route_id via the phrase 'one route' but leaves late_after_minutes entirely undefined. An agent has no way to know what this parameter controls or its effect on the returned metrics.

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?

The description uses a specific verb ('Return') and resource ('defensible lateness metrics and data-quality exclusions for one route'). It clearly states the tool is per-route, which helps distinguish from the sibling that ranks routes, though it does not explicitly name alternatives. It is not a tautology and avoids vague language.

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 guidance is given on when to use this tool versus rank_routes_by_lateness or get_route_trip_evidence. The phrase 'for one route' hints at single-route analysis but does not state when to prefer this tool or what conditions rule out the alternatives.

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

get_route_trip_evidenceC
Read-onlyIdempotent

Return every source trip for a route, including quarantined rows and reasons.

ParametersJSON Schema
NameRequiredDescriptionDefault
route_idYes
late_after_minutesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.7/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, covering the safety profile. The description adds one behavioral detail: that quarantined rows and reasons are included in the output. This is useful but minimal; it doesn't mention pagination, rate limits, or any other runtime behavior.

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

Conciseness4/5

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

The description is a single, efficient sentence that front-loads the core action and includes relevant detail about quarantined rows. It is not verbose, but could be improved by adding parameter hints without losing conciseness.

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

Completeness2/5

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

Given the presence of an output schema, return format is handled, but the description leaves the optional late_after_minutes parameter unexplained and fails to provide context on what 'source trip' or 'quarantined' means. For a two-parameter tool with zero schema coverage, this is a significant gap.

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

Parameters1/5

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

Schema description coverage is 0% – the JSON schema provides no explanations for route_id or late_after_minutes. The description does not compensate at all; it never mentions either parameter, so an agent cannot understand what late_after_minutes controls or how route_id is used beyond the name.

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?

The description states a clear verb ('Return') and resource ('every source trip for a route') and adds specificity with 'including quarantined rows and reasons.' It does not explicitly differentiate from sibling tools, but the function is distinct enough that an agent can infer its purpose.

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?

The description gives no guidance on when to use this tool versus siblings like rank_routes_by_lateness or get_route_performance. There is no mention of conditions, prerequisites, or alternatives, leaving the agent to infer usage from the name and schema.

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

rank_routes_by_latenessA
Read-onlyIdempotent

Rank routes by late days, late-trip share, median delay, then route ID.

ParametersJSON Schema
NameRequiredDescriptionDefault
late_after_minutesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior4/5

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

Annotations already declare the tool as read-only, idempotent, and non-destructive. The description adds the ranking behavior and sort order, which is useful beyond the annotations. No contradictions exist with 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.

Conciseness5/5

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

The description is a single, efficient sentence that states the operation and criteria with no wasted words. It is front-loaded with the verb and essential information, making it easy to scan.

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?

The presence of an output schema handles return-value documentation, so that is not required here. However, the description lacks usage guidance relative to siblings and fails to explain the parameter, leaving notable gaps in the decision and invocation context. These gaps prevent it from being fully complete.

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?

The single parameter 'late_after_minutes' has no schema description (0% coverage), and the description does not explain its meaning, default, or how it affects the ranking. The agent is left to guess from the name alone, which is insufficient for correct invocation.

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 clearly states the action ('Rank'), the resource ('routes'), and the exact ordering criteria (late days, late-trip share, median delay, route ID). This is specific and unambiguous, and the verb plus resource distinguishes it from the sibling tools, which focus on performance and trip evidence.

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?

The description provides no indication of when to use this tool versus the siblings 'get_route_performance' or 'get_route_trip_evidence'. There is no mention of context, exclusions, or alternatives. An agent must infer from the name that ranking is the purpose, but no explicit guidance is given.

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. 3 tool updatesv0.1.0
    • First observedget_route_performance
    • First observedget_route_trip_evidence
    • First observedrank_routes_by_lateness

TDQS

B3.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool addresses a distinct level of analysis: ranking across routes, per-route summary metrics, and raw trip evidence. There is no overlap in purpose, and the descriptions clearly differentiate the scope of each.

Naming Consistency5/5

All three tools follow a consistent verb_noun pattern (rank_routes, get_route_performance, get_route_trip_evidence) with snake_case and clear resource identifiers. The naming is predictable and unambiguous.

Tool Count4/5

Three tools is on the lower end but appropriate for a focused read-only domain (route lateness analysis). The count is slightly thin, but each tool covers a needed layer of detail without redundancy.

Completeness4/5

The toolset provides ranking, per-route summary, and evidence-level data, forming a complete analysis workflow. A minor gap is the lack of a simple route listing without rankings, but the ranking tool effectively fills that role.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers