diff --git a/docs/TOOLS.md b/docs/TOOLS.md index cadd5712a..8a9bec7ae 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -11,6 +11,7 @@ They are defined once and exposed through adapters; the Model Context Protocol | `src/tools/` | Tool definitions, catalog composition, argument validation, shared `area` and result helpers | | `src/tools/queries/` | Queries, one file per domain, reading only portable source contracts | | `src/tools/mcp/` | MCP protocol (JSON-RPC) and a stateless HTTP transport; knows the catalog interface, not queries | +| `src/tools/services.js` | The default services: the layers' source factories and request services, given a resolving fetch | | `server/mcp/` | Node composition: points the sources at a running app's `/api` routes; serves stdio and `/mcp` | Dependencies point downward only. `gods-eye-view/tools` and @@ -44,7 +45,8 @@ reported to clients without details. ## Services -Services are the portable source factories the layers already use, such as +`gods-eye-view/tools/services` builds the default set with +`createToolServices({ fetchImpl, appUrl })`. Services are the portable source factories the layers already use, such as `createUsgsEarthquakeSource`, `createFirmsSource` and `createLaunchSource`, plus a `places` service with `resolve(name, { signal })`. Sources request relative `/api/...` paths through an injected `fetchImpl`, so the same tool diff --git a/package.json b/package.json index 13b7bd8fd..7169a4c24 100644 --- a/package.json +++ b/package.json @@ -253,6 +253,7 @@ "./sources/transit-service": "./src/sources/transitService.js", "./sources/map-features": "./src/sources/overpassFeatures.js", "./tools": "./src/tools/index.js", - "./tools/mcp": "./src/tools/mcp/index.js" + "./tools/mcp": "./src/tools/mcp/index.js", + "./tools/services": "./src/tools/services.js" } } diff --git a/scripts/check-import-directions.mjs b/scripts/check-import-directions.mjs index 72e163988..5eeca6ae2 100644 --- a/scripts/check-import-directions.mjs +++ b/scripts/check-import-directions.mjs @@ -39,6 +39,7 @@ const portableExport = (key) => './director', './tools', './tools/mcp', + './tools/services', './voice/action-schemas', './voice/session', './data/lifecycle', diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index d9d5c93e0..03bc62849 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -3114,5 +3114,59 @@ "src/tools/schema.js" ], "external": [] + }, + "tools-services": { + "exports": ["./tools/services"], + "modules": [ + "src/tools/services.js", + "src/layers/bikeshare/registry.js", + "src/layers/bikeshare/source.js", + "src/layers/cctv/source.js", + "src/layers/cctv/sourcePolicy.js", + "src/layers/cyclones/source.js", + "src/sources/httpBody.js", + "src/layers/earthquakes/source.js", + "src/layers/earthquakes/records.js", + "src/layers/firms/source.js", + "src/layers/installations/source.js", + "src/sources/requestDeadline.js", + "src/data/militaryNames.js", + "src/sources/militaryTileGeometry.js", + "src/data/bundledJson.js", + "src/data/retryableLoad.js", + "src/data/local_data/osm_military_names/names.json", + "src/sources/openFreeMap.js", + "src/sources/vectorTiles.js", + "src/sources/phaseTiming.js", + "src/data/tomtomTiles.js", + "src/sources/capability.js", + "src/data/militaryInstallationData.js", + "src/layers/launches/source.js", + "src/layers/perimeters/source.js", + "src/layers/radio/source.js", + "src/layers/radio/policy.js", + "src/layers/satellites/source.js", + "src/layers/traffic/source.js", + "src/layers/traffic/roadModes.js", + "src/data/flowMatch.js", + "src/layers/traffic/flowSource.js", + "src/layers/traffic/flowDecode.js", + "src/sources/overpassRoads.js", + "src/layers/transit/source.js", + "src/sources/transitHistory.js", + "src/services/requests.js", + "src/sources/overpassFeatures.js", + "src/sources/featureSource.js", + "src/sources/overpassFeaturesRecords.js", + "src/sources/featureGeometry.js", + "src/sources/live/standalone.js", + "src/sources/live/contract.js", + "src/sources/live/aircraft.js", + "src/sources/live/vessels.js", + "src/tools/places.js", + "src/tools/catalog.js", + "src/tools/schema.js" + ], + "external": ["@mapbox/vector-tile", "pbf"] } } diff --git a/server/mcp/services.js b/server/mcp/services.js index fb3785223..a4ac48965 100644 --- a/server/mcp/services.js +++ b/server/mcp/services.js @@ -1,32 +1,9 @@ /** - * Local composition of tool services: the portable source factories, pointed - * at a running God's Eye View server's `/api` routes. + * Local composition of tool services: Core's default services, pointed at a + * running God's Eye View server's `/api` routes. */ -import { GBFS_CITY_REGISTRY } from '../../src/layers/bikeshare/registry.js'; -import { createBikeshareSource } from '../../src/layers/bikeshare/source.js'; -import { createCctvSource } from '../../src/layers/cctv/source.js'; -import { createCycloneSource } from '../../src/layers/cyclones/source.js'; -import { createUsgsEarthquakeSource } from '../../src/layers/earthquakes/source.js'; -import { createFirmsSource } from '../../src/layers/firms/source.js'; -import { createInstallationSource } from '../../src/layers/installations/source.js'; -import { createLaunchSource } from '../../src/layers/launches/source.js'; -import { createWfigsPerimeterSource } from '../../src/layers/perimeters/source.js'; -import { createRadioSource } from '../../src/layers/radio/source.js'; -import { createSatelliteSource } from '../../src/layers/satellites/source.js'; -import { createTrafficSource } from '../../src/layers/traffic/source.js'; -import { createTransitSource } from '../../src/layers/transit/source.js'; -import { createApplicationRequestServices } from '../../src/services/requests.js'; -import { - createAdsbLolSource, - createAisStreamSource, - createOpenSkySource, -} from '../../src/sources/live/standalone.js'; -import { - createGeocodePlaceService, - createPlaceSearchService, - createRouteService, -} from '../../src/tools/places.js'; +import { createToolServices } from '../../src/tools/services.js'; export const DEFAULT_API_BASE = 'http://localhost:4173'; @@ -49,41 +26,8 @@ export function createApiFetch({ /** Construct every service Core's tools read, backed by the local server. */ export function createLocalToolServices(options = {}) { - const fetchImpl = createApiFetch(options); - const requests = createApplicationRequestServices({ fetchImpl }); - return { - app: { baseUrl: options.apiBase ?? DEFAULT_API_BASE }, - earthquakes: createUsgsEarthquakeSource({ fetchImpl }), - fires: createFirmsSource({ fetchImpl }), - launches: createLaunchSource({ fetchImpl }), - aircraft: createOpenSkySource({ fetchImpl }), - military: createAdsbLolSource({ fetchImpl }), - vessels: createAisStreamSource({ - fetchImpl, - origin: () => new URL(options.apiBase ?? DEFAULT_API_BASE).origin, - }), - satellites: createSatelliteSource({ fetchImpl }), - cctv: createCctvSource({ fetchImpl }), - radio: createRadioSource({ fetchImpl }), - placeSearch: createPlaceSearchService({ fetchImpl }), - routing: createRouteService({ fetchImpl }), - bikeshare: { - systems: GBFS_CITY_REGISTRY, - getStations: createBikeshareSource({ fetchImpl }).getStations, - }, - transit: createTransitSource({ fetchImpl }), - traffic: createTrafficSource({ fetchImpl, tileFetchImpl: fetchImpl }), - weather: requests.weather, - regional: requests.regional, - terrain: requests.terrain, - summary: requests.summary, - features: requests.features, - cyclones: createCycloneSource({ fetchImpl }), - perimeters: createWfigsPerimeterSource({ fetchImpl }), - installations: createInstallationSource({ - fetchImpl, - tileFetchImpl: fetchImpl, - }), - places: createGeocodePlaceService({ fetchImpl }), - }; + return createToolServices({ + fetchImpl: createApiFetch(options), + appUrl: options.apiBase ?? DEFAULT_API_BASE, + }); } diff --git a/src/tools/services.js b/src/tools/services.js new file mode 100644 index 000000000..8071425ed --- /dev/null +++ b/src/tools/services.js @@ -0,0 +1,74 @@ +/** + * The default services Core's tools read: the layers' portable source + * factories and the application request services. `fetchImpl` must resolve + * the sources' relative `/api/...` paths; in a browser the page's own fetch + * does, and elsewhere a caller supplies a resolving fetch. `appUrl` is the + * address the app is served from. + */ + +import { GBFS_CITY_REGISTRY } from '../layers/bikeshare/registry.js'; +import { createBikeshareSource } from '../layers/bikeshare/source.js'; +import { createCctvSource } from '../layers/cctv/source.js'; +import { createCycloneSource } from '../layers/cyclones/source.js'; +import { createUsgsEarthquakeSource } from '../layers/earthquakes/source.js'; +import { createFirmsSource } from '../layers/firms/source.js'; +import { createInstallationSource } from '../layers/installations/source.js'; +import { createLaunchSource } from '../layers/launches/source.js'; +import { createWfigsPerimeterSource } from '../layers/perimeters/source.js'; +import { createRadioSource } from '../layers/radio/source.js'; +import { createSatelliteSource } from '../layers/satellites/source.js'; +import { createTrafficSource } from '../layers/traffic/source.js'; +import { createTransitSource } from '../layers/transit/source.js'; +import { createApplicationRequestServices } from '../services/requests.js'; +import { + createAdsbLolSource, + createAisStreamSource, + createOpenSkySource, +} from '../sources/live/standalone.js'; +import { + createGeocodePlaceService, + createPlaceSearchService, + createRouteService, +} from './places.js'; + +/** Construct every service Core's tools read. */ +export function createToolServices({ fetchImpl, appUrl }) { + if (typeof fetchImpl !== 'function') + throw new TypeError('A fetch implementation is required'); + const requests = createApplicationRequestServices({ fetchImpl }); + return { + app: { baseUrl: appUrl }, + earthquakes: createUsgsEarthquakeSource({ fetchImpl }), + fires: createFirmsSource({ fetchImpl }), + launches: createLaunchSource({ fetchImpl }), + aircraft: createOpenSkySource({ fetchImpl }), + military: createAdsbLolSource({ fetchImpl }), + vessels: createAisStreamSource({ + fetchImpl, + origin: () => new URL(appUrl).origin, + }), + satellites: createSatelliteSource({ fetchImpl }), + cctv: createCctvSource({ fetchImpl }), + radio: createRadioSource({ fetchImpl }), + placeSearch: createPlaceSearchService({ fetchImpl }), + routing: createRouteService({ fetchImpl }), + bikeshare: { + systems: GBFS_CITY_REGISTRY, + getStations: createBikeshareSource({ fetchImpl }).getStations, + }, + transit: createTransitSource({ fetchImpl }), + traffic: createTrafficSource({ fetchImpl, tileFetchImpl: fetchImpl }), + weather: requests.weather, + regional: requests.regional, + terrain: requests.terrain, + summary: requests.summary, + features: requests.features, + cyclones: createCycloneSource({ fetchImpl }), + perimeters: createWfigsPerimeterSource({ fetchImpl }), + installations: createInstallationSource({ + fetchImpl, + tileFetchImpl: fetchImpl, + }), + places: createGeocodePlaceService({ fetchImpl }), + }; +}