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.
Files changed (50) hide show
  1. aviationstack_mcp_server/__init__.py +0 -0
  2. aviationstack_mcp_server/__main__.py +15 -0
  3. aviationstack_mcp_server/client/__init__.py +9 -0
  4. aviationstack_mcp_server/client/aviationstack.py +138 -0
  5. aviationstack_mcp_server/client/factory.py +30 -0
  6. aviationstack_mcp_server/client/http.py +292 -0
  7. aviationstack_mcp_server/config/__init__.py +3 -0
  8. aviationstack_mcp_server/config/settings.py +162 -0
  9. aviationstack_mcp_server/errors/__init__.py +25 -0
  10. aviationstack_mcp_server/errors/exceptions.py +56 -0
  11. aviationstack_mcp_server/logging_config.py +52 -0
  12. aviationstack_mcp_server/mcp/__init__.py +3 -0
  13. aviationstack_mcp_server/mcp/dependencies.py +64 -0
  14. aviationstack_mcp_server/mcp/errors.py +118 -0
  15. aviationstack_mcp_server/mcp/prompts/__init__.py +0 -0
  16. aviationstack_mcp_server/mcp/prompts/aviation.py +98 -0
  17. aviationstack_mcp_server/mcp/resources/__init__.py +0 -0
  18. aviationstack_mcp_server/mcp/resources/documentation.py +165 -0
  19. aviationstack_mcp_server/mcp/resources/metadata.py +46 -0
  20. aviationstack_mcp_server/mcp/server.py +164 -0
  21. aviationstack_mcp_server/mcp/tools/__init__.py +0 -0
  22. aviationstack_mcp_server/mcp/tools/aircraft.py +64 -0
  23. aviationstack_mcp_server/mcp/tools/airlines.py +43 -0
  24. aviationstack_mcp_server/mcp/tools/airports.py +43 -0
  25. aviationstack_mcp_server/mcp/tools/flights.py +125 -0
  26. aviationstack_mcp_server/mcp/tools/reference.py +129 -0
  27. aviationstack_mcp_server/models/__init__.py +81 -0
  28. aviationstack_mcp_server/models/aircraft.py +43 -0
  29. aviationstack_mcp_server/models/airline.py +29 -0
  30. aviationstack_mcp_server/models/airport.py +33 -0
  31. aviationstack_mcp_server/models/common.py +20 -0
  32. aviationstack_mcp_server/models/flight.py +125 -0
  33. aviationstack_mcp_server/models/location.py +50 -0
  34. aviationstack_mcp_server/models/queries.py +284 -0
  35. aviationstack_mcp_server/models/route.py +24 -0
  36. aviationstack_mcp_server/models/tax.py +20 -0
  37. aviationstack_mcp_server/observability.py +27 -0
  38. aviationstack_mcp_server/security.py +62 -0
  39. aviationstack_mcp_server/services/__init__.py +13 -0
  40. aviationstack_mcp_server/services/aircraft_service.py +156 -0
  41. aviationstack_mcp_server/services/airline_service.py +89 -0
  42. aviationstack_mcp_server/services/airport_service.py +116 -0
  43. aviationstack_mcp_server/services/flight_service.py +366 -0
  44. aviationstack_mcp_server/services/reference_service.py +315 -0
  45. aviationstack_mcp_server/utils/__init__.py +0 -0
  46. aviationstack_mcp_server-0.1.0.dist-info/METADATA +57 -0
  47. aviationstack_mcp_server-0.1.0.dist-info/RECORD +50 -0
  48. aviationstack_mcp_server-0.1.0.dist-info/WHEEL +4 -0
  49. aviationstack_mcp_server-0.1.0.dist-info/entry_points.txt +3 -0
  50. 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
+ ]