@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.
- package/README.md +167 -0
- package/certification/geospatial-mcp-schemas/PROVENANCE.md +33 -0
- package/certification/geospatial-mcp-schemas/README.md +90 -0
- package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +73 -0
- package/certification/geospatial-mcp-schemas/index.json +311 -0
- package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
- package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
- package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
- package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
- package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +23 -0
- package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
- package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
- package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
- package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +30 -0
- package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
- package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
- package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
- package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
- package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
- package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
- package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
- package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +54 -0
- package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
- package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +55 -0
- package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
- package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
- package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
- package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
- package/dist/src/certification/certifier.d.ts +119 -0
- package/dist/src/certification/certifier.d.ts.map +1 -0
- package/dist/src/certification/certifier.js +410 -0
- package/dist/src/certification/cli.d.ts +3 -0
- package/dist/src/certification/cli.d.ts.map +1 -0
- package/dist/src/certification/cli.js +48 -0
- package/dist/src/certification/contracts.d.ts +62 -0
- package/dist/src/certification/contracts.d.ts.map +1 -0
- package/dist/src/certification/contracts.js +174 -0
- package/dist/src/certification/fixture-client.d.ts +4 -0
- package/dist/src/certification/fixture-client.d.ts.map +1 -0
- package/dist/src/certification/fixture-client.js +113 -0
- package/dist/src/certification/fixtures.d.ts +32 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -0
- package/dist/src/certification/fixtures.js +64 -0
- package/dist/src/certification/json-schema.d.ts +49 -0
- package/dist/src/certification/json-schema.d.ts.map +1 -0
- package/dist/src/certification/json-schema.js +164 -0
- package/dist/src/certification/mock-upstream.d.ts +29 -0
- package/dist/src/certification/mock-upstream.d.ts.map +1 -0
- package/dist/src/certification/mock-upstream.js +96 -0
- package/dist/src/certification/operator-catalog.d.ts +82 -0
- package/dist/src/certification/operator-catalog.d.ts.map +1 -0
- package/dist/src/certification/operator-catalog.js +466 -0
- package/dist/src/certification/report.d.ts +4 -0
- package/dist/src/certification/report.d.ts.map +1 -0
- package/dist/src/certification/report.js +90 -0
- package/dist/src/certification/run.d.ts +28 -0
- package/dist/src/certification/run.d.ts.map +1 -0
- package/dist/src/certification/run.js +31 -0
- package/dist/src/certification/schema-index.d.ts +69 -0
- package/dist/src/certification/schema-index.d.ts.map +1 -0
- package/dist/src/certification/schema-index.js +82 -0
- package/dist/src/certification/target.d.ts +40 -0
- package/dist/src/certification/target.d.ts.map +1 -0
- package/dist/src/certification/target.js +167 -0
- package/dist/src/eval/cli.d.ts +3 -0
- package/dist/src/eval/cli.d.ts.map +1 -0
- package/dist/src/eval/cli.js +116 -0
- package/dist/src/eval/corpus.d.ts +27 -0
- package/dist/src/eval/corpus.d.ts.map +1 -0
- package/dist/src/eval/corpus.js +134 -0
- package/dist/src/eval/drivers/anthropic.d.ts +14 -0
- package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
- package/dist/src/eval/drivers/anthropic.js +120 -0
- package/dist/src/eval/drivers/bedrock.d.ts +54 -0
- package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
- package/dist/src/eval/drivers/bedrock.js +178 -0
- package/dist/src/eval/drivers/deterministic.d.ts +17 -0
- package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
- package/dist/src/eval/drivers/deterministic.js +51 -0
- package/dist/src/eval/drivers/index.d.ts +34 -0
- package/dist/src/eval/drivers/index.d.ts.map +1 -0
- package/dist/src/eval/drivers/index.js +74 -0
- package/dist/src/eval/drivers/openai.d.ts +14 -0
- package/dist/src/eval/drivers/openai.d.ts.map +1 -0
- package/dist/src/eval/drivers/openai.js +136 -0
- package/dist/src/eval/grade.d.ts +14 -0
- package/dist/src/eval/grade.d.ts.map +1 -0
- package/dist/src/eval/grade.js +56 -0
- package/dist/src/eval/northstar-corpus.d.ts +3 -0
- package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
- package/dist/src/eval/northstar-corpus.js +194 -0
- package/dist/src/eval/operator-corpus.d.ts +3 -0
- package/dist/src/eval/operator-corpus.d.ts.map +1 -0
- package/dist/src/eval/operator-corpus.js +193 -0
- package/dist/src/eval/report.d.ts +104 -0
- package/dist/src/eval/report.d.ts.map +1 -0
- package/dist/src/eval/report.js +130 -0
- package/dist/src/eval/runner.d.ts +22 -0
- package/dist/src/eval/runner.d.ts.map +1 -0
- package/dist/src/eval/runner.js +116 -0
- package/dist/src/eval/types.d.ts +93 -0
- package/dist/src/eval/types.d.ts.map +1 -0
- package/dist/src/eval/types.js +10 -0
- package/dist/src/helpers.d.ts +11 -0
- package/dist/src/helpers.d.ts.map +1 -1
- package/dist/src/helpers.js +22 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +30 -4
- package/dist/src/proxy.d.ts +46 -0
- package/dist/src/proxy.d.ts.map +1 -0
- package/dist/src/proxy.js +124 -0
- package/dist/src/resources/styles.d.ts +29 -0
- package/dist/src/resources/styles.d.ts.map +1 -0
- package/dist/src/resources/styles.js +43 -0
- package/dist/src/styles.d.ts +110 -0
- package/dist/src/styles.d.ts.map +1 -0
- package/dist/src/styles.js +118 -0
- package/dist/src/tools/apply-style-preset.d.ts +27 -0
- package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
- package/dist/src/tools/apply-style-preset.js +29 -0
- package/dist/src/tools/count-features.d.ts.map +1 -1
- package/dist/src/tools/count-features.js +5 -4
- package/dist/src/tools/get-extent.d.ts.map +1 -1
- package/dist/src/tools/get-extent.js +6 -4
- package/dist/src/tools/get-style.d.ts +24 -0
- package/dist/src/tools/get-style.d.ts.map +1 -0
- package/dist/src/tools/get-style.js +28 -0
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +4 -1
- 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 @@
|
|
|
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"}
|