Conversation
New standalone MCP package for flight research with Google Flights: - 9 tools: FLIGHT_SEARCH, TRIP_CREATE/GET/LIST/UPDATE/DELETE, TRIP_EXECUTE/STOP, TRIP_ADD_SEARCHES - Background worker with parallel search execution (3 concurrent) - Local JSON persistence at ~/.deco/flights/trips/ - MCP-app UIs: trips dashboard with drill-down, live search progress, sortable results table - REST API at /api/trips for real-time UI polling - Tiered search strategy via agent instructions (5-8 searches, then drill down) - Open-jaw/multi-city support via fast-flights-ts - Airline inclusion filtering at Google level, exclusion filtering post-fetch - Google Flights URL per search task (copy to clipboard) - Weighted result scoring (price, stops, layover, airports, duration) - Recruit modal on home screen for one-click agent install Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsSuggested: Minor ( React with an emoji to override the release type:
Current version:
|
There was a problem hiding this comment.
26 issues found across 29 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/deco-flights/server/tools/trip-update.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-update.ts:58">
P2: Merging updates should preserve existing `preferences` fields; direct replacement drops previously stored preference values on partial updates.</violation>
<violation number="2" location="packages/deco-flights/server/tools/trip-update.ts:83">
P1: When regenerating the search plan, clear `searchTasks` too; otherwise previously generated tasks are reused and execution can run outdated routes/dates.</violation>
</file>
<file name="packages/deco-flights/server/lib/planner.ts">
<violation number="1" location="packages/deco-flights/server/lib/planner.ts:80">
P2: Sampling with `Math.ceil(totalCombinations / MAX_SEARCHES)` can return far fewer than `MAX_SEARCHES` searches, reducing flight coverage when combinations are capped.</violation>
</file>
<file name="packages/deco-flights/server/lib/storage.ts">
<violation number="1" location="packages/deco-flights/server/lib/storage.ts:13">
P1: Trip IDs are used in file paths without validation, enabling path traversal outside the trips directory.</violation>
<violation number="2" location="packages/deco-flights/server/lib/storage.ts:35">
P2: A single malformed trip JSON file will crash trip listing because JSON parsing errors are not handled.</violation>
</file>
<file name="packages/deco-flights/server/ui/trips-dashboard.ts">
<violation number="1" location="packages/deco-flights/server/ui/trips-dashboard.ts:136">
P1: Unescaped dynamic content is inserted into `innerHTML`, enabling XSS if trip/result fields contain HTML.</violation>
</file>
<file name="packages/deco-flights/server/ui/trip-card.ts">
<violation number="1" location="packages/deco-flights/server/ui/trip-card.ts:98">
P1: Unescaped trip fields are written via `innerHTML`, allowing script/HTML injection in the trip card UI.</violation>
</file>
<file name="packages/deco-flights/server/ui/trip-planner.ts">
<violation number="1" location="packages/deco-flights/server/ui/trip-planner.ts:102">
P2: Task sort fallback uses `||`, which converts `running` priority `0` to `3` and breaks the intended status ordering.</violation>
<violation number="2" location="packages/deco-flights/server/ui/trip-planner.ts:135">
P1: Unescaped trip/result data is injected into `innerHTML`, enabling XSS if any field contains HTML/JS payloads.</violation>
</file>
<file name="packages/deco-flights/server/lib/worker.ts">
<violation number="1" location="packages/deco-flights/server/lib/worker.ts:141">
P1: Errored tasks are retried indefinitely with no retry limit, which can keep the worker running forever on persistent failures.</violation>
<violation number="2" location="packages/deco-flights/server/lib/worker.ts:146">
P2: The pre-wave save does not actually persist `running` task states, so live progress can be stale during execution.</violation>
<violation number="3" location="packages/deco-flights/server/lib/worker.ts:173">
P1: Final trip status ignores errored/cancelled tasks and can incorrectly mark a cancelled run as `complete`.</violation>
</file>
<file name="packages/deco-flights/server/ui/search-results.ts">
<violation number="1" location="packages/deco-flights/server/ui/search-results.ts:95">
P1: Unsanitized tool-result fields are injected into `innerHTML`, which can allow script/markup injection in the UI.</violation>
</file>
<file name="packages/deco-flights/server/tools/trip-list.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-list.ts:9">
P3: The tool description says results are capped at 10, but the code actually returns up to 20.</violation>
</file>
<file name="packages/deco-flights/server/tools/trip-delete.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-delete.ts:16">
P1: TRIP_DELETE does not stop an active worker, so a running research job can recreate the trip file after deletion.</violation>
</file>
<file name="packages/deco-flights/server/index.ts">
<violation number="1" location="packages/deco-flights/server/index.ts:137">
P1: Avoid wildcard CORS for trip and MCP endpoints; it allows any origin to read potentially sensitive trip data.</violation>
</file>
<file name="packages/deco-flights/server/tools/flight-search.ts">
<violation number="1" location="packages/deco-flights/server/tools/flight-search.ts:81">
P2: Response field name mismatches UI expectations (`googleFlightsUrl` vs `fallbackUrl`), so the fallback link is not shown on empty/error results.</violation>
</file>
<file name="packages/deco-flights/server/tools/trip-stop.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-stop.ts:22">
P1: `TRIP_STOP` performs immediate abort cancellation, not "finish current search then stop" as documented.</violation>
<violation number="2" location="packages/deco-flights/server/tools/trip-stop.ts:33">
P2: `TRIP_STOP` unconditionally sets status to `draft`, which can incorrectly revert completed trips.</violation>
</file>
<file name="packages/deco-flights/server/tools/trip-add-searches.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-add-searches.ts:85">
P1: Adding searches during an active worker run can be overwritten by the worker’s stale snapshot, causing newly added tasks to be lost or never executed.</violation>
</file>
<file name="packages/deco-flights/server/lib/scraper.ts">
<violation number="1" location="packages/deco-flights/server/lib/scraper.ts:98">
P2: Per-leg airline data is overwritten with the first itinerary airline, which breaks avoid/preferred airline filtering for mixed-airline itineraries.</violation>
<violation number="2" location="packages/deco-flights/server/lib/scraper.ts:113">
P1: Stop count is overestimated for multi-leg searches (e.g., round-trips), which can incorrectly filter and down-rank valid itineraries.</violation>
</file>
<file name="packages/deco-flights/server/tools/trip-create.ts">
<violation number="1" location="packages/deco-flights/server/tools/trip-create.ts:41">
P2: Validate `tripLengthDays` bounds (positive integers and `min <= max`) to prevent invalid search plans.</violation>
<violation number="2" location="packages/deco-flights/server/tools/trip-create.ts:45">
P1: Restrict `passengers` to a positive integer; current schema allows invalid counts like 0, negatives, and decimals.</violation>
</file>
<file name="packages/deco-flights/server/lib/agent-instructions.ts">
<violation number="1" location="packages/deco-flights/server/lib/agent-instructions.ts:5">
P2: This makes 10 sound like a hard trip-wide cap, but drill-down searches can still be added after the initial plan.</violation>
</file>
<file name="packages/mesh-sdk/src/lib/constants.ts">
<violation number="1" location="packages/mesh-sdk/src/lib/constants.ts:343">
P2: Add an `appId` for the Flights registry template so the registry lookup can resolve its full metadata.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| if (planChanged) { | ||
| trip.searchPlan = generateSearchPlan(trip); | ||
| trip.results = undefined; |
There was a problem hiding this comment.
P1: When regenerating the search plan, clear searchTasks too; otherwise previously generated tasks are reused and execution can run outdated routes/dates.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/tools/trip-update.ts, line 83:
<comment>When regenerating the search plan, clear `searchTasks` too; otherwise previously generated tasks are reused and execution can run outdated routes/dates.</comment>
<file context>
@@ -0,0 +1,97 @@
+
+ if (planChanged) {
+ trip.searchPlan = generateSearchPlan(trip);
+ trip.results = undefined;
+ trip.status = "draft";
+ }
</file context>
| } | ||
|
|
||
| function tripPath(id: string): string { | ||
| return join(TRIPS_DIR, `${id}.json`); |
There was a problem hiding this comment.
P1: Trip IDs are used in file paths without validation, enabling path traversal outside the trips directory.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/lib/storage.ts, line 13:
<comment>Trip IDs are used in file paths without validation, enabling path traversal outside the trips directory.</comment>
<file context>
@@ -0,0 +1,96 @@
+}
+
+function tripPath(id: string): string {
+ return join(TRIPS_DIR, `${id}.json`);
+}
+
</file context>
| return join(TRIPS_DIR, `${id}.json`); | |
| if (!/^[A-Za-z0-9_-]+$/.test(id)) { | |
| throw new Error(`Invalid trip id: ${id}`); | |
| } | |
| return join(TRIPS_DIR, `${id}.json`); |
| var created=t.createdAt?new Date(t.createdAt).toLocaleDateString('en-US',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}):''; | ||
|
|
||
| h+='<tr style="cursor:pointer" onclick="openTrip(\\''+t.id+'\\')">'; | ||
| h+='<td class="font-medium">'+t.name+'</td>'; |
There was a problem hiding this comment.
P1: Unescaped dynamic content is inserted into innerHTML, enabling XSS if trip/result fields contain HTML.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/ui/trips-dashboard.ts, line 136:
<comment>Unescaped dynamic content is inserted into `innerHTML`, enabling XSS if trip/result fields contain HTML.</comment>
<file context>
@@ -0,0 +1,354 @@
+ var created=t.createdAt?new Date(t.createdAt).toLocaleDateString('en-US',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}):'';
+
+ h+='<tr style="cursor:pointer" onclick="openTrip(\\''+t.id+'\\')">';
+ h+='<td class="font-medium">'+t.name+'</td>';
+ h+='<td class="text-sm">'+t.origin+' → '+t.destinations.join(', ')+(t.returnOrigins&&t.returnOrigins.length?' <span class="muted">ret '+t.returnOrigins.join(', ')+'</span>':'')+'</td>';
+ h+='<td class="text-sm">'+fmtD(t.earliestDeparture)+' – '+fmtD(t.latestReturn)+'</td>';
</file context>
| html += '<div class="mt-2 text-sm muted">' + trip.searchPlan.searches.length + ' searches planned</div>'; | ||
| } | ||
|
|
||
| root.innerHTML = html; |
There was a problem hiding this comment.
P1: Unescaped trip fields are written via innerHTML, allowing script/HTML injection in the trip card UI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/ui/trip-card.ts, line 98:
<comment>Unescaped trip fields are written via `innerHTML`, allowing script/HTML injection in the trip card UI.</comment>
<file context>
@@ -0,0 +1,102 @@
+ html += '<div class="mt-2 text-sm muted">' + trip.searchPlan.searches.length + ' searches planned</div>';
+ }
+
+ root.innerHTML = html;
+ resizeHeight(Math.min(root.scrollHeight + 32, 280));
+}
</file context>
| var root = document.getElementById('root'); | ||
| var results = trip.results || []; | ||
|
|
||
| var h = '<div class="trip-banner"><h1>'+trip.name+'</h1>'; |
There was a problem hiding this comment.
P1: Unescaped trip/result data is injected into innerHTML, enabling XSS if any field contains HTML/JS payloads.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/ui/trip-planner.ts, line 135:
<comment>Unescaped trip/result data is injected into `innerHTML`, enabling XSS if any field contains HTML/JS payloads.</comment>
<file context>
@@ -0,0 +1,243 @@
+ var root = document.getElementById('root');
+ var results = trip.results || [];
+
+ var h = '<div class="trip-banner"><h1>'+trip.name+'</h1>';
+ h += '<div class="route"><span class="code">'+trip.origin+'</span><span style="opacity:0.5">→</span><span class="code">'+trip.destinations.join(', ')+'</span>';
+ h += '<span class="badge badge-'+trip.status+'" style="margin-left:12px">'+trip.status+'</span></div>';
</file context>
| const results: FlightResult[] = rawResults.map((r) => ({ | ||
| price: r.price, | ||
| currency: opts.currency || "USD", | ||
| flights: r.flights.map((leg) => ({ |
There was a problem hiding this comment.
P2: Per-leg airline data is overwritten with the first itinerary airline, which breaks avoid/preferred airline filtering for mixed-airline itineraries.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/lib/scraper.ts, line 98:
<comment>Per-leg airline data is overwritten with the first itinerary airline, which breaks avoid/preferred airline filtering for mixed-airline itineraries.</comment>
<file context>
@@ -0,0 +1,140 @@
+ const results: FlightResult[] = rawResults.map((r) => ({
+ price: r.price,
+ currency: opts.currency || "USD",
+ flights: r.flights.map((leg) => ({
+ airline: r.airlines[0] || "Unknown",
+ flightNumber: "",
</file context>
| latestReturn: z | ||
| .string() | ||
| .describe("Latest acceptable return date (YYYY-MM-DD)"), | ||
| tripLengthDays: z.object({ |
There was a problem hiding this comment.
P2: Validate tripLengthDays bounds (positive integers and min <= max) to prevent invalid search plans.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/tools/trip-create.ts, line 41:
<comment>Validate `tripLengthDays` bounds (positive integers and `min <= max`) to prevent invalid search plans.</comment>
<file context>
@@ -0,0 +1,97 @@
+ latestReturn: z
+ .string()
+ .describe("Latest acceptable return date (YYYY-MM-DD)"),
+ tripLengthDays: z.object({
+ min: z.number().describe("Minimum trip length in days"),
+ max: z.number().describe("Maximum trip length in days"),
</file context>
|
|
||
| ## CRITICAL RULES | ||
|
|
||
| 1. **MAX 10 SEARCHES PER TRIP.** The system hard-caps at 10. Keep date ranges tight. |
There was a problem hiding this comment.
P2: This makes 10 sound like a hard trip-wide cap, but drill-down searches can still be added after the initial plan.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/lib/agent-instructions.ts, line 5:
<comment>This makes 10 sound like a hard trip-wide cap, but drill-down searches can still be added after the initial plan.</comment>
<file context>
@@ -0,0 +1,54 @@
+
+## CRITICAL RULES
+
+1. **MAX 10 SEARCHES PER TRIP.** The system hard-caps at 10. Keep date ranges tight.
+2. **ALWAYS set currency in preferences.** Default to USD. Ask the user if unclear.
+ Example: \`preferences: { currency: "USD", maxStops: 1 }\`
</file context>
| type: "pack" as const, | ||
| }, | ||
| { | ||
| id: "deco-flights", |
There was a problem hiding this comment.
P2: Add an appId for the Flights registry template so the registry lookup can resolve its full metadata.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/mesh-sdk/src/lib/constants.ts, line 343:
<comment>Add an `appId` for the Flights registry template so the registry lookup can resolve its full metadata.</comment>
<file context>
@@ -333,6 +339,12 @@ export const WELL_KNOWN_AGENT_TEMPLATES = [
type: "pack" as const,
},
+ {
+ id: "deco-flights",
+ title: "Flights",
+ icon: "icon://Compass03?color=sky",
</file context>
| id: "deco-flights", | |
| id: "deco-flights", | |
| appId: "deco/flights", |
| export const TRIP_LIST = createTool({ | ||
| id: "TRIP_LIST", | ||
| description: | ||
| "List all saved trips with search tasks and top results (capped at 10 per trip).", |
There was a problem hiding this comment.
P3: The tool description says results are capped at 10, but the code actually returns up to 20.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/deco-flights/server/tools/trip-list.ts, line 9:
<comment>The tool description says results are capped at 10, but the code actually returns up to 20.</comment>
<file context>
@@ -0,0 +1,34 @@
+export const TRIP_LIST = createTool({
+ id: "TRIP_LIST",
+ description:
+ "List all saved trips with search tasks and top results (capped at 10 per trip).",
+ annotations: {
+ title: "List Trips",
</file context>
Summary
packages/deco-flights/standalone MCP server for flight research via Google Flightsfast-flights-ts@4.1.0(TypeScript port of fast-flights) for scraping/api/trips) for iframe polling (bypasses AppBridge limitations)Test plan
bun run --cwd packages/deco-flights devstarts server on :4747🤖 Generated with Claude Code
Summary by cubic
Add
@decocms/deco-flights, a standalone MCP agent for flight research powered by Google Flights, plus a home-screen Flights template and live dashboards. It runs parallel searches and streams real-time results with deep links to Google Flights.New Features
FLIGHT_SEARCH,TRIP_CREATE/GET/LIST/UPDATE/DELETE,TRIP_EXECUTE/STOP,TRIP_ADD_SEARCHES/api/tripsfor pollingWELL_KNOWN_AGENT_TEMPLATESMigration
bun run --cwd packages/deco-flights dev(port 4747)~/.deco/flights/trips/Written for commit b4def5e. Summary will update on new commits.