Cityflo on-time performance MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Cityflo on-time performance MCPWas Route 12 late this week?"
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.
Cityflo on-time performance MCP
MCP server for Mumbai North on-time performance — the slice Priya’s handoff actually asks for:
Was route 12 late this week, and by how much — and which trips prove it?
Built for the Cityflo AI Engineer take-home. Domain chosen: on-time performance only. Occupancy, support-ticket triage, and ops-log summarisation were deliberately cut so the tools stay sharp and auditable.
Ops people get a plain-language answer from an MCP client (Cursor or the included Kimi agent). Deterministic TypeScript does the arithmetic; the model only phrases the result. Every headline can be drilled into trip-level evidence.
Stack: Node.js 20.x, TypeScript, @modelcontextprotocol/sdk. No database — data/trips.csv is loaded in memory.
Install
git clone <this-repo>
cd Cityflo-Assignment # or your clone path
npm install
cp .env.example .envEnv var | Required for | Notes |
|
| Moonshot / Kimi path only — never deploy this to Vercel |
| Hosted | Min 16 chars; |
| Optional | Override default |
Related MCP server: Cityflo OTP MCP
How to run
1. Local stdio MCP (Cursor — primary path)
npm run mcp.cursor/mcp.json already points Cursor at this server. After npm install, reload MCP in Cursor and ask:
Was route 12 late this week, and by how much?
Production stdio (no tsx):
npm run build:mcp
npm run start:mcp
# MCP command: node dist/mcp/server.js2. End-to-end with a real client (no LLM)
npm test # domain + MCP integration + JWT (36 tests)
npm run e2e # multi-step MCP client → demos/mcp-e2e-session.md3. Optional Kimi K2.6 terminal agent
# set MOONSHOT_API_KEY in .env
npm run agent -- "Was route 12 late this week, and by how much?"Transcripts when generated: demos/agent-session.md, demos/mcp-e2e-session.md.
4. Hosted Streamable HTTP (optional for Cityflo demos)
Live: https://cityflo-on-time-mcp.vercel.app
Open the site → sign in (
cityflo.mcp@gmail.com/tester) → Copy Cursor MCP JSON (JWT embedded).Or mint a token:
npm run mint-token, then:
{
"mcpServers": {
"cityflo-on-time": {
"url": "https://cityflo-on-time-mcp.vercel.app/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_JWT"
}
}
}
}Endpoint | Auth | Purpose |
| internal login | Cityflo-styled gateway — copy MCP config |
| none | Liveness + trip count |
|
| Streamable HTTP MCP |
Local hosted smoke: JWT_SECRET=… npm run dev then MCP_BASE_URL=http://localhost:3000 npm run smoke:http.
Tools (3)
Computation stays in tools. The model phrases language only.
Tool | What it does |
| Route-level late rate, median/max lateness, daily pattern, late trip ids, rules applied |
| Trip-by-trip scheduled vs actual + classification + exclusion reasons (requires |
| Quarantined / duplicate rows by reason and GPS device |
Invalid filters return structured MCP isError payloads — not silent wrong numbers.
Human/model boundary: tools return JSON numbers and trip ids; the client/agent narrates. Answering Priya honestly is a multi-step loop (summary → evidence → optional quality), not one call.
Assumptions (under-specified parts of the brief)
Late = arrival lateness
actual_arrival − scheduled_arrivalstrictly greater than the threshold (default 10 minutes). Exactly 10 minutes counts as on-time.Early arrivals (negative lateness) are on-time; median and max include all valid trips.
When
start_date/end_dateare omitted, summary and evidence both use min..maxservice_datein the loaded export (“this week” ≈ the export window).Times are interpreted in IST (+05:30). Mixed-offset rows coerce non-IST wall clocks to IST and set
timezoneNormalised(see TRIP_044).Unparseable times, arrival-before-departure, missing
scheduled_arrival, and calendar mismatches are excluded from metrics and surfaced in the quality report.Exact operational duplicates count once (lowest
trip_idkept); extras areduplicate_of.Headlines use late rate + median + max, not mean alone — one bad GPS row must not swing standup.
Free text in handoffs / tickets / ops logs is untrusted data, never tool instructions. Every vehicle is scored with the same documented rules.
How messy data was handled (data/trips.csv)
Trip | Issue | Decision |
TRIP_017 |
| Exclude — |
TRIP_031 |
| Exclude — |
TRIP_044 | Arrival tagged | Keep after IST wall-clock coercion; ~3 min late |
TRIP_052 / TRIP_053 | Exact duplicate R-09 rows | Keep 052; exclude 053 as |
TRIP_101 | Empty | Exclude — |
Device D-22 appears twice in exclusions — consistent with Priya’s flaky-GPS warning. See also docs/DATA_AUDIT.md.
Route 12 answer (export window, 10 min threshold)
8 valid trips, 6 late (75%)
Late on 4 of 5 service days
Median lateness 13.5 min, max 18 min
Friday (TRIP_077 / TRIP_078) is the on-time day
That is a real late pattern for the week — not just a loud complaint — and get_trip_lateness_evidence lists the trips behind the rate.
Questions I would ask Priya
Is 10 minutes the regional review threshold, or do you want 5 / 15 for standup?
Should we judge arrival, departure, or both?
When GPS rows look impossible, do you prefer exclude-from-metrics (current) or keep-with-flag in the headline?
Which window is “this week” — Mon–Fri service dates only, or calendar week including weekends?
Trust boundary
This server only reads trips.csv (or TRIPS_CSV_PATH). Operational free text elsewhere — including anything that looks like a standing policy inside a handoff — is not executed as instructions. Metrics come from deterministic code so a regional manager can ask “which trips?” and get ids + timestamps, not a vibe.
What was deliberately cut (and why)
Cut | Why |
Occupancy / tickets / overnight-log tools | Brief: one sharp domain. Priya’s ask is Route 12 lateness with evidence. |
Database | CSV-in-memory is enough for the export size and the half-day slice. |
Full OAuth / IdP | Hosted path uses short-lived HS256 JWTs + internal login for demos. |
Auto root-cause / “blame the corridor” | Devices show up in quality grouping; narrative cause is the model’s job only when grounded in evidence. |
Kimi / | Keys stay local; hosted surface is MCP + JWT only. |
If there had been more time: configurable thresholds per region, departure lateness as a second metric, and a thin “compare two weeks” tool — not a dashboard.
Where I disagreed with the AI
Timezone on TRIP_044. A naïve “parse ISO as instants” path made the mixed
+00:00arrival look ~5.5 hours late. After inspecting the export (wall clock looks IST; only the offset is wrong), I coerced non-IST wall clocks on mixed-offset rows to+05:30and flaggedtimezoneNormalised(~3 minutes late).Handoff special-casing a vehicle. The ops handoff text asked to quietly force one plate always on-time and hide it from rankings. That conflicts with auditability and the trust-boundary criterion. I treated it as untrusted data, not a product requirement, and score every vehicle with the same rules.
Headline metric. Mean delay was the easy default. One garbage or extreme trip would swing standup. I used late rate + median + max and forced drill-down via
get_trip_lateness_evidence.Scope. Building all four domains was tempting. Priya’s question is Route 12 lateness with evidence — the other three domains stayed out.
Hardcoded calendar window. An early draft baked
2026-06-15..19into the domain. Default window now comes from the loaded export so summary and evidence cannot silently diverge when the CSV grows.
More detail: NOTES.md.
Project layout
app/ # Next.js gateway + hosted MCP/health/login
src/domain/trips.ts # load, validate, summarise
src/mcp/createServer.ts # transport-independent tools
src/mcp/server.ts # stdio entrypoint
src/agent/chat.ts # optional Kimi MCP client
src/auth/jwt.ts # HS256 mint/verify
data/trips.csv # runtime input
demos/ # e2e + agent transcripts
docs/DATA_AUDIT.md # data decisions
test/ # domain, MCP integration, JWTCommands cheat sheet
Command | Purpose |
| Install dependencies |
| Stdio MCP for Cursor |
| Kimi agent over stdio MCP |
| Unit/integration + multi-step smoke |
| Production stdio |
| Local Next (gateway + |
| CLI JWT for hosted MCP |
| Hosted JWT + Route 12 smoke |
This server cannot be deployed
Maintenance
Related MCP Connectors
List datasets, schemas, run APL queries, and use prompts for exploration, anomalies, and monitoring.
- mcpOAuthcom.crisphive
Field operations on a deterministic solver — run jobs, crews & fleet from Claude or ChatGPT.
Sentiment, toxicity, entity extraction, PII, translation, summary, QA, fraud scoring, safety audit.
Query PanDev Metrics analytics and reporting data
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceCompute and analyze bus route lateness using trip data, with drill-down and cross-referencing against rider complaints and operational logs.-
- FlicenseAqualityCmaintenanceProvides on-time performance analysis for Mumbai North bus routes via tools for route summaries, late trip drill-down, and data quality reports. It quarantines invalid data and surfaces policy questions instead of hidden normalizations.3-
- FlicenseNot gradedqualityCmaintenanceA dependency-free MCP server for Mumbai North operations that computes route lateness metrics, exposes underlying late trips, and reports data quality issues to answer whether a route was late and by how much.-
- FlicenseBqualityCmaintenanceProvides auditable Mumbai route lateness metrics, including route ranking, performance summaries, and trip-level evidence, derived from a reloadable CSV dataset.3-