@cyanheads/noaa-marine-mcp-server 0.1.1
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/AGENTS.md +387 -0
- package/CLAUDE.md +387 -0
- package/Dockerfile +99 -0
- package/LICENSE +201 -0
- package/README.md +300 -0
- package/changelog/0.1.x/0.1.1.md +28 -0
- package/changelog/template.md +127 -0
- package/dist/config/server-config.d.ts +12 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +20 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js +96 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts +105 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js +338 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts +40 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js +164 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js +227 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts +60 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js +180 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts +65 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js +232 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js.map +1 -0
- package/dist/services/coops/coops-service.d.ts +74 -0
- package/dist/services/coops/coops-service.d.ts.map +1 -0
- package/dist/services/coops/coops-service.js +217 -0
- package/dist/services/coops/coops-service.js.map +1 -0
- package/dist/services/coops/types.d.ts +66 -0
- package/dist/services/coops/types.d.ts.map +1 -0
- package/dist/services/coops/types.js +6 -0
- package/dist/services/coops/types.js.map +1 -0
- package/dist/services/ndbc/ndbc-service.d.ts +28 -0
- package/dist/services/ndbc/ndbc-service.d.ts.map +1 -0
- package/dist/services/ndbc/ndbc-service.js +213 -0
- package/dist/services/ndbc/ndbc-service.js.map +1 -0
- package/dist/services/ndbc/types.d.ts +60 -0
- package/dist/services/ndbc/types.d.ts.map +1 -0
- package/dist/services/ndbc/types.js +6 -0
- package/dist/services/ndbc/types.js.map +1 -0
- package/package.json +105 -0
- package/server.json +113 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Server-specific environment variable configuration for noaa-marine-mcp-server.
|
|
3
|
+
* @module config/server-config
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
7
|
+
const ServerConfigSchema = z.object({
|
|
8
|
+
applicationId: z
|
|
9
|
+
.string()
|
|
10
|
+
.default('noaa-marine-mcp-server')
|
|
11
|
+
.describe('Courtesy identifier sent as application= on CO-OPS requests.'),
|
|
12
|
+
});
|
|
13
|
+
let _config;
|
|
14
|
+
export function getServerConfig() {
|
|
15
|
+
_config ??= parseEnvConfig(ServerConfigSchema, {
|
|
16
|
+
applicationId: 'NOAA_APPLICATION_ID',
|
|
17
|
+
});
|
|
18
|
+
return _config;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=server-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,OAAO,CAAC,wBAAwB,CAAC;SACjC,QAAQ,CAAC,8DAA8D,CAAC;CAC5E,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,aAAa,EAAE,qBAAqB;KACrC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview noaa-marine-mcp-server MCP server entry point.
|
|
4
|
+
* @module index
|
|
5
|
+
*/
|
|
6
|
+
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { getServerConfig } from './config/server-config.js';
|
|
8
|
+
import { noaaMarineStationResource } from './mcp-server/resources/definitions/noaa-marine-station.resource.js';
|
|
9
|
+
import { noaaMarineFindStations } from './mcp-server/tools/definitions/noaa-marine-find-stations.tool.js';
|
|
10
|
+
import { noaaMarineGetConditions } from './mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js';
|
|
11
|
+
import { noaaMarineGetCurrents } from './mcp-server/tools/definitions/noaa-marine-get-currents.tool.js';
|
|
12
|
+
import { noaaMarineGetTidePredictions } from './mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js';
|
|
13
|
+
import { noaaMarineGetWaterLevel } from './mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js';
|
|
14
|
+
import { initCoopsService } from './services/coops/coops-service.js';
|
|
15
|
+
import { initNdbcService } from './services/ndbc/ndbc-service.js';
|
|
16
|
+
await createApp({
|
|
17
|
+
tools: [
|
|
18
|
+
noaaMarineFindStations,
|
|
19
|
+
noaaMarineGetTidePredictions,
|
|
20
|
+
noaaMarineGetWaterLevel,
|
|
21
|
+
noaaMarineGetCurrents,
|
|
22
|
+
noaaMarineGetConditions,
|
|
23
|
+
],
|
|
24
|
+
resources: [noaaMarineStationResource],
|
|
25
|
+
prompts: [],
|
|
26
|
+
instructions: 'US marine conditions via NOAA CO-OPS and NDBC. ' +
|
|
27
|
+
'Start with noaa_marine_find_stations to resolve a location or name to station IDs, ' +
|
|
28
|
+
'then call the appropriate data tool. ' +
|
|
29
|
+
'CO-OPS provides tide predictions (noaa_marine_get_tide_predictions), ' +
|
|
30
|
+
'observed water levels and storm surge (noaa_marine_get_water_level), ' +
|
|
31
|
+
'and tidal current predictions (noaa_marine_get_currents). ' +
|
|
32
|
+
'NDBC provides live buoy conditions — waves, wind, sea-surface temp (noaa_marine_get_conditions). ' +
|
|
33
|
+
'All water height data is referenced to MLLW by default (US nautical chart datum).',
|
|
34
|
+
setup(core) {
|
|
35
|
+
const serverConfig = getServerConfig();
|
|
36
|
+
initCoopsService(core.config, core.storage, serverConfig);
|
|
37
|
+
initNdbcService();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mEAAmE,CAAC;AAC5G,OAAO,EAAE,qBAAqB,EAAE,MAAM,iEAAiE,CAAC;AACxG,OAAO,EAAE,4BAA4B,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,KAAK,EAAE;QACL,sBAAsB;QACtB,4BAA4B;QAC5B,uBAAuB;QACvB,qBAAqB;QACrB,uBAAuB;KACxB;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,YAAY,EACV,iDAAiD;QACjD,qFAAqF;QACrF,uCAAuC;QACvC,uEAAuE;QACvE,uEAAuE;QACvE,4DAA4D;QAC5D,mGAAmG;QACnG,mFAAmF;IAErF,KAAK,CAAC,IAAI;QACR,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1D,eAAe,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview noaa-marine://station/{station_id} resource — metadata for a CO-OPS or NDBC station.
|
|
3
|
+
* @module mcp-server/resources/definitions/noaa-marine-station.resource
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
export declare const noaaMarineStationResource: import("@cyanheads/mcp-ts-core").ResourceDefinition<z.ZodObject<{
|
|
7
|
+
station_id: z.ZodString;
|
|
8
|
+
}, z.core.$strip>, undefined, undefined>;
|
|
9
|
+
//# sourceMappingURL=noaa-marine-station.resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noaa-marine-station.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAKrD,eAAO,MAAM,yBAAyB;;wCA0FpC,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview noaa-marine://station/{station_id} resource — metadata for a CO-OPS or NDBC station.
|
|
3
|
+
* @module mcp-server/resources/definitions/noaa-marine-station.resource
|
|
4
|
+
*/
|
|
5
|
+
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getCoopsService } from '../../../services/coops/coops-service.js';
|
|
8
|
+
import { getNdbcService } from '../../../services/ndbc/ndbc-service.js';
|
|
9
|
+
export const noaaMarineStationResource = resource('noaa-marine://station/{station_id}', {
|
|
10
|
+
name: 'noaa_marine_station',
|
|
11
|
+
description: 'Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, capabilities, and state. ' +
|
|
12
|
+
'CO-OPS station IDs are numeric (tide/water-level) or alphanumeric (current stations). ' +
|
|
13
|
+
'NDBC station IDs are 5-character alphanumeric codes. ' +
|
|
14
|
+
'Use noaa_marine_find_stations to discover station IDs.',
|
|
15
|
+
mimeType: 'application/json',
|
|
16
|
+
params: z.object({
|
|
17
|
+
station_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.regex(/^[A-Za-z0-9_-]{1,20}$/)
|
|
20
|
+
.describe('Station identifier. CO-OPS: numeric (e.g. 9447130) or alphanumeric (e.g. ACT4176). NDBC: e.g. 46041.'),
|
|
21
|
+
}),
|
|
22
|
+
async handler(params, ctx) {
|
|
23
|
+
const coopsSvc = getCoopsService();
|
|
24
|
+
const ndbcSvc = getNdbcService();
|
|
25
|
+
const id = params.station_id.toUpperCase();
|
|
26
|
+
// Search CO-OPS lists and NDBC in parallel
|
|
27
|
+
const [coopsResult, ndbcResult] = await Promise.allSettled([
|
|
28
|
+
Promise.all([
|
|
29
|
+
coopsSvc.getStations('tidepredictions', ctx),
|
|
30
|
+
coopsSvc.getStations('currentpredictions', ctx),
|
|
31
|
+
coopsSvc.getStations('waterlevels', ctx),
|
|
32
|
+
]),
|
|
33
|
+
ndbcSvc.getActiveStations(ctx),
|
|
34
|
+
]);
|
|
35
|
+
// Check CO-OPS
|
|
36
|
+
if (coopsResult.status === 'fulfilled') {
|
|
37
|
+
const [tide, current, waterLevel] = coopsResult.value;
|
|
38
|
+
const match = tide.find((s) => s.id.toUpperCase() === id) ??
|
|
39
|
+
current.find((s) => s.id.toUpperCase() === id) ??
|
|
40
|
+
waterLevel.find((s) => s.id.toUpperCase() === id);
|
|
41
|
+
if (match) {
|
|
42
|
+
const caps = [];
|
|
43
|
+
if (tide.some((s) => s.id.toUpperCase() === id))
|
|
44
|
+
caps.push('tide');
|
|
45
|
+
if (current.some((s) => s.id.toUpperCase() === id))
|
|
46
|
+
caps.push('current');
|
|
47
|
+
if (waterLevel.some((s) => s.id.toUpperCase() === id))
|
|
48
|
+
caps.push('water_level');
|
|
49
|
+
if (caps.length === 0)
|
|
50
|
+
caps.push('tide');
|
|
51
|
+
const result = {
|
|
52
|
+
station_id: match.id,
|
|
53
|
+
name: match.name,
|
|
54
|
+
source: 'coops',
|
|
55
|
+
latitude: match.lat,
|
|
56
|
+
longitude: match.lng,
|
|
57
|
+
capabilities: caps,
|
|
58
|
+
};
|
|
59
|
+
if (match.state)
|
|
60
|
+
result.state = match.state;
|
|
61
|
+
if (match.type)
|
|
62
|
+
result.type = match.type;
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Check NDBC
|
|
67
|
+
if (ndbcResult.status === 'fulfilled') {
|
|
68
|
+
const match = ndbcResult.value.find((s) => s.id.toUpperCase() === id);
|
|
69
|
+
if (match) {
|
|
70
|
+
const caps = [];
|
|
71
|
+
if (match.hasMet)
|
|
72
|
+
caps.push('met');
|
|
73
|
+
if (match.hasCurrents)
|
|
74
|
+
caps.push('currents');
|
|
75
|
+
if (caps.length === 0)
|
|
76
|
+
caps.push('buoy');
|
|
77
|
+
const result = {
|
|
78
|
+
station_id: match.id,
|
|
79
|
+
name: match.name,
|
|
80
|
+
source: 'ndbc',
|
|
81
|
+
latitude: match.lat,
|
|
82
|
+
longitude: match.lon,
|
|
83
|
+
type: match.type ?? 'buoy',
|
|
84
|
+
capabilities: caps,
|
|
85
|
+
};
|
|
86
|
+
if (match.owner)
|
|
87
|
+
result.owner = match.owner;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw notFound(`Station ${params.station_id} not found in CO-OPS or NDBC. Use noaa_marine_find_stations to discover valid IDs.`, {
|
|
92
|
+
station_id: params.station_id,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=noaa-marine-station.resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noaa-marine-station.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview noaa_marine_find_stations tool — multi-source station discovery for CO-OPS and NDBC.
|
|
3
|
+
* @module mcp-server/tools/definitions/noaa-marine-find-stations.tool
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
export declare const noaaMarineFindStations: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
|
+
latitude: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
longitude: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
radius_km: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
query: z.ZodOptional<z.ZodString>;
|
|
12
|
+
state: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
ID: "ID";
|
|
14
|
+
AL: "AL";
|
|
15
|
+
AK: "AK";
|
|
16
|
+
AZ: "AZ";
|
|
17
|
+
AR: "AR";
|
|
18
|
+
CA: "CA";
|
|
19
|
+
CO: "CO";
|
|
20
|
+
CT: "CT";
|
|
21
|
+
DE: "DE";
|
|
22
|
+
FL: "FL";
|
|
23
|
+
GA: "GA";
|
|
24
|
+
HI: "HI";
|
|
25
|
+
IL: "IL";
|
|
26
|
+
IN: "IN";
|
|
27
|
+
IA: "IA";
|
|
28
|
+
KS: "KS";
|
|
29
|
+
KY: "KY";
|
|
30
|
+
LA: "LA";
|
|
31
|
+
ME: "ME";
|
|
32
|
+
MD: "MD";
|
|
33
|
+
MA: "MA";
|
|
34
|
+
MI: "MI";
|
|
35
|
+
MN: "MN";
|
|
36
|
+
MS: "MS";
|
|
37
|
+
MO: "MO";
|
|
38
|
+
MT: "MT";
|
|
39
|
+
NE: "NE";
|
|
40
|
+
NV: "NV";
|
|
41
|
+
NH: "NH";
|
|
42
|
+
NJ: "NJ";
|
|
43
|
+
NM: "NM";
|
|
44
|
+
NY: "NY";
|
|
45
|
+
NC: "NC";
|
|
46
|
+
ND: "ND";
|
|
47
|
+
OH: "OH";
|
|
48
|
+
OK: "OK";
|
|
49
|
+
OR: "OR";
|
|
50
|
+
PA: "PA";
|
|
51
|
+
RI: "RI";
|
|
52
|
+
SC: "SC";
|
|
53
|
+
SD: "SD";
|
|
54
|
+
TN: "TN";
|
|
55
|
+
TX: "TX";
|
|
56
|
+
UT: "UT";
|
|
57
|
+
VT: "VT";
|
|
58
|
+
VA: "VA";
|
|
59
|
+
WA: "WA";
|
|
60
|
+
WV: "WV";
|
|
61
|
+
WI: "WI";
|
|
62
|
+
WY: "WY";
|
|
63
|
+
DC: "DC";
|
|
64
|
+
PR: "PR";
|
|
65
|
+
VI: "VI";
|
|
66
|
+
GU: "GU";
|
|
67
|
+
AS: "AS";
|
|
68
|
+
MP: "MP";
|
|
69
|
+
}>>;
|
|
70
|
+
source: z.ZodDefault<z.ZodEnum<{
|
|
71
|
+
coops: "coops";
|
|
72
|
+
ndbc: "ndbc";
|
|
73
|
+
all: "all";
|
|
74
|
+
}>>;
|
|
75
|
+
types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
76
|
+
water_level: "water_level";
|
|
77
|
+
met: "met";
|
|
78
|
+
tide: "tide";
|
|
79
|
+
current: "current";
|
|
80
|
+
buoy: "buoy";
|
|
81
|
+
}>>>;
|
|
82
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
83
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
84
|
+
stations: z.ZodArray<z.ZodObject<{
|
|
85
|
+
station_id: z.ZodString;
|
|
86
|
+
name: z.ZodString;
|
|
87
|
+
source: z.ZodEnum<{
|
|
88
|
+
coops: "coops";
|
|
89
|
+
ndbc: "ndbc";
|
|
90
|
+
}>;
|
|
91
|
+
type: z.ZodString;
|
|
92
|
+
latitude: z.ZodNumber;
|
|
93
|
+
longitude: z.ZodNumber;
|
|
94
|
+
distance_km: z.ZodOptional<z.ZodNumber>;
|
|
95
|
+
state: z.ZodOptional<z.ZodString>;
|
|
96
|
+
capabilities: z.ZodArray<z.ZodString>;
|
|
97
|
+
}, z.core.$strip>>;
|
|
98
|
+
total_found: z.ZodNumber;
|
|
99
|
+
}, z.core.$strip>, readonly [{
|
|
100
|
+
readonly reason: "no_results";
|
|
101
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
102
|
+
readonly when: "No stations match the query, location, or filters.";
|
|
103
|
+
readonly recovery: "Widen the search by increasing radius_km, removing type filters, or using a broader query.";
|
|
104
|
+
}], undefined>;
|
|
105
|
+
//# sourceMappingURL=noaa-marine-find-stations.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noaa-marine-find-stations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/noaa-marine-find-stations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA0EjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiUjC,CAAC"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview noaa_marine_find_stations tool — multi-source station discovery for CO-OPS and NDBC.
|
|
3
|
+
* @module mcp-server/tools/definitions/noaa-marine-find-stations.tool
|
|
4
|
+
*/
|
|
5
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { getCoopsService } from '../../../services/coops/coops-service.js';
|
|
8
|
+
import { getNdbcService } from '../../../services/ndbc/ndbc-service.js';
|
|
9
|
+
/** Haversine distance in km between two lat/lon pairs. */
|
|
10
|
+
function haversineKm(lat1, lon1, lat2, lon2) {
|
|
11
|
+
const R = 6371;
|
|
12
|
+
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
|
13
|
+
const dLon = ((lon2 - lon1) * Math.PI) / 180;
|
|
14
|
+
const a = Math.sin(dLat / 2) ** 2 +
|
|
15
|
+
Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
|
|
16
|
+
return R * 2 * Math.asin(Math.sqrt(a));
|
|
17
|
+
}
|
|
18
|
+
const STATE_CODES = [
|
|
19
|
+
'AL',
|
|
20
|
+
'AK',
|
|
21
|
+
'AZ',
|
|
22
|
+
'AR',
|
|
23
|
+
'CA',
|
|
24
|
+
'CO',
|
|
25
|
+
'CT',
|
|
26
|
+
'DE',
|
|
27
|
+
'FL',
|
|
28
|
+
'GA',
|
|
29
|
+
'HI',
|
|
30
|
+
'ID',
|
|
31
|
+
'IL',
|
|
32
|
+
'IN',
|
|
33
|
+
'IA',
|
|
34
|
+
'KS',
|
|
35
|
+
'KY',
|
|
36
|
+
'LA',
|
|
37
|
+
'ME',
|
|
38
|
+
'MD',
|
|
39
|
+
'MA',
|
|
40
|
+
'MI',
|
|
41
|
+
'MN',
|
|
42
|
+
'MS',
|
|
43
|
+
'MO',
|
|
44
|
+
'MT',
|
|
45
|
+
'NE',
|
|
46
|
+
'NV',
|
|
47
|
+
'NH',
|
|
48
|
+
'NJ',
|
|
49
|
+
'NM',
|
|
50
|
+
'NY',
|
|
51
|
+
'NC',
|
|
52
|
+
'ND',
|
|
53
|
+
'OH',
|
|
54
|
+
'OK',
|
|
55
|
+
'OR',
|
|
56
|
+
'PA',
|
|
57
|
+
'RI',
|
|
58
|
+
'SC',
|
|
59
|
+
'SD',
|
|
60
|
+
'TN',
|
|
61
|
+
'TX',
|
|
62
|
+
'UT',
|
|
63
|
+
'VT',
|
|
64
|
+
'VA',
|
|
65
|
+
'WA',
|
|
66
|
+
'WV',
|
|
67
|
+
'WI',
|
|
68
|
+
'WY',
|
|
69
|
+
'DC',
|
|
70
|
+
'PR',
|
|
71
|
+
'VI',
|
|
72
|
+
'GU',
|
|
73
|
+
'AS',
|
|
74
|
+
'MP',
|
|
75
|
+
];
|
|
76
|
+
export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
|
|
77
|
+
title: 'Find Marine Stations',
|
|
78
|
+
description: 'Find CO-OPS tide/water-level/current stations and NDBC buoys near a location or by name/state. ' +
|
|
79
|
+
'Returns a unified station list with source, type, capabilities, and coordinates. ' +
|
|
80
|
+
'This is the required first step to resolve place names or coordinates to station IDs before calling data tools. ' +
|
|
81
|
+
'CO-OPS station IDs are numeric (e.g. 9447130 for Seattle); current station IDs are alphanumeric (e.g. ACT4176). ' +
|
|
82
|
+
'NDBC buoy IDs are 5-character alphanumeric codes (e.g. 46041). ' +
|
|
83
|
+
'Provide latitude/longitude for proximity search, or query/state for name-based search — both may be combined.',
|
|
84
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
85
|
+
input: z.object({
|
|
86
|
+
latitude: z
|
|
87
|
+
.number()
|
|
88
|
+
.min(-90)
|
|
89
|
+
.max(90)
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Center latitude in decimal degrees for proximity search. Pair with longitude and optionally radius_km.'),
|
|
92
|
+
longitude: z
|
|
93
|
+
.number()
|
|
94
|
+
.min(-180)
|
|
95
|
+
.max(180)
|
|
96
|
+
.optional()
|
|
97
|
+
.describe('Center longitude in decimal degrees for proximity search. Pair with latitude and optionally radius_km.'),
|
|
98
|
+
radius_km: z
|
|
99
|
+
.number()
|
|
100
|
+
.min(1)
|
|
101
|
+
.max(1000)
|
|
102
|
+
.default(100)
|
|
103
|
+
.describe('Search radius in kilometers when latitude/longitude are provided. Defaults to 100 km.'),
|
|
104
|
+
query: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe('Station name substring to search (case-insensitive token match). E.g. "seattle", "puget sound".'),
|
|
108
|
+
state: z
|
|
109
|
+
.enum(STATE_CODES)
|
|
110
|
+
.optional()
|
|
111
|
+
.describe('Filter by 2-letter US state or territory code (CO-OPS stations only). E.g. "WA", "CA", "PR".'),
|
|
112
|
+
source: z
|
|
113
|
+
.enum(['coops', 'ndbc', 'all'])
|
|
114
|
+
.default('all')
|
|
115
|
+
.describe('Data source to search: coops (tide/water-level/current stations), ndbc (buoys), or all (default).'),
|
|
116
|
+
types: z
|
|
117
|
+
.array(z
|
|
118
|
+
.enum(['tide', 'current', 'water_level', 'buoy', 'met'])
|
|
119
|
+
.describe('Station capability type: tide (CO-OPS tide predictions), current (CO-OPS current predictions), ' +
|
|
120
|
+
'water_level (CO-OPS observed water levels), buoy (NDBC buoy), met (NDBC meteorological).'))
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('Filter by station type/capability. Omit to return all types.'),
|
|
123
|
+
limit: z
|
|
124
|
+
.number()
|
|
125
|
+
.int()
|
|
126
|
+
.min(1)
|
|
127
|
+
.max(200)
|
|
128
|
+
.default(20)
|
|
129
|
+
.describe('Maximum number of stations to return. Defaults to 20.'),
|
|
130
|
+
}),
|
|
131
|
+
output: z.object({
|
|
132
|
+
stations: z
|
|
133
|
+
.array(z
|
|
134
|
+
.object({
|
|
135
|
+
station_id: z.string().describe('Station identifier — use this ID with data tools.'),
|
|
136
|
+
name: z.string().describe('Station name.'),
|
|
137
|
+
source: z
|
|
138
|
+
.enum(['coops', 'ndbc'])
|
|
139
|
+
.describe('Data source: coops (CO-OPS) or ndbc (NDBC buoy).'),
|
|
140
|
+
type: z
|
|
141
|
+
.string()
|
|
142
|
+
.describe('Station type/capability string, e.g. "tide", "current", "water_level", "buoy".'),
|
|
143
|
+
latitude: z.number().describe('Station latitude in decimal degrees.'),
|
|
144
|
+
longitude: z.number().describe('Station longitude in decimal degrees.'),
|
|
145
|
+
distance_km: z
|
|
146
|
+
.number()
|
|
147
|
+
.optional()
|
|
148
|
+
.describe('Distance in km from the search center (only present when lat/lon search was used).'),
|
|
149
|
+
state: z
|
|
150
|
+
.string()
|
|
151
|
+
.optional()
|
|
152
|
+
.describe('US state or territory code (CO-OPS stations only).'),
|
|
153
|
+
capabilities: z
|
|
154
|
+
.array(z.string().describe('Capability identifier, e.g. "tide", "water_level".'))
|
|
155
|
+
.describe('List of data products available at this station.'),
|
|
156
|
+
})
|
|
157
|
+
.describe('A single station matching the search criteria.'))
|
|
158
|
+
.describe('Stations matching the search criteria, sorted by distance (if lat/lon provided) or name.'),
|
|
159
|
+
total_found: z
|
|
160
|
+
.number()
|
|
161
|
+
.describe('Total stations matching the filters before the limit was applied.'),
|
|
162
|
+
}),
|
|
163
|
+
errors: [
|
|
164
|
+
{
|
|
165
|
+
reason: 'no_results',
|
|
166
|
+
code: JsonRpcErrorCode.NotFound,
|
|
167
|
+
when: 'No stations match the query, location, or filters.',
|
|
168
|
+
recovery: 'Widen the search by increasing radius_km, removing type filters, or using a broader query.',
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
async handler(input, ctx) {
|
|
172
|
+
const coopsSvc = getCoopsService();
|
|
173
|
+
const ndbcSvc = getNdbcService();
|
|
174
|
+
const results = [];
|
|
175
|
+
const lat = input.latitude;
|
|
176
|
+
const lon = input.longitude;
|
|
177
|
+
const hasLatLon = lat !== undefined && lat !== null && lon !== undefined && lon !== null;
|
|
178
|
+
// Fetch CO-OPS and NDBC lists in parallel
|
|
179
|
+
const includeCoops = input.source === 'all' || input.source === 'coops';
|
|
180
|
+
const includeNdbc = input.source === 'all' || input.source === 'ndbc';
|
|
181
|
+
const [coopsResults, ndbcResult] = await Promise.allSettled([
|
|
182
|
+
includeCoops
|
|
183
|
+
? Promise.all([
|
|
184
|
+
coopsSvc.getStations('tidepredictions', ctx),
|
|
185
|
+
coopsSvc.getStations('currentpredictions', ctx),
|
|
186
|
+
coopsSvc.getStations('waterlevels', ctx),
|
|
187
|
+
])
|
|
188
|
+
: Promise.resolve(null),
|
|
189
|
+
includeNdbc ? ndbcSvc.getActiveStations(ctx) : Promise.resolve(null),
|
|
190
|
+
]);
|
|
191
|
+
// Process CO-OPS stations
|
|
192
|
+
if (coopsResults.status === 'fulfilled' && coopsResults.value) {
|
|
193
|
+
const [tideStations, currentStations, waterLevelStations] = coopsResults.value;
|
|
194
|
+
const tideSet = new Set();
|
|
195
|
+
const currentSet = new Set();
|
|
196
|
+
const waterLevelSet = new Set();
|
|
197
|
+
for (const s of tideStations)
|
|
198
|
+
tideSet.add(s.id);
|
|
199
|
+
for (const s of currentStations)
|
|
200
|
+
currentSet.add(s.id);
|
|
201
|
+
for (const s of waterLevelStations)
|
|
202
|
+
waterLevelSet.add(s.id);
|
|
203
|
+
// Build unique station map — a station can appear in multiple lists
|
|
204
|
+
const allCoops = new Map();
|
|
205
|
+
for (const s of tideStations) {
|
|
206
|
+
if (!allCoops.has(s.id))
|
|
207
|
+
allCoops.set(s.id, { ...s, capabilities: [] });
|
|
208
|
+
const entry = allCoops.get(s.id);
|
|
209
|
+
if (entry && !entry.capabilities.includes('tide'))
|
|
210
|
+
entry.capabilities.push('tide');
|
|
211
|
+
}
|
|
212
|
+
for (const s of currentStations) {
|
|
213
|
+
if (!allCoops.has(s.id))
|
|
214
|
+
allCoops.set(s.id, { ...s, capabilities: [] });
|
|
215
|
+
const entry = allCoops.get(s.id);
|
|
216
|
+
if (entry && !entry.capabilities.includes('current'))
|
|
217
|
+
entry.capabilities.push('current');
|
|
218
|
+
}
|
|
219
|
+
for (const s of waterLevelStations) {
|
|
220
|
+
if (!allCoops.has(s.id))
|
|
221
|
+
allCoops.set(s.id, { ...s, capabilities: [] });
|
|
222
|
+
const entry = allCoops.get(s.id);
|
|
223
|
+
if (entry && !entry.capabilities.includes('water_level'))
|
|
224
|
+
entry.capabilities.push('water_level');
|
|
225
|
+
}
|
|
226
|
+
for (const [, s] of allCoops) {
|
|
227
|
+
// Apply type filter
|
|
228
|
+
if (input.types && input.types.length > 0) {
|
|
229
|
+
const hasMatch = input.types.some((t) => (t === 'tide' && tideSet.has(s.id)) ||
|
|
230
|
+
(t === 'current' && currentSet.has(s.id)) ||
|
|
231
|
+
(t === 'water_level' && waterLevelSet.has(s.id)));
|
|
232
|
+
if (!hasMatch)
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Apply state filter
|
|
236
|
+
if (input.state && s.state !== input.state)
|
|
237
|
+
continue;
|
|
238
|
+
// Apply name query
|
|
239
|
+
if (input.query) {
|
|
240
|
+
const q = input.query.toLowerCase();
|
|
241
|
+
if (!s.name.toLowerCase().includes(q))
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const primaryType = s.capabilities[0] ?? 'tide';
|
|
245
|
+
const entry = {
|
|
246
|
+
station_id: s.id,
|
|
247
|
+
name: s.name,
|
|
248
|
+
source: 'coops',
|
|
249
|
+
type: primaryType,
|
|
250
|
+
latitude: s.lat,
|
|
251
|
+
longitude: s.lng,
|
|
252
|
+
capabilities: s.capabilities,
|
|
253
|
+
};
|
|
254
|
+
if (s.state)
|
|
255
|
+
entry.state = s.state;
|
|
256
|
+
if (hasLatLon && lat !== undefined && lon !== undefined) {
|
|
257
|
+
const dist = haversineKm(lat, lon, s.lat, s.lng);
|
|
258
|
+
if (dist > input.radius_km)
|
|
259
|
+
continue;
|
|
260
|
+
entry.distance_km = Math.round(dist * 10) / 10;
|
|
261
|
+
}
|
|
262
|
+
results.push(entry);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Process NDBC stations
|
|
266
|
+
if (ndbcResult.status === 'fulfilled' && ndbcResult.value) {
|
|
267
|
+
for (const s of ndbcResult.value) {
|
|
268
|
+
// Apply type filter
|
|
269
|
+
if (input.types && input.types.length > 0) {
|
|
270
|
+
const hasMatch = input.types.some((t) => (t === 'buoy' || t === 'met') && (s.hasMet || s.type?.toLowerCase().includes('buoy')));
|
|
271
|
+
if (!hasMatch)
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
// Apply name query
|
|
275
|
+
if (input.query) {
|
|
276
|
+
const q = input.query.toLowerCase();
|
|
277
|
+
if (!s.name.toLowerCase().includes(q) && !s.id.toLowerCase().includes(q))
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const capabilities = [];
|
|
281
|
+
if (s.hasMet)
|
|
282
|
+
capabilities.push('met');
|
|
283
|
+
if (s.hasCurrents)
|
|
284
|
+
capabilities.push('currents');
|
|
285
|
+
if (capabilities.length === 0)
|
|
286
|
+
capabilities.push('buoy');
|
|
287
|
+
const entry = {
|
|
288
|
+
station_id: s.id,
|
|
289
|
+
name: s.name,
|
|
290
|
+
source: 'ndbc',
|
|
291
|
+
type: 'buoy',
|
|
292
|
+
latitude: s.lat,
|
|
293
|
+
longitude: s.lon,
|
|
294
|
+
capabilities,
|
|
295
|
+
};
|
|
296
|
+
if (hasLatLon && lat !== undefined && lon !== undefined) {
|
|
297
|
+
const dist = haversineKm(lat, lon, s.lat, s.lon);
|
|
298
|
+
if (dist > input.radius_km)
|
|
299
|
+
continue;
|
|
300
|
+
entry.distance_km = Math.round(dist * 10) / 10;
|
|
301
|
+
}
|
|
302
|
+
results.push(entry);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Sort: by distance if lat/lon provided, otherwise by name
|
|
306
|
+
if (hasLatLon) {
|
|
307
|
+
results.sort((a, b) => (a.distance_km ?? 9999) - (b.distance_km ?? 9999));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
311
|
+
}
|
|
312
|
+
const total_found = results.length;
|
|
313
|
+
if (total_found === 0) {
|
|
314
|
+
throw ctx.fail('no_results', 'No stations match the specified search criteria.', {
|
|
315
|
+
...ctx.recoveryFor('no_results'),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const stations = results.slice(0, input.limit);
|
|
319
|
+
ctx.log.info('Station search complete', {
|
|
320
|
+
total_found,
|
|
321
|
+
returned: stations.length,
|
|
322
|
+
source: input.source,
|
|
323
|
+
});
|
|
324
|
+
return { stations, total_found };
|
|
325
|
+
},
|
|
326
|
+
format: (result) => {
|
|
327
|
+
const lines = [
|
|
328
|
+
`**${result.total_found} station(s) found** (showing ${result.stations.length})\n`,
|
|
329
|
+
];
|
|
330
|
+
for (const s of result.stations) {
|
|
331
|
+
const dist = s.distance_km !== undefined ? ` · ${s.distance_km} km` : '';
|
|
332
|
+
const state = s.state ? ` · ${s.state}` : '';
|
|
333
|
+
lines.push(`### ${s.name} (${s.station_id})`, `**Source:** ${s.source.toUpperCase()} · **Type:** ${s.type}${dist}${state}`, `**Coordinates:** ${s.latitude}, ${s.longitude}`, `**Capabilities:** ${s.capabilities.join(', ')}`, '');
|
|
334
|
+
}
|
|
335
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
//# sourceMappingURL=noaa-marine-find-stations.tool.js.map
|