aviationstack-mcp-server 0.1.0__py3-none-any.whl
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.
- aviationstack_mcp_server/__init__.py +0 -0
- aviationstack_mcp_server/__main__.py +15 -0
- aviationstack_mcp_server/client/__init__.py +9 -0
- aviationstack_mcp_server/client/aviationstack.py +138 -0
- aviationstack_mcp_server/client/factory.py +30 -0
- aviationstack_mcp_server/client/http.py +292 -0
- aviationstack_mcp_server/config/__init__.py +3 -0
- aviationstack_mcp_server/config/settings.py +162 -0
- aviationstack_mcp_server/errors/__init__.py +25 -0
- aviationstack_mcp_server/errors/exceptions.py +56 -0
- aviationstack_mcp_server/logging_config.py +52 -0
- aviationstack_mcp_server/mcp/__init__.py +3 -0
- aviationstack_mcp_server/mcp/dependencies.py +64 -0
- aviationstack_mcp_server/mcp/errors.py +118 -0
- aviationstack_mcp_server/mcp/prompts/__init__.py +0 -0
- aviationstack_mcp_server/mcp/prompts/aviation.py +98 -0
- aviationstack_mcp_server/mcp/resources/__init__.py +0 -0
- aviationstack_mcp_server/mcp/resources/documentation.py +165 -0
- aviationstack_mcp_server/mcp/resources/metadata.py +46 -0
- aviationstack_mcp_server/mcp/server.py +164 -0
- aviationstack_mcp_server/mcp/tools/__init__.py +0 -0
- aviationstack_mcp_server/mcp/tools/aircraft.py +64 -0
- aviationstack_mcp_server/mcp/tools/airlines.py +43 -0
- aviationstack_mcp_server/mcp/tools/airports.py +43 -0
- aviationstack_mcp_server/mcp/tools/flights.py +125 -0
- aviationstack_mcp_server/mcp/tools/reference.py +129 -0
- aviationstack_mcp_server/models/__init__.py +81 -0
- aviationstack_mcp_server/models/aircraft.py +43 -0
- aviationstack_mcp_server/models/airline.py +29 -0
- aviationstack_mcp_server/models/airport.py +33 -0
- aviationstack_mcp_server/models/common.py +20 -0
- aviationstack_mcp_server/models/flight.py +125 -0
- aviationstack_mcp_server/models/location.py +50 -0
- aviationstack_mcp_server/models/queries.py +284 -0
- aviationstack_mcp_server/models/route.py +24 -0
- aviationstack_mcp_server/models/tax.py +20 -0
- aviationstack_mcp_server/observability.py +27 -0
- aviationstack_mcp_server/security.py +62 -0
- aviationstack_mcp_server/services/__init__.py +13 -0
- aviationstack_mcp_server/services/aircraft_service.py +156 -0
- aviationstack_mcp_server/services/airline_service.py +89 -0
- aviationstack_mcp_server/services/airport_service.py +116 -0
- aviationstack_mcp_server/services/flight_service.py +366 -0
- aviationstack_mcp_server/services/reference_service.py +315 -0
- aviationstack_mcp_server/utils/__init__.py +0 -0
- aviationstack_mcp_server-0.1.0.dist-info/METADATA +57 -0
- aviationstack_mcp_server-0.1.0.dist-info/RECORD +50 -0
- aviationstack_mcp_server-0.1.0.dist-info/WHEEL +4 -0
- aviationstack_mcp_server-0.1.0.dist-info/entry_points.txt +3 -0
- aviationstack_mcp_server-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Airline(AviationstackModel):
|
|
9
|
+
"""Airline reference data."""
|
|
10
|
+
|
|
11
|
+
airline_name: str | None = None
|
|
12
|
+
iata_code: str | None = None
|
|
13
|
+
icao_code: str | None = None
|
|
14
|
+
callsign: str | None = None
|
|
15
|
+
status: str | None = None
|
|
16
|
+
country_name: str | None = None
|
|
17
|
+
country_iso2: str | None = None
|
|
18
|
+
|
|
19
|
+
airline_id: str | None = Field(
|
|
20
|
+
default=None,
|
|
21
|
+
alias="airline_id",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AirlineResponse(AviationstackModel):
|
|
26
|
+
"""Aviationstack airline response."""
|
|
27
|
+
|
|
28
|
+
pagination: dict | None = None
|
|
29
|
+
data: list[Airline] = Field(default_factory=list)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Airport(AviationstackModel):
|
|
9
|
+
"""Airport reference data."""
|
|
10
|
+
|
|
11
|
+
airport_name: str | None = None
|
|
12
|
+
iata_code: str | None = None
|
|
13
|
+
icao_code: str | None = None
|
|
14
|
+
city_iata_code: str | None = None
|
|
15
|
+
country_name: str | None = None
|
|
16
|
+
country_iso2: str | None = None
|
|
17
|
+
timezone: str | None = None
|
|
18
|
+
gmt: str | None = None
|
|
19
|
+
|
|
20
|
+
latitude: float | None = None
|
|
21
|
+
longitude: float | None = None
|
|
22
|
+
|
|
23
|
+
airport_id: str | None = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
alias="airport_id",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AirportResponse(AviationstackModel):
|
|
30
|
+
"""Aviationstack airport response."""
|
|
31
|
+
|
|
32
|
+
pagination: dict | None = None
|
|
33
|
+
data: list[Airport] = Field(default_factory=list)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AviationstackModel(BaseModel):
|
|
9
|
+
"""Base model for Aviationstack API data."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(
|
|
12
|
+
extra="allow",
|
|
13
|
+
populate_by_name=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AviationstackResponse(AviationstackModel):
|
|
18
|
+
"""Generic envelope returned by Aviationstack."""
|
|
19
|
+
|
|
20
|
+
pagination: dict[str, Any] | None = None
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FlightIdentifier(AviationstackModel):
|
|
9
|
+
"""Flight identification information."""
|
|
10
|
+
|
|
11
|
+
iata: str | None = None
|
|
12
|
+
icao: str | None = None
|
|
13
|
+
number: str | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AirlineReference(AviationstackModel):
|
|
17
|
+
"""Airline information associated with a flight."""
|
|
18
|
+
|
|
19
|
+
name: str | None = None
|
|
20
|
+
iata: str | None = None
|
|
21
|
+
icao: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AirportReference(AviationstackModel):
|
|
25
|
+
"""Airport information associated with a flight."""
|
|
26
|
+
|
|
27
|
+
airport: str | None = None
|
|
28
|
+
iata: str | None = None
|
|
29
|
+
icao: str | None = None
|
|
30
|
+
timezone: str | None = None
|
|
31
|
+
terminal: str | None = None
|
|
32
|
+
gate: str | None = None
|
|
33
|
+
|
|
34
|
+
scheduled: str | None = None
|
|
35
|
+
estimated: str | None = None
|
|
36
|
+
actual: str | None = None
|
|
37
|
+
delay: int | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FlightAircraftReference(AviationstackModel):
|
|
41
|
+
"""Aircraft information associated with a flight."""
|
|
42
|
+
|
|
43
|
+
registration: str | None = None
|
|
44
|
+
iata: str | None = None
|
|
45
|
+
icao: str | None = None
|
|
46
|
+
model: str | None = None
|
|
47
|
+
model_code: str | None = None
|
|
48
|
+
model_text: str | None = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
alias="modelText",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Flight(AviationstackModel):
|
|
55
|
+
"""Aviationstack flight record."""
|
|
56
|
+
|
|
57
|
+
flight_date: str | None = None
|
|
58
|
+
flight_status: str | None = None
|
|
59
|
+
|
|
60
|
+
flight: FlightIdentifier | None = None
|
|
61
|
+
airline: AirlineReference | None = None
|
|
62
|
+
departure: AirportReference | None = None
|
|
63
|
+
arrival: AirportReference | None = None
|
|
64
|
+
aircraft: FlightAircraftReference | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FlightResponse(AviationstackModel):
|
|
68
|
+
"""Aviationstack response containing flight records."""
|
|
69
|
+
|
|
70
|
+
pagination: dict | None = None
|
|
71
|
+
data: list[Flight] = Field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ScheduleFlightIdentifier(AviationstackModel):
|
|
75
|
+
"""Flight identifier returned by timetable endpoints."""
|
|
76
|
+
|
|
77
|
+
iata_number: str | None = Field(
|
|
78
|
+
default=None,
|
|
79
|
+
alias="iataNumber",
|
|
80
|
+
)
|
|
81
|
+
icao_number: str | None = Field(
|
|
82
|
+
default=None,
|
|
83
|
+
alias="icaoNumber",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ScheduleTimeReference(AviationstackModel):
|
|
88
|
+
"""Schedule timing information."""
|
|
89
|
+
|
|
90
|
+
estimated_time: str | None = Field(
|
|
91
|
+
default=None,
|
|
92
|
+
alias="estimatedTime",
|
|
93
|
+
)
|
|
94
|
+
scheduled_time: str | None = Field(
|
|
95
|
+
default=None,
|
|
96
|
+
alias="scheduledTime",
|
|
97
|
+
)
|
|
98
|
+
actual_time: str | None = Field(
|
|
99
|
+
default=None,
|
|
100
|
+
alias="actualTime",
|
|
101
|
+
)
|
|
102
|
+
iata_code: str | None = Field(
|
|
103
|
+
default=None,
|
|
104
|
+
alias="iataCode",
|
|
105
|
+
)
|
|
106
|
+
terminal: str | None = None
|
|
107
|
+
gate: str | None = None
|
|
108
|
+
delay: int | None = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ScheduleFlight(AviationstackModel):
|
|
112
|
+
"""Flight record returned by timetable/future schedule endpoints."""
|
|
113
|
+
|
|
114
|
+
airline: AirlineReference | None = None
|
|
115
|
+
flight: ScheduleFlightIdentifier | None = None
|
|
116
|
+
departure: ScheduleTimeReference | None = None
|
|
117
|
+
arrival: ScheduleTimeReference | None = None
|
|
118
|
+
aircraft: FlightAircraftReference | None = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ScheduleResponse(AviationstackModel):
|
|
122
|
+
"""Response containing scheduled flights."""
|
|
123
|
+
|
|
124
|
+
pagination: dict | None = None
|
|
125
|
+
data: list[ScheduleFlight] = Field(default_factory=list)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Country(AviationstackModel):
|
|
9
|
+
"""Country reference data."""
|
|
10
|
+
|
|
11
|
+
name: str | None = None
|
|
12
|
+
capital: str | None = None
|
|
13
|
+
currency_code: str | None = None
|
|
14
|
+
fips_code: str | None = None
|
|
15
|
+
country_iso2: str | None = None
|
|
16
|
+
country_iso3: str | None = None
|
|
17
|
+
continent: str | None = None
|
|
18
|
+
country_id: str | None = None
|
|
19
|
+
currency_name: str | None = None
|
|
20
|
+
country_iso_numeric: str | None = None
|
|
21
|
+
phone_prefix: str | None = None
|
|
22
|
+
population: int | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CountryResponse(AviationstackModel):
|
|
26
|
+
"""Response containing countries."""
|
|
27
|
+
|
|
28
|
+
pagination: dict | None = None
|
|
29
|
+
data: list[Country] = Field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class City(AviationstackModel):
|
|
33
|
+
"""City reference data."""
|
|
34
|
+
|
|
35
|
+
gmt: str | None = None
|
|
36
|
+
city_id: str | None = None
|
|
37
|
+
iata_code: str | None = None
|
|
38
|
+
country_iso2: str | None = None
|
|
39
|
+
geoname_id: str | None = None
|
|
40
|
+
latitude: float | None = None
|
|
41
|
+
longitude: float | None = None
|
|
42
|
+
timezone: str | None = None
|
|
43
|
+
city_name: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CityResponse(AviationstackModel):
|
|
47
|
+
"""Response containing cities."""
|
|
48
|
+
|
|
49
|
+
pagination: dict | None = None
|
|
50
|
+
data: list[City] = Field(default_factory=list)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date as Date
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QueryModel(BaseModel):
|
|
10
|
+
"""Base model for application query/input objects."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(
|
|
13
|
+
extra="forbid",
|
|
14
|
+
str_strip_whitespace=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PaginationQuery(QueryModel):
|
|
19
|
+
"""Common pagination parameters."""
|
|
20
|
+
|
|
21
|
+
limit: int = Field(
|
|
22
|
+
default=10,
|
|
23
|
+
ge=1,
|
|
24
|
+
le=100,
|
|
25
|
+
description="Maximum number of records to return.",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
offset: int = Field(
|
|
29
|
+
default=0,
|
|
30
|
+
ge=0,
|
|
31
|
+
description="Number of records to skip.",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ScheduleType(StrEnum):
|
|
36
|
+
"""Supported airport schedule types."""
|
|
37
|
+
|
|
38
|
+
ARRIVAL = "arrival"
|
|
39
|
+
DEPARTURE = "departure"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FlightSearchQuery(QueryModel):
|
|
43
|
+
"""Parameters for searching live flights."""
|
|
44
|
+
|
|
45
|
+
airline_name: str | None = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Airline name, such as Delta Air Lines.",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
airline_iata: str | None = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
min_length=2,
|
|
53
|
+
max_length=3,
|
|
54
|
+
description="Airline IATA code, such as DL.",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
flight_iata: str | None = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="Flight IATA number, such as DL123.",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
flight_icao: str | None = Field(
|
|
63
|
+
default=None,
|
|
64
|
+
description="Flight ICAO identifier.",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
departure_iata: str | None = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
min_length=3,
|
|
70
|
+
max_length=3,
|
|
71
|
+
description="Departure airport IATA code.",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
arrival_iata: str | None = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
min_length=3,
|
|
77
|
+
max_length=3,
|
|
78
|
+
description="Arrival airport IATA code.",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
limit: int = Field(
|
|
82
|
+
default=10,
|
|
83
|
+
ge=1,
|
|
84
|
+
le=100,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@field_validator(
|
|
88
|
+
"airline_iata",
|
|
89
|
+
"departure_iata",
|
|
90
|
+
"arrival_iata",
|
|
91
|
+
mode="before",
|
|
92
|
+
)
|
|
93
|
+
@classmethod
|
|
94
|
+
def normalize_iata_code(cls, value: str | None) -> str | None:
|
|
95
|
+
"""Normalize IATA codes to uppercase."""
|
|
96
|
+
|
|
97
|
+
if value is None:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
return value.strip().upper()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class HistoricalFlightQuery(QueryModel):
|
|
104
|
+
"""Parameters for historical flight searches."""
|
|
105
|
+
|
|
106
|
+
flight_date: Date = Field(
|
|
107
|
+
description="Flight date in YYYY-MM-DD format.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
airline_iata: str | None = Field(
|
|
111
|
+
default=None,
|
|
112
|
+
min_length=2,
|
|
113
|
+
max_length=3,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
departure_iata: str | None = Field(
|
|
117
|
+
default=None,
|
|
118
|
+
min_length=3,
|
|
119
|
+
max_length=3,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
arrival_iata: str | None = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
min_length=3,
|
|
125
|
+
max_length=3,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
limit: int = Field(
|
|
129
|
+
default=10,
|
|
130
|
+
ge=1,
|
|
131
|
+
le=100,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@field_validator(
|
|
135
|
+
"airline_iata",
|
|
136
|
+
"departure_iata",
|
|
137
|
+
"arrival_iata",
|
|
138
|
+
mode="before",
|
|
139
|
+
)
|
|
140
|
+
@classmethod
|
|
141
|
+
def normalize_iata_code(cls, value: str | None) -> str | None:
|
|
142
|
+
"""Normalize IATA codes."""
|
|
143
|
+
|
|
144
|
+
if value is None:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
return value.strip().upper()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class AirportScheduleQuery(QueryModel):
|
|
151
|
+
"""Parameters for airport arrival/departure schedule queries."""
|
|
152
|
+
|
|
153
|
+
airport_iata: str = Field(
|
|
154
|
+
min_length=3,
|
|
155
|
+
max_length=3,
|
|
156
|
+
description="Airport IATA code.",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
schedule_type: ScheduleType = Field(
|
|
160
|
+
description="Whether to retrieve arrival or departure schedules.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
airline_name: str | None = Field(
|
|
164
|
+
default=None,
|
|
165
|
+
description="Optional airline name filter.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
limit: int = Field(
|
|
169
|
+
default=10,
|
|
170
|
+
ge=1,
|
|
171
|
+
le=100,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@field_validator("airport_iata", mode="before")
|
|
175
|
+
@classmethod
|
|
176
|
+
def normalize_airport_code(cls, value: str) -> str:
|
|
177
|
+
"""Normalize airport IATA code."""
|
|
178
|
+
|
|
179
|
+
return value.strip().upper()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class FutureScheduleQuery(QueryModel):
|
|
183
|
+
"""Parameters for future airport schedule queries."""
|
|
184
|
+
|
|
185
|
+
airport_iata: str = Field(
|
|
186
|
+
min_length=3,
|
|
187
|
+
max_length=3,
|
|
188
|
+
description="Airport IATA code.",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
schedule_type: ScheduleType
|
|
192
|
+
|
|
193
|
+
date: Date = Field(
|
|
194
|
+
description="Future schedule date.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
airline_iata: str | None = Field(
|
|
198
|
+
default=None,
|
|
199
|
+
min_length=2,
|
|
200
|
+
max_length=3,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
limit: int = Field(
|
|
204
|
+
default=10,
|
|
205
|
+
ge=1,
|
|
206
|
+
le=100,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@field_validator("airport_iata", mode="before")
|
|
210
|
+
@classmethod
|
|
211
|
+
def normalize_airport_code(cls, value: str) -> str:
|
|
212
|
+
return value.strip().upper()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class AirportSearchQuery(PaginationQuery):
|
|
216
|
+
"""Parameters for airport searches."""
|
|
217
|
+
|
|
218
|
+
search: str | None = Field(
|
|
219
|
+
default=None,
|
|
220
|
+
description="Airport name, city, or other search text.",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class AirlineSearchQuery(PaginationQuery):
|
|
225
|
+
"""Parameters for airline searches."""
|
|
226
|
+
|
|
227
|
+
search: str | None = Field(
|
|
228
|
+
default=None,
|
|
229
|
+
description="Airline search text.",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class RouteSearchQuery(PaginationQuery):
|
|
234
|
+
"""Parameters for route searches."""
|
|
235
|
+
|
|
236
|
+
airline_iata: str | None = Field(
|
|
237
|
+
default=None,
|
|
238
|
+
min_length=2,
|
|
239
|
+
max_length=3,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
departure_iata: str | None = Field(
|
|
243
|
+
default=None,
|
|
244
|
+
min_length=3,
|
|
245
|
+
max_length=3,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
arrival_iata: str | None = Field(
|
|
249
|
+
default=None,
|
|
250
|
+
min_length=3,
|
|
251
|
+
max_length=3,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@field_validator(
|
|
255
|
+
"airline_iata",
|
|
256
|
+
"departure_iata",
|
|
257
|
+
"arrival_iata",
|
|
258
|
+
mode="before",
|
|
259
|
+
)
|
|
260
|
+
@classmethod
|
|
261
|
+
def normalize_codes(cls, value: str | None) -> str | None:
|
|
262
|
+
if value is None:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
return value.strip().upper()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class TaxSearchQuery(PaginationQuery):
|
|
269
|
+
"""Parameters for tax searches."""
|
|
270
|
+
|
|
271
|
+
search: str | None = Field(
|
|
272
|
+
default=None,
|
|
273
|
+
description="Tax name or code search text.",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class RecordLimitQuery(QueryModel):
|
|
278
|
+
"""Query for bounded reference-data retrieval."""
|
|
279
|
+
|
|
280
|
+
limit: int = Field(
|
|
281
|
+
default=10,
|
|
282
|
+
ge=1,
|
|
283
|
+
le=100,
|
|
284
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Route(AviationstackModel):
|
|
9
|
+
"""Airline route reference data."""
|
|
10
|
+
|
|
11
|
+
airline_iata: str | None = None
|
|
12
|
+
airline_icao: str | None = None
|
|
13
|
+
flight_number: str | None = None
|
|
14
|
+
dep_iata: str | None = None
|
|
15
|
+
dep_icao: str | None = None
|
|
16
|
+
arr_iata: str | None = None
|
|
17
|
+
arr_icao: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RouteResponse(AviationstackModel):
|
|
21
|
+
"""Response containing routes."""
|
|
22
|
+
|
|
23
|
+
pagination: dict | None = None
|
|
24
|
+
data: list[Route] = Field(default_factory=list)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.models.common import AviationstackModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Tax(AviationstackModel):
|
|
9
|
+
"""Aviation tax reference data."""
|
|
10
|
+
|
|
11
|
+
tax_id: str | None = None
|
|
12
|
+
tax_name: str | None = None
|
|
13
|
+
iata_code: str | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaxResponse(AviationstackModel):
|
|
17
|
+
"""Response containing aviation taxes."""
|
|
18
|
+
|
|
19
|
+
pagination: dict | None = None
|
|
20
|
+
data: list[Tax] = Field(default_factory=list)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
correlation_id: ContextVar[str] = ContextVar(
|
|
7
|
+
"correlation_id",
|
|
8
|
+
default="",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_correlation_id() -> str:
|
|
13
|
+
"""Generate a correlation ID for an operation."""
|
|
14
|
+
|
|
15
|
+
return uuid4().hex
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_correlation_id() -> str:
|
|
19
|
+
"""Return the current correlation ID."""
|
|
20
|
+
|
|
21
|
+
return correlation_id.get()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def set_correlation_id(value: str) -> None:
|
|
25
|
+
"""Set the current correlation ID."""
|
|
26
|
+
|
|
27
|
+
correlation_id.set(value)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from aviationstack_mcp_server.errors import AviationstackConfigurationError
|
|
6
|
+
|
|
7
|
+
SENSITIVE_KEYS = frozenset(
|
|
8
|
+
{
|
|
9
|
+
"access_key",
|
|
10
|
+
"api_key",
|
|
11
|
+
"apikey",
|
|
12
|
+
"authorization",
|
|
13
|
+
"token",
|
|
14
|
+
"password",
|
|
15
|
+
"secret",
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def redact_mapping(
|
|
21
|
+
values: dict[str, object],
|
|
22
|
+
) -> dict[str, object]:
|
|
23
|
+
"""Return a copy with sensitive values redacted."""
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
key: ("***REDACTED***" if key.lower() in SENSITIVE_KEYS else value)
|
|
27
|
+
for key, value in values.items()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_api_base_url(
|
|
32
|
+
base_url: str,
|
|
33
|
+
*,
|
|
34
|
+
environment: str,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Enforce secure, provider-specific API URL policy in production."""
|
|
37
|
+
|
|
38
|
+
parsed = urlparse(base_url)
|
|
39
|
+
|
|
40
|
+
if parsed.username or parsed.password or parsed.query or parsed.fragment:
|
|
41
|
+
raise AviationstackConfigurationError(
|
|
42
|
+
"API base URL must not contain credentials, query parameters, or fragments."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if environment == "production":
|
|
46
|
+
if parsed.scheme != "https":
|
|
47
|
+
raise AviationstackConfigurationError("Production API base URL must use HTTPS.")
|
|
48
|
+
|
|
49
|
+
if parsed.hostname != "api.aviationstack.com":
|
|
50
|
+
raise AviationstackConfigurationError(
|
|
51
|
+
"Production API base URL must use the Aviationstack API host."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_endpoint(endpoint: str) -> None:
|
|
56
|
+
"""Reject endpoint inputs that could turn the client into a URL proxy."""
|
|
57
|
+
|
|
58
|
+
parsed = urlparse(endpoint)
|
|
59
|
+
if parsed.scheme or parsed.netloc or parsed.query or parsed.fragment:
|
|
60
|
+
raise AviationstackConfigurationError(
|
|
61
|
+
"Aviationstack endpoints must be relative paths without query parameters."
|
|
62
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .aircraft_service import AircraftService
|
|
2
|
+
from .airline_service import AirlineService
|
|
3
|
+
from .airport_service import AirportService
|
|
4
|
+
from .flight_service import FlightService
|
|
5
|
+
from .reference_service import ReferenceDataService
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AircraftService",
|
|
9
|
+
"AirlineService",
|
|
10
|
+
"AirportService",
|
|
11
|
+
"FlightService",
|
|
12
|
+
"ReferenceDataService",
|
|
13
|
+
]
|