@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.
Files changed (92) hide show
  1. package/README.md +48 -8
  2. package/build/base-url.js +79 -0
  3. package/build/bridge-api-urls.js +9 -0
  4. package/build/chain-orchestrator.js +93 -15
  5. package/build/claude-user-config-doctor.js +317 -0
  6. package/build/commands.generated.js +2 -1
  7. package/build/conductor/bridge-api-client.js +178 -4
  8. package/build/conductor-bin.js +1 -1
  9. package/build/conductor-bundle-artifacts.js +7 -6
  10. package/build/credential-store.js +205 -4
  11. package/build/direct-ticket-tools.js +70 -0
  12. package/build/doctor.js +239 -80
  13. package/build/executor/cli.js +51 -1
  14. package/build/executor/credentials.js +1 -7
  15. package/build/executor/deps.js +18 -1
  16. package/build/executor/env.js +51 -25
  17. package/build/executor/heartbeat.js +138 -17
  18. package/build/executor/http-client.js +49 -8
  19. package/build/executor/job-errors.js +4 -0
  20. package/build/executor/job-runner.js +422 -22
  21. package/build/executor/observation.js +130 -0
  22. package/build/executor/permissions.js +104 -8
  23. package/build/executor/preflight.js +32 -0
  24. package/build/executor/runner.js +8 -0
  25. package/build/executor/test-clock.js +67 -3
  26. package/build/executor/types.js +4 -1
  27. package/build/executor/worker-command.js +11 -3
  28. package/build/executor/worker-config-isolation.js +287 -0
  29. package/build/executor/worker-finalization.js +68 -14
  30. package/build/executor/worktree.js +46 -4
  31. package/build/index.js +614 -244
  32. package/build/init.js +363 -73
  33. package/build/install-bridge.js +568 -80
  34. package/build/launcher-config-inspection.js +351 -0
  35. package/build/mcp-invoke.js +49 -6
  36. package/build/mcp-provisioning.js +30 -7
  37. package/build/mcp-registration-doctor.js +14 -5
  38. package/build/notifications.js +553 -0
  39. package/build/pipeline-orchestrator.js +146 -4
  40. package/build/pipeline-utils.js +3 -0
  41. package/build/pipelines.generated.js +22 -9
  42. package/build/plan-execution-ledger.js +550 -0
  43. package/build/plan-phase-routing.js +272 -0
  44. package/build/plane/alembic-head.js +110 -0
  45. package/build/plane/build-freshness.js +167 -0
  46. package/build/plane/cli.js +480 -0
  47. package/build/plane/defaults.js +266 -0
  48. package/build/plane/manifest.js +377 -0
  49. package/build/plane/member-logs.js +147 -0
  50. package/build/plane/member-roster.js +147 -0
  51. package/build/plane/preflight.js +289 -0
  52. package/build/plane/shutdown.js +195 -0
  53. package/build/plane/status.js +125 -0
  54. package/build/plane/supervisor.js +569 -0
  55. package/build/plane/test-fakes.js +156 -0
  56. package/build/plane/types.js +75 -0
  57. package/build/readme.generated.js +1 -1
  58. package/build/run-unit-tests-launcher.js +2 -0
  59. package/build/setup-epic.js +662 -27
  60. package/build/sfcc/log-gate.js +38 -11
  61. package/build/sfcc/log-query.js +55 -15
  62. package/build/sfcc/ocapi-shape.js +70 -14
  63. package/build/sfcc/output.js +41 -11
  64. package/build/sfcc/permissions.js +24 -2
  65. package/build/sfcc/read-body.js +92 -0
  66. package/build/sfcc/read-projection.js +185 -0
  67. package/build/sfcc/read-result.js +158 -0
  68. package/build/sfcc/reads-custom-object-def.js +57 -34
  69. package/build/sfcc/reads-site-preference.js +86 -33
  70. package/build/sfcc/reads-system-object.js +50 -38
  71. package/build/sfcc/sfcc-result.js +106 -0
  72. package/build/sfcc/tool-wrapper.js +56 -13
  73. package/build/sfcc/write-grants.js +45 -22
  74. package/build/sfcc/write-guard.js +21 -13
  75. package/build/sfcc/write-result.js +71 -15
  76. package/build/sfcc/write-tool-common.js +126 -32
  77. package/build/sfcc/writes-custom-object-def.js +6 -2
  78. package/build/sfcc/writes-system-object.js +11 -50
  79. package/build/start-tickets-prereqs.js +129 -0
  80. package/build/start-tickets.js +17 -13
  81. package/build/ticket-backend-metadata.js +59 -0
  82. package/build/ticket-key-utils.js +92 -0
  83. package/build/tool-error-envelope.js +71 -0
  84. package/build/tool-surface-gating.js +72 -0
  85. package/build/update-status.js +102 -0
  86. package/build/upgrade-advice.js +47 -0
  87. package/build/upgrade-cli.js +417 -101
  88. package/build/version.generated.js +1 -1
  89. package/build/worktree-core.js +73 -0
  90. package/docs/CONDUCTOR.md +23 -8
  91. package/package.json +3 -3
  92. 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 types.
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 { normalizeOcapiBody } from "./ocapi-shape.js";
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
- .string()
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
- .string()
46
- .describe('Known custom object type to search within, e.g. a custom type id starting with "c_". ' +
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 result = await ocapiGet(`/custom_object_definitions/${encodedType}/attribute_definitions`, credentials);
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 textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
100
+ return formatOcapiReadFailure(result);
80
101
  }
81
- const normalized = normalizeOcapiBody(result.body);
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 textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
126
+ return formatOcapiReadFailure(result);
105
127
  }
106
- const normalized = normalizeOcapiBody(result.body);
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 types object_type is required. " +
123
- "Type creation is v2 metadata-import only, not OCAPI. Oversized payloads auto-saved locally.",
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 types object_type is required. " +
131
- "Pass a string or structured OCAPI query. Type creation is v2 metadata-import only. " +
132
- "Oversized results auto-saved locally.",
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));