Cityflo on-time performance MCP
Click on "Install 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: fleetsync-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 installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/devbathani/Cityflo-Internal-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server