@pipeworx/mcp-aviationstack 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -9
- package/package.json +3 -3
- package/server.json +1 -1
- package/src/index.ts +194 -1
- package/src/server.ts +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @pipeworx/aviationstack
|
|
2
2
|
|
|
3
|
-
Aviationstack MCP — global flight tracking + airport / airline / route reference data.
|
|
3
|
+
Aviationstack MCP — global flight tracking + airport / airline / route reference data.
|
|
4
4
|
|
|
5
|
-
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1689+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
|
|
6
6
|
|
|
7
7
|
## Tools
|
|
8
8
|
|
|
@@ -12,19 +12,29 @@ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents
|
|
|
12
12
|
- `cities(search?, iata_code?, country_iso2?, limit?)` — city → primary airport mapping
|
|
13
13
|
- `countries(search?, country_iso2?, limit?)` — country reference
|
|
14
14
|
- `routes(dep_iata?, arr_iata?, airline_iata?, flight_number?, limit?)` — scheduled routes
|
|
15
|
+
- `aviationstack_future_flights(iataCode, type, date, airline_iata?, airline_icao?, flight_number?, limit?, offset?)` — scheduled future flights at one airport for a specific date (`type`: `departure` or `arrival`). Backed by Aviationstack's `/flightsFuture` endpoint, which apilayer's 2026-10-06 notice opened only for the **next 7 days** — `date` must be `tomorrow..today+7` (UTC); the pack refuses out-of-window dates itself before calling upstream (fleet #2700).
|
|
15
16
|
|
|
16
17
|
## Auth
|
|
17
18
|
|
|
18
19
|
- **Platform key:** gateway env `PLATFORM_AVIATIONSTACK_KEY`
|
|
19
20
|
- **BYO:** `?_apiKey=<key>` after registering at https://aviationstack.com/signup/free
|
|
20
21
|
|
|
21
|
-
###
|
|
22
|
+
### Plan tier
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
As of 2026-10-06 the shared platform key is on a plan that reaches `flights`, `airports`,
|
|
25
|
+
`airlines`, `cities`, `countries` and `routes` live (verified: `airports`/JFK and
|
|
26
|
+
`routes`/JFK→LAX both returned non-empty data — neither is free-tier-only any more; an
|
|
27
|
+
earlier version of this README, written 2026-08-28, said otherwise and was stale).
|
|
28
|
+
`aviationstack_future_flights` was added the same day the vendor emailed that they opened
|
|
29
|
+
`/flightsFuture` more broadly; whether our specific plan reaches *that* endpoint was not
|
|
30
|
+
independently confirmed before this tool shipped (the platform key's raw value is a
|
|
31
|
+
Cloudflare/Supabase secret, not available to a pack-authoring session) — if a live call
|
|
32
|
+
answers HTTP 403 `function_access_restricted`, that means the plan doesn't cover this
|
|
33
|
+
endpoint yet and is a money/plan decision, not a pack bug.
|
|
24
34
|
|
|
25
|
-
The free plan
|
|
26
|
-
endpoint answers 429 — including
|
|
27
|
-
|
|
35
|
+
The free plan (if the platform key ever reverts to it) is capped at **100 requests per
|
|
36
|
+
month**, and once that is spent every endpoint answers 429 — including ones the tier would
|
|
37
|
+
otherwise allow. Free plan is also HTTP-only (no https) — the pack already uses `http://`.
|
|
28
38
|
|
|
29
39
|
**Reading a 429 here:** Aviationstack labels the monthly-allowance refusal with the code
|
|
30
40
|
`rate_limit_reached` and only says "monthly" in the message text, so the code name argues
|
|
@@ -33,7 +43,8 @@ renews. The pack branches on the message, not the code. Fleet #577.
|
|
|
33
43
|
|
|
34
44
|
## Data source
|
|
35
45
|
|
|
36
|
-
`http://api.aviationstack.com/v1/` — `access_key` query param.
|
|
46
|
+
`http://api.aviationstack.com/v1/` — `access_key` query param. `/flightsFuture` spec:
|
|
47
|
+
https://api.swaggerhub.com/apis/apilayer-863/AviationstackAPI/1.0.0/swagger.json
|
|
37
48
|
|
|
38
49
|
## Quick Start
|
|
39
50
|
|
|
@@ -79,7 +90,7 @@ directly, instead of just this one's:
|
|
|
79
90
|
}
|
|
80
91
|
```
|
|
81
92
|
|
|
82
|
-
Both URLs reach the same gateway and the same
|
|
93
|
+
Both URLs reach the same gateway and the same 1689+ data sources. The
|
|
83
94
|
only difference is which pack's tools are listed **directly**; `ask_pipeworx`
|
|
84
95
|
reaches all of them from either one.
|
|
85
96
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipeworx/mcp-aviationstack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Aviationstack MCP — global flight + airport + airline data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@cloudflare/workers-types": "^4.20260405.1"
|
|
27
27
|
},
|
|
28
28
|
"pipeworx": {
|
|
29
|
-
"sourceHash": "v1-
|
|
30
|
-
"sourceCommit": "
|
|
29
|
+
"sourceHash": "v1-935ef0d23982e69db2037243fd0cba3475b438663a5b5a288b9f7e2189c770fe",
|
|
30
|
+
"sourceCommit": "211a6d307878cf994dd13cc72b7c2921b47759a7"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.pipeworx-io/aviationstack",
|
|
4
4
|
"title": "Aviationstack",
|
|
5
5
|
"description": "Aviationstack MCP — global flight + airport + airline data",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.3",
|
|
7
7
|
"websiteUrl": "https://pipeworx.io/packs/aviationstack",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/pipeworx-io/mcp-aviationstack",
|
package/src/index.ts
CHANGED
|
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
|
|
|
452
452
|
),
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
|
-
|
|
455
|
+
// Fleet #2382. Everything that isn't a timeout/abort here is a genuine
|
|
456
|
+
// NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
|
|
457
|
+
// Cloudflare's own "Network connection lost." — meaning `fetch()` itself
|
|
458
|
+
// threw and no HTTP response of any kind was ever received. Until this fix
|
|
459
|
+
// that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
|
|
460
|
+
// (or the Workers-runtime equivalent) names no upstream, carries no class
|
|
461
|
+
// token, and reads exactly like a defect in OUR code — because it says
|
|
462
|
+
// nothing about the call at all. It landed in `error`, the tier that means
|
|
463
|
+
// "Pipeworx has a defect", for every one of the (at the time of writing)
|
|
464
|
+
// ~470 packs that call this helper directly with no wrapper of their own.
|
|
465
|
+
//
|
|
466
|
+
// `dexscreener` hit this independently (fleet #1579) and fixed it with a
|
|
467
|
+
// bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
|
|
468
|
+
// but only covers one pack; every other caller of this shared helper still
|
|
469
|
+
// leaked the raw exception. Moving the same fix HERE — the one place that
|
|
470
|
+
// already carries the timeout case — covers every pack that uses
|
|
471
|
+
// `fetchWithTimeout` without a wrapper, for free, and without widening
|
|
472
|
+
// `classifyToolError`'s regex list: the fix is giving the message a proper
|
|
473
|
+
// `upstream_down:` token at the point the two facts (no response was ever
|
|
474
|
+
// received, and which host we were trying to reach) are actually in hand,
|
|
475
|
+
// not teaching the classifier to guess from prose after the fact.
|
|
476
|
+
//
|
|
477
|
+
// Safe on the same grounds as the timeout branch above: no argument a
|
|
478
|
+
// caller passes can make `fetch()` itself throw a connection-level error,
|
|
479
|
+
// so this is always an availability failure, never a caller mistake. Same
|
|
480
|
+
// `markInternalOrigin` treatment — an origin we run that never answered is
|
|
481
|
+
// still ours, not a third party's outage.
|
|
482
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
483
|
+
throw new Error(
|
|
484
|
+
markInternalOrigin(
|
|
485
|
+
`upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
|
|
486
|
+
`No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
|
|
487
|
+
'are valid — do not re-check them on the strength of this error. Retry shortly.',
|
|
488
|
+
url,
|
|
489
|
+
),
|
|
490
|
+
);
|
|
456
491
|
}
|
|
457
492
|
}
|
|
458
493
|
|
|
@@ -742,6 +777,109 @@ const tools: McpToolExport['tools'] = [
|
|
|
742
777
|
},
|
|
743
778
|
},
|
|
744
779
|
},
|
|
780
|
+
{
|
|
781
|
+
// apilayer emailed developer@mojibake.ai on 2026-10-06 ("What the new
|
|
782
|
+
// /flightsFuture range changes for your app"): they opened this endpoint
|
|
783
|
+
// to cover the next 7 days for itinerary/booking views. Vendor spec:
|
|
784
|
+
// https://api.swaggerhub.com/apis/apilayer-863/AviationstackAPI/1.0.0/swagger.json
|
|
785
|
+
// (paths./v1/flightsFuture — iataCode + type + date all required; date
|
|
786
|
+
// format YYYY-MM-DD). The 7-day window is the vendor's own notice, not a
|
|
787
|
+
// field the swagger spec encodes, so it is enforced here rather than left
|
|
788
|
+
// to a far-future call failing late and unexplained (fleet #2700).
|
|
789
|
+
name: 'aviationstack_future_flights',
|
|
790
|
+
description:
|
|
791
|
+
"Scheduled future flights at one airport, from Aviationstack's /flightsFuture endpoint — what flies a route on a day next week. Requires an IATA airport code, a direction (departure or arrival), and a date; returns each flight's number, airline, aircraft model, and scheduled terminal/gate/time. Aviationstack's booking-view window for this endpoint currently covers only the next 7 days (apilayer notice, 2026-10-06): date must be between tomorrow and 7 days from today — a date outside that window is refused before the call is made, not after a wasted round trip.",
|
|
792
|
+
inputSchema: {
|
|
793
|
+
type: 'object',
|
|
794
|
+
properties: {
|
|
795
|
+
iataCode: { type: 'string', description: '3-letter IATA airport code whose schedule to look up (e.g. "JFK")' },
|
|
796
|
+
type: {
|
|
797
|
+
type: 'string',
|
|
798
|
+
enum: ['departure', 'arrival'],
|
|
799
|
+
description: 'Whether to return flights scheduled to depart or arrive at iataCode',
|
|
800
|
+
},
|
|
801
|
+
date: {
|
|
802
|
+
type: 'string',
|
|
803
|
+
description:
|
|
804
|
+
'Future date, format YYYY-MM-DD, must fall within tomorrow through 7 days from today (UTC) — Aviationstack\'s current flightsFuture window.',
|
|
805
|
+
},
|
|
806
|
+
airline_iata: { type: 'string', description: 'Filter by airline IATA code' },
|
|
807
|
+
airline_icao: { type: 'string', description: 'Filter by airline ICAO code' },
|
|
808
|
+
flight_number: { type: 'string', description: 'Filter by flight number' },
|
|
809
|
+
limit: { type: 'number', description: '1-100 (default 100)' },
|
|
810
|
+
offset: { type: 'number', description: '0-based offset' },
|
|
811
|
+
},
|
|
812
|
+
required: ['iataCode', 'type', 'date'],
|
|
813
|
+
},
|
|
814
|
+
outputSchema: {
|
|
815
|
+
type: 'object',
|
|
816
|
+
properties: {
|
|
817
|
+
pagination: {
|
|
818
|
+
type: 'object',
|
|
819
|
+
properties: {
|
|
820
|
+
limit: { type: 'number' },
|
|
821
|
+
offset: { type: 'number' },
|
|
822
|
+
count: { type: 'number' },
|
|
823
|
+
total: { type: 'number' },
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
data: {
|
|
827
|
+
type: 'array',
|
|
828
|
+
items: {
|
|
829
|
+
type: 'object',
|
|
830
|
+
properties: {
|
|
831
|
+
weekday: { type: ['string', 'null'], description: 'ISO weekday number 1-7 as a string' },
|
|
832
|
+
departure: {
|
|
833
|
+
type: 'object',
|
|
834
|
+
properties: {
|
|
835
|
+
iataCode: { type: ['string', 'null'] },
|
|
836
|
+
icaoCode: { type: ['string', 'null'] },
|
|
837
|
+
terminal: { type: ['string', 'null'] },
|
|
838
|
+
gate: { type: ['string', 'null'] },
|
|
839
|
+
scheduledTime: { type: ['string', 'null'], description: 'Local time, HH:MM' },
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
arrival: {
|
|
843
|
+
type: 'object',
|
|
844
|
+
properties: {
|
|
845
|
+
iataCode: { type: ['string', 'null'] },
|
|
846
|
+
icaoCode: { type: ['string', 'null'] },
|
|
847
|
+
terminal: { type: ['string', 'null'] },
|
|
848
|
+
gate: { type: ['string', 'null'] },
|
|
849
|
+
scheduledTime: { type: ['string', 'null'], description: 'Local time, HH:MM' },
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
aircraft: {
|
|
853
|
+
type: ['object', 'null'],
|
|
854
|
+
properties: {
|
|
855
|
+
modelCode: { type: ['string', 'null'] },
|
|
856
|
+
modelText: { type: ['string', 'null'] },
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
airline: {
|
|
860
|
+
type: 'object',
|
|
861
|
+
properties: {
|
|
862
|
+
name: { type: ['string', 'null'] },
|
|
863
|
+
iataCode: { type: ['string', 'null'] },
|
|
864
|
+
icaoCode: { type: ['string', 'null'] },
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
flight: {
|
|
868
|
+
type: 'object',
|
|
869
|
+
properties: {
|
|
870
|
+
number: { type: ['string', 'null'] },
|
|
871
|
+
iataNumber: { type: ['string', 'null'] },
|
|
872
|
+
icaoNumber: { type: ['string', 'null'] },
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
codeshared: { type: ['object', 'null'] },
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
data_as_of: { type: 'string', description: 'ISO timestamp when this response was fetched live from Aviationstack' },
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
},
|
|
745
883
|
];
|
|
746
884
|
|
|
747
885
|
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
@@ -751,6 +889,11 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
|
|
|
751
889
|
'Aviationstack requires an API key. Contact the operator about platform credentials, or BYO via ?_apiKey=<key> after registering at https://aviationstack.com/signup/free.',
|
|
752
890
|
);
|
|
753
891
|
}
|
|
892
|
+
|
|
893
|
+
if (name === 'aviationstack_future_flights') {
|
|
894
|
+
return getFutureFlights(apiKey, args);
|
|
895
|
+
}
|
|
896
|
+
|
|
754
897
|
const path = `/${name}`;
|
|
755
898
|
const params = new URLSearchParams({ access_key: apiKey });
|
|
756
899
|
for (const [k, v] of Object.entries(args)) {
|
|
@@ -760,6 +903,56 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
|
|
|
760
903
|
return avsGet(`${path}?${params}`);
|
|
761
904
|
}
|
|
762
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Validate the date is inside Aviationstack's current flightsFuture window
|
|
908
|
+
* (tomorrow through 7 days from today, UTC — apilayer notice, 2026-10-06)
|
|
909
|
+
* BEFORE spending a call on it. Refusing early and by name beats a vendor
|
|
910
|
+
* 200-with-empty-data or a late 4xx for a date the caller had no way to know
|
|
911
|
+
* was out of range.
|
|
912
|
+
*/
|
|
913
|
+
function assertWithinFutureWindow(date: string): void {
|
|
914
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
915
|
+
throw new Error(`Aviationstack future_flights: date must be YYYY-MM-DD, got "${date}".`);
|
|
916
|
+
}
|
|
917
|
+
const requested = Date.parse(`${date}T00:00:00Z`);
|
|
918
|
+
if (Number.isNaN(requested)) {
|
|
919
|
+
throw new Error(`Aviationstack future_flights: "${date}" is not a valid calendar date.`);
|
|
920
|
+
}
|
|
921
|
+
const now = new Date();
|
|
922
|
+
const todayUtc = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
923
|
+
const DAY_MS = 86_400_000;
|
|
924
|
+
const minDate = todayUtc + DAY_MS; // tomorrow
|
|
925
|
+
const maxDate = todayUtc + 7 * DAY_MS; // today + 7
|
|
926
|
+
if (requested < minDate || requested > maxDate) {
|
|
927
|
+
const min = new Date(minDate).toISOString().slice(0, 10);
|
|
928
|
+
const max = new Date(maxDate).toISOString().slice(0, 10);
|
|
929
|
+
throw new Error(
|
|
930
|
+
`Aviationstack future_flights: "${date}" is outside the vendor's current flightsFuture window. Aviationstack opened this endpoint to only the next 7 days (apilayer notice, 2026-10-06) — valid range today is ${min} through ${max}. Pass a date inside that range.`,
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function getFutureFlights(apiKey: string, args: Record<string, unknown>): Promise<unknown> {
|
|
936
|
+
const iataCode = (args.iataCode as string | undefined)?.trim();
|
|
937
|
+
const type = args.type as string | undefined;
|
|
938
|
+
const date = (args.date as string | undefined)?.trim();
|
|
939
|
+
if (!iataCode) throw new Error('Aviationstack future_flights: iataCode is required (3-letter airport code, e.g. "JFK").');
|
|
940
|
+
if (type !== 'departure' && type !== 'arrival') {
|
|
941
|
+
throw new Error(`Aviationstack future_flights: type must be "departure" or "arrival", got "${type ?? ''}".`);
|
|
942
|
+
}
|
|
943
|
+
if (!date) throw new Error('Aviationstack future_flights: date is required (YYYY-MM-DD).');
|
|
944
|
+
assertWithinFutureWindow(date);
|
|
945
|
+
|
|
946
|
+
const params = new URLSearchParams({ access_key: apiKey, iataCode, type, date });
|
|
947
|
+
for (const k of ['airline_iata', 'airline_icao', 'flight_number', 'limit', 'offset'] as const) {
|
|
948
|
+
const v = args[k];
|
|
949
|
+
if (v === undefined || v === null || v === '') continue;
|
|
950
|
+
params.set(k, String(v));
|
|
951
|
+
}
|
|
952
|
+
const data = (await avsGet(`/flightsFuture?${params}`)) as Record<string, unknown>;
|
|
953
|
+
return { ...data, data_as_of: new Date().toISOString() };
|
|
954
|
+
}
|
|
955
|
+
|
|
763
956
|
/**
|
|
764
957
|
* Turn an Aviationstack limit refusal into a sentence that says which limit.
|
|
765
958
|
*
|
package/src/server.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
9
9
|
import pack from './index.js';
|
|
10
10
|
|
|
11
11
|
const server = new Server(
|
|
12
|
-
{ name: '@pipeworx/mcp-aviationstack', version: '0.1.
|
|
12
|
+
{ name: '@pipeworx/mcp-aviationstack', version: '0.1.3' },
|
|
13
13
|
{ capabilities: { tools: {} } },
|
|
14
14
|
);
|
|
15
15
|
|