@bridge_gpt/mcp-server 0.2.36 → 0.2.37

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 (90) hide show
  1. package/README.md +44 -6
  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 +51 -14
  63. package/build/sfcc/output.js +41 -11
  64. package/build/sfcc/permissions.js +24 -2
  65. package/build/sfcc/read-projection.js +181 -0
  66. package/build/sfcc/read-result.js +158 -0
  67. package/build/sfcc/reads-custom-object-def.js +29 -18
  68. package/build/sfcc/reads-site-preference.js +75 -29
  69. package/build/sfcc/reads-system-object.js +40 -34
  70. package/build/sfcc/sfcc-result.js +106 -0
  71. package/build/sfcc/tool-wrapper.js +56 -13
  72. package/build/sfcc/write-grants.js +45 -22
  73. package/build/sfcc/write-guard.js +21 -13
  74. package/build/sfcc/write-result.js +61 -14
  75. package/build/sfcc/write-tool-common.js +126 -32
  76. package/build/sfcc/writes-system-object.js +11 -50
  77. package/build/start-tickets-prereqs.js +129 -0
  78. package/build/start-tickets.js +17 -13
  79. package/build/ticket-backend-metadata.js +59 -0
  80. package/build/ticket-key-utils.js +92 -0
  81. package/build/tool-error-envelope.js +71 -0
  82. package/build/tool-surface-gating.js +72 -0
  83. package/build/update-status.js +102 -0
  84. package/build/upgrade-advice.js +47 -0
  85. package/build/upgrade-cli.js +417 -101
  86. package/build/version.generated.js +1 -1
  87. package/build/worktree-core.js +73 -0
  88. package/docs/CONDUCTOR.md +23 -8
  89. package/package.json +3 -3
  90. package/pipelines/implement-ticket.json +15 -5
@@ -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
+ }
@@ -20,7 +20,9 @@ import path from "path";
20
20
  import { z } from "zod";
21
21
  import { ocapiGet, ocapiPost } from "./client.js";
22
22
  import { withSfccGate } from "./tool-wrapper.js";
23
+ import { formatOcapiReadFailure, withSfccReadErrorBoundary } from "./read-result.js";
23
24
  import { normalizeOcapiBody } from "./ocapi-shape.js";
25
+ import { applyProjectionToBody, projectionInputFor, projectionQueryEntries, } from "./read-projection.js";
24
26
  import { truncateAndSaveIfNeeded } from "./output.js";
25
27
  // ---------------------------------------------------------------------------
26
28
  // Annotations (SPEC: read-only against developer sandboxes)
@@ -39,17 +41,19 @@ const customObjectAttributesGetInput = z.object({
39
41
  .string()
40
42
  .describe('Known custom object type identifier, e.g. a custom type id starting with "c_". ' +
41
43
  "OCAPI cannot enumerate custom object types — the type must already be known."),
44
+ projection: projectionInputFor("attribute_collection"),
42
45
  });
43
46
  const customObjectAttributeSearchInput = z.object({
44
47
  object_type: z
45
48
  .string()
46
49
  .describe('Known custom object type to search within, e.g. a custom type id starting with "c_". ' +
47
50
  "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.)."),
51
+ query: z.union([z.string(), z.record(z.string(), z.any())]).describe("Search query. A plain string is a case-insensitive substring match across " +
52
+ "id and display_name; a structured OCAPI query object passes through unchanged."),
50
53
  start: z.number().optional().describe("Zero-based offset for paging."),
51
54
  count: z.number().optional().describe("Maximum number of results to return."),
52
55
  sorts: z.array(z.any()).optional().describe("Array of OCAPI sort descriptors."),
56
+ projection: projectionInputFor("attribute_search"),
53
57
  });
54
58
  // ---------------------------------------------------------------------------
55
59
  // Helpers
@@ -63,30 +67,34 @@ function safeType(objectType) {
63
67
  function textResult(text) {
64
68
  return { content: [{ type: "text", text }] };
65
69
  }
66
- async function saveAndReturn(text, dir, filename) {
67
- const output = await truncateAndSaveIfNeeded(text, dir, filename);
70
+ async function saveAndReturn(text, dir, filename, page) {
71
+ const output = await truncateAndSaveIfNeeded(text, dir, filename, page);
68
72
  return textResult(output);
69
73
  }
70
74
  // ---------------------------------------------------------------------------
71
75
  // Handlers
72
76
  // ---------------------------------------------------------------------------
73
77
  function buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir) {
74
- return withSfccGate(gateDeps, async (args, credentials) => {
75
- const { object_type } = customObjectAttributesGetInput.parse(args);
78
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
79
+ const { object_type, projection } = customObjectAttributesGetInput.parse(args);
76
80
  const encodedType = encodeURIComponent(object_type);
77
- const result = await ocapiGet(`/custom_object_definitions/${encodedType}/attribute_definitions`, credentials);
81
+ const queryEntries = projectionQueryEntries("attribute_collection", projection);
82
+ const queryStr = Object.keys(queryEntries).length > 0
83
+ ? "?" + new URLSearchParams(queryEntries).toString()
84
+ : "";
85
+ const result = await ocapiGet(`/custom_object_definitions/${encodedType}/attribute_definitions${queryStr}`, credentials);
78
86
  if (!result.ok) {
79
- return textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
87
+ return formatOcapiReadFailure(result);
80
88
  }
81
89
  const normalized = normalizeOcapiBody(result.body);
82
90
  const text = JSON.stringify(normalized, null, 2);
83
91
  const dir = path.join(await getDocsDir(), "sfcc");
84
- return saveAndReturn(text, dir, `custom-object-def-attributes-${safeType(object_type)}-${safeTimestamp()}.json`);
85
- });
92
+ return saveAndReturn(text, dir, `custom-object-def-attributes-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
93
+ }));
86
94
  }
87
95
  function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir) {
88
- return withSfccGate(gateDeps, async (args, credentials) => {
89
- const { object_type, query, start, count, sorts } = customObjectAttributeSearchInput.parse(args);
96
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
97
+ const { object_type, query, start, count, sorts, projection } = customObjectAttributeSearchInput.parse(args);
90
98
  const encodedType = encodeURIComponent(object_type);
91
99
  // Coerce plain string queries into OCAPI text_query shape
92
100
  const resolvedQuery = typeof query === "string"
@@ -99,15 +107,16 @@ function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir) {
99
107
  postBody.count = count;
100
108
  if (sorts !== undefined)
101
109
  postBody.sorts = sorts;
110
+ applyProjectionToBody(postBody, "attribute_search", projection);
102
111
  const result = await ocapiPost(`/custom_object_definitions/${encodedType}/attribute_definition_search`, postBody, credentials);
103
112
  if (!result.ok) {
104
- return textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
113
+ return formatOcapiReadFailure(result);
105
114
  }
106
115
  const normalized = normalizeOcapiBody(result.body);
107
116
  const text = JSON.stringify(normalized, null, 2);
108
117
  const dir = path.join(await getDocsDir(), "sfcc");
109
- return saveAndReturn(text, dir, `custom-object-def-search-${safeType(object_type)}-${safeTimestamp()}.json`);
110
- });
118
+ return saveAndReturn(text, dir, `custom-object-def-search-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
119
+ }));
111
120
  }
112
121
  /**
113
122
  * Register the two SFCC custom-object-definition introspection read tools.
@@ -119,16 +128,18 @@ export function registerSfccCustomObjectDefReadTools(registerTool, deps) {
119
128
  registerTool("custom_object_definition_attributes_get", {
120
129
  description: "Retrieve attribute definitions for a KNOWN custom object type via " +
121
130
  "GET /custom_object_definitions/{type}/attribute_definitions. " +
131
+ "Returns each attribute's full schema by default. " +
122
132
  "OCAPI cannot enumerate types — object_type is required. " +
123
- "Type creation is v2 metadata-import only, not OCAPI. Oversized payloads auto-saved locally.",
133
+ "Type creation is v2 metadata-import only. Oversized payloads auto-saved locally.",
124
134
  inputSchema: customObjectAttributesGetInput,
125
135
  annotations: READ_ANNOTATIONS,
126
136
  }, buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir));
127
137
  registerTool("custom_object_definition_attribute_search", {
128
138
  description: "Search attribute definitions within a KNOWN custom object type via " +
129
139
  "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. " +
140
+ "Returns each attribute's full schema by default. " +
141
+ "OCAPI cannot enumerate types; object_type is required. " +
142
+ "Pass a string or OCAPI query. Type creation is v2 metadata-import only. " +
132
143
  "Oversized results auto-saved locally.",
133
144
  inputSchema: customObjectAttributeSearchInput,
134
145
  annotations: READ_ANNOTATIONS,
@@ -9,6 +9,27 @@
9
9
  * All three tools are read-only, run behind the T1 call-time gate, and route
10
10
  * oversized payloads through the sfcc/output.ts truncate-and-save seam.
11
11
  *
12
+ * PREFERENCE VALUES ARE NOT AVAILABLE HERE (BAPI-767).
13
+ * `preference_search` returns hits shaped `{_type, id}` and nothing more.
14
+ * Verified live against a developer sandbox (OCAPI v25_6): supplying
15
+ * `?select=(**)` changes the response not at all — zero hits gain a `value`
16
+ * key. That is why these two tools take no `projection` input while
17
+ * `site_preference_group_list` does: a projection that cannot alter the
18
+ * response is a flag with no observable effect.
19
+ *
20
+ * The consequence is a genuine caller-facing limitation, stated in both tool
21
+ * descriptions rather than left to be discovered: an ids-only read cannot
22
+ * distinguish "preference not set" from "preference set to the empty string".
23
+ *
24
+ * It is also, for now, accidentally protective. A name-heuristic scan of all 19
25
+ * preference groups on the tested sandbox found secret-named preferences in six
26
+ * of them — including one literally named `client_id` — and none of them expose
27
+ * a value through this endpoint. No redaction seam is built here because there
28
+ * is no value-bearing path to redact. If OCAPI ever starts returning values,
29
+ * redaction must be added on the raw body BEFORE normalization, serialization,
30
+ * truncation, or save, so one placement covers both the inline response and the
31
+ * docs/tmp/sfcc/ save path.
32
+ *
12
33
  * NOTE: SCAPI Preferences API (sfcc.preferences) is intentionally NOT used.
13
34
  * This keeps the tool context on the OCAPI Data API, preserving the single
14
35
  * AM-OAuth boundary established in T1. Do not "modernize" these tools onto
@@ -18,7 +39,9 @@ import path from "path";
18
39
  import { z } from "zod";
19
40
  import { ocapiGet, ocapiPost } from "./client.js";
20
41
  import { withSfccGate } from "./tool-wrapper.js";
42
+ import { formatOcapiReadFailure, formatSfccReadFailure, withSfccReadErrorBoundary, } from "./read-result.js";
21
43
  import { normalizeOcapiBody } from "./ocapi-shape.js";
44
+ import { projectionInputFor, projectionQueryEntries } from "./read-projection.js";
22
45
  import { truncateAndSaveIfNeeded } from "./output.js";
23
46
  // ---------------------------------------------------------------------------
24
47
  // Annotations (SPEC: read-only against developer sandboxes)
@@ -53,6 +76,7 @@ const sitePreferenceSearchInput = z.object({
53
76
  const sitePreferenceGroupListInput = z.object({
54
77
  count: z.number().optional().describe("Maximum number of site preference groups to return."),
55
78
  start: z.number().optional().describe("Zero-based offset for paging."),
79
+ projection: projectionInputFor("broad_list"),
56
80
  });
57
81
  // ---------------------------------------------------------------------------
58
82
  // Helpers
@@ -66,18 +90,37 @@ function safeGroup(group) {
66
90
  function textResult(text) {
67
91
  return { content: [{ type: "text", text }] };
68
92
  }
69
- async function saveAndReturn(text, dir, filename) {
70
- const output = await truncateAndSaveIfNeeded(text, dir, filename);
93
+ async function saveAndReturn(text, dir, filename, page) {
94
+ const output = await truncateAndSaveIfNeeded(text, dir, filename, page);
71
95
  return textResult(output);
72
96
  }
73
- // D-5: reject any non-sandbox instance context (safer than silently proceeding)
97
+ /**
98
+ * D-5: reject any non-sandbox instance context (safer than silently proceeding).
99
+ *
100
+ * The rejection decision is unchanged; BAPI-759 moved its representation to the
101
+ * shared formatter, so the guardrail fails at the protocol level like every
102
+ * other SFCC failure. Details carry non-sensitive guard metadata only — the
103
+ * rejected value is caller-supplied and enum-constrained.
104
+ *
105
+ * BAPI-766 reworded the message only. It used to open with "v1", which reads as
106
+ * an API version while OCAPI simultaneously reports `_v: "25.6"` in the same
107
+ * session; the constraint belongs to this Bridge tool, not to a version. The
108
+ * status, code, source, decision, and `details` payload are untouched.
109
+ */
74
110
  function rejectIfNotSandbox(instance) {
75
111
  if (instance !== "sandbox") {
76
- return textResult(JSON.stringify({
77
- error: "VALIDATION_ERROR",
112
+ return formatSfccReadFailure({
113
+ code: "VALIDATION_ERROR",
78
114
  status: 400,
79
- message: `v1 only supports the 'sandbox' instance context. Received: '${instance}'.`,
80
- }, null, 2));
115
+ message: `The Bridge site-preference tool only supports the 'sandbox' instance ` +
116
+ `context. Received: '${instance}'.`,
117
+ source: "tool",
118
+ details: {
119
+ guard: "sandbox_only",
120
+ received_instance: instance,
121
+ supported_instances: ["sandbox"],
122
+ },
123
+ });
81
124
  }
82
125
  return null;
83
126
  }
@@ -85,7 +128,7 @@ function rejectIfNotSandbox(instance) {
85
128
  // Handlers
86
129
  // ---------------------------------------------------------------------------
87
130
  export function buildSitePreferenceGetHandler(gateDeps, getDocsDir) {
88
- return withSfccGate(gateDeps, async (args, credentials) => {
131
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
89
132
  const { group, instance, start, count } = sitePreferenceGetInput.parse(args);
90
133
  const guard = rejectIfNotSandbox(instance);
91
134
  if (guard)
@@ -100,16 +143,16 @@ export function buildSitePreferenceGetHandler(gateDeps, getDocsDir) {
100
143
  postBody.count = count;
101
144
  const result = await ocapiPost(`/site_preferences/preference_groups/${encodedGroup}/${instance}/preference_search`, postBody, credentials);
102
145
  if (!result.ok) {
103
- return textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
146
+ return formatOcapiReadFailure(result);
104
147
  }
105
148
  const normalized = normalizeOcapiBody(result.body);
106
149
  const text = JSON.stringify(normalized, null, 2);
107
150
  const dir = path.join(await getDocsDir(), "sfcc");
108
- return saveAndReturn(text, dir, `site-preference-get-${safeGroup(group)}-${safeTimestamp()}.json`);
109
- });
151
+ return saveAndReturn(text, dir, `site-preference-get-${safeGroup(group)}-${safeTimestamp()}.json`, normalized.page);
152
+ }));
110
153
  }
111
154
  export function buildSitePreferenceSearchHandler(gateDeps, getDocsDir) {
112
- return withSfccGate(gateDeps, async (args, credentials) => {
155
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
113
156
  const { group, instance, query, start, count, sorts } = sitePreferenceSearchInput.parse(args);
114
157
  const guard = rejectIfNotSandbox(instance);
115
158
  if (guard)
@@ -128,18 +171,20 @@ export function buildSitePreferenceSearchHandler(gateDeps, getDocsDir) {
128
171
  postBody.sorts = sorts;
129
172
  const result = await ocapiPost(`/site_preferences/preference_groups/${encodedGroup}/${instance}/preference_search`, postBody, credentials);
130
173
  if (!result.ok) {
131
- return textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
174
+ return formatOcapiReadFailure(result);
132
175
  }
133
176
  const normalized = normalizeOcapiBody(result.body);
134
177
  const text = JSON.stringify(normalized, null, 2);
135
178
  const dir = path.join(await getDocsDir(), "sfcc");
136
- return saveAndReturn(text, dir, `site-preference-search-${safeGroup(group)}-${safeTimestamp()}.json`);
137
- });
179
+ return saveAndReturn(text, dir, `site-preference-search-${safeGroup(group)}-${safeTimestamp()}.json`, normalized.page);
180
+ }));
138
181
  }
139
182
  export function buildSitePreferenceGroupListHandler(gateDeps, getDocsDir) {
140
- return withSfccGate(gateDeps, async (args, credentials) => {
141
- const { count, start } = sitePreferenceGroupListInput.parse(args);
142
- const queryParams = {};
183
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
184
+ const { count, start, projection } = sitePreferenceGroupListInput.parse(args);
185
+ const queryParams = {
186
+ ...projectionQueryEntries("broad_list", projection),
187
+ };
143
188
  if (count !== undefined)
144
189
  queryParams.count = String(count);
145
190
  if (start !== undefined)
@@ -149,13 +194,13 @@ export function buildSitePreferenceGroupListHandler(gateDeps, getDocsDir) {
149
194
  : "";
150
195
  const result = await ocapiGet(`/system_object_definitions/SitePreferences/attribute_groups${queryStr}`, credentials);
151
196
  if (!result.ok) {
152
- return textResult(JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2));
197
+ return formatOcapiReadFailure(result);
153
198
  }
154
199
  const normalized = normalizeOcapiBody(result.body);
155
200
  const text = JSON.stringify(normalized, null, 2);
156
201
  const dir = path.join(await getDocsDir(), "sfcc");
157
- return saveAndReturn(text, dir, `site-preference-group-list-${safeTimestamp()}.json`);
158
- });
202
+ return saveAndReturn(text, dir, `site-preference-group-list-${safeTimestamp()}.json`, normalized.page);
203
+ }));
159
204
  }
160
205
  /**
161
206
  * Register the three SFCC site-preference introspection read tools.
@@ -173,18 +218,19 @@ export function registerSitePreferenceTools(registerTool, deps) {
173
218
  annotations: READ_ANNOTATIONS,
174
219
  }, buildSitePreferenceGroupListHandler(gateDeps, getDocsDir));
175
220
  registerTool("site_preference_get", {
176
- description: "Read effective preferences for a site preference group from the developer sandbox " +
177
- "via a match-all POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
178
- "Read-only, sandbox-only. Accepts optional `start`/`count` paging in the POST body. " +
179
- "Oversized payloads are auto-saved locally.",
221
+ description: "List preference IDENTIFIERS for a site preference group from the developer sandbox via a match-all " +
222
+ "POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
223
+ "Returns ids only, NOT values 'unset' and 'empty string' are indistinguishable. " +
224
+ "Read-only, sandbox-only. Optional start/count paging. Oversized payloads auto-saved locally.",
180
225
  inputSchema: sitePreferenceGetInput,
181
226
  annotations: READ_ANNOTATIONS,
182
227
  }, buildSitePreferenceGetHandler(gateDeps, getDocsDir));
183
228
  registerTool("site_preference_search", {
184
- description: "Search/filter preferences within a site preference group from the developer sandbox " +
185
- "via POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
186
- "Read-only; v1 sandbox only. Pass a plain string for id-only text search or a structured " +
187
- "OCAPI query object. Oversized results are auto-saved locally.",
229
+ description: "Search preference IDENTIFIERS within a site preference group via " +
230
+ "POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
231
+ "Returns ids only this endpoint does not expose values. Read-only; v1 sandbox only. " +
232
+ "Pass a plain string for id-only text search or a structured OCAPI query. " +
233
+ "Oversized results auto-saved locally.",
188
234
  inputSchema: sitePreferenceSearchInput,
189
235
  annotations: READ_ANNOTATIONS,
190
236
  }, buildSitePreferenceSearchHandler(gateDeps, getDocsDir));
@@ -13,7 +13,9 @@ import path from "path";
13
13
  import { z } from "zod";
14
14
  import { ocapiGet, ocapiPost } from "./client.js";
15
15
  import { withSfccGate } from "./tool-wrapper.js";
16
+ import { formatOcapiReadFailure, withSfccReadErrorBoundary } from "./read-result.js";
16
17
  import { normalizeOcapiBody } from "./ocapi-shape.js";
18
+ import { applyProjectionToBody, projectionInputFor, projectionQueryEntries, } from "./read-projection.js";
17
19
  import { truncateAndSaveIfNeeded } from "./output.js";
18
20
  // ---------------------------------------------------------------------------
19
21
  // Annotations (SPEC: read-only against developer sandboxes)
@@ -30,19 +32,23 @@ const READ_ANNOTATIONS = {
30
32
  const systemObjectListInput = z.object({
31
33
  count: z.number().optional().describe("Maximum number of system object types to return."),
32
34
  start: z.number().optional().describe("Zero-based offset for paging."),
35
+ projection: projectionInputFor("broad_list"),
33
36
  });
37
+ // No `projection` field: GET /system_object_definitions/{type} already returns
38
+ // its complete document and ignores `select` (verified live — 13 keys with and
39
+ // without it). A flag here would have no observable effect, which is the defect
40
+ // BAPI-760 removed from this same tool. See read-projection.ts.
34
41
  const systemObjectGetInput = z.object({
35
42
  object_type: z.string().describe("System object type identifier, e.g. \"Product\" or \"Order\"."),
36
- expand_attribute_definitions: z.boolean().optional().describe("When true, include the full attribute definition list in the response. " +
37
- "May produce a large payload — auto-saved locally when oversized."),
38
43
  });
39
44
  const systemObjectAttributeSearchInput = z.object({
40
45
  object_type: z.string().describe("System object type to search within, e.g. \"Order\"."),
41
- 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, " +
42
- "or a structured OCAPI query object (term_query, filtered_query, etc.)."),
46
+ query: z.union([z.string(), z.record(z.string(), z.any())]).describe("Search query. A plain string is a case-insensitive substring match across " +
47
+ "id and display_name; a structured OCAPI query object passes through unchanged."),
43
48
  start: z.number().optional().describe("Zero-based offset for paging."),
44
49
  count: z.number().optional().describe("Maximum number of results to return."),
45
50
  sorts: z.array(z.any()).optional().describe("Array of OCAPI sort descriptors."),
51
+ projection: projectionInputFor("attribute_search"),
46
52
  });
47
53
  // ---------------------------------------------------------------------------
48
54
  // Helpers
@@ -56,17 +62,19 @@ function safeType(objectType) {
56
62
  function textResult(text) {
57
63
  return { content: [{ type: "text", text }] };
58
64
  }
59
- async function saveAndReturn(text, dir, filename) {
60
- const output = await truncateAndSaveIfNeeded(text, dir, filename);
65
+ async function saveAndReturn(text, dir, filename, page) {
66
+ const output = await truncateAndSaveIfNeeded(text, dir, filename, page);
61
67
  return textResult(output);
62
68
  }
63
69
  // ---------------------------------------------------------------------------
64
70
  // Handlers
65
71
  // ---------------------------------------------------------------------------
66
72
  function buildSystemObjectListHandler(gateDeps, getDocsDir) {
67
- return withSfccGate(gateDeps, async (args, credentials) => {
68
- const { count, start } = systemObjectListInput.parse(args);
69
- const queryParams = {};
73
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
74
+ const { count, start, projection } = systemObjectListInput.parse(args);
75
+ const queryParams = {
76
+ ...projectionQueryEntries("broad_list", projection),
77
+ };
70
78
  if (count !== undefined)
71
79
  queryParams.count = String(count);
72
80
  if (start !== undefined)
@@ -76,34 +84,31 @@ function buildSystemObjectListHandler(gateDeps, getDocsDir) {
76
84
  : "";
77
85
  const result = await ocapiGet(`/system_object_definitions${queryStr}`, credentials);
78
86
  if (!result.ok) {
79
- return textResult(JSON.stringify({ error: `OCAPI error`, status: result.status, body: result.body }, null, 2));
87
+ return formatOcapiReadFailure(result);
80
88
  }
81
89
  const normalized = normalizeOcapiBody(result.body);
82
90
  const text = JSON.stringify(normalized, null, 2);
83
91
  const dir = path.join(await getDocsDir(), "sfcc");
84
- return saveAndReturn(text, dir, `system-object-list-${safeTimestamp()}.json`);
85
- });
92
+ return saveAndReturn(text, dir, `system-object-list-${safeTimestamp()}.json`, normalized.page);
93
+ }));
86
94
  }
87
95
  function buildSystemObjectGetHandler(gateDeps, getDocsDir) {
88
- return withSfccGate(gateDeps, async (args, credentials) => {
89
- const { object_type, expand_attribute_definitions } = systemObjectGetInput.parse(args);
96
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
97
+ const { object_type } = systemObjectGetInput.parse(args);
90
98
  const encodedType = encodeURIComponent(object_type);
91
- const expandParam = expand_attribute_definitions === true
92
- ? "?expand=attribute_definitions"
93
- : "";
94
- const result = await ocapiGet(`/system_object_definitions/${encodedType}${expandParam}`, credentials);
99
+ const result = await ocapiGet(`/system_object_definitions/${encodedType}`, credentials);
95
100
  if (!result.ok) {
96
- return textResult(JSON.stringify({ error: `OCAPI error`, status: result.status, body: result.body }, null, 2));
101
+ return formatOcapiReadFailure(result);
97
102
  }
98
103
  const normalized = normalizeOcapiBody(result.body);
99
104
  const text = JSON.stringify(normalized, null, 2);
100
105
  const dir = path.join(await getDocsDir(), "sfcc");
101
- return saveAndReturn(text, dir, `system-object-get-${safeType(object_type)}-${safeTimestamp()}.json`);
102
- });
106
+ return saveAndReturn(text, dir, `system-object-get-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
107
+ }));
103
108
  }
104
109
  function buildSystemObjectAttributeSearchHandler(gateDeps, getDocsDir) {
105
- return withSfccGate(gateDeps, async (args, credentials) => {
106
- const { object_type, query, start, count, sorts } = systemObjectAttributeSearchInput.parse(args);
110
+ return withSfccReadErrorBoundary(withSfccGate(gateDeps, async (args, credentials) => {
111
+ const { object_type, query, start, count, sorts, projection } = systemObjectAttributeSearchInput.parse(args);
107
112
  const encodedType = encodeURIComponent(object_type);
108
113
  // Coerce plain string queries into OCAPI text_query shape
109
114
  const resolvedQuery = typeof query === "string"
@@ -116,15 +121,16 @@ function buildSystemObjectAttributeSearchHandler(gateDeps, getDocsDir) {
116
121
  postBody.count = count;
117
122
  if (sorts !== undefined)
118
123
  postBody.sorts = sorts;
124
+ applyProjectionToBody(postBody, "attribute_search", projection);
119
125
  const result = await ocapiPost(`/system_object_definitions/${encodedType}/attribute_definition_search`, postBody, credentials);
120
126
  if (!result.ok) {
121
- return textResult(JSON.stringify({ error: `OCAPI error`, status: result.status, body: result.body }, null, 2));
127
+ return formatOcapiReadFailure(result);
122
128
  }
123
129
  const normalized = normalizeOcapiBody(result.body);
124
130
  const text = JSON.stringify(normalized, null, 2);
125
131
  const dir = path.join(await getDocsDir(), "sfcc");
126
- return saveAndReturn(text, dir, `system-object-search-${safeType(object_type)}-${safeTimestamp()}.json`);
127
- });
132
+ return saveAndReturn(text, dir, `system-object-search-${safeType(object_type)}-${safeTimestamp()}.json`, normalized.page);
133
+ }));
128
134
  }
129
135
  /**
130
136
  * Register the three SFCC system-object introspection read tools.
@@ -137,25 +143,25 @@ export function registerSystemObjectReadTools(registerTool, deps) {
137
143
  description: "List all system object types from the developer sandbox via " +
138
144
  "GET /system_object_definitions. Read-only. " +
139
145
  "Accepts optional `count` and `start` for paging (OCAPI default pagination applies when omitted). " +
140
- "Oversized outputs are auto-saved locally and previewed inline.",
146
+ "Returns ids only by default; pass projection=full for all fields. " +
147
+ "Oversized outputs are auto-saved locally.",
141
148
  inputSchema: systemObjectListInput,
142
149
  annotations: READ_ANNOTATIONS,
143
150
  }, buildSystemObjectListHandler(gateDeps, getDocsDir));
144
151
  registerTool("system_object_get", {
145
- description: "Retrieve a system object type from the developer sandbox. Read-only. " +
152
+ description: "Retrieve a system object type definition from the developer sandbox. Read-only. " +
146
153
  "GET /system_object_definitions/{type}. " +
147
- "Prefer system_object_attribute_search for targeted attribute lookups " +
148
- "use this for a full type dump or expanded attribute list " +
149
- "(expand_attribute_definitions=true). Oversized payloads are auto-saved locally.",
154
+ "Prefer system_object_attribute_search for targeted attribute lookups. " +
155
+ "Oversized payloads are auto-saved locally.",
150
156
  inputSchema: systemObjectGetInput,
151
157
  annotations: READ_ANNOTATIONS,
152
158
  }, buildSystemObjectGetHandler(gateDeps, getDocsDir));
153
159
  registerTool("system_object_attribute_search", {
154
160
  description: "Search attribute definitions for a system object type. Read-only. " +
155
161
  "POST /system_object_definitions/{type}/attribute_definition_search. " +
156
- "Prefer over system_object_get+expand for targeted c_ attribute lookups. " +
157
- "Pass a plain string for text search or a structured OCAPI query object. " +
158
- "Oversized results are auto-saved locally.",
162
+ "Use for targeted c_ attribute lookups; returns each attribute's full schema by default. " +
163
+ "Pass a plain string for text search or a structured OCAPI query. " +
164
+ "Oversized results auto-saved locally.",
159
165
  inputSchema: systemObjectAttributeSearchInput,
160
166
  annotations: READ_ANNOTATIONS,
161
167
  }, buildSystemObjectAttributeSearchHandler(gateDeps, getDocsDir));