@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.
package/README.md CHANGED
@@ -98,7 +98,7 @@ version explicitly in the client config:
98
98
  "mcpServers": {
99
99
  "squawk": {
100
100
  "command": "npx",
101
- "args": ["-y", "@squawk/mcp@0.12.0"]
101
+ "args": ["-y", "@squawk/mcp@0.13.0"]
102
102
  }
103
103
  }
104
104
  }
@@ -144,14 +144,17 @@ rest of the server keeps working normally. If you do not need aircraft lookups,
144
144
  is required.
145
145
 
146
146
  To enable lookups, install the data package alongside `@squawk/mcp`. Through `npx` the cleanest
147
- option is the `-p` flag, which adds extra packages to the temporary install npx builds:
147
+ option is the `-p` flag, which adds extra packages to the temporary install npx builds. Once more
148
+ than one package is involved, npx no longer infers which one to run, so give it each package
149
+ behind its own `-p` and name the actual binary (`squawk-mcp`, not the package name) as the
150
+ trailing command:
148
151
 
149
152
  ```json
150
153
  {
151
154
  "mcpServers": {
152
155
  "squawk": {
153
156
  "command": "npx",
154
- "args": ["-y", "-p", "@squawk/icao-registry-data", "@squawk/mcp"]
157
+ "args": ["-y", "-p", "@squawk/icao-registry-data", "-p", "@squawk/mcp", "squawk-mcp"]
155
158
  }
156
159
  }
157
160
  }
@@ -164,7 +167,14 @@ Pinning works the same way:
164
167
  "mcpServers": {
165
168
  "squawk": {
166
169
  "command": "npx",
167
- "args": ["-y", "-p", "@squawk/icao-registry-data@0.8.12", "@squawk/mcp@0.12.0"]
170
+ "args": [
171
+ "-y",
172
+ "-p",
173
+ "@squawk/icao-registry-data@0.8.12",
174
+ "-p",
175
+ "@squawk/mcp@0.13.0",
176
+ "squawk-mcp"
177
+ ]
168
178
  }
169
179
  }
170
180
  }
@@ -395,9 +405,9 @@ left to the model itself.
395
405
 
396
406
  ### Server diagnostics
397
407
 
398
- | Tool | Purpose |
399
- | -------------------- | ------------------------------------------------------------------------------------------------------------ |
400
- | `get_dataset_status` | Report NASR cycle date, build timestamp, and record counts for every loaded snapshot (incl. lazy-load state) |
408
+ | Tool | Purpose |
409
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
410
+ | `get_dataset_status` | Report NASR/CIFP cycle date, build timestamp, and record counts for every bundled snapshot, plus whether each is loaded |
401
411
 
402
412
  ## Configuration
403
413
 
@@ -462,13 +472,16 @@ Two things worth knowing before you trim:
462
472
  - **`datasets` is a group like any other.** It holds `get_dataset_status`, the tool that
463
473
  reports which NASR and CIFP cycles the running server is serving. An allowlist that
464
474
  leaves it out means you can no longer ask how current the data is, so include it unless
465
- you are sure you do not want it. It is the cheapest group in the catalog.
466
- - **Groups gate the catalog, not the data.** The bundled snapshots are loaded and indexed
467
- at startup no matter which groups you register, so trimming the catalog does not reduce
468
- the server's startup time or memory use. It also means a group you kept keeps working
469
- even when it reads data belonging to a group you dropped - `flightplan` still resolves
470
- airports and navaids in a route string with `airports` and `navaids` disabled, because
471
- you disabled those tools, not that data.
475
+ you are sure you do not want it. It is the cheapest group in the catalog, and it stays
476
+ cheap: cycle dates and record counts are read from a small metadata module in each data
477
+ package, so asking for status never loads a snapshot.
478
+ - **Trimming the catalog trims the data too.** Each snapshot is decompressed the first time
479
+ a tool actually reads it, so a group you never register is a group whose data never
480
+ loads. Dropping `procedures` from a session that never asks about approach plates saves
481
+ its load outright, not just its share of the context window. A group you kept still works
482
+ when it reads data belonging to a group you dropped - `flightplan` resolves airports and
483
+ navaids in a route string with `airports` and `navaids` disabled, loading those snapshots
484
+ on its own first call, because you disabled those tools, not that data.
472
485
 
473
486
  ## Notes
474
487
 
@@ -479,7 +492,11 @@ Two things worth knowing before you trim:
479
492
  - Live weather tools issue HTTPS requests to `https://aviationweather.gov/api/data/...` (or the
480
493
  override above). They are the only tools that touch the network at invocation time; everything
481
494
  else operates against bundled snapshots in memory.
482
- - The bundled snapshots are decompressed and indexed once when the server starts, which takes on
483
- the order of a second and is unaffected by which tool groups you register. The aircraft
484
- registration snapshot (the largest, and an optional peer dependency) is decompressed lazily on
485
- the first `lookup_aircraft_by_icao_hex` call, if the package is installed.
495
+ - Snapshots are decompressed and indexed on first use rather than at startup, so the server comes
496
+ up immediately and each dataset pays a one-time cost the first time a tool reads it. Every later
497
+ call against a loaded dataset is served from memory. Procedures is the largest of these loads and
498
+ navaids by far the smallest, with airports, fixes, airspace, and airways in between; loading all
499
+ of them is on the order of a second. Parsing a route string triggers the biggest single first
500
+ call, since `flightplan` draws on five snapshots at once. The aircraft registration snapshot (the
501
+ largest overall, and an optional peer dependency) loads on the first `lookup_aircraft_by_icao_hex`
502
+ call, if the package is installed.
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * @packageDocumentation
3
- * Shared resolver instances used across the squawk MCP tool modules. Each
4
- * resolver is constructed once at module load time so the bundled FAA data
5
- * snapshots are decoded and indexed exactly once per server process.
3
+ * Shared resolver accessors used across the squawk MCP tool modules. Every
4
+ * bundled FAA snapshot loads on demand: the first accessor call dynamically
5
+ * imports its data package, decompresses and indexes the snapshot, and caches
6
+ * the result for the life of the process. A session that only asks about
7
+ * airports never decompresses the CIFP procedure snapshot, and a session that
8
+ * asks nothing pays nothing beyond process start.
6
9
  *
7
- * The ICAO registry is the only resolver that loads lazily, and its data
10
+ * Each accessor memoizes the in-flight load rather than the finished
11
+ * resolver, so concurrent tool calls needing the same dataset share one
12
+ * import and one index build instead of racing to build two.
13
+ *
14
+ * The ICAO registry follows the same shape with one extra wrinkle: its data
8
15
  * package (`@squawk/icao-registry-data`) is declared as an optional peer
9
- * dependency rather than a required dep. The registry is built on the first
10
- * {@link getIcaoRegistry} call (decompressing ~40 MB on first access) and
11
- * cached for subsequent calls. When the peer is not installed, the import
16
+ * dependency rather than a required dep, so a failed import is an expected
17
+ * outcome rather than a broken install. When the peer is absent the import
12
18
  * throws `ERR_MODULE_NOT_FOUND` and {@link getIcaoRegistry} surfaces a
13
19
  * {@link MissingDataPackageError} for the tool handler to format.
14
20
  */
@@ -19,18 +25,75 @@ import { type FixResolver } from '@squawk/fixes';
19
25
  import { type IcaoRegistry } from '@squawk/icao-registry';
20
26
  import { type NavaidResolver } from '@squawk/navaids';
21
27
  import { type ProcedureResolver } from '@squawk/procedures';
22
- /** Eagerly-built airport resolver backed by the US NASR snapshot. */
23
- export declare const airportResolver: AirportResolver;
24
- /** Eagerly-built airspace resolver backed by the US NASR airspace GeoJSON snapshot. */
25
- export declare const airspaceResolver: AirspaceResolver;
26
- /** Eagerly-built airway resolver backed by the US NASR snapshot. */
27
- export declare const airwayResolver: AirwayResolver;
28
- /** Eagerly-built fix resolver backed by the US NASR snapshot. */
29
- export declare const fixResolver: FixResolver;
30
- /** Eagerly-built navaid resolver backed by the US NASR snapshot. */
31
- export declare const navaidResolver: NavaidResolver;
32
- /** Eagerly-built procedure resolver backed by the US NASR snapshot. */
33
- export declare const procedureResolver: ProcedureResolver;
28
+ /**
29
+ * Returns the shared airport resolver, importing and indexing the bundled US
30
+ * NASR airport snapshot on the first call.
31
+ *
32
+ * @returns The shared airport resolver.
33
+ */
34
+ export declare function getAirportResolver(): Promise<AirportResolver>;
35
+ /**
36
+ * Returns the shared airspace resolver, importing and indexing the bundled US
37
+ * NASR airspace GeoJSON snapshot on the first call.
38
+ *
39
+ * @returns The shared airspace resolver.
40
+ */
41
+ export declare function getAirspaceResolver(): Promise<AirspaceResolver>;
42
+ /**
43
+ * Returns the shared airway resolver, importing and indexing the bundled US
44
+ * NASR airway snapshot on the first call.
45
+ *
46
+ * @returns The shared airway resolver.
47
+ */
48
+ export declare function getAirwayResolver(): Promise<AirwayResolver>;
49
+ /**
50
+ * Returns the shared fix resolver, importing and indexing the bundled US NASR
51
+ * fix snapshot on the first call.
52
+ *
53
+ * @returns The shared fix resolver.
54
+ */
55
+ export declare function getFixResolver(): Promise<FixResolver>;
56
+ /**
57
+ * Returns the shared navaid resolver, importing and indexing the bundled US
58
+ * NASR navaid snapshot on the first call.
59
+ *
60
+ * @returns The shared navaid resolver.
61
+ */
62
+ export declare function getNavaidResolver(): Promise<NavaidResolver>;
63
+ /**
64
+ * Returns the shared procedure resolver, importing and indexing the bundled
65
+ * FAA CIFP procedure snapshot on the first call.
66
+ *
67
+ * @returns The shared procedure resolver.
68
+ */
69
+ export declare function getProcedureResolver(): Promise<ProcedureResolver>;
70
+ /**
71
+ * Whether each required bundled snapshot has been loaded into the running
72
+ * process. Build metadata is not part of this shape: it is available from
73
+ * each data package's `/meta` subpath without loading anything, so only the
74
+ * in-memory state has to be read from here.
75
+ */
76
+ export interface BundledDatasetLoadState {
77
+ /** Whether the airport snapshot is loaded. */
78
+ readonly airports: boolean;
79
+ /** Whether the airspace snapshot is loaded. */
80
+ readonly airspace: boolean;
81
+ /** Whether the airway snapshot is loaded. */
82
+ readonly airways: boolean;
83
+ /** Whether the fix snapshot is loaded. */
84
+ readonly fixes: boolean;
85
+ /** Whether the navaid snapshot is loaded. */
86
+ readonly navaids: boolean;
87
+ /** Whether the procedure snapshot is loaded. */
88
+ readonly procedures: boolean;
89
+ }
90
+ /**
91
+ * Reports which required bundled snapshots are currently in memory, without
92
+ * loading any of them.
93
+ *
94
+ * @returns Per-dataset load state.
95
+ */
96
+ export declare function getBundledDatasetLoadState(): BundledDatasetLoadState;
34
97
  /**
35
98
  * Error thrown when a tool tries to load an optional data package peer that
36
99
  * has not been installed alongside `@squawk/mcp`. Tool handlers catch this
@@ -59,12 +122,10 @@ type IcaoRegistryDataLoader = () => Promise<typeof import('@squawk/icao-registry
59
122
  * the bundled FAA aircraft registration snapshot on the first call. Subsequent
60
123
  * calls reuse the cached instance.
61
124
  *
62
- * The registry is initialized lazily because the underlying data package is
63
- * the largest snapshot in the suite (roughly 40 MB raw) and is declared as an
64
- * optional peer dependency rather than a required dep. Sessions that never
65
- * look up an aircraft by ICAO hex avoid the cost entirely, and consumers who
66
- * never install the peer see only the structured missing-package error
67
- * surfaced by the tool handler.
125
+ * The registry's data package is the largest snapshot in the suite (roughly
126
+ * 40 MB raw) and is declared as an optional peer dependency rather than a
127
+ * required dep, so consumers who never install the peer see only the
128
+ * structured missing-package error surfaced by the tool handler.
68
129
  *
69
130
  * @returns The shared registry instance.
70
131
  * @throws {MissingDataPackageError} when `@squawk/icao-registry-data` is not
@@ -76,8 +137,8 @@ export declare function getIcaoRegistry(): Promise<IcaoRegistry>;
76
137
  *
77
138
  * Test-only seam for swapping the optional data package loader. Production
78
139
  * code must not call this. Pass `undefined` to restore the default loader
79
- * and clear all cached state (instance, metadata, and the missing-peer
80
- * sticky flag) so subsequent tests start from a clean slate.
140
+ * and clear all cached state (instance, in-flight load, metadata, and the
141
+ * missing-peer sticky flag) so subsequent tests start from a clean slate.
81
142
  *
82
143
  * @param loader - Replacement loader, or `undefined` to reset.
83
144
  */
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGjF,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAErF,qEAAqE;AACrE,eAAO,MAAM,eAAe,EAAE,eAE5B,CAAC;AAEH,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,EAAE,gBAE7B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,cAAc,EAAE,cAE3B,CAAC;AAEH,iEAAiE;AACjE,eAAO,MAAM,WAAW,EAAE,WAAiE,CAAC;AAE5F,oEAAoE;AACpE,eAAO,MAAM,cAAc,EAAE,cAE3B,CAAC;AAEH,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,EAAE,iBAE9B,CAAC;AAEH;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC;;;;;OAKG;gBACS,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAQrD;AAoBD,2EAA2E;AAC3E,KAAK,sBAAsB,GAAG,MAAM,OAAO,CAAC,cAAc,4BAA4B,CAAC,CAAC,CAAC;AA2BzF;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAuB7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,IAAI,CAKN;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IACrC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAEzD"}
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAmGrF;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,CASpE;AAED;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC;;;;;OAKG;gBACS,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAQrD;AA0BD,2EAA2E;AAC3E,KAAK,sBAAsB,GAAG,MAAM,OAAO,CAAC,cAAc,4BAA4B,CAAC,CAAC,CAAC;AAwDzF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAS7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,sBAAsB,GAAG,SAAS,GACzC,IAAI,CAMN;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IACrC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAEzD"}
package/dist/resolvers.js CHANGED
@@ -1,52 +1,146 @@
1
1
  /**
2
2
  * @packageDocumentation
3
- * Shared resolver instances used across the squawk MCP tool modules. Each
4
- * resolver is constructed once at module load time so the bundled FAA data
5
- * snapshots are decoded and indexed exactly once per server process.
3
+ * Shared resolver accessors used across the squawk MCP tool modules. Every
4
+ * bundled FAA snapshot loads on demand: the first accessor call dynamically
5
+ * imports its data package, decompresses and indexes the snapshot, and caches
6
+ * the result for the life of the process. A session that only asks about
7
+ * airports never decompresses the CIFP procedure snapshot, and a session that
8
+ * asks nothing pays nothing beyond process start.
6
9
  *
7
- * The ICAO registry is the only resolver that loads lazily, and its data
10
+ * Each accessor memoizes the in-flight load rather than the finished
11
+ * resolver, so concurrent tool calls needing the same dataset share one
12
+ * import and one index build instead of racing to build two.
13
+ *
14
+ * The ICAO registry follows the same shape with one extra wrinkle: its data
8
15
  * package (`@squawk/icao-registry-data`) is declared as an optional peer
9
- * dependency rather than a required dep. The registry is built on the first
10
- * {@link getIcaoRegistry} call (decompressing ~40 MB on first access) and
11
- * cached for subsequent calls. When the peer is not installed, the import
16
+ * dependency rather than a required dep, so a failed import is an expected
17
+ * outcome rather than a broken install. When the peer is absent the import
12
18
  * throws `ERR_MODULE_NOT_FOUND` and {@link getIcaoRegistry} surfaces a
13
19
  * {@link MissingDataPackageError} for the tool handler to format.
14
20
  */
15
- import { usBundledAirports } from '@squawk/airport-data';
16
21
  import { createAirportResolver } from '@squawk/airports';
17
22
  import { createAirspaceResolver } from '@squawk/airspace';
18
- import { usBundledAirspace } from '@squawk/airspace-data';
19
- import { usBundledAirways } from '@squawk/airway-data';
20
23
  import { createAirwayResolver } from '@squawk/airways';
21
- import { usBundledFixes } from '@squawk/fix-data';
22
24
  import { createFixResolver } from '@squawk/fixes';
23
25
  import { createIcaoRegistry } from '@squawk/icao-registry';
24
- import { usBundledNavaids } from '@squawk/navaid-data';
25
26
  import { createNavaidResolver } from '@squawk/navaids';
26
- import { usBundledProcedures } from '@squawk/procedure-data';
27
27
  import { createProcedureResolver } from '@squawk/procedures';
28
- /** Eagerly-built airport resolver backed by the US NASR snapshot. */
29
- export const airportResolver = createAirportResolver({
30
- data: usBundledAirports.records,
31
- });
32
- /** Eagerly-built airspace resolver backed by the US NASR airspace GeoJSON snapshot. */
33
- export const airspaceResolver = createAirspaceResolver({
34
- data: usBundledAirspace,
35
- });
36
- /** Eagerly-built airway resolver backed by the US NASR snapshot. */
37
- export const airwayResolver = createAirwayResolver({
38
- data: usBundledAirways.records,
39
- });
40
- /** Eagerly-built fix resolver backed by the US NASR snapshot. */
41
- export const fixResolver = createFixResolver({ data: usBundledFixes.records });
42
- /** Eagerly-built navaid resolver backed by the US NASR snapshot. */
43
- export const navaidResolver = createNavaidResolver({
44
- data: usBundledNavaids.records,
45
- });
46
- /** Eagerly-built procedure resolver backed by the US NASR snapshot. */
47
- export const procedureResolver = createProcedureResolver({
48
- data: usBundledProcedures.records,
49
- });
28
+ /**
29
+ * Builds a {@link LazyDataset} around a data package's dynamic import.
30
+ *
31
+ * The in-flight promise is what gets memoized, not the finished resolver, so
32
+ * two tool calls arriving before the first load settles share a single import
33
+ * and a single index build. A rejected load clears the memo so a later call
34
+ * retries rather than inheriting a permanently poisoned cache.
35
+ *
36
+ * @typeParam TDataset - Dataset object exported by the data package.
37
+ * @typeParam TResolver - Resolver type built over the dataset records.
38
+ * @param importDataset - Dynamic import resolving to the bundled dataset.
39
+ * @param buildResolver - Builds the resolver from the imported dataset.
40
+ * @returns Accessors over the lazily-loaded dataset.
41
+ */
42
+ function createLazyDataset(importDataset, buildResolver) {
43
+ let loaded;
44
+ let pending;
45
+ return {
46
+ getResolver: () => {
47
+ if (loaded !== undefined) {
48
+ return Promise.resolve(loaded);
49
+ }
50
+ pending ??= importDataset().then((dataset) => {
51
+ loaded = buildResolver(dataset);
52
+ return loaded;
53
+ }, (err) => {
54
+ pending = undefined;
55
+ throw err;
56
+ });
57
+ return pending;
58
+ },
59
+ isLoaded: () => loaded !== undefined,
60
+ };
61
+ }
62
+ /** Lazily-loaded airport dataset backed by the US NASR snapshot. */
63
+ const airportDataset = createLazyDataset(async () => (await import('@squawk/airport-data')).usBundledAirports, (dataset) => createAirportResolver({ data: dataset.records }));
64
+ /** Lazily-loaded airspace dataset backed by the US NASR airspace GeoJSON snapshot. */
65
+ const airspaceDataset = createLazyDataset(async () => (await import('@squawk/airspace-data')).usBundledAirspace, (dataset) => createAirspaceResolver({ data: dataset }));
66
+ /** Lazily-loaded airway dataset backed by the US NASR snapshot. */
67
+ const airwayDataset = createLazyDataset(async () => (await import('@squawk/airway-data')).usBundledAirways, (dataset) => createAirwayResolver({ data: dataset.records }));
68
+ /** Lazily-loaded fix dataset backed by the US NASR snapshot. */
69
+ const fixDataset = createLazyDataset(async () => (await import('@squawk/fix-data')).usBundledFixes, (dataset) => createFixResolver({ data: dataset.records }));
70
+ /** Lazily-loaded navaid dataset backed by the US NASR snapshot. */
71
+ const navaidDataset = createLazyDataset(async () => (await import('@squawk/navaid-data')).usBundledNavaids, (dataset) => createNavaidResolver({ data: dataset.records }));
72
+ /** Lazily-loaded procedure dataset backed by the FAA CIFP snapshot. */
73
+ const procedureDataset = createLazyDataset(async () => (await import('@squawk/procedure-data')).usBundledProcedures, (dataset) => createProcedureResolver({ data: dataset.records }));
74
+ /**
75
+ * Returns the shared airport resolver, importing and indexing the bundled US
76
+ * NASR airport snapshot on the first call.
77
+ *
78
+ * @returns The shared airport resolver.
79
+ */
80
+ export function getAirportResolver() {
81
+ return airportDataset.getResolver();
82
+ }
83
+ /**
84
+ * Returns the shared airspace resolver, importing and indexing the bundled US
85
+ * NASR airspace GeoJSON snapshot on the first call.
86
+ *
87
+ * @returns The shared airspace resolver.
88
+ */
89
+ export function getAirspaceResolver() {
90
+ return airspaceDataset.getResolver();
91
+ }
92
+ /**
93
+ * Returns the shared airway resolver, importing and indexing the bundled US
94
+ * NASR airway snapshot on the first call.
95
+ *
96
+ * @returns The shared airway resolver.
97
+ */
98
+ export function getAirwayResolver() {
99
+ return airwayDataset.getResolver();
100
+ }
101
+ /**
102
+ * Returns the shared fix resolver, importing and indexing the bundled US NASR
103
+ * fix snapshot on the first call.
104
+ *
105
+ * @returns The shared fix resolver.
106
+ */
107
+ export function getFixResolver() {
108
+ return fixDataset.getResolver();
109
+ }
110
+ /**
111
+ * Returns the shared navaid resolver, importing and indexing the bundled US
112
+ * NASR navaid snapshot on the first call.
113
+ *
114
+ * @returns The shared navaid resolver.
115
+ */
116
+ export function getNavaidResolver() {
117
+ return navaidDataset.getResolver();
118
+ }
119
+ /**
120
+ * Returns the shared procedure resolver, importing and indexing the bundled
121
+ * FAA CIFP procedure snapshot on the first call.
122
+ *
123
+ * @returns The shared procedure resolver.
124
+ */
125
+ export function getProcedureResolver() {
126
+ return procedureDataset.getResolver();
127
+ }
128
+ /**
129
+ * Reports which required bundled snapshots are currently in memory, without
130
+ * loading any of them.
131
+ *
132
+ * @returns Per-dataset load state.
133
+ */
134
+ export function getBundledDatasetLoadState() {
135
+ return {
136
+ airports: airportDataset.isLoaded(),
137
+ airspace: airspaceDataset.isLoaded(),
138
+ airways: airwayDataset.isLoaded(),
139
+ fixes: fixDataset.isLoaded(),
140
+ navaids: navaidDataset.isLoaded(),
141
+ procedures: procedureDataset.isLoaded(),
142
+ };
143
+ }
50
144
  /**
51
145
  * Error thrown when a tool tries to load an optional data package peer that
52
146
  * has not been installed alongside `@squawk/mcp`. Tool handlers catch this
@@ -77,6 +171,11 @@ export class MissingDataPackageError extends Error {
77
171
  }
78
172
  /** Cached ICAO registry instance, populated on the first {@link getIcaoRegistry} call. */
79
173
  let icaoRegistryInstance;
174
+ /**
175
+ * In-flight registry load, memoized so concurrent lookups share one import
176
+ * and one index build. Cleared when the load rejects so a later call retries.
177
+ */
178
+ let icaoRegistryPending;
80
179
  /**
81
180
  * Cached metadata captured the first time the registry is loaded. Held
82
181
  * separately from the registry instance so {@link getIcaoRegistryMetadata}
@@ -111,17 +210,44 @@ let icaoRegistryDataLoader = defaultIcaoRegistryDataLoader;
111
210
  function isModuleNotFoundError(err) {
112
211
  return err instanceof Error && 'code' in err && err.code === 'ERR_MODULE_NOT_FOUND';
113
212
  }
213
+ /**
214
+ * Imports the optional registry peer and builds the registry, populating the
215
+ * module-level caches. Split out of {@link getIcaoRegistry} so the memoized
216
+ * in-flight promise has a single body behind it.
217
+ *
218
+ * @returns The newly built registry instance.
219
+ * @throws {MissingDataPackageError} when the peer is not installed.
220
+ */
221
+ async function loadIcaoRegistry() {
222
+ let registryDataModule;
223
+ try {
224
+ registryDataModule = await icaoRegistryDataLoader();
225
+ }
226
+ catch (err) {
227
+ icaoRegistryPending = undefined;
228
+ if (isModuleNotFoundError(err)) {
229
+ icaoRegistryMissing = true;
230
+ throw new MissingDataPackageError('icao-registry', '@squawk/icao-registry-data');
231
+ }
232
+ throw err;
233
+ }
234
+ const { usBundledRegistry } = registryDataModule;
235
+ icaoRegistryInstance = createIcaoRegistry({ data: usBundledRegistry.records });
236
+ icaoRegistryMetadata = {
237
+ generatedAt: usBundledRegistry.properties.generatedAt,
238
+ recordCount: usBundledRegistry.properties.recordCount,
239
+ };
240
+ return icaoRegistryInstance;
241
+ }
114
242
  /**
115
243
  * Returns the shared {@link IcaoRegistry} instance, decompressing and indexing
116
244
  * the bundled FAA aircraft registration snapshot on the first call. Subsequent
117
245
  * calls reuse the cached instance.
118
246
  *
119
- * The registry is initialized lazily because the underlying data package is
120
- * the largest snapshot in the suite (roughly 40 MB raw) and is declared as an
121
- * optional peer dependency rather than a required dep. Sessions that never
122
- * look up an aircraft by ICAO hex avoid the cost entirely, and consumers who
123
- * never install the peer see only the structured missing-package error
124
- * surfaced by the tool handler.
247
+ * The registry's data package is the largest snapshot in the suite (roughly
248
+ * 40 MB raw) and is declared as an optional peer dependency rather than a
249
+ * required dep, so consumers who never install the peer see only the
250
+ * structured missing-package error surfaced by the tool handler.
125
251
  *
126
252
  * @returns The shared registry instance.
127
253
  * @throws {MissingDataPackageError} when `@squawk/icao-registry-data` is not
@@ -131,40 +257,26 @@ export async function getIcaoRegistry() {
131
257
  if (icaoRegistryMissing) {
132
258
  throw new MissingDataPackageError('icao-registry', '@squawk/icao-registry-data');
133
259
  }
134
- if (icaoRegistryInstance === undefined) {
135
- let registryDataModule;
136
- try {
137
- registryDataModule = await icaoRegistryDataLoader();
138
- }
139
- catch (err) {
140
- if (isModuleNotFoundError(err)) {
141
- icaoRegistryMissing = true;
142
- throw new MissingDataPackageError('icao-registry', '@squawk/icao-registry-data');
143
- }
144
- throw err;
145
- }
146
- const { usBundledRegistry } = registryDataModule;
147
- icaoRegistryInstance = createIcaoRegistry({ data: usBundledRegistry.records });
148
- icaoRegistryMetadata = {
149
- generatedAt: usBundledRegistry.properties.generatedAt,
150
- recordCount: usBundledRegistry.properties.recordCount,
151
- };
260
+ if (icaoRegistryInstance !== undefined) {
261
+ return icaoRegistryInstance;
152
262
  }
153
- return icaoRegistryInstance;
263
+ icaoRegistryPending ??= loadIcaoRegistry();
264
+ return icaoRegistryPending;
154
265
  }
155
266
  /**
156
267
  * @internal
157
268
  *
158
269
  * Test-only seam for swapping the optional data package loader. Production
159
270
  * code must not call this. Pass `undefined` to restore the default loader
160
- * and clear all cached state (instance, metadata, and the missing-peer
161
- * sticky flag) so subsequent tests start from a clean slate.
271
+ * and clear all cached state (instance, in-flight load, metadata, and the
272
+ * missing-peer sticky flag) so subsequent tests start from a clean slate.
162
273
  *
163
274
  * @param loader - Replacement loader, or `undefined` to reset.
164
275
  */
165
276
  export function __setIcaoRegistryDataLoaderForTest(loader) {
166
277
  icaoRegistryDataLoader = loader ?? defaultIcaoRegistryDataLoader;
167
278
  icaoRegistryInstance = undefined;
279
+ icaoRegistryPending = undefined;
168
280
  icaoRegistryMetadata = undefined;
169
281
  icaoRegistryMissing = false;
170
282
  }
@@ -1,14 +1,16 @@
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 type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
9
  /**
9
10
  * Registers airport lookup tools (by FAA ID, by ICAO code, nearest, text
10
- * search) on the given MCP server. Tools share the {@link airportResolver}
11
- * singleton built at module load time from the bundled US NASR dataset.
11
+ * search) on the given MCP server. Tools share the resolver singleton from
12
+ * {@link getAirportResolver}, which builds the bundled US NASR dataset on
13
+ * the first invocation and reuses it thereafter.
12
14
  *
13
15
  * @param server - The MCP server instance to register tools on.
14
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"airports.d.ts","sourceRoot":"","sources":["../../src/tools/airports.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2J5D"}
1
+ {"version":3,"file":"airports.d.ts","sourceRoot":"","sources":["../../src/tools/airports.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6J5D"}