@honua/mcp-server 0.1.10-beta.0 → 0.1.11-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/certification/setup-parity.md +79 -0
- package/dist/src/certification/setup-parity.d.ts +86 -0
- package/dist/src/certification/setup-parity.d.ts.map +1 -0
- package/dist/src/certification/setup-parity.js +188 -0
- package/dist/src/eval/cli.js +2 -2
- package/dist/src/eval/corpus.d.ts.map +1 -1
- package/dist/src/eval/corpus.js +6 -5
- package/dist/src/eval/grade.d.ts.map +1 -1
- package/dist/src/eval/grade.js +25 -0
- package/dist/src/eval/report.d.ts +9 -0
- package/dist/src/eval/report.d.ts.map +1 -1
- package/dist/src/eval/report.js +15 -5
- package/dist/src/eval/runner.d.ts.map +1 -1
- package/dist/src/eval/runner.js +16 -1
- package/dist/src/eval/types.d.ts +37 -2
- package/dist/src/eval/types.d.ts.map +1 -1
- package/dist/src/eval/workflow-corpus.d.ts +9 -0
- package/dist/src/eval/workflow-corpus.d.ts.map +1 -0
- package/dist/src/eval/workflow-corpus.js +306 -0
- package/dist/src/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +7 -1
- package/dist/src/release/zero-to-map-cli.d.ts.map +1 -1
- package/dist/src/release/zero-to-map-cli.js +7 -10
- package/dist/src/release/zero-to-map.d.ts +36 -69
- package/dist/src/release/zero-to-map.d.ts.map +1 -1
- package/dist/src/release/zero-to-map.js +102 -178
- package/package.json +4 -2
- package/release/zero-to-map/README.md +61 -83
- package/release/zero-to-map/configs/claude-code.mcp.json +1 -1
- package/release/zero-to-map/configs/claude-desktop.json +1 -1
- package/release/zero-to-map/configs/cursor.json +1 -1
- package/release/zero-to-map/journey.v1.json +163 -751
- package/release/zero-to-map/contracts/esri-gp-mcp.v1.json +0 -73
|
@@ -9,7 +9,6 @@ import { publicHttps } from "./zero-to-map-provision.js";
|
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { inflateSync } from "node:zlib";
|
|
12
|
-
import { ADMIN_MCP_EXCLUDED_OPERATIONS, ADMIN_MCP_EXCLUDED_OPERATION_COUNT, ADMIN_MCP_EXCLUSION_ROSTER_SHA256, ADMIN_MCP_PUBLISHED_TOOL_NAMES, MCP_DEFAULT_STATIC_TOOL_COUNT, MCP_DEFAULT_TOTAL_TOOL_COUNT, } from "@honua/sdk-js/control-plane";
|
|
13
12
|
export const ZERO_TO_MAP_PLAN_SCHEMA = "honua.zero-to-map.plan/v1";
|
|
14
13
|
export const ZERO_TO_MAP_RECEIPT_SCHEMA = "honua.zero-to-map.receipt/v1";
|
|
15
14
|
export const ZERO_TO_MAP_CONSOLE_RECEIPT_SCHEMA = "honua.zero-to-map.console-receipt/v1";
|
|
@@ -18,63 +17,31 @@ export const ZERO_TO_MAP_CATALOG_RECEIPT_SCHEMA = "honua.zero-to-map.catalog/v1"
|
|
|
18
17
|
/**
|
|
19
18
|
* Stages in the release journey: install, admin, style, geoprocessing, studio,
|
|
20
19
|
* proposal, console, artifact. `style` proves a canonical style applied to a
|
|
21
|
-
* *published* layer reaches rendered pixels
|
|
22
|
-
*
|
|
20
|
+
* *published* layer reaches rendered pixels. `console` fetches the share URLs
|
|
21
|
+
* the same admin publish returned; it does not import a second-principal receipt.
|
|
23
22
|
*/
|
|
24
23
|
export const ZERO_TO_MAP_STAGE_COUNT = 8;
|
|
25
|
-
/** Identifier of the always-required default roster. */
|
|
26
|
-
export const ZERO_TO_MAP_BASE_PROFILE_ID = "base";
|
|
27
24
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `analysis` contributes six members; only `honua_buffer_features` - the
|
|
31
|
-
* MCP-native dataset Buffer verb the journey actually executes - is confirmed
|
|
32
|
-
* by an in-repo artifact. `esri-gp` contributes exactly the three tools named
|
|
33
|
-
* by `release/zero-to-map/contracts/esri-gp-mcp.v1.json`.
|
|
25
|
+
* Receipt id for the closed operator roster: the MCP tools this journey calls,
|
|
26
|
+
* not a 432/441 catalog total and not the `analysis` or `esri-gp` profiles.
|
|
34
27
|
*/
|
|
35
|
-
export const
|
|
36
|
-
{
|
|
37
|
-
id: "analysis",
|
|
38
|
-
memberCount: 6,
|
|
39
|
-
confirmedMembers: ["honua_buffer_features"],
|
|
40
|
-
namePrefixes: [],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "esri-gp",
|
|
44
|
-
memberCount: 3,
|
|
45
|
-
confirmedMembers: ["honua_esri_gp_describe_task", "honua_esri_gp_execute_task", "honua_esri_gp_list_tasks"],
|
|
46
|
-
namePrefixes: ["honua_esri_gp_"],
|
|
47
|
-
},
|
|
48
|
-
];
|
|
28
|
+
export const ZERO_TO_MAP_CLOSED_ROSTER_ID = "operator";
|
|
49
29
|
/**
|
|
50
30
|
* The reserved `tools/list` view name that opts a request back out of any
|
|
51
31
|
* server-configured or session-negotiated workflow view and returns the
|
|
52
32
|
* complete paginated catalog (honua-server#3428).
|
|
53
33
|
*
|
|
54
|
-
* The candidate
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* server profile for what is actually a discovery setting. The preflight
|
|
60
|
-
* therefore always selects this escape hatch explicitly rather than relying on
|
|
61
|
-
* the candidate having no default configured.
|
|
34
|
+
* The candidate may publish a bounded workflow view and may make one the
|
|
35
|
+
* default through {@link ZERO_TO_MAP_WORKFLOW_VIEW_CONFIG_KEY}. Selecting a
|
|
36
|
+
* view can only *narrow* discovery, so a host that configures a default view
|
|
37
|
+
* would hide tools the journey calls. The preflight always selects this escape
|
|
38
|
+
* hatch explicitly rather than relying on the candidate having no default.
|
|
62
39
|
*/
|
|
63
40
|
export const ZERO_TO_MAP_FULL_CATALOG_VIEW = "full";
|
|
64
41
|
/** Configuration key whose value would narrow the candidate's default `tools/list`. */
|
|
65
42
|
export const ZERO_TO_MAP_WORKFLOW_VIEW_CONFIG_KEY = "Mcp:WorkflowViews:DefaultView";
|
|
66
43
|
/** Environment-variable spelling of {@link ZERO_TO_MAP_WORKFLOW_VIEW_CONFIG_KEY}. */
|
|
67
44
|
export const ZERO_TO_MAP_WORKFLOW_VIEW_ENV_KEY = "Mcp__WorkflowViews__DefaultView";
|
|
68
|
-
/** Profiles the candidate must advertise before the journey is executable. */
|
|
69
|
-
export const ZERO_TO_MAP_REQUIRED_PROFILES = [
|
|
70
|
-
ZERO_TO_MAP_BASE_PROFILE_ID,
|
|
71
|
-
...ZERO_TO_MAP_ADDITIVE_PROFILES.map((profile) => profile.id),
|
|
72
|
-
];
|
|
73
|
-
/** Tools the base roster contributes: 47 static + 385 published Admin projections. */
|
|
74
|
-
export const ZERO_TO_MAP_BASE_PROFILE_TOOL_COUNT = MCP_DEFAULT_STATIC_TOOL_COUNT + ADMIN_MCP_PUBLISHED_TOOL_NAMES.length;
|
|
75
|
-
/** Derived candidate total. Never hardcode this; it follows from the enabled profiles. */
|
|
76
|
-
export const ZERO_TO_MAP_EXPECTED_TOTAL_TOOL_COUNT = ZERO_TO_MAP_BASE_PROFILE_TOOL_COUNT +
|
|
77
|
-
ZERO_TO_MAP_ADDITIVE_PROFILES.reduce((total, profile) => total + profile.memberCount, 0);
|
|
78
45
|
const STUDIO_GENERATION_MUTATION_TOOLS = new Set([
|
|
79
46
|
"honua_studio_add_control",
|
|
80
47
|
"honua_studio_add_layer",
|
|
@@ -840,24 +807,69 @@ function summarizeNames(names, limit = 10) {
|
|
|
840
807
|
? `${sorted.slice(0, limit).join(", ")}, ... (+${sorted.length - limit})`
|
|
841
808
|
: sorted.join(", ");
|
|
842
809
|
}
|
|
810
|
+
/**
|
|
811
|
+
* Distinct MCP tools the plan calls, sorted. This is the closed operator roster.
|
|
812
|
+
* A server may advertise more; the preflight does not require a catalog total.
|
|
813
|
+
*/
|
|
814
|
+
export function zeroToMapClosedRoster(plan) {
|
|
815
|
+
return [...new Set(requiredMcpToolUses(plan).map((use) => use.tool))].sort();
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Turn a share URL into an absolute request URL.
|
|
819
|
+
*
|
|
820
|
+
* Admin publication returns `StudioPublishedRoutes.BuildActiveUrl`, a
|
|
821
|
+
* root-relative `/api/v1/studio/published/...` path. Live mode resolves that
|
|
822
|
+
* path against the MCP endpoint origin. Absolute HTTPS is accepted as-is.
|
|
823
|
+
* HTTP is accepted only for a loopback origin, which is the local-install proof.
|
|
824
|
+
*/
|
|
825
|
+
export function resolvePublishedShareUrl(url, mcpUrl) {
|
|
826
|
+
if (url.includes("${"))
|
|
827
|
+
throw new Error("published share URL was not resolved");
|
|
828
|
+
const absolute = url.startsWith("/") ? joinSharePath(url, mcpUrl) : url;
|
|
829
|
+
let requested;
|
|
830
|
+
try {
|
|
831
|
+
requested = new URL(absolute);
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
throw new Error("published share URL must be HTTPS, or HTTP on a loopback origin");
|
|
835
|
+
}
|
|
836
|
+
if (requested.username || requested.password || requested.search || requested.hash) {
|
|
837
|
+
throw new Error("published share URL must not embed credentials, query, or fragment");
|
|
838
|
+
}
|
|
839
|
+
const loopback = requested.hostname === "localhost" || requested.hostname === "127.0.0.1" || requested.hostname === "::1";
|
|
840
|
+
if (requested.protocol === "https:" || (requested.protocol === "http:" && loopback))
|
|
841
|
+
return requested.toString();
|
|
842
|
+
throw new Error("published share URL must be HTTPS, or HTTP on a loopback origin");
|
|
843
|
+
}
|
|
844
|
+
function joinSharePath(path, mcpUrl) {
|
|
845
|
+
if (!mcpUrl)
|
|
846
|
+
throw new Error("a root-relative share URL requires the MCP endpoint origin");
|
|
847
|
+
let endpoint;
|
|
848
|
+
try {
|
|
849
|
+
endpoint = new URL(mcpUrl);
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
throw new Error("a root-relative share URL requires the MCP endpoint origin");
|
|
853
|
+
}
|
|
854
|
+
return `${endpoint.origin}${path}`;
|
|
855
|
+
}
|
|
843
856
|
/**
|
|
844
857
|
* Preflight the advertised MCP catalog before the first live mutation.
|
|
845
858
|
*
|
|
846
|
-
* The
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
* excluded / truncated / drifted) and name the journey stage and action that
|
|
853
|
-
* needs the tool.
|
|
859
|
+
* The closed roster is the set of tools this journey calls. The preflight does
|
|
860
|
+
* not require a 432- or 441-tool catalog, and it does not require the
|
|
861
|
+
* `analysis` or `esri-gp` profiles. A missing member names the stage and
|
|
862
|
+
* action that needs it. Extra advertised tools are not a failure: the server
|
|
863
|
+
* may publish more than this journey uses. Input-schema drift on a planned
|
|
864
|
+
* call still blocks before that call is sent.
|
|
854
865
|
*/
|
|
855
866
|
async function assertMcpCatalog(plan, adapter) {
|
|
856
867
|
const requiredUses = requiredMcpToolUses(plan);
|
|
857
|
-
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
868
|
+
const closedRoster = zeroToMapClosedRoster(plan);
|
|
869
|
+
// Opt out of any server-configured or session-negotiated workflow view. A
|
|
870
|
+
// candidate with `Mcp:WorkflowViews:DefaultView` set would otherwise hide
|
|
871
|
+
// tools this journey calls, and the finding would look like a missing tool
|
|
872
|
+
// rather than a discovery setting.
|
|
861
873
|
const catalog = await adapter.listTools({ view: ZERO_TO_MAP_FULL_CATALOG_VIEW });
|
|
862
874
|
const names = catalog.map((tool) => tool.name);
|
|
863
875
|
const findings = [];
|
|
@@ -865,123 +877,22 @@ async function assertMcpCatalog(plan, adapter) {
|
|
|
865
877
|
if (duplicateNames.length > 0) {
|
|
866
878
|
findings.push(`duplicate: the catalog advertises ${summarizeNames(duplicateNames)} more than once`);
|
|
867
879
|
}
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
// --- base profile: the 11 audited secret/session exclusions ----------------
|
|
882
|
-
const excludedToolNames = ADMIN_MCP_EXCLUDED_OPERATIONS.map((operation) => operation.toolName);
|
|
883
|
-
if (excludedToolNames.length !== ADMIN_MCP_EXCLUDED_OPERATION_COUNT) {
|
|
884
|
-
findings.push(`excluded: the generated exclusion roster carries ${excludedToolNames.length} operations; the audited ` +
|
|
885
|
-
`contract requires ${ADMIN_MCP_EXCLUDED_OPERATION_COUNT}`);
|
|
886
|
-
}
|
|
887
|
-
const advertisedExclusions = excludedToolNames.filter((name) => advertisedAdminSet.has(name)).sort();
|
|
888
|
-
if (advertisedExclusions.length > 0) {
|
|
889
|
-
findings.push(`excluded: the catalog advertises audited secret/session operations ${summarizeNames(advertisedExclusions)}`);
|
|
890
|
-
}
|
|
891
|
-
// --- additive profiles, verified independently -----------------------------
|
|
892
|
-
const nonAdminNames = names.filter((name) => !name.startsWith("honua_admin_"));
|
|
893
|
-
const namedProfiles = ZERO_TO_MAP_ADDITIVE_PROFILES.filter((profile) => profile.namePrefixes.length > 0);
|
|
894
|
-
const fallbackProfile = ZERO_TO_MAP_ADDITIVE_PROFILES.find((profile) => profile.namePrefixes.length === 0);
|
|
895
|
-
const claimed = new Set();
|
|
896
|
-
const profileReceipts = [];
|
|
897
|
-
for (const profile of namedProfiles) {
|
|
898
|
-
const members = nonAdminNames.filter((name) => profile.namePrefixes.some((prefix) => name.startsWith(prefix)));
|
|
899
|
-
for (const member of members)
|
|
900
|
-
claimed.add(member);
|
|
901
|
-
const memberSet = new Set(members);
|
|
902
|
-
const missingMembers = profile.confirmedMembers.filter((name) => !memberSet.has(name)).sort();
|
|
903
|
-
const unexpectedMembers = [...memberSet].filter((name) => !profile.confirmedMembers.includes(name)).sort();
|
|
904
|
-
if (missingMembers.length > 0) {
|
|
905
|
-
findings.push(`missing: the ${profile.id} profile is not advertised - ${missingMembers
|
|
906
|
-
.map((tool) => {
|
|
907
|
-
const use = requiredUses.find((candidate) => candidate.tool === tool);
|
|
908
|
-
return use ? describeToolUse(use) : tool;
|
|
909
|
-
})
|
|
910
|
-
.join(", ")}. Enable the ${profile.id} server profile on the candidate and rerun.`);
|
|
911
|
-
}
|
|
912
|
-
if (unexpectedMembers.length > 0) {
|
|
913
|
-
findings.push(`unexpected: the ${profile.id} profile advertises ${summarizeNames(unexpectedMembers)}, which the checked-in profile contract does not declare`);
|
|
914
|
-
}
|
|
915
|
-
if (members.length !== profile.memberCount) {
|
|
916
|
-
findings.push(`${members.length < profile.memberCount ? "missing" : "unexpected"}: the ${profile.id} profile advertises ` +
|
|
917
|
-
`${members.length} members; the profile contract declares ${profile.memberCount}`);
|
|
918
|
-
}
|
|
919
|
-
profileReceipts.push({
|
|
920
|
-
id: profile.id,
|
|
921
|
-
expectedMembers: profile.memberCount,
|
|
922
|
-
advertisedMembers: members.length,
|
|
923
|
-
confirmedMembers: [...profile.confirmedMembers].sort(),
|
|
924
|
-
nameResolvedMembers: [...members].sort(),
|
|
925
|
-
rosterSha256: rosterDigest(members),
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
// The fallback profile's members are not separable from the base static
|
|
929
|
-
// surface by name (the server has not published an `analysis` roster yet:
|
|
930
|
-
// honua-server#3363). It is therefore verified by its confirmed members plus
|
|
931
|
-
// its declared size, and the base static count is derived from the residue.
|
|
932
|
-
const residue = nonAdminNames.filter((name) => !claimed.has(name));
|
|
933
|
-
const fallbackMembers = fallbackProfile ? residue.length - MCP_DEFAULT_STATIC_TOOL_COUNT : 0;
|
|
934
|
-
const baseStaticTools = residue.length - fallbackMembers;
|
|
935
|
-
if (fallbackProfile) {
|
|
936
|
-
const residueSet = new Set(residue);
|
|
937
|
-
const missingMembers = fallbackProfile.confirmedMembers.filter((name) => !residueSet.has(name)).sort();
|
|
938
|
-
if (missingMembers.length > 0) {
|
|
939
|
-
findings.push(`missing: the ${fallbackProfile.id} profile is not advertised - ${missingMembers
|
|
940
|
-
.map((tool) => {
|
|
941
|
-
const use = requiredUses.find((candidate) => candidate.tool === tool);
|
|
942
|
-
return use ? describeToolUse(use) : tool;
|
|
943
|
-
})
|
|
944
|
-
.join(", ")}. Enable the ${fallbackProfile.id} server profile on the candidate and rerun.`);
|
|
945
|
-
}
|
|
946
|
-
if (fallbackMembers !== fallbackProfile.memberCount) {
|
|
947
|
-
findings.push(`${fallbackMembers < fallbackProfile.memberCount ? "missing" : "unexpected"}: the non-Admin surface ` +
|
|
948
|
-
`advertises ${residue.length} tools; the base static roster (${MCP_DEFAULT_STATIC_TOOL_COUNT}) plus the ` +
|
|
949
|
-
`${fallbackProfile.id} profile (${fallbackProfile.memberCount}) derive ` +
|
|
950
|
-
`${MCP_DEFAULT_STATIC_TOOL_COUNT + fallbackProfile.memberCount}`);
|
|
951
|
-
}
|
|
952
|
-
profileReceipts.push({
|
|
953
|
-
id: fallbackProfile.id,
|
|
954
|
-
expectedMembers: fallbackProfile.memberCount,
|
|
955
|
-
advertisedMembers: Math.max(fallbackMembers, 0),
|
|
956
|
-
confirmedMembers: [...fallbackProfile.confirmedMembers].sort(),
|
|
957
|
-
nameResolvedMembers: [...fallbackProfile.confirmedMembers].filter((name) => residueSet.has(name)).sort(),
|
|
958
|
-
rosterSha256: rosterDigest(residue),
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
// --- derived totals --------------------------------------------------------
|
|
962
|
-
if (ZERO_TO_MAP_BASE_PROFILE_TOOL_COUNT !== MCP_DEFAULT_TOTAL_TOOL_COUNT) {
|
|
963
|
-
findings.push(`unexpected: the generated base roster derives ${MCP_DEFAULT_STATIC_TOOL_COUNT} static + ${expectedAdmin.size} ` +
|
|
964
|
-
`Admin = ${ZERO_TO_MAP_BASE_PROFILE_TOOL_COUNT} tools, but the default roster constant is ` +
|
|
965
|
-
`${MCP_DEFAULT_TOTAL_TOOL_COUNT}`);
|
|
966
|
-
}
|
|
967
|
-
const expectedTotal = ZERO_TO_MAP_EXPECTED_TOTAL_TOOL_COUNT;
|
|
968
|
-
if (names.length !== expectedTotal) {
|
|
969
|
-
const shortfall = expectedTotal - names.length;
|
|
970
|
-
findings.push(shortfall > 0 && duplicateNames.length === 0 && unexpectedAdmin.length === 0
|
|
971
|
-
? `truncated: the catalog returned ${names.length} of ${expectedTotal} tools (${shortfall} short). Every tools/list page must be drained before the roster is judged; a short read is a pagination fault, not a roster decision. This preflight already selects view="${ZERO_TO_MAP_FULL_CATALOG_VIEW}", so a candidate workflow view configured through ${ZERO_TO_MAP_WORKFLOW_VIEW_CONFIG_KEY} (${ZERO_TO_MAP_WORKFLOW_VIEW_ENV_KEY}) is not the cause.`
|
|
972
|
-
: `unexpected: the catalog advertises ${names.length} tools; the enabled profiles ` +
|
|
973
|
-
`(${ZERO_TO_MAP_REQUIRED_PROFILES.join(" + ")}) derive ${expectedTotal}`);
|
|
880
|
+
const advertised = new Map(catalog.map((tool) => [tool.name, tool]));
|
|
881
|
+
const missing = closedRoster.filter((tool) => !advertised.has(tool));
|
|
882
|
+
if (missing.length > 0) {
|
|
883
|
+
const described = missing
|
|
884
|
+
.map((tool) => requiredUses
|
|
885
|
+
.filter((use) => use.tool === tool)
|
|
886
|
+
.map(describeToolUse)
|
|
887
|
+
.join(", "))
|
|
888
|
+
.join("; ");
|
|
889
|
+
findings.push(`missing: the ${ZERO_TO_MAP_CLOSED_ROSTER_ID} roster is not advertised - ${described}. ` +
|
|
890
|
+
`This preflight already selects view="${ZERO_TO_MAP_FULL_CATALOG_VIEW}", so a candidate workflow view ` +
|
|
891
|
+
`configured through ${ZERO_TO_MAP_WORKFLOW_VIEW_CONFIG_KEY} (${ZERO_TO_MAP_WORKFLOW_VIEW_ENV_KEY}) is not the cause.`);
|
|
974
892
|
}
|
|
975
893
|
if (findings.length > 0) {
|
|
976
|
-
throw new JourneyBlockedError(`MCP catalog preflight failed for
|
|
977
|
-
}
|
|
978
|
-
// --- every tool the plan actually calls ------------------------------------
|
|
979
|
-
const advertised = new Map(catalog.map((tool) => [tool.name, tool]));
|
|
980
|
-
const missingRequired = [...new Set(requiredUses.map((use) => use.tool))].filter((tool) => !advertised.has(tool));
|
|
981
|
-
if (missingRequired.length > 0) {
|
|
982
|
-
throw new JourneyBlockedError(`MCP catalog is missing required tools: ${missingRequired.join(", ")}`, "mcp-catalog-incomplete");
|
|
894
|
+
throw new JourneyBlockedError(`MCP catalog preflight failed for the ${ZERO_TO_MAP_CLOSED_ROSTER_ID} roster: ${findings.join("; ")}`, "mcp-catalog-incomplete");
|
|
983
895
|
}
|
|
984
|
-
// --- input-schema drift on every planned call ------------------------------
|
|
985
896
|
for (const stage of plan.stages) {
|
|
986
897
|
for (const action of stage.actions) {
|
|
987
898
|
if (action.kind !== "mcp")
|
|
@@ -1003,20 +914,33 @@ async function assertMcpCatalog(plan, adapter) {
|
|
|
1003
914
|
}
|
|
1004
915
|
}
|
|
1005
916
|
}
|
|
917
|
+
const adminNames = names.filter((name) => name.startsWith("honua_admin_"));
|
|
918
|
+
const staticNames = names.filter((name) => !name.startsWith("honua_admin_"));
|
|
919
|
+
const closedAdmin = closedRoster.filter((name) => name.startsWith("honua_admin_"));
|
|
920
|
+
const closedStatic = closedRoster.filter((name) => !name.startsWith("honua_admin_"));
|
|
1006
921
|
return {
|
|
1007
922
|
schemaVersion: ZERO_TO_MAP_CATALOG_RECEIPT_SCHEMA,
|
|
1008
|
-
activeProfiles: [
|
|
923
|
+
activeProfiles: [ZERO_TO_MAP_CLOSED_ROSTER_ID],
|
|
1009
924
|
requestedView: ZERO_TO_MAP_FULL_CATALOG_VIEW,
|
|
1010
|
-
expectedTotalTools:
|
|
925
|
+
expectedTotalTools: closedRoster.length,
|
|
1011
926
|
advertisedTotalTools: names.length,
|
|
1012
|
-
baseStaticTools,
|
|
1013
|
-
baseAdminTools:
|
|
1014
|
-
auditedExclusions:
|
|
1015
|
-
profiles:
|
|
927
|
+
baseStaticTools: closedStatic.length,
|
|
928
|
+
baseAdminTools: closedAdmin.length,
|
|
929
|
+
auditedExclusions: 0,
|
|
930
|
+
profiles: [
|
|
931
|
+
{
|
|
932
|
+
id: ZERO_TO_MAP_CLOSED_ROSTER_ID,
|
|
933
|
+
expectedMembers: closedRoster.length,
|
|
934
|
+
advertisedMembers: closedRoster.length,
|
|
935
|
+
confirmedMembers: closedRoster,
|
|
936
|
+
nameResolvedMembers: closedRoster,
|
|
937
|
+
rosterSha256: rosterDigest(closedRoster),
|
|
938
|
+
},
|
|
939
|
+
],
|
|
1016
940
|
catalogSha256: rosterDigest(names),
|
|
1017
|
-
adminRosterSha256: rosterDigest(
|
|
1018
|
-
staticRosterSha256: rosterDigest(
|
|
1019
|
-
exclusionRosterSha256:
|
|
941
|
+
adminRosterSha256: rosterDigest(adminNames),
|
|
942
|
+
staticRosterSha256: rosterDigest(staticNames),
|
|
943
|
+
exclusionRosterSha256: rosterDigest([]),
|
|
1020
944
|
};
|
|
1021
945
|
}
|
|
1022
946
|
function duplicateValues(values) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honua/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11-beta.0",
|
|
4
4
|
"description": "Platform-free MCP server for geospatial services: expose discovery, query, and analysis over any public ArcGIS/Esri FeatureServer or OGC API endpoint to any MCP client, with no platform lock-in",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Honua",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"test:certification": "npm run build --silent && vitest run test/certification && node dist/src/certification/cli.js",
|
|
57
57
|
"test:certification:admin": "vitest run test/proxy.test.ts test/certification/admin-parity.test.ts",
|
|
58
58
|
"certify:admin-parity": "npm run build --silent && node dist/src/certification/admin-parity.js",
|
|
59
|
+
"certify:setup-parity": "npm run build --silent && node dist/src/certification/setup-parity.js",
|
|
59
60
|
"test:certification:artifact": "npm run build --silent && node dist/src/certification/cli.js --artifact-only",
|
|
60
61
|
"test:certification:standalone": "npm run build --silent && node dist/src/certification/cli.js --target standalone",
|
|
61
62
|
"test:certification:standalone-ogc": "npm run build --silent && node dist/src/certification/cli.js --target standalone-ogc",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"qs": "^6.16.0"
|
|
89
90
|
},
|
|
90
91
|
"peerDependencies": {
|
|
91
|
-
"@honua/sdk-js": "^0.1.
|
|
92
|
+
"@honua/sdk-js": "^0.1.11-beta.0"
|
|
92
93
|
},
|
|
93
94
|
"devDependencies": {
|
|
94
95
|
"@biomejs/biome": "1.9.4",
|
|
@@ -102,6 +103,7 @@
|
|
|
102
103
|
"dist/src",
|
|
103
104
|
"release/zero-to-map",
|
|
104
105
|
"certification/geospatial-mcp-schemas",
|
|
106
|
+
"certification/setup-parity.md",
|
|
105
107
|
"README.md",
|
|
106
108
|
"LICENSE"
|
|
107
109
|
],
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
# Zero-to-map 2026.1 release journey
|
|
2
2
|
|
|
3
|
+
The September 9 release decision on #1401 narrows first-cut qualification.
|
|
4
|
+
This older journey still exercises the broader Admin/analysis and multi-family
|
|
5
|
+
Studio integration. Its roster is not the bounded 2026.1 setup gate. Use the
|
|
6
|
+
[pinned setup catalog check](../../certification/setup-parity.md) for that
|
|
7
|
+
gate's HTTP/stdio discovery evidence; retain separate install and style/render
|
|
8
|
+
execution receipts. Full Admin MCP and broader analysis qualification remain
|
|
9
|
+
outside the bounded first-cut dependency chain.
|
|
10
|
+
|
|
3
11
|
This bundle is the executable contract for `honua-release#123` D9.3: install
|
|
4
|
-
Honua, configure and publish deterministic data with
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`honua://jobs/{
|
|
12
|
+
Honua, configure and publish deterministic data with the closed admin roster,
|
|
13
|
+
buffer the published parcels with `honua_validate_plan` then
|
|
14
|
+
`honua_execute_plan` (`analytics.buffer-aggregate`), prove `geometry.buffer`
|
|
15
|
+
through the SDK GPServer runner, compose distinct map, app, and dashboard
|
|
16
|
+
packages in Studio, save each as an immutable version, and publish that saved
|
|
17
|
+
version. An admin `honua_studio_propose_publication` returns `shareUrl` with
|
|
18
|
+
`humanConfirmationRequired: false`. The same session fetches each share URL and
|
|
19
|
+
requires HTTP 200. It does not import a Console receipt and it does not require
|
|
20
|
+
a second principal. The buffer job is polled through `honua://jobs/{jobId}` to
|
|
21
|
+
terminal success, and `artifacts[].artifactId` is read from
|
|
22
|
+
`honua://jobs/{jobId}/results`; a queued submission alone cannot pass.
|
|
13
23
|
|
|
14
24
|
The driver reuses `honua admin install` and `honua-mcp-proxy`. It does not ship
|
|
15
25
|
a second installer, admin client, or MCP transport. The installer is the
|
|
@@ -49,17 +59,21 @@ Prerequisites:
|
|
|
49
59
|
|
|
50
60
|
- Node.js 20.19 or newer and Docker with Compose
|
|
51
61
|
- the 2026.1 `honua` CLI work from `honua-sdk-js#1370-#1373`
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`
|
|
58
|
-
`
|
|
59
|
-
|
|
62
|
+
- one admin credential. The journey does not require a second principal
|
|
63
|
+
- a server build that advertises the closed operator roster this journey calls:
|
|
64
|
+
`honua_admin_connections_create`, `honua_admin_connections_test`,
|
|
65
|
+
`honua_admin_import_upload_url`, `honua_admin_layer_publish`,
|
|
66
|
+
`honua_admin_services_access_policy_set`, `honua_admin_server_status`,
|
|
67
|
+
`honua_admin_api_key_list`, `honua_admin_api_key_effective_permissions`,
|
|
68
|
+
`honua_validate_plan`, `honua_execute_plan`, and the Studio lifecycle tools
|
|
69
|
+
including `honua_studio_save_version`, `honua_studio_get_version`,
|
|
70
|
+
`honua_studio_reopen_version`, and `honua_studio_propose_publication`.
|
|
71
|
+
It does not require a 432- or 441-tool catalog, and it does not require the
|
|
72
|
+
`analysis` or `esri-gp` profiles. `honua_esri_gp_*` and `honua_buffer_features`
|
|
73
|
+
are not called
|
|
74
|
+
- the `geoprocessing/Buffer` GPServer task seeded for the separate
|
|
75
|
+
`geometry.buffer` proof. That task takes WKB and has no `layerId`
|
|
60
76
|
- these fixtures available to the server at an HTTP(S) base URL
|
|
61
|
-
- a writable checkpoint path; the first pass emits the exact resolved Console
|
|
62
|
-
receipt request after all pre-approval work completes
|
|
63
77
|
|
|
64
78
|
Serve the checked-in fixtures from a URL reachable by the Honua container, for
|
|
65
79
|
example with an existing static-file server. Then run:
|
|
@@ -97,26 +111,13 @@ compose file it generates, so add both to the `honua` service's environment (or
|
|
|
97
111
|
start an equivalent server yourself) before running with `--execute`. If you
|
|
98
112
|
pass a different `--var dbSecretReference=env:<NAME>`, permit `<NAME>` instead.
|
|
99
113
|
|
|
100
|
-
The
|
|
101
|
-
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
--checkpoint-digest <checkpoint.integrity.digest> \
|
|
108
|
-
--console-receipt ./console-approval.json
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The resume atomically claims the checkpoint before any adapter work. A second
|
|
112
|
-
or concurrent claimant fails closed. The checkpoint is bound to the exact plan
|
|
113
|
-
bytes, SDK source SHA (`HONUA_SOURCE_REVISION`), target, MCP endpoint,
|
|
114
|
-
candidate/release, and (for AWS) provisioning receipt; it contains only
|
|
115
|
-
allowlisted captures/evidence, including the non-secret deterministic
|
|
116
|
-
`serviceName` identity and access-credential ID/grants/reference digest required
|
|
117
|
-
by external producers, and never the database password, admin key, token, or
|
|
118
|
-
authorization material. A successful resume
|
|
119
|
-
marks it consumed and binds the final receipt hash.
|
|
114
|
+
The live run does not pause for a Console receipt. An admin publish returns
|
|
115
|
+
`shareUrl` (`/api/v1/studio/published` plus the route) with
|
|
116
|
+
`humanConfirmationRequired: false`, and the journey fetches that URL. A
|
|
117
|
+
root-relative share path is resolved against the MCP endpoint origin. HTTP is
|
|
118
|
+
accepted only for a loopback origin; any other share URL must be HTTPS and must
|
|
119
|
+
not embed credentials. One admin credential performs configuration, publication,
|
|
120
|
+
and the fetch.
|
|
120
121
|
|
|
121
122
|
For a deployment already provisioned by the DevOps ECS producer, use
|
|
122
123
|
`--target aws-ecs --provision-receipt <pre-teardown-binding.json>`. The binding
|
|
@@ -126,27 +127,14 @@ the real image/digest, Terraform, readiness, and a secret-free access receipt
|
|
|
126
127
|
whose reference digest is verified against the producer's Secrets Manager ARN;
|
|
127
128
|
it does not invoke or pretend to invoke the Docker installer.
|
|
128
129
|
|
|
129
|
-
The catalog is checked
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
| --- | --- | --- |
|
|
138
|
-
| `base` | 47 static + 385 `honua_admin_*` = 432 | the generated Admin contract, plus the 11 audited secret/session exclusions proven absent |
|
|
139
|
-
| `analysis` | 6 | `honua_buffer_features` by name; the rest by declared size (the server has not published an `analysis` roster - honua-server#3363) |
|
|
140
|
-
| `esri-gp` | 3 | exactly `honua_esri_gp_list_tasks`, `honua_esri_gp_describe_task`, `honua_esri_gp_execute_task` from `contracts/esri-gp-mcp.v1.json` |
|
|
141
|
-
|
|
142
|
-
The enabled candidate therefore advertises 432 + 6 + 3 = 441 tools. A missing,
|
|
143
|
-
unexpected, duplicate, excluded, truncated, or schema-drifted member fails with
|
|
144
|
-
its own labelled finding naming the journey stage and action that needs the
|
|
145
|
-
tool; a short catalog is reported as a pagination fault rather than a roster
|
|
146
|
-
decision. The pagination safety ceiling in `listAllTools` is deliberately
|
|
147
|
-
separate from every roster number, so reading a larger profile-enabled catalog
|
|
148
|
-
is never mistaken for a hostile server. Default-profile certification
|
|
149
|
-
(`certifyAdminCatalogParity`) continues to require exactly 432.
|
|
130
|
+
The catalog is checked before the first MCP call. The preflight selects
|
|
131
|
+
`tools/list` view `full` and asserts the closed roster: every tool this journey
|
|
132
|
+
calls, by name, with the stage and action that needs it. It does not require
|
|
133
|
+
432 or 441 tools and it does not require the `analysis` or `esri-gp` profiles.
|
|
134
|
+
Extra advertised tools are not a failure. A duplicate name or an input schema
|
|
135
|
+
that rejects a planned argument blocks the run without sending that call.
|
|
136
|
+
Default-profile certification (`certifyAdminCatalogParity`) is a separate gate
|
|
137
|
+
and is not this journey's roster.
|
|
150
138
|
|
|
151
139
|
The connection is
|
|
152
140
|
created with `secretReference` + `secretType`; raw database credentials never
|
|
@@ -164,30 +152,20 @@ The Studio version tools are also preflighted before the first server mutation.
|
|
|
164
152
|
Map, app, and dashboard each exercise layer, style, view, widget, and control
|
|
165
153
|
mutations before validation; family-specific shortcuts or embedded lookalikes
|
|
166
154
|
cannot satisfy the release roster.
|
|
167
|
-
The journey does not treat `honua_studio_get_draft` as reopen evidence
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
version identities, all three reopened drafts and intent-bearing publication
|
|
178
|
-
versions, all three Console request/publication/status/public-URL identities,
|
|
179
|
-
execution operations, audit correlations, routes, candidate, and release. Each
|
|
180
|
-
audit operation must equal its family's execution operation, each proposal ID
|
|
181
|
-
must equal its publication request ID, and the app public URL must equal the
|
|
182
|
-
top-level share URL. The Studio proposal actions reject any pre-approval
|
|
183
|
-
publication ID or public URL and must report both `recorded=true` and
|
|
184
|
-
`humanConfirmationRequired=true`. Agent execution never claims the human
|
|
185
|
-
publication step. Stage 7 independently requires identity-preserving HTTPS
|
|
186
|
-
HTTP 200 responses for map, app, and dashboard URLs.
|
|
155
|
+
The journey does not treat `honua_studio_get_draft` as reopen evidence. Save
|
|
156
|
+
captures read `/structuredContent/version/versionId`,
|
|
157
|
+
`/structuredContent/version/contentHash`, and `versionNumber` under `version`.
|
|
158
|
+
`honua_studio_get_version` still returns top-level `versionId` and `contentHash`.
|
|
159
|
+
Publication runs after that save. `honua_studio_propose_publication` takes
|
|
160
|
+
`itemId`, `versionId`, `contentHash`, `route`, `visibility`, and `note`. It does
|
|
161
|
+
not take `draftId`, `generation`, or `embed`. For this admin journey the result
|
|
162
|
+
has `humanConfirmationRequired: false` and a `shareUrl`. The journey fetches
|
|
163
|
+
each share URL and requires HTTP 200. It does not forbid `shareUrl` and it does
|
|
164
|
+
not wait for a second principal.
|
|
187
165
|
|
|
188
166
|
## Evidence status
|
|
189
167
|
|
|
190
168
|
The fixtures, plan, contracts, and simulated end-to-end tests are deterministic.
|
|
191
|
-
They are not a live release recording.
|
|
192
|
-
|
|
193
|
-
|
|
169
|
+
They are not a live release recording. Contract mode stays `blocked` and does
|
|
170
|
+
not start Docker. A live pass is the same admin session reaching HTTP 200 on
|
|
171
|
+
the three share URLs.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"honua": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["-y", "--package", "@honua/mcp-server@0.1.
|
|
5
|
+
"args": ["-y", "--package", "@honua/mcp-server@0.1.10-beta.0", "honua-mcp-proxy"],
|
|
6
6
|
"env": {
|
|
7
7
|
"HONUA_MCP_REMOTE_URL": "http://localhost:8080/mcp"
|
|
8
8
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"honua": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["-y", "--package", "@honua/mcp-server@0.1.
|
|
5
|
+
"args": ["-y", "--package", "@honua/mcp-server@0.1.10-beta.0", "honua-mcp-proxy"],
|
|
6
6
|
"env": {
|
|
7
7
|
"HONUA_MCP_REMOTE_URL": "http://localhost:8080/mcp"
|
|
8
8
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"honua": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["-y", "--package", "@honua/mcp-server@0.1.
|
|
5
|
+
"args": ["-y", "--package", "@honua/mcp-server@0.1.10-beta.0", "honua-mcp-proxy"],
|
|
6
6
|
"env": {
|
|
7
7
|
"HONUA_MCP_REMOTE_URL": "http://localhost:8080/mcp"
|
|
8
8
|
}
|