@squawk/mcp 0.8.13 → 0.9.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.8.13"]
101
+ "args": ["-y", "@squawk/mcp@0.9.0"]
102
102
  }
103
103
  }
104
104
  }
@@ -130,6 +130,50 @@ again, or fall back to a pinned version. The host process itself also has to res
130
130
  update to take effect, since each MCP server is a long-running stdio subprocess that loads its
131
131
  code once at spawn time.
132
132
 
133
+ ### Enabling the aircraft registry (optional peer)
134
+
135
+ `@squawk/icao-registry-data` is the largest snapshot in the suite (roughly 8 MB on disk after
136
+ gzip). Most sessions never need a tail-number lookup, so the package is declared as an optional
137
+ **peer dependency** of `@squawk/mcp` rather than a required dep - npm 7+ does not auto-install
138
+ optional peers, which keeps the default `npx @squawk/mcp` install lean.
139
+
140
+ When the data package is not installed, `lookup_aircraft_by_icao_hex` is still listed in the tool
141
+ catalog. The first invocation returns a structured `isError: true` result whose
142
+ `structuredContent.missingDataPackage.installCommand` field names the exact command to run; the
143
+ rest of the server keeps working normally. If you do not need aircraft lookups, no further action
144
+ is required.
145
+
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:
148
+
149
+ ```json
150
+ {
151
+ "mcpServers": {
152
+ "squawk": {
153
+ "command": "npx",
154
+ "args": ["-y", "-p", "@squawk/icao-registry-data", "@squawk/mcp"]
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ Pinning works the same way:
161
+
162
+ ```json
163
+ {
164
+ "mcpServers": {
165
+ "squawk": {
166
+ "command": "npx",
167
+ "args": ["-y", "-p", "@squawk/icao-registry-data@0.8.3", "@squawk/mcp@0.9.0"]
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ For non-`npx` setups (a local install, a global install, a Docker image), add
174
+ `@squawk/icao-registry-data` to the same dependency manifest that pulls in `@squawk/mcp` and the
175
+ runtime will resolve it through ordinary Node module resolution.
176
+
133
177
  ### Pinning a specific Node binary
134
178
 
135
179
  `npx` resolves `node` through whatever PATH the host launches with. On macOS, GUI apps often
@@ -262,8 +306,11 @@ Covers SIDs, STARs, and Instrument Approach Procedures (IAPs) from FAA CIFP.
262
306
  | ----------------------------- | ------------------------------------------------------------- |
263
307
  | `lookup_aircraft_by_icao_hex` | Resolve a 24-bit ICAO hex address to an aircraft registration |
264
308
 
265
- The registry data (~40 MB raw) is loaded lazily on the first lookup so sessions that never need it
266
- do not pay the decompression cost.
309
+ `@squawk/icao-registry-data` is an optional peer dependency. Default installs of `@squawk/mcp` do
310
+ not include it; the tool reports a structured "data not installed" error with the exact install
311
+ command until the peer is added. See [Enabling the aircraft registry](#enabling-the-aircraft-registry-optional-peer)
312
+ for how to add it. Once present, the registry (~40 MB raw) is decompressed lazily on the first
313
+ lookup so sessions that never need it do not pay the cost.
267
314
 
268
315
  ### Weather (`@squawk/weather` + `@squawk/weather/fetch`)
269
316
 
@@ -351,5 +398,6 @@ left to the model itself.
351
398
  override above). They are the only tools that touch the network at invocation time; everything
352
399
  else operates against bundled snapshots in memory.
353
400
  - The bundled snapshots are decompressed and indexed once when the server starts. Expect a few
354
- hundred milliseconds of startup time. The aircraft registration snapshot (the largest) is loaded
355
- lazily on the first `lookup_aircraft_by_icao_hex` call.
401
+ hundred milliseconds of startup time. The aircraft registration snapshot (the largest, and an
402
+ optional peer dependency) is decompressed lazily on the first `lookup_aircraft_by_icao_hex` call,
403
+ if the package is installed.
@@ -4,10 +4,13 @@
4
4
  * resolver is constructed once at module load time so the bundled FAA data
5
5
  * snapshots are decoded and indexed exactly once per server process.
6
6
  *
7
- * The ICAO registry is the only resolver that loads lazily: its bundled data
8
- * package decompresses ~40 MB of records on import, which is wasted work for
9
- * sessions that never call the tail-number lookup. The registry is built on
10
- * the first {@link getIcaoRegistry} call and cached for subsequent calls.
7
+ * The ICAO registry is the only resolver that loads lazily, and its data
8
+ * 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
12
+ * throws `ERR_MODULE_NOT_FOUND` and {@link getIcaoRegistry} surfaces a
13
+ * {@link MissingDataPackageError} for the tool handler to format.
11
14
  */
12
15
  import { type AirportResolver } from '@squawk/airports';
13
16
  import { type AirspaceResolver } from '@squawk/airspace';
@@ -28,18 +31,57 @@ export declare const fixResolver: FixResolver;
28
31
  export declare const navaidResolver: NavaidResolver;
29
32
  /** Eagerly-built procedure resolver backed by the US NASR snapshot. */
30
33
  export declare const procedureResolver: ProcedureResolver;
34
+ /**
35
+ * Error thrown when a tool tries to load an optional data package peer that
36
+ * has not been installed alongside `@squawk/mcp`. Tool handlers catch this
37
+ * and surface the install command to the MCP client so the user can resolve
38
+ * the missing dependency without inspecting the server logs.
39
+ */
40
+ export declare class MissingDataPackageError extends Error {
41
+ /** Short dataset name shown to users (e.g. `"icao-registry"`). */
42
+ readonly datasetName: string;
43
+ /** npm package name the user must install (e.g. `"@squawk/icao-registry-data"`). */
44
+ readonly packageName: string;
45
+ /** Suggested install command, e.g. `"npm install @squawk/icao-registry-data"`. */
46
+ readonly installCommand: string;
47
+ /**
48
+ * Constructs a new error describing a missing optional data peer.
49
+ *
50
+ * @param datasetName - Short dataset name for the user-facing message.
51
+ * @param packageName - npm package name that needs to be installed.
52
+ */
53
+ constructor(datasetName: string, packageName: string);
54
+ }
55
+ /** Loader signature for the optional `@squawk/icao-registry-data` peer. */
56
+ type IcaoRegistryDataLoader = () => Promise<typeof import('@squawk/icao-registry-data')>;
31
57
  /**
32
58
  * Returns the shared {@link IcaoRegistry} instance, decompressing and indexing
33
59
  * the bundled FAA aircraft registration snapshot on the first call. Subsequent
34
60
  * calls reuse the cached instance.
35
61
  *
36
- * The registry is initialized lazily because the underlying bundled data
37
- * package is the largest snapshot in the suite (roughly 40 MB raw). Sessions
38
- * that never look up an aircraft by ICAO hex avoid the cost entirely.
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.
39
68
  *
40
69
  * @returns The shared registry instance.
70
+ * @throws {MissingDataPackageError} when `@squawk/icao-registry-data` is not
71
+ * installed alongside `@squawk/mcp`.
41
72
  */
42
73
  export declare function getIcaoRegistry(): Promise<IcaoRegistry>;
74
+ /**
75
+ * @internal
76
+ *
77
+ * Test-only seam for swapping the optional data package loader. Production
78
+ * 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.
81
+ *
82
+ * @param loader - Replacement loader, or `undefined` to reset.
83
+ */
84
+ export declare function __setIcaoRegistryDataLoaderForTest(loader: IcaoRegistryDataLoader | undefined): void;
43
85
  /**
44
86
  * Reports whether the lazily-loaded ICAO aircraft registry has been
45
87
  * initialized in this process.
@@ -57,4 +99,5 @@ export declare function getIcaoRegistryMetadata(): {
57
99
  generatedAt: string;
58
100
  recordCount: number;
59
101
  } | undefined;
102
+ export {};
60
103
  //# sourceMappingURL=resolvers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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;AAYH;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAU7D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IACnC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5C,SAAS,CAEZ"}
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,IACnC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5C,SAAS,CAEZ"}
package/dist/resolvers.js CHANGED
@@ -4,10 +4,13 @@
4
4
  * resolver is constructed once at module load time so the bundled FAA data
5
5
  * snapshots are decoded and indexed exactly once per server process.
6
6
  *
7
- * The ICAO registry is the only resolver that loads lazily: its bundled data
8
- * package decompresses ~40 MB of records on import, which is wasted work for
9
- * sessions that never call the tail-number lookup. The registry is built on
10
- * the first {@link getIcaoRegistry} call and cached for subsequent calls.
7
+ * The ICAO registry is the only resolver that loads lazily, and its data
8
+ * 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
12
+ * throws `ERR_MODULE_NOT_FOUND` and {@link getIcaoRegistry} surfaces a
13
+ * {@link MissingDataPackageError} for the tool handler to format.
11
14
  */
12
15
  import { usBundledAirports } from '@squawk/airport-data';
13
16
  import { createAirportResolver } from '@squawk/airports';
@@ -44,6 +47,34 @@ export const navaidResolver = createNavaidResolver({
44
47
  export const procedureResolver = createProcedureResolver({
45
48
  data: usBundledProcedures.records,
46
49
  });
50
+ /**
51
+ * Error thrown when a tool tries to load an optional data package peer that
52
+ * has not been installed alongside `@squawk/mcp`. Tool handlers catch this
53
+ * and surface the install command to the MCP client so the user can resolve
54
+ * the missing dependency without inspecting the server logs.
55
+ */
56
+ export class MissingDataPackageError extends Error {
57
+ /** Short dataset name shown to users (e.g. `"icao-registry"`). */
58
+ datasetName;
59
+ /** npm package name the user must install (e.g. `"@squawk/icao-registry-data"`). */
60
+ packageName;
61
+ /** Suggested install command, e.g. `"npm install @squawk/icao-registry-data"`. */
62
+ installCommand;
63
+ /**
64
+ * Constructs a new error describing a missing optional data peer.
65
+ *
66
+ * @param datasetName - Short dataset name for the user-facing message.
67
+ * @param packageName - npm package name that needs to be installed.
68
+ */
69
+ constructor(datasetName, packageName) {
70
+ const installCommand = `npm install ${packageName}`;
71
+ super(`${datasetName} data is not installed. Run: ${installCommand}`);
72
+ this.name = 'MissingDataPackageError';
73
+ this.datasetName = datasetName;
74
+ this.packageName = packageName;
75
+ this.installCommand = installCommand;
76
+ }
77
+ }
47
78
  /** Cached ICAO registry instance, populated on the first {@link getIcaoRegistry} call. */
48
79
  let icaoRegistryInstance;
49
80
  /**
@@ -52,20 +83,67 @@ let icaoRegistryInstance;
52
83
  * can return it without forcing another import.
53
84
  */
54
85
  let icaoRegistryMetadata;
86
+ /**
87
+ * Sticky flag set once the optional `@squawk/icao-registry-data` peer is
88
+ * confirmed missing. Subsequent {@link getIcaoRegistry} calls short-circuit
89
+ * to a {@link MissingDataPackageError} without retrying the import; running
90
+ * `npm install` while the server is live is not a supported workflow.
91
+ */
92
+ let icaoRegistryMissing = false;
93
+ /** Default loader: a dynamic import of the actual peer package. */
94
+ const defaultIcaoRegistryDataLoader = () => import('@squawk/icao-registry-data');
95
+ /**
96
+ * Active loader used by {@link getIcaoRegistry}. Replaceable in tests via
97
+ * {@link __setIcaoRegistryDataLoaderForTest} so the missing-peer code path
98
+ * can be exercised without uninstalling the workspace-symlinked package.
99
+ */
100
+ let icaoRegistryDataLoader = defaultIcaoRegistryDataLoader;
101
+ /**
102
+ * Returns `true` when the given thrown value is Node's
103
+ * `ERR_MODULE_NOT_FOUND`, which the ESM loader raises both when the package
104
+ * specifier cannot be resolved and when a transitive resolution within the
105
+ * package fails. The latter is rare but should still surface as the missing-
106
+ * peer error so the user gets a single actionable message.
107
+ *
108
+ * @param err - The value caught from a dynamic import.
109
+ * @returns `true` when the value is an `ERR_MODULE_NOT_FOUND` error.
110
+ */
111
+ function isModuleNotFoundError(err) {
112
+ return err instanceof Error && 'code' in err && err.code === 'ERR_MODULE_NOT_FOUND';
113
+ }
55
114
  /**
56
115
  * Returns the shared {@link IcaoRegistry} instance, decompressing and indexing
57
116
  * the bundled FAA aircraft registration snapshot on the first call. Subsequent
58
117
  * calls reuse the cached instance.
59
118
  *
60
- * The registry is initialized lazily because the underlying bundled data
61
- * package is the largest snapshot in the suite (roughly 40 MB raw). Sessions
62
- * that never look up an aircraft by ICAO hex avoid the cost entirely.
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.
63
125
  *
64
126
  * @returns The shared registry instance.
127
+ * @throws {MissingDataPackageError} when `@squawk/icao-registry-data` is not
128
+ * installed alongside `@squawk/mcp`.
65
129
  */
66
130
  export async function getIcaoRegistry() {
131
+ if (icaoRegistryMissing) {
132
+ throw new MissingDataPackageError('icao-registry', '@squawk/icao-registry-data');
133
+ }
67
134
  if (icaoRegistryInstance === undefined) {
68
- const { usBundledRegistry } = await import('@squawk/icao-registry-data');
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;
69
147
  icaoRegistryInstance = createIcaoRegistry({ data: usBundledRegistry.records });
70
148
  icaoRegistryMetadata = {
71
149
  generatedAt: usBundledRegistry.properties.generatedAt,
@@ -74,6 +152,22 @@ export async function getIcaoRegistry() {
74
152
  }
75
153
  return icaoRegistryInstance;
76
154
  }
155
+ /**
156
+ * @internal
157
+ *
158
+ * Test-only seam for swapping the optional data package loader. Production
159
+ * 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.
162
+ *
163
+ * @param loader - Replacement loader, or `undefined` to reset.
164
+ */
165
+ export function __setIcaoRegistryDataLoaderForTest(loader) {
166
+ icaoRegistryDataLoader = loader ?? defaultIcaoRegistryDataLoader;
167
+ icaoRegistryInstance = undefined;
168
+ icaoRegistryMetadata = undefined;
169
+ icaoRegistryMissing = false;
170
+ }
77
171
  /**
78
172
  * Reports whether the lazily-loaded ICAO aircraft registry has been
79
173
  * initialized in this process.
@@ -4,10 +4,12 @@
4
4
  * lookup, backed by the FAA ReleasableAircraft snapshot in
5
5
  * `@squawk/icao-registry-data`.
6
6
  *
7
- * Unlike the other domain modules, the registry data is loaded lazily on the
8
- * first tool invocation through {@link getIcaoRegistry}. The bundled data
9
- * package decompresses ~40 MB of records on import, which is wasteful for
10
- * sessions that never look up an aircraft.
7
+ * The registry data package is an **optional peer dependency** of
8
+ * `@squawk/mcp`. The tool is always registered with the MCP server so it
9
+ * appears in the catalog, but the data is loaded lazily on the first tool
10
+ * invocation through {@link getIcaoRegistry}. When the peer is not installed
11
+ * the tool returns a structured `isError: true` result naming the package and
12
+ * the install command, rather than crashing the server.
11
13
  */
12
14
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"icao-registry.d.ts","sourceRoot":"","sources":["../../src/tools/icao-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6BjE"}
1
+ {"version":3,"file":"icao-registry.d.ts","sourceRoot":"","sources":["../../src/tools/icao-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+CjE"}
@@ -4,13 +4,15 @@
4
4
  * lookup, backed by the FAA ReleasableAircraft snapshot in
5
5
  * `@squawk/icao-registry-data`.
6
6
  *
7
- * Unlike the other domain modules, the registry data is loaded lazily on the
8
- * first tool invocation through {@link getIcaoRegistry}. The bundled data
9
- * package decompresses ~40 MB of records on import, which is wasteful for
10
- * sessions that never look up an aircraft.
7
+ * The registry data package is an **optional peer dependency** of
8
+ * `@squawk/mcp`. The tool is always registered with the MCP server so it
9
+ * appears in the catalog, but the data is loaded lazily on the first tool
10
+ * invocation through {@link getIcaoRegistry}. When the peer is not installed
11
+ * the tool returns a structured `isError: true` result naming the package and
12
+ * the install command, rather than crashing the server.
11
13
  */
12
14
  import { z } from 'zod';
13
- import { getIcaoRegistry } from '../resolvers.js';
15
+ import { MissingDataPackageError, getIcaoRegistry } from '../resolvers.js';
14
16
  /** Pattern matching a valid 24-bit ICAO hex address (1-6 hex digits). */
15
17
  const ICAO_HEX_PATTERN = /^[0-9A-Fa-f]{1,6}$/;
16
18
  /**
@@ -21,7 +23,7 @@ const ICAO_HEX_PATTERN = /^[0-9A-Fa-f]{1,6}$/;
21
23
  export function registerIcaoRegistryTools(server) {
22
24
  server.registerTool('lookup_aircraft_by_icao_hex', {
23
25
  title: 'Look up an aircraft by ICAO hex address',
24
- description: 'Looks up a US-registered aircraft by its 24-bit ICAO hex address (the same identifier transmitted by Mode S and ADS-B). Returns registration, make, model, operator, aircraft type, engine type, and year of manufacture when known. Returns null when no match is found. The first call may take a few hundred milliseconds while the bundled FAA registration snapshot decompresses.',
26
+ description: 'Looks up a US-registered aircraft by its 24-bit ICAO hex address (the same identifier transmitted by Mode S and ADS-B). Returns registration, make, model, operator, aircraft type, engine type, and year of manufacture when known. Returns null when no match is found. Backed by `@squawk/icao-registry-data`, which is an optional peer dependency of `@squawk/mcp`; if the package is not installed the tool returns an actionable error including the install command. The first successful call may take a few hundred milliseconds while the bundled FAA registration snapshot decompresses.',
25
27
  inputSchema: {
26
28
  icaoHex: z
27
29
  .string()
@@ -29,17 +31,36 @@ export function registerIcaoRegistryTools(server) {
29
31
  .describe('24-bit ICAO hex address (1-6 hex digits, case-insensitive).'),
30
32
  },
31
33
  }, async ({ icaoHex }) => {
32
- const registry = await getIcaoRegistry();
33
- const aircraft = registry.lookup(icaoHex);
34
- if (aircraft === undefined) {
34
+ try {
35
+ const registry = await getIcaoRegistry();
36
+ const aircraft = registry.lookup(icaoHex);
37
+ if (aircraft === undefined) {
38
+ return {
39
+ content: [{ type: 'text', text: `No aircraft found for ICAO hex "${icaoHex}".` }],
40
+ structuredContent: { aircraft: null },
41
+ };
42
+ }
35
43
  return {
36
- content: [{ type: 'text', text: `No aircraft found for ICAO hex "${icaoHex}".` }],
37
- structuredContent: { aircraft: null },
44
+ content: [{ type: 'text', text: JSON.stringify(aircraft, null, 2) }],
45
+ structuredContent: { aircraft },
38
46
  };
39
47
  }
40
- return {
41
- content: [{ type: 'text', text: JSON.stringify(aircraft, null, 2) }],
42
- structuredContent: { aircraft },
43
- };
48
+ catch (err) {
49
+ if (err instanceof MissingDataPackageError) {
50
+ return {
51
+ content: [{ type: 'text', text: err.message }],
52
+ structuredContent: {
53
+ aircraft: null,
54
+ missingDataPackage: {
55
+ datasetName: err.datasetName,
56
+ packageName: err.packageName,
57
+ installCommand: err.installCommand,
58
+ },
59
+ },
60
+ isError: true,
61
+ };
62
+ }
63
+ throw err;
64
+ }
44
65
  });
45
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squawk/mcp",
3
- "version": "0.8.13",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Model Context Protocol server exposing squawk's aviation libraries as tools for LLM clients",
6
6
  "author": "Neil Cochran",
@@ -51,7 +51,6 @@
51
51
  "@squawk/flightplan": "^0.5.1",
52
52
  "@squawk/geo": "^0.4.3",
53
53
  "@squawk/icao-registry": "^0.5.1",
54
- "@squawk/icao-registry-data": "^0.8.3",
55
54
  "@squawk/navaid-data": "^0.6.3",
56
55
  "@squawk/navaids": "^0.4.1",
57
56
  "@squawk/notams": "^0.3.5",
@@ -61,7 +60,16 @@
61
60
  "@squawk/weather": "^0.5.5",
62
61
  "zod": "^4.4.3"
63
62
  },
63
+ "peerDependencies": {
64
+ "@squawk/icao-registry-data": "^0.8.3"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@squawk/icao-registry-data": {
68
+ "optional": true
69
+ }
70
+ },
64
71
  "devDependencies": {
72
+ "@squawk/icao-registry-data": "^0.8.3",
65
73
  "@types/node": "^25.6.0"
66
74
  },
67
75
  "keywords": [