Skip to content

feat(mcp): live airspace + maritime tools; fix OAuth consent UI#2442

Open
koala73 wants to merge 11 commits intomainfrom
fix/oauth-consent-branding-and-mcp-brief-context
Open

feat(mcp): live airspace + maritime tools; fix OAuth consent UI#2442
koala73 wants to merge 11 commits intomainfrom
fix/oauth-consent-branding-and-mcp-brief-context

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Mar 28, 2026

Summary

New MCP Tools

  • get_airspace — live ADS-B aircraft over any country. Queries civilian flights (OpenSky track-aircraft) and military aircraft (list-military-flights) in parallel using country bbox. Supports type: all|civilian|military filter. Returns counts + flight lists with callsign, position, altitude, heading.
  • get_maritime_activity — live AIS vessel density and disruptions for a country's waters. Returns density zones (ships/day, intensity), dark ship events, chokepoint congestion.
  • Country → bounding box resolved via shared/country-bboxes.json (167 entries, generated from public/data/countries.geojson by scripts/generate-country-bboxes.cjs).

OAuth Consent Page Fixes

  • CSS arrow bullets: content:'→'content:'\2192' (HTML entities don't work in CSS content:)
  • Branding: logo/title renamed from "WorldMonitor" → "WorldMonitor MCP" on both consent and error pages

Country Brief Context Injection

  • get_country_brief now fetches list-feed-digest?variant=geo&lang=en headlines (4s budget) before calling the brief API
  • Passes top-15 headlines as ?context= to get-country-intel-brief — grounds the LLM and prevents hallucinated events
  • Brief timeout reduced 25s→24s to stay within 30s Edge ceiling

Test plan

  • get_airspace({ country_code: "AE" }) — returns live flight counts + positions over UAE
  • get_airspace({ country_code: "TW", type: "military" }) — military only
  • get_maritime_activity({ country_code: "AE" }) — density zones + disruptions for Gulf
  • Unknown country code returns error message (not exception)
  • Country brief returns grounded intel (real headlines, not hallucinated events)
  • Arrow bullets () render correctly on consent page
  • Page title/logo shows "WorldMonitor MCP"
  • 131 edge-function tests + 2493 unit tests pass

- CSS content:'\2192' (not HTML entity which doesn't work in CSS)
- Rename logo/title to "WorldMonitor MCP" on both consent and error pages
- Inject real news headlines into get_country_brief to prevent hallucination
  Fetches list-feed-digest (4s budget), passes top-15 headlines as ?context=
  to get-country-intel-brief; brief timeout reduced to 24s to stay under Edge ceiling
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Building Building Preview, Comment Mar 28, 2026 7:42pm

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR delivers three focused fixes: corrects a CSS content: property syntax error on the OAuth consent page (HTML entities are invalid in CSS — the Unicode escape \2192 is the right form), renames the page branding from "WorldMonitor" to "WorldMonitor MCP" so users have clear product context during OAuth, and grounds the get_country_brief tool with real geopolitical headlines by pre-fetching the /list-feed-digest endpoint and injecting the top-15 headlines as a ?context= query parameter on the downstream brief call.

Key changes:

  • api/oauth/authorize.js: CSS arrow bullet fix and "WorldMonitor MCP" branding in two page functions
  • api/mcp.ts: Defensive country-code normalization (.toUpperCase().slice(0,2)), 4 s digest pre-fetch with silent fallback, context injected as URL-encoded query param, brief timeout trimmed 25 s → 24 s to stay within the 30 s Edge ceiling
  • All three findings are P2 (style/best-practices): the DigestPayload type is duplicated and could be lifted to module scope; the context would be cleaner in the POST body than as a query parameter; and the 4 s + 24 s = 28 s worst-case budget leaves only ~2 s margin before the hard Edge kill

Confidence Score: 5/5

Safe to merge — all three changes are correct and the only findings are P2 style suggestions that do not block correctness.

The CSS fix is unambiguously correct, the branding rename is clean, and the context-injection logic has proper error isolation (try/catch fallback). No P0 or P1 issues found. The three P2 notes (type duplication, query-param vs. POST-body, tight timing budget) are improvement suggestions rather than defects.

No files require special attention, though api/mcp.ts is worth a second read if the timing margin ever becomes a concern in production.

Important Files Changed

Filename Overview
api/mcp.ts Adds headline context injection to get_country_brief via a 4 s digest pre-fetch; context is URL-encoded and appended as a query param on the downstream POST. Three P2 items: duplicated DigestPayload type, large payload in query string vs. POST body, and a tight 28/30 s total timing budget.
api/oauth/authorize.js Two clean fixes: corrects CSS content: property to use Unicode escape \2192 (was incorrectly using HTML entity →), and renames "WorldMonitor" → "WorldMonitor MCP" in page titles and logo text across both htmlError and consentPage functions.

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant Edge as mcp.ts (Edge Fn)
    participant Digest as /api/news/v1/list-feed-digest
    participant Brief as /api/intelligence/v1/get-country-intel-brief

    Client->>Edge: tools/call get_country_brief {country_code, framework}
    Edge->>Digest: GET ?variant=geo&lang=en (timeout 4s)
    alt digest ok
        Digest-->>Edge: {categories: {items: [{title}]}}
        Note over Edge: Extract top-15 headlines, join with newline, slice(0,4000), encodeURIComponent
    else digest fails / timeout
        Digest-->>Edge: error (caught, contextParam='')
    end
    Edge->>Brief: POST ?context=<encoded> {country_code, framework} (timeout 24s)
    Brief-->>Edge: intelligence brief JSON
    Edge-->>Client: {content: [{type:"text", text: JSON}]}
Loading

Reviews (1): Last reviewed commit: "fix(oauth): fix CSS arrow bullets + add ..." | Re-trigger Greptile

signal: AbortSignal.timeout(4_000),
});
if (digestRes.ok) {
type DigestPayload = { categories?: Record<string, { items?: { title?: string }[] }> };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 DigestPayload type is duplicated

The DigestPayload type declaration inside get_country_brief._execute is identical to the one inside get_world_brief._execute (line 272). Declaring it once at module level removes the duplication and keeps a single source of truth if the shape ever changes.

// At module level, before TOOL_REGISTRY:
type DigestPayload = { categories?: Record<string, { items?: { title?: string }[] }> };

koala73 added 3 commits March 28, 2026 22:06
New tools answer real-time positional questions via existing bbox RPCs:
- get_airspace: civilian ADS-B (OpenSky) + military flights over any country
  parallel-fetches track-aircraft + list-military-flights, capped at 100 each
- get_maritime_activity: AIS density zones + disruptions for a country's waters
  calls get-vessel-snapshot with country bbox

Country → bounding box resolved via shared/country-bboxes.json (167 entries,
generated from public/data/countries.geojson by scripts/generate-country-bboxes.cjs).
Both API calls use 8s AbortSignal.timeout; get_airspace uses Promise.allSettled
so one failure doesn't block the other.
@koala73 koala73 changed the title fix(oauth): CSS arrows + MCP branding + country brief news context feat(mcp): live airspace + maritime tools; fix OAuth consent UI Mar 28, 2026
- JSON import: add 'with { type: json }' so node --test works without tsx loader
- get_airspace: surface upstream failures; partial outage => partial:true+warnings,
  total outage => throw (prevents misleading zero-aircraft response)
- pre-push hook: add #!/usr/bin/env bash shebang (was no shebang, ran as /bin/sh
  on Linux CI/contributors; process substitution + [[ ]] require bash)
Vercel's esbuild bundler does not support `with { type: 'json' }` import
attributes, causing builds to fail with "Expected ';' but found 'with'".

Fix: generate shared/country-bboxes.ts (typed TS module) alongside the
existing JSON file. The TS import has no attributes and bundles cleanly
with all esbuild versions.

Also extend the pre-push bundle check to include api/*.ts root-level files
so this class of error is caught locally before push.
…rgin)

Digest pre-fetch: 4 s → 2 s (cached endpoint, silent fallback on miss)
Brief call: 24 s → 22 s
Total worst-case: 24 s vs Vercel Edge 30 s hard kill — was 28 s (2 s margin)
9 new tests:
- get_airspace: happy path, unknown code, partial failure (mil down),
  total failure (-32603), type=civilian skips military fetch
- get_maritime_activity: happy path, unknown code, API failure (-32603),
  empty snapshot handled gracefully

Also fixes import to use .ts extension so Node --test resolver finds the
country-bboxes module (tsx resolves .ts directly; .js alias only works
under moduleResolution:bundler at typecheck time)
…orts

Vercel edge bundler refuses .ts extension imports even from .ts edge
functions. Plain .js is the only safe runtime import for edge functions.

Pattern: generate shared/country-bboxes.js (pure ESM, no TS syntax) +
shared/country-bboxes.d.ts (type declaration). TypeScript uses the .d.ts
for tuple types at check time; Vercel and Node --test load the .js at
runtime. The previous .ts module is removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant