@bridge_gpt/mcp-server 0.2.36 → 0.2.38
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 +48 -8
- package/build/base-url.js +79 -0
- package/build/bridge-api-urls.js +9 -0
- package/build/chain-orchestrator.js +93 -15
- package/build/claude-user-config-doctor.js +317 -0
- package/build/commands.generated.js +2 -1
- package/build/conductor/bridge-api-client.js +178 -4
- package/build/conductor-bin.js +1 -1
- package/build/conductor-bundle-artifacts.js +7 -6
- package/build/credential-store.js +205 -4
- package/build/direct-ticket-tools.js +70 -0
- package/build/doctor.js +239 -80
- package/build/executor/cli.js +51 -1
- package/build/executor/credentials.js +1 -7
- package/build/executor/deps.js +18 -1
- package/build/executor/env.js +51 -25
- package/build/executor/heartbeat.js +138 -17
- package/build/executor/http-client.js +49 -8
- package/build/executor/job-errors.js +4 -0
- package/build/executor/job-runner.js +422 -22
- package/build/executor/observation.js +130 -0
- package/build/executor/permissions.js +104 -8
- package/build/executor/preflight.js +32 -0
- package/build/executor/runner.js +8 -0
- package/build/executor/test-clock.js +67 -3
- package/build/executor/types.js +4 -1
- package/build/executor/worker-command.js +11 -3
- package/build/executor/worker-config-isolation.js +287 -0
- package/build/executor/worker-finalization.js +68 -14
- package/build/executor/worktree.js +46 -4
- package/build/index.js +614 -244
- package/build/init.js +363 -73
- package/build/install-bridge.js +568 -80
- package/build/launcher-config-inspection.js +351 -0
- package/build/mcp-invoke.js +49 -6
- package/build/mcp-provisioning.js +30 -7
- package/build/mcp-registration-doctor.js +14 -5
- package/build/notifications.js +553 -0
- package/build/pipeline-orchestrator.js +146 -4
- package/build/pipeline-utils.js +3 -0
- package/build/pipelines.generated.js +22 -9
- package/build/plan-execution-ledger.js +550 -0
- package/build/plan-phase-routing.js +272 -0
- package/build/plane/alembic-head.js +110 -0
- package/build/plane/build-freshness.js +167 -0
- package/build/plane/cli.js +480 -0
- package/build/plane/defaults.js +266 -0
- package/build/plane/manifest.js +377 -0
- package/build/plane/member-logs.js +147 -0
- package/build/plane/member-roster.js +147 -0
- package/build/plane/preflight.js +289 -0
- package/build/plane/shutdown.js +195 -0
- package/build/plane/status.js +125 -0
- package/build/plane/supervisor.js +569 -0
- package/build/plane/test-fakes.js +156 -0
- package/build/plane/types.js +75 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +2 -0
- package/build/setup-epic.js +662 -27
- package/build/sfcc/log-gate.js +38 -11
- package/build/sfcc/log-query.js +55 -15
- package/build/sfcc/ocapi-shape.js +70 -14
- package/build/sfcc/output.js +41 -11
- package/build/sfcc/permissions.js +24 -2
- package/build/sfcc/read-body.js +92 -0
- package/build/sfcc/read-projection.js +185 -0
- package/build/sfcc/read-result.js +158 -0
- package/build/sfcc/reads-custom-object-def.js +57 -34
- package/build/sfcc/reads-site-preference.js +86 -33
- package/build/sfcc/reads-system-object.js +50 -38
- package/build/sfcc/sfcc-result.js +106 -0
- package/build/sfcc/tool-wrapper.js +56 -13
- package/build/sfcc/write-grants.js +45 -22
- package/build/sfcc/write-guard.js +21 -13
- package/build/sfcc/write-result.js +71 -15
- package/build/sfcc/write-tool-common.js +126 -32
- package/build/sfcc/writes-custom-object-def.js +6 -2
- package/build/sfcc/writes-system-object.js +11 -50
- package/build/start-tickets-prereqs.js +129 -0
- package/build/start-tickets.js +17 -13
- package/build/ticket-backend-metadata.js +59 -0
- package/build/ticket-key-utils.js +92 -0
- package/build/tool-error-envelope.js +71 -0
- package/build/tool-surface-gating.js +72 -0
- package/build/update-status.js +102 -0
- package/build/upgrade-advice.js +47 -0
- package/build/upgrade-cli.js +417 -101
- package/build/version.generated.js +1 -1
- package/build/worktree-core.js +73 -0
- package/docs/CONDUCTOR.md +23 -8
- package/package.json +3 -3
- package/pipelines/implement-ticket.json +15 -5
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SFCC read projection policy (BAPI-767).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS MODULE EXISTS
|
|
5
|
+
* ----------------------
|
|
6
|
+
* Before BAPI-767 no `select` was constructed anywhere in the SFCC read path.
|
|
7
|
+
* The only occurrence of the OCAPI full-projection expression in the tree was
|
|
8
|
+
* the *grant template* in `permissions.ts` (`read_attributes: "(**)"`) — a
|
|
9
|
+
* permission the tools were never actually exercising. The consequence was the
|
|
10
|
+
* headline finding of the 2026-08-11 introspection report: an agent could
|
|
11
|
+
* enumerate every attribute id on a type and learn nothing about any of them —
|
|
12
|
+
* not the `value_type`, not `mandatory`, `localizable`, `searchable`, or
|
|
13
|
+
* `site_specific`. The MCP could *write* a type it could not read back.
|
|
14
|
+
*
|
|
15
|
+
* The fix is a projection, and the reason it lives in one module rather than in
|
|
16
|
+
* each handler is that the *default* is the load-bearing part. "Complete by
|
|
17
|
+
* default on attribute reads, lean by default on broad lists" is a single
|
|
18
|
+
* ratified policy; six handlers each deciding their own default is how that
|
|
19
|
+
* policy silently drifts. Requirement 2 of the ticket asks for exactly one
|
|
20
|
+
* place with its own test, and this is it.
|
|
21
|
+
*
|
|
22
|
+
* THE `(**)` EXPRESSION IS PRIVATE
|
|
23
|
+
* --------------------------------
|
|
24
|
+
* Callers select a `projection` of `"full"` or `"lean"`. They never see or pass
|
|
25
|
+
* raw OCAPI select syntax. That keeps the tool surface stable if the expression
|
|
26
|
+
* ever changes, and — more importantly — stops an agent from constructing an
|
|
27
|
+
* arbitrary select expression against a live instance.
|
|
28
|
+
*
|
|
29
|
+
* EVERY CATEGORY HERE WAS VERIFIED LIVE
|
|
30
|
+
* -------------------------------------
|
|
31
|
+
* The category table is not documentation-sourced. Each entry was probed
|
|
32
|
+
* against a developer sandbox (OCAPI Data API v25_6) before it was written, and
|
|
33
|
+
* the observed key-count expansion is recorded next to it. This matters because
|
|
34
|
+
* an unverified projection flag is worse than no flag: it reads as a capability
|
|
35
|
+
* and delivers nothing.
|
|
36
|
+
*
|
|
37
|
+
* `system_object_get` is deliberately ABSENT from this policy. Its endpoint,
|
|
38
|
+
* GET /system_object_definitions/{type}, already returns its complete 13-field
|
|
39
|
+
* document and ignores `select` entirely (verified: 13 keys with and without).
|
|
40
|
+
* Giving it a `projection` flag would ship a parameter with no observable
|
|
41
|
+
* effect — the same defect BAPI-760 removed from that very tool when it deleted
|
|
42
|
+
* the no-op `expand_attribute_definitions` boolean. A tool that cannot honor a
|
|
43
|
+
* projection does not get a projection input.
|
|
44
|
+
*/
|
|
45
|
+
import { z } from "zod";
|
|
46
|
+
/** The two projection values, as a runtime-checkable list. */
|
|
47
|
+
export const SFCC_READ_PROJECTIONS = ["full", "lean"];
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// The policy
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* OCAPI's full-projection expression.
|
|
53
|
+
*
|
|
54
|
+
* Intentionally NOT exported. Only `selectExpressionFor` may map a projection
|
|
55
|
+
* onto it, so there is exactly one place where an outbound `select` value can
|
|
56
|
+
* originate.
|
|
57
|
+
*/
|
|
58
|
+
const FULL_SELECT_EXPRESSION = "(**)";
|
|
59
|
+
/**
|
|
60
|
+
* The single endpoint-category → projection policy table (Requirement 2).
|
|
61
|
+
*
|
|
62
|
+
* Verified live against a developer sandbox, 2026-08-13 (OCAPI v25_6). The
|
|
63
|
+
* key-count expansions below are observations, not estimates:
|
|
64
|
+
*
|
|
65
|
+
* attribute_search POST .../attribute_definition_search
|
|
66
|
+
* system_object: 4 → 25 keys
|
|
67
|
+
* custom_object_def: 4 → 26 keys
|
|
68
|
+
* attribute_collection GET .../attribute_definitions
|
|
69
|
+
* custom_object_def: 4 → 26 keys
|
|
70
|
+
* broad_list GET /system_object_definitions
|
|
71
|
+
* 4 → 13 keys
|
|
72
|
+
* GET .../SitePreferences/attribute_groups
|
|
73
|
+
* 4 → 10 keys
|
|
74
|
+
*
|
|
75
|
+
* The ratified requirement these defaults encode: complete by default on
|
|
76
|
+
* attribute reads, lean by default on broad lists, caller can override in
|
|
77
|
+
* either direction. An opt-in-only flag was explicitly rejected — the caller
|
|
78
|
+
* who does not know to ask is precisely the caller the report described.
|
|
79
|
+
*/
|
|
80
|
+
const PROJECTION_POLICY = {
|
|
81
|
+
attribute_search: {
|
|
82
|
+
defaultProjection: "full",
|
|
83
|
+
placement: "body",
|
|
84
|
+
inputDescription: "Detail level. Default: full — OCAPI's complete metadata projection " +
|
|
85
|
+
"(value_type, mandatory, localizable, searchable, site_specific); " +
|
|
86
|
+
"default_value is withheld. lean returns ids only.",
|
|
87
|
+
},
|
|
88
|
+
attribute_collection: {
|
|
89
|
+
defaultProjection: "full",
|
|
90
|
+
placement: "query",
|
|
91
|
+
inputDescription: "Detail level. Default: full — OCAPI's complete metadata projection " +
|
|
92
|
+
"(value_type, mandatory, localizable, searchable, site_specific); " +
|
|
93
|
+
"default_value is withheld. lean returns ids only.",
|
|
94
|
+
},
|
|
95
|
+
broad_list: {
|
|
96
|
+
defaultProjection: "lean",
|
|
97
|
+
placement: "query",
|
|
98
|
+
inputDescription: "Detail level. Default: lean (ids only); full returns all fields.",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Resolution
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the effective projection for a category.
|
|
106
|
+
*
|
|
107
|
+
* An omitted / undefined request resolves to the category default. An explicit
|
|
108
|
+
* caller value always wins — including an explicit `"lean"` on a full-default
|
|
109
|
+
* category, which is the "override in either direction" half of the
|
|
110
|
+
* requirement and is easy to lose to a truthiness check.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveReadProjection(category, requested) {
|
|
113
|
+
return requested ?? PROJECTION_POLICY[category].defaultProjection;
|
|
114
|
+
}
|
|
115
|
+
/** The wire placement (`body` or `query`) for a category. */
|
|
116
|
+
export function projectionPlacementFor(category) {
|
|
117
|
+
return PROJECTION_POLICY[category].placement;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Map an effective projection onto an OCAPI `select` expression.
|
|
121
|
+
*
|
|
122
|
+
* Returns `undefined` for `lean` — deliberately, so `lean` means "send no
|
|
123
|
+
* `select` at all" rather than "send an empty one". OCAPI's unqualified
|
|
124
|
+
* response is the lean shape; there is no lean expression to construct.
|
|
125
|
+
*/
|
|
126
|
+
export function selectExpressionFor(projection) {
|
|
127
|
+
return projection === "full" ? FULL_SELECT_EXPRESSION : undefined;
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Application helpers
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
/**
|
|
133
|
+
* Apply the resolved projection to an OCAPI search POST body.
|
|
134
|
+
*
|
|
135
|
+
* Mutates and returns `body` so a handler can build its body normally and then
|
|
136
|
+
* hand it here as the last step. A `lean` resolution leaves the body untouched
|
|
137
|
+
* — no `select` key is added.
|
|
138
|
+
*
|
|
139
|
+
* Throws if the category is not body-placed, rather than silently writing a
|
|
140
|
+
* `select` into a body OCAPI would ignore for that endpoint.
|
|
141
|
+
*/
|
|
142
|
+
export function applyProjectionToBody(body, category, requested) {
|
|
143
|
+
if (projectionPlacementFor(category) !== "body") {
|
|
144
|
+
throw new Error(`SFCC read category "${category}" carries its projection in the query string, not the POST body.`);
|
|
145
|
+
}
|
|
146
|
+
const expression = selectExpressionFor(resolveReadProjection(category, requested));
|
|
147
|
+
if (expression !== undefined) {
|
|
148
|
+
body.select = expression;
|
|
149
|
+
}
|
|
150
|
+
return body;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Query-string entries carrying the resolved projection.
|
|
154
|
+
*
|
|
155
|
+
* Returns an empty object for `lean`, so a caller can spread it unconditionally
|
|
156
|
+
* into its query params without a conditional at each call site.
|
|
157
|
+
*
|
|
158
|
+
* Throws if the category is body-placed, for the same reason as above.
|
|
159
|
+
*/
|
|
160
|
+
export function projectionQueryEntries(category, requested) {
|
|
161
|
+
if (projectionPlacementFor(category) !== "query") {
|
|
162
|
+
throw new Error(`SFCC read category "${category}" carries its projection in the POST body, not the query string.`);
|
|
163
|
+
}
|
|
164
|
+
const expression = selectExpressionFor(resolveReadProjection(category, requested));
|
|
165
|
+
return expression === undefined ? {} : { select: expression };
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Input schema
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
/**
|
|
171
|
+
* The optional `projection` Zod field for a category's input schema.
|
|
172
|
+
*
|
|
173
|
+
* The description is category-specific because the default differs by category,
|
|
174
|
+
* and the default is decision-critical *before* invocation — an agent choosing
|
|
175
|
+
* whether to pass `projection` cannot learn the answer from the response. It is
|
|
176
|
+
* kept to the fewest words that convey the default and the override, because
|
|
177
|
+
* every tool description and schema competes for the same tools/list token
|
|
178
|
+
* budget (see `integration/budget-config.ts`).
|
|
179
|
+
*/
|
|
180
|
+
export function projectionInputFor(category) {
|
|
181
|
+
return z
|
|
182
|
+
.enum(["full", "lean"])
|
|
183
|
+
.optional()
|
|
184
|
+
.describe(PROJECTION_POLICY[category].inputDescription);
|
|
185
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SFCC read boundary, layered on the universal SFCC result contract (BAPI-759,
|
|
3
|
+
* generalized by BAPI-766).
|
|
4
|
+
*
|
|
5
|
+
* This module owns the *read-specific* concerns: OCAPI fault-message extraction,
|
|
6
|
+
* the read-boundary error wrapper, and the fixed read messages. The envelope
|
|
7
|
+
* itself — normalization, serialization, and the `isError` predicate — lives in
|
|
8
|
+
* `sfcc-result.ts` and is shared with the write and WebDAV log surfaces, so
|
|
9
|
+
* there is exactly one implementation for the whole SFCC surface.
|
|
10
|
+
*
|
|
11
|
+
* Read handlers never construct an error envelope inline — they call
|
|
12
|
+
* `formatSfccReadFailure`, which is re-exported here as a thin delegate to the
|
|
13
|
+
* shared formatter.
|
|
14
|
+
*
|
|
15
|
+
* The unified guarantees (`isError: true` on every failure, and a body that
|
|
16
|
+
* parses as `{ error: { code, status, message, source, details } }`) are
|
|
17
|
+
* documented in `sfcc-result.ts`. An object-valued OCAPI body is preserved
|
|
18
|
+
* verbatim under `error.details` (`_v`, `fault.type`, `fault.message`,
|
|
19
|
+
* `fault.arguments` and any other upstream field), so upstream data is never
|
|
20
|
+
* discarded and never collides with the Bridge-owned envelope fields.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
import { formatSfccFailure } from "./sfcc-result.js";
|
|
24
|
+
export { formatSfccReadFailure } from "./sfcc-result.js";
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Fixed messages
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* The one message returned for an unexpected throw. Bridge-authored and fixed:
|
|
30
|
+
* the thrown value is never serialized, so a credential, token, request header,
|
|
31
|
+
* or stack frame carried by the exception cannot reach the caller.
|
|
32
|
+
*/
|
|
33
|
+
export const SFCC_INTERNAL_ERROR_MESSAGE = "An unexpected internal error occurred while handling this SFCC read. " +
|
|
34
|
+
"The underlying failure detail is withheld from this response to avoid " +
|
|
35
|
+
"leaking credentials or internal state.";
|
|
36
|
+
/** The one message returned when arguments fail schema validation at the read boundary. */
|
|
37
|
+
export const SFCC_VALIDATION_ERROR_MESSAGE = "Arguments failed schema validation before any OCAPI call was made. " +
|
|
38
|
+
"See error.details.issues for the offending fields.";
|
|
39
|
+
/** Bridge-authored fallback used when an OCAPI body carries no usable `fault.message`. */
|
|
40
|
+
export function ocapiFallbackMessage(status) {
|
|
41
|
+
return (`The SFCC OCAPI Data API returned HTTP ${status} for this read and supplied no ` +
|
|
42
|
+
`fault message. The upstream response body is preserved under error.details.`);
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// OCAPI read failures
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/** True for a plain (non-array, non-null) object. */
|
|
48
|
+
function isPlainObject(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format a non-OK OCAPI read response through the shared formatter.
|
|
53
|
+
*
|
|
54
|
+
* The message is the upstream `fault.message` when it is a non-empty string,
|
|
55
|
+
* otherwise a stable Bridge-authored fallback — raw response text is never
|
|
56
|
+
* promoted into `error.message`. The whole upstream body is preserved under
|
|
57
|
+
* `error.details`.
|
|
58
|
+
*
|
|
59
|
+
* `upstreamHttpStatus` exists for the case where Bridge reports a status other
|
|
60
|
+
* than the one HTTP returned. The OCAPI client does not currently normalize a
|
|
61
|
+
* read status (`ocapiGet`/`ocapiPost` surface `Response.status` verbatim), so
|
|
62
|
+
* in practice the two are equal and the field is omitted; the parameter keeps
|
|
63
|
+
* the seam honest if a normalization step is ever introduced.
|
|
64
|
+
*/
|
|
65
|
+
export function formatOcapiReadFailure(result, upstreamHttpStatus) {
|
|
66
|
+
return formatSfccFailure({
|
|
67
|
+
code: "OCAPI_FAULT",
|
|
68
|
+
status: result.status,
|
|
69
|
+
message: ocapiFaultMessage(result.body, result.status),
|
|
70
|
+
source: "ocapi",
|
|
71
|
+
details: result.body,
|
|
72
|
+
upstreamHttpStatus,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Pull a usable human-readable message out of an OCAPI fault body. */
|
|
76
|
+
function ocapiFaultMessage(body, status) {
|
|
77
|
+
if (isPlainObject(body) && isPlainObject(body.fault)) {
|
|
78
|
+
const faultMessage = body.fault.message;
|
|
79
|
+
if (typeof faultMessage === "string" && faultMessage.trim() !== "") {
|
|
80
|
+
return faultMessage;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ocapiFallbackMessage(status);
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Read boundary
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Recognize a Zod/schema failure without matching on the exception message.
|
|
90
|
+
*
|
|
91
|
+
* `instanceof` covers the normal case. The structural fallback covers a
|
|
92
|
+
* ZodError produced by a second copy of Zod (a duplicated dependency or a
|
|
93
|
+
* module-mocked import), where the prototype chain differs but the contract
|
|
94
|
+
* — a `ZodError` name plus an `issues` array — does not.
|
|
95
|
+
*/
|
|
96
|
+
function isSchemaFailure(err) {
|
|
97
|
+
if (err instanceof z.ZodError)
|
|
98
|
+
return true;
|
|
99
|
+
return (typeof err === "object" &&
|
|
100
|
+
err !== null &&
|
|
101
|
+
err.name === "ZodError" &&
|
|
102
|
+
Array.isArray(err.issues));
|
|
103
|
+
}
|
|
104
|
+
/** Reduce Zod issues to field path + code + message; nothing else is disclosed. */
|
|
105
|
+
function safeZodIssues(err) {
|
|
106
|
+
const issues = Array.isArray(err.issues) ? err.issues : [];
|
|
107
|
+
return issues.map((issue) => ({
|
|
108
|
+
path: Array.isArray(issue.path) ? issue.path.map(String).join(".") : "",
|
|
109
|
+
code: typeof issue.code === "string" ? issue.code : "invalid",
|
|
110
|
+
message: typeof issue.message === "string" ? issue.message : "Invalid input.",
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap a read tool callback so no failure escapes as a raw throw.
|
|
115
|
+
*
|
|
116
|
+
* This is the outermost project-owned callback for every SFCC read tool. It
|
|
117
|
+
* converts:
|
|
118
|
+
*
|
|
119
|
+
* - a recognized Zod/schema failure → `VALIDATION_ERROR` (400, source `tool`)
|
|
120
|
+
* - any other thrown value → `INTERNAL_ERROR` (500, source `tool`)
|
|
121
|
+
*
|
|
122
|
+
* A result the inner handler *returns* — success or an already-formatted
|
|
123
|
+
* failure — is passed through untouched, so a gate rejection keeps its
|
|
124
|
+
* `NOT_CONFIGURED` code rather than being reclassified.
|
|
125
|
+
*
|
|
126
|
+
* Note on ordering: the MCP SDK validates `inputSchema` before invoking this
|
|
127
|
+
* callback and turns a rejection into its own `isError: true` result, so the
|
|
128
|
+
* transport path is already a tool result rather than a transport error. This
|
|
129
|
+
* boundary covers the paths the SDK does not: the handler's own `schema.parse`,
|
|
130
|
+
* in-process dispatch through `TOOL_HANDLERS` (which bypasses SDK validation
|
|
131
|
+
* entirely), and every unexpected throw from the gate, the OCAPI client, or the
|
|
132
|
+
* output seam.
|
|
133
|
+
*/
|
|
134
|
+
export function withSfccReadErrorBoundary(handler) {
|
|
135
|
+
return async (...args) => {
|
|
136
|
+
try {
|
|
137
|
+
return await handler(...args);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (isSchemaFailure(err)) {
|
|
141
|
+
return formatSfccFailure({
|
|
142
|
+
code: "VALIDATION_ERROR",
|
|
143
|
+
status: 400,
|
|
144
|
+
message: SFCC_VALIDATION_ERROR_MESSAGE,
|
|
145
|
+
source: "tool",
|
|
146
|
+
details: { issues: safeZodIssues(err) },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return formatSfccFailure({
|
|
150
|
+
code: "INTERNAL_ERROR",
|
|
151
|
+
status: 500,
|
|
152
|
+
message: SFCC_INTERNAL_ERROR_MESSAGE,
|
|
153
|
+
source: "tool",
|
|
154
|
+
details: {},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -11,16 +11,32 @@
|
|
|
11
11
|
* OCAPI's CustomObjectDefinitions resource only exposes operations scoped to
|
|
12
12
|
* a known object_type — there is no top-level list and no bare get-by-type
|
|
13
13
|
* (unlike SystemObjectDefinitions). A caller-supplied object_type is always
|
|
14
|
-
* required; OCAPI cannot enumerate custom object
|
|
14
|
+
* required; OCAPI cannot enumerate custom object type IDs directly.
|
|
15
|
+
*
|
|
16
|
+
* That is narrower than "wholly undiscoverable," and the tools below say so:
|
|
17
|
+
* `system_object_list` at `projection: "full"` returns a distinct
|
|
18
|
+
* `display_name` and `attribute_definition_count` per custom type, from which a
|
|
19
|
+
* candidate ID can be derived (e.g. strip spaces from "Product Quality Result"
|
|
20
|
+
* → "ProductQualityResult") and confirmed by checking that this module's own
|
|
21
|
+
* `custom_object_definition_attributes_get` returns an attribute count matching
|
|
22
|
+
* that row's `attribute_definition_count`. This adds no new tool and no new
|
|
23
|
+
* projection value — it is existing capability, stated accurately.
|
|
15
24
|
*
|
|
16
25
|
* Note: custom object TYPE creation/update is NOT available via OCAPI REST.
|
|
17
26
|
* It is a v2 metadata-import capability (metadata XML → WebDAV → import job).
|
|
27
|
+
*
|
|
28
|
+
* Both tools can return complete attribute-definition documents at
|
|
29
|
+
* `projection: "full"` (BAPI-767). Bridge withholds each attribute's
|
|
30
|
+
* `default_value` from that response (BAPI-769, `read-body.ts`) — every other
|
|
31
|
+
* schema field (type, flags, labels) is unaffected.
|
|
18
32
|
*/
|
|
19
33
|
import path from "path";
|
|
20
34
|
import { z } from "zod";
|
|
21
35
|
import { ocapiGet, ocapiPost } from "./client.js";
|
|
22
36
|
import { withSfccGate } from "./tool-wrapper.js";
|
|
23
|
-
import {
|
|
37
|
+
import { formatOcapiReadFailure, withSfccReadErrorBoundary } from "./read-result.js";
|
|
38
|
+
import { readSfccBody } from "./read-body.js";
|
|
39
|
+
import { applyProjectionToBody, projectionInputFor, projectionQueryEntries, } from "./read-projection.js";
|
|
24
40
|
import { truncateAndSaveIfNeeded } from "./output.js";
|
|
25
41
|
// ---------------------------------------------------------------------------
|
|
26
42
|
// Annotations (SPEC: read-only against developer sandboxes)
|
|
@@ -34,22 +50,23 @@ const READ_ANNOTATIONS = {
|
|
|
34
50
|
// ---------------------------------------------------------------------------
|
|
35
51
|
// Input schemas
|
|
36
52
|
// ---------------------------------------------------------------------------
|
|
53
|
+
const OBJECT_TYPE_DISCOVERY_HINT = 'Known custom object type id (e.g. starting with "c_"). OCAPI cannot enumerate ' +
|
|
54
|
+
"type IDs directly, but you can discover a candidate via system_object_list at " +
|
|
55
|
+
"projection=\"full\" (distinct display_name + attribute_definition_count per " +
|
|
56
|
+
"custom type) and confirm it by checking that this tool's returned attribute " +
|
|
57
|
+
"count matches that row's attribute_definition_count.";
|
|
37
58
|
const customObjectAttributesGetInput = z.object({
|
|
38
|
-
object_type: z
|
|
39
|
-
|
|
40
|
-
.describe('Known custom object type identifier, e.g. a custom type id starting with "c_". ' +
|
|
41
|
-
"OCAPI cannot enumerate custom object types — the type must already be known."),
|
|
59
|
+
object_type: z.string().describe(OBJECT_TYPE_DISCOVERY_HINT),
|
|
60
|
+
projection: projectionInputFor("attribute_collection"),
|
|
42
61
|
});
|
|
43
62
|
const customObjectAttributeSearchInput = z.object({
|
|
44
|
-
object_type: z
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"OCAPI cannot enumerate custom object types — the type must already be known."),
|
|
48
|
-
query: z.union([z.string(), z.record(z.string(), z.any())]).describe("Search query. Pass a plain string for a text search across id and display_name, " +
|
|
49
|
-
"or a structured OCAPI query object (term_query, filtered_query, etc.)."),
|
|
63
|
+
object_type: z.string().describe(OBJECT_TYPE_DISCOVERY_HINT),
|
|
64
|
+
query: z.union([z.string(), z.record(z.string(), z.any())]).describe("Search query. A plain string is a case-insensitive substring match across " +
|
|
65
|
+
"id and display_name; a structured OCAPI query object passes through unchanged."),
|
|
50
66
|
start: z.number().optional().describe("Zero-based offset for paging."),
|
|
51
67
|
count: z.number().optional().describe("Maximum number of results to return."),
|
|
52
68
|
sorts: z.array(z.any()).optional().describe("Array of OCAPI sort descriptors."),
|
|
69
|
+
projection: projectionInputFor("attribute_search"),
|
|
53
70
|
});
|
|
54
71
|
// ---------------------------------------------------------------------------
|
|
55
72
|
// Helpers
|
|
@@ -63,30 +80,34 @@ function safeType(objectType) {
|
|
|
63
80
|
function textResult(text) {
|
|
64
81
|
return { content: [{ type: "text", text }] };
|
|
65
82
|
}
|
|
66
|
-
async function saveAndReturn(text, dir, filename) {
|
|
67
|
-
const output = await truncateAndSaveIfNeeded(text, dir, filename);
|
|
83
|
+
async function saveAndReturn(text, dir, filename, page) {
|
|
84
|
+
const output = await truncateAndSaveIfNeeded(text, dir, filename, page);
|
|
68
85
|
return textResult(output);
|
|
69
86
|
}
|
|
70
87
|
// ---------------------------------------------------------------------------
|
|
71
88
|
// Handlers
|
|
72
89
|
// ---------------------------------------------------------------------------
|
|
73
90
|
function buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir) {
|
|
74
|
-
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
75
|
-
const { object_type } = customObjectAttributesGetInput.parse(args);
|
|
91
|
+
return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
|
|
92
|
+
const { object_type, projection } = customObjectAttributesGetInput.parse(args);
|
|
76
93
|
const encodedType = encodeURIComponent(object_type);
|
|
77
|
-
const
|
|
94
|
+
const queryEntries = projectionQueryEntries("attribute_collection", projection);
|
|
95
|
+
const queryStr = Object.keys(queryEntries).length > 0
|
|
96
|
+
? "?" + new URLSearchParams(queryEntries).toString()
|
|
97
|
+
: "";
|
|
98
|
+
const result = await ocapiGet(`/custom_object_definitions/${encodedType}/attribute_definitions${queryStr}`, credentials);
|
|
78
99
|
if (!result.ok) {
|
|
79
|
-
return
|
|
100
|
+
return formatOcapiReadFailure(result);
|
|
80
101
|
}
|
|
81
|
-
const normalized =
|
|
102
|
+
const normalized = readSfccBody(result.body);
|
|
82
103
|
const text = JSON.stringify(normalized, null, 2);
|
|
83
104
|
const dir = path.join(await getDocsDir(), "sfcc");
|
|
84
|
-
return saveAndReturn(text, dir, `custom-object-def-attributes-${safeType(object_type)}-${safeTimestamp()}.json
|
|
85
|
-
});
|
|
105
|
+
return saveAndReturn(text, dir, `custom-object-def-attributes-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
|
|
106
|
+
}));
|
|
86
107
|
}
|
|
87
108
|
function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir) {
|
|
88
|
-
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
89
|
-
const { object_type, query, start, count, sorts } = customObjectAttributeSearchInput.parse(args);
|
|
109
|
+
return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
|
|
110
|
+
const { object_type, query, start, count, sorts, projection } = customObjectAttributeSearchInput.parse(args);
|
|
90
111
|
const encodedType = encodeURIComponent(object_type);
|
|
91
112
|
// Coerce plain string queries into OCAPI text_query shape
|
|
92
113
|
const resolvedQuery = typeof query === "string"
|
|
@@ -99,15 +120,16 @@ function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir) {
|
|
|
99
120
|
postBody.count = count;
|
|
100
121
|
if (sorts !== undefined)
|
|
101
122
|
postBody.sorts = sorts;
|
|
123
|
+
applyProjectionToBody(postBody, "attribute_search", projection);
|
|
102
124
|
const result = await ocapiPost(`/custom_object_definitions/${encodedType}/attribute_definition_search`, postBody, credentials);
|
|
103
125
|
if (!result.ok) {
|
|
104
|
-
return
|
|
126
|
+
return formatOcapiReadFailure(result);
|
|
105
127
|
}
|
|
106
|
-
const normalized =
|
|
128
|
+
const normalized = readSfccBody(result.body);
|
|
107
129
|
const text = JSON.stringify(normalized, null, 2);
|
|
108
130
|
const dir = path.join(await getDocsDir(), "sfcc");
|
|
109
|
-
return saveAndReturn(text, dir, `custom-object-def-search-${safeType(object_type)}-${safeTimestamp()}.json
|
|
110
|
-
});
|
|
131
|
+
return saveAndReturn(text, dir, `custom-object-def-search-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
|
|
132
|
+
}));
|
|
111
133
|
}
|
|
112
134
|
/**
|
|
113
135
|
* Register the two SFCC custom-object-definition introspection read tools.
|
|
@@ -118,18 +140,19 @@ export function registerSfccCustomObjectDefReadTools(registerTool, deps) {
|
|
|
118
140
|
const { gateDeps, getDocsDir } = deps;
|
|
119
141
|
registerTool("custom_object_definition_attributes_get", {
|
|
120
142
|
description: "Retrieve attribute definitions for a KNOWN custom object type via " +
|
|
121
|
-
"GET /custom_object_definitions/{type}/attribute_definitions
|
|
122
|
-
"OCAPI cannot enumerate
|
|
123
|
-
"
|
|
143
|
+
"GET /custom_object_definitions/{type}/attribute_definitions " +
|
|
144
|
+
"(default_value withheld). OCAPI cannot enumerate type IDs directly; " +
|
|
145
|
+
"derive one via system_object_list (projection=full, attribute_definition_count), " +
|
|
146
|
+
"confirm here. Type creation is v2 metadata-import only.",
|
|
124
147
|
inputSchema: customObjectAttributesGetInput,
|
|
125
148
|
annotations: READ_ANNOTATIONS,
|
|
126
149
|
}, buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir));
|
|
127
150
|
registerTool("custom_object_definition_attribute_search", {
|
|
128
151
|
description: "Search attribute definitions within a KNOWN custom object type via " +
|
|
129
|
-
"POST /custom_object_definitions/{type}/attribute_definition_search
|
|
130
|
-
"OCAPI cannot enumerate
|
|
131
|
-
"
|
|
132
|
-
"
|
|
152
|
+
"POST /custom_object_definitions/{type}/attribute_definition_search " +
|
|
153
|
+
"(default_value withheld). OCAPI cannot enumerate type IDs directly; " +
|
|
154
|
+
"derive one via system_object_list (projection=full, attribute_definition_count), " +
|
|
155
|
+
"confirm here. Type creation is v2 metadata-import only.",
|
|
133
156
|
inputSchema: customObjectAttributeSearchInput,
|
|
134
157
|
annotations: READ_ANNOTATIONS,
|
|
135
158
|
}, buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir));
|