@honua/mcp-server 0.0.9-alpha.0 → 0.0.20-alpha.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 (147) hide show
  1. package/README.md +167 -0
  2. package/certification/geospatial-mcp-schemas/PROVENANCE.md +33 -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 +73 -0
  6. package/certification/geospatial-mcp-schemas/index.json +311 -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 +23 -0
  20. package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
  21. package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
  22. package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
  23. package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
  24. package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
  25. package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
  26. package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
  27. package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +30 -0
  28. package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
  29. package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
  30. package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
  31. package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
  32. package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
  33. package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
  34. package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
  35. package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
  36. package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
  37. package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
  38. package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +54 -0
  39. package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
  40. package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +55 -0
  41. package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
  42. package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
  43. package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
  44. package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
  45. package/dist/src/certification/certifier.d.ts +119 -0
  46. package/dist/src/certification/certifier.d.ts.map +1 -0
  47. package/dist/src/certification/certifier.js +410 -0
  48. package/dist/src/certification/cli.d.ts +3 -0
  49. package/dist/src/certification/cli.d.ts.map +1 -0
  50. package/dist/src/certification/cli.js +48 -0
  51. package/dist/src/certification/contracts.d.ts +62 -0
  52. package/dist/src/certification/contracts.d.ts.map +1 -0
  53. package/dist/src/certification/contracts.js +174 -0
  54. package/dist/src/certification/fixture-client.d.ts +4 -0
  55. package/dist/src/certification/fixture-client.d.ts.map +1 -0
  56. package/dist/src/certification/fixture-client.js +113 -0
  57. package/dist/src/certification/fixtures.d.ts +32 -0
  58. package/dist/src/certification/fixtures.d.ts.map +1 -0
  59. package/dist/src/certification/fixtures.js +64 -0
  60. package/dist/src/certification/json-schema.d.ts +49 -0
  61. package/dist/src/certification/json-schema.d.ts.map +1 -0
  62. package/dist/src/certification/json-schema.js +164 -0
  63. package/dist/src/certification/mock-upstream.d.ts +29 -0
  64. package/dist/src/certification/mock-upstream.d.ts.map +1 -0
  65. package/dist/src/certification/mock-upstream.js +96 -0
  66. package/dist/src/certification/operator-catalog.d.ts +82 -0
  67. package/dist/src/certification/operator-catalog.d.ts.map +1 -0
  68. package/dist/src/certification/operator-catalog.js +466 -0
  69. package/dist/src/certification/report.d.ts +4 -0
  70. package/dist/src/certification/report.d.ts.map +1 -0
  71. package/dist/src/certification/report.js +90 -0
  72. package/dist/src/certification/run.d.ts +28 -0
  73. package/dist/src/certification/run.d.ts.map +1 -0
  74. package/dist/src/certification/run.js +31 -0
  75. package/dist/src/certification/schema-index.d.ts +69 -0
  76. package/dist/src/certification/schema-index.d.ts.map +1 -0
  77. package/dist/src/certification/schema-index.js +82 -0
  78. package/dist/src/certification/target.d.ts +40 -0
  79. package/dist/src/certification/target.d.ts.map +1 -0
  80. package/dist/src/certification/target.js +167 -0
  81. package/dist/src/eval/cli.d.ts +3 -0
  82. package/dist/src/eval/cli.d.ts.map +1 -0
  83. package/dist/src/eval/cli.js +116 -0
  84. package/dist/src/eval/corpus.d.ts +27 -0
  85. package/dist/src/eval/corpus.d.ts.map +1 -0
  86. package/dist/src/eval/corpus.js +134 -0
  87. package/dist/src/eval/drivers/anthropic.d.ts +14 -0
  88. package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
  89. package/dist/src/eval/drivers/anthropic.js +120 -0
  90. package/dist/src/eval/drivers/bedrock.d.ts +54 -0
  91. package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
  92. package/dist/src/eval/drivers/bedrock.js +178 -0
  93. package/dist/src/eval/drivers/deterministic.d.ts +17 -0
  94. package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
  95. package/dist/src/eval/drivers/deterministic.js +51 -0
  96. package/dist/src/eval/drivers/index.d.ts +34 -0
  97. package/dist/src/eval/drivers/index.d.ts.map +1 -0
  98. package/dist/src/eval/drivers/index.js +74 -0
  99. package/dist/src/eval/drivers/openai.d.ts +14 -0
  100. package/dist/src/eval/drivers/openai.d.ts.map +1 -0
  101. package/dist/src/eval/drivers/openai.js +136 -0
  102. package/dist/src/eval/grade.d.ts +14 -0
  103. package/dist/src/eval/grade.d.ts.map +1 -0
  104. package/dist/src/eval/grade.js +56 -0
  105. package/dist/src/eval/northstar-corpus.d.ts +3 -0
  106. package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
  107. package/dist/src/eval/northstar-corpus.js +194 -0
  108. package/dist/src/eval/operator-corpus.d.ts +3 -0
  109. package/dist/src/eval/operator-corpus.d.ts.map +1 -0
  110. package/dist/src/eval/operator-corpus.js +193 -0
  111. package/dist/src/eval/report.d.ts +104 -0
  112. package/dist/src/eval/report.d.ts.map +1 -0
  113. package/dist/src/eval/report.js +130 -0
  114. package/dist/src/eval/runner.d.ts +22 -0
  115. package/dist/src/eval/runner.d.ts.map +1 -0
  116. package/dist/src/eval/runner.js +116 -0
  117. package/dist/src/eval/types.d.ts +93 -0
  118. package/dist/src/eval/types.d.ts.map +1 -0
  119. package/dist/src/eval/types.js +10 -0
  120. package/dist/src/helpers.d.ts +11 -0
  121. package/dist/src/helpers.d.ts.map +1 -1
  122. package/dist/src/helpers.js +22 -0
  123. package/dist/src/index.d.ts +7 -0
  124. package/dist/src/index.d.ts.map +1 -1
  125. package/dist/src/index.js +30 -4
  126. package/dist/src/proxy.d.ts +46 -0
  127. package/dist/src/proxy.d.ts.map +1 -0
  128. package/dist/src/proxy.js +124 -0
  129. package/dist/src/resources/styles.d.ts +29 -0
  130. package/dist/src/resources/styles.d.ts.map +1 -0
  131. package/dist/src/resources/styles.js +43 -0
  132. package/dist/src/styles.d.ts +110 -0
  133. package/dist/src/styles.d.ts.map +1 -0
  134. package/dist/src/styles.js +118 -0
  135. package/dist/src/tools/apply-style-preset.d.ts +27 -0
  136. package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
  137. package/dist/src/tools/apply-style-preset.js +29 -0
  138. package/dist/src/tools/count-features.d.ts.map +1 -1
  139. package/dist/src/tools/count-features.js +5 -4
  140. package/dist/src/tools/get-extent.d.ts.map +1 -1
  141. package/dist/src/tools/get-extent.js +6 -4
  142. package/dist/src/tools/get-style.d.ts +24 -0
  143. package/dist/src/tools/get-style.d.ts.map +1 -0
  144. package/dist/src/tools/get-style.js +28 -0
  145. package/dist/src/tools/query-features.d.ts.map +1 -1
  146. package/dist/src/tools/query-features.js +4 -1
  147. package/package.json +19 -4
@@ -0,0 +1,96 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
+ import { createOperatorCatalogServer } from "./operator-catalog.js";
6
+ function readBearer(req) {
7
+ const header = req.headers.authorization;
8
+ if (typeof header === "string" && header.toLowerCase().startsWith("bearer ")) {
9
+ return header.slice(7).trim();
10
+ }
11
+ const apiKey = req.headers["x-api-key"];
12
+ if (typeof apiKey === "string" && apiKey.length > 0) {
13
+ return apiKey;
14
+ }
15
+ return undefined;
16
+ }
17
+ /** Start the mock operator upstream and resolve once it is listening. */
18
+ export async function startMockUpstream(options = {}) {
19
+ const authToken = options.authToken ?? "mock-cert-token";
20
+ const paginate = options.paginate ?? true;
21
+ // One transport + server per session (the documented multi-session pattern),
22
+ // so multiple certifier clients — the authenticated pass and the
23
+ // unauthenticated auth-contract pass — can connect concurrently.
24
+ const sessions = new Map();
25
+ const httpServer = createServer((req, res) => {
26
+ void handleHttpRequest(req, res, sessions, authToken, paginate);
27
+ });
28
+ await new Promise((resolve) => httpServer.listen(0, "127.0.0.1", resolve));
29
+ const address = httpServer.address();
30
+ const url = `http://127.0.0.1:${address.port}/mcp`;
31
+ return {
32
+ url,
33
+ authToken,
34
+ async close() {
35
+ for (const transport of sessions.values()) {
36
+ await transport.close().catch(() => { });
37
+ }
38
+ sessions.clear();
39
+ await new Promise((resolve) => httpServer.close(() => resolve()));
40
+ },
41
+ };
42
+ }
43
+ async function readJsonBody(req) {
44
+ const chunks = [];
45
+ for await (const chunk of req) {
46
+ chunks.push(chunk);
47
+ }
48
+ if (chunks.length === 0) {
49
+ return undefined;
50
+ }
51
+ try {
52
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ async function handleHttpRequest(req, res, sessions, authToken, paginate) {
59
+ const token = readBearer(req);
60
+ if (token && token === authToken) {
61
+ // AuthInfo the transport threads through to request handlers (per request).
62
+ req.auth = { token, clientId: "cert-client", scopes: ["operator"] };
63
+ }
64
+ const sessionId = req.headers["mcp-session-id"];
65
+ const existing = typeof sessionId === "string" ? sessions.get(sessionId) : undefined;
66
+ if (existing) {
67
+ await existing.handleRequest(req, res);
68
+ return;
69
+ }
70
+ // No session yet — only an initialize request may open one.
71
+ const body = await readJsonBody(req);
72
+ if (!isInitializeRequest(body)) {
73
+ res.writeHead(400, { "Content-Type": "application/json" });
74
+ res.end(JSON.stringify({
75
+ jsonrpc: "2.0",
76
+ error: { code: -32000, message: "Bad Request: no valid session ID provided" },
77
+ id: null,
78
+ }));
79
+ return;
80
+ }
81
+ const transport = new StreamableHTTPServerTransport({
82
+ sessionIdGenerator: () => randomUUID(),
83
+ enableJsonResponse: true,
84
+ onsessioninitialized: (id) => {
85
+ sessions.set(id, transport);
86
+ },
87
+ });
88
+ transport.onclose = () => {
89
+ if (transport.sessionId) {
90
+ sessions.delete(transport.sessionId);
91
+ }
92
+ };
93
+ const server = createOperatorCatalogServer({ paginate });
94
+ await server.connect(transport);
95
+ await transport.handleRequest(req, res, body);
96
+ }
@@ -0,0 +1,82 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { type JsonSchema } from "./schema-index.js";
3
+ /**
4
+ * In-process mock of the honua-server `/mcp` operator surface.
5
+ *
6
+ * This is the offline, CI-default certification target: a representative
7
+ * streamable-HTTP MCP catalog shaped like the real honua-server `/mcp` (see
8
+ * honua-server #1950), NOT the retired 9-tool static `@honua/mcp-server`. It
9
+ * exists so the `mcp-sdk` CI gate can certify the operator surface — tools with
10
+ * output schemas, structured errors, cursor pagination, and an auth boundary —
11
+ * on every PR without a live server.
12
+ *
13
+ * Fixtures are derived from the vendored geospatial-mcp schemas: every mapped
14
+ * tool advertises the vendored inputSchema verbatim, so the certifier's
15
+ * conformance check exercises the real standard shape rather than a hand-rolled
16
+ * approximation.
17
+ *
18
+ * Nothing here is a product server. It is a labeled test fixture whose only job
19
+ * is to give the certifier a faithful `/mcp`-shaped surface to certify offline.
20
+ */
21
+ /** Canonical structured error the operator surface returns on failure paths. */
22
+ export interface StructuredOperatorError {
23
+ /**
24
+ * Stable, machine-actionable code. The auth boundary emits `unauthenticated`
25
+ * / `permission_denied`; argument validation emits `invalid_argument`.
26
+ */
27
+ code: "unauthenticated" | "permission_denied" | "invalid_argument" | "not_found" | "execution_failed";
28
+ /** geoprocessing-error envelope (validates against common/geoprocessing-error.schema.json). */
29
+ error: {
30
+ kind: "ValidationFailed" | "AuthorizationDenied" | "UnknownDataset" | "UnknownProcess" | "ExecutionFailed" | "Timeout" | "Cancelled" | "OutputBindingFailed";
31
+ message: string;
32
+ stepId?: string;
33
+ violations?: {
34
+ code: string;
35
+ message: string;
36
+ fieldPath?: string;
37
+ }[];
38
+ };
39
+ }
40
+ export interface OperatorTool {
41
+ name: string;
42
+ title: string;
43
+ description: string;
44
+ inputSchema: JsonSchema;
45
+ outputSchema: JsonSchema;
46
+ readOnly: boolean;
47
+ /** Deterministic structured output for a successful (authenticated) call. */
48
+ sampleOutput: Record<string, unknown>;
49
+ }
50
+ /**
51
+ * Representative operator catalog. Every tool advertises its vendored standard
52
+ * inputSchema verbatim — including the Honua-extension discovery tools
53
+ * (`honua_list_capabilities`, `honua_resolve_entity`) and
54
+ * `honua_publish_service`, which the standard maps to its own `publish_service`
55
+ * entry as a documented divergence from the known-gap `publish_result` family.
56
+ */
57
+ export declare function buildOperatorTools(): OperatorTool[];
58
+ export interface OperatorResource {
59
+ uri: string;
60
+ name: string;
61
+ mimeType: string;
62
+ contents: unknown;
63
+ }
64
+ export declare function buildOperatorResources(): OperatorResource[];
65
+ /** Page size for the paginated tools/list and resources/list responses. */
66
+ export declare const CATALOG_PAGE_SIZE = 12;
67
+ export interface OperatorCatalogOptions {
68
+ /**
69
+ * Whether an initial catalog page is followed by a `nextCursor` (default
70
+ * true). The certifier's pagination contract requires at least one paginated
71
+ * list; disable only for negative tests.
72
+ */
73
+ paginate?: boolean;
74
+ }
75
+ /**
76
+ * Build the low-level MCP `Server` for the mock operator catalog. Auth is
77
+ * enforced per request from `extra.authInfo` (populated by the HTTP transport
78
+ * from the bearer token), so a single server instance drives both the
79
+ * authenticated certification pass and the unauthenticated auth-contract pass.
80
+ */
81
+ export declare function createOperatorCatalogServer(options?: OperatorCatalogOptions): Server;
82
+ //# sourceMappingURL=operator-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-catalog.d.ts","sourceRoot":"","sources":["../../../src/certification/operator-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAUnE,OAAO,EAAE,KAAK,UAAU,EAA6D,MAAM,mBAAmB,CAAC;AAE/G;;;;;;;;;;;;;;;;;GAiBG;AAEH,gFAAgF;AAChF,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,WAAW,GAAG,kBAAkB,CAAC;IACtG,+FAA+F;IAC/F,KAAK,EAAE;QACL,IAAI,EACA,kBAAkB,GAClB,qBAAqB,GACrB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,SAAS,GACT,WAAW,GACX,qBAAqB,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACtE,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,YAAY,EAAE,UAAU,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AA0BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,EAAE,CAwSnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,EAAE,CA2B3D;AAED,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAoCpC,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CA+ExF"}
@@ -0,0 +1,466 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { compileValidator } from "./json-schema.js";
4
+ import { buildReferenceToolLookup, loadSchemaFile, loadSchemaIndex } from "./schema-index.js";
5
+ const OBJECT_SCHEMA = (properties, required = []) => ({
6
+ $schema: "https://json-schema.org/draft/2020-12/schema",
7
+ type: "object",
8
+ properties,
9
+ required,
10
+ additionalProperties: true,
11
+ });
12
+ /** Load a vendored tool inputSchema by its advertised (reference) name. */
13
+ function vendoredInputSchema(referenceName) {
14
+ const index = loadSchemaIndex();
15
+ const lookup = buildReferenceToolLookup(index);
16
+ const entry = lookup.get(referenceName);
17
+ if (!entry) {
18
+ throw new Error(`operator-catalog: no vendored schema mapped for ${referenceName}`);
19
+ }
20
+ // Advertise the vendored shape verbatim (minus the schema $id, which is a
21
+ // registry URL, not a tool inputSchema field) so conformance exercises the
22
+ // real standard shape.
23
+ const schema = { ...loadSchemaFile(entry.schema) };
24
+ delete schema.$id;
25
+ return schema;
26
+ }
27
+ /**
28
+ * Representative operator catalog. Every tool advertises its vendored standard
29
+ * inputSchema verbatim — including the Honua-extension discovery tools
30
+ * (`honua_list_capabilities`, `honua_resolve_entity`) and
31
+ * `honua_publish_service`, which the standard maps to its own `publish_service`
32
+ * entry as a documented divergence from the known-gap `publish_result` family.
33
+ */
34
+ export function buildOperatorTools() {
35
+ return [
36
+ {
37
+ name: "honua_list_layers",
38
+ title: "List layers",
39
+ description: "Discover published services and their layers.",
40
+ inputSchema: vendoredInputSchema("honua_list_layers"),
41
+ outputSchema: OBJECT_SCHEMA({
42
+ layers: {
43
+ type: "array",
44
+ items: OBJECT_SCHEMA({
45
+ serviceId: { type: "string" },
46
+ layerId: { type: "integer" },
47
+ name: { type: "string" },
48
+ geometryType: { type: "string" },
49
+ }, ["serviceId", "layerId", "name"]),
50
+ },
51
+ nextCursor: { type: "string" },
52
+ }, ["layers"]),
53
+ readOnly: true,
54
+ sampleOutput: {
55
+ layers: [{ serviceId: "svc-parks", layerId: 0, name: "Parks", geometryType: "Polygon" }],
56
+ },
57
+ },
58
+ {
59
+ name: "honua_query_features",
60
+ title: "Query features",
61
+ description: "Query features from a published layer with attribute/spatial filters.",
62
+ inputSchema: vendoredInputSchema("honua_query_features"),
63
+ outputSchema: OBJECT_SCHEMA({
64
+ features: {
65
+ type: "array",
66
+ items: OBJECT_SCHEMA({ attributes: { type: "object" } }, ["attributes"]),
67
+ },
68
+ count: { type: "integer", minimum: 0 },
69
+ nextCursor: { type: "string" },
70
+ }, ["features", "count"]),
71
+ readOnly: true,
72
+ sampleOutput: {
73
+ features: [{ attributes: { OBJECTID: 1, NAME: "Central Park" } }],
74
+ count: 1,
75
+ },
76
+ },
77
+ {
78
+ name: "honua_render_map",
79
+ title: "Render map",
80
+ description: "Render an image of ordered layers over an extent.",
81
+ inputSchema: vendoredInputSchema("honua_render_map"),
82
+ outputSchema: OBJECT_SCHEMA({
83
+ image: OBJECT_SCHEMA({
84
+ format: { type: "string" },
85
+ base64: { type: "string" },
86
+ width: { type: "integer" },
87
+ height: { type: "integer" },
88
+ }, ["format", "base64", "width", "height"]),
89
+ }, ["image"]),
90
+ readOnly: true,
91
+ sampleOutput: {
92
+ image: { format: "image/png", base64: "iVBORw0KGgo=", width: 512, height: 512 },
93
+ },
94
+ },
95
+ {
96
+ name: "honua_geocode_address",
97
+ title: "Geocode address",
98
+ description: "Resolve a freeform address to ranked candidates.",
99
+ inputSchema: vendoredInputSchema("honua_geocode_address"),
100
+ outputSchema: OBJECT_SCHEMA({
101
+ candidates: {
102
+ type: "array",
103
+ items: OBJECT_SCHEMA({
104
+ label: { type: "string" },
105
+ score: { type: "number" },
106
+ lon: { type: "number" },
107
+ lat: { type: "number" },
108
+ }, ["label", "score", "lon", "lat"]),
109
+ },
110
+ }, ["candidates"]),
111
+ readOnly: true,
112
+ sampleOutput: {
113
+ candidates: [{ label: "1600 Pennsylvania Ave NW", score: 0.98, lon: -77.0365, lat: 38.8977 }],
114
+ },
115
+ },
116
+ {
117
+ name: "honua_solve_route",
118
+ title: "Solve route",
119
+ description: "Solve an ordered route through a sequence of stops.",
120
+ inputSchema: vendoredInputSchema("honua_solve_route"),
121
+ outputSchema: OBJECT_SCHEMA({
122
+ route: OBJECT_SCHEMA({ distanceMeters: { type: "number" }, durationSeconds: { type: "number" } }, [
123
+ "distanceMeters",
124
+ "durationSeconds",
125
+ ]),
126
+ }, ["route"]),
127
+ readOnly: true,
128
+ sampleOutput: { route: { distanceMeters: 1234.5, durationSeconds: 210 } },
129
+ },
130
+ {
131
+ name: "honua_plan_analysis",
132
+ title: "Plan analysis",
133
+ description: "Compile a natural-language intent into an analysis plan.",
134
+ inputSchema: vendoredInputSchema("honua_plan_analysis"),
135
+ outputSchema: OBJECT_SCHEMA({
136
+ plan: OBJECT_SCHEMA({ planId: { type: "string" }, steps: { type: "array" } }, ["planId", "steps"]),
137
+ }, ["plan"]),
138
+ readOnly: true,
139
+ sampleOutput: {
140
+ plan: { planId: "plan-001", steps: [{ stepId: "s1", kind: "QueryFeatures" }] },
141
+ },
142
+ },
143
+ {
144
+ name: "honua_ground_candidates",
145
+ title: "Ground candidates",
146
+ description: "Ground a goal to a workflow family and ranked candidates.",
147
+ inputSchema: vendoredInputSchema("honua_ground_candidates"),
148
+ outputSchema: OBJECT_SCHEMA({ intentId: { type: "string" }, candidates: { type: "array" } }, [
149
+ "intentId",
150
+ "candidates",
151
+ ]),
152
+ readOnly: true,
153
+ sampleOutput: { intentId: "int-001", candidates: [{ family: "Analyze", score: 0.9 }] },
154
+ },
155
+ {
156
+ name: "honua_clarify_intent",
157
+ title: "Clarify intent",
158
+ description: "Re-run grounding with the operator's answers merged in.",
159
+ inputSchema: vendoredInputSchema("honua_clarify_intent"),
160
+ outputSchema: OBJECT_SCHEMA({ intentId: { type: "string" }, resolved: { type: "boolean" } }, [
161
+ "intentId",
162
+ "resolved",
163
+ ]),
164
+ readOnly: true,
165
+ sampleOutput: { intentId: "int-001", resolved: true },
166
+ },
167
+ {
168
+ name: "honua_validate_plan",
169
+ title: "Validate plan",
170
+ description: "Validate a (possibly partial) analysis plan and report structured violations.",
171
+ inputSchema: vendoredInputSchema("honua_validate_plan"),
172
+ outputSchema: OBJECT_SCHEMA({
173
+ valid: { type: "boolean" },
174
+ violations: {
175
+ type: "array",
176
+ items: OBJECT_SCHEMA({ code: { type: "string" }, message: { type: "string" } }, ["code", "message"]),
177
+ },
178
+ }, ["valid", "violations"]),
179
+ readOnly: true,
180
+ sampleOutput: { valid: false, violations: [{ code: "EMPTY_PLAN_ID", message: "planId is empty" }] },
181
+ },
182
+ {
183
+ name: "honua_dry_run_plan",
184
+ title: "Dry-run plan",
185
+ description: "Preview a plan's execution without submitting a job.",
186
+ inputSchema: vendoredInputSchema("honua_dry_run_plan"),
187
+ outputSchema: OBJECT_SCHEMA({
188
+ planId: { type: "string" },
189
+ estimated: OBJECT_SCHEMA({ steps: { type: "integer" } }, ["steps"]),
190
+ }, ["planId", "estimated"]),
191
+ readOnly: true,
192
+ sampleOutput: { planId: "plan-001", estimated: { steps: 1 } },
193
+ },
194
+ {
195
+ name: "honua_list_capabilities",
196
+ title: "List capabilities",
197
+ description: "List capabilities advertised by the connected honua deployment.",
198
+ inputSchema: vendoredInputSchema("honua_list_capabilities"),
199
+ outputSchema: OBJECT_SCHEMA({
200
+ capabilities: {
201
+ type: "array",
202
+ items: OBJECT_SCHEMA({ name: { type: "string" }, supported: { type: "boolean" } }, ["name", "supported"]),
203
+ },
204
+ }, ["capabilities"]),
205
+ readOnly: true,
206
+ sampleOutput: { capabilities: [{ name: "query", supported: true }] },
207
+ },
208
+ {
209
+ name: "honua_resolve_entity",
210
+ title: "Resolve entity",
211
+ description: "Resolve a freeform reference to canonical platform entities.",
212
+ inputSchema: vendoredInputSchema("honua_resolve_entity"),
213
+ outputSchema: OBJECT_SCHEMA({
214
+ entities: {
215
+ type: "array",
216
+ items: OBJECT_SCHEMA({ id: { type: "string" }, kind: { type: "string" } }, ["id", "kind"]),
217
+ },
218
+ }, ["entities"]),
219
+ readOnly: true,
220
+ sampleOutput: { entities: [{ id: "svc-parks", kind: "service" }] },
221
+ },
222
+ // ── Mutating / control-plane tools (never round-tripped) ─────────
223
+ {
224
+ name: "honua_execute_plan",
225
+ title: "Execute plan",
226
+ description: "Submit an executable plan and return an execution-job reference.",
227
+ inputSchema: vendoredInputSchema("honua_execute_plan"),
228
+ outputSchema: OBJECT_SCHEMA({ jobId: { type: "string" }, status: { type: "string" } }, ["jobId", "status"]),
229
+ readOnly: false,
230
+ sampleOutput: { jobId: "job-001", status: "Submitted" },
231
+ },
232
+ {
233
+ name: "honua_cancel_job",
234
+ title: "Cancel job",
235
+ description: "Request cancellation of a submitted execution job.",
236
+ inputSchema: vendoredInputSchema("honua_cancel_job"),
237
+ outputSchema: OBJECT_SCHEMA({ jobId: { type: "string" }, status: { type: "string" } }, ["jobId", "status"]),
238
+ readOnly: false,
239
+ sampleOutput: { jobId: "job-001", status: "Cancelling" },
240
+ },
241
+ {
242
+ name: "honua_propose_operation",
243
+ title: "Propose operation",
244
+ description: "Propose an in-scope control-plane operation for human approval.",
245
+ inputSchema: vendoredInputSchema("honua_propose_operation"),
246
+ outputSchema: OBJECT_SCHEMA({ proposalId: { type: "string" }, status: { type: "string" } }, [
247
+ "proposalId",
248
+ "status",
249
+ ]),
250
+ readOnly: false,
251
+ sampleOutput: { proposalId: "prop-001", status: "PendingApproval" },
252
+ },
253
+ {
254
+ name: "honua_publish_service",
255
+ title: "Publish service",
256
+ description: "Publish a source table as a new hosted service (documented divergence from the standard publish_result family, which remains a known-gap).",
257
+ inputSchema: vendoredInputSchema("honua_publish_service"),
258
+ outputSchema: OBJECT_SCHEMA({ publishedServiceId: { type: "string" }, uri: { type: "string" } }, [
259
+ "publishedServiceId",
260
+ "uri",
261
+ ]),
262
+ readOnly: false,
263
+ sampleOutput: { publishedServiceId: "svc-pub-001", uri: "honua://services/svc-pub-001" },
264
+ },
265
+ {
266
+ name: "honua_create_map_package",
267
+ title: "Create map package",
268
+ description: "Compose a map package from an intent and inputs.",
269
+ inputSchema: vendoredInputSchema("honua_create_map_package"),
270
+ outputSchema: OBJECT_SCHEMA({ mapPackageId: { type: "string" }, uri: { type: "string" } }, [
271
+ "mapPackageId",
272
+ "uri",
273
+ ]),
274
+ readOnly: false,
275
+ sampleOutput: { mapPackageId: "map-001", uri: "honua://maps/map-001" },
276
+ },
277
+ {
278
+ name: "honua_create_app_package",
279
+ title: "Create app package",
280
+ description: "Compose an app package from an intent and inputs.",
281
+ inputSchema: vendoredInputSchema("honua_create_app_package"),
282
+ outputSchema: OBJECT_SCHEMA({ appPackageId: { type: "string" }, uri: { type: "string" } }, [
283
+ "appPackageId",
284
+ "uri",
285
+ ]),
286
+ readOnly: false,
287
+ sampleOutput: { appPackageId: "app-001", uri: "honua://apps/app-001" },
288
+ },
289
+ ];
290
+ }
291
+ export function buildOperatorResources() {
292
+ return [
293
+ {
294
+ uri: "honua://results/res-001",
295
+ name: "Result package res-001",
296
+ mimeType: "application/json",
297
+ contents: { result_package_id: "res-001", status: "Ready", artifacts: [] },
298
+ },
299
+ {
300
+ uri: "honua://workspaces/ws-001",
301
+ name: "Workspace ws-001",
302
+ mimeType: "application/json",
303
+ contents: { workspace_id: "ws-001", artifacts: [] },
304
+ },
305
+ {
306
+ uri: "honua://styles/topographic",
307
+ name: "Style topographic",
308
+ mimeType: "application/json",
309
+ contents: { style_id: "topographic", title: "Topographic" },
310
+ },
311
+ {
312
+ uri: "honua://services/svc-parks",
313
+ name: "Published service svc-parks",
314
+ mimeType: "application/json",
315
+ contents: { published_service_id: "svc-parks", title: "Parks" },
316
+ },
317
+ ];
318
+ }
319
+ /** Page size for the paginated tools/list and resources/list responses. */
320
+ export const CATALOG_PAGE_SIZE = 12;
321
+ const UNAUTHENTICATED_ERROR = {
322
+ code: "unauthenticated",
323
+ error: {
324
+ kind: "AuthorizationDenied",
325
+ message: "Request is not authenticated. Provide a bearer token or API key.",
326
+ },
327
+ };
328
+ function invalidArgumentError(violations) {
329
+ return {
330
+ code: "invalid_argument",
331
+ error: {
332
+ kind: "ValidationFailed",
333
+ message: "One or more arguments failed validation.",
334
+ violations,
335
+ },
336
+ };
337
+ }
338
+ function validateToolArgs(tool, args) {
339
+ const validate = compileValidator(tool.inputSchema);
340
+ const valid = validate(args);
341
+ if (valid) {
342
+ return [];
343
+ }
344
+ return (validate.errors ?? []).map((e) => ({
345
+ code: "SCHEMA_VIOLATION",
346
+ message: `${e.instancePath || "(root)"} ${e.message ?? "is invalid"}`.trim(),
347
+ fieldPath: e.instancePath || undefined,
348
+ }));
349
+ }
350
+ /**
351
+ * Build the low-level MCP `Server` for the mock operator catalog. Auth is
352
+ * enforced per request from `extra.authInfo` (populated by the HTTP transport
353
+ * from the bearer token), so a single server instance drives both the
354
+ * authenticated certification pass and the unauthenticated auth-contract pass.
355
+ */
356
+ export function createOperatorCatalogServer(options = {}) {
357
+ const paginate = options.paginate ?? true;
358
+ const tools = buildOperatorTools();
359
+ const resources = buildOperatorResources();
360
+ const toolsByName = new Map(tools.map((t) => [t.name, t]));
361
+ const server = new Server({ name: "honua", version: "operator-mcp-mock" }, {
362
+ capabilities: { tools: { listChanged: true }, resources: { listChanged: true } },
363
+ instructions: "Mock honua /mcp operator surface for offline certification.",
364
+ });
365
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
366
+ const advertised = tools.map((t) => ({
367
+ name: t.name,
368
+ title: t.title,
369
+ description: t.description,
370
+ inputSchema: t.inputSchema,
371
+ outputSchema: t.outputSchema,
372
+ annotations: { readOnlyHint: t.readOnly },
373
+ }));
374
+ return paginateList(advertised, request.params?.cursor, paginate, "tools");
375
+ });
376
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
377
+ const tool = toolsByName.get(request.params.name);
378
+ if (!tool) {
379
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`, {
380
+ code: "not_found",
381
+ error: { kind: "UnknownProcess", message: `Unknown tool: ${request.params.name}` },
382
+ });
383
+ }
384
+ // Auth boundary: tools/call requires an authenticated request.
385
+ if (!extra.authInfo) {
386
+ return toolErrorResult(UNAUTHENTICATED_ERROR);
387
+ }
388
+ // Argument validation: invalid args yield a structured, actionable tool
389
+ // result (isError) — never a protocol-level error.
390
+ const violations = validateToolArgs(tool, request.params.arguments ?? {});
391
+ if (violations.length > 0) {
392
+ return toolErrorResult(invalidArgumentError(violations));
393
+ }
394
+ return {
395
+ content: [{ type: "text", text: JSON.stringify(tool.sampleOutput) }],
396
+ structuredContent: tool.sampleOutput,
397
+ isError: false,
398
+ };
399
+ });
400
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
401
+ const advertised = resources.map((r) => ({ uri: r.uri, name: r.name, mimeType: r.mimeType }));
402
+ return paginateList(advertised, request.params?.cursor, paginate, "resources");
403
+ });
404
+ server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
405
+ // Auth boundary: resources/read requires an authenticated request. Resources
406
+ // have no `isError` result channel, so the structured signal is carried on a
407
+ // protocol error's `data`.
408
+ if (!extra.authInfo) {
409
+ throw new McpError(ErrorCode.InvalidRequest, UNAUTHENTICATED_ERROR.error.message, UNAUTHENTICATED_ERROR);
410
+ }
411
+ const resource = resources.find((r) => r.uri === request.params.uri);
412
+ if (!resource) {
413
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`, {
414
+ code: "not_found",
415
+ error: { kind: "UnknownDataset", message: `Unknown resource: ${request.params.uri}` },
416
+ });
417
+ }
418
+ return {
419
+ contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: JSON.stringify(resource.contents) }],
420
+ };
421
+ });
422
+ return server;
423
+ }
424
+ function toolErrorResult(error) {
425
+ return {
426
+ content: [{ type: "text", text: error.error.message }],
427
+ structuredContent: error,
428
+ isError: true,
429
+ };
430
+ }
431
+ /**
432
+ * Split a catalog into a first page + `nextCursor`, honoring an incoming cursor.
433
+ * The cursor is an opaque, deterministic offset token.
434
+ */
435
+ function paginateList(items, cursor, paginate, key) {
436
+ if (!paginate || items.length <= CATALOG_PAGE_SIZE) {
437
+ return { [key]: items };
438
+ }
439
+ const offset = decodeCursor(cursor);
440
+ const page = items.slice(offset, offset + CATALOG_PAGE_SIZE);
441
+ const nextOffset = offset + page.length;
442
+ const result = { [key]: page };
443
+ if (nextOffset < items.length) {
444
+ result.nextCursor = encodeCursor(nextOffset);
445
+ }
446
+ return result;
447
+ }
448
+ function encodeCursor(offset) {
449
+ return Buffer.from(`offset:${offset}`, "utf8").toString("base64");
450
+ }
451
+ function decodeCursor(cursor) {
452
+ if (!cursor) {
453
+ return 0;
454
+ }
455
+ try {
456
+ const decoded = Buffer.from(cursor, "base64").toString("utf8");
457
+ const match = /^offset:(\d+)$/.exec(decoded);
458
+ if (match) {
459
+ return Number.parseInt(match[1], 10);
460
+ }
461
+ }
462
+ catch {
463
+ // fall through
464
+ }
465
+ return 0;
466
+ }
@@ -0,0 +1,4 @@
1
+ import type { CertificationReport } from "./certifier.js";
2
+ /** Render a human-readable Markdown summary of a certification report. */
3
+ export declare function renderMarkdown(report: CertificationReport): string;
4
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/certification/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CA6GlE"}