@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.
Files changed (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +202 -7
  3. package/dist/src/certification/census-fixture-client.d.ts.map +1 -1
  4. package/dist/src/certification/census-fixture-client.js +5 -0
  5. package/dist/src/certification/certifier.d.ts +1 -1
  6. package/dist/src/certification/certifier.d.ts.map +1 -1
  7. package/dist/src/certification/certifier.js +28 -20
  8. package/dist/src/certification/cli.js +1 -1
  9. package/dist/src/certification/cql2.d.ts +24 -0
  10. package/dist/src/certification/cql2.d.ts.map +1 -0
  11. package/dist/src/certification/cql2.js +354 -0
  12. package/dist/src/certification/fixtures.d.ts +8 -0
  13. package/dist/src/certification/fixtures.d.ts.map +1 -1
  14. package/dist/src/certification/fixtures.js +26 -10
  15. package/dist/src/certification/ogc-data.d.ts +30 -0
  16. package/dist/src/certification/ogc-data.d.ts.map +1 -0
  17. package/dist/src/certification/ogc-data.js +726 -0
  18. package/dist/src/certification/ogc-fixture-client.d.ts +4 -0
  19. package/dist/src/certification/ogc-fixture-client.d.ts.map +1 -0
  20. package/dist/src/certification/ogc-fixture-client.js +190 -0
  21. package/dist/src/certification/target.d.ts +10 -1
  22. package/dist/src/certification/target.d.ts.map +1 -1
  23. package/dist/src/certification/target.js +45 -2
  24. package/dist/src/eval/cli.js +6 -3
  25. package/dist/src/eval/corpus.d.ts +7 -0
  26. package/dist/src/eval/corpus.d.ts.map +1 -1
  27. package/dist/src/eval/corpus.js +12 -0
  28. package/dist/src/eval/ogc-corpus.d.ts +3 -0
  29. package/dist/src/eval/ogc-corpus.d.ts.map +1 -0
  30. package/dist/src/eval/ogc-corpus.js +437 -0
  31. package/dist/src/eval/runner.d.ts +6 -0
  32. package/dist/src/eval/runner.d.ts.map +1 -1
  33. package/dist/src/eval/runner.js +21 -7
  34. package/dist/src/helpers.d.ts +8 -13
  35. package/dist/src/helpers.d.ts.map +1 -1
  36. package/dist/src/helpers.js +7 -35
  37. package/dist/src/index.d.ts +14 -7
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +24 -15
  40. package/dist/src/neutral/errors.d.ts +73 -0
  41. package/dist/src/neutral/errors.d.ts.map +1 -0
  42. package/dist/src/neutral/errors.js +112 -0
  43. package/dist/src/neutral/filter.d.ts +108 -0
  44. package/dist/src/neutral/filter.d.ts.map +1 -0
  45. package/dist/src/neutral/filter.js +271 -0
  46. package/dist/src/neutral/query.d.ts +101 -0
  47. package/dist/src/neutral/query.d.ts.map +1 -0
  48. package/dist/src/neutral/query.js +180 -0
  49. package/dist/src/neutral/result.d.ts +33 -0
  50. package/dist/src/neutral/result.d.ts.map +1 -0
  51. package/dist/src/neutral/result.js +38 -0
  52. package/dist/src/neutral/source-ref.d.ts +126 -0
  53. package/dist/src/neutral/source-ref.d.ts.map +1 -0
  54. package/dist/src/neutral/source-ref.js +267 -0
  55. package/dist/src/nl-map-control.d.ts.map +1 -1
  56. package/dist/src/tools/count-features.d.ts +85 -11
  57. package/dist/src/tools/count-features.d.ts.map +1 -1
  58. package/dist/src/tools/count-features.js +49 -31
  59. package/dist/src/tools/describe-layer.d.ts +30 -6
  60. package/dist/src/tools/describe-layer.d.ts.map +1 -1
  61. package/dist/src/tools/describe-layer.js +99 -8
  62. package/dist/src/tools/get-extent.d.ts +69 -11
  63. package/dist/src/tools/get-extent.d.ts.map +1 -1
  64. package/dist/src/tools/get-extent.js +28 -46
  65. package/dist/src/tools/list-services.d.ts +7 -0
  66. package/dist/src/tools/list-services.d.ts.map +1 -1
  67. package/dist/src/tools/list-services.js +8 -1
  68. package/dist/src/tools/list-sources.d.ts +44 -0
  69. package/dist/src/tools/list-sources.d.ts.map +1 -0
  70. package/dist/src/tools/list-sources.js +138 -0
  71. package/dist/src/tools/query-features.d.ts +99 -19
  72. package/dist/src/tools/query-features.d.ts.map +1 -1
  73. package/dist/src/tools/query-features.js +38 -45
  74. package/dist/src/tools/statistics.d.ts +80 -12
  75. package/dist/src/tools/statistics.d.ts.map +1 -1
  76. package/dist/src/tools/statistics.js +56 -23
  77. 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"}