@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.
- package/README.md +259 -15
- package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -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 +79 -0
- package/certification/geospatial-mcp-schemas/index.json +390 -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 +27 -0
- package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -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/edit_features.schema.json +74 -0
- package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
- package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
- package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -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/overlay_features.schema.json +53 -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 +74 -0
- package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
- package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -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/summarize_statistics.schema.json +71 -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/capability.d.ts +48 -0
- package/dist/src/capability.d.ts.map +1 -0
- package/dist/src/capability.js +44 -0
- package/dist/src/certification/census-data.d.ts +33 -0
- package/dist/src/certification/census-data.d.ts.map +1 -0
- package/dist/src/certification/census-data.js +506 -0
- package/dist/src/certification/census-fixture-client.d.ts +7 -0
- package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
- package/dist/src/certification/census-fixture-client.js +226 -0
- package/dist/src/certification/certifier.d.ts +125 -0
- package/dist/src/certification/certifier.d.ts.map +1 -0
- package/dist/src/certification/certifier.js +455 -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 +63 -0
- package/dist/src/certification/contracts.d.ts +103 -0
- package/dist/src/certification/contracts.d.ts.map +1 -0
- package/dist/src/certification/contracts.js +245 -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 +45 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -0
- package/dist/src/certification/fixtures.js +92 -0
- package/dist/src/certification/json-schema.d.ts +71 -0
- package/dist/src/certification/json-schema.d.ts.map +1 -0
- package/dist/src/certification/json-schema.js +172 -0
- package/dist/src/certification/lifecycle.d.ts +14 -0
- package/dist/src/certification/lifecycle.d.ts.map +1 -0
- package/dist/src/certification/lifecycle.js +326 -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 +90 -0
- package/dist/src/certification/operator-catalog.d.ts.map +1 -0
- package/dist/src/certification/operator-catalog.js +650 -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 +103 -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 +32 -0
- package/dist/src/certification/schema-index.d.ts +94 -0
- package/dist/src/certification/schema-index.d.ts.map +1 -0
- package/dist/src/certification/schema-index.js +109 -0
- package/dist/src/certification/target.d.ts +48 -0
- package/dist/src/certification/target.d.ts.map +1 -0
- package/dist/src/certification/target.js +219 -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 +118 -0
- package/dist/src/eval/corpus.d.ts +34 -0
- package/dist/src/eval/corpus.d.ts.map +1 -0
- package/dist/src/eval/corpus.js +150 -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 +64 -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 +75 -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 +109 -0
- package/dist/src/eval/report.d.ts.map +1 -0
- package/dist/src/eval/report.js +133 -0
- package/dist/src/eval/runner.d.ts +28 -0
- package/dist/src/eval/runner.d.ts.map +1 -0
- package/dist/src/eval/runner.js +130 -0
- package/dist/src/eval/standalone-corpus.d.ts +3 -0
- package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
- package/dist/src/eval/standalone-corpus.js +699 -0
- package/dist/src/eval/types.d.ts +118 -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 +31 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +50 -4
- package/dist/src/provenance.d.ts +44 -0
- package/dist/src/provenance.d.ts.map +1 -0
- package/dist/src/provenance.js +37 -0
- 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 +76 -0
- package/dist/src/styles.d.ts +112 -0
- package/dist/src/styles.d.ts.map +1 -0
- package/dist/src/styles.js +144 -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 +41 -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 +39 -0
- package/dist/src/tools/list-services.d.ts.map +1 -1
- package/dist/src/tools/list-services.js +16 -1
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +4 -1
- package/package.json +28 -4
|
@@ -0,0 +1,650 @@
|
|
|
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
|
+
// OPTIONAL mutation-profile tool (geospatial-mcp `mutation` profile) — matched
|
|
291
|
+
// as an advertised-only known gap, never round-tripped by the read-only path;
|
|
292
|
+
// the mutating-round-trip contract drives its insert→update→delete lifecycle.
|
|
293
|
+
// This fixture opts into the optional mutation profile so that path stays
|
|
294
|
+
// certified; the real honua-server surface does NOT advertise this tool —
|
|
295
|
+
// Honua does not support AI operational data editing (honua-server ADR-0028).
|
|
296
|
+
name: "honua_edit_features",
|
|
297
|
+
title: "Edit features",
|
|
298
|
+
description: "Apply feature inserts, updates, and deletes to a published editable layer.",
|
|
299
|
+
inputSchema: OBJECT_SCHEMA({
|
|
300
|
+
serviceId: { type: "string", minLength: 1 },
|
|
301
|
+
layerId: { type: "integer", minimum: 0 },
|
|
302
|
+
adds: {
|
|
303
|
+
type: "array",
|
|
304
|
+
items: OBJECT_SCHEMA({ attributes: { type: "object" } }, ["attributes"]),
|
|
305
|
+
},
|
|
306
|
+
updates: {
|
|
307
|
+
type: "array",
|
|
308
|
+
items: OBJECT_SCHEMA({ objectId: { type: "integer" }, attributes: { type: "object" } }, [
|
|
309
|
+
"objectId",
|
|
310
|
+
"attributes",
|
|
311
|
+
]),
|
|
312
|
+
},
|
|
313
|
+
deletes: { type: "array", items: { type: "integer" } },
|
|
314
|
+
}, ["serviceId", "layerId"]),
|
|
315
|
+
outputSchema: OBJECT_SCHEMA({
|
|
316
|
+
addResults: { type: "array", items: EDIT_RESULT_SCHEMA },
|
|
317
|
+
updateResults: { type: "array", items: EDIT_RESULT_SCHEMA },
|
|
318
|
+
deleteResults: { type: "array", items: EDIT_RESULT_SCHEMA },
|
|
319
|
+
}),
|
|
320
|
+
readOnly: false,
|
|
321
|
+
sampleOutput: { addResults: [], updateResults: [], deleteResults: [] },
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
const EDIT_RESULT_SCHEMA = OBJECT_SCHEMA({ objectId: { type: "integer" }, success: { type: "boolean" } }, [
|
|
326
|
+
"objectId",
|
|
327
|
+
"success",
|
|
328
|
+
]);
|
|
329
|
+
export function buildOperatorResources() {
|
|
330
|
+
return [
|
|
331
|
+
{
|
|
332
|
+
uri: "honua://results/res-001",
|
|
333
|
+
name: "Result package res-001",
|
|
334
|
+
mimeType: "application/json",
|
|
335
|
+
contents: { result_package_id: "res-001", status: "Ready", artifacts: [] },
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
uri: "honua://workspaces/ws-001",
|
|
339
|
+
name: "Workspace ws-001",
|
|
340
|
+
mimeType: "application/json",
|
|
341
|
+
contents: { workspace_id: "ws-001", artifacts: [] },
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
uri: "honua://styles/topographic",
|
|
345
|
+
name: "Style topographic",
|
|
346
|
+
mimeType: "application/json",
|
|
347
|
+
contents: { style_id: "topographic", title: "Topographic" },
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
uri: "honua://services/svc-parks",
|
|
351
|
+
name: "Published service svc-parks",
|
|
352
|
+
mimeType: "application/json",
|
|
353
|
+
contents: { published_service_id: "svc-parks", title: "Parks" },
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
/** Page size for the paginated tools/list and resources/list responses. */
|
|
358
|
+
export const CATALOG_PAGE_SIZE = 12;
|
|
359
|
+
const UNAUTHENTICATED_ERROR = {
|
|
360
|
+
code: "unauthenticated",
|
|
361
|
+
error: {
|
|
362
|
+
kind: "AuthorizationDenied",
|
|
363
|
+
message: "Request is not authenticated. Provide a bearer token or API key.",
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
function invalidArgumentError(violations) {
|
|
367
|
+
return {
|
|
368
|
+
code: "invalid_argument",
|
|
369
|
+
error: {
|
|
370
|
+
kind: "ValidationFailed",
|
|
371
|
+
message: "One or more arguments failed validation.",
|
|
372
|
+
violations,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function validateToolArgs(tool, args) {
|
|
377
|
+
const validate = compileValidator(tool.inputSchema);
|
|
378
|
+
const valid = validate(args);
|
|
379
|
+
if (valid) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
return (validate.errors ?? []).map((e) => ({
|
|
383
|
+
code: "SCHEMA_VIOLATION",
|
|
384
|
+
message: `${e.instancePath || "(root)"} ${e.message ?? "is invalid"}`.trim(),
|
|
385
|
+
fieldPath: e.instancePath || undefined,
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
/** Feature-store key seeded so the pagination + mutation contracts have real rows. */
|
|
389
|
+
const SEED_SERVICE = "svc-parks";
|
|
390
|
+
const SEED_LAYER = 0;
|
|
391
|
+
function seedFeatureStore() {
|
|
392
|
+
const layer = new Map();
|
|
393
|
+
// OBJECTID 1 first so the existing single-feature round-trip fixture is stable.
|
|
394
|
+
const names = ["Central Park", "Riverside Park", "Lincoln Park", "Golden Gate Park", "Prospect Park"];
|
|
395
|
+
names.forEach((name, i) => layer.set(i + 1, { OBJECTID: i + 1, NAME: name }));
|
|
396
|
+
return new Map([[`${SEED_SERVICE}::${SEED_LAYER}`, layer]]);
|
|
397
|
+
}
|
|
398
|
+
function newCatalogState() {
|
|
399
|
+
return { features: seedFeatureStore(), nextObjectId: 1000, jobs: new Map(), nextJobSeq: 1 };
|
|
400
|
+
}
|
|
401
|
+
/** Number of polls before the job lifecycle mock reports `Succeeded`. */
|
|
402
|
+
const JOB_SUCCESS_AFTER_POLLS = 3;
|
|
403
|
+
/** Stateful tool handlers keyed by tool name; absent ⇒ echo the tool's sampleOutput. */
|
|
404
|
+
function buildToolHandlers(state) {
|
|
405
|
+
const handlers = new Map();
|
|
406
|
+
handlers.set("honua_query_features", (args) => {
|
|
407
|
+
const key = `${String(args.serviceId)}::${Number(args.layerId)}`;
|
|
408
|
+
const layer = state.features.get(key) ?? new Map();
|
|
409
|
+
let rows = [...layer.values()].sort((a, b) => Number(a.OBJECTID) - Number(b.OBJECTID));
|
|
410
|
+
if (Array.isArray(args.objectIds)) {
|
|
411
|
+
const wanted = new Set(args.objectIds.map((v) => Number(v)));
|
|
412
|
+
rows = rows.filter((r) => wanted.has(Number(r.OBJECTID)));
|
|
413
|
+
}
|
|
414
|
+
const total = rows.length;
|
|
415
|
+
const offset = decodeFeatureOffset(args);
|
|
416
|
+
const limit = clampLimit(args.limit);
|
|
417
|
+
const page = rows.slice(offset, offset + limit);
|
|
418
|
+
const nextOffset = offset + page.length;
|
|
419
|
+
const out = {
|
|
420
|
+
features: page.map((attributes) => ({ attributes })),
|
|
421
|
+
count: page.length,
|
|
422
|
+
totalCount: total,
|
|
423
|
+
resultOffset: offset,
|
|
424
|
+
};
|
|
425
|
+
if (nextOffset < total) {
|
|
426
|
+
out.nextCursor = encodeCursor(nextOffset);
|
|
427
|
+
out.exceededTransferLimit = true;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
out.exceededTransferLimit = false;
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
});
|
|
434
|
+
handlers.set("honua_edit_features", (args) => {
|
|
435
|
+
const key = `${String(args.serviceId)}::${Number(args.layerId)}`;
|
|
436
|
+
let layer = state.features.get(key);
|
|
437
|
+
if (!layer) {
|
|
438
|
+
layer = new Map();
|
|
439
|
+
state.features.set(key, layer);
|
|
440
|
+
}
|
|
441
|
+
const addResults = asArray(args.adds).map((add) => {
|
|
442
|
+
const objectId = state.nextObjectId++;
|
|
443
|
+
layer.set(objectId, { ...(add.attributes ?? {}), OBJECTID: objectId });
|
|
444
|
+
return { objectId, success: true };
|
|
445
|
+
});
|
|
446
|
+
const updateResults = asArray(args.updates).map((u) => {
|
|
447
|
+
const objectId = Number(u.objectId);
|
|
448
|
+
const existing = layer.get(objectId);
|
|
449
|
+
if (!existing) {
|
|
450
|
+
return { objectId, success: false };
|
|
451
|
+
}
|
|
452
|
+
layer.set(objectId, { ...existing, ...(u.attributes ?? {}), OBJECTID: objectId });
|
|
453
|
+
return { objectId, success: true };
|
|
454
|
+
});
|
|
455
|
+
const deleteResults = asArray(args.deletes).map((raw) => {
|
|
456
|
+
const objectId = Number(raw);
|
|
457
|
+
return { objectId, success: layer.delete(objectId) };
|
|
458
|
+
});
|
|
459
|
+
return { addResults, updateResults, deleteResults };
|
|
460
|
+
});
|
|
461
|
+
handlers.set("honua_execute_plan", () => {
|
|
462
|
+
const seq = state.nextJobSeq++;
|
|
463
|
+
const jobId = `job-${String(seq).padStart(3, "0")}`;
|
|
464
|
+
const resultPackageId = `res-${jobId}`;
|
|
465
|
+
state.jobs.set(jobId, { jobId, polls: 0, resultPackageId });
|
|
466
|
+
return { jobId, status: "Submitted" };
|
|
467
|
+
});
|
|
468
|
+
return handlers;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Build the low-level MCP `Server` for the mock operator catalog. Auth is
|
|
472
|
+
* enforced per request from `extra.authInfo` (populated by the HTTP transport
|
|
473
|
+
* from the bearer token), so a single server instance drives both the
|
|
474
|
+
* authenticated certification pass and the unauthenticated auth-contract pass.
|
|
475
|
+
*/
|
|
476
|
+
export function createOperatorCatalogServer(options = {}) {
|
|
477
|
+
const paginate = options.paginate ?? true;
|
|
478
|
+
const tools = buildOperatorTools();
|
|
479
|
+
const resources = buildOperatorResources();
|
|
480
|
+
const toolsByName = new Map(tools.map((t) => [t.name, t]));
|
|
481
|
+
const state = newCatalogState();
|
|
482
|
+
const handlers = buildToolHandlers(state);
|
|
483
|
+
const server = new Server({ name: "honua", version: "operator-mcp-mock" }, {
|
|
484
|
+
capabilities: { tools: { listChanged: true }, resources: { listChanged: true } },
|
|
485
|
+
instructions: "Mock honua /mcp operator surface for offline certification.",
|
|
486
|
+
});
|
|
487
|
+
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
488
|
+
const advertised = tools.map((t) => ({
|
|
489
|
+
name: t.name,
|
|
490
|
+
title: t.title,
|
|
491
|
+
description: t.description,
|
|
492
|
+
inputSchema: t.inputSchema,
|
|
493
|
+
outputSchema: t.outputSchema,
|
|
494
|
+
annotations: { readOnlyHint: t.readOnly },
|
|
495
|
+
}));
|
|
496
|
+
return paginateList(advertised, request.params?.cursor, paginate, "tools");
|
|
497
|
+
});
|
|
498
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
499
|
+
const tool = toolsByName.get(request.params.name);
|
|
500
|
+
if (!tool) {
|
|
501
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`, {
|
|
502
|
+
code: "not_found",
|
|
503
|
+
error: { kind: "UnknownProcess", message: `Unknown tool: ${request.params.name}` },
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
// Auth boundary: tools/call requires an authenticated request.
|
|
507
|
+
if (!extra.authInfo) {
|
|
508
|
+
return toolErrorResult(UNAUTHENTICATED_ERROR);
|
|
509
|
+
}
|
|
510
|
+
// Argument validation: invalid args yield a structured, actionable tool
|
|
511
|
+
// result (isError) — never a protocol-level error.
|
|
512
|
+
const violations = validateToolArgs(tool, request.params.arguments ?? {});
|
|
513
|
+
if (violations.length > 0) {
|
|
514
|
+
return toolErrorResult(invalidArgumentError(violations));
|
|
515
|
+
}
|
|
516
|
+
const handler = handlers.get(tool.name);
|
|
517
|
+
const output = handler ? handler(request.params.arguments ?? {}) : tool.sampleOutput;
|
|
518
|
+
return {
|
|
519
|
+
content: [{ type: "text", text: JSON.stringify(output) }],
|
|
520
|
+
structuredContent: output,
|
|
521
|
+
isError: false,
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|
525
|
+
const advertised = resources.map((r) => ({ uri: r.uri, name: r.name, mimeType: r.mimeType }));
|
|
526
|
+
return paginateList(advertised, request.params?.cursor, paginate, "resources");
|
|
527
|
+
});
|
|
528
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
|
|
529
|
+
// Auth boundary: resources/read requires an authenticated request. Resources
|
|
530
|
+
// have no `isError` result channel, so the structured signal is carried on a
|
|
531
|
+
// protocol error's `data`.
|
|
532
|
+
if (!extra.authInfo) {
|
|
533
|
+
throw new McpError(ErrorCode.InvalidRequest, UNAUTHENTICATED_ERROR.error.message, UNAUTHENTICATED_ERROR);
|
|
534
|
+
}
|
|
535
|
+
const uri = request.params.uri;
|
|
536
|
+
// Dynamic job lifecycle resource: honua://jobs/{id} advances a documented
|
|
537
|
+
// Submitted→Running→Succeeded state machine on each poll.
|
|
538
|
+
const jobMatch = /^honua:\/\/jobs\/([^/]+)$/.exec(uri);
|
|
539
|
+
if (jobMatch) {
|
|
540
|
+
return readJobResource(state, jobMatch[1], uri);
|
|
541
|
+
}
|
|
542
|
+
// Dynamic result package (produced by a Succeeded job) or the seeded packages.
|
|
543
|
+
const resultMatch = /^honua:\/\/results\/([^/]+)$/.exec(uri);
|
|
544
|
+
if (resultMatch && !resources.some((r) => r.uri === uri)) {
|
|
545
|
+
return jsonResource(uri, {
|
|
546
|
+
resultPackageId: resultMatch[1],
|
|
547
|
+
status: "Ready",
|
|
548
|
+
artifacts: [{ kind: "FeatureService", uri: `honua://services/svc-pub-${resultMatch[1]}` }],
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const resource = resources.find((r) => r.uri === uri);
|
|
552
|
+
if (!resource) {
|
|
553
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`, {
|
|
554
|
+
code: "not_found",
|
|
555
|
+
error: { kind: "UnknownDataset", message: `Unknown resource: ${uri}` },
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: JSON.stringify(resource.contents) }],
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
return server;
|
|
563
|
+
}
|
|
564
|
+
function readJobResource(state, jobId, uri) {
|
|
565
|
+
const job = state.jobs.get(jobId);
|
|
566
|
+
if (!job) {
|
|
567
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown job: ${jobId}`, {
|
|
568
|
+
code: "not_found",
|
|
569
|
+
error: { kind: "UnknownProcess", message: `Unknown job: ${jobId}` },
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
job.polls++;
|
|
573
|
+
const succeeded = job.polls >= JOB_SUCCESS_AFTER_POLLS;
|
|
574
|
+
const status = succeeded ? "Succeeded" : "Running";
|
|
575
|
+
const fraction = Math.min(job.polls / JOB_SUCCESS_AFTER_POLLS, 1);
|
|
576
|
+
const body = {
|
|
577
|
+
jobId: job.jobId,
|
|
578
|
+
status,
|
|
579
|
+
progress: { fraction: Number(fraction.toFixed(4)), message: `step ${job.polls}` },
|
|
580
|
+
};
|
|
581
|
+
if (succeeded) {
|
|
582
|
+
body.resultPackageId = job.resultPackageId;
|
|
583
|
+
body.resultUri = `honua://results/${job.resultPackageId}`;
|
|
584
|
+
}
|
|
585
|
+
return jsonResource(uri, body);
|
|
586
|
+
}
|
|
587
|
+
function jsonResource(uri, body) {
|
|
588
|
+
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(body) }] };
|
|
589
|
+
}
|
|
590
|
+
function asArray(value) {
|
|
591
|
+
return Array.isArray(value) ? value : [];
|
|
592
|
+
}
|
|
593
|
+
function clampLimit(raw) {
|
|
594
|
+
const n = Number(raw);
|
|
595
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
596
|
+
return 100;
|
|
597
|
+
}
|
|
598
|
+
return Math.min(Math.floor(n), 1000);
|
|
599
|
+
}
|
|
600
|
+
/** Resolve a feature-page offset from an explicit `resultOffset` or a page `cursor`. */
|
|
601
|
+
function decodeFeatureOffset(args) {
|
|
602
|
+
if (typeof args.cursor === "string") {
|
|
603
|
+
return decodeCursor(args.cursor);
|
|
604
|
+
}
|
|
605
|
+
const off = Number(args.resultOffset);
|
|
606
|
+
return Number.isFinite(off) && off >= 0 ? Math.floor(off) : 0;
|
|
607
|
+
}
|
|
608
|
+
function toolErrorResult(error) {
|
|
609
|
+
return {
|
|
610
|
+
content: [{ type: "text", text: error.error.message }],
|
|
611
|
+
structuredContent: error,
|
|
612
|
+
isError: true,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Split a catalog into a first page + `nextCursor`, honoring an incoming cursor.
|
|
617
|
+
* The cursor is an opaque, deterministic offset token.
|
|
618
|
+
*/
|
|
619
|
+
function paginateList(items, cursor, paginate, key) {
|
|
620
|
+
if (!paginate || items.length <= CATALOG_PAGE_SIZE) {
|
|
621
|
+
return { [key]: items };
|
|
622
|
+
}
|
|
623
|
+
const offset = decodeCursor(cursor);
|
|
624
|
+
const page = items.slice(offset, offset + CATALOG_PAGE_SIZE);
|
|
625
|
+
const nextOffset = offset + page.length;
|
|
626
|
+
const result = { [key]: page };
|
|
627
|
+
if (nextOffset < items.length) {
|
|
628
|
+
result.nextCursor = encodeCursor(nextOffset);
|
|
629
|
+
}
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
function encodeCursor(offset) {
|
|
633
|
+
return Buffer.from(`offset:${offset}`, "utf8").toString("base64");
|
|
634
|
+
}
|
|
635
|
+
function decodeCursor(cursor) {
|
|
636
|
+
if (!cursor) {
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
const decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
641
|
+
const match = /^offset:(\d+)$/.exec(decoded);
|
|
642
|
+
if (match) {
|
|
643
|
+
return Number.parseInt(match[1], 10);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// fall through
|
|
648
|
+
}
|
|
649
|
+
return 0;
|
|
650
|
+
}
|
|
@@ -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,CA2HlE"}
|