@honua/mcp-server 0.1.2-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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Query, SortSpec } from "@honua/sdk-js/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { GEOMETRY_TYPES } from "../helpers.js";
|
|
4
|
+
import { SPATIAL_PREDICATES, temporalSchema } from "./filter.js";
|
|
5
|
+
/**
|
|
6
|
+
* PROTOCOL-NEUTRAL query vocabulary for the MCP tool contract (#1005).
|
|
7
|
+
*
|
|
8
|
+
* Everything here lowers onto the SDK's stable `Query` envelope, which each
|
|
9
|
+
* protocol adapter translates into its own dialect. Nothing in the vocabulary
|
|
10
|
+
* is Esri-shaped: the filter is the typed semantic filter, geometry is GeoJSON
|
|
11
|
+
* (or a bbox), the spatial predicate is the neutral one, sorting is
|
|
12
|
+
* `field`/`direction`, and paging is `limit`/`offset`.
|
|
13
|
+
*
|
|
14
|
+
* Two deprecated compatibility inputs remain so existing clients keep working:
|
|
15
|
+
* `where` (source-native filter text, whose language depends on the backend)
|
|
16
|
+
* and `geometryType` (an Esri geometry-type tag for Esri-JSON geometry). Both
|
|
17
|
+
* are optional; neither is required by any tool.
|
|
18
|
+
*/
|
|
19
|
+
/** Filter + spatial + temporal fields shared by every query-family tool. */
|
|
20
|
+
export declare const queryFilterFields: {
|
|
21
|
+
readonly filter: z.ZodOptional<z.ZodType<import("./filter.js").FilterNode, z.ZodTypeDef, import("./filter.js").FilterNode>>;
|
|
22
|
+
readonly where: z.ZodOptional<z.ZodString>;
|
|
23
|
+
readonly bbox: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
24
|
+
readonly bboxSrid: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
readonly geometry: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
26
|
+
readonly geometryType: z.ZodOptional<z.ZodEnum<["esriGeometryPoint", "esriGeometryPolyline", "esriGeometryPolygon", "esriGeometryEnvelope", "esriGeometryMultipoint"]>>;
|
|
27
|
+
readonly spatialRel: z.ZodOptional<z.ZodEnum<["intersects", "contains", "within", "crosses", "touches", "overlaps", "bbox-intersects"]>>;
|
|
28
|
+
readonly temporal: z.ZodOptional<z.ZodObject<{
|
|
29
|
+
instant: z.ZodOptional<z.ZodString>;
|
|
30
|
+
start: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
end: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
field: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
field?: string | undefined;
|
|
35
|
+
instant?: string | undefined;
|
|
36
|
+
start?: string | null | undefined;
|
|
37
|
+
end?: string | null | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
field?: string | undefined;
|
|
40
|
+
instant?: string | undefined;
|
|
41
|
+
start?: string | null | undefined;
|
|
42
|
+
end?: string | null | undefined;
|
|
43
|
+
}>>;
|
|
44
|
+
};
|
|
45
|
+
/** Projection / ordering / paging fields for the feature-returning tools. */
|
|
46
|
+
export declare const queryShapeFields: {
|
|
47
|
+
readonly outFields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
48
|
+
readonly orderBy: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodObject<{
|
|
49
|
+
field: z.ZodString;
|
|
50
|
+
direction: z.ZodOptional<z.ZodEnum<["asc", "desc"]>>;
|
|
51
|
+
}, "strip", z.ZodTypeAny, {
|
|
52
|
+
field: string;
|
|
53
|
+
direction?: "asc" | "desc" | undefined;
|
|
54
|
+
}, {
|
|
55
|
+
field: string;
|
|
56
|
+
direction?: "asc" | "desc" | undefined;
|
|
57
|
+
}>, "many">, z.ZodString]>>;
|
|
58
|
+
readonly limit: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
readonly offset: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
readonly returnGeometry: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
61
|
+
readonly geometryFormat: z.ZodOptional<z.ZodEnum<["geojson", "esri-json"]>>;
|
|
62
|
+
readonly outSr: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
63
|
+
};
|
|
64
|
+
export interface NeutralQueryInput {
|
|
65
|
+
filter?: unknown;
|
|
66
|
+
where?: string | undefined;
|
|
67
|
+
bbox?: number[] | undefined;
|
|
68
|
+
bboxSrid?: number | undefined;
|
|
69
|
+
geometry?: Record<string, unknown> | undefined;
|
|
70
|
+
geometryType?: (typeof GEOMETRY_TYPES)[number] | undefined;
|
|
71
|
+
spatialRel?: (typeof SPATIAL_PREDICATES)[number] | undefined;
|
|
72
|
+
temporal?: z.infer<typeof temporalSchema> | undefined;
|
|
73
|
+
outFields?: string[] | undefined;
|
|
74
|
+
orderBy?: string | {
|
|
75
|
+
field: string;
|
|
76
|
+
direction?: "asc" | "desc";
|
|
77
|
+
}[] | undefined;
|
|
78
|
+
limit?: number | undefined;
|
|
79
|
+
offset?: number | undefined;
|
|
80
|
+
returnGeometry?: boolean | undefined;
|
|
81
|
+
outSr?: string | number | undefined;
|
|
82
|
+
}
|
|
83
|
+
/** Parse the legacy `"NAME DESC,VALUE ASC"` order string into canonical sort specs. */
|
|
84
|
+
export declare function parseOrderBy(orderBy: NeutralQueryInput["orderBy"]): SortSpec[] | undefined;
|
|
85
|
+
export interface ToQueryOptions {
|
|
86
|
+
/** Protocol of the resolved source; drives protocol-quirk compensation only. */
|
|
87
|
+
readonly protocol: string;
|
|
88
|
+
/** Apply the default page size / cap. Off for aggregate + extent queries. */
|
|
89
|
+
readonly paginate?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Lower the neutral tool input onto the SDK's canonical `Query`.
|
|
93
|
+
*
|
|
94
|
+
* The one protocol-specific compensation is `outFields`: a GeoServices
|
|
95
|
+
* `query` with no `outFields` returns OBJECTID only, whereas every other
|
|
96
|
+
* protocol returns all properties. Requesting `*` for the GeoServices family
|
|
97
|
+
* keeps "omit outFields ⇒ all fields" true across protocols rather than
|
|
98
|
+
* leaking an Esri default into the neutral contract.
|
|
99
|
+
*/
|
|
100
|
+
export declare function toQuery(input: NeutralQueryInput, options: ToQueryOptions): Query;
|
|
101
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/neutral/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAyB,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAc,MAAM,eAAe,CAAC;AAC3D,OAAO,EAEL,kBAAkB,EAKlB,cAAc,EAIf,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;GAaG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;CA4BpB,CAAC;AAOX,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;CAmBnB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAC3D,UAAU,CAAC,EAAE,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,GAAG,SAAS,CAAC;IACtD,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,EAAE,GAAG,SAAS,CAAC;IAC/E,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,QAAQ,EAAE,GAAG,SAAS,CAmB1F;AA2CD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,cAAc,GAAG,KAAK,CAwChF"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { GEOMETRY_TYPES, clampLimit } from "../helpers.js";
|
|
3
|
+
import { FilterInputError, SPATIAL_PREDICATES, andFilters, bboxToOperand, filterSchema, geometrySchema, temporalSchema, toGeometryOperand, toQueryFilter, toTemporalFilter, } from "./filter.js";
|
|
4
|
+
import { isGeoServicesProtocol } from "./source-ref.js";
|
|
5
|
+
/**
|
|
6
|
+
* PROTOCOL-NEUTRAL query vocabulary for the MCP tool contract (#1005).
|
|
7
|
+
*
|
|
8
|
+
* Everything here lowers onto the SDK's stable `Query` envelope, which each
|
|
9
|
+
* protocol adapter translates into its own dialect. Nothing in the vocabulary
|
|
10
|
+
* is Esri-shaped: the filter is the typed semantic filter, geometry is GeoJSON
|
|
11
|
+
* (or a bbox), the spatial predicate is the neutral one, sorting is
|
|
12
|
+
* `field`/`direction`, and paging is `limit`/`offset`.
|
|
13
|
+
*
|
|
14
|
+
* Two deprecated compatibility inputs remain so existing clients keep working:
|
|
15
|
+
* `where` (source-native filter text, whose language depends on the backend)
|
|
16
|
+
* and `geometryType` (an Esri geometry-type tag for Esri-JSON geometry). Both
|
|
17
|
+
* are optional; neither is required by any tool.
|
|
18
|
+
*/
|
|
19
|
+
/** Filter + spatial + temporal fields shared by every query-family tool. */
|
|
20
|
+
export const queryFilterFields = {
|
|
21
|
+
filter: filterSchema
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Typed, protocol-neutral filter. Nodes are {"op": …}: comparison (eq/ne/lt/lte/gt/gte with field+value), in (field+values), between (field+lower+upper), is-null/is-not-null (field), like (field+pattern[+caseSensitive]), and/or (args), not (arg), spatial (intersects/contains/within/crosses/touches/overlaps/bbox-intersects with geometry or bbox), temporal (before/after/during/time-intersects with field+value). Compiles to SQL-92, CQL2, FES 2.0, or OData depending on the backend, and fails with a named capability error rather than silently dropping a construct.'),
|
|
24
|
+
where: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("[DEPRECATED — use `filter`] Source-native filter text. Its language depends on the backend (GeoServices SQL-92, CQL2, OData); it is not protocol neutral."),
|
|
28
|
+
bbox: z.array(z.number()).length(4).optional().describe("Spatial filter envelope [minX, minY, maxX, maxY]."),
|
|
29
|
+
bboxSrid: z.number().int().optional().describe("Spatial reference (SRID/WKID) of the bbox ordinates (default 4326)."),
|
|
30
|
+
geometry: geometrySchema
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Spatial filter geometry as GeoJSON (RFC 7946). Esri-JSON geometry is still accepted for compatibility and is converted on the way in."),
|
|
33
|
+
geometryType: z
|
|
34
|
+
.enum(GEOMETRY_TYPES)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("[DEPRECATED — send GeoJSON] Esri geometry type tag, used only to disambiguate Esri-JSON geometry."),
|
|
37
|
+
spatialRel: z
|
|
38
|
+
.enum(SPATIAL_PREDICATES)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Protocol-neutral spatial predicate applied to `geometry`/`bbox` (default: intersects)."),
|
|
41
|
+
temporal: temporalSchema.optional(),
|
|
42
|
+
};
|
|
43
|
+
const sortSpecSchema = z.object({
|
|
44
|
+
field: z.string().min(1),
|
|
45
|
+
direction: z.enum(["asc", "desc"]).optional(),
|
|
46
|
+
});
|
|
47
|
+
/** Projection / ordering / paging fields for the feature-returning tools. */
|
|
48
|
+
export const queryShapeFields = {
|
|
49
|
+
outFields: z.array(z.string()).optional().describe("Fields to return; defaults to all."),
|
|
50
|
+
orderBy: z
|
|
51
|
+
.union([z.array(sortSpecSchema), z.string()])
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Sort order as [{"field":"NAME","direction":"desc"}], or the legacy string form "NAME DESC,VALUE ASC".'),
|
|
54
|
+
limit: z.number().int().positive().optional().describe("Max features to return (default 100, max 2000)."),
|
|
55
|
+
offset: z.number().int().nonnegative().optional().describe("Number of features to skip (for pagination)."),
|
|
56
|
+
returnGeometry: z.boolean().optional().default(false).describe("Include geometry in results (default: false)."),
|
|
57
|
+
geometryFormat: z
|
|
58
|
+
.enum(["geojson", "esri-json"])
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Geometry encoding of returned features. Defaults to `geojson` for neutrally addressed sources, and to `esri-json` when the deprecated serviceId/layerId pair was used (preserving legacy client output)."),
|
|
61
|
+
outSr: z
|
|
62
|
+
.union([z.string(), z.number()])
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Output spatial reference (WKID or CRS identifier) for returned geometry."),
|
|
65
|
+
};
|
|
66
|
+
/** Parse the legacy `"NAME DESC,VALUE ASC"` order string into canonical sort specs. */
|
|
67
|
+
export function parseOrderBy(orderBy) {
|
|
68
|
+
if (orderBy === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (Array.isArray(orderBy)) {
|
|
71
|
+
return orderBy.length > 0
|
|
72
|
+
? orderBy.map((spec) => ({ field: spec.field, direction: spec.direction ?? "asc" }))
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
const specs = orderBy
|
|
76
|
+
.split(",")
|
|
77
|
+
.map((part) => part.trim())
|
|
78
|
+
.filter((part) => part.length > 0)
|
|
79
|
+
.map((part) => {
|
|
80
|
+
const [field, direction] = part.split(/\s+/);
|
|
81
|
+
return {
|
|
82
|
+
field,
|
|
83
|
+
direction: (direction ?? "asc").toLowerCase() === "desc" ? "desc" : "asc",
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return specs.length > 0 ? specs : undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Lower the spatial constraint onto whichever canonical channel the widest set
|
|
90
|
+
* of protocols can serve.
|
|
91
|
+
*
|
|
92
|
+
* An envelope tested with `intersects` is the one spatial constraint every
|
|
93
|
+
* protocol expresses natively — it is the OGC API Features / STAC `bbox`
|
|
94
|
+
* parameter (Part 1, no filtering extension required) and a plain GeoServices
|
|
95
|
+
* geometry parameter — so it travels as `Query.spatialFilter`. Anything richer
|
|
96
|
+
* (a real polygon, `within`, `crosses`, …) becomes a typed spatial filter node,
|
|
97
|
+
* which compiles to CQL2 / FES / SQL-92 where supported and fails closed,
|
|
98
|
+
* naming the construct, where it is not.
|
|
99
|
+
*/
|
|
100
|
+
function spatialConstraint(input) {
|
|
101
|
+
if (input.geometry && input.bbox) {
|
|
102
|
+
throw new FilterInputError("Pass either `geometry` or `bbox` for the spatial filter, not both.");
|
|
103
|
+
}
|
|
104
|
+
const operand = input.bbox
|
|
105
|
+
? bboxToOperand(input.bbox, input.bboxSrid)
|
|
106
|
+
: input.geometry
|
|
107
|
+
? toGeometryOperand(input.geometry, input.geometryType, input.bboxSrid)
|
|
108
|
+
: undefined;
|
|
109
|
+
if (!operand)
|
|
110
|
+
return {};
|
|
111
|
+
const predicate = input.spatialRel ?? "intersects";
|
|
112
|
+
const isPortableEnvelope = operand.geometryType === "esriGeometryEnvelope" && (predicate === "intersects" || predicate === "bbox-intersects");
|
|
113
|
+
if (isPortableEnvelope) {
|
|
114
|
+
return {
|
|
115
|
+
spatialFilter: {
|
|
116
|
+
geometry: operand.geometry,
|
|
117
|
+
geometryType: operand.geometryType,
|
|
118
|
+
spatialRel: predicate === "intersects" ? "esriSpatialRelIntersects" : "esriSpatialRelEnvelopeIntersects",
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { filterNode: { kind: "spatial", operator: predicate, geometry: operand } };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Lower the neutral tool input onto the SDK's canonical `Query`.
|
|
126
|
+
*
|
|
127
|
+
* The one protocol-specific compensation is `outFields`: a GeoServices
|
|
128
|
+
* `query` with no `outFields` returns OBJECTID only, whereas every other
|
|
129
|
+
* protocol returns all properties. Requesting `*` for the GeoServices family
|
|
130
|
+
* keeps "omit outFields ⇒ all fields" true across protocols rather than
|
|
131
|
+
* leaking an Esri default into the neutral contract.
|
|
132
|
+
*/
|
|
133
|
+
export function toQuery(input, options) {
|
|
134
|
+
const conjuncts = [];
|
|
135
|
+
if (input.filter !== undefined && input.filter !== null) {
|
|
136
|
+
conjuncts.push(toQueryFilter(filterSchema.parse(input.filter)));
|
|
137
|
+
}
|
|
138
|
+
const spatial = spatialConstraint(input);
|
|
139
|
+
if (spatial.filterNode)
|
|
140
|
+
conjuncts.push(spatial.filterNode);
|
|
141
|
+
const query = {};
|
|
142
|
+
const filter = andFilters(conjuncts);
|
|
143
|
+
if (filter)
|
|
144
|
+
query.filter = filter;
|
|
145
|
+
if (spatial.spatialFilter)
|
|
146
|
+
query.spatialFilter = spatial.spatialFilter;
|
|
147
|
+
if (input.where !== undefined && input.where.trim().length > 0)
|
|
148
|
+
query.where = input.where;
|
|
149
|
+
if (input.temporal)
|
|
150
|
+
query.temporalFilter = toTemporalFilter(input.temporal);
|
|
151
|
+
const outFields = input.outFields && input.outFields.length > 0
|
|
152
|
+
? input.outFields
|
|
153
|
+
: isGeoServicesProtocol(options.protocol)
|
|
154
|
+
? ["*"]
|
|
155
|
+
: undefined;
|
|
156
|
+
if (outFields)
|
|
157
|
+
query.outFields = outFields;
|
|
158
|
+
const orderBy = parseOrderBy(input.orderBy);
|
|
159
|
+
if (orderBy)
|
|
160
|
+
query.orderBy = orderBy;
|
|
161
|
+
if (options.paginate !== false) {
|
|
162
|
+
const pagination = { limit: clampLimit(input.limit) };
|
|
163
|
+
if (input.offset !== undefined)
|
|
164
|
+
pagination.offset = input.offset;
|
|
165
|
+
query.pagination = pagination;
|
|
166
|
+
}
|
|
167
|
+
else if (input.offset !== undefined || input.limit !== undefined) {
|
|
168
|
+
const pagination = {};
|
|
169
|
+
if (input.limit !== undefined)
|
|
170
|
+
pagination.limit = clampLimit(input.limit);
|
|
171
|
+
if (input.offset !== undefined)
|
|
172
|
+
pagination.offset = input.offset;
|
|
173
|
+
query.pagination = pagination;
|
|
174
|
+
}
|
|
175
|
+
if (input.returnGeometry !== undefined)
|
|
176
|
+
query.returnGeometry = input.returnGeometry;
|
|
177
|
+
if (input.outSr !== undefined)
|
|
178
|
+
query.outSr = input.outSr;
|
|
179
|
+
return query;
|
|
180
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DegradedReason, Result } from "@honua/sdk-js/contract";
|
|
2
|
+
/**
|
|
3
|
+
* PROTOCOL-NEUTRAL result projection (#1005).
|
|
4
|
+
*
|
|
5
|
+
* A `Result` carries whatever geometry encoding its protocol produced —
|
|
6
|
+
* Esri-JSON from GeoServices, GeoJSON from OGC API Features / STAC. Returning
|
|
7
|
+
* that raw makes the tool output protocol-dependent, so the neutral contract
|
|
8
|
+
* normalizes to GeoJSON and says which encoding it used.
|
|
9
|
+
*
|
|
10
|
+
* `Result.degraded` is surfaced verbatim: when a protocol served a request a
|
|
11
|
+
* different way than asked (client-side aggregation on OGC, for example), the
|
|
12
|
+
* answer says so instead of quietly presenting a weaker number as authoritative.
|
|
13
|
+
*/
|
|
14
|
+
export type GeometryFormat = "geojson" | "esri-json";
|
|
15
|
+
/** Re-encode one feature geometry into the requested format. */
|
|
16
|
+
export declare function projectGeometry(geometry: Record<string, unknown> | null | undefined, format: GeometryFormat): Record<string, unknown> | null;
|
|
17
|
+
/** Normalize `Result.degraded` for tool output; `undefined` when nothing degraded. */
|
|
18
|
+
export declare function projectDegraded(degraded: readonly DegradedReason[] | undefined): {
|
|
19
|
+
capability: import("@honua/sdk-js/contract").Capability;
|
|
20
|
+
reason: string;
|
|
21
|
+
protocol: import("@honua/sdk-js/contract").Protocol | null;
|
|
22
|
+
sourceId: string | null;
|
|
23
|
+
}[] | undefined;
|
|
24
|
+
export interface ProjectFeaturesOptions {
|
|
25
|
+
readonly returnGeometry: boolean;
|
|
26
|
+
readonly geometryFormat: GeometryFormat;
|
|
27
|
+
}
|
|
28
|
+
/** Project canonical features onto the tool's neutral feature shape. */
|
|
29
|
+
export declare function projectFeatures(result: Result, options: ProjectFeaturesOptions): {
|
|
30
|
+
geometry?: Record<string, unknown> | undefined;
|
|
31
|
+
attributes: Record<string, unknown>;
|
|
32
|
+
}[];
|
|
33
|
+
//# sourceMappingURL=result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../../src/neutral/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAGrE;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAMrD,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EACpD,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAMhC;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS;;;;;gBAQ9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB;;;IAU9E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { esriToGeoJson } from "@honua/sdk-js/geometry";
|
|
2
|
+
function looksGeoJson(geometry) {
|
|
3
|
+
return typeof geometry.type === "string" && "coordinates" in geometry;
|
|
4
|
+
}
|
|
5
|
+
/** Re-encode one feature geometry into the requested format. */
|
|
6
|
+
export function projectGeometry(geometry, format) {
|
|
7
|
+
if (!geometry)
|
|
8
|
+
return null;
|
|
9
|
+
if (format === "esri-json")
|
|
10
|
+
return geometry;
|
|
11
|
+
if (looksGeoJson(geometry))
|
|
12
|
+
return geometry;
|
|
13
|
+
const converted = esriToGeoJson(geometry);
|
|
14
|
+
return converted ? converted : null;
|
|
15
|
+
}
|
|
16
|
+
/** Normalize `Result.degraded` for tool output; `undefined` when nothing degraded. */
|
|
17
|
+
export function projectDegraded(degraded) {
|
|
18
|
+
if (!degraded || degraded.length === 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
return degraded.map((reason) => ({
|
|
21
|
+
capability: reason.capability,
|
|
22
|
+
reason: reason.reason,
|
|
23
|
+
protocol: reason.protocol ?? null,
|
|
24
|
+
sourceId: reason.sourceId ?? null,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/** Project canonical features onto the tool's neutral feature shape. */
|
|
28
|
+
export function projectFeatures(result, options) {
|
|
29
|
+
return result.features.map((feature) => {
|
|
30
|
+
const geometry = options.returnGeometry
|
|
31
|
+
? projectGeometry(feature.geometry, options.geometryFormat)
|
|
32
|
+
: null;
|
|
33
|
+
return {
|
|
34
|
+
attributes: feature.attributes,
|
|
35
|
+
...(geometry ? { geometry } : {}),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { HonuaClient } from "@honua/sdk-js";
|
|
2
|
+
import type { CapabilityAwareSource, SourceDescriptor, SourceLocator } from "@honua/sdk-js/contract";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* PROTOCOL-NEUTRAL source addressing for the MCP tool contract (issue #1005).
|
|
6
|
+
*
|
|
7
|
+
* The tool surface used to address data the way GeoServices does — a required
|
|
8
|
+
* `serviceId` string plus a required numeric `layerId`. That vocabulary is
|
|
9
|
+
* meaningless on OGC API Features, STAC, WFS, and OData, so a "vendor-neutral"
|
|
10
|
+
* server whose only addressing mode is Esri-shaped concedes the claim on
|
|
11
|
+
* inspection.
|
|
12
|
+
*
|
|
13
|
+
* A source is now addressed the way the SDK addresses one: a
|
|
14
|
+
* `SourceDescriptor` (protocol + locator) resolved into a canonical
|
|
15
|
+
* `Source` through `createDataset`. Over the wire that descriptor is carried as
|
|
16
|
+
* one compact string:
|
|
17
|
+
*
|
|
18
|
+
* ```text
|
|
19
|
+
* <protocol>:<address>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* | protocol | address form | example |
|
|
23
|
+
* | ------------------------------- | --------------------- | -------------------------------------- |
|
|
24
|
+
* | `geoservices-feature-service` | `<serviceId>/<layer>` | `geoservices-feature-service:Parks/0` |
|
|
25
|
+
* | `geoservices-map-service` | `<serviceId>/<layer>` | `geoservices-map-service:Basemap/2` |
|
|
26
|
+
* | `ogc-features` | `<collectionId>` | `ogc-features:hotels` |
|
|
27
|
+
* | `ogc-records` | `<collectionId>` | `ogc-records:catalog` |
|
|
28
|
+
* | `stac` | `<collectionId>` | `stac:sentinel-2-l2a` |
|
|
29
|
+
* | `wfs` | `<typeName>` | `wfs:topp:states` |
|
|
30
|
+
* | `odata` | `<entitySet>` | `odata:People` |
|
|
31
|
+
*
|
|
32
|
+
* `honua_list_sources` emits exactly these strings, so the normal agent flow is
|
|
33
|
+
* discover-then-address with no parsing on the client side. The reference is
|
|
34
|
+
* deliberately fail-closed: an unrecognized protocol token is refused with the
|
|
35
|
+
* accepted forms rather than guessed at, because guessing "which protocol did
|
|
36
|
+
* they mean" is how a neutral surface silently becomes a single-vendor one.
|
|
37
|
+
*
|
|
38
|
+
* The legacy `serviceId` + `layerId` pair is still accepted (see
|
|
39
|
+
* {@link legacyGeoServicesRef}) but is deprecated and is no longer *required* by
|
|
40
|
+
* any schema.
|
|
41
|
+
*/
|
|
42
|
+
/** Protocols reachable through a `<protocol>:<address>` source reference. */
|
|
43
|
+
export declare const SOURCE_REF_PROTOCOLS: readonly ["geoservices-feature-service", "geoservices-map-service", "ogc-features", "ogc-records", "stac", "wfs", "odata"];
|
|
44
|
+
export type SourceRefProtocol = (typeof SOURCE_REF_PROTOCOLS)[number];
|
|
45
|
+
/** Endpoint layouts an OGC API / STAC source may be published under. */
|
|
46
|
+
export declare const SOURCE_LAYOUTS: readonly ["honua-facade", "ogc-api", "auto", "stac-api", "stac-static"];
|
|
47
|
+
export type SourceLayout = (typeof SOURCE_LAYOUTS)[number];
|
|
48
|
+
/** A parsed, validated protocol-neutral source reference. */
|
|
49
|
+
export interface ParsedSourceRef {
|
|
50
|
+
/** Canonical `<protocol>:<address>` form (aliases normalized). */
|
|
51
|
+
readonly ref: string;
|
|
52
|
+
readonly protocol: SourceRefProtocol;
|
|
53
|
+
readonly address: string;
|
|
54
|
+
readonly layout?: SourceLayout;
|
|
55
|
+
}
|
|
56
|
+
export declare class SourceRefError extends Error {
|
|
57
|
+
readonly code = "INVALID_SOURCE_REFERENCE";
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a `<protocol>:<address>` source reference. Fails closed: an omitted or
|
|
62
|
+
* unrecognized protocol token is refused with the accepted forms instead of
|
|
63
|
+
* being inferred, so a mistyped reference can never silently resolve against
|
|
64
|
+
* the wrong protocol.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseSourceRef(raw: string, layout?: SourceLayout): ParsedSourceRef;
|
|
67
|
+
/** Build the canonical reference for the deprecated GeoServices `serviceId`/`layerId` pair. */
|
|
68
|
+
export declare function legacyGeoServicesRef(serviceId: string, layerId: number): string;
|
|
69
|
+
/** Project a parsed reference onto the SDK's protocol-neutral `SourceLocator`. */
|
|
70
|
+
export declare function toSourceLocator(ref: ParsedSourceRef, baseUrl: string): SourceLocator;
|
|
71
|
+
/** Build the `SourceDescriptor` a reference denotes, without contacting the endpoint. */
|
|
72
|
+
export declare function toSourceDescriptor(ref: ParsedSourceRef, baseUrl: string): SourceDescriptor;
|
|
73
|
+
/** A resolved source plus the neutral identity it was addressed by. */
|
|
74
|
+
export interface ResolvedSource {
|
|
75
|
+
readonly ref: ParsedSourceRef;
|
|
76
|
+
readonly descriptor: SourceDescriptor;
|
|
77
|
+
readonly source: CapabilityAwareSource;
|
|
78
|
+
/** True when the caller used the deprecated `serviceId`/`layerId` pair. */
|
|
79
|
+
readonly legacyAddressing: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Zod fields every source-addressed tool mixes into its input schema.
|
|
83
|
+
*
|
|
84
|
+
* `source` is the protocol-neutral reference; `serviceId`/`layerId` remain as
|
|
85
|
+
* OPTIONAL, deprecated compatibility inputs so existing MCP clients keep
|
|
86
|
+
* working. No Esri-only field is required by any tool schema.
|
|
87
|
+
*/
|
|
88
|
+
export declare const sourceRefFields: {
|
|
89
|
+
readonly source: z.ZodOptional<z.ZodString>;
|
|
90
|
+
readonly layout: z.ZodOptional<z.ZodEnum<["honua-facade", "ogc-api", "auto", "stac-api", "stac-static"]>>;
|
|
91
|
+
readonly serviceId: z.ZodOptional<z.ZodString>;
|
|
92
|
+
readonly layerId: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
};
|
|
94
|
+
export type SourceRefInput = {
|
|
95
|
+
source?: string | undefined;
|
|
96
|
+
layout?: SourceLayout | undefined;
|
|
97
|
+
serviceId?: string | undefined;
|
|
98
|
+
layerId?: number | undefined;
|
|
99
|
+
};
|
|
100
|
+
/** Resolve the neutral reference an input addresses (without building a `Source`). */
|
|
101
|
+
export declare function resolveSourceRef(input: SourceRefInput): {
|
|
102
|
+
ref: ParsedSourceRef;
|
|
103
|
+
legacyAddressing: boolean;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Resolve an input onto a canonical `Source`.
|
|
107
|
+
*
|
|
108
|
+
* The dataset runs under the `degraded` capability policy so a protocol that
|
|
109
|
+
* cannot serve a request first-party (OGC API Features has no server-side
|
|
110
|
+
* aggregation, for example) answers with an explicit `Result.degraded` reason
|
|
111
|
+
* instead of failing outright. Capabilities the protocol cannot serve at all
|
|
112
|
+
* still throw `HonuaCapabilityNotSupportedError`, which the tool layer turns
|
|
113
|
+
* into a structured tool error — never a silent empty result.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveSource(client: HonuaClient, input: SourceRefInput): ResolvedSource;
|
|
116
|
+
/** True when a resolved source speaks a GeoServices (Esri) protocol. */
|
|
117
|
+
export declare function isGeoServicesProtocol(protocol: string): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the OGC API Features endpoint layout a metadata call must use.
|
|
120
|
+
*
|
|
121
|
+
* `honua-facade` (the default) needs no resolution; a third-party OGC API root
|
|
122
|
+
* is discovered from its landing page once and cached on the client. STAC
|
|
123
|
+
* layouts are not OGC API Features layouts and resolve to `undefined`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function ogcLayoutFor(client: HonuaClient, layout: SourceLayout | undefined): Promise<import("../../../dist/src/core/types.js").OgcEndpointLayout> | Promise<undefined>;
|
|
126
|
+
//# sourceMappingURL=source-ref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-ref.d.ts","sourceRoot":"","sources":["../../../src/neutral/source-ref.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAY,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE/G,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,4HAQvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAetE,wEAAwE;AACxE,eAAO,MAAM,cAAc,yEAA0E,CAAC;AAEtG,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;CAChC;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,8BAA8B;gBAE/B,OAAO,EAAE,MAAM;CAI5B;AAwBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,eAAe,CA0BlF;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/E;AAiBD,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAiBpF;AAED,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAO1F;AAED,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;CAyBlB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B,CAAC;AAEF,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,GAAG,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,CAuB3G;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,GAAG,cAAc,CAiCxF;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,SAAS,6FAKjF"}
|