@bridge_gpt/mcp-server 0.2.38 → 0.2.41
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 +189 -14
- package/build/agent-capabilities/probe-context.js +2 -1
- package/build/agent-launchers/claude-executor-adapter.js +392 -0
- package/build/agent-launchers/executor-adapter-inspection.js +163 -0
- package/build/agent-launchers/executor-adapter-registry.js +90 -0
- package/build/agent-launchers/executor-adapter.js +136 -0
- package/build/agent-registry.js +28 -0
- package/build/agents.generated.js +1 -1
- package/build/claude-login.js +85 -0
- package/build/claude-user-config-doctor.js +59 -33
- package/build/commands.generated.js +12 -11
- package/build/conduct-epic/bridge-client.js +345 -0
- package/build/conduct-epic/checkpoint-store.js +479 -0
- package/build/conduct-epic/cli.js +1765 -0
- package/build/conduct-epic/lock.js +302 -0
- package/build/conduct-epic/pr-state.js +286 -0
- package/build/conduct-epic/spawn.js +101 -0
- package/build/conductor/bridge-api-client.js +37 -2
- package/build/conductor/doctor.js +11 -1
- package/build/conductor/install-doctor.js +184 -10
- package/build/conductor-bin.js +7 -7
- package/build/credential-store.js +10 -4
- package/build/credentials-cli.js +34 -19
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +579 -88
- package/build/executor/agent-identity.js +32 -0
- package/build/executor/cli.js +50 -39
- package/build/executor/deps.js +15 -1
- package/build/executor/env.js +56 -45
- package/build/executor/index.js +9 -1
- package/build/executor/install-preflight.js +138 -0
- package/build/executor/job-errors.js +200 -0
- package/build/executor/job-runner.js +619 -268
- package/build/executor/observation.js +165 -0
- package/build/executor/permissions.js +163 -36
- package/build/executor/platform.js +54 -0
- package/build/executor/preflight.js +175 -67
- package/build/executor/process.js +39 -7
- package/build/executor/runner.js +19 -0
- package/build/executor/service-lifecycle.js +269 -0
- package/build/executor/service-unit.js +121 -12
- package/build/executor/stale-artifacts.js +70 -0
- package/build/executor/test-clock.js +188 -24
- package/build/executor/worker-command.js +22 -58
- package/build/executor/worker-log.js +82 -0
- package/build/executor/worktree-lock.js +264 -0
- package/build/index.js +527 -357
- package/build/install-bridge-conductor.js +376 -38
- package/build/install-bridge.js +414 -114
- package/build/install-doctor.js +13 -0
- package/build/install-reexec.js +5 -3
- package/build/mcp-install-state.js +130 -0
- package/build/mcp-profile.js +11 -2
- package/build/mcp-provisioning.js +15 -0
- package/build/merge-pull-request.js +562 -0
- package/build/phase-result-artifacts.js +450 -0
- package/build/pipeline-orchestrator.js +4 -0
- package/build/pipeline-utils.js +16 -0
- package/build/pipelines.generated.js +7 -7
- package/build/plane/preflight.js +18 -14
- package/build/plane/supervisor.js +8 -1
- package/build/project-root.js +34 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +36 -9
- package/build/setup-epic.js +57 -4
- package/build/sfcc/permissions.js +25 -6
- package/build/sfcc/reads-site-preference.js +6 -0
- package/build/sfcc/register.js +61 -23
- package/build/sfcc/registration-inventory.js +89 -0
- package/build/sfcc/setup-status.js +18 -34
- package/build/sfcc/tool-wrapper.js +294 -17
- package/build/sfcc/write-grants.js +33 -1
- package/build/sfcc/write-guard.js +41 -12
- package/build/sfcc/writes-custom-object-def.js +6 -2
- package/build/sfcc/writes-site-preference.js +6 -1
- package/build/sfcc/writes-system-object.js +11 -2
- package/build/sfcc/writes.js +13 -8
- package/build/start-tickets-prereqs.js +25 -15
- package/build/start-tickets.js +123 -21
- package/build/version.generated.js +1 -1
- package/build/worktree-core.js +9 -3
- package/docs/install/mcp-tool-integrations.md +54 -9
- package/docs/install/sfcc-integration.md +71 -24
- package/package.json +3 -3
- package/build/executor/worker-config-isolation.js +0 -287
|
@@ -65,6 +65,7 @@ export const MODULE_MOCK_MANIFEST = [
|
|
|
65
65
|
"build/sfcc/reads-site-preference.test.js",
|
|
66
66
|
"build/sfcc/reads-system-object.test.js",
|
|
67
67
|
"build/sfcc/register.test.js",
|
|
68
|
+
"build/sfcc/registration-boundary.test.js",
|
|
68
69
|
"build/sfcc/setup-status.test.js",
|
|
69
70
|
"build/sfcc/tool-wrapper.test.js",
|
|
70
71
|
"build/sfcc/write-gate-rejection.test.js",
|
|
@@ -96,14 +97,24 @@ export const FORCE_EXIT_QUARANTINE = ["build/secret-safety.test.js"];
|
|
|
96
97
|
// normal unit suite.
|
|
97
98
|
const INTEGRATION_DIR_PREFIX = "build/integration/";
|
|
98
99
|
const INTEGRATION_DIR_ALLOWLIST = new Set(["build/integration/measure-tools.test.js"]);
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
100
|
+
// Compiled test files that live OUTSIDE build/integration/ but still belong to
|
|
101
|
+
// the separately-invoked `npm run test:integration` lane, so the unit lane must
|
|
102
|
+
// skip them. BAPI-683 originally introduced this set as
|
|
103
|
+
// KNOWN_UNREGISTERED_ORPHANS: two pre-existing files that were wired into no
|
|
104
|
+
// script at all, preserved as-is rather than silently picked up. BAPI-822
|
|
105
|
+
// retired that state — every entry here is now named explicitly by
|
|
106
|
+
// scripts.test:integration, and validateManifest below fails if an entry
|
|
107
|
+
// disappears from the build output.
|
|
108
|
+
//
|
|
109
|
+
// Only add a file here when it genuinely needs the integration lane (an
|
|
110
|
+
// MCP_INTEGRATION=1 gate, real subprocesses, or an optional native dependency
|
|
111
|
+
// like better-sqlite3). A plain compiled test outside build/integration/ is
|
|
112
|
+
// discovered into the unit lane automatically and needs no registration —
|
|
113
|
+
// tests/pytest/mcp/test_mcp_server_test_registration_multicomponent.py fails if
|
|
114
|
+
// a *.integration.test.ts ends up in neither lane.
|
|
115
|
+
export const INTEGRATION_LANE_ONLY = [
|
|
104
116
|
"build/conductor/conductor-runtime.integration.test.js",
|
|
105
|
-
|
|
106
|
-
]);
|
|
117
|
+
];
|
|
107
118
|
/** Recursively lists compiled `*.test.js` files under `<root>/<buildDir>`, sorted and package-relative. */
|
|
108
119
|
export function discoverTestFiles(options) {
|
|
109
120
|
const { root, buildDir = DEFAULT_BUILD_DIR, fsImpl = { readdirSync } } = options;
|
|
@@ -133,7 +144,7 @@ export function discoverTestFiles(options) {
|
|
|
133
144
|
results.sort();
|
|
134
145
|
return results;
|
|
135
146
|
}
|
|
136
|
-
/** Throws if the module-mock
|
|
147
|
+
/** Throws if the module-mock, force-exit-quarantine, or integration-lane manifest has drifted from the discovered build output. */
|
|
137
148
|
export function validateManifest(discovered) {
|
|
138
149
|
const discoveredSet = new Set(discovered);
|
|
139
150
|
const seen = new Set();
|
|
@@ -157,6 +168,21 @@ export function validateManifest(discovered) {
|
|
|
157
168
|
`(rebuild with 'npm run build', or remove it from FORCE_EXIT_QUARANTINE if the file was deleted/renamed)`);
|
|
158
169
|
}
|
|
159
170
|
}
|
|
171
|
+
// The integration-lane set is the one exclusion that removes a discovered
|
|
172
|
+
// file from every unit batch, so a stale entry is invisible by construction:
|
|
173
|
+
// the excluded file simply stops running and nothing reports it. Validating
|
|
174
|
+
// it against the build output turns that into a loud failure (BAPI-822).
|
|
175
|
+
for (const entry of INTEGRATION_LANE_ONLY) {
|
|
176
|
+
if (seen.has(entry)) {
|
|
177
|
+
throw new Error(`run-unit-tests: integration-lane entry duplicated or also present in another manifest: ${entry}`);
|
|
178
|
+
}
|
|
179
|
+
seen.add(entry);
|
|
180
|
+
if (!discoveredSet.has(entry)) {
|
|
181
|
+
throw new Error(`run-unit-tests: integration-lane entry not found in build output: ${entry} ` +
|
|
182
|
+
`(rebuild with 'npm run build', or remove it from INTEGRATION_LANE_ONLY — and from ` +
|
|
183
|
+
`scripts.test:integration — if the file was deleted/renamed)`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
160
186
|
}
|
|
161
187
|
/** Selects and validates the requested group's file list from the discovered inventory. */
|
|
162
188
|
export function selectTestGroup(mode, discovered) {
|
|
@@ -165,12 +191,13 @@ export function selectTestGroup(mode, discovered) {
|
|
|
165
191
|
}
|
|
166
192
|
validateManifest(discovered);
|
|
167
193
|
const moduleMockSet = new Set(MODULE_MOCK_MANIFEST);
|
|
194
|
+
const integrationLaneSet = new Set(INTEGRATION_LANE_ONLY);
|
|
168
195
|
const normal = discovered.filter((file) => {
|
|
169
196
|
if (moduleMockSet.has(file))
|
|
170
197
|
return false;
|
|
171
198
|
if (file.startsWith(INTEGRATION_DIR_PREFIX) && !INTEGRATION_DIR_ALLOWLIST.has(file))
|
|
172
199
|
return false;
|
|
173
|
-
if (
|
|
200
|
+
if (integrationLaneSet.has(file))
|
|
174
201
|
return false;
|
|
175
202
|
return true;
|
|
176
203
|
});
|
package/build/setup-epic.js
CHANGED
|
@@ -38,6 +38,10 @@ import os from "node:os";
|
|
|
38
38
|
import readline from "node:readline";
|
|
39
39
|
import { approveEpicPlan, createEpicRunWithDisposition, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
40
40
|
import { validateBranchName } from "./base-ref.js";
|
|
41
|
+
// BAPI-814/E1a: reused from the conduct-epic client on purpose. The override is
|
|
42
|
+
// ONE repository-wide row with ONE reader; adding a second wrapper under
|
|
43
|
+
// `conductor/` would give the v2 path its own copy to drift from.
|
|
44
|
+
import { getIndexBranch } from "./conduct-epic/bridge-client.js";
|
|
41
45
|
import { hashPlan } from "./conductor/plan.js";
|
|
42
46
|
/** Accepted `policy_json.review_policy.source` values (the `ReviewPolicy` surface). */
|
|
43
47
|
export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
|
|
@@ -45,6 +49,36 @@ export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
|
|
|
45
49
|
"native_review_decision",
|
|
46
50
|
"none",
|
|
47
51
|
];
|
|
52
|
+
/**
|
|
53
|
+
* The warning lines for an active repository-wide index-branch override, or an
|
|
54
|
+
* empty array when there is nothing to say (BAPI-814/E1a).
|
|
55
|
+
*
|
|
56
|
+
* Fail-open by construction. This is optional enrichment layered onto a command
|
|
57
|
+
* whose real job is creating an epic run, so an unreadable override must not
|
|
58
|
+
* become a preflight failure: an unavailable lookup produces one bounded,
|
|
59
|
+
* sanitized diagnostic and `setup-epic` continues. The client already returns a
|
|
60
|
+
* sanitized `error` string and never the raw service body, so nothing further
|
|
61
|
+
* needs stripping here.
|
|
62
|
+
*/
|
|
63
|
+
async function describeActiveIndexOverride(access, deps) {
|
|
64
|
+
const result = await getIndexBranch(access, deps.fetch);
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
return [
|
|
67
|
+
` [warn] The repository's indexed-branch override could not be read: ${result.error}. ` +
|
|
68
|
+
`Continuing — this is optional context, not a prerequisite.`,
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
const override = result.value.override;
|
|
72
|
+
if (override === null)
|
|
73
|
+
return [];
|
|
74
|
+
return [
|
|
75
|
+
` [warn] ${access.repoName} has an ACTIVE index-branch override: ` +
|
|
76
|
+
`'${override.override_branch}'. Repository parses currently index that branch, ` +
|
|
77
|
+
`and the base-branch config field this run may fall back on resolves through the ` +
|
|
78
|
+
`same override.`,
|
|
79
|
+
` Setup is CONTINUING — this is a warning, not a refusal.`,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
48
82
|
/** Echoed single-line prompt on stderr (mirrors connect-github's helper). */
|
|
49
83
|
function defaultPromptLine(promptText) {
|
|
50
84
|
return new Promise((resolve) => {
|
|
@@ -102,9 +136,13 @@ export function getSetupEpicUsage() {
|
|
|
102
136
|
" --review-policy <src> PER-RUN review policy source, one of:",
|
|
103
137
|
` ${SETUP_EPIC_REVIEW_POLICY_SOURCES.join(", ")}.`,
|
|
104
138
|
" Composed into policy_json.review_policy on create.",
|
|
105
|
-
" This
|
|
106
|
-
"
|
|
107
|
-
"
|
|
139
|
+
" This flag sets the policy for THIS run only and never",
|
|
140
|
+
" changes a repository default. To persist one, pass",
|
|
141
|
+
" --review-policy to `install-bridge conductor`, which",
|
|
142
|
+
" stores it as the supervisor project default; that",
|
|
143
|
+
" default is stamped into a new run only when the run",
|
|
144
|
+
" sets no review policy of its own, so a value supplied",
|
|
145
|
+
" here always wins.",
|
|
108
146
|
" --json Emit a single JSON result object on stdout",
|
|
109
147
|
" -h, --help Show this help",
|
|
110
148
|
"",
|
|
@@ -981,6 +1019,21 @@ export async function runSetupEpicCli(argv, overrides = {}) {
|
|
|
981
1019
|
say(`Local hash: ${localHash}`);
|
|
982
1020
|
for (const w of warnings)
|
|
983
1021
|
say(` [warn] ${w}`);
|
|
1022
|
+
// --- Active index-branch override (BAPI-814/E1a) -------------------------
|
|
1023
|
+
// Non-mutating enrichment, placed once credentials and repository identity are
|
|
1024
|
+
// both known and before any run-state read or mutating request.
|
|
1025
|
+
//
|
|
1026
|
+
// WARNING ONLY, never a refusal. The override is repository-wide: while it is
|
|
1027
|
+
// active every parse indexes that branch, and the create-PR config-field
|
|
1028
|
+
// fallback and feature-branch provisioning read the same overridden
|
|
1029
|
+
// `config_code_repositories.base_branch`. A v2 operator setting up an epic in
|
|
1030
|
+
// that repository needs to know, but the condition is legitimate — it is
|
|
1031
|
+
// usually another epic mid-flight — so setup continues.
|
|
1032
|
+
//
|
|
1033
|
+
// Emitted in `--dry-run` too: the condition already exists in the repository
|
|
1034
|
+
// and is independent of whether this invocation writes anything.
|
|
1035
|
+
for (const line of await describeActiveIndexOverride(access, deps))
|
|
1036
|
+
say(line);
|
|
984
1037
|
// --- Feature branch selection (BAPI-655) --------------------------------
|
|
985
1038
|
// Resolved AFTER local/access context is known but BEFORE any run-state read
|
|
986
1039
|
// or mutating request, so malformed interactive input fails before network
|
|
@@ -1171,7 +1224,7 @@ export async function runSetupEpicCli(argv, overrides = {}) {
|
|
|
1171
1224
|
say(` - feature branch: ${featureBranch} (create from repository base branch; no request made in dry-run)`);
|
|
1172
1225
|
}
|
|
1173
1226
|
if (opts.reviewPolicy !== undefined) {
|
|
1174
|
-
say(` - review policy: ${opts.reviewPolicy} (per-run policy_json.review_policy;
|
|
1227
|
+
say(` - review policy: ${opts.reviewPolicy} (per-run policy_json.review_policy; sets no repository default — use \`install-bridge conductor --review-policy\` for that)`);
|
|
1175
1228
|
}
|
|
1176
1229
|
say(` - POST /jira/epic-runs/runs/${opts.epicKey}/plan (v${plan.plan_version})`);
|
|
1177
1230
|
say(` - POST /jira/epic-runs/runs/${opts.epicKey}/approve-plan (v${plan.plan_version})`);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Issues a harmless GET /system_object_definitions probe. On 200: reports the
|
|
5
5
|
* detected OCAPI version. On 401/403: prints the exact OCAPI Settings JSON to
|
|
6
|
-
* paste in Business Manager, split into read
|
|
7
|
-
*
|
|
6
|
+
* paste in Business Manager, split into read/search grants (for the `sfcc` read
|
|
7
|
+
* tools) and mutation grants (for the nine shipped `sfcc-write` tools).
|
|
8
8
|
*/
|
|
9
9
|
import { ocapiGet } from "./client.js";
|
|
10
10
|
import { normalizeOcapiPage } from "./ocapi-shape.js";
|
|
@@ -14,7 +14,7 @@ import { formatOcapiWriteGrantJson } from "./write-grants.js";
|
|
|
14
14
|
// OCAPI Settings JSON template
|
|
15
15
|
// Operators paste this in: Administration > Site Development > Open Commerce API Settings → Data API tab.
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
|
-
const
|
|
17
|
+
const OCAPI_SETTINGS_READ_SEARCH = (ocapiVersion) => JSON.stringify({
|
|
18
18
|
_v: ocapiVersion,
|
|
19
19
|
clients: [
|
|
20
20
|
{
|
|
@@ -138,12 +138,31 @@ function formatProbeCount(normalized) {
|
|
|
138
138
|
}
|
|
139
139
|
return `${returned} items returned on this page (OCAPI did not provide the instance total)`;
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Render the two grant blocks an operator pastes in Business Manager.
|
|
143
|
+
*
|
|
144
|
+
* Both labels were wrong before BAPI-817, in opposite directions:
|
|
145
|
+
*
|
|
146
|
+
* - The first block was labelled **"READ-ONLY GRANTS"** while actually
|
|
147
|
+
* granting `methods: ["get","post"]` with `write_attributes: "(**)"` across
|
|
148
|
+
* three resource families. An operator pasted a grant they had been told was
|
|
149
|
+
* read-only. It is renamed to read/search, which is what it is — the `post`
|
|
150
|
+
* is OCAPI's search convention (`*_search` endpoints are POSTs), not a
|
|
151
|
+
* mutation, but calling that "read-only" is still a claim about a grant the
|
|
152
|
+
* operator cannot verify without reading OCAPI's own semantics.
|
|
153
|
+
* - The second was labelled **"v2 — forward-looking"** for capabilities that
|
|
154
|
+
* had already shipped. All nine write tools exist; the grant is required
|
|
155
|
+
* today by anyone enabling `sfcc-write`, not speculative future work.
|
|
156
|
+
*
|
|
157
|
+
* A label that understates a grant is worse than no label, because the operator
|
|
158
|
+
* stops reading the JSON once the heading has told them it is safe.
|
|
159
|
+
*/
|
|
141
160
|
function ocapiSettingsInstructions(ocapiVersion) {
|
|
142
161
|
return (`To grant OCAPI access, paste the JSON below in Business Manager:\n` +
|
|
143
162
|
` Administration > Site Development > Open Commerce API Settings → Data API tab\n\n` +
|
|
144
|
-
`--- READ
|
|
145
|
-
`${
|
|
146
|
-
`---
|
|
163
|
+
`--- READ/SEARCH TOOL GRANTS (required for the sfcc read tools) ---\n` +
|
|
164
|
+
`${OCAPI_SETTINGS_READ_SEARCH(ocapiVersion)}\n\n` +
|
|
165
|
+
`--- MUTATION GRANTS (required for the nine sfcc-write tools) ---\n` +
|
|
147
166
|
`${OCAPI_SETTINGS_WRITE_IMPORT(ocapiVersion)}\n\n` +
|
|
148
167
|
`Replace <YOUR_CLIENT_ID> with the client_id from your dw.json.`);
|
|
149
168
|
}
|
|
@@ -113,6 +113,12 @@ async function saveAndReturn(text, dir, filename, page) {
|
|
|
113
113
|
* an API version while OCAPI simultaneously reports `_v: "25.6"` in the same
|
|
114
114
|
* session; the constraint belongs to this Bridge tool, not to a version. The
|
|
115
115
|
* status, code, source, decision, and `details` payload are untouched.
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ BAPI-817: like `write-guard.ts#rejectIfNotSandboxForWrite`, this checks a
|
|
118
|
+
* caller-supplied OCAPI instance-CONTEXT argument, NOT the host that will be
|
|
119
|
+
* contacted. It is secondary. The instance actually reached is decided by the
|
|
120
|
+
* resolved-hostname boundary in `tool-wrapper.ts`, which runs before this and
|
|
121
|
+
* does not consult `instance`.
|
|
116
122
|
*/
|
|
117
123
|
function rejectIfNotSandbox(instance) {
|
|
118
124
|
if (instance !== "sandbox") {
|
package/build/sfcc/register.js
CHANGED
|
@@ -5,10 +5,32 @@
|
|
|
5
5
|
* SFCC tools with a single call adjacent to `registerConductorTools`. All
|
|
6
6
|
* SFCC business logic lives inside the sfcc/ module; index.ts stays a
|
|
7
7
|
* thin registration/composition layer.
|
|
8
|
+
*
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
* Two structural properties this module owns (BAPI-817)
|
|
11
|
+
* ---------------------------------------------------------------------------
|
|
12
|
+
*
|
|
13
|
+
* **1. The target boundary is unavoidable.** The raw host registrar is used in
|
|
14
|
+
* exactly one place — `registerSfccTool`, the adapter below — and every other
|
|
15
|
+
* registration in this module and in every child registrar goes through that
|
|
16
|
+
* adapter. So the resolved-hostname sandbox check runs for all twenty tools:
|
|
17
|
+
* the two always-on diagnostics, the eight reads (including the five with no
|
|
18
|
+
* `instance` parameter at all), the nine writes, and `sfcc_log_query`. Asking
|
|
19
|
+
* each handler to remember a check is what produced the unguarded reads in the
|
|
20
|
+
* first place; wrapping the registrar removes the opportunity to forget.
|
|
21
|
+
*
|
|
22
|
+
* **2. Reads and writes are separately gated.** Before BAPI-817 one boolean
|
|
23
|
+
* (`includeReadTools`, from `ACTIVE_GROUPS.has("sfcc")`) registered eighteen
|
|
24
|
+
* tools, nine of them destructive — while the README described the group as
|
|
25
|
+
* "the 7 heavy SFCC read tools" and stated "All SFCC tools are read-only".
|
|
26
|
+
* Enabling introspection silently enabled mutation. This module now takes two
|
|
27
|
+
* independent flags so the `sfcc` profile is genuinely read-oriented and the
|
|
28
|
+
* nine writes require the deliberate `sfcc-write` opt-in (the ticket's
|
|
29
|
+
* Decision 2(c)).
|
|
8
30
|
*/
|
|
9
31
|
import { z } from "zod";
|
|
10
32
|
import { buildSfccSetupStatusHandler } from "./setup-status.js";
|
|
11
|
-
import { withSfccGate } from "./tool-wrapper.js";
|
|
33
|
+
import { withSfccGate, withSfccTargetBoundary } from "./tool-wrapper.js";
|
|
12
34
|
import { checkPermissionsTool } from "./permissions.js";
|
|
13
35
|
import { registerSystemObjectReadTools } from "./reads-system-object.js";
|
|
14
36
|
import { registerSfccCustomObjectDefReadTools } from "./reads-custom-object-def.js";
|
|
@@ -22,7 +44,9 @@ import { registerSfccLogQueryTool } from "./log-query.js";
|
|
|
22
44
|
* Register all SFCC tools through the host's registerTool wrapper.
|
|
23
45
|
*
|
|
24
46
|
* Call this once in index.ts, immediately after `registerConductorTools(registerTool)`.
|
|
25
|
-
*
|
|
47
|
+
* Read and write surfaces are selected independently by `deps.includeReadTools`
|
|
48
|
+
* and `deps.includeWriteTools`; the two always-on diagnostics register
|
|
49
|
+
* regardless. Every path goes through `registerSfccTool`.
|
|
26
50
|
*/
|
|
27
51
|
export function registerSfccTools(registerTool, deps) {
|
|
28
52
|
const gateDeps = {
|
|
@@ -30,8 +54,17 @@ export function registerSfccTools(registerTool, deps) {
|
|
|
30
54
|
getGetHeaders: deps.getGetHeaders,
|
|
31
55
|
repoName: deps.repoName,
|
|
32
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The one and only bridge between the host registrar and the SFCC surface.
|
|
59
|
+
*
|
|
60
|
+
* Every SFCC tool is registered through this, so every SFCC tool crosses the
|
|
61
|
+
* resolved-hostname boundary before its own handler runs. `registerTool` (the
|
|
62
|
+
* raw host registrar) must not be referenced anywhere else in this module or
|
|
63
|
+
* passed to a child registrar — `registration-boundary.test.ts` pins that.
|
|
64
|
+
*/
|
|
65
|
+
const registerSfccTool = (name, config, handler) => registerTool(name, config, withSfccTargetBoundary(handler));
|
|
33
66
|
// sfcc_setup_status — aggregate prerequisite reporter.
|
|
34
|
-
|
|
67
|
+
registerSfccTool("sfcc_setup_status", {
|
|
35
68
|
description: "Report on every SFCC prerequisite: Bridge API key, repo name, version config, " +
|
|
36
69
|
"dw.json presence/uniqueness, and AM token acquisition. Always-registered; " +
|
|
37
70
|
"returns status without requiring full SFCC configuration to be complete.",
|
|
@@ -40,53 +73,58 @@ export function registerSfccTools(registerTool, deps) {
|
|
|
40
73
|
}, buildSfccSetupStatusHandler(deps.buildGetUrl, deps.getGetHeaders, deps.repoName, deps.getResolvedApiKey));
|
|
41
74
|
// check_permissions — OCAPI access probe + settings JSON printer.
|
|
42
75
|
const gatedCheckPermissions = withSfccGate(gateDeps, async (_args, credentials) => checkPermissionsTool(credentials));
|
|
43
|
-
|
|
76
|
+
registerSfccTool("check_permissions", {
|
|
44
77
|
description: "Probe SFCC OCAPI access via GET /system_object_definitions. " +
|
|
45
78
|
"On 200: reports OK and the detected OCAPI version. " +
|
|
46
79
|
"On 401/403: prints the exact OCAPI Settings JSON to paste in Business Manager " +
|
|
47
|
-
"(
|
|
80
|
+
"(read/search and mutation grants).",
|
|
48
81
|
inputSchema: z.object({
|
|
49
82
|
instance: z
|
|
50
83
|
.string()
|
|
84
|
+
.min(1)
|
|
51
85
|
.optional()
|
|
52
|
-
.describe("
|
|
86
|
+
.describe("Sandbox hostname to use instead of dw.json. Still sandbox-validated."),
|
|
53
87
|
}),
|
|
54
88
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
55
89
|
}, gatedCheckPermissions);
|
|
56
|
-
//
|
|
57
|
-
//
|
|
90
|
+
// The read profile — eight OCAPI reads plus sfcc_log_query — is gated behind
|
|
91
|
+
// ACTIVE_GROUPS.has("sfcc") (BAPI-453). sfcc_setup_status and
|
|
92
|
+
// check_permissions above remain always-on (D-4 decision).
|
|
58
93
|
if (deps.includeReadTools) {
|
|
59
94
|
// system_object_list, system_object_get, system_object_attribute_search (BAPI-401 T1–T3)
|
|
60
|
-
registerSystemObjectReadTools(
|
|
95
|
+
registerSystemObjectReadTools(registerSfccTool, {
|
|
61
96
|
gateDeps,
|
|
62
97
|
getDocsDir: deps.getDocsDir,
|
|
63
98
|
});
|
|
64
99
|
// custom_object_definition_attributes_get, custom_object_definition_attribute_search (BAPI-402 T4–T5)
|
|
65
|
-
registerSfccCustomObjectDefReadTools(
|
|
66
|
-
gateDeps,
|
|
67
|
-
getDocsDir: deps.getDocsDir,
|
|
68
|
-
});
|
|
69
|
-
// site_preference_get, site_preference_search (BAPI-403 T6 — optional)
|
|
70
|
-
registerSitePreferenceTools(registerTool, {
|
|
100
|
+
registerSfccCustomObjectDefReadTools(registerSfccTool, {
|
|
71
101
|
gateDeps,
|
|
72
102
|
getDocsDir: deps.getDocsDir,
|
|
73
103
|
});
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
// branch so writes become default-on with reads whenever the sfcc profile
|
|
77
|
-
// group is active.
|
|
78
|
-
registerSfccWriteTools(registerTool, {
|
|
104
|
+
// site_preference_group_list, site_preference_get, site_preference_search (BAPI-403 T6)
|
|
105
|
+
registerSitePreferenceTools(registerSfccTool, {
|
|
79
106
|
gateDeps,
|
|
80
107
|
getDocsDir: deps.getDocsDir,
|
|
81
108
|
});
|
|
82
109
|
// sfcc_log_query (BAPI-556 T7) — gated, on-demand redacted/filtered log query.
|
|
83
|
-
// Uses its OWN call-time gate (log capability = WebDAV Basic
|
|
84
|
-
// OCAPI version/dw.json/AM-token gate above.
|
|
85
|
-
|
|
110
|
+
// Uses its OWN call-time capability gate (log capability = WebDAV Basic
|
|
111
|
+
// auth), NOT the OCAPI version/dw.json/AM-token gate above. It is read-only,
|
|
112
|
+
// so it belongs to the read profile.
|
|
113
|
+
registerSfccLogQueryTool(registerSfccTool, {
|
|
86
114
|
buildGetUrl: deps.buildGetUrl,
|
|
87
115
|
getGetHeaders: deps.getGetHeaders,
|
|
88
116
|
getPostHeaders: deps.getPostHeaders,
|
|
89
117
|
repoName: deps.repoName,
|
|
90
118
|
});
|
|
91
119
|
}
|
|
120
|
+
// The nine destructive write tools are gated INDEPENDENTLY, behind
|
|
121
|
+
// ACTIVE_GROUPS.has("sfcc-write") (BAPI-817). This branch is deliberately not
|
|
122
|
+
// nested inside the read branch: `sfcc-write` alone is a valid selection, and
|
|
123
|
+
// `sfcc` alone must never register a tool carrying WRITE_ANNOTATIONS.
|
|
124
|
+
if (deps.includeWriteTools) {
|
|
125
|
+
registerSfccWriteTools(registerSfccTool, {
|
|
126
|
+
gateDeps,
|
|
127
|
+
getDocsDir: deps.getDocsDir,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
92
130
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical inventory of every SFCC MCP tool name, by registration mode
|
|
3
|
+
* (BAPI-816).
|
|
4
|
+
*
|
|
5
|
+
* `register.ts` (`registerSfccTools`) decides WHICH tools register; this
|
|
6
|
+
* module is the single readable description of what that decision produces.
|
|
7
|
+
* Registrar tests compare captured registrations against these composed
|
|
8
|
+
* arrays instead of independently maintained numeric pins, which is exactly
|
|
9
|
+
* what let `register-site-preference.integration.test.ts` pin a stale `10`
|
|
10
|
+
* while the real registrar had already grown to 20 — a full write surface's
|
|
11
|
+
* worth of drift that no test caught (BAPI-816).
|
|
12
|
+
*
|
|
13
|
+
* `includeReadTools` is the legacy `SfccHostDeps` flag name (`register.ts`).
|
|
14
|
+
* Since BAPI-817 it gates the READ profile only — the eight OCAPI reads plus
|
|
15
|
+
* the log-query tool — while `includeWriteTools` independently gates the nine
|
|
16
|
+
* writes. The name predates both surfaces and was kept for call-site stability.
|
|
17
|
+
*/
|
|
18
|
+
import { SFCC_WRITE_TOOL_NAMES } from "./writes.js";
|
|
19
|
+
/**
|
|
20
|
+
* Registered unconditionally, regardless of `includeReadTools` (D-4 decision
|
|
21
|
+
* in register.ts): the aggregate prerequisite reporter and the OCAPI access
|
|
22
|
+
* probe.
|
|
23
|
+
*/
|
|
24
|
+
export const SFCC_ALWAYS_REGISTERED_TOOL_NAMES = [
|
|
25
|
+
"sfcc_setup_status",
|
|
26
|
+
"check_permissions",
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The eight read tools registered by the three read-module registrars
|
|
30
|
+
* (`registerSystemObjectReadTools`, `registerSfccCustomObjectDefReadTools`,
|
|
31
|
+
* `registerSitePreferenceTools`) when `includeReadTools` is true.
|
|
32
|
+
*/
|
|
33
|
+
export const SFCC_READ_TOOL_NAMES = [
|
|
34
|
+
"system_object_list",
|
|
35
|
+
"system_object_get",
|
|
36
|
+
"system_object_attribute_search",
|
|
37
|
+
"custom_object_definition_attributes_get",
|
|
38
|
+
"custom_object_definition_attribute_search",
|
|
39
|
+
"site_preference_group_list",
|
|
40
|
+
"site_preference_get",
|
|
41
|
+
"site_preference_search",
|
|
42
|
+
];
|
|
43
|
+
/** The on-demand redacted/filtered log-query tool (BAPI-556 T7). */
|
|
44
|
+
export const SFCC_LOG_QUERY_TOOL_NAME = "sfcc_log_query";
|
|
45
|
+
/**
|
|
46
|
+
* The `sfcc` READ profile: the eight OCAPI reads plus the log-query tool
|
|
47
|
+
* (BAPI-817).
|
|
48
|
+
*
|
|
49
|
+
* This is the exact set `includeReadTools` registers. Every member is read-only
|
|
50
|
+
* and carries read-only annotations; by construction no tool annotated
|
|
51
|
+
* `WRITE_ANNOTATIONS` can appear here, which is what makes "enabling `sfcc`
|
|
52
|
+
* cannot enable a mutation" checkable rather than merely intended.
|
|
53
|
+
*/
|
|
54
|
+
export const SFCC_READ_PROFILE_TOOL_NAMES = [
|
|
55
|
+
...SFCC_READ_TOOL_NAMES,
|
|
56
|
+
SFCC_LOG_QUERY_TOOL_NAME,
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* The `sfcc-write` WRITE profile: the nine destructive tools (BAPI-817).
|
|
60
|
+
*
|
|
61
|
+
* Reused from `writes.ts#SFCC_WRITE_TOOL_NAMES` rather than duplicated, so the
|
|
62
|
+
* profile inventory and the registrar's own list cannot drift apart.
|
|
63
|
+
*/
|
|
64
|
+
export const SFCC_WRITE_PROFILE_TOOL_NAMES = [
|
|
65
|
+
...SFCC_WRITE_TOOL_NAMES,
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* Every profile-gated tool, across both groups.
|
|
69
|
+
*
|
|
70
|
+
* Retained as the aggregate for consumers that care about "gated vs always-on"
|
|
71
|
+
* rather than which group gates it — but composed from the two profile
|
|
72
|
+
* inventories above, never hand-maintained. Order matches registration order in
|
|
73
|
+
* `register.ts`: the read branch runs first, then the write branch.
|
|
74
|
+
*/
|
|
75
|
+
export const SFCC_PROFILE_GATED_TOOL_NAMES = [
|
|
76
|
+
...SFCC_READ_PROFILE_TOOL_NAMES,
|
|
77
|
+
...SFCC_WRITE_PROFILE_TOOL_NAMES,
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* The complete SFCC registration surface: always-on plus both gated profiles.
|
|
81
|
+
*
|
|
82
|
+
* This is the twenty-tool surface a `BRIDGE_MCP_PROFILE=sfcc,sfcc-write` (or
|
|
83
|
+
* `full`) session exposes, and the iteration source for the boundary-coverage
|
|
84
|
+
* test — so a newly added tool cannot silently evade that coverage.
|
|
85
|
+
*/
|
|
86
|
+
export const SFCC_ALL_REGISTERED_TOOL_NAMES = [
|
|
87
|
+
...SFCC_ALWAYS_REGISTERED_TOOL_NAMES,
|
|
88
|
+
...SFCC_PROFILE_GATED_TOOL_NAMES,
|
|
89
|
+
];
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* prevent the others from running.
|
|
7
7
|
*/
|
|
8
8
|
import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
|
|
9
|
-
import { resolveSfccCredentials } from "./credentials.js";
|
|
10
9
|
import { getAmToken } from "./client.js";
|
|
11
10
|
// ---------------------------------------------------------------------------
|
|
12
11
|
// Implementation
|
|
@@ -57,39 +56,20 @@ export async function sfccSetupStatusTool(deps) {
|
|
|
57
56
|
versionStatus = "— Skipped (Bridge API not configured)";
|
|
58
57
|
}
|
|
59
58
|
lines.push(`3. SFCC Version: ${versionStatus}`);
|
|
60
|
-
// 4. dw.json / credential resolution
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
source: result.credentials.source,
|
|
70
|
-
};
|
|
71
|
-
credStatus = `✓ Found (${resolvedCredentials.source})`;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
credStatus = `✗ ${result.error}`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
credStatus = `✗ Resolution error: ${err instanceof Error ? err.message : String(err)}`;
|
|
79
|
-
}
|
|
80
|
-
lines.push(`4. dw.json / Credentials: ${credStatus}`);
|
|
81
|
-
// 5. AM token acquisition (only if credentials were resolved)
|
|
59
|
+
// 4. dw.json / credential resolution — read from the boundary-approved target
|
|
60
|
+
// rather than resolved again here (BAPI-817).
|
|
61
|
+
const target = deps.target;
|
|
62
|
+
// Report source and instance ID only — never the secret values.
|
|
63
|
+
const instanceLabel = target.approved
|
|
64
|
+
? target.hostname.split(".")[0] ?? target.hostname
|
|
65
|
+
: null;
|
|
66
|
+
lines.push(`4. dw.json / Credentials: ${target.approved ? `✓ Found (${target.credentials.source})` : `✗ ${target.error}`}`);
|
|
67
|
+
// 5. AM token acquisition (only when the boundary approved a target).
|
|
82
68
|
let tokenStatus = "— Skipped (credentials not available)";
|
|
83
|
-
if (
|
|
69
|
+
if (target.approved) {
|
|
84
70
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await getAmToken(credResult.credentials);
|
|
88
|
-
tokenStatus = `✓ Token acquired for instance ${resolvedCredentials.hostname}`;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
tokenStatus = "✗ Credentials not resolved";
|
|
92
|
-
}
|
|
71
|
+
await getAmToken(target.credentials);
|
|
72
|
+
tokenStatus = `✓ Token acquired for instance ${instanceLabel}`;
|
|
93
73
|
}
|
|
94
74
|
catch (err) {
|
|
95
75
|
// Sanitize: strip any token/secret-looking content from error message.
|
|
@@ -135,10 +115,14 @@ export async function sfccSetupStatusTool(deps) {
|
|
|
135
115
|
}
|
|
136
116
|
/**
|
|
137
117
|
* Build the sfcc_setup_status handler bound to the host's runtime deps.
|
|
118
|
+
*
|
|
119
|
+
* The returned handler takes the `SfccTargetContext` the registration-level
|
|
120
|
+
* sandbox boundary supplies (BAPI-817) and forwards it, so this tool reports on
|
|
121
|
+
* the same target the boundary approved and never resolves one of its own.
|
|
138
122
|
*/
|
|
139
123
|
export function buildSfccSetupStatusHandler(buildGetUrl, getGetHeaders, repoName, getApiKey) {
|
|
140
|
-
return async (_args) => {
|
|
124
|
+
return async (_args, target) => {
|
|
141
125
|
const apiKey = await getApiKey();
|
|
142
|
-
return sfccSetupStatusTool({ buildGetUrl, getGetHeaders, repoName, apiKey });
|
|
126
|
+
return sfccSetupStatusTool({ buildGetUrl, getGetHeaders, repoName, apiKey, target });
|
|
143
127
|
};
|
|
144
128
|
}
|