@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.
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 @@
1
+ {"version":3,"file":"cql2.d.ts","sourceRoot":"","sources":["../../../src/certification/cql2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AA2FD,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnC,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;AAqQvC,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAErD"}
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Minimal CQL2-text evaluator for the offline OGC API Features fixture (#1005).
3
+ *
4
+ * The neutral tool contract lowers `Query.filter` onto CQL2 text for the OGC
5
+ * family, so an offline fixture that ignored `filter=` would certify a filter
6
+ * path that never actually filtered. This evaluator parses the exact subset the
7
+ * SDK's CQL2 compiler emits — comparisons, `IN`, `BETWEEN`, `IS [NOT] NULL`,
8
+ * `LIKE`/`CASEI`, `AND`/`OR`/`NOT`, parentheses, and `DATE()`/`TIMESTAMP()`
9
+ * literals — and REFUSES anything else rather than silently matching every row,
10
+ * which would turn a filter regression into a passing test.
11
+ *
12
+ * It is a fixture, not a CQL2 implementation: spatial predicates are out of
13
+ * scope here (the fixture serves spatial constraints through the `bbox`
14
+ * parameter, exactly as OGC API Features Part 1 defines).
15
+ */
16
+ export class Cql2UnsupportedError extends Error {
17
+ constructor(message) {
18
+ super(`cql2 fixture: ${message}`);
19
+ this.name = "Cql2UnsupportedError";
20
+ }
21
+ }
22
+ const KEYWORDS = new Set(["AND", "OR", "NOT", "IN", "IS", "NULL", "BETWEEN", "LIKE", "TRUE", "FALSE", "CASEI"]);
23
+ /** Temporal predicates the fixture evaluates exactly. */
24
+ const TEMPORAL_FUNCTIONS = new Set(["T_BEFORE", "T_AFTER", "T_DURING", "T_INTERSECTS"]);
25
+ /**
26
+ * Spatial predicates. OGC API Features Part 1 expresses a spatial constraint
27
+ * through the `bbox` parameter, which this fixture serves exactly; the Part 3
28
+ * `S_*` predicates are refused rather than approximated, because a spatial
29
+ * predicate that silently matched the wrong rows would be worse than an error.
30
+ */
31
+ const SPATIAL_FUNCTIONS = new Set([
32
+ "S_INTERSECTS",
33
+ "S_CONTAINS",
34
+ "S_WITHIN",
35
+ "S_CROSSES",
36
+ "S_TOUCHES",
37
+ "S_OVERLAPS",
38
+ "S_DISJOINT",
39
+ "S_EQUALS",
40
+ ]);
41
+ function tokenize(input) {
42
+ const tokens = [];
43
+ let i = 0;
44
+ while (i < input.length) {
45
+ const ch = input[i];
46
+ if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
47
+ i += 1;
48
+ continue;
49
+ }
50
+ if (ch === "'") {
51
+ let value = "";
52
+ i += 1;
53
+ while (i < input.length) {
54
+ if (input[i] === "'") {
55
+ if (input[i + 1] === "'") {
56
+ value += "'";
57
+ i += 2;
58
+ continue;
59
+ }
60
+ i += 1;
61
+ break;
62
+ }
63
+ value += input[i];
64
+ i += 1;
65
+ }
66
+ tokens.push({ kind: "string", value });
67
+ continue;
68
+ }
69
+ if (ch === "(" || ch === ")" || ch === ",") {
70
+ tokens.push({ kind: "symbol", value: ch });
71
+ i += 1;
72
+ continue;
73
+ }
74
+ const twoChar = input.slice(i, i + 2);
75
+ if (twoChar === "<=" || twoChar === ">=" || twoChar === "<>") {
76
+ tokens.push({ kind: "symbol", value: twoChar });
77
+ i += 2;
78
+ continue;
79
+ }
80
+ if (ch === "=" || ch === "<" || ch === ">") {
81
+ tokens.push({ kind: "symbol", value: ch });
82
+ i += 1;
83
+ continue;
84
+ }
85
+ const numberMatch = /^-?\d+(?:\.\d+)?/.exec(input.slice(i));
86
+ if (numberMatch && /[-\d]/.test(ch)) {
87
+ tokens.push({ kind: "number", value: numberMatch[0] });
88
+ i += numberMatch[0].length;
89
+ continue;
90
+ }
91
+ const identMatch = /^[A-Za-z_][A-Za-z0-9_.:]*/.exec(input.slice(i));
92
+ if (identMatch) {
93
+ const upper = identMatch[0].toUpperCase();
94
+ tokens.push({
95
+ kind: KEYWORDS.has(upper) ? "keyword" : "ident",
96
+ value: KEYWORDS.has(upper) ? upper : identMatch[0],
97
+ });
98
+ i += identMatch[0].length;
99
+ continue;
100
+ }
101
+ throw new Cql2UnsupportedError(`unexpected character "${ch}" in ${JSON.stringify(input)}`);
102
+ }
103
+ return tokens;
104
+ }
105
+ class Parser {
106
+ tokens;
107
+ index = 0;
108
+ constructor(tokens) {
109
+ this.tokens = tokens;
110
+ }
111
+ peek() {
112
+ return this.tokens[this.index];
113
+ }
114
+ next() {
115
+ const token = this.tokens[this.index];
116
+ if (!token)
117
+ throw new Cql2UnsupportedError("unexpected end of expression");
118
+ this.index += 1;
119
+ return token;
120
+ }
121
+ accept(kind, value) {
122
+ const token = this.peek();
123
+ if (token && token.kind === kind && token.value === value) {
124
+ this.index += 1;
125
+ return true;
126
+ }
127
+ return false;
128
+ }
129
+ expect(kind, value) {
130
+ if (!this.accept(kind, value)) {
131
+ throw new Cql2UnsupportedError(`expected "${value}" but found ${JSON.stringify(this.peek()?.value ?? "<end>")}`);
132
+ }
133
+ }
134
+ parse() {
135
+ const predicate = this.parseOr();
136
+ if (this.index !== this.tokens.length) {
137
+ throw new Cql2UnsupportedError(`trailing input at ${JSON.stringify(this.peek()?.value)}`);
138
+ }
139
+ return predicate;
140
+ }
141
+ parseOr() {
142
+ let left = this.parseAnd();
143
+ while (this.accept("keyword", "OR")) {
144
+ const right = this.parseAnd();
145
+ const previous = left;
146
+ left = (row) => previous(row) || right(row);
147
+ }
148
+ return left;
149
+ }
150
+ parseAnd() {
151
+ let left = this.parseFactor();
152
+ while (this.accept("keyword", "AND")) {
153
+ const right = this.parseFactor();
154
+ const previous = left;
155
+ left = (row) => previous(row) && right(row);
156
+ }
157
+ return left;
158
+ }
159
+ parseFactor() {
160
+ if (this.accept("keyword", "NOT")) {
161
+ const inner = this.parseFactor();
162
+ return (row) => !inner(row);
163
+ }
164
+ if (this.accept("symbol", "(")) {
165
+ const inner = this.parseOr();
166
+ this.expect("symbol", ")");
167
+ return inner;
168
+ }
169
+ const upcoming = this.peek();
170
+ if (upcoming?.kind === "ident") {
171
+ const name = upcoming.value.toUpperCase();
172
+ if (SPATIAL_FUNCTIONS.has(name)) {
173
+ throw new Cql2UnsupportedError(`spatial predicate ${name} requires OGC API Features Part 3 filtering, which this endpoint does not publish. Use bbox for an envelope-intersects constraint.`);
174
+ }
175
+ if (TEMPORAL_FUNCTIONS.has(name)) {
176
+ return this.parseTemporal(name);
177
+ }
178
+ }
179
+ return this.parsePredicate();
180
+ }
181
+ /** `T_BEFORE(field, TIMESTAMP('…'))` / `T_DURING(field, INTERVAL('…','…'))`. */
182
+ parseTemporal(name) {
183
+ this.next();
184
+ this.expect("symbol", "(");
185
+ const property = this.next();
186
+ if (property.kind !== "ident") {
187
+ throw new Cql2UnsupportedError(`${name} expects a property reference, found ${JSON.stringify(property.value)}`);
188
+ }
189
+ this.expect("symbol", ",");
190
+ const bound = this.parseTemporalOperand();
191
+ this.expect("symbol", ")");
192
+ const field = property.value;
193
+ return (row) => {
194
+ const raw = row[field];
195
+ if (typeof raw !== "string")
196
+ return false;
197
+ const value = Date.parse(raw);
198
+ if (Number.isNaN(value))
199
+ return false;
200
+ switch (name) {
201
+ case "T_BEFORE":
202
+ return value < bound.start;
203
+ case "T_AFTER":
204
+ return value > bound.end;
205
+ case "T_DURING":
206
+ return value >= bound.start && value <= bound.end;
207
+ default:
208
+ return value >= bound.start && value <= bound.end;
209
+ }
210
+ };
211
+ }
212
+ parseTemporalOperand() {
213
+ const token = this.next();
214
+ if (token.kind !== "ident") {
215
+ throw new Cql2UnsupportedError(`expected a temporal literal, found ${JSON.stringify(token.value)}`);
216
+ }
217
+ const name = token.value.toUpperCase();
218
+ this.expect("symbol", "(");
219
+ if (name === "INTERVAL") {
220
+ const start = this.next();
221
+ this.expect("symbol", ",");
222
+ const end = this.next();
223
+ this.expect("symbol", ")");
224
+ return { start: Date.parse(start.value), end: Date.parse(end.value) };
225
+ }
226
+ if (name === "DATE" || name === "TIMESTAMP") {
227
+ const instant = this.next();
228
+ this.expect("symbol", ")");
229
+ const parsed = Date.parse(instant.value);
230
+ return { start: parsed, end: parsed };
231
+ }
232
+ throw new Cql2UnsupportedError(`unsupported temporal literal "${name}"`);
233
+ }
234
+ /** `CASEI(x)` wraps either a property reference or a literal. */
235
+ parseOperand() {
236
+ if (this.accept("keyword", "CASEI")) {
237
+ this.expect("symbol", "(");
238
+ const inner = this.parseOperand();
239
+ this.expect("symbol", ")");
240
+ return { ...inner, caseInsensitive: true };
241
+ }
242
+ const token = this.next();
243
+ if (token.kind === "ident") {
244
+ // Temporal literals arrive as DATE('…') / TIMESTAMP('…').
245
+ if ((token.value === "DATE" || token.value === "TIMESTAMP") && this.accept("symbol", "(")) {
246
+ const literal = this.next();
247
+ this.expect("symbol", ")");
248
+ return { literal: literal.value, caseInsensitive: false };
249
+ }
250
+ return { property: token.value, caseInsensitive: false };
251
+ }
252
+ if (token.kind === "string")
253
+ return { literal: token.value, caseInsensitive: false };
254
+ if (token.kind === "number")
255
+ return { literal: Number(token.value), caseInsensitive: false };
256
+ if (token.kind === "keyword" && (token.value === "TRUE" || token.value === "FALSE")) {
257
+ return { literal: token.value === "TRUE", caseInsensitive: false };
258
+ }
259
+ if (token.kind === "keyword" && token.value === "NULL")
260
+ return { literal: null, caseInsensitive: false };
261
+ throw new Cql2UnsupportedError(`unsupported operand ${JSON.stringify(token.value)}`);
262
+ }
263
+ parsePredicate() {
264
+ const left = this.parseOperand();
265
+ const read = (row) => (left.property !== undefined ? row[left.property] : left.literal);
266
+ if (this.accept("keyword", "IS")) {
267
+ const negated = this.accept("keyword", "NOT");
268
+ this.expect("keyword", "NULL");
269
+ return (row) => {
270
+ const value = read(row);
271
+ const isNull = value === null || value === undefined;
272
+ return negated ? !isNull : isNull;
273
+ };
274
+ }
275
+ if (this.accept("keyword", "IN")) {
276
+ this.expect("symbol", "(");
277
+ const values = [];
278
+ do {
279
+ values.push(this.parseOperand().literal);
280
+ } while (this.accept("symbol", ","));
281
+ this.expect("symbol", ")");
282
+ return (row) => values.some((candidate) => looseEquals(read(row), candidate));
283
+ }
284
+ if (this.accept("keyword", "BETWEEN")) {
285
+ const lower = this.parseOperand().literal;
286
+ this.expect("keyword", "AND");
287
+ const upper = this.parseOperand().literal;
288
+ return (row) => {
289
+ const value = read(row);
290
+ return compare(value, lower) >= 0 && compare(value, upper) <= 0;
291
+ };
292
+ }
293
+ if (this.accept("keyword", "LIKE")) {
294
+ const right = this.parseOperand();
295
+ const caseInsensitive = left.caseInsensitive || right.caseInsensitive;
296
+ const pattern = likeToRegExp(String(right.literal ?? ""), caseInsensitive);
297
+ return (row) => {
298
+ const value = read(row);
299
+ return value !== null && value !== undefined && pattern.test(String(value));
300
+ };
301
+ }
302
+ const operatorToken = this.next();
303
+ if (operatorToken.kind !== "symbol") {
304
+ throw new Cql2UnsupportedError(`expected a comparison operator, found ${JSON.stringify(operatorToken.value)}`);
305
+ }
306
+ const right = this.parseOperand();
307
+ const rightValue = (row) => (right.property !== undefined ? row[right.property] : right.literal);
308
+ const operator = operatorToken.value;
309
+ return (row) => {
310
+ const a = read(row);
311
+ const b = rightValue(row);
312
+ if (a === null || a === undefined || b === null || b === undefined) {
313
+ return operator === "<>" ? a !== b : false;
314
+ }
315
+ switch (operator) {
316
+ case "=":
317
+ return looseEquals(a, b);
318
+ case "<>":
319
+ return !looseEquals(a, b);
320
+ case "<":
321
+ return compare(a, b) < 0;
322
+ case "<=":
323
+ return compare(a, b) <= 0;
324
+ case ">":
325
+ return compare(a, b) > 0;
326
+ case ">=":
327
+ return compare(a, b) >= 0;
328
+ default:
329
+ throw new Cql2UnsupportedError(`unsupported operator "${operator}"`);
330
+ }
331
+ };
332
+ }
333
+ }
334
+ function looseEquals(a, b) {
335
+ if (typeof a === "number" || typeof b === "number")
336
+ return Number(a) === Number(b);
337
+ return String(a) === String(b);
338
+ }
339
+ function compare(a, b) {
340
+ if (typeof a === "number" && typeof b === "number")
341
+ return a === b ? 0 : a < b ? -1 : 1;
342
+ const as = String(a);
343
+ const bs = String(b);
344
+ return as === bs ? 0 : as < bs ? -1 : 1;
345
+ }
346
+ function likeToRegExp(pattern, caseInsensitive) {
347
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
348
+ const translated = escaped.replace(/%/g, "[\\s\\S]*").replace(/_/g, "[\\s\\S]");
349
+ return new RegExp(`^${translated}$`, caseInsensitive ? "i" : "");
350
+ }
351
+ /** Compile a CQL2-text expression into a row predicate. */
352
+ export function compileCql2(filter) {
353
+ return new Parser(tokenize(filter)).parse();
354
+ }
@@ -12,6 +12,14 @@ export interface RoundTripEnv {
12
12
  layerId: number;
13
13
  address: string;
14
14
  styleId: string | undefined;
15
+ /**
16
+ * Protocol-neutral source reference the source-addressed tools round-trip
17
+ * against (#1005). Defaults to the GeoServices pair so existing targets are
18
+ * unchanged; the non-GeoServices lane overrides it with an OGC collection.
19
+ */
20
+ sourceRef?: string;
21
+ /** Numeric field on `sourceRef` usable for an aggregate round trip. */
22
+ statisticField?: string;
15
23
  }
16
24
  /** Resolve round-trip identifiers from the environment, with safe fallbacks. */
17
25
  export declare function resolveRoundTripEnv(env?: NodeJS.ProcessEnv): RoundTripEnv;
@@ -1 +1 @@
1
- {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../src/certification/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAQtF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsC/F;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMhF"}
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../src/certification/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAiBtF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA6C/F;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMhF"}
@@ -15,7 +15,16 @@ export function resolveRoundTripEnv(env = process.env) {
15
15
  const layerId = Number.isFinite(parsedLayer) && parsedLayer >= 0 ? parsedLayer : 0;
16
16
  const address = env.HONUA_MCP_ADDRESS ?? "1600 Pennsylvania Ave NW, Washington DC";
17
17
  const styleId = env.HONUA_MCP_STYLE_ID ?? env.HONUA_STYLE_ID;
18
- return { serviceId, layerId, address, styleId };
18
+ const sourceRef = env.HONUA_MCP_SOURCE;
19
+ const statisticField = env.HONUA_MCP_STATISTIC_FIELD;
20
+ return {
21
+ serviceId,
22
+ layerId,
23
+ address,
24
+ styleId,
25
+ ...(sourceRef ? { sourceRef } : {}),
26
+ ...(statisticField ? { statisticField } : {}),
27
+ };
19
28
  }
20
29
  /**
21
30
  * Build the read-only round-trip argument map keyed by advertised tool name.
@@ -25,23 +34,30 @@ export function resolveRoundTripEnv(env = process.env) {
25
34
  */
26
35
  export function buildRoundTripInputs(env) {
27
36
  const { serviceId, layerId, address } = env;
37
+ // Source-addressed tools round-trip through the protocol-neutral reference
38
+ // when the target provides one (#1005), and otherwise through the deprecated
39
+ // GeoServices pair — so both addressing modes stay certified.
40
+ const target = env.sourceRef ? { source: env.sourceRef } : { serviceId, layerId };
41
+ const statisticField = env.statisticField ?? "OBJECTID";
28
42
  return {
29
- // ── Platform-free standalone surface tools (issue #369). Round-tripped by the
30
- // `standalone` cert target against a plain public FeatureServer fixture. The
31
- // Honua-only style tools resolve to a structured "not available on this
32
- // target" result (non-error) — certifying graceful degradation, not a crash.
43
+ // ── Platform-free standalone surface tools (issues #369, #1005). Round-tripped
44
+ // by the `standalone` (Esri FeatureServer) and `standalone-ogc` (OGC API
45
+ // Features) cert targets. The Honua-only style tools resolve to a structured
46
+ // "not available on this target" result (non-error) — certifying graceful
47
+ // degradation, not a crash.
48
+ honua_list_sources: {},
33
49
  honua_list_services: {},
34
- honua_describe_layer: { serviceId, layerId },
35
- honua_count_features: { serviceId, layerId },
36
- honua_get_extent: { serviceId, layerId },
37
- honua_statistics: { serviceId, layerId, statisticType: "count", onField: "OBJECTID" },
50
+ honua_describe_layer: { ...target },
51
+ honua_count_features: { ...target },
52
+ honua_get_extent: { ...target },
53
+ honua_statistics: { ...target, statisticType: "count", onField: statisticField },
38
54
  honua_explain_capability_gap: { capability: "query", protocol: "wmts" },
39
55
  honua_get_style: {},
40
56
  honua_apply_style_preset: { styleId: "topographic" },
41
57
  // ── Honua-enhanced operator catalog tools (offline/remote/stdio-proxy targets).
42
58
  honua_list_layers: {},
43
59
  honua_list_capabilities: {},
44
- honua_query_features: { serviceId, layerId, limit: 1 },
60
+ honua_query_features: { ...target, limit: 1 },
45
61
  honua_render_map: { layers: [{ serviceId, layerId }], bbox: [-77.1, 38.8, -76.9, 39.0] },
46
62
  honua_geocode_address: { address },
47
63
  honua_solve_route: {
@@ -0,0 +1,30 @@
1
+ /** A recorded OGC API Features collection: metadata, queryables, and every item. */
2
+ export interface OgcFixtureCollection {
3
+ readonly collection: {
4
+ readonly id: string;
5
+ readonly title: string | null;
6
+ readonly description: string | null;
7
+ readonly extent: unknown;
8
+ readonly crs: readonly string[] | null;
9
+ readonly itemType: string;
10
+ };
11
+ readonly queryables: Record<string, {
12
+ type?: string;
13
+ title?: string;
14
+ [key: string]: unknown;
15
+ }> | null;
16
+ readonly features: ReadonlyArray<{
17
+ readonly type: "Feature";
18
+ readonly id?: string | number;
19
+ readonly geometry: Record<string, unknown> | null;
20
+ readonly properties: Record<string, unknown> | null;
21
+ }>;
22
+ readonly numberMatched: number;
23
+ }
24
+ /** The public OGC API Features endpoint these fixtures were recorded from. */
25
+ export declare const OGC_ENDPOINT = "https://demo.pygeoapi.io/master";
26
+ /** Recorded collections, keyed by collection id. */
27
+ export declare const OGC_COLLECTIONS: Readonly<Record<string, OgcFixtureCollection>>;
28
+ /** Collection ids in advertised order. */
29
+ export declare const OGC_COLLECTION_IDS: readonly string[];
30
+ //# sourceMappingURL=ogc-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ogc-data.d.ts","sourceRoot":"","sources":["../../../src/certification/ogc-data.ts"],"names":[],"mappings":"AAeA,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;QACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IACtG,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;QACzB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KACrD,CAAC,CAAC;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,8EAA8E;AAC9E,eAAO,MAAM,YAAY,oCAAoC,CAAC;AAE9D,oDAAoD;AACpD,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAmsBjE,CAAC;AAEX,0CAA0C;AAC1C,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAmC,CAAC"}