@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.
Files changed (117) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +232 -9
  3. package/dist/src/certification/admin-parity.d.ts +96 -0
  4. package/dist/src/certification/admin-parity.d.ts.map +1 -0
  5. package/dist/src/certification/admin-parity.js +235 -0
  6. package/dist/src/certification/census-fixture-client.d.ts.map +1 -1
  7. package/dist/src/certification/census-fixture-client.js +5 -0
  8. package/dist/src/certification/certifier.d.ts +1 -1
  9. package/dist/src/certification/certifier.d.ts.map +1 -1
  10. package/dist/src/certification/certifier.js +28 -20
  11. package/dist/src/certification/cli.js +1 -1
  12. package/dist/src/certification/cql2.d.ts +24 -0
  13. package/dist/src/certification/cql2.d.ts.map +1 -0
  14. package/dist/src/certification/cql2.js +354 -0
  15. package/dist/src/certification/fixtures.d.ts +8 -0
  16. package/dist/src/certification/fixtures.d.ts.map +1 -1
  17. package/dist/src/certification/fixtures.js +26 -10
  18. package/dist/src/certification/ogc-data.d.ts +30 -0
  19. package/dist/src/certification/ogc-data.d.ts.map +1 -0
  20. package/dist/src/certification/ogc-data.js +726 -0
  21. package/dist/src/certification/ogc-fixture-client.d.ts +4 -0
  22. package/dist/src/certification/ogc-fixture-client.d.ts.map +1 -0
  23. package/dist/src/certification/ogc-fixture-client.js +190 -0
  24. package/dist/src/certification/target.d.ts +10 -1
  25. package/dist/src/certification/target.d.ts.map +1 -1
  26. package/dist/src/certification/target.js +63 -28
  27. package/dist/src/credential-endpoint.d.ts +3 -0
  28. package/dist/src/credential-endpoint.d.ts.map +1 -0
  29. package/dist/src/credential-endpoint.js +24 -0
  30. package/dist/src/eval/cli.js +6 -3
  31. package/dist/src/eval/corpus.d.ts +7 -0
  32. package/dist/src/eval/corpus.d.ts.map +1 -1
  33. package/dist/src/eval/corpus.js +12 -0
  34. package/dist/src/eval/ogc-corpus.d.ts +3 -0
  35. package/dist/src/eval/ogc-corpus.d.ts.map +1 -0
  36. package/dist/src/eval/ogc-corpus.js +437 -0
  37. package/dist/src/eval/runner.d.ts +6 -0
  38. package/dist/src/eval/runner.d.ts.map +1 -1
  39. package/dist/src/eval/runner.js +21 -7
  40. package/dist/src/helpers.d.ts +8 -13
  41. package/dist/src/helpers.d.ts.map +1 -1
  42. package/dist/src/helpers.js +7 -35
  43. package/dist/src/index.d.ts +36 -8
  44. package/dist/src/index.d.ts.map +1 -1
  45. package/dist/src/index.js +55 -18
  46. package/dist/src/neutral/errors.d.ts +73 -0
  47. package/dist/src/neutral/errors.d.ts.map +1 -0
  48. package/dist/src/neutral/errors.js +112 -0
  49. package/dist/src/neutral/filter.d.ts +108 -0
  50. package/dist/src/neutral/filter.d.ts.map +1 -0
  51. package/dist/src/neutral/filter.js +271 -0
  52. package/dist/src/neutral/query.d.ts +101 -0
  53. package/dist/src/neutral/query.d.ts.map +1 -0
  54. package/dist/src/neutral/query.js +180 -0
  55. package/dist/src/neutral/result.d.ts +33 -0
  56. package/dist/src/neutral/result.d.ts.map +1 -0
  57. package/dist/src/neutral/result.js +38 -0
  58. package/dist/src/neutral/source-ref.d.ts +126 -0
  59. package/dist/src/neutral/source-ref.d.ts.map +1 -0
  60. package/dist/src/neutral/source-ref.js +267 -0
  61. package/dist/src/nl-map-control.d.ts.map +1 -1
  62. package/dist/src/proxy.d.ts.map +1 -1
  63. package/dist/src/proxy.js +29 -14
  64. package/dist/src/release/zero-to-map-checkpoint.d.ts +34 -0
  65. package/dist/src/release/zero-to-map-checkpoint.d.ts.map +1 -0
  66. package/dist/src/release/zero-to-map-checkpoint.js +369 -0
  67. package/dist/src/release/zero-to-map-cli.d.ts +51 -0
  68. package/dist/src/release/zero-to-map-cli.d.ts.map +1 -0
  69. package/dist/src/release/zero-to-map-cli.js +880 -0
  70. package/dist/src/release/zero-to-map-provision.d.ts +37 -0
  71. package/dist/src/release/zero-to-map-provision.d.ts.map +1 -0
  72. package/dist/src/release/zero-to-map-provision.js +177 -0
  73. package/dist/src/release/zero-to-map.d.ts +296 -0
  74. package/dist/src/release/zero-to-map.d.ts.map +1 -0
  75. package/dist/src/release/zero-to-map.js +1069 -0
  76. package/dist/src/tools/admin-install-local.d.ts +30 -0
  77. package/dist/src/tools/admin-install-local.d.ts.map +1 -0
  78. package/dist/src/tools/admin-install-local.js +22 -0
  79. package/dist/src/tools/control-plane-command.d.ts +99 -0
  80. package/dist/src/tools/control-plane-command.d.ts.map +1 -0
  81. package/dist/src/tools/control-plane-command.js +180 -0
  82. package/dist/src/tools/count-features.d.ts +85 -11
  83. package/dist/src/tools/count-features.d.ts.map +1 -1
  84. package/dist/src/tools/count-features.js +49 -31
  85. package/dist/src/tools/describe-layer.d.ts +30 -6
  86. package/dist/src/tools/describe-layer.d.ts.map +1 -1
  87. package/dist/src/tools/describe-layer.js +99 -8
  88. package/dist/src/tools/docs-search.d.ts +100 -0
  89. package/dist/src/tools/docs-search.d.ts.map +1 -0
  90. package/dist/src/tools/docs-search.js +381 -0
  91. package/dist/src/tools/get-extent.d.ts +69 -11
  92. package/dist/src/tools/get-extent.d.ts.map +1 -1
  93. package/dist/src/tools/get-extent.js +28 -46
  94. package/dist/src/tools/list-services.d.ts +7 -0
  95. package/dist/src/tools/list-services.d.ts.map +1 -1
  96. package/dist/src/tools/list-services.js +8 -1
  97. package/dist/src/tools/list-sources.d.ts +44 -0
  98. package/dist/src/tools/list-sources.d.ts.map +1 -0
  99. package/dist/src/tools/list-sources.js +138 -0
  100. package/dist/src/tools/query-features.d.ts +99 -19
  101. package/dist/src/tools/query-features.d.ts.map +1 -1
  102. package/dist/src/tools/query-features.js +38 -45
  103. package/dist/src/tools/statistics.d.ts +80 -12
  104. package/dist/src/tools/statistics.d.ts.map +1 -1
  105. package/dist/src/tools/statistics.js +56 -23
  106. package/package.json +37 -8
  107. package/release/zero-to-map/README.md +174 -0
  108. package/release/zero-to-map/configs/claude-code.mcp.json +11 -0
  109. package/release/zero-to-map/configs/claude-desktop.json +11 -0
  110. package/release/zero-to-map/configs/cursor.json +11 -0
  111. package/release/zero-to-map/contracts/aws-ecs-provision-binding.schema.json +59 -0
  112. package/release/zero-to-map/contracts/checkpoint.schema.json +152 -0
  113. package/release/zero-to-map/contracts/console-receipt.schema.json +150 -0
  114. package/release/zero-to-map/contracts/esri-gp-mcp.v1.json +73 -0
  115. package/release/zero-to-map/fixtures/parcels.geojson +33 -0
  116. package/release/zero-to-map/fixtures/zoning.geojson +24 -0
  117. 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;AAyFD,qFAAqF;AACrF,wBAAgB,6BAA6B,IAAI,WAAW,CAiE3D"}
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 { CertTargetMode } from "./target.js";
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":"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"}
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 === "standalone";
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, options.targetMode));
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, notes);
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, notes) {
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, targetMode) {
244
- // The platform-free standalone surface is built on the high-level MCP server,
245
- // which rejects invalid arguments with a JSON-RPC -32602 (InvalidParams) error
246
- // BEFORE the tool body runs — a valid MCP-native structured error, but not the
247
- // Honua GeoprocessingError envelope this contract asserts. That envelope is a
248
- // Honua-operator richness a plain FeatureServer does not (and need not) provide,
249
- // so we skip-with-reason rather than fail. Other targets are unaffected.
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"}