@honua/mcp-server 0.0.10-alpha.0 → 0.1.0-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 (176) hide show
  1. package/README.md +259 -15
  2. package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -0
  3. package/certification/geospatial-mcp-schemas/README.md +90 -0
  4. package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
  5. package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +79 -0
  6. package/certification/geospatial-mcp-schemas/index.json +390 -0
  7. package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
  8. package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
  9. package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
  10. package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
  11. package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
  12. package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
  13. package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
  14. package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
  15. package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
  16. package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
  17. package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
  18. package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
  19. package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +27 -0
  20. package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -0
  21. package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
  22. package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
  23. package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
  24. package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
  25. package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
  26. package/certification/geospatial-mcp-schemas/tools/edit_features.schema.json +74 -0
  27. package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
  28. package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
  29. package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
  30. package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
  31. package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
  32. package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
  33. package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
  34. package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -0
  35. package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
  36. package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
  37. package/certification/geospatial-mcp-schemas/tools/overlay_features.schema.json +53 -0
  38. package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
  39. package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
  40. package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
  41. package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
  42. package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
  43. package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
  44. package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
  45. package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +74 -0
  46. package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
  47. package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
  48. package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -0
  49. package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
  50. package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
  51. package/certification/geospatial-mcp-schemas/tools/summarize_statistics.schema.json +71 -0
  52. package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
  53. package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
  54. package/dist/src/capability.d.ts +48 -0
  55. package/dist/src/capability.d.ts.map +1 -0
  56. package/dist/src/capability.js +44 -0
  57. package/dist/src/certification/census-data.d.ts +33 -0
  58. package/dist/src/certification/census-data.d.ts.map +1 -0
  59. package/dist/src/certification/census-data.js +506 -0
  60. package/dist/src/certification/census-fixture-client.d.ts +7 -0
  61. package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
  62. package/dist/src/certification/census-fixture-client.js +226 -0
  63. package/dist/src/certification/certifier.d.ts +125 -0
  64. package/dist/src/certification/certifier.d.ts.map +1 -0
  65. package/dist/src/certification/certifier.js +455 -0
  66. package/dist/src/certification/cli.d.ts +3 -0
  67. package/dist/src/certification/cli.d.ts.map +1 -0
  68. package/dist/src/certification/cli.js +63 -0
  69. package/dist/src/certification/contracts.d.ts +103 -0
  70. package/dist/src/certification/contracts.d.ts.map +1 -0
  71. package/dist/src/certification/contracts.js +245 -0
  72. package/dist/src/certification/fixture-client.d.ts +4 -0
  73. package/dist/src/certification/fixture-client.d.ts.map +1 -0
  74. package/dist/src/certification/fixture-client.js +113 -0
  75. package/dist/src/certification/fixtures.d.ts +45 -0
  76. package/dist/src/certification/fixtures.d.ts.map +1 -0
  77. package/dist/src/certification/fixtures.js +92 -0
  78. package/dist/src/certification/json-schema.d.ts +71 -0
  79. package/dist/src/certification/json-schema.d.ts.map +1 -0
  80. package/dist/src/certification/json-schema.js +172 -0
  81. package/dist/src/certification/lifecycle.d.ts +14 -0
  82. package/dist/src/certification/lifecycle.d.ts.map +1 -0
  83. package/dist/src/certification/lifecycle.js +326 -0
  84. package/dist/src/certification/mock-upstream.d.ts +29 -0
  85. package/dist/src/certification/mock-upstream.d.ts.map +1 -0
  86. package/dist/src/certification/mock-upstream.js +96 -0
  87. package/dist/src/certification/operator-catalog.d.ts +90 -0
  88. package/dist/src/certification/operator-catalog.d.ts.map +1 -0
  89. package/dist/src/certification/operator-catalog.js +650 -0
  90. package/dist/src/certification/report.d.ts +4 -0
  91. package/dist/src/certification/report.d.ts.map +1 -0
  92. package/dist/src/certification/report.js +103 -0
  93. package/dist/src/certification/run.d.ts +28 -0
  94. package/dist/src/certification/run.d.ts.map +1 -0
  95. package/dist/src/certification/run.js +32 -0
  96. package/dist/src/certification/schema-index.d.ts +94 -0
  97. package/dist/src/certification/schema-index.d.ts.map +1 -0
  98. package/dist/src/certification/schema-index.js +109 -0
  99. package/dist/src/certification/target.d.ts +48 -0
  100. package/dist/src/certification/target.d.ts.map +1 -0
  101. package/dist/src/certification/target.js +219 -0
  102. package/dist/src/eval/cli.d.ts +3 -0
  103. package/dist/src/eval/cli.d.ts.map +1 -0
  104. package/dist/src/eval/cli.js +118 -0
  105. package/dist/src/eval/corpus.d.ts +34 -0
  106. package/dist/src/eval/corpus.d.ts.map +1 -0
  107. package/dist/src/eval/corpus.js +150 -0
  108. package/dist/src/eval/drivers/anthropic.d.ts +14 -0
  109. package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
  110. package/dist/src/eval/drivers/anthropic.js +120 -0
  111. package/dist/src/eval/drivers/bedrock.d.ts +54 -0
  112. package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
  113. package/dist/src/eval/drivers/bedrock.js +178 -0
  114. package/dist/src/eval/drivers/deterministic.d.ts +17 -0
  115. package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
  116. package/dist/src/eval/drivers/deterministic.js +64 -0
  117. package/dist/src/eval/drivers/index.d.ts +34 -0
  118. package/dist/src/eval/drivers/index.d.ts.map +1 -0
  119. package/dist/src/eval/drivers/index.js +74 -0
  120. package/dist/src/eval/drivers/openai.d.ts +14 -0
  121. package/dist/src/eval/drivers/openai.d.ts.map +1 -0
  122. package/dist/src/eval/drivers/openai.js +136 -0
  123. package/dist/src/eval/grade.d.ts +14 -0
  124. package/dist/src/eval/grade.d.ts.map +1 -0
  125. package/dist/src/eval/grade.js +75 -0
  126. package/dist/src/eval/northstar-corpus.d.ts +3 -0
  127. package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
  128. package/dist/src/eval/northstar-corpus.js +194 -0
  129. package/dist/src/eval/operator-corpus.d.ts +3 -0
  130. package/dist/src/eval/operator-corpus.d.ts.map +1 -0
  131. package/dist/src/eval/operator-corpus.js +193 -0
  132. package/dist/src/eval/report.d.ts +109 -0
  133. package/dist/src/eval/report.d.ts.map +1 -0
  134. package/dist/src/eval/report.js +133 -0
  135. package/dist/src/eval/runner.d.ts +28 -0
  136. package/dist/src/eval/runner.d.ts.map +1 -0
  137. package/dist/src/eval/runner.js +130 -0
  138. package/dist/src/eval/standalone-corpus.d.ts +3 -0
  139. package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
  140. package/dist/src/eval/standalone-corpus.js +699 -0
  141. package/dist/src/eval/types.d.ts +118 -0
  142. package/dist/src/eval/types.d.ts.map +1 -0
  143. package/dist/src/eval/types.js +10 -0
  144. package/dist/src/helpers.d.ts +11 -0
  145. package/dist/src/helpers.d.ts.map +1 -1
  146. package/dist/src/helpers.js +22 -0
  147. package/dist/src/index.d.ts +31 -0
  148. package/dist/src/index.d.ts.map +1 -1
  149. package/dist/src/index.js +50 -4
  150. package/dist/src/provenance.d.ts +44 -0
  151. package/dist/src/provenance.d.ts.map +1 -0
  152. package/dist/src/provenance.js +37 -0
  153. package/dist/src/proxy.d.ts +46 -0
  154. package/dist/src/proxy.d.ts.map +1 -0
  155. package/dist/src/proxy.js +124 -0
  156. package/dist/src/resources/styles.d.ts +29 -0
  157. package/dist/src/resources/styles.d.ts.map +1 -0
  158. package/dist/src/resources/styles.js +76 -0
  159. package/dist/src/styles.d.ts +112 -0
  160. package/dist/src/styles.d.ts.map +1 -0
  161. package/dist/src/styles.js +144 -0
  162. package/dist/src/tools/apply-style-preset.d.ts +27 -0
  163. package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
  164. package/dist/src/tools/apply-style-preset.js +41 -0
  165. package/dist/src/tools/count-features.d.ts.map +1 -1
  166. package/dist/src/tools/count-features.js +5 -4
  167. package/dist/src/tools/get-extent.d.ts.map +1 -1
  168. package/dist/src/tools/get-extent.js +6 -4
  169. package/dist/src/tools/get-style.d.ts +24 -0
  170. package/dist/src/tools/get-style.d.ts.map +1 -0
  171. package/dist/src/tools/get-style.js +39 -0
  172. package/dist/src/tools/list-services.d.ts.map +1 -1
  173. package/dist/src/tools/list-services.js +16 -1
  174. package/dist/src/tools/query-features.d.ts.map +1 -1
  175. package/dist/src/tools/query-features.js +4 -1
  176. package/package.json +28 -4
@@ -0,0 +1,245 @@
1
+ import { McpError } from "@modelcontextprotocol/sdk/types.js";
2
+ import { validateAgainstSchema } from "./json-schema.js";
3
+ import { loadSchemaFile } from "./schema-index.js";
4
+ /**
5
+ * Pure, deterministic contract-check analyzers for MCP certification.
6
+ *
7
+ * These operate on values already obtained from the certified surface (tool
8
+ * results, thrown errors, paginated list responses) so they are unit-testable
9
+ * without any transport. The certifier does the I/O; these judge the shape.
10
+ *
11
+ * Contracts enforced (§P0.2):
12
+ * - output-schema : a tool's structuredContent validates against its outputSchema.
13
+ * - error-shape : invalid arguments yield an `isError` tool result carrying a
14
+ * structured, actionable code (validated against the vendored
15
+ * geoprocessing-error schema), never a protocol error.
16
+ * - auth : unauthenticated tools/call and resources/read surface a
17
+ * structured `unauthenticated` / `permission_denied` signal.
18
+ * - pagination : list `nextCursor` is honored and terminates.
19
+ */
20
+ /** Machine codes that satisfy the unauthenticated / permission-denied contract. */
21
+ export const AUTH_ERROR_CODES = ["unauthenticated", "permission_denied"];
22
+ /** geoprocessing-error `kind` values that denote an authorization failure. */
23
+ export const AUTH_ERROR_KINDS = ["AuthorizationDenied", "Unauthenticated", "PermissionDenied"];
24
+ let cachedErrorSchema;
25
+ function geoprocessingErrorSchema() {
26
+ if (!cachedErrorSchema) {
27
+ cachedErrorSchema = loadSchemaFile("common/geoprocessing-error.schema.json");
28
+ }
29
+ return cachedErrorSchema;
30
+ }
31
+ function isRecord(value) {
32
+ return typeof value === "object" && value !== null;
33
+ }
34
+ /**
35
+ * Normalize a structured error envelope out of any of the places a surface may
36
+ * carry it: a tool result's `structuredContent`, an `McpError`'s `data`, or a
37
+ * bare geoprocessing-error object. Returns undefined when no structure is found.
38
+ */
39
+ export function extractStructuredError(source) {
40
+ if (source instanceof McpError) {
41
+ const fromData = extractStructuredError(source.data);
42
+ if (fromData) {
43
+ return fromData;
44
+ }
45
+ // A raw code may live on the error's message-less data; nothing structured.
46
+ return undefined;
47
+ }
48
+ if (!isRecord(source)) {
49
+ return undefined;
50
+ }
51
+ // Tool result → dig into structuredContent first.
52
+ if (isRecord(source.structuredContent)) {
53
+ const nested = extractStructuredError(source.structuredContent);
54
+ if (nested) {
55
+ return nested;
56
+ }
57
+ }
58
+ const code = typeof source.code === "string" ? source.code : undefined;
59
+ // Envelope with a nested geoprocessing-error under `error`.
60
+ if (isRecord(source.error)) {
61
+ return { code, error: source.error };
62
+ }
63
+ // The object may itself be a geoprocessing-error (has `kind` + `message`).
64
+ if (typeof source.kind === "string" && typeof source.message === "string") {
65
+ return { code, error: source };
66
+ }
67
+ if (code) {
68
+ return { code };
69
+ }
70
+ return undefined;
71
+ }
72
+ /** Validate a value against the vendored geoprocessing-error schema. */
73
+ export function validateGeoprocessingError(error) {
74
+ const result = validateAgainstSchema(geoprocessingErrorSchema(), error);
75
+ return { ok: result.valid, errors: result.errors };
76
+ }
77
+ /**
78
+ * error-shape contract: an invalid-argument tool call must return an `isError`
79
+ * result whose structuredContent carries an actionable code and a
80
+ * geoprocessing-error-shaped `ValidationFailed` envelope.
81
+ */
82
+ export function checkInvalidArgsContract(result) {
83
+ const errors = [];
84
+ if (result.isError !== true) {
85
+ errors.push("expected isError=true for an invalid-argument call (got a protocol error or a success)");
86
+ }
87
+ const envelope = extractStructuredError(result);
88
+ if (!envelope) {
89
+ errors.push("no structured error envelope found on the tool result");
90
+ return { ok: false, errors };
91
+ }
92
+ if (!envelope.code || envelope.code.trim().length === 0) {
93
+ errors.push("structured error is missing a machine-actionable `code`");
94
+ }
95
+ if (envelope.error === undefined) {
96
+ errors.push("structured error is missing a geoprocessing-error envelope");
97
+ }
98
+ else {
99
+ const validation = validateGeoprocessingError(envelope.error);
100
+ if (!validation.ok) {
101
+ errors.push(...validation.errors.map((e) => `geoprocessing-error: ${e}`));
102
+ }
103
+ const kind = isRecord(envelope.error) ? envelope.error.kind : undefined;
104
+ if (kind !== "ValidationFailed") {
105
+ errors.push(`expected geoprocessing-error kind "ValidationFailed", got "${String(kind)}"`);
106
+ }
107
+ const violations = isRecord(envelope.error) ? envelope.error.violations : undefined;
108
+ if (!Array.isArray(violations) || violations.length === 0) {
109
+ errors.push("expected at least one structured validation `violation`");
110
+ }
111
+ }
112
+ return { ok: errors.length === 0, errors };
113
+ }
114
+ /**
115
+ * auth contract: an unauthenticated call must surface a structured
116
+ * `unauthenticated` / `permission_denied` signal — either as a code on the
117
+ * envelope or as an AuthorizationDenied geoprocessing-error kind.
118
+ */
119
+ export function checkAuthContract(source) {
120
+ const envelope = extractStructuredError(source);
121
+ if (!envelope) {
122
+ return { ok: false, errors: ["no structured error found for the unauthenticated request"] };
123
+ }
124
+ const code = envelope.code;
125
+ const kind = isRecord(envelope.error) ? envelope.error.kind : undefined;
126
+ const codeOk = typeof code === "string" && AUTH_ERROR_CODES.includes(code);
127
+ const kindOk = typeof kind === "string" && AUTH_ERROR_KINDS.includes(kind);
128
+ if (codeOk || kindOk) {
129
+ return { ok: true, errors: [] };
130
+ }
131
+ return {
132
+ ok: false,
133
+ errors: [
134
+ `expected a structured auth signal (code in [${AUTH_ERROR_CODES.join(", ")}] or kind in [${AUTH_ERROR_KINDS.join(", ")}]), got code="${String(code)}" kind="${String(kind)}"`,
135
+ ],
136
+ };
137
+ }
138
+ /** Documented job states, ordered by lifecycle rank (terminal states are highest). */
139
+ export const JOB_STATE_RANK = {
140
+ Submitted: 0,
141
+ Queued: 0,
142
+ Running: 1,
143
+ Succeeded: 2,
144
+ Failed: 2,
145
+ Cancelled: 2,
146
+ };
147
+ const TERMINAL_JOB_STATES = new Set(["Succeeded", "Failed", "Cancelled"]);
148
+ /**
149
+ * mutating-round-trip contract: an insert must be observable, an update must be
150
+ * reflected, and a delete must remove the feature — the full edit lifecycle
151
+ * verified against the surface's own read path.
152
+ */
153
+ export function checkFeatureRoundTrip(obs) {
154
+ const errors = [];
155
+ if (obs.insertedObjectId === undefined) {
156
+ errors.push("insert did not return a new objectId");
157
+ }
158
+ if (!obs.presentAfterInsert) {
159
+ errors.push("inserted feature was not observable via a follow-up query");
160
+ }
161
+ if (obs.attributeAfterUpdate !== obs.expectedAttribute) {
162
+ errors.push(`updated attribute did not round-trip (expected ${JSON.stringify(obs.expectedAttribute)}, read ${JSON.stringify(obs.attributeAfterUpdate)})`);
163
+ }
164
+ if (!obs.absentAfterDelete) {
165
+ errors.push("feature was still observable after delete");
166
+ }
167
+ return { ok: errors.length === 0, errors };
168
+ }
169
+ /**
170
+ * async-job-lifecycle contract: an execute → poll(honua://jobs/{id}) → fetch-result
171
+ * flow must follow the documented state machine — statuses are all recognized,
172
+ * rank never regresses, progress is monotonic within [0,1], the job terminates
173
+ * (Succeeded here), and the result package is retrievable.
174
+ */
175
+ export function checkJobLifecycle(obs) {
176
+ const errors = [];
177
+ const allStatuses = [obs.initialStatus, ...obs.polledStatuses, obs.finalStatus];
178
+ let lastRank = -1;
179
+ for (const status of allStatuses) {
180
+ const rank = JOB_STATE_RANK[status];
181
+ if (rank === undefined) {
182
+ errors.push(`unrecognized job state "${status}" (not in the documented state machine)`);
183
+ continue;
184
+ }
185
+ if (rank < lastRank) {
186
+ errors.push(`job state regressed from rank ${lastRank} to "${status}" (rank ${rank})`);
187
+ }
188
+ lastRank = rank;
189
+ }
190
+ let lastFraction = -1;
191
+ for (const fraction of obs.progressFractions) {
192
+ if (typeof fraction !== "number" || Number.isNaN(fraction) || fraction < 0 || fraction > 1) {
193
+ errors.push(`progress fraction ${String(fraction)} is out of the documented [0,1] range`);
194
+ continue;
195
+ }
196
+ if (fraction < lastFraction) {
197
+ errors.push(`progress fraction regressed from ${lastFraction} to ${fraction}`);
198
+ }
199
+ lastFraction = fraction;
200
+ }
201
+ if (!TERMINAL_JOB_STATES.has(obs.finalStatus)) {
202
+ errors.push(`job did not reach a terminal state (ended "${obs.finalStatus}")`);
203
+ }
204
+ if (obs.finalStatus === "Succeeded" && !obs.resultReady) {
205
+ errors.push("job succeeded but no ready result package could be fetched");
206
+ }
207
+ return { ok: errors.length === 0, errors };
208
+ }
209
+ /**
210
+ * pagination contract: following `nextCursor` yields additional distinct items,
211
+ * never repeats a cursor (terminates), and the final page has no cursor.
212
+ */
213
+ export function checkPaginationContract(pages) {
214
+ const errors = [];
215
+ if (pages.length === 0) {
216
+ return { ok: false, errors: ["no list pages captured"] };
217
+ }
218
+ const seenCursors = new Set();
219
+ const seenKeys = new Set();
220
+ for (let i = 0; i < pages.length; i++) {
221
+ const page = pages[i];
222
+ for (const key of page.keys) {
223
+ seenKeys.add(key);
224
+ }
225
+ const isLast = i === pages.length - 1;
226
+ if (!isLast) {
227
+ if (!page.nextCursor) {
228
+ errors.push(`page ${i} is not the last page but advertised no nextCursor`);
229
+ }
230
+ else if (seenCursors.has(page.nextCursor)) {
231
+ errors.push(`nextCursor "${page.nextCursor}" repeated — pagination does not terminate`);
232
+ }
233
+ else {
234
+ seenCursors.add(page.nextCursor);
235
+ }
236
+ }
237
+ else if (page.nextCursor) {
238
+ errors.push(`final page still advertises nextCursor "${page.nextCursor}" — pagination did not terminate`);
239
+ }
240
+ }
241
+ if (pages.length > 1 && seenKeys.size <= pages[0].keys.length) {
242
+ errors.push("following nextCursor did not yield any additional distinct items");
243
+ }
244
+ return { ok: errors.length === 0, errors };
245
+ }
@@ -0,0 +1,4 @@
1
+ import type { HonuaClient } from "@honua/sdk-js";
2
+ /** Build an offline fixture client usable anywhere a `HonuaClient` is expected. */
3
+ export declare function createFixtureClient(): HonuaClient;
4
+ //# sourceMappingURL=fixture-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-client.d.ts","sourceRoot":"","sources":["../../../src/certification/fixture-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,eAAe,CAAC;AAsDvB,mFAAmF;AACnF,wBAAgB,mBAAmB,IAAI,WAAW,CAkEjD"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Offline fixture `HonuaClient` for deterministic certification.
3
+ *
4
+ * This is the standalone (non-vitest) counterpart of `test/test-helpers.ts`:
5
+ * it returns canned, schema-shaped responses for every method the MCP tools and
6
+ * resources call, so the certifier can run `tools/call` round-trips with no live
7
+ * backend, no network access, and no model/API calls. CI instead points the
8
+ * certifier at a live honua-server via `createClientFromEnv`.
9
+ */
10
+ const STYLES_LIST = {
11
+ styles: [
12
+ { id: "topographic", title: "Topographic", links: [] },
13
+ { id: "imagery", title: "Imagery", links: [] },
14
+ ],
15
+ default: "topographic",
16
+ };
17
+ const TOPOGRAPHIC_METADATA = {
18
+ id: "topographic",
19
+ title: "Topographic",
20
+ description: "Default topographic basemap style",
21
+ version: "3",
22
+ links: [{ rel: "preview", type: "image/png", href: "https://example.test/ogc/styles/topographic/preview" }],
23
+ };
24
+ const TOPOGRAPHIC_STYLESHEET = {
25
+ version: 8,
26
+ name: "Topographic",
27
+ layers: [{ id: "background", type: "background" }],
28
+ };
29
+ function jsonResponse(body, status = 200) {
30
+ return new Response(JSON.stringify(body), {
31
+ status,
32
+ headers: { "Content-Type": "application/json" },
33
+ });
34
+ }
35
+ async function fixtureFetch(_method, path) {
36
+ if (path === "/ogc/styles") {
37
+ return jsonResponse(STYLES_LIST);
38
+ }
39
+ if (path === "/ogc/styles/topographic/metadata") {
40
+ return jsonResponse(TOPOGRAPHIC_METADATA);
41
+ }
42
+ if (path === "/ogc/styles/topographic") {
43
+ return jsonResponse(TOPOGRAPHIC_STYLESHEET);
44
+ }
45
+ return jsonResponse({ error: "not found" }, 404);
46
+ }
47
+ /** Build an offline fixture client usable anywhere a `HonuaClient` is expected. */
48
+ export function createFixtureClient() {
49
+ const client = {
50
+ serverBaseUrl: "https://example.test",
51
+ async listServices() {
52
+ return {
53
+ services: [
54
+ { name: "Parks", type: "FeatureServer" },
55
+ { name: "Basemap", type: "MapServer" },
56
+ { name: "Census", type: "FeatureServer" },
57
+ ],
58
+ };
59
+ },
60
+ async getFeatureServiceMetadata(_serviceId) {
61
+ return {
62
+ serviceDescription: "Test service",
63
+ layers: [
64
+ { id: 0, name: "Layer 0" },
65
+ { id: 1, name: "Layer 1" },
66
+ ],
67
+ spatialReference: { wkid: 4326 },
68
+ };
69
+ },
70
+ async getLayerMetadata(_serviceId, _layerId) {
71
+ return {
72
+ id: 0,
73
+ name: "Test Layer",
74
+ description: "A test layer",
75
+ geometryType: "esriGeometryPoint",
76
+ fields: [
77
+ { name: "OBJECTID", type: "esriFieldTypeOID" },
78
+ { name: "NAME", type: "esriFieldTypeString", alias: "Feature Name" },
79
+ { name: "VALUE", type: "esriFieldTypeDouble" },
80
+ ],
81
+ extent: { xmin: -180, ymin: -90, xmax: 180, ymax: 90 },
82
+ spatialReference: { wkid: 4326 },
83
+ relationships: [{ id: 0, name: "rel_0", relatedTableId: 1 }],
84
+ };
85
+ },
86
+ async queryFeatures(request) {
87
+ const extraParams = request?.extraParams ?? {};
88
+ // count-only and extent-only projections mirror the GeoServices query
89
+ // surface the tools exercise (returnCountOnly / returnExtentOnly).
90
+ if (extraParams.returnCountOnly === true) {
91
+ return { count: 2 };
92
+ }
93
+ if (extraParams.returnExtentOnly === true) {
94
+ return {
95
+ count: 2,
96
+ extent: { xmin: -180, ymin: -90, xmax: 180, ymax: 90, spatialReference: { wkid: 4326 } },
97
+ };
98
+ }
99
+ return {
100
+ objectIdFieldName: "OBJECTID",
101
+ geometryType: "esriGeometryPoint",
102
+ spatialReference: { wkid: 4326 },
103
+ features: [
104
+ { attributes: { OBJECTID: 1, NAME: "Park A", VALUE: 100 } },
105
+ { attributes: { OBJECTID: 2, NAME: "Park B", VALUE: 200 } },
106
+ ],
107
+ exceededTransferLimit: false,
108
+ };
109
+ },
110
+ pipelineFetch: fixtureFetch,
111
+ };
112
+ return client;
113
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Deterministic round-trip fixture inputs for the honua `/mcp` operator surface.
3
+ *
4
+ * Certification calls `tools/call` on read-only operator tools to validate their
5
+ * structured output against the advertised outputSchema. Inputs are resolved
6
+ * from environment overrides so the same harness drives both the offline mock
7
+ * upstream and a live honua-server `/mcp` in remote mode, without ever calling a
8
+ * mutating tool (execute/cancel/publish/propose/create).
9
+ */
10
+ export interface RoundTripEnv {
11
+ serviceId: string;
12
+ layerId: number;
13
+ address: string;
14
+ styleId: string | undefined;
15
+ }
16
+ /** Resolve round-trip identifiers from the environment, with safe fallbacks. */
17
+ export declare function resolveRoundTripEnv(env?: NodeJS.ProcessEnv): RoundTripEnv;
18
+ /**
19
+ * Build the read-only round-trip argument map keyed by advertised tool name.
20
+ * Tools without a fixture entry are still discovered, schema/conformance/output
21
+ * checked, but are not round-tripped (no call is made). Mutating tools are never
22
+ * listed here.
23
+ */
24
+ export declare function buildRoundTripInputs(env: RoundTripEnv): Record<string, Record<string, unknown>>;
25
+ /** Target layer for the mutating round-trip + pagination contracts. */
26
+ export interface EditTarget {
27
+ serviceId: string;
28
+ layerId: number;
29
+ }
30
+ /**
31
+ * Resolve the editable/pageable target for the deep contracts. Defaults to the
32
+ * offline mock's seeded `svc-parks` layer 0; override with
33
+ * `HONUA_MCP_EDIT_SERVICE_ID` / `HONUA_MCP_EDIT_LAYER_ID` (or the shared
34
+ * `HONUA_MCP_SERVICE_ID` / `HONUA_MCP_LAYER_ID`) to point a live mutating cert at
35
+ * a dedicated scratch service.
36
+ */
37
+ export declare function resolveEditTarget(env?: NodeJS.ProcessEnv): EditTarget;
38
+ /**
39
+ * A deliberately invalid argument set for the error-shape contract. Certification
40
+ * picks the first advertised tool that has an entry here and calls it with these
41
+ * args, expecting a structured `ValidationFailed` tool result (not a protocol
42
+ * error). `honua_query_features` requires `layerId`, so omitting it is invalid.
43
+ */
44
+ export declare function buildInvalidArgsInputs(): Record<string, Record<string, unknown>>;
45
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../src/certification/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAQtF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsC/F;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMhF"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Deterministic round-trip fixture inputs for the honua `/mcp` operator surface.
3
+ *
4
+ * Certification calls `tools/call` on read-only operator tools to validate their
5
+ * structured output against the advertised outputSchema. Inputs are resolved
6
+ * from environment overrides so the same harness drives both the offline mock
7
+ * upstream and a live honua-server `/mcp` in remote mode, without ever calling a
8
+ * mutating tool (execute/cancel/publish/propose/create).
9
+ */
10
+ /** Resolve round-trip identifiers from the environment, with safe fallbacks. */
11
+ export function resolveRoundTripEnv(env = process.env) {
12
+ const serviceId = env.HONUA_MCP_SERVICE_ID ?? env.HONUA_SERVICE_ID ?? "svc-parks";
13
+ const layerRaw = env.HONUA_MCP_LAYER_ID ?? env.HONUA_LAYER_ID ?? "0";
14
+ const parsedLayer = Number.parseInt(layerRaw, 10);
15
+ const layerId = Number.isFinite(parsedLayer) && parsedLayer >= 0 ? parsedLayer : 0;
16
+ const address = env.HONUA_MCP_ADDRESS ?? "1600 Pennsylvania Ave NW, Washington DC";
17
+ const styleId = env.HONUA_MCP_STYLE_ID ?? env.HONUA_STYLE_ID;
18
+ return { serviceId, layerId, address, styleId };
19
+ }
20
+ /**
21
+ * Build the read-only round-trip argument map keyed by advertised tool name.
22
+ * Tools without a fixture entry are still discovered, schema/conformance/output
23
+ * checked, but are not round-tripped (no call is made). Mutating tools are never
24
+ * listed here.
25
+ */
26
+ export function buildRoundTripInputs(env) {
27
+ const { serviceId, layerId, address } = env;
28
+ return {
29
+ // ── Platform-free standalone surface tools (issue #369). Round-tripped by the
30
+ // `standalone` cert target against a plain public FeatureServer fixture. The
31
+ // Honua-only style tools resolve to a structured "not available on this
32
+ // target" result (non-error) — certifying graceful degradation, not a crash.
33
+ honua_list_services: {},
34
+ honua_describe_layer: { serviceId, layerId },
35
+ honua_count_features: { serviceId, layerId },
36
+ honua_get_extent: { serviceId, layerId },
37
+ honua_statistics: { serviceId, layerId, statisticType: "count", onField: "OBJECTID" },
38
+ honua_explain_capability_gap: { capability: "query", protocol: "wmts" },
39
+ honua_get_style: {},
40
+ honua_apply_style_preset: { styleId: "topographic" },
41
+ // ── Honua-enhanced operator catalog tools (offline/remote/stdio-proxy targets).
42
+ honua_list_layers: {},
43
+ honua_list_capabilities: {},
44
+ honua_query_features: { serviceId, layerId, limit: 1 },
45
+ honua_render_map: { layers: [{ serviceId, layerId }], bbox: [-77.1, 38.8, -76.9, 39.0] },
46
+ honua_geocode_address: { address },
47
+ honua_solve_route: {
48
+ stops: [
49
+ { lon: -77.03, lat: 38.9 },
50
+ { lon: -76.61, lat: 39.29 },
51
+ ],
52
+ },
53
+ honua_plan_analysis: { intent: "Count parks within the district boundary" },
54
+ honua_ground_candidates: { goal: "Analyze park coverage" },
55
+ honua_clarify_intent: {
56
+ goal: "Analyze park coverage",
57
+ intentId: "int-001",
58
+ response: { answers: { q1: ["yes"] } },
59
+ },
60
+ honua_validate_plan: { plan: {} },
61
+ honua_dry_run_plan: { plan: { planId: "plan-001", steps: [{ stepId: "s1", kind: "QueryFeatures" }] } },
62
+ honua_resolve_entity: { text: "parks" },
63
+ };
64
+ }
65
+ /**
66
+ * Resolve the editable/pageable target for the deep contracts. Defaults to the
67
+ * offline mock's seeded `svc-parks` layer 0; override with
68
+ * `HONUA_MCP_EDIT_SERVICE_ID` / `HONUA_MCP_EDIT_LAYER_ID` (or the shared
69
+ * `HONUA_MCP_SERVICE_ID` / `HONUA_MCP_LAYER_ID`) to point a live mutating cert at
70
+ * a dedicated scratch service.
71
+ */
72
+ export function resolveEditTarget(env = process.env) {
73
+ const base = resolveRoundTripEnv(env);
74
+ const serviceId = env.HONUA_MCP_EDIT_SERVICE_ID ?? base.serviceId;
75
+ const layerRaw = env.HONUA_MCP_EDIT_LAYER_ID;
76
+ const parsedLayer = layerRaw !== undefined ? Number.parseInt(layerRaw, 10) : Number.NaN;
77
+ const layerId = Number.isFinite(parsedLayer) && parsedLayer >= 0 ? parsedLayer : base.layerId;
78
+ return { serviceId, layerId };
79
+ }
80
+ /**
81
+ * A deliberately invalid argument set for the error-shape contract. Certification
82
+ * picks the first advertised tool that has an entry here and calls it with these
83
+ * args, expecting a structured `ValidationFailed` tool result (not a protocol
84
+ * error). `honua_query_features` requires `layerId`, so omitting it is invalid.
85
+ */
86
+ export function buildInvalidArgsInputs() {
87
+ return {
88
+ honua_query_features: { serviceId: "svc-parks" },
89
+ honua_geocode_address: { maxResults: 3 },
90
+ honua_solve_route: { travelProfile: "driving" },
91
+ };
92
+ }
@@ -0,0 +1,71 @@
1
+ import type { ValidateFunction } from "ajv/dist/2020.js";
2
+ import type { JsonSchema } from "./schema-index.js";
3
+ export interface WellFormedResult {
4
+ wellFormed: boolean;
5
+ errors: string[];
6
+ }
7
+ /**
8
+ * Check that a value is a structurally well-formed JSON Schema by asking Ajv to
9
+ * compile it. A schema that compiles is usable as a validator; a schema Ajv
10
+ * refuses to compile is malformed.
11
+ */
12
+ export declare function checkWellFormed(schema: unknown): WellFormedResult;
13
+ export interface ValidationResult {
14
+ valid: boolean;
15
+ errors: string[];
16
+ }
17
+ /** Compile a schema into a reusable validator, or throw a descriptive error. */
18
+ export declare function compileValidator(schema: JsonSchema): ValidateFunction;
19
+ /** Validate a value against a JSON Schema and collect human-readable errors. */
20
+ export declare function validateAgainstSchema(schema: JsonSchema, value: unknown): ValidationResult;
21
+ export interface ConformanceResult {
22
+ conformant: boolean;
23
+ /** Required standard properties the advertised schema honors. */
24
+ matchedRequired: string[];
25
+ /** Conformance violations (missing or type-incompatible required properties). */
26
+ violations: string[];
27
+ /** Non-fatal observations (e.g. advertised extra properties, type widenings). */
28
+ notes: string[];
29
+ }
30
+ export interface ConformanceOptions {
31
+ /**
32
+ * Required standard properties this surface is permitted to omit because it is
33
+ * a documented, platform-gated degradation of the tool (see the SDK-local
34
+ * `PLATFORM_FREE_DEGRADATIONS` registry in `schema-index.ts`).
35
+ *
36
+ * A property listed here is a platform *binding* — an identifier that only has
37
+ * meaning against a Honua publishing/authoring surface (e.g. `apply_style_preset`
38
+ * requires `serviceId`+`layerId` to bind a preset onto a *published* layer via
39
+ * the server OGC API – Styles authoring surface, ADR-0048). A platform-free
40
+ * target has no such surface, so it degrades the tool to a non-error, client-side
41
+ * projection keyed on the taxonomy essence alone (e.g. `styleId`) and legitimately
42
+ * omits the binding identifiers.
43
+ *
44
+ * When a required standard property is absent AND listed here, it is recorded as
45
+ * a degradation note rather than a conformance violation. Everything else stays
46
+ * strict: unlisted required properties still fail, and type incompatibilities on
47
+ * properties the surface DOES advertise remain violations — degradation relaxes
48
+ * platform binding, never correctness on what is accepted.
49
+ */
50
+ shedRequired?: readonly string[];
51
+ }
52
+ /**
53
+ * Deterministically compare an advertised tool inputSchema against a standard
54
+ * geospatial-mcp tool schema.
55
+ *
56
+ * Conformance contract (intentionally permissive to avoid false negatives — we
57
+ * fail only on demonstrable non-conformance, never on the absence of a tool):
58
+ *
59
+ * 1. Every property the standard marks `required` MUST be present in the
60
+ * advertised schema's properties (a client that follows the standard will
61
+ * send it, so the tool must accept it).
62
+ * 2. For each such required property, when both schemas declare a `type`, the
63
+ * advertised type must be compatible (equal, or a superset, or `integer`
64
+ * accepted where the standard says `number`).
65
+ *
66
+ * Extra advertised properties and looser-but-compatible types are recorded as
67
+ * notes, not violations: a richer reference shape is allowed to extend the
68
+ * bare standard.
69
+ */
70
+ export declare function checkConformance(advertised: JsonSchema, standard: JsonSchema, options?: ConformanceOptions): ConformanceResult;
71
+ //# sourceMappingURL=json-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../../src/certification/json-schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA2C,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAElG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAwBpD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAcjE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AASD,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAGrE;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAQ1F;AAmCD,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iFAAiF;IACjF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iFAAiF;IACjF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,UAAU,EACpB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAkEnB"}