@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,437 @@
1
+ /**
2
+ * NON-GeoServices (OGC API Features) eval corpus — issue #1005.
3
+ *
4
+ * The standalone corpus proves the surface works against a plain Esri
5
+ * FeatureServer. That is only half the vendor-neutrality claim: a tool contract
6
+ * can pass every GeoServices scenario and still be Esri-shaped. This corpus runs
7
+ * the same catalog against a plain **OGC API Features** endpoint — recorded from
8
+ * the public pygeoapi demo (`src/certification/ogc-data.ts`), the pinned
9
+ * `ogc-features` target in `config/live-conformance-endpoints.v1.json` — where
10
+ * nothing Esri exists: no `/rest/services`, no `serviceId`, no `layerId`.
11
+ *
12
+ * Every scenario addresses data with the protocol-neutral
13
+ * `ogc-features:<collectionId>` reference and filters with the typed semantic
14
+ * filter, a `bbox`, or a canonical temporal predicate. Grading is SEMANTIC, on
15
+ * anchors verified against the recorded data:
16
+ *
17
+ * obs (5 observations): avg(value)=96.14 max=103.5 stn_id=2147 => 2 rows
18
+ * value>95 => 2 2001-01-01..2004-01-01 => 3 rows
19
+ * bbox(-80,42,-78,44) => 2 rows
20
+ * utah_city_locations (31 cities): sum(POP_2000)=354212 max=105166 (Provo)
21
+ * CO_SEAT='yes' => 1 POP_2000>100000 => 1
22
+ *
23
+ * Two scenarios grade HONESTY rather than data: OGC API Features has no
24
+ * server-side aggregation and no server-side extent operation, so those answers
25
+ * must arrive carrying an explicit degradation reason; and a CQL2 spatial
26
+ * predicate the endpoint does not publish must come back as a structured
27
+ * capability refusal, never as an empty feature list.
28
+ *
29
+ * Selected via `HONUA_EVAL_CORPUS=ogc` / `--corpus ogc`; the runner swaps in the
30
+ * OGC fixture client for this corpus (see `runner.ts`).
31
+ */
32
+ const OBS = "ogc-features:obs";
33
+ const CITIES = "ogc-features:utah_city_locations";
34
+ export const OGC_CORPUS = [
35
+ // ── Protocol-neutral discovery ───────────────────────────────────────────
36
+ {
37
+ id: "ogc-discover-sources",
38
+ title: "Discover sources on an endpoint with no GeoServices catalog",
39
+ category: "discovery",
40
+ prompt: "What data can I query at this endpoint? List the sources and how I address them.",
41
+ criteria: {
42
+ requiredTools: ["honua_list_sources"],
43
+ answerMustInclude: ["ogc-features:obs", "ogc-features:utah_city_locations"],
44
+ },
45
+ script: [{ tool: "honua_list_sources", args: {} }],
46
+ },
47
+ {
48
+ id: "ogc-discover-geoservices-absent",
49
+ title: "The GeoServices family is reported absent, not as 'no data'",
50
+ category: "degradation",
51
+ prompt: "Does this endpoint publish any Esri feature services? Be honest if it does not.",
52
+ criteria: {
53
+ requiredTools: ["honua_list_sources"],
54
+ answerMustInclude: ["geoservices", "ogc-features:obs"],
55
+ answerMustMatch: ['"available":\\s*false'],
56
+ },
57
+ script: [{ tool: "honua_list_sources", args: {} }],
58
+ },
59
+ // ── Schema grounding ─────────────────────────────────────────────────────
60
+ {
61
+ id: "ogc-describe-collection",
62
+ title: "Inspect an OGC collection's schema before querying",
63
+ category: "grounding",
64
+ prompt: "Show me the fields on the observations collection before I query it.",
65
+ criteria: {
66
+ requiredTools: ["honua_describe_layer"],
67
+ answerMustInclude: ["stn_id", "value", "ogc-features"],
68
+ },
69
+ script: [{ tool: "honua_describe_layer", args: { source: OBS } }],
70
+ },
71
+ {
72
+ id: "ogc-describe-capabilities",
73
+ title: "Report the capabilities the protocol actually advertises",
74
+ category: "grounding",
75
+ prompt: "What can I actually do with the observations collection — can I aggregate on the server?",
76
+ criteria: {
77
+ requiredTools: ["honua_describe_layer"],
78
+ answerMustInclude: ["capabilities", "query"],
79
+ answerMustNotInclude: ['"queryAggregate"'],
80
+ },
81
+ script: [{ tool: "honua_describe_layer", args: { source: OBS } }],
82
+ },
83
+ // ── Counting through numberMatched ───────────────────────────────────────
84
+ {
85
+ id: "ogc-count-all",
86
+ title: "Count every record in an OGC collection",
87
+ category: "count",
88
+ prompt: "How many observations are there? Just the number.",
89
+ criteria: {
90
+ requiredTools: ["honua_count_features"],
91
+ forbiddenTools: ["honua_query_features"],
92
+ answerMustMatch: ['"count":\\s*5'],
93
+ },
94
+ script: [{ tool: "honua_count_features", args: { source: OBS } }],
95
+ },
96
+ {
97
+ id: "ogc-count-typed-filter",
98
+ title: "Count with the typed semantic filter (compiled to CQL2)",
99
+ category: "count",
100
+ prompt: "How many observations came from station 2147?",
101
+ criteria: {
102
+ requiredTools: ["honua_count_features"],
103
+ answerMustMatch: ['"count":\\s*2'],
104
+ },
105
+ script: [
106
+ {
107
+ tool: "honua_count_features",
108
+ args: { source: OBS, filter: { op: "eq", field: "stn_id", value: 2147 } },
109
+ },
110
+ ],
111
+ },
112
+ {
113
+ id: "ogc-count-county-seat",
114
+ title: "Count with a string equality filter",
115
+ category: "count",
116
+ prompt: "How many of these Utah cities are county seats?",
117
+ criteria: {
118
+ requiredTools: ["honua_count_features"],
119
+ answerMustMatch: ['"count":\\s*1'],
120
+ },
121
+ script: [
122
+ {
123
+ tool: "honua_count_features",
124
+ args: { source: CITIES, filter: { op: "eq", field: "CO_SEAT", value: "yes" } },
125
+ },
126
+ ],
127
+ },
128
+ // ── Typed filter + ordering ──────────────────────────────────────────────
129
+ {
130
+ id: "ogc-filter-and-sort",
131
+ title: "Filter and sort with the protocol-neutral vocabulary",
132
+ category: "query",
133
+ prompt: "Show me the observations with a value above 95, highest first.",
134
+ criteria: {
135
+ requiredTools: ["honua_query_features"],
136
+ answerMustMatch: ["103.5", '"returnedCount":\\s*2'],
137
+ },
138
+ script: [
139
+ {
140
+ tool: "honua_query_features",
141
+ args: {
142
+ source: OBS,
143
+ filter: { op: "gt", field: "value", value: 95 },
144
+ orderBy: [{ field: "value", direction: "desc" }],
145
+ },
146
+ },
147
+ ],
148
+ },
149
+ {
150
+ id: "ogc-filter-compound",
151
+ title: "Compound AND filter over an OGC collection",
152
+ category: "query",
153
+ prompt: "Which observations from station 2147 have a value under 100?",
154
+ criteria: {
155
+ requiredTools: ["honua_query_features"],
156
+ answerMustMatch: ["93.5", '"returnedCount":\\s*1'],
157
+ answerMustNotInclude: ["103.5"],
158
+ },
159
+ script: [
160
+ {
161
+ tool: "honua_query_features",
162
+ args: {
163
+ source: OBS,
164
+ filter: {
165
+ op: "and",
166
+ args: [
167
+ { op: "eq", field: "stn_id", value: 2147 },
168
+ { op: "lt", field: "value", value: 100 },
169
+ ],
170
+ },
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ {
176
+ id: "ogc-filter-largest-city",
177
+ title: "Find the largest city with a typed filter",
178
+ category: "reasoning",
179
+ prompt: "Which of these Utah cities had more than 100,000 people in 2000?",
180
+ criteria: {
181
+ requiredTools: ["honua_query_features"],
182
+ answerMustMatch: ["105166", '"returnedCount":\\s*1'],
183
+ },
184
+ script: [
185
+ {
186
+ tool: "honua_query_features",
187
+ args: { source: CITIES, filter: { op: "gt", field: "POP_2000", value: 100000 } },
188
+ },
189
+ ],
190
+ },
191
+ // ── Canonical geometry + temporal predicates ─────────────────────────────
192
+ {
193
+ id: "ogc-bbox-filter",
194
+ title: "Spatial filter through the canonical bbox",
195
+ category: "query",
196
+ prompt: "Which observations fall inside the box from -80,42 to -78,44? Include their geometry.",
197
+ criteria: {
198
+ requiredTools: ["honua_query_features"],
199
+ answerMustMatch: ['"returnedCount":\\s*2', '"geometryFormat":\\s*"geojson"'],
200
+ answerMustInclude: ["Point"],
201
+ },
202
+ script: [
203
+ {
204
+ tool: "honua_query_features",
205
+ args: { source: OBS, bbox: [-80, 42, -78, 44], returnGeometry: true },
206
+ },
207
+ ],
208
+ },
209
+ {
210
+ id: "ogc-temporal-interval",
211
+ title: "Canonical temporal interval on the collection time dimension",
212
+ category: "query",
213
+ prompt: "How many observations were recorded between 2001 and the start of 2004?",
214
+ criteria: {
215
+ requiredTools: ["honua_count_features"],
216
+ answerMustMatch: ['"count":\\s*3'],
217
+ },
218
+ script: [
219
+ {
220
+ tool: "honua_count_features",
221
+ args: { source: OBS, temporal: { start: "2001-01-01T00:00:00Z", end: "2004-01-01T00:00:00Z" } },
222
+ },
223
+ ],
224
+ },
225
+ {
226
+ id: "ogc-temporal-field",
227
+ title: "Canonical temporal predicate bound to an explicit field",
228
+ category: "query",
229
+ prompt: "Count the observations whose datetime column falls in 2001 through 2003.",
230
+ criteria: {
231
+ requiredTools: ["honua_count_features"],
232
+ answerMustMatch: ['"count":\\s*3'],
233
+ },
234
+ script: [
235
+ {
236
+ tool: "honua_count_features",
237
+ args: {
238
+ source: OBS,
239
+ temporal: { start: "2001-01-01T00:00:00Z", end: "2004-01-01T00:00:00Z", field: "datetime" },
240
+ },
241
+ },
242
+ ],
243
+ },
244
+ // ── Aggregation with honest degradation ──────────────────────────────────
245
+ {
246
+ id: "ogc-aggregate-sum",
247
+ title: "Aggregate over a protocol with no server-side aggregation",
248
+ category: "analysis",
249
+ prompt: "What is the total 2000 population across these Utah cities?",
250
+ criteria: {
251
+ requiredTools: ["honua_statistics"],
252
+ answerMustInclude: ["sum_POP_2000"],
253
+ answerMustMatch: ["354212"],
254
+ },
255
+ script: [{ tool: "honua_statistics", args: { source: CITIES, statisticType: "sum", onField: "POP_2000" } }],
256
+ },
257
+ {
258
+ id: "ogc-aggregate-degradation-reported",
259
+ title: "Client-side aggregation is reported, not passed off as server-side",
260
+ category: "degradation",
261
+ prompt: "Average the observation values, and tell me whether the server computed that or you did.",
262
+ criteria: {
263
+ requiredTools: ["honua_statistics"],
264
+ answerMustInclude: ["degraded", "queryAggregate", "client-side"],
265
+ answerMustMatch: ["96.14"],
266
+ },
267
+ script: [{ tool: "honua_statistics", args: { source: OBS, statisticType: "avg", onField: "value" } }],
268
+ },
269
+ {
270
+ id: "ogc-aggregate-grouped",
271
+ title: "Grouped aggregation across an OGC collection",
272
+ category: "analysis",
273
+ prompt: "Break the Utah cities down by whether they are county seats and count each group.",
274
+ criteria: {
275
+ requiredTools: ["honua_statistics"],
276
+ answerMustInclude: ["CO_SEAT"],
277
+ answerMustMatch: ['"count_POP_2000":\\s*30', '"count_POP_2000":\\s*1'],
278
+ },
279
+ script: [
280
+ {
281
+ tool: "honua_statistics",
282
+ args: { source: CITIES, statisticType: "count", onField: "POP_2000", groupBy: ["CO_SEAT"] },
283
+ },
284
+ ],
285
+ },
286
+ // ── Extent with honest provenance ────────────────────────────────────────
287
+ {
288
+ id: "ogc-extent-declared",
289
+ title: "Extent comes from the declared collection extent, and says so",
290
+ category: "degradation",
291
+ prompt: "What area do these Utah cities cover?",
292
+ criteria: {
293
+ requiredTools: ["honua_get_extent"],
294
+ answerMustInclude: ["declared-extent"],
295
+ answerMustMatch: ["-112.108489"],
296
+ },
297
+ script: [{ tool: "honua_get_extent", args: { source: CITIES } }],
298
+ },
299
+ // ── Capability honesty ───────────────────────────────────────────────────
300
+ {
301
+ id: "ogc-spatial-predicate-refused",
302
+ title: "An inexpressible spatial predicate refuses instead of returning nothing",
303
+ category: "degradation",
304
+ prompt: "Find the observations strictly within this polygon. If this endpoint cannot do that, say so — do not tell me there are none.",
305
+ criteria: {
306
+ requiredTools: ["honua_query_features"],
307
+ answerMustInclude: ["Part 3"],
308
+ answerMustNotInclude: ['"returnedCount": 0'],
309
+ },
310
+ script: [
311
+ {
312
+ tool: "honua_query_features",
313
+ args: {
314
+ source: OBS,
315
+ filter: {
316
+ op: "within",
317
+ geometry: {
318
+ type: "Polygon",
319
+ coordinates: [
320
+ [
321
+ [-80, 42],
322
+ [-78, 42],
323
+ [-78, 44],
324
+ [-80, 44],
325
+ [-80, 42],
326
+ ],
327
+ ],
328
+ },
329
+ },
330
+ },
331
+ },
332
+ ],
333
+ },
334
+ {
335
+ id: "ogc-bad-source-reference",
336
+ title: "A malformed source reference is refused with the accepted forms",
337
+ category: "grounding",
338
+ prompt: "Query the collection called 'observations-2024' on this server.",
339
+ criteria: {
340
+ requiredTools: ["honua_query_features"],
341
+ answerMustInclude: ["invalid_source_reference", "ogc-features:<collectionId>"],
342
+ },
343
+ script: [{ tool: "honua_query_features", args: { source: "observations-2024" } }],
344
+ },
345
+ {
346
+ id: "ogc-style-unavailable",
347
+ title: "Styling degrades honestly on an OGC endpoint too",
348
+ category: "degradation",
349
+ prompt: "List the map styles this endpoint publishes.",
350
+ criteria: {
351
+ requiredTools: ["honua_get_style"],
352
+ answerMustInclude: ["OGC API - Styles"],
353
+ answerMustMatch: ['"available":\\s*false'],
354
+ },
355
+ script: [{ tool: "honua_get_style", args: {} }],
356
+ },
357
+ {
358
+ id: "ogc-capability-gap-explained",
359
+ title: "Explain the aggregation gap for the OGC protocol",
360
+ category: "grounding",
361
+ prompt: "Can OGC API Features aggregate on the server? If not, what should I do instead?",
362
+ criteria: {
363
+ requiredTools: ["honua_explain_capability_gap"],
364
+ answerMustInclude: ["explanation"],
365
+ },
366
+ script: [
367
+ {
368
+ tool: "honua_explain_capability_gap",
369
+ args: { capability: "queryAggregate", protocol: "ogc-features" },
370
+ },
371
+ ],
372
+ },
373
+ // ── Multi-step workflows ─────────────────────────────────────────────────
374
+ {
375
+ id: "ogc-discover-describe-query",
376
+ title: "Discover → describe → query, end to end, with zero Esri vocabulary",
377
+ category: "multi-step",
378
+ prompt: "I don't know this endpoint. Find a source, inspect its schema, then show me its highest-value observation.",
379
+ criteria: {
380
+ requiredTools: ["honua_list_sources", "honua_describe_layer", "honua_query_features"],
381
+ expectedToolSequence: ["honua_list_sources", "honua_describe_layer", "honua_query_features"],
382
+ answerMustMatch: ["103.5"],
383
+ },
384
+ script: [
385
+ { tool: "honua_list_sources", args: {} },
386
+ { tool: "honua_describe_layer", args: { source: OBS } },
387
+ {
388
+ tool: "honua_query_features",
389
+ args: { source: OBS, orderBy: [{ field: "value", direction: "desc" }], limit: 1 },
390
+ },
391
+ ],
392
+ },
393
+ {
394
+ id: "ogc-count-then-list",
395
+ title: "Check cardinality, then fetch the matching records",
396
+ category: "multi-step",
397
+ prompt: "How many observations exceed 95, and which are they?",
398
+ criteria: {
399
+ requiredTools: ["honua_count_features", "honua_query_features"],
400
+ expectedToolSequence: ["honua_count_features", "honua_query_features"],
401
+ answerMustMatch: ['"count":\\s*2', "99.9"],
402
+ },
403
+ script: [
404
+ {
405
+ tool: "honua_count_features",
406
+ args: { source: OBS, filter: { op: "gt", field: "value", value: 95 } },
407
+ },
408
+ {
409
+ tool: "honua_query_features",
410
+ args: { source: OBS, filter: { op: "gt", field: "value", value: 95 } },
411
+ },
412
+ ],
413
+ },
414
+ // ── Ambiguity / refusal ──────────────────────────────────────────────────
415
+ {
416
+ id: "ogc-clarify-which-collection",
417
+ title: "Ambiguous 'the data' → clarify which collection",
418
+ category: "clarification",
419
+ prompt: "Show me the data from this server.",
420
+ criteria: { requiredTools: [], expectClarification: true },
421
+ script: [],
422
+ clarify: {
423
+ question: "Which collection would you like — the observations (ogc-features:obs) or the Utah city locations (ogc-features:utah_city_locations)?",
424
+ },
425
+ },
426
+ {
427
+ id: "ogc-refuse-write",
428
+ title: "Read-only endpoint → refuse an edit",
429
+ category: "clarification",
430
+ prompt: "Set every observation value to 100 and save it.",
431
+ criteria: { requiredTools: [], expectClarification: true },
432
+ script: [],
433
+ clarify: {
434
+ question: "This MCP surface is read-only, so I can't write features to this OGC API Features endpoint. Would you like me to query or aggregate the observations instead?",
435
+ },
436
+ },
437
+ ];
@@ -23,6 +23,12 @@ export interface RunEvalOptions {
23
23
  * Defaults to true when `HONUA_EVAL_CORPUS=standalone`.
24
24
  */
25
25
  forceStandaloneSurface?: boolean;
26
+ /**
27
+ * Use the NON-GeoServices fixture surface (a plain OGC API Features endpoint,
28
+ * recorded pygeoapi collections) instead of either Honua/Esri fixture. Implies
29
+ * offline. Defaults to true when `HONUA_EVAL_CORPUS=ogc` (issue #1005).
30
+ */
31
+ forceOgcSurface?: boolean;
26
32
  }
27
33
  export declare function runEval(options?: RunEvalOptions): Promise<EvalReport>;
28
34
  //# sourceMappingURL=runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/eval/runner.ts"],"names":[],"mappings":"AAUA,OAAO,EAAiB,KAAK,UAAU,EAAkB,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAmC,MAAM,YAAY,CAAC;AAEzF;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AA+FD,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiD/E"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/eval/runner.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,UAAU,EAAkB,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAmC,MAAM,YAAY,CAAC;AAEzF;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4GD,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkD/E"}
@@ -2,10 +2,11 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
3
3
  import { createStandaloneFixtureClient } from "../certification/census-fixture-client.js";
4
4
  import { createFixtureClient } from "../certification/fixture-client.js";
5
+ import { createOgcFixtureClient } from "../certification/ogc-fixture-client.js";
5
6
  import { createServer } from "../index.js";
6
7
  import { readNegotiatedProtocolVersion, resolveSuiteGitSha } from "../provenance.js";
7
8
  import { connectUpstream, resolveProxyOptions } from "../proxy.js";
8
- import { isStandaloneCorpus, resolveCorpus } from "./corpus.js";
9
+ import { isOgcCorpus, isStandaloneCorpus, resolveCorpus } from "./corpus.js";
9
10
  import { resolveDrivers } from "./drivers/index.js";
10
11
  import { grade } from "./grade.js";
11
12
  import { assembleReport } from "./report.js";
@@ -17,8 +18,12 @@ function isTruthy(value) {
17
18
  const v = value.trim().toLowerCase();
18
19
  return v.length > 0 && v !== "0" && v !== "false" && v !== "no" && v !== "off";
19
20
  }
20
- async function connectSurface(env, forceOffline, standalone) {
21
- const live = !forceOffline && !standalone && typeof env.HONUA_MCP_REMOTE_URL === "string" && env.HONUA_MCP_REMOTE_URL.length > 0;
21
+ async function connectSurface(env, forceOffline, standalone, ogc) {
22
+ const live = !forceOffline &&
23
+ !standalone &&
24
+ !ogc &&
25
+ typeof env.HONUA_MCP_REMOTE_URL === "string" &&
26
+ env.HONUA_MCP_REMOTE_URL.length > 0;
22
27
  if (live) {
23
28
  const options = resolveProxyOptions(env);
24
29
  // The live lane authenticates via the proxy's real credential headers — no
@@ -41,7 +46,11 @@ async function connectSurface(env, forceOffline, standalone) {
41
46
  },
42
47
  };
43
48
  }
44
- const fixtureClient = standalone ? createStandaloneFixtureClient() : createFixtureClient();
49
+ const fixtureClient = ogc
50
+ ? createOgcFixtureClient()
51
+ : standalone
52
+ ? createStandaloneFixtureClient()
53
+ : createFixtureClient();
45
54
  const server = createServer(fixtureClient);
46
55
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
47
56
  const client = new Client({ name: "honua-mcp-eval", version: "1.0.0" });
@@ -50,7 +59,11 @@ async function connectSurface(env, forceOffline, standalone) {
50
59
  return {
51
60
  client,
52
61
  backend: "fixture",
53
- mcpTransport: standalone ? "in-memory (standalone FeatureServer fixture)" : "in-memory",
62
+ mcpTransport: ogc
63
+ ? "in-memory (standalone OGC API Features fixture)"
64
+ : standalone
65
+ ? "in-memory (standalone FeatureServer fixture)"
66
+ : "in-memory",
54
67
  auth: "none",
55
68
  close: async () => {
56
69
  await client.close().catch(() => { });
@@ -86,8 +99,9 @@ export async function runEval(options = {}) {
86
99
  const corpus = options.corpus ?? resolveCorpus(env);
87
100
  const drivers = options.drivers ?? resolveDrivers({ env });
88
101
  const standalone = options.forceStandaloneSurface ?? isStandaloneCorpus(env);
89
- const forceOffline = options.forceOffline ?? standalone;
90
- const surface = await connectSurface(env, forceOffline, standalone);
102
+ const ogc = options.forceOgcSurface ?? isOgcCorpus(env);
103
+ const forceOffline = options.forceOffline ?? (standalone || ogc);
104
+ const surface = await connectSurface(env, forceOffline, standalone, ogc);
91
105
  try {
92
106
  const listed = await surface.client.listTools();
93
107
  const tools = listed.tools.map((t) => ({
@@ -1,6 +1,12 @@
1
- import type { EsriGeometryType, EsriSpatialRel } from "@honua/sdk-js/honua";
1
+ import type { EsriGeometryType } from "@honua/sdk-js/honua";
2
+ /**
3
+ * Esri geometry-type tags.
4
+ *
5
+ * These are the SDK's canonical geometry-operand tags, not part of the tool
6
+ * contract: the tool surface takes GeoJSON (see `neutral/filter.ts`) and only
7
+ * accepts these to disambiguate Esri-JSON geometry sent by legacy clients.
8
+ */
2
9
  export declare const GEOMETRY_TYPES: readonly ["esriGeometryPoint", "esriGeometryPolyline", "esriGeometryPolygon", "esriGeometryEnvelope", "esriGeometryMultipoint"];
3
- export declare function mapSpatialRel(rel: string | undefined): EsriSpatialRel | undefined;
4
10
  export declare function clampLimit(limit: number | undefined): number;
5
11
  export declare function resolveGeometryType(geometry: Record<string, unknown> | undefined, geometryType: EsriGeometryType | undefined): EsriGeometryType | undefined;
6
12
  export declare function jsonText(result: unknown): {
@@ -10,17 +16,6 @@ export declare function jsonText(result: unknown): {
10
16
  }>;
11
17
  };
12
18
  export declare function metadataErrorText(): string;
13
- /**
14
- * Coerce a feature `count` from a query response into a usable value.
15
- *
16
- * The default `grpc-web` transport returns counts via `toSafeNumberOrString`,
17
- * which yields a **string** when the proto count exceeds
18
- * `Number.MAX_SAFE_INTEGER` (chosen specifically to avoid precision loss). Both
19
- * a finite `number` and a numeric `string` are valid counts; anything else
20
- * (non-finite numbers, non-numeric strings, missing values) returns
21
- * `undefined`.
22
- */
23
- export declare function coerceCount(value: unknown): number | string | undefined;
24
19
  export declare function encodeServiceId(serviceId: string): string;
25
20
  export declare function decodeServiceId(encoded: string): string;
26
21
  export declare function parseLayerId(value: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM5E,eAAO,MAAM,cAAc,iIAMjB,CAAC;AAQX,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAKjF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG5D;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,gBAAgB,GAAG,SAAS,CA8B9B;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAE5F;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAWvE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC3C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqBd"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM5D;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,iIAMjB,CAAC;AAEX,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG5D;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,YAAY,EAAE,gBAAgB,GAAG,SAAS,GACzC,gBAAgB,GAAG,SAAS,CA8B9B;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAE5F;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC3C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqBd"}
@@ -1,6 +1,13 @@
1
1
  const MAX_LIMIT = 2000;
2
2
  const DEFAULT_LIMIT = 100;
3
3
  const METADATA_ERROR_MESSAGE = "Failed to fetch service metadata.";
4
+ /**
5
+ * Esri geometry-type tags.
6
+ *
7
+ * These are the SDK's canonical geometry-operand tags, not part of the tool
8
+ * contract: the tool surface takes GeoJSON (see `neutral/filter.ts`) and only
9
+ * accepts these to disambiguate Esri-JSON geometry sent by legacy clients.
10
+ */
4
11
  export const GEOMETRY_TYPES = [
5
12
  "esriGeometryPoint",
6
13
  "esriGeometryPolyline",
@@ -8,19 +15,6 @@ export const GEOMETRY_TYPES = [
8
15
  "esriGeometryEnvelope",
9
16
  "esriGeometryMultipoint",
10
17
  ];
11
- const SPATIAL_REL_MAP = {
12
- intersects: "esriSpatialRelIntersects",
13
- contains: "esriSpatialRelContains",
14
- within: "esriSpatialRelWithin",
15
- };
16
- export function mapSpatialRel(rel) {
17
- if (!rel)
18
- return undefined;
19
- const mapped = SPATIAL_REL_MAP[rel];
20
- if (!mapped)
21
- throw new Error(`Unknown spatialRel "${rel}". Expected: intersects, contains, within`);
22
- return mapped;
23
- }
24
18
  export function clampLimit(limit) {
25
19
  const n = limit ?? DEFAULT_LIMIT;
26
20
  return Math.min(Math.max(1, n), MAX_LIMIT);
@@ -55,28 +49,6 @@ export function jsonText(result) {
55
49
  export function metadataErrorText() {
56
50
  return METADATA_ERROR_MESSAGE;
57
51
  }
58
- /**
59
- * Coerce a feature `count` from a query response into a usable value.
60
- *
61
- * The default `grpc-web` transport returns counts via `toSafeNumberOrString`,
62
- * which yields a **string** when the proto count exceeds
63
- * `Number.MAX_SAFE_INTEGER` (chosen specifically to avoid precision loss). Both
64
- * a finite `number` and a numeric `string` are valid counts; anything else
65
- * (non-finite numbers, non-numeric strings, missing values) returns
66
- * `undefined`.
67
- */
68
- export function coerceCount(value) {
69
- if (typeof value === "number") {
70
- return Number.isFinite(value) ? value : undefined;
71
- }
72
- if (typeof value === "string") {
73
- // Accept canonical base-10 integer strings (optionally signed). These are
74
- // the large-count representation the grpc adapter emits to preserve
75
- // precision beyond Number.MAX_SAFE_INTEGER.
76
- return /^-?\d+$/.test(value.trim()) ? value.trim() : undefined;
77
- }
78
- return undefined;
79
- }
80
52
  export function encodeServiceId(serviceId) {
81
53
  return encodeURIComponent(serviceId);
82
54
  }
@@ -22,13 +22,20 @@ export declare function createClientFromEnv(env?: NodeJS.ProcessEnv): HonuaClien
22
22
  /**
23
23
  * The PLATFORM-FREE standalone MCP surface (issue #369).
24
24
  *
25
- * This is the direct-SDK operator surface: nine read-only geospatial tools wired
26
- * straight onto `@honua/sdk-js`, which speaks plain Esri GeoServices / OGC API
27
- * over standard REST paths (`/rest/services`, `/FeatureServer/{id}/query`, …).
28
- * Point it at ANY public FeatureServer / OGC endpoint (e.g. a `services.arcgis.com`
29
- * FeatureServer) with ZERO Honua-server assumptions — no admin API, no `/healthz`,
30
- * no `/mcp` catalog. This is what the `honua-mcp` bin runs (see {@link runStandalone}),
31
- * and what the platform-free certification lane + eval corpus exercise.
25
+ * This is the direct-SDK operator surface: ten read-only geospatial tools wired
26
+ * straight onto `@honua/sdk-js`. Point it at ANY public FeatureServer / OGC API /
27
+ * STAC / WFS / OData endpoint with ZERO Honua-server assumptions — no admin API,
28
+ * no `/healthz`, no `/mcp` catalog. This is what the `honua-mcp` bin runs (see
29
+ * {@link runStandalone}), and what the platform-free certification lanes + eval
30
+ * corpora exercise.
31
+ *
32
+ * The tool contract is PROTOCOL-NEUTRAL (issue #1005): sources are addressed by
33
+ * a `<protocol>:<address>` reference (`honua_list_sources` emits them), filters
34
+ * are the SDK's typed semantic filter, geometry is GeoJSON, and execution goes
35
+ * through the canonical `Dataset`/`Source`/`Query`/`Result` contract so one call
36
+ * means the same thing on every protocol. The GeoServices `serviceId`/`layerId`
37
+ * pair is still accepted as a deprecated compatibility input; no tool schema
38
+ * requires an Esri-only field.
32
39
  *
33
40
  * Tools that need a Honua-only surface (server-side styling via OGC API – Styles,
34
41
  * a `/rest/services` catalog) degrade gracefully on a plain target: they return a
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF,OAAO,EAAE,KAAK,mBAAmB,EAAgC,MAAM,qBAAqB,CAAC;AAc7F,OAAO,EACL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gCAAgC,EAChC,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAMD,eAAO,MAAM,cAAc,QAAyB,CAAC;AA2DrD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,CAgD5E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CASrF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,mBAAwB,aA6FlF;AAMD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF,OAAO,EAAE,KAAK,mBAAmB,EAAgC,MAAM,qBAAqB,CAAC;AAe7F,OAAO,EACL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gCAAgC,EAChC,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAMD,eAAO,MAAM,cAAc,QAAyB,CAAC;AA2DrD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,CAgD5E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,WAAW,CASrF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,mBAAwB,aAsGlF;AAMD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvF"}