@honua/mcp-server 0.1.1-beta.0 → 0.1.4-beta.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/LICENSE +201 -0
- package/README.md +202 -7
- package/dist/src/certification/census-fixture-client.d.ts.map +1 -1
- package/dist/src/certification/census-fixture-client.js +5 -0
- package/dist/src/certification/certifier.d.ts +1 -1
- package/dist/src/certification/certifier.d.ts.map +1 -1
- package/dist/src/certification/certifier.js +28 -20
- package/dist/src/certification/cli.js +1 -1
- package/dist/src/certification/cql2.d.ts +24 -0
- package/dist/src/certification/cql2.d.ts.map +1 -0
- package/dist/src/certification/cql2.js +354 -0
- package/dist/src/certification/fixtures.d.ts +8 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -1
- package/dist/src/certification/fixtures.js +26 -10
- package/dist/src/certification/ogc-data.d.ts +30 -0
- package/dist/src/certification/ogc-data.d.ts.map +1 -0
- package/dist/src/certification/ogc-data.js +726 -0
- package/dist/src/certification/ogc-fixture-client.d.ts +4 -0
- package/dist/src/certification/ogc-fixture-client.d.ts.map +1 -0
- package/dist/src/certification/ogc-fixture-client.js +190 -0
- package/dist/src/certification/target.d.ts +10 -1
- package/dist/src/certification/target.d.ts.map +1 -1
- package/dist/src/certification/target.js +45 -2
- package/dist/src/eval/cli.js +6 -3
- package/dist/src/eval/corpus.d.ts +7 -0
- package/dist/src/eval/corpus.d.ts.map +1 -1
- package/dist/src/eval/corpus.js +12 -0
- package/dist/src/eval/ogc-corpus.d.ts +3 -0
- package/dist/src/eval/ogc-corpus.d.ts.map +1 -0
- package/dist/src/eval/ogc-corpus.js +437 -0
- package/dist/src/eval/runner.d.ts +6 -0
- package/dist/src/eval/runner.d.ts.map +1 -1
- package/dist/src/eval/runner.js +21 -7
- package/dist/src/helpers.d.ts +8 -13
- package/dist/src/helpers.d.ts.map +1 -1
- package/dist/src/helpers.js +7 -35
- package/dist/src/index.d.ts +14 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -15
- package/dist/src/neutral/errors.d.ts +73 -0
- package/dist/src/neutral/errors.d.ts.map +1 -0
- package/dist/src/neutral/errors.js +112 -0
- package/dist/src/neutral/filter.d.ts +108 -0
- package/dist/src/neutral/filter.d.ts.map +1 -0
- package/dist/src/neutral/filter.js +271 -0
- package/dist/src/neutral/query.d.ts +101 -0
- package/dist/src/neutral/query.d.ts.map +1 -0
- package/dist/src/neutral/query.js +180 -0
- package/dist/src/neutral/result.d.ts +33 -0
- package/dist/src/neutral/result.d.ts.map +1 -0
- package/dist/src/neutral/result.js +38 -0
- package/dist/src/neutral/source-ref.d.ts +126 -0
- package/dist/src/neutral/source-ref.d.ts.map +1 -0
- package/dist/src/neutral/source-ref.js +267 -0
- package/dist/src/nl-map-control.d.ts.map +1 -1
- package/dist/src/tools/count-features.d.ts +85 -11
- package/dist/src/tools/count-features.d.ts.map +1 -1
- package/dist/src/tools/count-features.js +49 -31
- package/dist/src/tools/describe-layer.d.ts +30 -6
- package/dist/src/tools/describe-layer.d.ts.map +1 -1
- package/dist/src/tools/describe-layer.js +99 -8
- package/dist/src/tools/get-extent.d.ts +69 -11
- package/dist/src/tools/get-extent.d.ts.map +1 -1
- package/dist/src/tools/get-extent.js +28 -46
- package/dist/src/tools/list-services.d.ts +7 -0
- package/dist/src/tools/list-services.d.ts.map +1 -1
- package/dist/src/tools/list-services.js +8 -1
- package/dist/src/tools/list-sources.d.ts +44 -0
- package/dist/src/tools/list-sources.d.ts.map +1 -0
- package/dist/src/tools/list-sources.js +138 -0
- package/dist/src/tools/query-features.d.ts +99 -19
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +38 -45
- package/dist/src/tools/statistics.d.ts +80 -12
- package/dist/src/tools/statistics.d.ts.map +1 -1
- package/dist/src/tools/statistics.js +56 -23
- package/package.json +31 -7
|
@@ -1,19 +1,110 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonText } from "../helpers.js";
|
|
3
|
+
import { withCapabilityHonesty } from "../neutral/errors.js";
|
|
4
|
+
import { ogcLayoutFor, resolveSource, sourceRefFields } from "../neutral/source-ref.js";
|
|
5
|
+
/**
|
|
6
|
+
* `honua_describe_layer` — protocol-neutral source description (#1005).
|
|
7
|
+
*
|
|
8
|
+
* The envelope is the same for every protocol: the neutral source reference,
|
|
9
|
+
* its protocol, the capabilities the runtime will actually honor, and a field
|
|
10
|
+
* schema. Where the schema comes from is protocol-specific (GeoServices layer
|
|
11
|
+
* metadata, an OGC collection + its queryables); the shape the agent reads is
|
|
12
|
+
* not. Protocols with no metadata document report `schemaAvailable: false`
|
|
13
|
+
* with a reason instead of an empty field list, which would read as "this
|
|
14
|
+
* source has no attributes".
|
|
15
|
+
*/
|
|
3
16
|
export const schema = z.object({
|
|
4
|
-
|
|
5
|
-
layerId: z.number().int().nonnegative().describe("The layer ID within the service"),
|
|
17
|
+
...sourceRefFields,
|
|
6
18
|
});
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
name: meta.name,
|
|
19
|
+
async function describeGeoServices(client, resolved) {
|
|
20
|
+
const { serviceId, layerId } = resolved.descriptor.locator;
|
|
21
|
+
const meta = await client.getLayerMetadata(serviceId, layerId);
|
|
22
|
+
return {
|
|
23
|
+
name: meta.name ?? null,
|
|
12
24
|
description: meta.description ?? null,
|
|
13
25
|
geometryType: meta.geometryType ?? null,
|
|
14
|
-
fields: (meta.fields ?? []).map((f) => ({ name: f.name, type: f.type, alias: f.alias ?? f.name })),
|
|
26
|
+
fields: (meta.fields ?? []).map((f) => ({ name: f.name, type: f.type ?? null, alias: f.alias ?? f.name })),
|
|
15
27
|
extent: meta.extent ?? null,
|
|
16
28
|
spatialReference: meta.spatialReference ?? null,
|
|
17
29
|
relationships: meta.relationships ?? [],
|
|
30
|
+
schemaAvailable: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function bboxToExtent(bbox) {
|
|
34
|
+
if (!bbox || bbox.length < 4)
|
|
35
|
+
return null;
|
|
36
|
+
const [xmin, ymin, xmax, ymax] = bbox;
|
|
37
|
+
return { xmin, ymin, xmax, ymax };
|
|
38
|
+
}
|
|
39
|
+
async function describeOgcCollection(client, resolved) {
|
|
40
|
+
const collectionId = resolved.descriptor.locator.collectionId;
|
|
41
|
+
const layout = await ogcLayoutFor(client, resolved.ref.layout);
|
|
42
|
+
const request = { collectionId, ...(layout ? { layout } : {}) };
|
|
43
|
+
const meta = resolved.descriptor.protocol === "ogc-records"
|
|
44
|
+
? await client.getOgcRecordCollection(request)
|
|
45
|
+
: await client.getOgcCollection(request);
|
|
46
|
+
let fields = [];
|
|
47
|
+
let schemaAvailable = true;
|
|
48
|
+
let schemaReason;
|
|
49
|
+
try {
|
|
50
|
+
const queryables = await client.getOgcQueryables(request);
|
|
51
|
+
fields = Object.entries(queryables.properties ?? {}).map(([name, property]) => ({
|
|
52
|
+
name,
|
|
53
|
+
type: property.type ?? null,
|
|
54
|
+
alias: property.title ?? name,
|
|
55
|
+
}));
|
|
56
|
+
if (fields.length === 0) {
|
|
57
|
+
schemaAvailable = false;
|
|
58
|
+
schemaReason = "the collection advertises no queryable properties.";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
schemaAvailable = false;
|
|
63
|
+
schemaReason = `queryables are not published by this endpoint: ${error instanceof Error ? error.message : String(error)}`;
|
|
64
|
+
}
|
|
65
|
+
const temporal = meta.extent?.temporal?.interval?.[0];
|
|
66
|
+
return {
|
|
67
|
+
name: meta.title ?? meta.id ?? collectionId,
|
|
68
|
+
description: meta.description ?? null,
|
|
69
|
+
geometryType: null,
|
|
70
|
+
fields,
|
|
71
|
+
extent: bboxToExtent(meta.extent?.spatial?.bbox?.[0]),
|
|
72
|
+
spatialReference: meta.extent?.spatial?.crs ?? meta.crs?.[0] ?? null,
|
|
73
|
+
relationships: [],
|
|
74
|
+
...(temporal ? { timeField: null } : {}),
|
|
75
|
+
schemaAvailable,
|
|
76
|
+
...(schemaReason ? { schemaReason } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function describeFromDescriptorOnly(resolved) {
|
|
80
|
+
return {
|
|
81
|
+
name: resolved.ref.address,
|
|
82
|
+
description: null,
|
|
83
|
+
geometryType: null,
|
|
84
|
+
fields: [],
|
|
85
|
+
extent: null,
|
|
86
|
+
spatialReference: null,
|
|
87
|
+
relationships: [],
|
|
88
|
+
schemaAvailable: false,
|
|
89
|
+
schemaReason: `protocol "${resolved.descriptor.protocol}" publishes no schema document this server can read without a discovery pass; query the source and inspect the returned attributes instead.`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function execute(client, input) {
|
|
93
|
+
return withCapabilityHonesty(async () => {
|
|
94
|
+
const resolved = resolveSource(client, input);
|
|
95
|
+
const protocol = resolved.descriptor.protocol;
|
|
96
|
+
const envelope = protocol === "geoservices-feature-service" || protocol === "geoservices-map-service" || protocol === "grpc"
|
|
97
|
+
? await describeGeoServices(client, resolved)
|
|
98
|
+
: protocol === "ogc-features" || protocol === "ogc-records"
|
|
99
|
+
? await describeOgcCollection(client, resolved)
|
|
100
|
+
: describeFromDescriptorOnly(resolved);
|
|
101
|
+
return jsonText({
|
|
102
|
+
source: resolved.ref.ref,
|
|
103
|
+
protocol,
|
|
104
|
+
capabilities: [...resolved.source.capabilities].sort(),
|
|
105
|
+
...envelope,
|
|
106
|
+
// Retained for clients written against the pre-#1005 shape.
|
|
107
|
+
id: resolved.descriptor.locator.layerId ?? resolved.ref.address,
|
|
108
|
+
});
|
|
18
109
|
});
|
|
19
110
|
}
|
|
@@ -1,26 +1,77 @@
|
|
|
1
1
|
import type { HonuaClient } from "@honua/sdk-js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* `honua_get_extent` — protocol-neutral bounding box (#1005).
|
|
5
|
+
*
|
|
6
|
+
* Runs the canonical `Source.queryExtent()`. Protocols with no server-side
|
|
7
|
+
* extent operation (OGC API Features, STAC, OData) answer from the collection's
|
|
8
|
+
* declared extent under the degraded capability policy, and say so; protocols
|
|
9
|
+
* that cannot answer at all refuse with a structured capability error.
|
|
10
|
+
*/
|
|
3
11
|
export declare const schema: z.ZodObject<{
|
|
4
|
-
|
|
5
|
-
layerId: z.ZodNumber;
|
|
12
|
+
filter: z.ZodOptional<z.ZodType<import("../neutral/filter.js").FilterNode, z.ZodTypeDef, import("../neutral/filter.js").FilterNode>>;
|
|
6
13
|
where: z.ZodOptional<z.ZodString>;
|
|
14
|
+
bbox: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
15
|
+
bboxSrid: z.ZodOptional<z.ZodNumber>;
|
|
7
16
|
geometry: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
8
17
|
geometryType: z.ZodOptional<z.ZodEnum<["esriGeometryPoint", "esriGeometryPolyline", "esriGeometryPolygon", "esriGeometryEnvelope", "esriGeometryMultipoint"]>>;
|
|
9
|
-
spatialRel: z.ZodOptional<z.ZodEnum<["intersects", "contains", "within"]>>;
|
|
18
|
+
spatialRel: z.ZodOptional<z.ZodEnum<["intersects", "contains", "within", "crosses", "touches", "overlaps", "bbox-intersects"]>>;
|
|
19
|
+
temporal: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
instant: z.ZodOptional<z.ZodString>;
|
|
21
|
+
start: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
|
+
end: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
23
|
+
field: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
field?: string | undefined;
|
|
26
|
+
instant?: string | undefined;
|
|
27
|
+
start?: string | null | undefined;
|
|
28
|
+
end?: string | null | undefined;
|
|
29
|
+
}, {
|
|
30
|
+
field?: string | undefined;
|
|
31
|
+
instant?: string | undefined;
|
|
32
|
+
start?: string | null | undefined;
|
|
33
|
+
end?: string | null | undefined;
|
|
34
|
+
}>>;
|
|
35
|
+
source: z.ZodOptional<z.ZodString>;
|
|
36
|
+
layout: z.ZodOptional<z.ZodEnum<["honua-facade", "ogc-api", "auto", "stac-api", "stac-static"]>>;
|
|
37
|
+
serviceId: z.ZodOptional<z.ZodString>;
|
|
38
|
+
layerId: z.ZodOptional<z.ZodNumber>;
|
|
10
39
|
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
where?: string | undefined;
|
|
40
|
+
filter?: import("../neutral/filter.js").FilterNode | undefined;
|
|
41
|
+
serviceId?: string | undefined;
|
|
14
42
|
geometry?: Record<string, unknown> | undefined;
|
|
43
|
+
bbox?: number[] | undefined;
|
|
15
44
|
geometryType?: "esriGeometryPoint" | "esriGeometryPolyline" | "esriGeometryPolygon" | "esriGeometryEnvelope" | "esriGeometryMultipoint" | undefined;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
45
|
+
temporal?: {
|
|
46
|
+
field?: string | undefined;
|
|
47
|
+
instant?: string | undefined;
|
|
48
|
+
start?: string | null | undefined;
|
|
49
|
+
end?: string | null | undefined;
|
|
50
|
+
} | undefined;
|
|
51
|
+
layout?: "honua-facade" | "ogc-api" | "auto" | "stac-api" | "stac-static" | undefined;
|
|
52
|
+
layerId?: number | undefined;
|
|
53
|
+
source?: string | undefined;
|
|
20
54
|
where?: string | undefined;
|
|
55
|
+
bboxSrid?: number | undefined;
|
|
56
|
+
spatialRel?: "intersects" | "contains" | "within" | "crosses" | "touches" | "overlaps" | "bbox-intersects" | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
filter?: import("../neutral/filter.js").FilterNode | undefined;
|
|
59
|
+
serviceId?: string | undefined;
|
|
21
60
|
geometry?: Record<string, unknown> | undefined;
|
|
61
|
+
bbox?: number[] | undefined;
|
|
22
62
|
geometryType?: "esriGeometryPoint" | "esriGeometryPolyline" | "esriGeometryPolygon" | "esriGeometryEnvelope" | "esriGeometryMultipoint" | undefined;
|
|
23
|
-
|
|
63
|
+
temporal?: {
|
|
64
|
+
field?: string | undefined;
|
|
65
|
+
instant?: string | undefined;
|
|
66
|
+
start?: string | null | undefined;
|
|
67
|
+
end?: string | null | undefined;
|
|
68
|
+
} | undefined;
|
|
69
|
+
layout?: "honua-facade" | "ogc-api" | "auto" | "stac-api" | "stac-static" | undefined;
|
|
70
|
+
layerId?: number | undefined;
|
|
71
|
+
source?: string | undefined;
|
|
72
|
+
where?: string | undefined;
|
|
73
|
+
bboxSrid?: number | undefined;
|
|
74
|
+
spatialRel?: "intersects" | "contains" | "within" | "crosses" | "touches" | "overlaps" | "bbox-intersects" | undefined;
|
|
24
75
|
}>;
|
|
25
76
|
export type Input = z.infer<typeof schema>;
|
|
26
77
|
export declare function execute(client: HonuaClient, input: Input): Promise<{
|
|
@@ -28,5 +79,12 @@ export declare function execute(client: HonuaClient, input: Input): Promise<{
|
|
|
28
79
|
type: "text";
|
|
29
80
|
text: string;
|
|
30
81
|
}>;
|
|
82
|
+
} | {
|
|
83
|
+
structuredContent: Record<string, unknown>;
|
|
84
|
+
isError: true;
|
|
85
|
+
content: Array<{
|
|
86
|
+
type: "text";
|
|
87
|
+
text: string;
|
|
88
|
+
}>;
|
|
31
89
|
}>;
|
|
32
90
|
//# sourceMappingURL=get-extent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-extent.d.ts","sourceRoot":"","sources":["../../../src/tools/get-extent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"get-extent.d.ts","sourceRoot":"","sources":["../../../src/tools/get-extent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE3C,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;;;;;;;;;;;;GAgB9D"}
|
|
@@ -1,51 +1,33 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { jsonText } from "../helpers.js";
|
|
3
|
+
import { withCapabilityHonesty } from "../neutral/errors.js";
|
|
4
|
+
import { queryFilterFields, toQuery } from "../neutral/query.js";
|
|
5
|
+
import { resolveSource, sourceRefFields } from "../neutral/source-ref.js";
|
|
6
|
+
/**
|
|
7
|
+
* `honua_get_extent` — protocol-neutral bounding box (#1005).
|
|
8
|
+
*
|
|
9
|
+
* Runs the canonical `Source.queryExtent()`. Protocols with no server-side
|
|
10
|
+
* extent operation (OGC API Features, STAC, OData) answer from the collection's
|
|
11
|
+
* declared extent under the degraded capability policy, and say so; protocols
|
|
12
|
+
* that cannot answer at all refuse with a structured capability error.
|
|
13
|
+
*/
|
|
3
14
|
export const schema = z.object({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
where: z.string().optional().describe("SQL WHERE clause"),
|
|
7
|
-
geometry: z.record(z.unknown()).optional().describe("Esri JSON geometry for spatial filter"),
|
|
8
|
-
geometryType: z
|
|
9
|
-
.enum(GEOMETRY_TYPES)
|
|
10
|
-
.optional()
|
|
11
|
-
.describe("Esri geometry type. If omitted, inferred from geometry when possible."),
|
|
12
|
-
spatialRel: z
|
|
13
|
-
.enum(["intersects", "contains", "within"])
|
|
14
|
-
.optional()
|
|
15
|
-
.describe("Spatial relationship (default: intersects)"),
|
|
15
|
+
...sourceRefFields,
|
|
16
|
+
...queryFilterFields,
|
|
16
17
|
});
|
|
17
18
|
export async function execute(client, input) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!hasExtent) {
|
|
33
|
-
// Some backends can return count-only payloads for empty extent queries.
|
|
34
|
-
if (count !== undefined) {
|
|
35
|
-
return jsonText({ extent: null, count });
|
|
36
|
-
}
|
|
37
|
-
throw new Error("Extent query did not return an extent payload.");
|
|
38
|
-
}
|
|
39
|
-
let extent;
|
|
40
|
-
if (response.extent === null) {
|
|
41
|
-
extent = null;
|
|
42
|
-
}
|
|
43
|
-
else if (typeof response.extent === "object" && !Array.isArray(response.extent)) {
|
|
44
|
-
// A JSON array is `typeof "object"` but is not a valid extent envelope.
|
|
45
|
-
extent = response.extent;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
throw new Error("Extent query returned an invalid extent payload.");
|
|
49
|
-
}
|
|
50
|
-
return jsonText({ extent, count });
|
|
19
|
+
return withCapabilityHonesty(async () => {
|
|
20
|
+
const resolved = resolveSource(client, input);
|
|
21
|
+
const query = toQuery(input, { protocol: resolved.descriptor.protocol, paginate: false });
|
|
22
|
+
const { extent, count } = await resolved.source.queryExtent(query);
|
|
23
|
+
return jsonText({
|
|
24
|
+
source: resolved.ref.ref,
|
|
25
|
+
protocol: resolved.descriptor.protocol,
|
|
26
|
+
extent: extent ?? null,
|
|
27
|
+
count: count ?? null,
|
|
28
|
+
// An extent that did not come from a server-side extent operation is an
|
|
29
|
+
// honest, but weaker, answer; name the capability so the caller can tell.
|
|
30
|
+
extentCapability: resolved.source.capabilities.has("queryExtent") ? "server" : "declared-extent",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
51
33
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { HonuaClient } from "@honua/sdk-js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* `honua_list_services` — the GeoServices-specific service catalog.
|
|
5
|
+
*
|
|
6
|
+
* Retained for existing clients; `honua_list_sources` is the protocol-neutral
|
|
7
|
+
* replacement (#1005), because "what services are under /rest/services?" is a
|
|
8
|
+
* question only an Esri endpoint can answer.
|
|
9
|
+
*/
|
|
3
10
|
export declare const schema: z.ZodObject<{
|
|
4
11
|
includeDetails: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
5
12
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-services.d.ts","sourceRoot":"","sources":["../../../src/tools/list-services.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,MAAM;;;;;;EAQjB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAI3C,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;;;;;GA+C9D"}
|
|
1
|
+
{"version":3,"file":"list-services.d.ts","sourceRoot":"","sources":["../../../src/tools/list-services.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AACH,eAAO,MAAM,MAAM;;;;;;EAQjB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAI3C,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;;;;;GA+C9D"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonText, mapWithConcurrency, metadataErrorText } from "../helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* `honua_list_services` — the GeoServices-specific service catalog.
|
|
5
|
+
*
|
|
6
|
+
* Retained for existing clients; `honua_list_sources` is the protocol-neutral
|
|
7
|
+
* replacement (#1005), because "what services are under /rest/services?" is a
|
|
8
|
+
* question only an Esri endpoint can answer.
|
|
9
|
+
*/
|
|
3
10
|
export const schema = z.object({
|
|
4
11
|
includeDetails: z
|
|
5
12
|
.boolean()
|
|
@@ -22,7 +29,7 @@ export async function execute(client, input) {
|
|
|
22
29
|
catalogAvailable: false,
|
|
23
30
|
services: [],
|
|
24
31
|
reason: `service catalog is not available on this target: ${err instanceof Error ? err.message : String(err)}`,
|
|
25
|
-
guidance: "No /rest/services directory here
|
|
32
|
+
guidance: "No /rest/services directory here — this tool only sees a GeoServices catalog. Call honua_list_sources, which probes every protocol family this endpoint may publish (OGC API Features included) and returns protocol-neutral source references.",
|
|
26
33
|
});
|
|
27
34
|
}
|
|
28
35
|
const services = (response.services ?? []).filter((s) => s.type === "FeatureServer");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HonuaClient } from "@honua/sdk-js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* `honua_list_sources` — protocol-neutral discovery (#1005).
|
|
5
|
+
*
|
|
6
|
+
* `honua_list_services` answers a GeoServices-only question ("what is in
|
|
7
|
+
* `/rest/services`?"). This tool answers the neutral one — "what sources can I
|
|
8
|
+
* query here, and how do I address them?" — and emits the exact
|
|
9
|
+
* `<protocol>:<address>` references every other tool accepts, so an agent never
|
|
10
|
+
* has to construct one.
|
|
11
|
+
*
|
|
12
|
+
* Each protocol family is probed independently and degrades on its own: an
|
|
13
|
+
* endpoint that serves OGC collections but has no `/rest/services` directory
|
|
14
|
+
* reports the GeoServices family as unavailable-with-reason and still returns
|
|
15
|
+
* its OGC sources. A family that is absent is never reported as "no sources".
|
|
16
|
+
*/
|
|
17
|
+
export declare const schema: z.ZodObject<{
|
|
18
|
+
protocol: z.ZodDefault<z.ZodOptional<z.ZodEnum<["auto", "geoservices", "ogc-features", "ogc-records"]>>>;
|
|
19
|
+
layout: z.ZodOptional<z.ZodEnum<["honua-facade", "ogc-api", "auto", "stac-api", "stac-static"]>>;
|
|
20
|
+
maxServices: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
protocol: "ogc-features" | "ogc-records" | "geoservices" | "auto";
|
|
23
|
+
maxServices: number;
|
|
24
|
+
layout?: "honua-facade" | "ogc-api" | "auto" | "stac-api" | "stac-static" | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
layout?: "honua-facade" | "ogc-api" | "auto" | "stac-api" | "stac-static" | undefined;
|
|
27
|
+
protocol?: "ogc-features" | "ogc-records" | "geoservices" | "auto" | undefined;
|
|
28
|
+
maxServices?: number | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
export type Input = z.infer<typeof schema>;
|
|
31
|
+
export declare function execute(client: HonuaClient, input: Input): Promise<{
|
|
32
|
+
content: Array<{
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
}>;
|
|
36
|
+
} | {
|
|
37
|
+
structuredContent: Record<string, unknown>;
|
|
38
|
+
isError: true;
|
|
39
|
+
content: Array<{
|
|
40
|
+
type: "text";
|
|
41
|
+
text: string;
|
|
42
|
+
}>;
|
|
43
|
+
}>;
|
|
44
|
+
//# sourceMappingURL=list-sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-sources.d.ts","sourceRoot":"","sources":["../../../src/tools/list-sources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;EAoBjB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AA6F3C,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK;;;;;;;;;;;;GAkC9D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { PROTOCOL_DEFAULT_CAPABILITIES } from "@honua/sdk-js/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { jsonText, mapWithConcurrency } from "../helpers.js";
|
|
4
|
+
import { withCapabilityHonesty } from "../neutral/errors.js";
|
|
5
|
+
import { SOURCE_LAYOUTS, ogcLayoutFor } from "../neutral/source-ref.js";
|
|
6
|
+
/**
|
|
7
|
+
* `honua_list_sources` — protocol-neutral discovery (#1005).
|
|
8
|
+
*
|
|
9
|
+
* `honua_list_services` answers a GeoServices-only question ("what is in
|
|
10
|
+
* `/rest/services`?"). This tool answers the neutral one — "what sources can I
|
|
11
|
+
* query here, and how do I address them?" — and emits the exact
|
|
12
|
+
* `<protocol>:<address>` references every other tool accepts, so an agent never
|
|
13
|
+
* has to construct one.
|
|
14
|
+
*
|
|
15
|
+
* Each protocol family is probed independently and degrades on its own: an
|
|
16
|
+
* endpoint that serves OGC collections but has no `/rest/services` directory
|
|
17
|
+
* reports the GeoServices family as unavailable-with-reason and still returns
|
|
18
|
+
* its OGC sources. A family that is absent is never reported as "no sources".
|
|
19
|
+
*/
|
|
20
|
+
export const schema = z.object({
|
|
21
|
+
protocol: z
|
|
22
|
+
.enum(["auto", "geoservices", "ogc-features", "ogc-records"])
|
|
23
|
+
.optional()
|
|
24
|
+
.default("auto")
|
|
25
|
+
.describe("Restrict discovery to one protocol family. `auto` (default) probes every family this server can list."),
|
|
26
|
+
layout: z
|
|
27
|
+
.enum(SOURCE_LAYOUTS)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Endpoint layout for the OGC families: "honua-facade" (default) or "ogc-api"/"auto" for a third-party OGC API root.'),
|
|
30
|
+
maxServices: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.positive()
|
|
34
|
+
.max(200)
|
|
35
|
+
.optional()
|
|
36
|
+
.default(25)
|
|
37
|
+
.describe("Maximum GeoServices services expanded into per-layer sources (each costs one metadata request)."),
|
|
38
|
+
});
|
|
39
|
+
const METADATA_CONCURRENCY = 8;
|
|
40
|
+
function capabilitiesFor(protocol) {
|
|
41
|
+
return [...PROTOCOL_DEFAULT_CAPABILITIES[protocol]].sort();
|
|
42
|
+
}
|
|
43
|
+
async function discoverGeoServices(client, maxServices) {
|
|
44
|
+
let response;
|
|
45
|
+
try {
|
|
46
|
+
response = await client.listServices();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return {
|
|
50
|
+
available: false,
|
|
51
|
+
reason: `no GeoServices catalog on this target: ${err instanceof Error ? err.message : String(err)}`,
|
|
52
|
+
sources: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const services = (response.services ?? []).filter((s) => s.type === "FeatureServer" || s.type === "MapServer");
|
|
56
|
+
const expanded = services.slice(0, maxServices);
|
|
57
|
+
const perService = await mapWithConcurrency(expanded, METADATA_CONCURRENCY, async (service) => {
|
|
58
|
+
const protocol = service.type === "MapServer" ? "geoservices-map-service" : "geoservices-feature-service";
|
|
59
|
+
try {
|
|
60
|
+
const meta = await client.getFeatureServiceMetadata(service.name);
|
|
61
|
+
const layers = [...(meta.layers ?? []), ...(meta.tables ?? [])];
|
|
62
|
+
return layers.map((layer) => ({
|
|
63
|
+
source: `${protocol}:${service.name}/${layer.id}`,
|
|
64
|
+
protocol,
|
|
65
|
+
title: layer.name ?? null,
|
|
66
|
+
capabilities: capabilitiesFor(protocol),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// The service exists but its metadata is unreadable; still emit the
|
|
71
|
+
// conventional layer-0 reference rather than dropping the service.
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
source: `${protocol}:${service.name}/0`,
|
|
75
|
+
protocol,
|
|
76
|
+
title: service.name,
|
|
77
|
+
capabilities: capabilitiesFor(protocol),
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return { available: true, sources: perService.flat() };
|
|
83
|
+
}
|
|
84
|
+
async function discoverOgc(client, family, layout) {
|
|
85
|
+
try {
|
|
86
|
+
const resolved = await ogcLayoutFor(client, layout);
|
|
87
|
+
const request = resolved ? { layout: resolved } : {};
|
|
88
|
+
const response = family === "ogc-records"
|
|
89
|
+
? await client.listOgcRecordCollections(request)
|
|
90
|
+
: await client.listOgcCollections(request);
|
|
91
|
+
return {
|
|
92
|
+
available: true,
|
|
93
|
+
sources: (response.collections ?? []).map((collection) => ({
|
|
94
|
+
source: `${family}:${collection.id}`,
|
|
95
|
+
protocol: family,
|
|
96
|
+
title: collection.title ?? collection.id,
|
|
97
|
+
capabilities: capabilitiesFor(family),
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return {
|
|
103
|
+
available: false,
|
|
104
|
+
reason: `no ${family} collections on this target: ${err instanceof Error ? err.message : String(err)}`,
|
|
105
|
+
sources: [],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function execute(client, input) {
|
|
110
|
+
return withCapabilityHonesty(async () => {
|
|
111
|
+
const families = {};
|
|
112
|
+
if (input.protocol === "auto" || input.protocol === "geoservices") {
|
|
113
|
+
families.geoservices = await discoverGeoServices(client, input.maxServices);
|
|
114
|
+
}
|
|
115
|
+
if (input.protocol === "auto" || input.protocol === "ogc-features") {
|
|
116
|
+
families["ogc-features"] = await discoverOgc(client, "ogc-features", input.layout);
|
|
117
|
+
}
|
|
118
|
+
if (input.protocol === "ogc-records") {
|
|
119
|
+
families["ogc-records"] = await discoverOgc(client, "ogc-records", input.layout);
|
|
120
|
+
}
|
|
121
|
+
const sources = Object.values(families).flatMap((family) => family.sources);
|
|
122
|
+
return jsonText({
|
|
123
|
+
sources,
|
|
124
|
+
sourceCount: sources.length,
|
|
125
|
+
families: Object.fromEntries(Object.entries(families).map(([name, outcome]) => [
|
|
126
|
+
name,
|
|
127
|
+
{
|
|
128
|
+
available: outcome.available,
|
|
129
|
+
...(outcome.reason ? { reason: outcome.reason } : {}),
|
|
130
|
+
count: outcome.sources.length,
|
|
131
|
+
},
|
|
132
|
+
])),
|
|
133
|
+
guidance: sources.length > 0
|
|
134
|
+
? "Pass a `source` value verbatim to honua_describe_layer / honua_query_features / honua_count_features / honua_get_extent / honua_statistics."
|
|
135
|
+
: 'No source catalog is published here. Address a known source directly, e.g. source="ogc-features:<collectionId>", "stac:<collectionId>", "wfs:<typeName>", "odata:<entitySet>", or "geoservices-feature-service:<serviceId>/<layerId>".',
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|