Skip to content

Latest commit

 

History

History
505 lines (412 loc) · 20 KB

File metadata and controls

505 lines (412 loc) · 20 KB

Changelog

All notable changes to Metro MCP will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[5.0.0] - 2026-08-13

Metro MCP 5.0 upgrades the remote server to MCP 2026-07-28 and a request-scoped SDK v2 architecture. The transit surface remains exactly thirteen read-only tools, three resources, and three prompts.

Added

  • MCP 2026-07-28 request metadata, discovery, cache hints, cancellation, and Multi Round-Trip Requests (MRTR) for ambiguous station selection.
  • Cloudflare's OAuth Provider with Client ID Metadata Documents first, temporary DCR fallback, explicit transit:read consent, PKCE, RFC 9207 issuer identifiers, RFC 8707 resource binding, RFC 9728 discovery, rotating refresh tokens, and revocation.
  • Workerd protocol/OAuth lifecycle coverage plus a loopback-only authenticated conformance runner pinned to @modelcontextprotocol/conformance@0.2.0-alpha.11 and its frozen 2026-07-28 requirements.
  • Dedicated production and preview OAUTH_KV bindings and separately configured GitHub OAuth apps/callbacks.

Changed

  • /mcp is the only canonical MCP resource. Modern MCP 2026 operations are stateless and do not require initialize; ordinary MCP 2025 stateless tools, resources, and prompts remain supported.
  • POST and OPTIONS /sse are rewritten to /mcp before authorization. /sse is not an OAuth audience.
  • Access tokens last at most 60 minutes. Refresh tokens last at most 30 days and rotate on use. Bearer credentials are accepted only through the Authorization header.
  • DCR remains available temporarily and sunsets on 2027-06-30; CIMD or pre-registration is preferred.
  • Protocol work no longer creates or addresses a Durable Object. The original MetroMcpAgent export, namespace, and v1 migration remain inactive for rollback.

Breaking

  • Legacy GET SSE, DELETE, slash variants, and session message URLs now return 405; there is no persistent SSE stream, resumability, or server push.
  • Tokens without an audience, tokens bound to /sse, and clients from the old DCR store must reauthorize against canonical /mcp.
  • Compatible legacy JWTs already bound to /mcp expire at the earlier of their embedded expiry and 2026-11-30T00:00:00Z.
  • The old active MCP_SESSION, OAUTH_CLIENTS, and RATE_LIMIT_KV bindings are removed. Self-hosters must configure a dedicated OAUTH_KV, global_fetch_strictly_public, and the new origin/allowlist/MRTR environment contract.

Deferred

  • MCP Apps and embedded interactive UI are explicitly deferred to the next PR.

[4.0.0] - Durable Objects rearchitect via cloudflare/agents McpAgent

A foundational rewrite of how Metro MCP serves sessions. The MCP API surface stays additive (clients keep working unchanged), but the infrastructure underneath the /mcp and /sse endpoints is replaced.

Why a major bump

The KV-backed, request-per-message session model from 3.x is replaced by a Durable Object that holds the session for its lifetime. This unlocks capabilities the MCP 2025-06-18 spec assumes a server can do but the old infra couldn't: server-initiated push, resumability, elicitations, subscribable resources.

Added

Durable Object-backed sessions (cloudflare/agents McpAgent)

  • New MetroMcpAgent class extends agents/mcp's McpAgent, which itself extends Agent → DurableObject. One DO instance per Mcp-Session-Id.
  • Hibernatable WebSocket transport — DO evicts while idle, no billing during quiet periods. Wakes on incoming messages.
  • DurableObjectEventStore — built into McpAgent — handles Last-Event-ID replay for resumable streams.
  • MCP_SESSION DO binding in wrangler.jsonc with first-time migration tag: "v1", new_sqlite_classes: ["MetroMcpAgent"].

New MCP capabilities (resources, prompts, elicitations, progress)

  • Resources — three transit:// URI templates:

    • transit://stations/{city}/{id} — station metadata
    • transit://routes/{city}/{id} — route info
    • transit://incidents/{city} — live service advisories
    • resources/list for the incidents template; stations/routes omit list because the catalog is large (use tools to discover).
    • resources/subscribe is declared at the SDK level but the incident poller that fans out notifications/resources/updated arrives with Phase 2.5.
  • Prompts — three canned templates:

    • service-briefing(city, lineCode?) — 3-sentence status briefing
    • commute-planner(city, from, to) — multi-step real-time plan
    • accessibility-check(stationNames) — DC elevator outage scan
  • Elicitationselicitation/create is now used in get_station_predictions. When a station name resolves to multiple matches (e.g., "Times Square" → 127, R16, 725), the server asks the user which one instead of silently picking the first. Clients that don't declare elicitation capability fall back to the legacy first-match behavior automatically.

  • Progress notificationsnotifications/progress for get_all_stations when the client opts in via params._meta.progressToken. Two checkpoints: "Fetching…" and "Normalizing…". The NYC catalog is ~600 stations so the visibility matters for long-running clients.

Honesty in transport advertisement

  • supportsServerPush: true (was false — hibernatable WS push works)
  • supportsResumability: true (was false — DurableObjectEventStore)
  • note tightened to reflect actual DO-backed transport

Changed

Infrastructure

  • wrangler.tomlwrangler.jsonc. IDE schema validation, inline comments, no nested-table indent sensitivity.
  • compatibility_date: "2025-12-25""2026-04-07" for the hibernatable WebSocket auto-Close-reply runtime behavior.
  • MCP_SESSIONS KV namespace deprecated. Sessions live in the DO. KV binding kept as optional during the drain window (existing 24h-TTL entries are harmless); remove in 4.1.

Tool implementation

  • All 13 tools migrated from the hand-rolled MCP_TOOLS array + MCPHandler.handleToolCall switch to server.registerTool(...) calls inside MetroMcpAgent.init().
  • Schemas now expressed in Zod (the SDK's idiomatic path), which derives JSON Schema automatically.
  • Tool result shape preserved exactly from 3.2.0: { content: [...], structuredContent: {...} }.

Build / DX

  • Node --max-old-space-size=12288 baked into build, lint, type-check scripts. Zod's deep generic inference for the SDK's registerTool signature OOMs at the default 4GB heap once you have 14+ typed tool handlers. GitHub Actions default runners default to 7GB — bump there if you use CI. Long-term fix is project references or switching to AnySchema.
  • package.json 3.2.0 → 4.0.0.

Removed

  • src/mcp-handler.ts — replaced by MetroMcpAgent.init() handlers
  • src/mcp-tools.ts — tools registered inline now
  • src/mcp-types.ts — no longer used (SDK provides equivalents)
  • src/utils/sse-formatter.ts — SDK handles SSE framing
  • src/utils/ — empty after the above
  • tests/unit/mcp-tools.test.ts — 16 tests against the deleted array; type-check now enforces tool shape via the SDK's registerTool generics
  • The POST / MCP alias. Clients use /mcp (recommended) or /sse.

Deferred to Phase 2.5

  • DO-runtime tests via @cloudflare/vitest-pool-workers. The dependency is installed and ready; integration tests against a miniflare workers runtime land in a follow-up PR so they can be exercised against a real staging deploy. The existing 103 unit tests (auth, RFC 8707 audience binding, middleware, config) cover the highest-risk non-MCP code paths.
  • Incident poller that publishes notifications/resources/updated to subscribers of transit://incidents/{city}. Needs a Cron-triggered DO that polls upstream feeds and fans out — non-trivial design. Until then, incident resources are read-on-request.

Backwards compatibility

  • MCP API surface: every tool keeps its name, inputs, outputs, and the content + structuredContent result shape. Phase 1's tool annotations and outputSchema declarations are preserved (now via the SDK's typed API rather than the static array).
  • OAuth flow: unchanged. RFC 8707 audience binding from 3.2.0 continues to work; the verified user identity flows into the DO via ctx.props.
  • JWT tokens issued under 3.x stay valid for their remaining 90-day TTL.
  • Existing client integrations (Claude Desktop, mcp-cli, etc.) need no changes.

Migration steps for self-hosters

  1. bun install (or npm install) to pick up the new deps.
  2. cp wrangler.jsonc.example wrangler.jsonc if running fresh; the existing config is straightforward to translate.
  3. Set compatibility_date: "2026-04-07".
  4. Add the durable_objects and migrations blocks (see wrangler.jsonc.example).
  5. bunx wrangler deploy — the migration runs automatically and creates the MetroMcpAgent namespace.
  6. The MCP_SESSIONS KV namespace is no longer referenced and can be deleted in a follow-up once any in-flight sessions have drained.

[3.2.0] - MCP 2025-06-18 alignment + RFC 8707 audience binding

Added

MCP 2025-06-18 surface

  • Tool title (human-readable display name, separate from machine name)
  • Tool annotations: readOnlyHint, idempotentHint, openWorldHint declared on every tool. All Metro MCP tools are read-only live-data queries.
  • Tool outputSchema declared for every tool. Clients can validate responses and integrate typed data without re-parsing.
  • Tool results now emit structuredContent alongside content. The text payload is the JSON serialization of structuredContent, per spec SHOULD.
  • Normalized prediction shape: minutesAway: integer | null + arrivalStatus: 'ARRIVING' | 'BOARDING' | 'DELAYED' | 'SCHEDULED' instead of the mixed "3 min" | "ARR" string. Clients can now sort/compare and render however they want.

RFC 8707 — Resource Indicators (audience binding)

  • /authorize accepts an optional resource parameter (must be an absolute URI)
  • Tokens issued from such flows carry a JWT aud claim bound to the canonical MCP resource URI ({scheme}://{host}/mcp).
  • Each authenticated request verifies that the token's audience matches the request's canonical resource. Mismatch → 401.
  • /.well-known/oauth-authorization-server now advertises resource_indicators_supported: true.

Honesty in the transport advertisement

  • Server-info now distinguishes:
    • supportsSSEResponses: true — POST → SSE response format works
    • supportsServerPush: false — persistent GET-stream push is not implemented
    • supportsResumability: false — Last-Event-ID replay is not implemented

Changed

  • Single source of truth for SERVER_VERSION and MCP_PROTOCOL_VERSION in src/config.ts. Removed hardcoded '3.1.3' and '2025-06-18' strings from router.ts and mcp-handler.ts.
  • package.json version bumped 3.1.1 → 3.2.0.

Deprecation timeline

  • Legacy tokens (no aud claim) are grandfathered. They continue to work with a console.warn deprecation log until their natural 90-day TTL expires. Re-authenticate with a resource parameter to bind future tokens.
  • Clients SHOULD send resource={mcp_endpoint} on /authorize. Future major versions may require it.

Backwards compatibility

  • All new tool fields (title, annotations, outputSchema, structuredContent) are additive. Clients that only know MCP 2025-03-26 keep reading content[0].text unchanged.
  • The text payload is now the serialization of structuredContent (per spec). It is no longer pretty-printed with 2-space indentation — clients that parse it as JSON are unaffected; clients that displayed it raw will see compact JSON.

[Unreleased] - Security and Testing Improvements

Added

Testing Infrastructure

  • ✅ Comprehensive test suite with Vitest (89 tests)
  • ✅ Unit tests for all critical components
    • Rate limiting tests (95% coverage)
    • Input validation tests (98% coverage)
    • Security headers tests (92% coverage)
    • Configuration tests (88% coverage)
    • Authentication tests (85% coverage)
  • ✅ Test utilities and mocking infrastructure for Cloudflare Workers
  • ✅ Code coverage reporting with V8 provider
  • ✅ Coverage thresholds enforcement (60%+)
  • ✅ Test scripts: test, test:watch, test:coverage, test:ui

Rate Limiting

  • ✅ Production-ready rate limiting using Cloudflare KV
  • ✅ Sliding window algorithm for accurate rate tracking
  • ✅ Configurable limits per endpoint type:
    • OAuth endpoints: 200 requests/minute
    • MCP endpoints: 100 requests/minute
    • Static endpoints: 50 requests/minute
  • ✅ Standard HTTP rate limit headers:
    • X-RateLimit-Limit: Maximum requests per window
    • X-RateLimit-Remaining: Requests remaining in window
    • X-RateLimit-Reset: Unix timestamp when limit resets
    • Retry-After: Seconds until retry allowed
  • ✅ Fail-open strategy for availability (if KV fails, allow requests)
  • ✅ Client identification via CF-Connecting-IP header
  • ✅ Automatic cleanup via KV TTL

Input Validation & Sanitization

  • ✅ Comprehensive input validation module
  • ✅ 5-layer validation strategy:
    1. Type checking
    2. Sanitization (remove dangerous characters)
    3. Format validation (regex patterns)
    4. Length limits
    5. Whitelist approach (where applicable)
  • ✅ Protection against:
    • XSS attacks
    • SQL/NoSQL injection
    • Path traversal
    • Malformed requests
  • ✅ Validation for all input types:
    • Station names
    • Station codes
    • Line codes
    • Search queries
    • City codes (whitelist)
  • ✅ Detailed, helpful error messages
  • ✅ Type-safe validation functions
  • ✅ JSON-RPC request validation

Security Headers

  • ✅ Adaptive Content Security Policy (CSP):
    • Strict CSP for JSON responses (script-src 'none')
    • Functional CSP for HTML responses (script-src 'self' 'unsafe-inline')
    • Automatic context detection from Content-Type
  • ✅ All recommended security headers:
    • Content-Security-Policy: Context-aware XSS protection
    • X-Frame-Options: DENY: Clickjacking protection
    • X-Content-Type-Options: nosniff: MIME sniffing prevention
    • Referrer-Policy: strict-origin-when-cross-origin: Privacy protection
    • Permissions-Policy: Disable unused browser features
    • X-XSS-Protection: 0: Disable deprecated XSS filter
  • ✅ CORS headers for cross-origin requests
  • ✅ Convenience functions:
    • createSecureJsonResponse()
    • createSecureHtmlResponse()
    • addSecurityHeadersAuto()

Configuration Management

  • ✅ Centralized configuration module (src/config.ts)
  • ✅ Environment variable validation at startup
  • ✅ Type-safe configuration access
  • ✅ Default values and documentation
  • ✅ Environment detection (development/staging/production)
  • ✅ Endpoint-specific rate limit configuration
  • ✅ Runtime configuration validation

Documentation

  • TESTING_GUIDE.md: Complete testing guide (500+ lines)
    • How to run tests
    • How to write tests
    • Test utilities documentation
    • Coverage requirements
    • Debugging tips
    • Best practices
  • SECURITY.md: Security architecture guide (600+ lines)
    • Security philosophy
    • Authentication & authorization
    • Rate limiting implementation
    • Input validation strategy
    • Security headers explained
    • Best practices for developers and operators
    • Incident response procedures
    • Security checklist
  • MIGRATION.md: Step-by-step migration guide (400+ lines)
    • Overview of changes
    • Breaking changes (none!)
    • New requirements
    • Migration steps
    • Testing procedures
    • Troubleshooting
    • Rollback plan
  • QUICK_REFERENCE.md: Quick reference guide
    • Common commands
    • Development workflow
    • Troubleshooting
    • Security checklist
  • PR_DESCRIPTION.md: Full PR description with rationale

Code Quality

  • ✅ Comprehensive JSDoc comments explaining WHY for every major decision
  • ✅ Educational comments throughout codebase
  • ✅ Type safety improvements in src/types.ts
  • ✅ ESLint and TypeScript strict mode configurations

Changed

Dependencies

  • ➕ Added @vitest/coverage-v8 for code coverage
  • ➕ Added @vitest/ui for interactive test UI
  • 🔄 Updated vitest.config.ts with comprehensive test configuration

Configuration

  • 🔄 Updated wrangler.toml with RATE_LIMIT_KV namespace binding
  • 🔄 Updated package.json with new test scripts
  • 🔄 Enhanced tsconfig.json for stricter type checking

Type Definitions

  • 🔄 Updated src/types.ts with:
    • RATE_LIMIT_KV binding in Env interface
    • New types for validation and rate limiting
    • Comprehensive JSDoc comments
    • Additional interfaces for error handling

Improved

Error Messages

  • ✨ More detailed validation error messages
  • ✨ Helpful guidance for fixing issues
  • ✨ Consistent error format across all modules
  • ✨ Field-specific error information

Security

  • 🔒 All user inputs validated and sanitized
  • 🔒 Rate limiting prevents abuse
  • 🔒 Adaptive security headers for maximum protection
  • 🔒 Defense-in-depth approach

Developer Experience

  • 👨‍💻 Test-driven development workflow
  • 👨‍💻 Interactive test UI for debugging
  • 👨‍💻 Comprehensive documentation
  • 👨‍💻 Educational comments throughout code
  • 👨‍💻 Type-safe configuration

Fixed

Security Issues

  • 🔒 Fixed: No rate limiting (Critical)
  • 🔒 Fixed: No input validation (High Priority)
  • 🔒 Fixed: CSP headers not adaptive (Security Enhancement)
  • 🔒 Fixed: Zero test coverage (Critical)

Performance

  • ⏱️ Rate limiting: +1-2ms per request (KV read/write)
  • ⏱️ Input validation: +<1ms per request (regex checks)
  • ⏱️ Security headers: +~0.5KB response size
  • ⏱️ Total overhead: ~2-3ms (negligible)

Breaking Changes

NONE! This update is 100% backward compatible.

Migration Required

  1. Create RATE_LIMIT_KV namespace:

    wrangler kv:namespace create "RATE_LIMIT_KV"
    wrangler kv:namespace create "RATE_LIMIT_KV" --preview
  2. Update wrangler.toml with KV IDs

  3. Install new dependencies:

    npm install
  4. Run tests:

    npm test

See MIGRATION.md for detailed instructions.

Known Issues

  • Rate limiting KV namespace IDs in wrangler.toml are placeholders
    • Update with actual IDs after creating namespaces

Future Plans

Version 1.1.0 (Planned)

  • Increase test coverage to 80%
  • Add integration tests for complete OAuth flow
  • Add performance/load tests
  • OpenAPI/Swagger API documentation
  • Enhanced logging and monitoring

Version 1.2.0 (Planned)

  • Premium tier rate limiting
  • User-specific rate limits (in addition to IP-based)
  • Rate limit analytics dashboard
  • More transit systems (BART, MBTA, etc.)

Version 2.0.0 (Planned)

  • WebSocket support for real-time updates
  • GraphQL API (in addition to JSON-RPC)
  • Enhanced caching with Durable Objects
  • Multi-region deployment optimization

[1.0.0] - 2024-12-26

Initial release.

Features

  • OAuth 2.1 authentication with PKCE
  • JWT-based authorization
  • MCP protocol implementation
  • DC Metro (WMATA) support
  • NYC Subway (MTA) support
  • Real-time train predictions
  • Station search
  • Service alerts
  • Elevator/escalator status (DC only)

Version Numbering

We follow Semantic Versioning:

  • MAJOR version for incompatible API changes
  • MINOR version for new functionality (backward compatible)
  • PATCH version for backward-compatible bug fixes

Release Process

  1. Update CHANGELOG.md with all changes
  2. Update version in package.json
  3. Run full test suite: npm test
  4. Verify coverage: npm run test:coverage
  5. Tag release: git tag v1.x.x
  6. Deploy: npm run deploy
  7. Create GitHub release with changelog

Links