@squawk/mcp 0.12.0 → 0.13.0

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.
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  * MCP tool module wrapping `@squawk/airports` airport lookup methods, backed
4
- * by the US NASR snapshot in `@squawk/airport-data`. The dataset is loaded
5
- * and indexed eagerly when the shared {@link airportResolver} is imported.
4
+ * by the US NASR snapshot in `@squawk/airport-data`. The dataset is imported
5
+ * and indexed by the first handler to call {@link getAirportResolver}, so a
6
+ * session that never asks about an airport never pays for the snapshot.
6
7
  */
7
8
  import { z } from 'zod';
8
- import { airportResolver } from '../resolvers.js';
9
+ import { getAirportResolver } from '../resolvers.js';
9
10
  /** All {@link FacilityType} values, used for input validation. */
10
11
  const FACILITY_TYPE_VALUES = [
11
12
  'AIRPORT',
@@ -17,20 +18,21 @@ const FACILITY_TYPE_VALUES = [
17
18
  ];
18
19
  /**
19
20
  * Registers airport lookup tools (by FAA ID, by ICAO code, nearest, text
20
- * search) on the given MCP server. Tools share the {@link airportResolver}
21
- * singleton built at module load time from the bundled US NASR dataset.
21
+ * search) on the given MCP server. Tools share the resolver singleton from
22
+ * {@link getAirportResolver}, which builds the bundled US NASR dataset on
23
+ * the first invocation and reuses it thereafter.
22
24
  *
23
25
  * @param server - The MCP server instance to register tools on.
24
26
  */
25
27
  export function registerAirportTools(server) {
26
- const resolver = airportResolver;
27
28
  server.registerTool('get_airport_by_faa_id', {
28
29
  title: 'Get airport by FAA identifier',
29
30
  description: 'Looks up a US airport by its FAA location identifier (e.g. "JFK", "LAX", "3N6"). Returns the full airport record including runways, frequencies, and ILS data, or null if no match is found.',
30
31
  inputSchema: {
31
32
  faaId: z.string().min(1).describe('FAA location identifier (case-insensitive).'),
32
33
  },
33
- }, ({ faaId }) => {
34
+ }, async ({ faaId }) => {
35
+ const resolver = await getAirportResolver();
34
36
  const airport = resolver.byFaaId(faaId);
35
37
  if (airport === undefined) {
36
38
  return {
@@ -49,7 +51,8 @@ export function registerAirportTools(server) {
49
51
  inputSchema: {
50
52
  icao: z.string().min(1).describe('ICAO airport code (case-insensitive).'),
51
53
  },
52
- }, ({ icao }) => {
54
+ }, async ({ icao }) => {
55
+ const resolver = await getAirportResolver();
53
56
  const airport = resolver.byIcao(icao);
54
57
  if (airport === undefined) {
55
58
  return {
@@ -89,7 +92,8 @@ export function registerAirportTools(server) {
89
92
  .optional()
90
93
  .describe('Only include airports with at least one runway meeting this length in feet.'),
91
94
  },
92
- }, ({ lat, lon, maxDistanceNm, limit, facilityTypes, minRunwayLengthFt }) => {
95
+ }, async ({ lat, lon, maxDistanceNm, limit, facilityTypes, minRunwayLengthFt }) => {
96
+ const resolver = await getAirportResolver();
93
97
  const query = { lat, lon };
94
98
  if (maxDistanceNm !== undefined) {
95
99
  query.maxDistanceNm = maxDistanceNm;
@@ -134,7 +138,8 @@ export function registerAirportTools(server) {
134
138
  .optional()
135
139
  .describe('Minimum match score (exclusive) in [0, 1] a result must reach. Defaults to 0. Raise it to drop weak fuzzy matches.'),
136
140
  },
137
- }, ({ text, limit, facilityTypes, minScore }) => {
141
+ }, async ({ text, limit, facilityTypes, minScore }) => {
142
+ const resolver = await getAirportResolver();
138
143
  const query = { text };
139
144
  if (limit !== undefined) {
140
145
  query.limit = limit;
@@ -6,9 +6,11 @@
6
6
  */
7
7
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
8
  /**
9
- * Registers airspace query tools on the given MCP server. The bundled US
10
- * airspace GeoJSON snapshot is decoded and indexed eagerly via the shared
11
- * {@link airspaceResolver}.
9
+ * Registers airspace query tools on the given MCP server. Each handler pulls
10
+ * the shared resolver from {@link getAirspaceResolver}, which decodes and
11
+ * indexes the bundled US airspace GeoJSON snapshot on the first invocation.
12
+ * `get_airspace_for_airport` additionally resolves the airport identifier,
13
+ * so it loads the airport snapshot alongside the airspace one.
12
14
  *
13
15
  * @param server - The MCP server instance to register tools on.
14
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"airspace.d.ts","sourceRoot":"","sources":["../../src/tools/airspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmFzE;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0L7D"}
1
+ {"version":3,"file":"airspace.d.ts","sourceRoot":"","sources":["../../src/tools/airspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmFzE;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkM7D"}
@@ -5,7 +5,7 @@
5
5
  * `@squawk/airspace-data`.
6
6
  */
7
7
  import { z } from 'zod';
8
- import { airportResolver, airspaceResolver } from '../resolvers.js';
8
+ import { getAirportResolver, getAirspaceResolver } from '../resolvers.js';
9
9
  /** All {@link AirspaceType} values, used for input validation. */
10
10
  const AIRSPACE_TYPE_VALUES = [
11
11
  'CLASS_B',
@@ -58,9 +58,11 @@ function summarizeFeature(feature) {
58
58
  };
59
59
  }
60
60
  /**
61
- * Registers airspace query tools on the given MCP server. The bundled US
62
- * airspace GeoJSON snapshot is decoded and indexed eagerly via the shared
63
- * {@link airspaceResolver}.
61
+ * Registers airspace query tools on the given MCP server. Each handler pulls
62
+ * the shared resolver from {@link getAirspaceResolver}, which decodes and
63
+ * indexes the bundled US airspace GeoJSON snapshot on the first invocation.
64
+ * `get_airspace_for_airport` additionally resolves the airport identifier,
65
+ * so it loads the airport snapshot alongside the airspace one.
64
66
  *
65
67
  * @param server - The MCP server instance to register tools on.
66
68
  */
@@ -79,7 +81,8 @@ export function registerAirspaceTools(server) {
79
81
  .optional()
80
82
  .describe('Restrict results to these airspace types. Omit to include all types.'),
81
83
  },
82
- }, ({ lat, lon, altitudeFt, airspaceTypes }) => {
84
+ }, async ({ lat, lon, altitudeFt, airspaceTypes }) => {
85
+ const airspaceResolver = await getAirspaceResolver();
83
86
  const query = { lat, lon, altitudeFt };
84
87
  if (airspaceTypes !== undefined) {
85
88
  query.types = new Set(airspaceTypes);
@@ -103,7 +106,11 @@ export function registerAirspaceTools(server) {
103
106
  .optional()
104
107
  .describe('Restrict results to these airspace types. Defaults to surface-area classes (CLASS_B, CLASS_C, CLASS_D, CLASS_E2).'),
105
108
  },
106
- }, ({ airportId, airspaceTypes }) => {
109
+ }, async ({ airportId, airspaceTypes }) => {
110
+ const [airportResolver, airspaceResolver] = await Promise.all([
111
+ getAirportResolver(),
112
+ getAirspaceResolver(),
113
+ ]);
107
114
  const airport = airportResolver.byFaaId(airportId) ?? airportResolver.byIcao(airportId);
108
115
  if (airport === undefined) {
109
116
  return {
@@ -131,7 +138,8 @@ export function registerAirspaceTools(server) {
131
138
  .optional()
132
139
  .describe('Altitude in feet MSL. Defaults to 0 (surface), which selects the LOW stratum where applicable. Pass FL180+ to target HIGH or FL600+ to target UTA.'),
133
140
  },
134
- }, ({ lat, lon, altitudeFt }) => {
141
+ }, async ({ lat, lon, altitudeFt }) => {
142
+ const airspaceResolver = await getAirspaceResolver();
135
143
  const query = {
136
144
  lat,
137
145
  lon,
@@ -158,7 +166,8 @@ export function registerAirspaceTools(server) {
158
166
  .optional()
159
167
  .describe('Restrict to a single stratum (LOW, HIGH, UTA, CTA, FIR, or CTA/FIR). Omit to return every published stratum for the center.'),
160
168
  },
161
- }, ({ artccId, stratum }) => {
169
+ }, async ({ artccId, stratum }) => {
170
+ const airspaceResolver = await getAirspaceResolver();
162
171
  const features = airspaceResolver.byArtcc(artccId, stratum);
163
172
  return {
164
173
  content: [{ type: 'text', text: JSON.stringify({ features }, null, 2) }],
@@ -190,7 +199,8 @@ export function registerAirspaceTools(server) {
190
199
  .optional()
191
200
  .describe('Minimum match score (exclusive) in [0, 1] a result must reach. Defaults to 0. Raise it to drop weak fuzzy matches.'),
192
201
  },
193
- }, ({ text, airspaceTypes, limit, minScore }) => {
202
+ }, async ({ text, airspaceTypes, limit, minScore }) => {
203
+ const airspaceResolver = await getAirspaceResolver();
194
204
  const query = { text };
195
205
  if (airspaceTypes !== undefined) {
196
206
  query.types = new Set(airspaceTypes);
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  /**
8
- * Registers airway lookup tools on the given MCP server. Uses the shared
9
- * {@link airwayResolver} built at module load time.
8
+ * Registers airway lookup tools on the given MCP server. Each handler pulls
9
+ * the shared resolver from {@link getAirwayResolver}, which imports and
10
+ * indexes the bundled US NASR snapshot on the first invocation.
10
11
  *
11
12
  * @param server - The MCP server instance to register tools on.
12
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"airways.d.ts","sourceRoot":"","sources":["../../src/tools/airways.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwBzE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2H3D"}
1
+ {"version":3,"file":"airways.d.ts","sourceRoot":"","sources":["../../src/tools/airways.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwBzE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+H3D"}
@@ -4,7 +4,7 @@
4
4
  * expansion methods, backed by the US NASR snapshot in `@squawk/airway-data`.
5
5
  */
6
6
  import { z } from 'zod';
7
- import { airwayResolver } from '../resolvers.js';
7
+ import { getAirwayResolver } from '../resolvers.js';
8
8
  /** All {@link AirwayType} values, used for input validation. */
9
9
  const AIRWAY_TYPE_VALUES = [
10
10
  'VICTOR',
@@ -21,8 +21,9 @@ const AIRWAY_TYPE_VALUES = [
21
21
  'PUERTO_RICO',
22
22
  ];
23
23
  /**
24
- * Registers airway lookup tools on the given MCP server. Uses the shared
25
- * {@link airwayResolver} built at module load time.
24
+ * Registers airway lookup tools on the given MCP server. Each handler pulls
25
+ * the shared resolver from {@link getAirwayResolver}, which imports and
26
+ * indexes the bundled US NASR snapshot on the first invocation.
26
27
  *
27
28
  * @param server - The MCP server instance to register tools on.
28
29
  */
@@ -33,8 +34,9 @@ export function registerAirwayTools(server) {
33
34
  inputSchema: {
34
35
  designation: z.string().min(1).describe('Airway designation (case-insensitive).'),
35
36
  },
36
- }, ({ designation }) => {
37
- const airways = airwayResolver.byDesignation(designation);
37
+ }, async ({ designation }) => {
38
+ const resolver = await getAirwayResolver();
39
+ const airways = resolver.byDesignation(designation);
38
40
  return {
39
41
  content: [{ type: 'text', text: JSON.stringify(airways, null, 2) }],
40
42
  structuredContent: { airways },
@@ -48,8 +50,9 @@ export function registerAirwayTools(server) {
48
50
  entryFix: z.string().min(1).describe('Identifier of the entry fix (case-insensitive).'),
49
51
  exitFix: z.string().min(1).describe('Identifier of the exit fix (case-insensitive).'),
50
52
  },
51
- }, ({ designation, entryFix, exitFix }) => {
52
- const expansion = airwayResolver.expand(designation, entryFix, exitFix);
53
+ }, async ({ designation, entryFix, exitFix }) => {
54
+ const resolver = await getAirwayResolver();
55
+ const expansion = resolver.expand(designation, entryFix, exitFix);
53
56
  if (expansion === undefined) {
54
57
  return {
55
58
  content: [
@@ -75,8 +78,9 @@ export function registerAirwayTools(server) {
75
78
  .min(1)
76
79
  .describe('Fix, navaid, or waypoint identifier (case-insensitive).'),
77
80
  },
78
- }, ({ ident }) => {
79
- const results = airwayResolver.byFix(ident);
81
+ }, async ({ ident }) => {
82
+ const resolver = await getAirwayResolver();
83
+ const results = resolver.byFix(ident);
80
84
  return {
81
85
  content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
82
86
  structuredContent: { results },
@@ -107,7 +111,8 @@ export function registerAirwayTools(server) {
107
111
  .optional()
108
112
  .describe('Minimum match score (exclusive) in [0, 1] a result must reach. Defaults to 0. Raise it to drop weak fuzzy matches.'),
109
113
  },
110
- }, ({ text, airwayTypes, limit, minScore }) => {
114
+ }, async ({ text, airwayTypes, limit, minScore }) => {
115
+ const resolver = await getAirwayResolver();
111
116
  const query = { text };
112
117
  if (airwayTypes !== undefined) {
113
118
  query.types = new Set(airwayTypes);
@@ -118,7 +123,7 @@ export function registerAirwayTools(server) {
118
123
  if (minScore !== undefined) {
119
124
  query.minScore = minScore;
120
125
  }
121
- const airways = airwayResolver.search(query);
126
+ const airways = resolver.search(query);
122
127
  return {
123
128
  content: [{ type: 'text', text: JSON.stringify(airways, null, 2) }],
124
129
  structuredContent: { airways },
@@ -1,16 +1,23 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  * MCP tool module exposing build provenance and record counts for every
4
- * bundled FAA snapshot the server loads. Lets an LLM client report exactly
5
- * which NASR cycle and registry vintage answered a query - critical context
6
- * for aviation use, where stale data is a real safety concern.
4
+ * bundled FAA snapshot the server can serve. Lets an LLM client report
5
+ * exactly which NASR cycle and registry vintage answered a query - critical
6
+ * context for aviation use, where stale data is a real safety concern.
7
+ *
8
+ * Every dataset's metadata comes from its data package's `/meta` subpath, a
9
+ * generated constant carrying the cycle date, build timestamp, and record
10
+ * counts. Reading it costs nothing, so reporting status never pulls a
11
+ * snapshot into memory that a session did not otherwise need. Each entry also
12
+ * carries whether its records are currently loaded, which is the one fact
13
+ * that has to be read from the resolver layer.
7
14
  */
8
15
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
16
  /**
10
17
  * Registers the `get_dataset_status` tool on the given MCP server. The tool
11
- * takes no input and reports the NASR cycle date, generation timestamp, and
12
- * record counts for each loaded snapshot. The lazily-loaded ICAO registry
13
- * reports its load state without being initialized as a side effect.
18
+ * takes no input and reports the cycle effective date, generation timestamp,
19
+ * and record counts for every bundled snapshot, along with whether each one
20
+ * is currently loaded. Answering does not load anything.
14
21
  *
15
22
  * @param server - The MCP server instance to register tools on.
16
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"datasets.d.ts","sourceRoot":"","sources":["../../src/tools/datasets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWzE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8D5D"}
1
+ {"version":3,"file":"datasets.d.ts","sourceRoot":"","sources":["../../src/tools/datasets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAezE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkC5D"}
@@ -1,68 +1,47 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  * MCP tool module exposing build provenance and record counts for every
4
- * bundled FAA snapshot the server loads. Lets an LLM client report exactly
5
- * which NASR cycle and registry vintage answered a query - critical context
6
- * for aviation use, where stale data is a real safety concern.
4
+ * bundled FAA snapshot the server can serve. Lets an LLM client report
5
+ * exactly which NASR cycle and registry vintage answered a query - critical
6
+ * context for aviation use, where stale data is a real safety concern.
7
+ *
8
+ * Every dataset's metadata comes from its data package's `/meta` subpath, a
9
+ * generated constant carrying the cycle date, build timestamp, and record
10
+ * counts. Reading it costs nothing, so reporting status never pulls a
11
+ * snapshot into memory that a session did not otherwise need. Each entry also
12
+ * carries whether its records are currently loaded, which is the one fact
13
+ * that has to be read from the resolver layer.
7
14
  */
8
- import { usBundledAirports } from '@squawk/airport-data';
9
- import { usBundledAirspace } from '@squawk/airspace-data';
10
- import { usBundledAirways } from '@squawk/airway-data';
11
- import { usBundledFixes } from '@squawk/fix-data';
12
- import { usBundledNavaids } from '@squawk/navaid-data';
13
- import { usBundledProcedures } from '@squawk/procedure-data';
14
- import { getIcaoRegistryMetadata, isIcaoRegistryLoaded } from '../resolvers.js';
15
+ import { usBundledAirportsProperties } from '@squawk/airport-data/meta';
16
+ import { usBundledAirspaceProperties } from '@squawk/airspace-data/meta';
17
+ import { usBundledAirwaysProperties } from '@squawk/airway-data/meta';
18
+ import { usBundledFixesProperties } from '@squawk/fix-data/meta';
19
+ import { usBundledNavaidsProperties } from '@squawk/navaid-data/meta';
20
+ import { usBundledProceduresProperties } from '@squawk/procedure-data/meta';
21
+ import { getBundledDatasetLoadState, getIcaoRegistryMetadata, isIcaoRegistryLoaded, } from '../resolvers.js';
15
22
  /**
16
23
  * Registers the `get_dataset_status` tool on the given MCP server. The tool
17
- * takes no input and reports the NASR cycle date, generation timestamp, and
18
- * record counts for each loaded snapshot. The lazily-loaded ICAO registry
19
- * reports its load state without being initialized as a side effect.
24
+ * takes no input and reports the cycle effective date, generation timestamp,
25
+ * and record counts for every bundled snapshot, along with whether each one
26
+ * is currently loaded. Answering does not load anything.
20
27
  *
21
28
  * @param server - The MCP server instance to register tools on.
22
29
  */
23
30
  export function registerDatasetTools(server) {
24
31
  server.registerTool('get_dataset_status', {
25
32
  title: 'Report status of every bundled dataset',
26
- description: 'Returns the cycle effective date (FAA NASR for airports/airspace/navaids/fixes/airways; FAA CIFP for procedures), build timestamp, and record counts for each dataset the server loads (airports, airspace, navaids, fixes, airways, procedures, ICAO aircraft registry). Use this when a user asks "how current is the data?" or before answering a question that depends on procedure/navaid currency. The aircraft registry is lazily loaded and reports its load state without forcing initialization.',
33
+ description: 'Returns the cycle effective date (FAA NASR for airports/airspace/navaids/fixes/airways; FAA CIFP for procedures), build timestamp, and record counts for every dataset the server can serve, plus a `loaded` flag saying whether that dataset is currently in memory. Use this when a user asks "how current is the data?" or before answering a question that depends on procedure/navaid currency. Calling it is cheap and never forces a dataset to load, so `loaded: false` means only that nothing has needed that snapshot yet - the cycle dates it reports are accurate either way. The aircraft registry is an optional peer package, so it reports its vintage only once a lookup has loaded it.',
27
34
  inputSchema: {},
28
35
  }, () => {
36
+ const loadState = getBundledDatasetLoadState();
29
37
  const registryMetadata = getIcaoRegistryMetadata();
30
38
  const datasets = {
31
- airports: {
32
- nasrCycleDate: usBundledAirports.properties.nasrCycleDate,
33
- generatedAt: usBundledAirports.properties.generatedAt,
34
- recordCount: usBundledAirports.properties.recordCount,
35
- },
36
- airspace: {
37
- nasrCycleDate: usBundledAirspace.properties.nasrCycleDate,
38
- generatedAt: usBundledAirspace.properties.generatedAt,
39
- featureCount: usBundledAirspace.properties.featureCount,
40
- },
41
- navaids: {
42
- nasrCycleDate: usBundledNavaids.properties.nasrCycleDate,
43
- generatedAt: usBundledNavaids.properties.generatedAt,
44
- recordCount: usBundledNavaids.properties.recordCount,
45
- },
46
- fixes: {
47
- nasrCycleDate: usBundledFixes.properties.nasrCycleDate,
48
- generatedAt: usBundledFixes.properties.generatedAt,
49
- recordCount: usBundledFixes.properties.recordCount,
50
- },
51
- airways: {
52
- nasrCycleDate: usBundledAirways.properties.nasrCycleDate,
53
- generatedAt: usBundledAirways.properties.generatedAt,
54
- recordCount: usBundledAirways.properties.recordCount,
55
- waypointCount: usBundledAirways.properties.waypointCount,
56
- },
57
- procedures: {
58
- cifpCycleDate: usBundledProcedures.properties.cifpCycleDate,
59
- generatedAt: usBundledProcedures.properties.generatedAt,
60
- recordCount: usBundledProcedures.properties.recordCount,
61
- sidCount: usBundledProcedures.properties.sidCount,
62
- starCount: usBundledProcedures.properties.starCount,
63
- iapCount: usBundledProcedures.properties.iapCount,
64
- legCount: usBundledProcedures.properties.legCount,
65
- },
39
+ airports: { loaded: loadState.airports, ...usBundledAirportsProperties },
40
+ airspace: { loaded: loadState.airspace, ...usBundledAirspaceProperties },
41
+ navaids: { loaded: loadState.navaids, ...usBundledNavaidsProperties },
42
+ fixes: { loaded: loadState.fixes, ...usBundledFixesProperties },
43
+ airways: { loaded: loadState.airways, ...usBundledAirwaysProperties },
44
+ procedures: { loaded: loadState.procedures, ...usBundledProceduresProperties },
66
45
  icaoRegistry: registryMetadata !== undefined
67
46
  ? {
68
47
  loaded: true,
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  /**
8
- * Registers fix/waypoint lookup tools on the given MCP server. Uses the
9
- * shared {@link fixResolver} built at module load time.
8
+ * Registers fix/waypoint lookup tools on the given MCP server. Each handler
9
+ * pulls the shared resolver from {@link getFixResolver}, which imports and
10
+ * indexes the bundled US NASR snapshot on the first invocation.
10
11
  *
11
12
  * @param server - The MCP server instance to register tools on.
12
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fixes.d.ts","sourceRoot":"","sources":["../../src/tools/fixes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoBzE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwJxD"}
1
+ {"version":3,"file":"fixes.d.ts","sourceRoot":"","sources":["../../src/tools/fixes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoBzE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4JxD"}
@@ -4,7 +4,7 @@
4
4
  * backed by the US NASR snapshot in `@squawk/fix-data`.
5
5
  */
6
6
  import { z } from 'zod';
7
- import { fixResolver } from '../resolvers.js';
7
+ import { getFixResolver } from '../resolvers.js';
8
8
  /** All {@link FixUseCode} values, used for input validation. */
9
9
  const FIX_USE_CODE_VALUES = [
10
10
  'WP',
@@ -17,8 +17,9 @@ const FIX_USE_CODE_VALUES = [
17
17
  'RADAR',
18
18
  ];
19
19
  /**
20
- * Registers fix/waypoint lookup tools on the given MCP server. Uses the
21
- * shared {@link fixResolver} built at module load time.
20
+ * Registers fix/waypoint lookup tools on the given MCP server. Each handler
21
+ * pulls the shared resolver from {@link getFixResolver}, which imports and
22
+ * indexes the bundled US NASR snapshot on the first invocation.
22
23
  *
23
24
  * @param server - The MCP server instance to register tools on.
24
25
  */
@@ -29,8 +30,9 @@ export function registerFixTools(server) {
29
30
  inputSchema: {
30
31
  ident: z.string().min(1).describe('Fix identifier (case-insensitive).'),
31
32
  },
32
- }, ({ ident }) => {
33
- const fixes = fixResolver.byIdent(ident);
33
+ }, async ({ ident }) => {
34
+ const resolver = await getFixResolver();
35
+ const fixes = resolver.byIdent(ident);
34
36
  return {
35
37
  content: [{ type: 'text', text: JSON.stringify(fixes, null, 2) }],
36
38
  structuredContent: { fixes },
@@ -57,8 +59,9 @@ export function registerFixTools(server) {
57
59
  .optional()
58
60
  .describe('Maximum great-circle distance in nautical miles. Omit to let the nearest match win regardless of distance.'),
59
61
  },
60
- }, ({ ident, lat, lon, toleranceNm }) => {
61
- const fix = fixResolver.byIdentAtPosition(ident, lat, lon, toleranceNm) ?? null;
62
+ }, async ({ ident, lat, lon, toleranceNm }) => {
63
+ const resolver = await getFixResolver();
64
+ const fix = resolver.byIdentAtPosition(ident, lat, lon, toleranceNm) ?? null;
62
65
  return {
63
66
  content: [{ type: 'text', text: JSON.stringify(fix, null, 2) }],
64
67
  structuredContent: { fix },
@@ -86,7 +89,8 @@ export function registerFixTools(server) {
86
89
  .optional()
87
90
  .describe('Restrict results to these FAA fix-use codes (e.g. WP=waypoint, RP=reporting point, VFR=VFR waypoint). Omit to include all use codes.'),
88
91
  },
89
- }, ({ lat, lon, maxDistanceNm, limit, useCodes }) => {
92
+ }, async ({ lat, lon, maxDistanceNm, limit, useCodes }) => {
93
+ const resolver = await getFixResolver();
90
94
  const query = { lat, lon };
91
95
  if (maxDistanceNm !== undefined) {
92
96
  query.maxDistanceNm = maxDistanceNm;
@@ -97,7 +101,7 @@ export function registerFixTools(server) {
97
101
  if (useCodes !== undefined) {
98
102
  query.useCodes = new Set(useCodes);
99
103
  }
100
- const results = fixResolver.nearest(query);
104
+ const results = resolver.nearest(query);
101
105
  return {
102
106
  content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
103
107
  structuredContent: { results },
@@ -128,7 +132,8 @@ export function registerFixTools(server) {
128
132
  .optional()
129
133
  .describe('Minimum match score (exclusive) in [0, 1] a result must reach. Defaults to 0. Raise it to drop weak fuzzy matches.'),
130
134
  },
131
- }, ({ text, useCodes, limit, minScore }) => {
135
+ }, async ({ text, useCodes, limit, minScore }) => {
136
+ const resolver = await getFixResolver();
132
137
  const query = { text };
133
138
  if (useCodes !== undefined) {
134
139
  query.useCodes = new Set(useCodes);
@@ -139,7 +144,7 @@ export function registerFixTools(server) {
139
144
  if (minScore !== undefined) {
140
145
  query.minScore = minScore;
141
146
  }
142
- const fixes = fixResolver.search(query);
147
+ const fixes = resolver.search(query);
143
148
  return {
144
149
  content: [{ type: 'text', text: JSON.stringify(fixes, null, 2) }],
145
150
  structuredContent: { fixes },
@@ -3,13 +3,15 @@
3
3
  * MCP tool module wrapping `@squawk/flightplan` route-string parsing and
4
4
  * great-circle distance computation. The flightplan resolver composes the
5
5
  * shared airport, navaid, fix, airway, and procedure resolvers from the
6
- * package's `resolvers` module.
6
+ * package's `resolvers` module, so the first route parsed in a session loads
7
+ * all five of those snapshots.
7
8
  */
8
9
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
10
  /**
10
11
  * Registers flight plan parsing and route distance tools on the given MCP
11
- * server. The flightplan resolver is built once at registration time and
12
- * shares the bundled NASR data via the resolver singletons.
12
+ * server. Each handler pulls the composed resolver from
13
+ * {@link getFlightplanResolver}, which builds it from the bundled NASR and
14
+ * CIFP snapshots on the first invocation.
13
15
  *
14
16
  * @param server - The MCP server instance to register tools on.
15
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"flightplan.d.ts","sourceRoot":"","sources":["../../src/tools/flightplan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmBzE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAgJ/D"}
1
+ {"version":3,"file":"flightplan.d.ts","sourceRoot":"","sources":["../../src/tools/flightplan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoDzE;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4I/D"}
@@ -3,26 +3,47 @@
3
3
  * MCP tool module wrapping `@squawk/flightplan` route-string parsing and
4
4
  * great-circle distance computation. The flightplan resolver composes the
5
5
  * shared airport, navaid, fix, airway, and procedure resolvers from the
6
- * package's `resolvers` module.
6
+ * package's `resolvers` module, so the first route parsed in a session loads
7
+ * all five of those snapshots.
7
8
  */
8
9
  import { z } from 'zod';
9
10
  import { computeRouteDistance, computeRouteTiming, createFlightplanResolver, extractRoutePoints, routeToLineString, } from '@squawk/flightplan';
10
- import { airportResolver, airwayResolver, fixResolver, navaidResolver, procedureResolver, } from '../resolvers.js';
11
+ import { getAirportResolver, getAirwayResolver, getFixResolver, getNavaidResolver, getProcedureResolver, } from '../resolvers.js';
12
+ /**
13
+ * Memoized composed resolver. Holding the promise rather than the finished
14
+ * resolver means concurrent route parses share one load of the five
15
+ * underlying snapshots instead of each building their own composition.
16
+ */
17
+ let flightplanResolver;
18
+ /**
19
+ * Returns the composed flightplan resolver, loading the five snapshots it
20
+ * draws on (airports, navaids, fixes, airways, procedures) in parallel on the
21
+ * first call. A rejected load clears the memo so a later call can retry.
22
+ *
23
+ * @returns The shared flightplan resolver.
24
+ */
25
+ function getFlightplanResolver() {
26
+ flightplanResolver ??= Promise.all([
27
+ getAirportResolver(),
28
+ getNavaidResolver(),
29
+ getFixResolver(),
30
+ getAirwayResolver(),
31
+ getProcedureResolver(),
32
+ ]).then(([airports, navaids, fixes, airways, procedures]) => createFlightplanResolver({ airports, navaids, fixes, airways, procedures }), (err) => {
33
+ flightplanResolver = undefined;
34
+ throw err;
35
+ });
36
+ return flightplanResolver;
37
+ }
11
38
  /**
12
39
  * Registers flight plan parsing and route distance tools on the given MCP
13
- * server. The flightplan resolver is built once at registration time and
14
- * shares the bundled NASR data via the resolver singletons.
40
+ * server. Each handler pulls the composed resolver from
41
+ * {@link getFlightplanResolver}, which builds it from the bundled NASR and
42
+ * CIFP snapshots on the first invocation.
15
43
  *
16
44
  * @param server - The MCP server instance to register tools on.
17
45
  */
18
46
  export function registerFlightplanTools(server) {
19
- const resolver = createFlightplanResolver({
20
- airports: airportResolver,
21
- navaids: navaidResolver,
22
- fixes: fixResolver,
23
- airways: airwayResolver,
24
- procedures: procedureResolver,
25
- });
26
47
  server.registerTool('parse_flightplan_route', {
27
48
  title: 'Parse a flight plan route string',
28
49
  description: 'Parses a whitespace-separated flight plan route string (e.g. "KJFK DCT MERIT J60 MARTN DCT KLAX") into structured route elements. Each token is classified as an airport, SID, STAR, airway, direct (DCT), waypoint, lat/lon coordinate, speed/altitude group, or unresolved. Airway tokens are expanded into waypoint sequences between the entry and exit fixes. SID/STAR tokens are expanded into their first common route, and the dotted PROCCODE.TRANSITION form (e.g. "NUBLE4.JJIMY") additionally merges the named transition\'s waypoints into the expansion.',
@@ -32,7 +53,8 @@ export function registerFlightplanTools(server) {
32
53
  .min(1)
33
54
  .describe('Whitespace-separated route string in ICAO Item 15 conventions.'),
34
55
  },
35
- }, ({ routeString }) => {
56
+ }, async ({ routeString }) => {
57
+ const resolver = await getFlightplanResolver();
36
58
  const route = resolver.parse(routeString);
37
59
  return {
38
60
  content: [{ type: 'text', text: JSON.stringify(route, null, 2) }],
@@ -53,7 +75,8 @@ export function registerFlightplanTools(server) {
53
75
  .optional()
54
76
  .describe('Optional ground speed in knots used to compute estimated time enroute. Omit to skip ETE.'),
55
77
  },
56
- }, ({ routeString, groundSpeedKt }) => {
78
+ }, async ({ routeString, groundSpeedKt }) => {
79
+ const resolver = await getFlightplanResolver();
57
80
  const route = resolver.parse(routeString);
58
81
  const result = computeRouteDistance(route, groundSpeedKt);
59
82
  return {
@@ -70,7 +93,8 @@ export function registerFlightplanTools(server) {
70
93
  .min(1)
71
94
  .describe('Whitespace-separated route string in ICAO Item 15 conventions.'),
72
95
  },
73
- }, ({ routeString }) => {
96
+ }, async ({ routeString }) => {
97
+ const resolver = await getFlightplanResolver();
74
98
  const route = resolver.parse(routeString);
75
99
  const points = extractRoutePoints(route);
76
100
  const lineString = routeToLineString(route);
@@ -114,7 +138,8 @@ export function registerFlightplanTools(server) {
114
138
  .optional()
115
139
  .describe('Optional fuel on board in the same unit as fuelBurnPerHr. With the burn rate, enables endurance and fuel-sufficiency reporting.'),
116
140
  },
117
- }, ({ routeString, trueAirspeedKt, wind, fuelBurnPerHr, fuelAvailable }) => {
141
+ }, async ({ routeString, trueAirspeedKt, wind, fuelBurnPerHr, fuelAvailable }) => {
142
+ const resolver = await getFlightplanResolver();
118
143
  const route = resolver.parse(routeString);
119
144
  const result = computeRouteTiming(route, {
120
145
  trueAirspeedKt,
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  /**
8
- * Registers navaid lookup tools on the given MCP server. Uses the shared
9
- * {@link navaidResolver} built at module load time.
8
+ * Registers navaid lookup tools on the given MCP server. Each handler pulls
9
+ * the shared resolver from {@link getNavaidResolver}, which imports and
10
+ * indexes the bundled US NASR snapshot on the first invocation.
10
11
  *
11
12
  * @param server - The MCP server instance to register tools on.
12
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"navaids.d.ts","sourceRoot":"","sources":["../../src/tools/navaids.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6L3D"}
1
+ {"version":3,"file":"navaids.d.ts","sourceRoot":"","sources":["../../src/tools/navaids.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkM3D"}