@honua/mcp-server 0.1.2-beta.0 → 0.1.8-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 +232 -9
- package/dist/src/certification/admin-parity.d.ts +96 -0
- package/dist/src/certification/admin-parity.d.ts.map +1 -0
- package/dist/src/certification/admin-parity.js +235 -0
- 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 +63 -28
- package/dist/src/credential-endpoint.d.ts +3 -0
- package/dist/src/credential-endpoint.d.ts.map +1 -0
- package/dist/src/credential-endpoint.js +24 -0
- 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 +36 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +55 -18
- 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/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +29 -14
- package/dist/src/release/zero-to-map-checkpoint.d.ts +34 -0
- package/dist/src/release/zero-to-map-checkpoint.d.ts.map +1 -0
- package/dist/src/release/zero-to-map-checkpoint.js +369 -0
- package/dist/src/release/zero-to-map-cli.d.ts +51 -0
- package/dist/src/release/zero-to-map-cli.d.ts.map +1 -0
- package/dist/src/release/zero-to-map-cli.js +880 -0
- package/dist/src/release/zero-to-map-provision.d.ts +37 -0
- package/dist/src/release/zero-to-map-provision.d.ts.map +1 -0
- package/dist/src/release/zero-to-map-provision.js +177 -0
- package/dist/src/release/zero-to-map.d.ts +296 -0
- package/dist/src/release/zero-to-map.d.ts.map +1 -0
- package/dist/src/release/zero-to-map.js +1069 -0
- package/dist/src/tools/admin-install-local.d.ts +30 -0
- package/dist/src/tools/admin-install-local.d.ts.map +1 -0
- package/dist/src/tools/admin-install-local.js +22 -0
- package/dist/src/tools/control-plane-command.d.ts +99 -0
- package/dist/src/tools/control-plane-command.d.ts.map +1 -0
- package/dist/src/tools/control-plane-command.js +180 -0
- 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/docs-search.d.ts +100 -0
- package/dist/src/tools/docs-search.d.ts.map +1 -0
- package/dist/src/tools/docs-search.js +381 -0
- 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 +37 -8
- package/release/zero-to-map/README.md +174 -0
- package/release/zero-to-map/configs/claude-code.mcp.json +11 -0
- package/release/zero-to-map/configs/claude-desktop.json +11 -0
- package/release/zero-to-map/configs/cursor.json +11 -0
- package/release/zero-to-map/contracts/aws-ecs-provision-binding.schema.json +59 -0
- package/release/zero-to-map/contracts/checkpoint.schema.json +152 -0
- package/release/zero-to-map/contracts/console-receipt.schema.json +150 -0
- package/release/zero-to-map/contracts/esri-gp-mcp.v1.json +73 -0
- package/release/zero-to-map/fixtures/parcels.geojson +33 -0
- package/release/zero-to-map/fixtures/zoning.geojson +24 -0
- package/release/zero-to-map/journey.v1.json +1248 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { ADMIN_MCP_CONTRACT_SERVER_SHA, ADMIN_MCP_COVERAGE_SHA256, ADMIN_MCP_EXCLUDED_OPERATIONS, ADMIN_MCP_EXCLUSION_ROSTER_SHA256, ADMIN_MCP_PUBLISHED_TOOL_NAMES, ADMIN_PUBLISHED_OPERATION_COUNT, MCP_DEFAULT_STATIC_TOOL_COUNT, MCP_DEFAULT_TOTAL_TOOL_COUNT, } from "@honua/sdk-js/control-plane";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
7
|
+
import { connectUpstream, createProxyServer, resolveProxyOptions } from "../proxy.js";
|
|
8
|
+
const DEFAULT_EXPECTATION = {
|
|
9
|
+
publishedToolNames: ADMIN_MCP_PUBLISHED_TOOL_NAMES,
|
|
10
|
+
excludedToolNames: ADMIN_MCP_EXCLUDED_OPERATIONS.map((operation) => operation.toolName),
|
|
11
|
+
expectedStaticTools: MCP_DEFAULT_STATIC_TOOL_COUNT,
|
|
12
|
+
expectedTotalTools: MCP_DEFAULT_TOTAL_TOOL_COUNT,
|
|
13
|
+
};
|
|
14
|
+
export function certifyAdminCatalogParity(directCatalog, proxiedCatalog, expectation = DEFAULT_EXPECTATION) {
|
|
15
|
+
const direct = normalizeAdminTools(directCatalog);
|
|
16
|
+
const proxied = normalizeAdminTools(proxiedCatalog);
|
|
17
|
+
const expectedTools = expectation.publishedToolNames.length;
|
|
18
|
+
const directStaticTools = directCatalog.length - direct.length;
|
|
19
|
+
const proxiedStaticTools = proxiedCatalog.length - proxied.length;
|
|
20
|
+
const differences = [];
|
|
21
|
+
if (expectedTools !== ADMIN_PUBLISHED_OPERATION_COUNT) {
|
|
22
|
+
differences.push(`certification expectation contains ${expectedTools} admin tools; generated contract requires ${ADMIN_PUBLISHED_OPERATION_COUNT}`);
|
|
23
|
+
}
|
|
24
|
+
if (direct.length !== expectedTools) {
|
|
25
|
+
differences.push(`direct catalog exposes ${direct.length} admin tools; expected ${expectedTools}`);
|
|
26
|
+
}
|
|
27
|
+
if (proxied.length !== expectedTools) {
|
|
28
|
+
differences.push(`proxied catalog exposes ${proxied.length} admin tools; expected ${expectedTools}`);
|
|
29
|
+
}
|
|
30
|
+
if (directStaticTools !== expectation.expectedStaticTools) {
|
|
31
|
+
differences.push(`direct catalog exposes ${directStaticTools} static tools; expected ${expectation.expectedStaticTools}`);
|
|
32
|
+
}
|
|
33
|
+
if (proxiedStaticTools !== expectation.expectedStaticTools) {
|
|
34
|
+
differences.push(`proxied catalog exposes ${proxiedStaticTools} static tools; expected ${expectation.expectedStaticTools}`);
|
|
35
|
+
}
|
|
36
|
+
if (directCatalog.length !== expectation.expectedTotalTools) {
|
|
37
|
+
differences.push(`direct catalog exposes ${directCatalog.length} total tools; expected ${expectation.expectedTotalTools}`);
|
|
38
|
+
}
|
|
39
|
+
if (proxiedCatalog.length !== expectation.expectedTotalTools) {
|
|
40
|
+
differences.push(`proxied catalog exposes ${proxiedCatalog.length} total tools; expected ${expectation.expectedTotalTools}`);
|
|
41
|
+
}
|
|
42
|
+
if (expectation.expectedStaticTools + expectedTools !== expectation.expectedTotalTools) {
|
|
43
|
+
differences.push(`certification equation failed: ${expectation.expectedStaticTools} static + ${expectedTools} admin ` +
|
|
44
|
+
`!= ${expectation.expectedTotalTools} total tools`);
|
|
45
|
+
}
|
|
46
|
+
for (const duplicate of findDuplicateNames(direct)) {
|
|
47
|
+
differences.push(`direct catalog repeats admin tool ${duplicate}`);
|
|
48
|
+
}
|
|
49
|
+
for (const duplicate of findDuplicateNames(proxied)) {
|
|
50
|
+
differences.push(`proxied catalog repeats admin tool ${duplicate}`);
|
|
51
|
+
}
|
|
52
|
+
for (const duplicate of findDuplicateNames(directCatalog)) {
|
|
53
|
+
if (!duplicate.startsWith("honua_admin_"))
|
|
54
|
+
differences.push(`direct catalog repeats static tool ${duplicate}`);
|
|
55
|
+
}
|
|
56
|
+
for (const duplicate of findDuplicateNames(proxiedCatalog)) {
|
|
57
|
+
if (!duplicate.startsWith("honua_admin_"))
|
|
58
|
+
differences.push(`proxied catalog repeats static tool ${duplicate}`);
|
|
59
|
+
}
|
|
60
|
+
compareExpectedRoster("direct", direct, expectation, differences);
|
|
61
|
+
compareExpectedRoster("proxied", proxied, expectation, differences);
|
|
62
|
+
const directByName = new Map(direct.map((tool) => [tool.name, tool]));
|
|
63
|
+
const proxiedByName = new Map(proxied.map((tool) => [tool.name, tool]));
|
|
64
|
+
for (const name of [...new Set([...directByName.keys(), ...proxiedByName.keys()])].sort()) {
|
|
65
|
+
const directTool = directByName.get(name);
|
|
66
|
+
const proxiedTool = proxiedByName.get(name);
|
|
67
|
+
if (!directTool)
|
|
68
|
+
differences.push(`${name} is missing from the direct HTTP catalog`);
|
|
69
|
+
else if (!proxiedTool)
|
|
70
|
+
differences.push(`${name} is missing from the proxied catalog`);
|
|
71
|
+
else if (stableJson(directTool) !== stableJson(proxiedTool))
|
|
72
|
+
differences.push(`${name} descriptor differs`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
schemaVersion: "honua.admin-mcp-parity.v1",
|
|
76
|
+
family: "honua_admin_*",
|
|
77
|
+
expectedTools,
|
|
78
|
+
expectedStaticTools: expectation.expectedStaticTools,
|
|
79
|
+
expectedTotalTools: expectation.expectedTotalTools,
|
|
80
|
+
directTools: direct.length,
|
|
81
|
+
proxiedTools: proxied.length,
|
|
82
|
+
directStaticTools,
|
|
83
|
+
proxiedStaticTools,
|
|
84
|
+
directTotalTools: directCatalog.length,
|
|
85
|
+
proxiedTotalTools: proxiedCatalog.length,
|
|
86
|
+
coverageSha256: ADMIN_MCP_COVERAGE_SHA256,
|
|
87
|
+
exclusionRosterSha256: ADMIN_MCP_EXCLUSION_ROSTER_SHA256,
|
|
88
|
+
contractServerSha: ADMIN_MCP_CONTRACT_SERVER_SHA,
|
|
89
|
+
pass: differences.length === 0,
|
|
90
|
+
differences,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function compareExpectedRoster(transport, tools, expectation, differences) {
|
|
94
|
+
const actual = new Set(tools.map((tool) => tool.name));
|
|
95
|
+
const expected = new Set(expectation.publishedToolNames);
|
|
96
|
+
for (const name of [...expected].sort()) {
|
|
97
|
+
if (!actual.has(name))
|
|
98
|
+
differences.push(`${name} is missing from the ${transport} catalog contract`);
|
|
99
|
+
}
|
|
100
|
+
for (const name of [...actual].sort()) {
|
|
101
|
+
if (!expected.has(name))
|
|
102
|
+
differences.push(`${name} is not classified as published in the ${transport} catalog contract`);
|
|
103
|
+
}
|
|
104
|
+
for (const name of [...expectation.excludedToolNames].sort()) {
|
|
105
|
+
if (actual.has(name))
|
|
106
|
+
differences.push(`${transport} catalog advertises explicitly excluded admin tool ${name}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function findDuplicateNames(tools) {
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const duplicates = new Set();
|
|
112
|
+
for (const tool of tools) {
|
|
113
|
+
if (seen.has(tool.name))
|
|
114
|
+
duplicates.add(tool.name);
|
|
115
|
+
else
|
|
116
|
+
seen.add(tool.name);
|
|
117
|
+
}
|
|
118
|
+
return [...duplicates].sort();
|
|
119
|
+
}
|
|
120
|
+
export function normalizeAdminTools(tools) {
|
|
121
|
+
return tools
|
|
122
|
+
.filter((tool) => tool.name.startsWith("honua_admin_"))
|
|
123
|
+
.map((tool) => ({
|
|
124
|
+
name: tool.name,
|
|
125
|
+
title: tool.title,
|
|
126
|
+
description: tool.description,
|
|
127
|
+
inputSchema: tool.inputSchema,
|
|
128
|
+
outputSchema: tool.outputSchema,
|
|
129
|
+
annotations: tool.annotations,
|
|
130
|
+
}))
|
|
131
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Hostile-server safety ceiling on the number of `tools/list` pages a single
|
|
135
|
+
* catalog read may consume.
|
|
136
|
+
*
|
|
137
|
+
* This is deliberately NOT the expected roster size. A roster assertion says
|
|
138
|
+
* "this catalog is wrong"; this ceiling says "this server never stopped
|
|
139
|
+
* paging". Conflating the two produced a single unactionable diagnostic and,
|
|
140
|
+
* worse, made an opt-in profile catalog (base + analysis + Esri GP) impossible
|
|
141
|
+
* to read at all because the *transport* refused any catalog larger than the
|
|
142
|
+
* default roster.
|
|
143
|
+
*/
|
|
144
|
+
export const MCP_TOOLS_LIST_MAX_PAGES = 1_024;
|
|
145
|
+
/**
|
|
146
|
+
* Hostile-server safety ceiling on the number of tools a single catalog read
|
|
147
|
+
* may accumulate. Independent of {@link MCP_DEFAULT_TOTAL_TOOL_COUNT} so that
|
|
148
|
+
* enabling additive server profiles is a roster question, not a transport
|
|
149
|
+
* failure.
|
|
150
|
+
*/
|
|
151
|
+
export const MCP_TOOLS_LIST_MAX_TOOLS = 8_192;
|
|
152
|
+
/** Maximum accepted `tools/list` continuation cursor length, in characters. */
|
|
153
|
+
export const MCP_TOOLS_LIST_MAX_CURSOR_LENGTH = 1_024;
|
|
154
|
+
/**
|
|
155
|
+
* Read the complete MCP tool catalog. The server deliberately pages large
|
|
156
|
+
* catalogs, so a single tools/list response is not certification evidence for
|
|
157
|
+
* the 432-tool default roster (47 static plus 385 Admin MCP tools).
|
|
158
|
+
*
|
|
159
|
+
* Roster size is *not* enforced here. This function is responsible only for
|
|
160
|
+
* retrieving a complete, non-hostile catalog; deciding whether that catalog is
|
|
161
|
+
* the 432-tool default roster or a profile-enabled superset belongs to the
|
|
162
|
+
* certification and preflight callers.
|
|
163
|
+
*/
|
|
164
|
+
export async function listAllTools(client) {
|
|
165
|
+
const tools = [];
|
|
166
|
+
let cursor;
|
|
167
|
+
const seenCursors = new Set();
|
|
168
|
+
let pageCount = 0;
|
|
169
|
+
do {
|
|
170
|
+
pageCount += 1;
|
|
171
|
+
if (pageCount > MCP_TOOLS_LIST_MAX_PAGES) {
|
|
172
|
+
throw new Error(`MCP tools/list exceeded the ${MCP_TOOLS_LIST_MAX_PAGES}-page pagination safety ceiling after ${tools.length} tools; the server did not terminate its cursor chain. This is a pagination fault, not a roster assertion.`);
|
|
173
|
+
}
|
|
174
|
+
const page = await client.listTools(cursor ? { cursor } : undefined);
|
|
175
|
+
if (tools.length + page.tools.length > MCP_TOOLS_LIST_MAX_TOOLS) {
|
|
176
|
+
throw new Error(`MCP tools/list exceeded the ${MCP_TOOLS_LIST_MAX_TOOLS}-tool pagination safety ceiling on page ${pageCount}; the server returned an implausible catalog. This is a pagination fault, not a roster assertion.`);
|
|
177
|
+
}
|
|
178
|
+
tools.push(...page.tools);
|
|
179
|
+
cursor = page.nextCursor;
|
|
180
|
+
if (cursor !== undefined && (cursor.length === 0 || cursor.length > MCP_TOOLS_LIST_MAX_CURSOR_LENGTH)) {
|
|
181
|
+
throw new Error("MCP tools/list returned an invalid bounded cursor");
|
|
182
|
+
}
|
|
183
|
+
if (cursor && seenCursors.has(cursor)) {
|
|
184
|
+
throw new Error(`MCP tools/list repeated cursor ${cursor}`);
|
|
185
|
+
}
|
|
186
|
+
if (cursor && page.tools.length === 0) {
|
|
187
|
+
throw new Error("MCP tools/list returned an empty page with a continuation cursor");
|
|
188
|
+
}
|
|
189
|
+
if (cursor)
|
|
190
|
+
seenCursors.add(cursor);
|
|
191
|
+
} while (cursor);
|
|
192
|
+
return tools;
|
|
193
|
+
}
|
|
194
|
+
export async function runAdminParityCertification(env = process.env, outputPath = "admin-mcp-parity.json") {
|
|
195
|
+
if (ADMIN_MCP_CONTRACT_SERVER_SHA === null) {
|
|
196
|
+
throw new Error("Admin MCP live certification is blocked until config/admin-client.v1.json pins the final server head.");
|
|
197
|
+
}
|
|
198
|
+
const upstream = await connectUpstream(resolveProxyOptions(env));
|
|
199
|
+
const proxy = createProxyServer(upstream);
|
|
200
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
201
|
+
const proxied = new Client({ name: "honua-admin-parity", version: "1.0.0" });
|
|
202
|
+
try {
|
|
203
|
+
await proxy.connect(serverTransport);
|
|
204
|
+
await proxied.connect(clientTransport);
|
|
205
|
+
const directTools = await listAllTools(upstream);
|
|
206
|
+
const proxiedTools = await listAllTools(proxied);
|
|
207
|
+
const receipt = certifyAdminCatalogParity(directTools, proxiedTools);
|
|
208
|
+
await writeFile(outputPath, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
|
|
209
|
+
if (!receipt.pass)
|
|
210
|
+
throw new Error(`Admin MCP parity failed: ${receipt.differences.join("; ")}`);
|
|
211
|
+
return receipt;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
await proxied.close().catch(() => { });
|
|
215
|
+
await proxy.close().catch(() => { });
|
|
216
|
+
await upstream.close().catch(() => { });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function stableJson(value) {
|
|
220
|
+
if (Array.isArray(value))
|
|
221
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
222
|
+
if (value && typeof value === "object") {
|
|
223
|
+
return `{${Object.entries(value)
|
|
224
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
225
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
|
|
226
|
+
.join(",")}}`;
|
|
227
|
+
}
|
|
228
|
+
return JSON.stringify(value) ?? "undefined";
|
|
229
|
+
}
|
|
230
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
231
|
+
runAdminParityCertification(process.env, process.argv[2]).catch((error) => {
|
|
232
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"census-fixture-client.d.ts","sourceRoot":"","sources":["../../../src/certification/census-fixture-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAOjD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AA4F1B,gEAAgE;AAChE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,CAOjE;
|
|
1
|
+
{"version":3,"file":"census-fixture-client.d.ts","sourceRoot":"","sources":["../../../src/certification/census-fixture-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAOjD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AA4F1B,gEAAgE;AAChE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,CAOjE;AA8FD,qFAAqF;AACrF,wBAAgB,6BAA6B,IAAI,WAAW,CAiE3D"}
|
|
@@ -154,6 +154,11 @@ function projectAttributes(row, outFields) {
|
|
|
154
154
|
return { ...row };
|
|
155
155
|
}
|
|
156
156
|
const requested = Array.isArray(outFields) ? outFields : outFields.split(",").map((f) => f.trim());
|
|
157
|
+
// A FeatureServer serializes `outFields` as a comma list, so `["*"]` reaches
|
|
158
|
+
// the wire as `outFields=*` and selects every field. Mirror that here.
|
|
159
|
+
if (requested.some((field) => field.trim() === "*")) {
|
|
160
|
+
return { ...row };
|
|
161
|
+
}
|
|
157
162
|
const attributes = {};
|
|
158
163
|
for (const field of requested) {
|
|
159
164
|
const value = fieldValue(row, field);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { type AuthMode, type SuiteProvenance } from "../provenance.js";
|
|
3
3
|
import { AUTH_ERROR_CODES } from "./contracts.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type CertTargetMode } from "./target.js";
|
|
5
5
|
/** Per-tool certification record (machine-readable). */
|
|
6
6
|
export interface ToolCertification {
|
|
7
7
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certifier.d.ts","sourceRoot":"","sources":["../../../src/certification/certifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EAGrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAMjB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"certifier.d.ts","sourceRoot":"","sources":["../../../src/certification/certifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,9 @@ import { AUTH_ERROR_CODES, checkAuthContract, checkInvalidArgsContract, checkPag
|
|
|
3
3
|
import { buildInvalidArgsInputs, buildRoundTripInputs, resolveRoundTripEnv } from "./fixtures.js";
|
|
4
4
|
import { checkConformance, checkWellFormed, validateAgainstSchema } from "./json-schema.js";
|
|
5
5
|
import { runDeepContracts } from "./lifecycle.js";
|
|
6
|
+
import { OGC_COLLECTION_IDS } from "./ogc-data.js";
|
|
6
7
|
import { PLATFORM_FREE_DEGRADATIONS, buildReferenceToolLookup, loadSchemaFile, loadSchemaIndex, } from "./schema-index.js";
|
|
8
|
+
import { isStandaloneTargetMode } from "./target.js";
|
|
7
9
|
const STANDARD_SOURCE = "geospatial-mcp@54cbd49 (spec/schemas)";
|
|
8
10
|
/** Determine whether a discovered tool is safe to round-trip (read-only). */
|
|
9
11
|
export function isReadOnlyTool(tool) {
|
|
@@ -13,6 +15,20 @@ export function isReadOnlyTool(tool) {
|
|
|
13
15
|
const mutating = /(create|update|delete|publish|apply_.*write|execute|edit|deploy|cancel|propose)/i;
|
|
14
16
|
return !mutating.test(tool.name);
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Bind the round-trip inputs to what the target actually serves.
|
|
20
|
+
*
|
|
21
|
+
* The non-GeoServices lane has no `serviceId`/`layerId` at all, so the
|
|
22
|
+
* source-addressed tools round-trip through the protocol-neutral reference for
|
|
23
|
+
* a recorded OGC collection instead (#1005). An explicit `HONUA_MCP_SOURCE`
|
|
24
|
+
* always wins.
|
|
25
|
+
*/
|
|
26
|
+
function withTargetRoundTripDefaults(env, targetMode) {
|
|
27
|
+
if (targetMode !== "standalone-ogc" || env.sourceRef) {
|
|
28
|
+
return env;
|
|
29
|
+
}
|
|
30
|
+
return { ...env, sourceRef: `ogc-features:${OGC_COLLECTION_IDS[0]}`, statisticField: env.statisticField ?? "value" };
|
|
31
|
+
}
|
|
16
32
|
/**
|
|
17
33
|
* Connect an MCP client to the certified surface and produce a deterministic
|
|
18
34
|
* certification report. The client is already connected (offline mock upstream,
|
|
@@ -20,7 +36,7 @@ export function isReadOnlyTool(tool) {
|
|
|
20
36
|
*/
|
|
21
37
|
export async function certify(options) {
|
|
22
38
|
const { client } = options;
|
|
23
|
-
const roundTripEnv = resolveRoundTripEnv(options.env);
|
|
39
|
+
const roundTripEnv = withTargetRoundTripDefaults(resolveRoundTripEnv(options.env), options.targetMode);
|
|
24
40
|
const roundTripInputs = buildRoundTripInputs(roundTripEnv);
|
|
25
41
|
const index = loadSchemaIndex();
|
|
26
42
|
const referenceLookup = buildReferenceToolLookup(index);
|
|
@@ -31,7 +47,7 @@ export async function certify(options) {
|
|
|
31
47
|
// The platform-free standalone target has no Honua publishing surface, so
|
|
32
48
|
// reference-shape tools (e.g. apply_style_preset) intentionally degrade their
|
|
33
49
|
// platform-bound required properties. Conformance is evaluated accordingly.
|
|
34
|
-
const platformFree = options.targetMode
|
|
50
|
+
const platformFree = isStandaloneTargetMode(options.targetMode);
|
|
35
51
|
const tools = [];
|
|
36
52
|
const advertisedNames = new Set();
|
|
37
53
|
for (const tool of advertisedTools) {
|
|
@@ -47,7 +63,7 @@ export async function certify(options) {
|
|
|
47
63
|
contracts.push(paginationCheck("tools", toolPages));
|
|
48
64
|
contracts.push(paginationCheck("resources", resourcePages));
|
|
49
65
|
collectOutputSchemaContracts(tools, contracts);
|
|
50
|
-
contracts.push(await runErrorShapeContract(client, advertisedTools
|
|
66
|
+
contracts.push(await runErrorShapeContract(client, advertisedTools));
|
|
51
67
|
await runAuthContracts(options, advertisedTools, advertisedResources, contracts);
|
|
52
68
|
// Deep contracts: mutating round-trip, unauthenticated-write refusal, async job
|
|
53
69
|
// lifecycle, and query pagination. Safe by construction — mutations only run
|
|
@@ -155,7 +171,7 @@ async function certifyTool(client, tool, referenceLookup, roundTripInputs, platf
|
|
|
155
171
|
let structuredOutputValidated = null;
|
|
156
172
|
const fixtureInput = roundTripInputs[tool.name];
|
|
157
173
|
if (readOnly && fixtureInput !== undefined) {
|
|
158
|
-
const outcome = await roundTripTool(client, tool, fixtureInput, errors
|
|
174
|
+
const outcome = await roundTripTool(client, tool, fixtureInput, errors);
|
|
159
175
|
roundTrip = outcome.roundTrip;
|
|
160
176
|
structuredOutputValidated = outcome.structuredOutputValidated;
|
|
161
177
|
}
|
|
@@ -179,7 +195,7 @@ async function certifyTool(client, tool, referenceLookup, roundTripInputs, platf
|
|
|
179
195
|
notes,
|
|
180
196
|
};
|
|
181
197
|
}
|
|
182
|
-
async function roundTripTool(client, tool, input, errors
|
|
198
|
+
async function roundTripTool(client, tool, input, errors) {
|
|
183
199
|
try {
|
|
184
200
|
const result = (await client.callTool({ name: tool.name, arguments: input }));
|
|
185
201
|
if (result.isError) {
|
|
@@ -240,21 +256,13 @@ function collectOutputSchemaContracts(tools, contracts) {
|
|
|
240
256
|
});
|
|
241
257
|
}
|
|
242
258
|
}
|
|
243
|
-
async function runErrorShapeContract(client, advertised
|
|
244
|
-
// The platform-free
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
if (targetMode === "standalone") {
|
|
251
|
-
return {
|
|
252
|
-
contract: "error-shape",
|
|
253
|
-
target: "(standalone)",
|
|
254
|
-
status: "skipped",
|
|
255
|
-
detail: "standalone surface uses MCP-native input validation (JSON-RPC -32602 InvalidParams); the GeoprocessingError envelope is a Honua-operator surface feature",
|
|
256
|
-
};
|
|
257
|
-
}
|
|
259
|
+
async function runErrorShapeContract(client, advertised) {
|
|
260
|
+
// The platform-free surface used to skip this contract: its tools relied on
|
|
261
|
+
// MCP-native input validation (JSON-RPC -32602 InvalidParams), which is a
|
|
262
|
+
// valid structured error but not the GeoprocessingError envelope asserted
|
|
263
|
+
// here. Since #1005 the source-addressed tools validate the request
|
|
264
|
+
// themselves and emit that envelope, so the contract is exercised on every
|
|
265
|
+
// target.
|
|
258
266
|
const invalidInputs = buildInvalidArgsInputs();
|
|
259
267
|
const advertisedNames = new Set(advertised.map((t) => t.name));
|
|
260
268
|
const target = Object.keys(invalidInputs).find((name) => advertisedNames.has(name));
|
|
@@ -23,7 +23,7 @@ function parseArgs(argv) {
|
|
|
23
23
|
else if (arg === "--target") {
|
|
24
24
|
const value = argv[++i];
|
|
25
25
|
if (!value) {
|
|
26
|
-
throw new Error("--target requires a target mode (offline | remote | stdio-proxy | standalone)");
|
|
26
|
+
throw new Error("--target requires a target mode (offline | remote | stdio-proxy | standalone | standalone-ogc)");
|
|
27
27
|
}
|
|
28
28
|
target = value;
|
|
29
29
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 declare class Cql2UnsupportedError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
type Row = Record<string, unknown>;
|
|
20
|
+
type Predicate = (row: Row) => boolean;
|
|
21
|
+
/** Compile a CQL2-text expression into a row predicate. */
|
|
22
|
+
export declare function compileCql2(filter: string): Predicate;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=cql2.d.ts.map
|
|
@@ -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"}
|