@honua/mcp-server 0.1.1-beta.0 → 0.1.4-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +202 -7
- package/dist/src/certification/census-fixture-client.d.ts.map +1 -1
- package/dist/src/certification/census-fixture-client.js +5 -0
- package/dist/src/certification/certifier.d.ts +1 -1
- package/dist/src/certification/certifier.d.ts.map +1 -1
- package/dist/src/certification/certifier.js +28 -20
- package/dist/src/certification/cli.js +1 -1
- package/dist/src/certification/cql2.d.ts +24 -0
- package/dist/src/certification/cql2.d.ts.map +1 -0
- package/dist/src/certification/cql2.js +354 -0
- package/dist/src/certification/fixtures.d.ts +8 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -1
- package/dist/src/certification/fixtures.js +26 -10
- package/dist/src/certification/ogc-data.d.ts +30 -0
- package/dist/src/certification/ogc-data.d.ts.map +1 -0
- package/dist/src/certification/ogc-data.js +726 -0
- package/dist/src/certification/ogc-fixture-client.d.ts +4 -0
- package/dist/src/certification/ogc-fixture-client.d.ts.map +1 -0
- package/dist/src/certification/ogc-fixture-client.js +190 -0
- package/dist/src/certification/target.d.ts +10 -1
- package/dist/src/certification/target.d.ts.map +1 -1
- package/dist/src/certification/target.js +45 -2
- package/dist/src/eval/cli.js +6 -3
- package/dist/src/eval/corpus.d.ts +7 -0
- package/dist/src/eval/corpus.d.ts.map +1 -1
- package/dist/src/eval/corpus.js +12 -0
- package/dist/src/eval/ogc-corpus.d.ts +3 -0
- package/dist/src/eval/ogc-corpus.d.ts.map +1 -0
- package/dist/src/eval/ogc-corpus.js +437 -0
- package/dist/src/eval/runner.d.ts +6 -0
- package/dist/src/eval/runner.d.ts.map +1 -1
- package/dist/src/eval/runner.js +21 -7
- package/dist/src/helpers.d.ts +8 -13
- package/dist/src/helpers.d.ts.map +1 -1
- package/dist/src/helpers.js +7 -35
- package/dist/src/index.d.ts +14 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -15
- package/dist/src/neutral/errors.d.ts +73 -0
- package/dist/src/neutral/errors.d.ts.map +1 -0
- package/dist/src/neutral/errors.js +112 -0
- package/dist/src/neutral/filter.d.ts +108 -0
- package/dist/src/neutral/filter.d.ts.map +1 -0
- package/dist/src/neutral/filter.js +271 -0
- package/dist/src/neutral/query.d.ts +101 -0
- package/dist/src/neutral/query.d.ts.map +1 -0
- package/dist/src/neutral/query.js +180 -0
- package/dist/src/neutral/result.d.ts +33 -0
- package/dist/src/neutral/result.d.ts.map +1 -0
- package/dist/src/neutral/result.js +38 -0
- package/dist/src/neutral/source-ref.d.ts +126 -0
- package/dist/src/neutral/source-ref.d.ts.map +1 -0
- package/dist/src/neutral/source-ref.js +267 -0
- package/dist/src/nl-map-control.d.ts.map +1 -1
- package/dist/src/tools/count-features.d.ts +85 -11
- package/dist/src/tools/count-features.d.ts.map +1 -1
- package/dist/src/tools/count-features.js +49 -31
- package/dist/src/tools/describe-layer.d.ts +30 -6
- package/dist/src/tools/describe-layer.d.ts.map +1 -1
- package/dist/src/tools/describe-layer.js +99 -8
- package/dist/src/tools/get-extent.d.ts +69 -11
- package/dist/src/tools/get-extent.d.ts.map +1 -1
- package/dist/src/tools/get-extent.js +28 -46
- package/dist/src/tools/list-services.d.ts +7 -0
- package/dist/src/tools/list-services.d.ts.map +1 -1
- package/dist/src/tools/list-services.js +8 -1
- package/dist/src/tools/list-sources.d.ts +44 -0
- package/dist/src/tools/list-sources.d.ts.map +1 -0
- package/dist/src/tools/list-sources.js +138 -0
- package/dist/src/tools/query-features.d.ts +99 -19
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +38 -45
- package/dist/src/tools/statistics.d.ts +80 -12
- package/dist/src/tools/statistics.d.ts.map +1 -1
- package/dist/src/tools/statistics.js +56 -23
- package/package.json +31 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ogc-fixture-client.d.ts","sourceRoot":"","sources":["../../../src/certification/ogc-fixture-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA2HjD,uEAAuE;AACvE,wBAAgB,sBAAsB,IAAI,WAAW,CAgGpD"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { compileCql2 } from "./cql2.js";
|
|
2
|
+
import { OGC_COLLECTIONS, OGC_COLLECTION_IDS, OGC_ENDPOINT } from "./ogc-data.js";
|
|
3
|
+
/**
|
|
4
|
+
* Offline NON-GeoServices fixture `HonuaClient` (issue #1005).
|
|
5
|
+
*
|
|
6
|
+
* Where `census-fixture-client.ts` models a plain Esri FeatureServer, this one
|
|
7
|
+
* models a plain **OGC API Features** endpoint — no `/rest/services`, no
|
|
8
|
+
* FeatureServer query surface, no Honua surfaces — replaying the recorded
|
|
9
|
+
* pygeoapi demo collections (`ogc-data.ts`) through an in-process evaluator that
|
|
10
|
+
* honours the OGC API Features Part 1 query parameters (`limit`, `offset`,
|
|
11
|
+
* `bbox`, `datetime`, `properties`, `sortby`) plus the Part 3 `filter` +
|
|
12
|
+
* `filter-lang=cql2-text` the neutral tool contract compiles to.
|
|
13
|
+
*
|
|
14
|
+
* Its whole purpose is to prove the protocol-neutral tool contract on a
|
|
15
|
+
* protocol whose vocabulary shares nothing with GeoServices: the GeoServices
|
|
16
|
+
* entry points all reject, so a tool that still secretly depended on
|
|
17
|
+
* `serviceId`/`layerId` fails loudly here instead of passing by accident.
|
|
18
|
+
*/
|
|
19
|
+
const NO_GEOSERVICES = "This endpoint is OGC API Features only; it publishes no GeoServices/FeatureServer surface.";
|
|
20
|
+
function requireCollection(collectionId) {
|
|
21
|
+
const collection = OGC_COLLECTIONS[String(collectionId)];
|
|
22
|
+
if (!collection) {
|
|
23
|
+
throw new Error(`OGC API Features fixture: unknown collection "${String(collectionId)}"`);
|
|
24
|
+
}
|
|
25
|
+
return collection;
|
|
26
|
+
}
|
|
27
|
+
function geometryBbox(geometry) {
|
|
28
|
+
if (!geometry)
|
|
29
|
+
return undefined;
|
|
30
|
+
const coordinates = geometry.coordinates;
|
|
31
|
+
const xs = [];
|
|
32
|
+
const ys = [];
|
|
33
|
+
const visit = (node) => {
|
|
34
|
+
if (!Array.isArray(node))
|
|
35
|
+
return;
|
|
36
|
+
if (typeof node[0] === "number" && typeof node[1] === "number") {
|
|
37
|
+
xs.push(node[0]);
|
|
38
|
+
ys.push(node[1]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const child of node)
|
|
42
|
+
visit(child);
|
|
43
|
+
};
|
|
44
|
+
visit(coordinates);
|
|
45
|
+
if (xs.length === 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];
|
|
48
|
+
}
|
|
49
|
+
function bboxIntersects(feature, bbox) {
|
|
50
|
+
const [minX, minY, maxX, maxY] = bbox.split(",").map(Number);
|
|
51
|
+
const box = geometryBbox(feature.geometry);
|
|
52
|
+
if (!box)
|
|
53
|
+
return false;
|
|
54
|
+
return !(box[2] < minX || box[0] > maxX || box[3] < minY || box[1] > maxY);
|
|
55
|
+
}
|
|
56
|
+
/** The collection's own time dimension, when it publishes one. */
|
|
57
|
+
function timeField(collectionId) {
|
|
58
|
+
const properties = OGC_COLLECTIONS[collectionId]?.queryables ?? {};
|
|
59
|
+
return "datetime" in properties ? "datetime" : undefined;
|
|
60
|
+
}
|
|
61
|
+
function withinDatetime(feature, datetime, field) {
|
|
62
|
+
const raw = feature.properties?.[field];
|
|
63
|
+
if (typeof raw !== "string")
|
|
64
|
+
return false;
|
|
65
|
+
const value = Date.parse(raw);
|
|
66
|
+
if (Number.isNaN(value))
|
|
67
|
+
return false;
|
|
68
|
+
const [rawStart, rawEnd] = datetime.includes("/") ? datetime.split("/") : [datetime, datetime];
|
|
69
|
+
const start = rawStart === ".." || rawStart === "" ? Number.NEGATIVE_INFINITY : Date.parse(rawStart);
|
|
70
|
+
const end = rawEnd === ".." || rawEnd === "" ? Number.POSITIVE_INFINITY : Date.parse(rawEnd);
|
|
71
|
+
return value >= start && value <= end;
|
|
72
|
+
}
|
|
73
|
+
function sortFeatures(features, sortby) {
|
|
74
|
+
const specs = sortby
|
|
75
|
+
.split(",")
|
|
76
|
+
.map((part) => part.trim())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.map((part) => part.startsWith("-") ? { field: part.slice(1), descending: true } : { field: part, descending: false });
|
|
79
|
+
return [...features].sort((a, b) => {
|
|
80
|
+
for (const spec of specs) {
|
|
81
|
+
const av = a.properties?.[spec.field];
|
|
82
|
+
const bv = b.properties?.[spec.field];
|
|
83
|
+
if (av === bv)
|
|
84
|
+
continue;
|
|
85
|
+
const less = typeof av === "number" && typeof bv === "number" ? av < bv : String(av) < String(bv);
|
|
86
|
+
return (less ? -1 : 1) * (spec.descending ? -1 : 1);
|
|
87
|
+
}
|
|
88
|
+
return 0;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function projectProperties(feature, properties) {
|
|
92
|
+
if (properties === undefined)
|
|
93
|
+
return feature;
|
|
94
|
+
const requested = (Array.isArray(properties) ? properties : String(properties).split(","))
|
|
95
|
+
.map((name) => name.trim())
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
if (requested.length === 0 || requested.includes("*"))
|
|
98
|
+
return feature;
|
|
99
|
+
const projected = {};
|
|
100
|
+
for (const name of requested) {
|
|
101
|
+
if (feature.properties && name in feature.properties)
|
|
102
|
+
projected[name] = feature.properties[name];
|
|
103
|
+
}
|
|
104
|
+
return { ...feature, properties: projected };
|
|
105
|
+
}
|
|
106
|
+
/** Build the offline platform-free OGC API Features fixture client. */
|
|
107
|
+
export function createOgcFixtureClient() {
|
|
108
|
+
const client = {
|
|
109
|
+
serverBaseUrl: OGC_ENDPOINT,
|
|
110
|
+
async listOgcCollections(_request = {}) {
|
|
111
|
+
return {
|
|
112
|
+
collections: OGC_COLLECTION_IDS.map((id) => OGC_COLLECTIONS[id].collection),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
async getOgcCollection(request) {
|
|
116
|
+
return requireCollection(request.collectionId).collection;
|
|
117
|
+
},
|
|
118
|
+
async getOgcQueryables(request) {
|
|
119
|
+
const collection = requireCollection(request.collectionId);
|
|
120
|
+
if (!collection.queryables) {
|
|
121
|
+
throw new Error(`OGC API Features fixture: collection "${String(request.collectionId)}" publishes no queryables`);
|
|
122
|
+
}
|
|
123
|
+
return { type: "object", properties: collection.queryables };
|
|
124
|
+
},
|
|
125
|
+
async listOgcItems(request) {
|
|
126
|
+
const collectionId = String(request.collectionId);
|
|
127
|
+
const collection = requireCollection(collectionId);
|
|
128
|
+
let features = collection.features;
|
|
129
|
+
if (request.filter !== undefined && request.filter !== "") {
|
|
130
|
+
if (request.filterLang !== undefined && request.filterLang !== "cql2-text") {
|
|
131
|
+
throw new Error(`OGC API Features fixture: unsupported filter-lang "${String(request.filterLang)}"`);
|
|
132
|
+
}
|
|
133
|
+
const predicate = compileCql2(request.filter);
|
|
134
|
+
features = features.filter((feature) => predicate(feature.properties ?? {}));
|
|
135
|
+
}
|
|
136
|
+
if (request.bbox !== undefined) {
|
|
137
|
+
features = features.filter((feature) => bboxIntersects(feature, request.bbox));
|
|
138
|
+
}
|
|
139
|
+
if (request.datetime !== undefined) {
|
|
140
|
+
const field = timeField(collectionId);
|
|
141
|
+
if (!field) {
|
|
142
|
+
throw new Error(`OGC API Features fixture: collection "${collectionId}" has no time dimension`);
|
|
143
|
+
}
|
|
144
|
+
features = features.filter((feature) => withinDatetime(feature, request.datetime, field));
|
|
145
|
+
}
|
|
146
|
+
if (request.ids !== undefined) {
|
|
147
|
+
const ids = (Array.isArray(request.ids) ? request.ids : String(request.ids).split(",")).map(String);
|
|
148
|
+
features = features.filter((feature) => ids.includes(String(feature.id)));
|
|
149
|
+
}
|
|
150
|
+
const numberMatched = features.length;
|
|
151
|
+
if (request.sortby)
|
|
152
|
+
features = sortFeatures(features, request.sortby);
|
|
153
|
+
const offset = request.offset ?? 0;
|
|
154
|
+
// OGC API Features servers apply a default and a maximum page size
|
|
155
|
+
// (Part 1 §7.15.4). The default mirrors the pygeoapi demo's 10; the
|
|
156
|
+
// maximum is generous so a client that asks to drain a collection
|
|
157
|
+
// actually drains it, rather than silently aggregating one page.
|
|
158
|
+
const limit = Math.min(request.limit ?? 10, 1000);
|
|
159
|
+
const page = features
|
|
160
|
+
.slice(offset, offset + limit)
|
|
161
|
+
.map((feature) => projectProperties(feature, request.properties));
|
|
162
|
+
return {
|
|
163
|
+
type: "FeatureCollection",
|
|
164
|
+
features: page,
|
|
165
|
+
numberMatched,
|
|
166
|
+
numberReturned: page.length,
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
// ── Surfaces this endpoint does NOT publish ──────────────────
|
|
170
|
+
async listServices() {
|
|
171
|
+
throw new Error(NO_GEOSERVICES);
|
|
172
|
+
},
|
|
173
|
+
async getFeatureServiceMetadata() {
|
|
174
|
+
throw new Error(NO_GEOSERVICES);
|
|
175
|
+
},
|
|
176
|
+
async getLayerMetadata() {
|
|
177
|
+
throw new Error(NO_GEOSERVICES);
|
|
178
|
+
},
|
|
179
|
+
async queryFeatures() {
|
|
180
|
+
throw new Error(NO_GEOSERVICES);
|
|
181
|
+
},
|
|
182
|
+
async pipelineFetch(_method, _path) {
|
|
183
|
+
return new Response(JSON.stringify({ error: { code: 404, message: "not found" } }), {
|
|
184
|
+
status: 404,
|
|
185
|
+
headers: { "Content-Type": "application/json" },
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
return client;
|
|
190
|
+
}
|
|
@@ -20,8 +20,17 @@ import { type ProxyOptions } from "../proxy.js";
|
|
|
20
20
|
* of a PLAIN public FeatureServer (recorded census layer, no
|
|
21
21
|
* Honua surfaces). Proves the tools certify green against a
|
|
22
22
|
* non-Honua endpoint, with Honua-only tools skip-with-reason.
|
|
23
|
+
* - `standalone-ogc` : the same surface against a NON-GeoServices endpoint
|
|
24
|
+
* (issue #1005) — an in-process fixture of a plain OGC API
|
|
25
|
+
* Features server replaying recorded pygeoapi collections.
|
|
26
|
+
* Nothing Esri-shaped exists there (the GeoServices entry
|
|
27
|
+
* points all reject), so it proves the tool contract is
|
|
28
|
+
* genuinely protocol-neutral rather than GeoServices with
|
|
29
|
+
* extra fields.
|
|
23
30
|
*/
|
|
24
|
-
export type CertTargetMode = "offline" | "remote" | "stdio-proxy" | "standalone";
|
|
31
|
+
export type CertTargetMode = "offline" | "remote" | "stdio-proxy" | "standalone" | "standalone-ogc";
|
|
32
|
+
/** Target modes that certify the platform-free `createServer` surface. */
|
|
33
|
+
export declare function isStandaloneTargetMode(mode: CertTargetMode): boolean;
|
|
25
34
|
export interface CertificationTarget {
|
|
26
35
|
mode: CertTargetMode;
|
|
27
36
|
backend: "mock-upstream" | "live";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../../../src/certification/target.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../../../src/certification/target.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,aAAa,CAAC;AAMjE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,gBAAgB,CAAC;AAEpG,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAEpE;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,eAAe,GAAG,MAAM,CAAC;IAClC,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,2BAA2B,EAAE,OAAO,CAAC;IACrC,gFAAgF;IAChF,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,cAAc,CActF;AAED,6EAA6E;AAC7E,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,YAAY,CAc9E;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,IAAI,MAAM,CAc1C;AAED,gDAAgD;AAChD,wBAAsB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAehH"}
|
|
@@ -7,12 +7,22 @@ import { createClientFromEnv, createServer } from "../index.js";
|
|
|
7
7
|
import { connectUpstream } from "../proxy.js";
|
|
8
8
|
import { createStandaloneFixtureClient } from "./census-fixture-client.js";
|
|
9
9
|
import { startMockUpstream } from "./mock-upstream.js";
|
|
10
|
+
import { OGC_ENDPOINT } from "./ogc-data.js";
|
|
11
|
+
import { createOgcFixtureClient } from "./ogc-fixture-client.js";
|
|
12
|
+
/** Target modes that certify the platform-free `createServer` surface. */
|
|
13
|
+
export function isStandaloneTargetMode(mode) {
|
|
14
|
+
return mode === "standalone" || mode === "standalone-ogc";
|
|
15
|
+
}
|
|
10
16
|
export function resolveTargetMode(env = process.env) {
|
|
11
17
|
const raw = (env.HONUA_MCP_CERT_TARGET ?? "offline").trim().toLowerCase();
|
|
12
|
-
if (raw === "offline" ||
|
|
18
|
+
if (raw === "offline" ||
|
|
19
|
+
raw === "remote" ||
|
|
20
|
+
raw === "stdio-proxy" ||
|
|
21
|
+
raw === "standalone" ||
|
|
22
|
+
raw === "standalone-ogc") {
|
|
13
23
|
return raw;
|
|
14
24
|
}
|
|
15
|
-
throw new Error(`HONUA_MCP_CERT_TARGET must be "offline", "remote", "stdio-proxy", or "standalone", received "${raw}"`);
|
|
25
|
+
throw new Error(`HONUA_MCP_CERT_TARGET must be "offline", "remote", "stdio-proxy", "standalone", or "standalone-ogc", received "${raw}"`);
|
|
16
26
|
}
|
|
17
27
|
/** Resolve the remote `/mcp` endpoint + credentials from the environment. */
|
|
18
28
|
export function resolveRemoteProxyOptions(env) {
|
|
@@ -56,8 +66,41 @@ export async function openCertificationTarget(env = process.env) {
|
|
|
56
66
|
if (mode === "standalone") {
|
|
57
67
|
return openStandaloneTarget(env);
|
|
58
68
|
}
|
|
69
|
+
if (mode === "standalone-ogc") {
|
|
70
|
+
return openStandaloneOgcTarget(env);
|
|
71
|
+
}
|
|
59
72
|
return openOfflineTarget(env);
|
|
60
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Open the NON-GeoServices standalone target (#1005): the same platform-free
|
|
76
|
+
* `createServer` surface, wired to an in-process fixture of a plain OGC API
|
|
77
|
+
* Features endpoint (recorded pygeoapi collections). The GeoServices entry
|
|
78
|
+
* points on that fixture all reject, so any tool still requiring Esri
|
|
79
|
+
* addressing fails here rather than passing by accident.
|
|
80
|
+
*/
|
|
81
|
+
async function openStandaloneOgcTarget(env) {
|
|
82
|
+
const server = createServer(createOgcFixtureClient());
|
|
83
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
84
|
+
const client = new Client({ name: "honua-mcp-certifier", version: "1.0.0" });
|
|
85
|
+
await server.connect(serverTransport);
|
|
86
|
+
await client.connect(clientTransport);
|
|
87
|
+
return {
|
|
88
|
+
mode: "standalone-ogc",
|
|
89
|
+
backend: "mock-upstream",
|
|
90
|
+
serverLabel: `honua-mcp standalone → ${OGC_ENDPOINT} (recorded OGC API Features fixture, no GeoServices surface)`,
|
|
91
|
+
authMode: "anonymous",
|
|
92
|
+
honuaTransport: env.HONUA_TRANSPORT ?? null,
|
|
93
|
+
client,
|
|
94
|
+
supportsUnauthenticatedPass: false,
|
|
95
|
+
connectUnauthenticated() {
|
|
96
|
+
return Promise.reject(new Error("standalone-ogc target has no auth boundary"));
|
|
97
|
+
},
|
|
98
|
+
async close() {
|
|
99
|
+
await client.close().catch(() => { });
|
|
100
|
+
await server.close().catch(() => { });
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
61
104
|
/**
|
|
62
105
|
* Open the platform-free standalone target: the direct-SDK `createServer` surface,
|
|
63
106
|
* reached over an in-memory MCP transport. By default it is wired to an in-process
|
package/dist/src/eval/cli.js
CHANGED
|
@@ -22,10 +22,12 @@ import { runEval } from "./runner.js";
|
|
|
22
22
|
* explicit request yields an honest driverError, not a silent
|
|
23
23
|
* skip. Without --driver, drivers are auto-resolved from env.
|
|
24
24
|
* --corpus <name> Select the corpus explicitly: analyst | operator | northstar |
|
|
25
|
-
* all. This overrides HONUA_EVAL_CORPUS and is how the `eval:live`
|
|
25
|
+
* standalone | ogc | all. This overrides HONUA_EVAL_CORPUS and is how the `eval:live`
|
|
26
26
|
* script targets the operator surface without relying on shell env
|
|
27
27
|
* (cross-platform). `northstar` targets the three P1-gate
|
|
28
|
-
* workflows
|
|
28
|
+
* workflows; `standalone` and `ogc` target the two platform-free
|
|
29
|
+
* fixture surfaces (a plain Esri FeatureServer and a plain OGC API
|
|
30
|
+
* Features endpoint). Defaults to the env-driven selection.
|
|
29
31
|
*
|
|
30
32
|
* Corpus selection is env-driven (see `resolveCorpus`): default is the analyst
|
|
31
33
|
* corpus (in-process SDK fixture surface, the CI control); `HONUA_EVAL_CORPUS=operator`
|
|
@@ -37,7 +39,7 @@ import { runEval } from "./runner.js";
|
|
|
37
39
|
* chain), and live remote MCP runs when HONUA_MCP_REMOTE_URL is set. Keys are
|
|
38
40
|
* never hardcoded — they come from the environment.
|
|
39
41
|
*/
|
|
40
|
-
const CORPUS_NAMES = ["analyst", "operator", "northstar", "standalone", "all"];
|
|
42
|
+
const CORPUS_NAMES = ["analyst", "operator", "northstar", "standalone", "ogc", "all"];
|
|
41
43
|
function parseArgs(argv) {
|
|
42
44
|
let artifactOnly = false;
|
|
43
45
|
let offline = false;
|
|
@@ -93,6 +95,7 @@ async function main() {
|
|
|
93
95
|
...(drivers.length > 0 ? { drivers: selectDrivers(drivers) } : {}),
|
|
94
96
|
...(corpus ? { corpus: corpusForName(corpus) } : {}),
|
|
95
97
|
...(corpus === "standalone" ? { forceStandaloneSurface: true } : {}),
|
|
98
|
+
...(corpus === "ogc" ? { forceOgcSurface: true } : {}),
|
|
96
99
|
});
|
|
97
100
|
mkdirSync(outDir, { recursive: true });
|
|
98
101
|
const jsonPath = resolve(outDir, "mcp-eval-results.json");
|
|
@@ -31,4 +31,11 @@ export declare function resolveCorpus(env?: NodeJS.ProcessEnv): Scenario[];
|
|
|
31
31
|
* corpus resolves against real recorded non-Honua data.
|
|
32
32
|
*/
|
|
33
33
|
export declare function isStandaloneCorpus(env?: NodeJS.ProcessEnv): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a corpus selection targets the NON-GeoServices surface (issue #1005) —
|
|
36
|
+
* a plain OGC API Features endpoint replayed from recorded pygeoapi collections.
|
|
37
|
+
* The offline runner uses this to pick the OGC fixture client, on which every
|
|
38
|
+
* GeoServices entry point rejects.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isOgcCorpus(env?: NodeJS.ProcessEnv): boolean;
|
|
34
41
|
//# sourceMappingURL=corpus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/corpus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/corpus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,EAAE,QAAQ,EAkG5B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,EAAE,CAmB9E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEzE"}
|
package/dist/src/eval/corpus.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NORTHSTAR_CORPUS } from "./northstar-corpus.js";
|
|
2
|
+
import { OGC_CORPUS } from "./ogc-corpus.js";
|
|
2
3
|
import { OPERATOR_CORPUS } from "./operator-corpus.js";
|
|
3
4
|
import { STANDALONE_CORPUS } from "./standalone-corpus.js";
|
|
4
5
|
/**
|
|
@@ -129,6 +130,8 @@ export function resolveCorpus(env = process.env) {
|
|
|
129
130
|
return NORTHSTAR_CORPUS;
|
|
130
131
|
case "standalone":
|
|
131
132
|
return STANDALONE_CORPUS;
|
|
133
|
+
case "ogc":
|
|
134
|
+
return OGC_CORPUS;
|
|
132
135
|
case "all":
|
|
133
136
|
// `all` spans the Honua-surface corpora only. The platform-free standalone
|
|
134
137
|
// corpus targets a DIFFERENT surface (a plain FeatureServer fixture) and is
|
|
@@ -148,3 +151,12 @@ export function resolveCorpus(env = process.env) {
|
|
|
148
151
|
export function isStandaloneCorpus(env = process.env) {
|
|
149
152
|
return (env.HONUA_EVAL_CORPUS ?? "").trim().toLowerCase() === "standalone";
|
|
150
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether a corpus selection targets the NON-GeoServices surface (issue #1005) —
|
|
156
|
+
* a plain OGC API Features endpoint replayed from recorded pygeoapi collections.
|
|
157
|
+
* The offline runner uses this to pick the OGC fixture client, on which every
|
|
158
|
+
* GeoServices entry point rejects.
|
|
159
|
+
*/
|
|
160
|
+
export function isOgcCorpus(env = process.env) {
|
|
161
|
+
return (env.HONUA_EVAL_CORPUS ?? "").trim().toLowerCase() === "ogc";
|
|
162
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ogc-corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/ogc-corpus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAqC3C,eAAO,MAAM,UAAU,EAAE,QAAQ,EAgahC,CAAC"}
|