salesforce-metadata-mcp 2.8.0 → 2.11.1

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 (46) hide show
  1. package/CHANGELOG.md +673 -0
  2. package/README.md +5 -3
  3. package/TOOLS.md +2 -2
  4. package/dist/index.js +19 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/schemas/index.d.ts +222 -69
  7. package/dist/schemas/index.d.ts.map +1 -1
  8. package/dist/schemas/index.js +93 -18
  9. package/dist/schemas/index.js.map +1 -1
  10. package/dist/services/deployment.d.ts +49 -1
  11. package/dist/services/deployment.d.ts.map +1 -1
  12. package/dist/services/deployment.js +108 -8
  13. package/dist/services/deployment.js.map +1 -1
  14. package/dist/services/mcpgen.d.ts.map +1 -1
  15. package/dist/services/mcpgen.js +14 -4
  16. package/dist/services/mcpgen.js.map +1 -1
  17. package/dist/services/salesforce.d.ts +80 -13
  18. package/dist/services/salesforce.d.ts.map +1 -1
  19. package/dist/services/salesforce.js +832 -265
  20. package/dist/services/salesforce.js.map +1 -1
  21. package/dist/tools/agentforce.d.ts.map +1 -1
  22. package/dist/tools/agentforce.js +187 -44
  23. package/dist/tools/agentforce.js.map +1 -1
  24. package/dist/tools/automation.js +6 -6
  25. package/dist/tools/automation.js.map +1 -1
  26. package/dist/tools/data.d.ts.map +1 -1
  27. package/dist/tools/data.js +3 -1
  28. package/dist/tools/data.js.map +1 -1
  29. package/dist/tools/deployment.d.ts.map +1 -1
  30. package/dist/tools/deployment.js +29 -5
  31. package/dist/tools/deployment.js.map +1 -1
  32. package/dist/tools/flows.d.ts.map +1 -1
  33. package/dist/tools/flows.js +7 -3
  34. package/dist/tools/flows.js.map +1 -1
  35. package/dist/tools/integrations.d.ts.map +1 -1
  36. package/dist/tools/integrations.js +27 -2
  37. package/dist/tools/integrations.js.map +1 -1
  38. package/dist/tools/metadata.d.ts.map +1 -1
  39. package/dist/tools/metadata.js +13 -1
  40. package/dist/tools/metadata.js.map +1 -1
  41. package/dist/tools/objects.js +1 -1
  42. package/dist/tools/objects.js.map +1 -1
  43. package/dist/tools/ui.d.ts.map +1 -1
  44. package/dist/tools/ui.js +8 -8
  45. package/dist/tools/ui.js.map +1 -1
  46. package/package.json +5 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,678 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.11.1] - 2026-08-03
4
+
5
+ ### Fixed — `sf_retrieve_metadata` reported success on zero-result retrieves, plus two dead parameters found while investigating
6
+
7
+ Ajay's follow-up report confirmed v2.11.0's `sf_create_agent_action`/`sf_describe_object` fixes were
8
+ correct — his contradicting evidence turned out to be tested against a stale Claude Desktop MCP
9
+ subprocess predating the rebuild (a recurring gotcha this project has hit before: a rebuilt `dist/`
10
+ only takes effect after a full restart). His genuinely new finding, independent of that mix-up, was
11
+ real: `sf_retrieve_metadata` returned `success: true` and `"Retrieved 1 file(s)."` for a retrieve that
12
+ found **zero** of the requested components — because Salesforce's retrieve zip always includes
13
+ `package.xml` as its own manifest even when every requested member comes back empty, and the file count
14
+ included it. Verified with his exact repro (4 nonexistent `GenAiFunction` names) against a control
15
+ retrieve of a real component.
16
+
17
+ Fixed in `retrieveMetadataAndWait`: the reported count now excludes `package.xml`, and when components
18
+ were requested by name but none came back, the result is `success: false` with an explicit message
19
+ listing what was requested — instead of a misleadingly cheerful count of a manifest nobody asked for.
20
+
21
+ **Investigating this surfaced two more, more severe versions of the same underlying problem**, both
22
+ previously untested: the tool's schema has advertised `metadataType`+`componentName` (single-component
23
+ shortcut) and `packageXml` (raw manifest) as alternatives to `components` since this tool existed, but
24
+ the handler only ever read `params.components` — the other two were silently no-ops. A call using
25
+ either form retrieved **nothing at all** (an empty `<unpackaged>` body) while still reporting a
26
+ "successful" retrieve of the manifest-only zip, even when the requested component genuinely existed.
27
+ Wired up both: `metadataType`/`componentName` now builds a single-item `components` array, and
28
+ `packageXml` now extracts the `<types>`/`<version>` content from the supplied document and uses it
29
+ directly as the retrieve request body (needs the `met:` namespace prefix added to every element, since
30
+ the SOAP body has no default namespace, unlike a standalone package.xml). Verified live for all three
31
+ paths: the shortcut form now retrieves a real flow's XML, the zero-match array form now fails honestly,
32
+ and a hand-written raw `packageXml` retrieve returns real content.
33
+
34
+ No regressions: `test-suite.mjs` 209/212 (2 pre-existing unrelated failures, same as before).
35
+
36
+ ## [2.11.0] - 2026-08-03
37
+
38
+ ### Fixed — `sf_create_agent_action` was fundamentally broken for Flow/ApexClass-backed actions; the misdiagnosis pointed users at their org's licensing instead of the real bug
39
+
40
+ Ajay reported that `sf_create_agent_action` failed for `type=Flow` even against a confirmed-Active
41
+ flow, and that the failure message wrongly concluded custom agent actions were unsupported in the org
42
+ ("check your Agentforce/Einstein licensing") — disprovable because he had just created five
43
+ Flow-backed actions successfully through Salesforce's own Agentforce Builder UI, in the same org, same
44
+ user, same flows.
45
+
46
+ **Root cause, found by following Ajay's own recommended debugging path (diff a UI-created action's
47
+ real XML against what the tool generates) and going further once retrieval came back empty:**
48
+ `sf_create_agent_action` built a classic Metadata API `.genAiFunction` deploy with
49
+ `<invocationTarget>` set to the flow/class **API name** — but UI-created actions, read back via the
50
+ Tooling API's `GenAiFunctionDefinition` object (the classic Metadata API's `GenAiFunction` type turned
51
+ out not to expose these records at all — confirmed by a wildcard retrieve finding zero results even
52
+ for the five UI-created ones), showed `InvocationTarget` holding an 18-character **record ID**
53
+ (`FlowDefinition.Id` / `ApexClass.Id`), not a name. The generic "Specify a valid invocationTarget and
54
+ invocationTargetType" error — previously treated as a reliable org-capability signal after testing in
55
+ an unrelated org — turned out to be Salesforce's error for *this exact payload bug* in an org that
56
+ supports the feature just fine.
57
+
58
+ Fixed by switching `sf_create_agent_action` from the classic Metadata API deploy to a Tooling API
59
+ `GenAiFunctionDefinition` upsert — the same mechanism Agentforce Builder itself uses — resolving
60
+ `reference` to the right record ID first (`FlowDefinition.DeveloperName` → `Id` for Flow,
61
+ `ApexClass.Name` → `Id` for ApexClass). Verified live end-to-end, not just unit-tested: created a real
62
+ action against Ajay's own reported flow through the actual registered tool handler (reproducing his
63
+ repro exactly), confirmed the fix also works for ApexClass, and confirmed a Tooling-API-created action
64
+ is correctly picked up by a topic (`GenAiPlugin`) deployed the normal Metadata-API way — the two APIs
65
+ share the same underlying records, so mixing them across the 5-step agent sequence is safe.
66
+ `PromptTemplate`/`DataCategoryGroup`/`ExternalService` are now honestly refused (clear "not yet
67
+ verified" message) rather than silently deploying an unverified, likely-broken payload — matching this
68
+ project's standing rule not to ship unverified metadata behavior.
69
+
70
+ **`sf_create_agent`'s pre-flight capability probe (added in v2.10.0)** used the same broken deploy
71
+ path internally, so it always failed and hard-blocked agent creation even in orgs — like this one —
72
+ where it works fine. Rewritten to match: it now attempts a real Tooling API insert with a
73
+ syntactically-valid-but-nonexistent target ID. This fails fast on a specific field-validation error in
74
+ any org where the object exists (verified live: `INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST`), and since
75
+ nothing is ever actually written, there's no cleanup step either — simpler than the old probe, which
76
+ had to deploy-then-delete a throwaway. Verified live: `sf_create_agent` now succeeds normally in this
77
+ org, both with and without `skipActionCapabilityCheck`.
78
+
79
+ Also fixed: `sf_describe_object` returned a bare `NOT_FOUND` 404 for standard objects whose *feature*
80
+ is disabled (Ajay's repro: `Quote`, when the Quotes feature is off in Setup) — indistinguishable from a
81
+ typo'd or nonexistent object name. Added a hint for a hardcoded list of commonly feature-gated standard
82
+ objects (Quote, Contract, Order, Campaign, Territory2, WorkOrder, ServiceAppointment, etc.) pointing at
83
+ Setup instead of sending the user hunting for a naming mistake that isn't there.
84
+
85
+ **`qa-agentforce.mjs` needed a substantial rewrite, not just the tool code.** The suite's own
86
+ assertions were written under the same now-disproven assumption ("demo-org cannot create GenAiFunction
87
+ actions") baked into skip logic throughout — so the action/topic verification paths had never actually
88
+ executed in any prior session, and once they finally ran (because the underlying bug they were gated
89
+ behind is now fixed), they surfaced a second, independent, pre-existing gap: `existsInOrg`'s plain REST
90
+ `queryRecords` can't see `GenAiFunction`/`GenAiFunctionDefinition` (Tooling-API-only, confirmed) or
91
+ `GenAiPlugin` (no SOQL interface at all, confirmed both plain REST and Tooling — only readable via
92
+ classic Metadata API `readMetadata`) — it happened to work before only because those checks were never
93
+ reached. Rewrote `existsInOrg` to dispatch to the right API per type, and rewrote the action-type
94
+ matrix check to read `GenAiFunctionDefinition.InvocationTargetType` back via Tooling API instead of a
95
+ `retrieveMetadata` call that (per the finding above) was always going to come back empty. Suite went
96
+ from 33 passed/15 failed (pre-fix) to 35 passed/2 failed against a live org.
97
+
98
+ **Known open item, not a code defect:** after extensive live testing today (mine plus Ajay's own
99
+ UI-based testing), further `GenAiFunctionDefinition` inserts in `demo-org` started failing with
100
+ `DUPLICATE_DEVELOPER_NAME` ("...already exists or has been previously used") even against
101
+ brand-new, never-used developer names and master labels — isolated by testing fresh-name/reused-label
102
+ and fresh-everything/reused-target combinations independently; neither explains it, and the condition
103
+ persisted for several minutes and across unrelated work. Most likely an org-side rate limit or cooldown
104
+ on this object triggered by the volume of creates/deletes during today's testing, not a regression in
105
+ this fix — the two remaining `qa-agentforce.mjs` failures are this condition surfacing honestly through
106
+ the tool's real error message, not a masked defect. Re-run the suite after some time has passed to
107
+ confirm it clears; if it doesn't, this needs further investigation as a genuine platform limit.
108
+
109
+ ## [2.10.0] - 2026-08-01
110
+
111
+ ### Fixed/Added — 4 more findings from an extended manual test session (Bugs 6-8, and a sharper Bug 3), plus 2 new tools
112
+
113
+ Ajay's follow-up report superseded the previous one. Bugs 1, 2, 4, 5 from that report were already
114
+ fixed in v2.9.0 (confirmed still present in this session, not re-fixed) — the report describing them
115
+ as unfixed was written from a Claude Desktop session whose MCP server subprocess had been running
116
+ since before v2.9.0 landed; restarting Claude Desktop picks up a rebuilt `dist/`, an already-spawned
117
+ subprocess does not. New findings below.
118
+
119
+ **Bug 3, revisited with much stronger evidence.** The previous release's conclusion (org-side schema
120
+ propagation lag) held, but Ajay's new evidence sharpened *what kind* of lag it is: persisted 2+ hours
121
+ in his session (ruling out ordinary propagation delay), and — critically — REST describe worked fine
122
+ on **pre-existing** custom fields on standard objects (Opportunity's own custom fields) while failing
123
+ on everything newly created, which looked like it might be specific to brand-new custom *objects*
124
+ rather than fields in general. Tested that distinction directly and decisively: created a field on the
125
+ standard `Account` object and a field on a brand-new custom object side by side, then polled both for
126
+ 2 full minutes. **Both were equally stuck** — this rules out "new object vs. existing object" as the
127
+ differentiator. The real explanation: Ajay's "working" standard-object fields were older, already-
128
+ propagated fields from earlier sessions, not freshly created ones — not evidence that new fields on
129
+ standard objects propagate faster. Also directly tested Ajay's identity-mismatch hypothesis (REST and
130
+ Metadata clients authenticating as different users): confirmed live that both use the exact same
131
+ `auth.accessToken`, resolving to the same user (`semwalajaydevorg@agentforce.com`) and same org ID —
132
+ ruled out. Revised, more precise conclusion: this org has a severe (multi-hour-observed), general
133
+ schema-cache propagation lag affecting all newly created fields, regardless of parent object type —
134
+ still not something any client-side code change can fix.
135
+
136
+ **Bug 6: `sf_create_agent` now probes GenAiFunction (custom action) support before creating a Bot
137
+ shell.** Reproduced Ajay's exact finding: active Agentforce permission set licenses are not sufficient
138
+ evidence that custom actions work — confirmed by deploying a `GenAiFunction` aimed at a deliberately
139
+ nonexistent target and getting the identical generic error a real target would get, meaning the org
140
+ never even attempts target resolution. Added a pre-flight probe (a real, throwaway `GenAiFunction`
141
+ deploy, cleaned up automatically) that runs before the Bot shell is created on the first call in the
142
+ 5-step sequence: if actions aren't supported, the call fails immediately with no shell created, instead
143
+ of leaving an orphaned Bot with no planner/topic/action once step 2 turns out to be unreachable. Added
144
+ `skipActionCapabilityCheck` for topics-only agents that don't need custom actions. Verified live both
145
+ ways: the probe correctly blocks with no shell created, and the skip flag correctly bypasses it.
146
+
147
+ **Bug 7: `sf_create_agent_topic` now validates that every referenced action exists before deploying.**
148
+ Previously, a topic referencing a missing action deployed and failed with an opaque Salesforce support
149
+ ErrorId naming nothing. `GenAiFunction` isn't SOQL/Tooling-queryable in every org (confirmed: it isn't
150
+ in `demo-org`), so existence is checked via Metadata API `readMetadata` instead, which works regardless
151
+ of SOQL support for the type. Verified live: a topic referencing a nonexistent action is now rejected
152
+ before ever reaching Salesforce, naming the specific missing action.
153
+
154
+ **Bug 8: added `sf_delete_metadata`.** There was previously no way to remove anything deployed by this
155
+ MCP server — `sf_deploy_metadata` has no `destructiveChanges` support, and diagnostic/orphaned metadata
156
+ had nowhere to go (confirmed: Ajay's stranded `AccountOpportunityAgent` Bot shell was still in the org,
157
+ alongside 13 similar leftovers from earlier sessions). Wraps the `deleteMetadata` SOAP call, which
158
+ already existed as an internal service function but was never exposed as a tool. Verified live with a
159
+ real round-trip (create → delete → confirm gone) and by actually attempting cleanup of the accumulated
160
+ org backlog: correctly surfaced Salesforce's own 10-record-per-call limit (batched around it), and
161
+ correctly surfaced a real dependency-order error when a `GenAiPlannerBundle` was still referenced by a
162
+ Bot. The specific stuck Bot records from earlier sessions remain undeletable — a pre-existing, already-
163
+ documented Salesforce-side "unexpected error" on those particular records, not something this tool or
164
+ any client-side retry can work around.
165
+
166
+ **Documented, not a bug**: Flow `textTemplates` strip leading/trailing whitespace on deploy while
167
+ preserving internal newlines — concatenating per-iteration templates in a loop without an internal
168
+ separator runs lines together. Added to the `sf_create_flow` schema description.
169
+
170
+ **Confirmed already fixed, not re-touched**: Bugs 1 (duplicate Decision rule names), 2 (filter value
171
+ typing), 4 (FLS warning), 5 (raw-XML pre-validation) — all still present and correct in this codebase,
172
+ verified by direct inspection before assuming anything needed re-fixing.
173
+
174
+ Tool count 222→223 (`sf_delete_metadata`). Regression: `qa-agentforce.mjs` gained 5 new checks for
175
+ Bugs 6/7 (all existing `sf_create_agent` calls updated with `skipActionCapabilityCheck` so the new
176
+ probe doesn't change what those pre-existing tests were actually testing); `test-suite.mjs` gained a
177
+ real create→delete→confirm-gone round-trip test for `sf_delete_metadata`.
178
+
179
+ ## [2.9.0] - 2026-08-01
180
+
181
+ ### Fixed — 5 real bugs from a genuine manual test session in Claude Desktop, all reproduced and re-verified live
182
+
183
+ Ajay tested v2.8.9 by hand in Claude Desktop against a real Developer Edition org — created a custom
184
+ object, six custom fields, and two Flows built against them — and reported five specific issues found
185
+ along the way. Every one below was reproduced first (not assumed from the report), root-caused in the
186
+ source, fixed, and re-verified against `demo-org` before being called done.
187
+
188
+ **`sf_create_flow` emits duplicate rule developer names for 2+ Decision elements (blocking).**
189
+ Reproduced exactly: `Deployment failed: Duplicate developer name: Rule_1`. Root cause: each Decision's
190
+ rule `<name>` was `Rule_1`, `Rule_2`, ... scoped per-decision, but Salesforce requires uniqueness across
191
+ the whole Flow. Fixed in both XML generators by namespacing with the parent Decision's own name
192
+ (`Decision_One_Rule_1`), verified unique in the deployed XML afterward. Fixing this surfaced a second,
193
+ related bug in the same code path: `<defaultConnectorLabel>` was only emitted when `defaultConnector`
194
+ was set, but Salesforce requires it unconditionally (it labels the implicit "no rule matched" branch,
195
+ which exists whether or not it connects anywhere) — every existing Decision test in this repo's own
196
+ suite happened to set `defaultConnector` explicitly, which is exactly why this had never been caught.
197
+ Fixed in both generators; added a permanent regression test with two Decisions, the second deliberately
198
+ relying on implicit fall-through. Flow suite: 142→144 checks, 144/144 passing.
199
+
200
+ **`sf_create_flow`'s filter/assignment values only accepted strings, and GetRecords filters silently
201
+ mistyped numbers.** The schema rejected a native boolean (`value: true`) with a bare zod error; the
202
+ reported workaround (`value: "true"`) did work correctly (booleans were already string-sniffed
203
+ correctly), but investigating this surfaced a real, more serious, previously-unreported bug in the same
204
+ code: GetRecords filter values had NO numeric detection at all — unlike Decision/CreateRecords/
205
+ UpdateRecords, which all correctly detect numeric-looking values, GetRecords filters emitted
206
+ `<stringValue>100</stringValue>` for every non-boolean value including numbers, which risks incorrect
207
+ or no matches when filtering Number/Currency/Percent fields with comparison operators. Fixed the missing
208
+ numeric detection in both generators, and widened `rightValue`/`filterValue`/`filters[].value`/
209
+ `inputAssignments[].value`/`assignments[].value` to accept string, number, or boolean directly (coerced
210
+ to string before the existing, now-correct typed-XML logic) so the natural call shape works without a
211
+ string-coercion workaround. Verified live: a native boolean and a native number filter both produce the
212
+ correct typed XML element in the deployed flow.
213
+
214
+ **REST describe / SOQL not seeing custom fields that the Metadata API and Tooling API confirm exist.**
215
+ Reproduced exactly — `describe field count: 10` (only standard fields), matching the report precisely,
216
+ persisting well past 60 seconds and after granting FLS (also reproduced: 0 grants by default). Ruled
217
+ out an in-process cache (there isn't one — `sf_describe_object` makes a fresh HTTP call every time,
218
+ confirmed by reading the code) and ruled out XML element ordering in the picklist-value generator via a
219
+ controlled live A/B test (an initial hypothesis that looked promising but didn't hold up once tested
220
+ head-to-head). What's actually happening: Salesforce's own REST describe/SOQL schema cache lagged
221
+ behind the Metadata API and (intermittently, itself) the Tooling API by 10+ minutes on this org during
222
+ testing — a genuine platform-side propagation characteristic, not something any client-side code
223
+ change can fix. Implemented the mitigation Ajay suggested regardless: `sf_describe_object` gained
224
+ `waitForFields`/`timeoutSeconds` to poll until named fields appear (or say clearly that they still
225
+ haven't, with a pointer to the Tooling API to confirm the field truly exists), so callers don't have to
226
+ hand-roll retry loops. Documented plainly in both the tool description and the CHANGELOG that this is
227
+ an org-side lag, not a bug in this server — don't re-diagnose it as one without new evidence.
228
+
229
+ **`sf_create_custom_field` leaves every new field with zero FLS grants and no indication a follow-up
230
+ call is needed.** Reproduced: `sf_get_field_permissions` returns 0 grants immediately after a
231
+ `success: true` field creation, for every profile including System Administrator. Fixed by checking FLS
232
+ right after creation and appending an explicit warning to the response when none exist, naming the
233
+ exact fix (`sf_create_field_level_security`) — chose this over auto-granting FLS by default since that
234
+ would silently change existing behavior; surfacing the truth is a strictly additive fix.
235
+
236
+ **`sf_create_flow_from_xml` surfaced raw, unhelpful Salesforce schema errors for 3 common mistakes.**
237
+ Added `validateAndNormalizeFlowXml()`: a bounded, depth-tracking tokenizer (not a full XML parser —
238
+ sufficient for direct children of the root `<Flow>` element) that catches, before ever deploying: (1)
239
+ non-contiguous top-level elements of the same type (interleaved `<assignments>`/`<decisions>` etc.,
240
+ which Salesforce reports as a baffling "Element X is duplicated at this location in type Flow" instead
241
+ of naming the real issue — grouping), (2) missing `<start>` `locationX`/`locationY`, auto-defaulted
242
+ rather than just flagged since there's no ambiguity about a safe default, (3) an SObject-typed
243
+ `<variables>` entry missing `<objectType>`, naming the specific variable (Salesforce's own error for
244
+ this one was already good — replicated its style for consistency, and to catch it pre-deploy instead of
245
+ after). Verified live: interleaved elements and a missing objectType are both now rejected pre-deploy
246
+ with a specific, actionable message and never reach Salesforce at all; missing start coordinates are
247
+ silently defaulted and the flow deploys successfully.
248
+
249
+ **Full regression, both before committing and after every individual fix**: `qa-flow-comprehensive.mjs`
250
+ 142→144/144 (2 new checks from the Bug 1 regression test, all passing). `test-suite.mjs` and the
251
+ Agentforce suites held their existing pass rates — none of these five fixes touch Agentforce code paths.
252
+
253
+ ## [2.8.9] - 2026-07-31
254
+
255
+ ### Fixed — the v2.8.8 command-injection guard was over-broad; narrowed after a second round of live testing
256
+
257
+ Asked for a second round of testing before publishing v2.8.8, rather than treating the first pass as
258
+ done. Good call: it surfaced a real false-positive regression in the fix itself. The original guard
259
+ rejected any `sf` CLI argument containing `` " ` $ & | ; < > ^ `` — but a systematic, one-character-
260
+ at-a-time live test (each character alone, then each paired with a quote) showed every one of those
261
+ metacharacters is completely inert on its own through cross-spawn; **only a literal `"` combined with
262
+ a following metacharacter reaches a live shell.** A quote alone never did either. That means the
263
+ original guard would have rejected entirely legitimate values this codebase's own tools pass
264
+ routinely — a package description like `"Sales & Service Tools"`, a company name like
265
+ `"O'Brien Industries"` — with zero actual security benefit, since none of those ever reach a shell
266
+ regardless. Narrowed to reject only `"` (plus raw newlines, never legitimate in a single CLI arg
267
+ either). Re-verified live, both directions: the original exploit payload (`"x & echo ... & echo x"`)
268
+ is still rejected; a package description containing `&` and `'` now reaches the real `sf` CLI instead
269
+ of being rejected by the guard.
270
+
271
+ ### Reviewed in depth on request — Flow builder and Agentforce agent/topic/action/planner creation
272
+
273
+ Ajay asked specifically for closer attention here. Read both Flow XML generators
274
+ (`buildFlowXml`/SOAP and `buildFlowDeployXml`/ZIP, ~800 lines combined) end to end and all four
275
+ Agentforce tools (`sf_create_agent`, `sf_create_agent_topic`, `sf_create_agent_action`,
276
+ `sf_create_agent_planner`) line by line, not just grep-sampled. Result: both Flow builders escape
277
+ every free-text value correctly and consistently (verified their `buildFilterValue`/
278
+ `typedResourceValue` helpers apply `x()` at every actual string-literal insertion point); the
279
+ remaining raw interpolations are all zod-enum-constrained `dataType` fields or booleans, not user
280
+ text, so no injection surface. Agentforce's XML construction is equally clean throughout.
281
+
282
+ **Did find one real, separate bug while reading this closely, unrelated to injection**:
283
+ `sf_create_agent_action`'s `inputs` parameter (input parameter mappings — `[{name, value}]`) is
284
+ accepted by the schema and documented, but was never wired into the generated `GenAiFunction` XML at
285
+ all — confirmed by grep, not a one-off oversight in this pass. Any caller who passed `inputs` had them
286
+ silently discarded, with no error and no indication in the response. This went uncaught because
287
+ `demo-org` cannot create `GenAiFunction` actions at all (a pre-existing, documented org-licensing
288
+ limit), so this parameter has never been exercised against a live org — and this project's own rule is
289
+ not to ship metadata XML shapes that haven't been verified that way. Rather than guess at the correct
290
+ XML (a real risk of shipping a second, differently-wrong bug), the tool's success message now says
291
+ explicitly when `inputs` was provided but not applied, so callers aren't silently misled. Implementing
292
+ it for real is still blocked on the same thing blocking the rest of GenAiFunction action testing: an
293
+ org where custom agent actions are actually licensed.
294
+
295
+ **Full regression**, both directions of this round: `test-suite.mjs` 207/211 passed — the one new
296
+ "failure" beyond the existing 2 pre-existing ones (`sf_share_report_folder`) is test-state
297
+ accumulation (it selects an existing report folder from the org, and today's own repeated
298
+ `test-suite.mjs` runs have created enough of them that one now has a too-long derived name), not a
299
+ code regression — unrelated to any change in this release. `qa-agentforce.mjs`,
300
+ `qa-agentforce-adjacent.mjs`, and `qa-flow-comprehensive.mjs` (142/142) all held their existing rates.
301
+
302
+ ## [2.8.8] - 2026-07-31
303
+
304
+ ### Security audit — one confirmed, exploitable command-injection vulnerability fixed, plus 13 more real findings across SOQL, generated-code, and credential-handling surfaces
305
+
306
+ Ajay asked for a standing security review of this project, not tied to any specific bug report. Went
307
+ through the codebase systematically rather than waiting for a report, and proved every finding below
308
+ by actually attempting the attack through the real, compiled MCP tool handler — not just reading code
309
+ and guessing. Everything here was found live against `demo-org`, the same standard this project holds
310
+ itself to for functional bugs.
311
+
312
+ **Command injection — CRITICAL, confirmed live-exploitable, now fixed.** Every `sf` CLI invocation in
313
+ this codebase built a shell command by joining an args array with spaces and running it through
314
+ `execSync`. Six tools passed unvalidated string parameters straight into that array:
315
+ `sf_create_scratch_org`, `sf_delete_scratch_org`, `sf_create_package`, `sf_create_package_version`,
316
+ `sf_install_package`, `sf_uninstall_package`, `sf_run_code_scanner`, `sf_scan_apex_antipatterns`.
317
+ Proof of concept: a `devHubAlias` of `DevHub & echo INJECTED > marker.txt & echo x` actually created
318
+ the marker file when run through `sf_create_scratch_org`'s real handler. Fixed by replacing every
319
+ `execSync`/`exec` call with `cross-spawn`, which passes each argument as a genuine argv entry instead
320
+ of shell text — re-verified the same payload no longer executes. **cross-spawn alone was not enough**:
321
+ an argument combining a literal `"` with a shell metacharacter (e.g. `"x & ... & echo x"`) still
322
+ reached a live shell even through cross-spawn 7.0.6's own escaping, because `sf` is a `.cmd` batch
323
+ shim on Windows and its internal `%*` argument-forwarding to node.exe is a second, uncontrolled
324
+ re-parsing step outside cross-spawn's reach — also proven live before being closed with an explicit
325
+ character allowlist (`runSfCli` now rejects any argument containing `"`, `` ` ``, `$`, `&`, `|`, `;`,
326
+ `<`, `>`, `^`, or a newline before it ever reaches cross-spawn; none of this codebase's CLI arguments —
327
+ aliases, IDs, paths, keys, rule selectors — ever legitimately need one). Also deleted `createScratchOrg`,
328
+ a second, entirely dead implementation of scratch-org creation with the same injection flaw plus
329
+ broken shell quoting that would have failed on both POSIX and Windows — confirmed zero references
330
+ anywhere before removing it.
331
+
332
+ **SOQL injection — real, live, found across 9 call sites.** `sf_get_field_history` (`recordId` and
333
+ `objectApiName`, the latter landing unquoted in a FROM-clause identifier position — no amount of
334
+ quote-escaping fixes that, only an identifier allowlist does), `sf_get_setup_audit_trail` /
335
+ `sf_get_login_history` / `sf_get_event_logs` (`startDate`/`endDate`, interpolated unquoted with zero
336
+ format validation), `sf_get_apex_test_results` (`testRunId`), `sf_detect_devops_merge_conflict` /
337
+ `sf_check_devops_commit_status` / `sf_list_devops_work_items` (`workItemId`/`projectId`/`stageId`),
338
+ and `sf_create_field_dependency` (`objectName`/`dependentField` in a Tooling API query). Fixed with a
339
+ new `soqlEscape()` helper (formalizing the backslash-quote pattern already used correctly at ~15 other
340
+ call sites in this file) and a new `assertSoqlDateLiteral()` validator for the unquoted date-literal
341
+ positions. `sf_query_records`'s raw `soql`/`whereClause`/`orderBy` and `sf_create_apex_batch`'s
342
+ `queryFilter` were deliberately left alone — both are documented, intentional raw-SOQL-fragment
343
+ parameters, the entire point of those tools, not an oversight.
344
+
345
+ **Code injection into generated, later-executed source — found in both Apex and TypeScript
346
+ generation.** `sf_create_invocable_action`'s `label`/`description` and its input/output variable
347
+ labels landed unescaped inside `@InvocableMethod`/`@InvocableVariable` annotation string literals in
348
+ generated Apex — an unescaped `'` breaks out of the literal and injects arbitrary class members into
349
+ a class that gets deployed and can execute. Same pattern in the (dead, unwired — see below)
350
+ `createApexScheduler`'s doc comment (a literal `*/` closes the comment early, turning the rest of the
351
+ generated file back into live code) and `createRestResource`'s `urlMapping`. Fixed with the same
352
+ `soqlEscape()` helper (Apex and SOQL use identical backslash-quote string-literal escaping) plus a
353
+ comment-safe `*/`-stripping helper for the comment-only cases. **More seriously, this same class of
354
+ bug was live and reachable** in `sf_create_mcp_server` and `sf_create_mcp_tool` (the tools that
355
+ scaffold a brand-new MCP server project on disk): `serverName` and `toolName` were spliced unescaped
356
+ into generated `.ts` source as string literals, and `sf_create_mcp_tool`'s `inputSchema` keys —
357
+ `z.record(z.unknown())`, so literally any string — were spliced in as raw, unquoted object-property
358
+ names with zero validation. Proof of concept: an `inputSchema` key of
359
+ `x(){require("fs").writeFileSync("pwn2","x");return z.string()` was accepted by the old code and would
360
+ have become live, executable code in the generated project the moment it was built and run. Fixed:
361
+ `serverName`/`toolName` now go through `JSON.stringify()` (matching how `toolDescription` was already
362
+ correctly handled in the same function — this was an inconsistency, not a from-scratch gap), and
363
+ `inputSchema` keys are now validated against a strict identifier regex before generation, with a clear
364
+ rejection instead of silent code smuggling.
365
+
366
+ **Credential leak in the codebase's own default error-sanitization path — real, high-impact, silently
367
+ broken since introduction.** `sanitizeError()` is called in essentially every one of this codebase's
368
+ ~100 catch blocks — it's what stands between an internal error and what gets returned to the calling
369
+ LLM/user. Its only token-redaction rule was `[A-Fa-f0-9]{40,}` (pure hex, 40+ chars). Real Salesforce
370
+ access tokens/session IDs look like `00Dxxxxxxxxxxxx!AQEAQ...` — base64url-ish with a literal `!`
371
+ separator, never pure hex — so a real token embedded in any error message (a network client echoing a
372
+ request URL, an API error quoting back session context) would have passed straight through
373
+ completely unredacted. Proved this live with a realistic fake token before and after the fix. There
374
+ was a second function, `redactSensitive()`, that had the right idea (a `Bearer` pattern, a `00D...`
375
+ pattern) but its own character classes stopped at the first `!` or `.`, so it only partially redacted
376
+ even the cases it targeted, and — separately — it was only ever called from 2 of the ~100 relevant
377
+ sites, so almost nothing benefited from it regardless. Fixed by broadening the character classes to
378
+ cover the actual token shape and having `sanitizeError()` call `redactSensitive()` internally, so
379
+ every existing caller gets the protection retroactively with no call-site changes needed.
380
+
381
+ **HTTP transport mode has no authentication and bound to all network interfaces by default.** This
382
+ project supports `TRANSPORT=http` as an alternative to the default stdio transport. Its `/mcp` endpoint
383
+ has no auth of its own — any request that reaches it executes MCP tools using whatever Salesforce
384
+ credentials the server process is configured with — and `http.Server.listen(port)` with no explicit
385
+ host binds to every interface, not just localhost, by Node's own default. Fixed: defaults to
386
+ `127.0.0.1` now; set `HOST` explicitly (e.g. behind a reverse proxy that adds real auth) to opt into
387
+ broader exposure instead of getting it by accident. This mode is opt-in and off by default (stdio is
388
+ the default transport), so this was a foot-gun for anyone who did enable it, not an always-on gap.
389
+
390
+ **Audited and confirmed clean, no action needed:** XML metadata builders (spot-checked ~60 candidate
391
+ interpolation sites; every one was already either correctly wrapped in the existing `x()` escaper or a
392
+ boolean/number field with no injection surface at all — the real gaps here were already closed in
393
+ v2.8.2). `npm audit`: still exactly the 2 moderate findings already documented and deliberately left
394
+ (a path-traversal bug in `@hono/node-server`'s `serve-static`, a transitive dependency of the MCP SDK
395
+ that this project's own HTTP transport — a from-scratch, two-route implementation — never calls into
396
+ regardless of transport mode; confirmed again this session, not just carried forward from memory).
397
+ Secrets/credential files: confirmed `.gitignore` still covers `.env`, `.env.local`, `*.key`, and
398
+ `CLAUDE.local.md`, and confirmed via `git log --all` that nothing secret-shaped has ever been committed.
399
+
400
+ **Also found, not fixed (dead code, zero live exposure, hardened anyway for defense-in-depth):**
401
+ `createInvocableAction`, `createApexScheduler`, and `createRestResource` are all exported from
402
+ `services/salesforce.ts` but never imported by any tool file — confirmed via full-codebase grep before
403
+ and after — so their injection fixes above protect nothing reachable today. Left in place rather than
404
+ deleted (unlike `createScratchOrg`, which was both dead *and* had no legitimate salvage value) since
405
+ these look like intended-but-never-wired functionality; flagging here so a future session that wires
406
+ them up inherits the fix rather than reintroducing the bug.
407
+
408
+ **Full regression confirmation:** `test-suite.mjs` (211 tests) — 208 passed, 2 failed (both pre-existing,
409
+ unrelated, intentional negative-test probes), 1 skipped — no change from the pre-audit baseline.
410
+ `qa-agentforce.mjs`, `qa-agentforce-adjacent.mjs`, and `qa-flow-comprehensive.mjs` all held their
411
+ existing pass rates. Every fix above was also independently proven by re-attempting its specific
412
+ exploit through the real, compiled tool handler and confirming it now fails cleanly instead of
413
+ executing.
414
+
415
+ ## [2.8.7] - 2026-07-31
416
+
417
+ ### Added — `sf_create_external_client_app`, and it closes a gap `sf_create_connected_app` structurally cannot
418
+
419
+ Asked why the previous release reached for a Connected App at all when External Client Apps (ECAs) are
420
+ Salesforce's newer, recommended replacement, the honest answer was: the codebase simply had no ECA
421
+ tool, so the fix used what existed. Built the missing tool instead of leaving that as a permanent
422
+ excuse — and it turns out ECAs solve a real limitation, not just a modernization nicety.
423
+
424
+ `ExternalClientApplication` decomposes into three independent metadata types
425
+ (`ExternalClientApplication`, `ExtlClntAppOauthSettings`, `ExtlClntAppOauthConfigurablePolicies`),
426
+ none of which are documented with a field-level reference on Salesforce's own metadata pages for the
427
+ parts that matter most. Every field below — including the full 36-value OAuth scope enum, which the
428
+ official docs don't list at all — was discovered by deploying deliberately-invalid values against a
429
+ live org and reading Salesforce's own rejection messages (the scope enum came back complete, unprompted,
430
+ in a single error string), then confirmed correct by deploying the real values and reading the records
431
+ back. `sf_create_external_client_app` deploys all three components in one call.
432
+
433
+ **The actual reason to prefer this over `sf_create_connected_app`:** on a classic Connected App,
434
+ Client Credentials Flow's "Run As" user can only ever be picked in Setup UI — confirmed in the
435
+ previous release, and still true, there is no metadata or REST field for it. On an External Client
436
+ App, `ExtlClntAppOauthConfigurablePolicies.clientCredentialsFlowUser` sets it directly, verified live:
437
+ create the app, enable the flow, name the user, done — no human required for that part. The one thing
438
+ that's still Setup-UI-only for *either* app type is viewing the Consumer Key/Secret — Salesforce does
439
+ not expose it via any API, full stop, and the tool's success message says so rather than implying the
440
+ app is immediately usable.
441
+
442
+ **Also fixed the same "cannot fail" defect in `sf_create_connected_app`'s own regression test** — it
443
+ called the underlying function with a wrong parameter name (`appName` instead of `fullName`) and
444
+ omitted the two required fields (`callbackUrls`, `scopes`) entirely, so it always threw internally and
445
+ passed via `orgLimitFallback`'s catch. This is very likely *how* the scope-enum bug fixed in the
446
+ previous release went unnoticed for as long as it did: the one test that should have caught it never
447
+ actually ran the real code path. Fixed with real required parameters; added a matching real test for
448
+ the new tool.
449
+
450
+ Also updated the tool count (221→222) across `package.json`, `server.json`, `README.md`, `TOOLS.md`,
451
+ and `CLAUDE.md`.
452
+
453
+ ## [2.8.6] - 2026-07-31
454
+
455
+ ### Fixed — `sf_create_connected_app` could never successfully grant any OAuth scope
456
+
457
+ Asked to prove an Agentforce agent can hold a live conversation, the trail led to the connected app
458
+ required for the Agent API's client-credentials flow — and that tool turned out to be broken end to
459
+ end, independent of anything Agentforce-specific. Every scope value in the schema (`api`, `web`,
460
+ `chatter_api`, `offline_access`, ...) is lowercase snake_case, matching the OAuth2 `scope`
461
+ query-parameter convention developers know. The `ConnectedApp` Metadata API's scope enum
462
+ (`ConnectedAppOauthAccessScope`) is a completely different, undocumented set of PascalCase literals
463
+ that has no relationship to that convention — `'api' is not a valid value for the enum
464
+ 'ConnectedAppOauthAccessScope'`. Every one of the tool's own scope values was therefore guaranteed to
465
+ fail deployment, 100% of the time, for every caller who ever used it. Fixed with a mapping table where
466
+ each entry was individually verified by deploying it against a live org and reading Salesforce's own
467
+ accept/reject response — not guessed from docs: `api`→`Api`, `web`→`Web`, `full`→`Full`,
468
+ `chatter_api`→`Chatter`, `wave_api`→`Wave`, `eclair_api`→`Eclair`, `content`→`Content`,
469
+ `openid`→`OpenID`, `profile`→`Profile`, `email`→`Email`, `address`→`Address`, `phone`→`Phone`,
470
+ `offline_access`→`RefreshToken`, `custom_permissions`→`CustomPermissions`, `pardot_api`→`Pardot`.
471
+ Also added `chatbot_api`→`Chatbot` (verified live) — needed by any external caller of a Salesforce
472
+ bot/Agentforce agent, previously missing from the enum entirely. `visualforce` has **no** confirmed
473
+ working literal — `Visualforce`, `VisualForce`, `Vf`, and `ViewVisualforce` were all tried and
474
+ rejected; it is passed through unmapped and documented as a known open gap rather than guessed.
475
+
476
+ Also added `enableClientCredentialsFlow`, which sets `isClientCredentialEnabled`/`isAdminApproved` on
477
+ the deployed app (both verified accepted by the Metadata API). This does **not** make Client
478
+ Credentials Flow usable by itself: Salesforce still requires a human in Setup → App Manager → Edit
479
+ Policies to pick the flow's "Run As" user, and the Consumer Secret can only ever be viewed/copied from
480
+ that same Setup UI — neither is exposed by any API, Connect resource, or Tooling endpoint found during
481
+ a deliberate search. The tool's success message now says this explicitly instead of implying the app
482
+ is ready to use.
483
+
484
+ ### Fixed — `sf_create_agent_action`'s `label` parameter was required but the code already treated it as optional
485
+
486
+ The handler has always used `params.label ?? params.actionName` as a fallback, but the zod schema
487
+ marked `label` as required — so the fallback code was dead and every caller who reasonably omitted a
488
+ redundant label got a hard validation rejection. Schema now matches the code's actual behavior.
489
+
490
+ ### Fixed — `activateAgent`/`deactivateAgent` claimed success on total failure
491
+
492
+ Not wired to any MCP tool (dead code, found during this session), but worth fixing since it embodies a
493
+ pattern already flagged elsewhere in this codebase: its final fallback returned `success: true` even
494
+ when every attempt inside the function had failed. Verified live: the `/connect/einstein/copilot/
495
+ {name}/activate` Connect resource 404s ("resource does not exist"), and both `BotDefinition.Status`
496
+ and `BotVersion.Status` are read-only via REST ("No such column" / "Unable to create/update fields:
497
+ Status... check security settings" — Salesforce's own read-only-field wording, not a permissions gap).
498
+ No working programmatic activation path was found despite testing roughly ten REST/Connect/Tooling
499
+ endpoint candidates. The function now reports failure honestly instead of guessing success; Agent
500
+ activation (Setup → Agent Builder → Activate) appears to be Setup-UI-only as of API v66.0.
501
+
502
+ ### Fixed — `test-suite.mjs` had 5 Agentforce/Einstein tests that could never fail (not 3, as
503
+ ### previously scoped)
504
+
505
+ `sf_create_einstein_bot`, `sf_create_einstein_prediction`, `sf_create_agent`, `sf_create_agent_topic`,
506
+ and `sf_create_agent_action` were each wrapped in try/catch returning `success: true`, then passed
507
+ through `orgLimitFallback` (which also counts HTTP 500/404/NOT_FOUND as a pass). The agent/topic tests
508
+ imported `createAgent`/`createAgentTopic` from `services/salesforce.js`, which no MCP tool calls —
509
+ `src/tools/agentforce.ts` builds its own XML inline — so they exercised orphaned code while the shipped
510
+ handlers had zero coverage from this suite. The action test hand-rolled `GenAiFunction` XML with a
511
+ `type`/`functionRef` shape that doesn't match what the real tool deploys
512
+ (`invocationTarget`/`invocationTargetType`), aimed at a flow named `NonExistentFlow`. All five removed;
513
+ real, live-org-verified coverage for these five tools plus `sf_create_agent_planner`,
514
+ `sf_create_bot_routing`, and `sf_assign_skill_to_agent` (none of which had any test before) lives in
515
+ `qa-agentforce.mjs` (47 checks) and `qa-agentforce-adjacent.mjs` (11 checks). `test-suite.mjs` is now
516
+ 210 tests (was 215); re-run against `demo-org` after the changes above: 206 passed, 3 failed (all
517
+ pre-existing and unrelated — `sf_share_report_folder` field-length validation,
518
+ `sf_delete_scratch_org`/`sf_install_package` both probing intentionally-nonexistent targets), 1 skipped.
519
+
520
+ ### Investigated — the Agentforce conversation test is still unproven, now for precisely two reasons
521
+
522
+ Went further than any previous session on both blockers named in the last release:
523
+
524
+ **Custom agent actions (`GenAiFunction`) are conclusively an org licensing ceiling, not a code
525
+ defect.** Beyond re-confirming every `invocationTargetType` is still rejected against a fresh
526
+ confirmed-Active flow, this session found and tried the specific fix: `demo-org` has two relevant
527
+ Permission Set Licenses sitting unused (`Agentforce Service Agent User` — 200 seats, 0 assigned;
528
+ `Agent platform builder` — 5 seats, 0 assigned). Assigning `Agent Platform Builder`'s permission set
529
+ succeeded but changed nothing. Assigning the one that actually gates this
530
+ (`AgentforceServiceAgentUser`, and its `AgentforceServiceAgentBase` companion) was rejected outright by
531
+ Salesforce: *"Ajay Semwal can't be assigned the Agentforce Service Agent User permission set license,
532
+ because Ajay Semwal's user license doesn't support it."* That's a base User License / edition ceiling,
533
+ not a permission set gap — nothing reachable from this MCP server's tools can change it.
534
+
535
+ **A live conversation turn needs a Setup UI session, and none exists in this MCP server by design.**
536
+ The Agent API requires an ACTIVE agent and a connected app's Consumer Key/Secret. Both dead ends above
537
+ converge here: agent activation has no API path (see the `activateAgent` fix above), and Consumer
538
+ Secret can only be viewed once from Setup → App Manager, never via any API. Getting either therefore
539
+ requires a human (or a browser-automation session with its own explicit device-confirmation step) in
540
+ Salesforce Setup — outside what a stdio metadata MCP server does. Recorded as a precise, actionable
541
+ handoff rather than an unexplained skip.
542
+
543
+ **Also tried and reverted:** attempting to pre-stage a working connected app for that eventual Setup UI
544
+ session surfaced a third, unresolved anomaly — connected apps created via `upsertMetadata` (Salesforce's
545
+ own SOAP response says `success: true`) became unretrievable via `readMetadata`/`listMetadataType`/SOQL
546
+ within roughly 10–20 minutes, while apps checked within seconds of creation persisted and deleted
547
+ cleanly. Root cause not identified (not a scope-literal issue — reproduced with the corrected literals
548
+ above). Flagged for follow-up, not fixed; treat any connected app in `demo-org` as unverified until
549
+ checked shortly after creation.
550
+
551
+ **QA housekeeping:** cascade-deleted 15 of the 22 leftover `QA*`-prefixed Agentforce agents (plus their
552
+ topics/planners/actions) accumulated across this and prior sessions, via the previously-unwired
553
+ `deleteAgent` service function. 8 remain — Salesforce's Metadata API delete rejects them with a generic
554
+ internal-server error (`ErrorId ...`, not an application-level error), on both batch and individual
555
+ retry; not something a client-side retry fixes. Today's own flow/Apex test artifacts were left in place
556
+ (flows are Active and must be deactivated before delete; Apex class deletion via this org's Metadata
557
+ API delete path is rejected as "not available for this organization") — low-risk, consistent with this
558
+ project's existing no-teardown QA methodology. The broader "few hundred QA artifacts, no teardown
559
+ suite" backlog noted previously is real, unaddressed, and grew further from this session's full
560
+ `test-suite.mjs` run (required to confirm no regressions) — still a separate, unstarted engineering task.
561
+
562
+ ## [2.8.5] - 2026-07-30
563
+
564
+ ### Fixed — 8 more bugs, from widening Agentforce QA past the four headline tools
565
+
566
+ Asked whether the v2.8.4 Agentforce testing was thorough, the honest answer was no: 4 of 8 agent-related tools, no runtime assertions, no content-level verification, only 2 of 5 action types, and a parameter (`actionNames`) that shipped without ever being exercised. Closing those gaps found eight more real bugs.
567
+
568
+ **`sf_retrieve_metadata` could never return metadata to anyone.** It started an async retrieve, handed back a job id, and the package contained no tool that calls `checkRetrieveStatus` — while the tool's own description told callers the zip was "available via Metadata API checkRetrieveStatus". Added `pollRetrieveStatus`/`retrieveMetadataAndWait`: the tool now waits for the job, unpacks the zip, and returns each file's path and source. This also unblocked every content-level assertion in the QA suites, which is how several bugs below were confirmed.
569
+
570
+ **`sf_create_einstein_bot` was non-functional — five separate defects, each hidden behind the previous one:**
571
+ - `<defaultLocale>` is not a `Bot` field ("Element defaultLocale invalid at this location").
572
+ - The ML domain element is `<botMlDomain>` with a `<name>` child, not `<mlDomain>` with `<developerName>`.
573
+ - The Bot and its BotVersion were upserted in two separate calls, but a Bot alone is rejected with "Bot needs at least one Bot version" — the sequence could never succeed. The version is now embedded as `<botVersions>` in the same payload.
574
+ - `BotStep` takes `<type>`, not `<conversationStepType>`, and its text belongs in `<botMessages><message>`, not a flat `<botMessage>`.
575
+ - `<isGoalStep>` is not a `BotDialog` field; it is `isPlaceholderDialog`.
576
+
577
+ **`sf_create_einstein_prediction` was also non-functional, and its XML is now correct element-by-element:** `<label>` → `<masterLabel>`; `<predictionType>` → `<type>`, whose `AIPredictionType` enum accepts only `BinaryClassification` and `Regression` (`Classification` and `Numeric` are rejected outright, so the schema's friendly `Classification` alias is now mapped before it reaches the XML); `<developerName>` and `<aiApplicationDeveloperName>` are both required, the latter naming an existing AIApplication; `<predictionField>` is a plain string, not a `fieldName`/`objectName` pair; and `<positiveLabel>`, `<negativeLabel>` and `<active>` are not elements of this type at all. `targetField` was a **required** schema parameter the XML never used — it is the predicted field and now populates `<predictionField>`.
578
+
579
+ **Verification widened, not just repeated:**
580
+ - `qa-agentforce.mjs` grew from 27 to 47 checks: content-level assertions that the deployed Bot really carries `persona` as `<role>`, `company`/`toneType`, the org-valid `agentType`/`type` enums and **no** `systemPrompt`; that the deployed planner carries every requested topic, `AiCopilot__ReAct` and a description; all five action types asserting their own `invocationTargetType` mapping; the previously-untested `actionNames`; planner replace semantics (verified by deploying a second action-free topic, so it no longer depends on actions the org cannot create); and a runtime section asserting the agent→planner link survives into deployed metadata and reading real `BotVersion` status from the org.
581
+ - New `qa-agentforce-adjacent.mjs` covers the four tools nothing had ever driven: `sf_create_einstein_bot`, `sf_create_bot_routing`, `sf_create_einstein_prediction`, `sf_assign_skill_to_agent`. `sf_assign_skill_to_agent` passes fully, including its negative cases.
582
+
583
+ **Results against `demo-org`:** `qa-agentforce.mjs` 30 passed / 1 failed / 16 skipped (the failure a transient `fetch failed`, not a defect); `qa-agentforce-adjacent.mjs` 6 passed / 0 failed / 5 skipped.
584
+
585
+ **Honest limits, unchanged and still not papered over.** No conversation test exists: the Agent API needs an ACTIVE agent plus a connected app with client credentials, and creating a connected app is a change to the org this suite should not make unasked — so "agent answers a real conversation turn" is a recorded skip. `demo-org` cannot create GenAiFunction actions, cannot deploy classic Bots ("You don't have access to bots of type Bot" — it licenses Agentforce agents instead), has no Queue for bot routing, and has no AIApplication for predictions. Those are org boundaries, recorded as skips and never as passes. The corrected Einstein Bot and Prediction XML is therefore structurally verified element-by-element against a live org, but neither has completed a successful end-to-end deploy anywhere.
586
+
587
+ ## [2.8.4] - 2026-07-30
588
+
589
+ ### Fixed — 6 Agentforce bugs; agent creation was broken end to end
590
+
591
+ Agentforce had three tests in `test-suite.mjs` and none of them could fail. They imported `createAgent`/`createAgentTopic` from `services/salesforce.js`, which **no MCP tool calls** — `src/tools/agentforce.ts` builds its own XML inline, so the tests covered an orphaned parallel implementation. Each was also wrapped in `try/catch` returning `success: true`, with results passed through `orgLimitFallback()`, which counts HTTP 500 / 404 / NOT_FOUND as a pass. `sf_create_agent_action`'s test never called the tool at all — it hand-rolled XML aimed at a flow named `NonExistentFlow`. And `sf_create_agent_planner`, the step without which an agent routes nothing, had no test whatsoever.
592
+
593
+ New `qa-agentforce.mjs` registers the four real tool handlers against a stub server, validates arguments through each tool's own zod schema, invokes the real handler, and verifies results with SOQL. Bugs it found:
594
+
595
+ - **`sf_create_agent_planner` deployed `GenAiPlanner`, a metadata type that no longer exists.** Salesforce superseded it with `GenAiPlannerBundle` and rejects the old type with "Not available for deploy for this API version". Step 4 of 4 therefore failed 100% of the time. Rewritten against the live org: `description`, `masterLabel` and `plannerType` are all required, `AiCopilot__ReAct` is the only accepted `PlannerType` (`ReAct`, `Standard` and `AiCopilot__Standard` are all rejected by the enum), topics belong in `<genAiPlugins><genAiPluginName>`, and `<botName>` is invalid on the bundle entirely.
596
+ - **The agent→planner link was never written, by any tool.** Because `<botName>` isn't valid on the planner, nothing connected the Bot to it — so even a successful planner left the agent inert. The link lives on the Bot, as `<conversationDefinitionPlanners><genAiPlannerName>`, verified accepted against the org. `sf_create_agent` gained an optional `plannerName` to write it; since that tool is idempotent, the sequence is now 5 steps, ending in a second `sf_create_agent` call. Its success message says so explicitly instead of claiming the agent is "fully wired".
597
+ - **`sf_create_agent` broke outright whenever `instructions` was passed.** It emitted `<systemPrompt>` inside `<botVersions>`, which Salesforce rejects: "Element systemPrompt invalid at this location in type BotVersion". Isolated by deploying one agent per optional parameter — minimal, `+description`, `+company`, `+persona` and `+tone` all succeed; only `+instructions` fails. `BotVersion` has no system-prompt field (confirmed by retrieving real Bot/BotVersion metadata from the org), so the parameter was removed and its description now points callers at `sf_create_agent_topic`'s `instructions`, which is where agent guidance actually belongs. CLAUDE.md's field-placement note listed `systemPrompt` as valid and has been corrected.
598
+ - **`sf_create_agent`'s `type` parameter was a no-op whose only two allowed values were both invalid.** It was schema-validated and passed to `buildBotDeployZip`, which ignores it — the XML hardcodes `agentType`/`type`. Its enum offered `Default` and `EinsteinCopilot`, which the code's own comment records as rejected by Salesforce. Removed.
599
+ - **`sf_create_agent_action` required two parameters it never reads.** `agentName` and `topicName` were mandatory while the schema's own descriptions called them "informational only, NOT written to the action XML". A caller correctly reasoning that the action XML doesn't need them got a hard zod rejection. Both are now optional.
600
+ - **`sf_create_agent_action`'s failure message sent callers to re-check a target that was already fine.** "Specify a valid invocationTarget and invocationTargetType" is also what Salesforce returns when custom agent actions aren't enabled in the org at all. The message now says so when it sees that specific error.
601
+
602
+ **Verified against `demo-org`:** agent deploys with every remaining optional field, both removed parameters are now rejected by the strict schema, the planner deploys as a `GenAiPlannerBundle` and appears in `GenAiPlannerDefinition`, a planner naming a nonexistent topic is still rejected, and the agent re-deploys with the planner link and reports it.
603
+
604
+ **Explicitly not fixed, because it is not a defect:** `sf_create_agent_action` cannot create actions in `demo-org`, but the cause is the org, not the tool. Hand-deploying spec-compliant `GenAiFunction` XML outside the suite — flat and bundle layouts, and every documented `invocationTargetType` (`flow`, `apex`, `standardInvocableAction`, `customInvocableAction`, `flowService`, plus capitalised variants) against a confirmed-Active flow, a confirmed-Active `@InvocableMethod` class, and standard invocable actions — was rejected identically every time. An earlier draft of this work called that a product bug; it isn't, and the suite now detects the condition and skips the dependent checks rather than reporting nine false failures. `sf_create_agent_topic` is likewise uninvolved: a zero-action topic deploys clean, and its failures here were downstream of actions that could not be created.
605
+
606
+ Also corrected in this release's own record: an earlier commit claimed `demo-org` had no Agentforce at all, based on a `sf org list metadata-types` call that returned 261 types without `Bot`. The org reports 266 types with `Bot` present — that probe ran while the org was still provisioning.
607
+
608
+ ## [2.8.3] - 2026-07-30
609
+
610
+ ### Fixed — 10 Flow bugs, found by testing the SOAP builder for the first time
611
+
612
+ v2.8.2 reported "30/30 Flow scenarios passing," and that was true, but it hid a structural gap: `sf_create_flow` builds XML with `buildFlowXml` (SOAP/upsertMetadata) while `sf_create_flow_from_xml` and every existing suite use `buildFlowDeployXml` (ZIP/deploy). These are two independent ~300-line generators, and **no suite had ever exercised the SOAP one**. Neither suite passed `triggerObject`/`triggerType` either — grep returns zero hits — so record-triggered flows, the single most common thing an admin builds, were never tested at all.
613
+
614
+ Wrote `qa-flow-comprehensive.mjs` to close both gaps: 46 flow definitions run through **both** builders, plus runtime verification that creates real records and asserts the flow actually fired. 142 checks. Bugs it found:
615
+
616
+ - **`flowType` was written straight into `<processType>`, so every record-triggered and scheduled flow failed to deploy** — `FlowProcessType` has no `RecordTriggeredFlow` or `ScheduledFlow` member; both are `AutoLaunchedFlow` at the process level, distinguished only by the `<start>` element's `triggerType`. Added `toProcessType()` in both builders.
617
+ - **Scheduled flows had no schedule at all** — the start element only handled record triggers, so a `ScheduledFlow` deployed as a plain autolaunched flow that never ran. Added `<schedule>` with new `scheduleFrequency`/`scheduleStartDate`/`scheduleStartTime` params.
618
+ - **`recordTriggerType` was hardcoded to `CreateAndUpdate`** — create-only and update-only triggers were unreachable. Now a parameter, and omitted entirely for `RecordBeforeDelete`, which Salesforce rejects it on.
619
+ - **No Update Records element existed** — Get/Create/Delete were present but not Update, so the most common admin pattern ("when the Opportunity closes, update its Account") could not be built. Added in both addressing modes, with a guardrail on the `inputReference` + `inputAssignments` combination Salesforce forbids.
620
+ - **No Formula, Constant, or Text Template resources existed.** Added to both builders; constants emit type-correct value tags instead of coercing everything to `stringValue`.
621
+ - **`IsNotNull` was rejected by Salesforce everywhere it appeared** — `FlowComparisonOperator` has no such member ("is not null" is `IsNull` compared against `false`), yet the schema advertises it. Now translated in all six sites that accept an operator. The first fix attempt landed in the SOAP builder only and inverted the boolean without changing the operator name in the ZIP builder; the new suite caught that on its first run, and the filter paths in *both* builders turned out to have the same untranslated operator. All six now share one `nullOperatorXml()` helper so this cannot land one-sided again.
622
+ - **Null checks in `GetRecords`/`UpdateRecords` filters emitted an empty `<stringValue>`** where Salesforce requires a `booleanValue`.
623
+ - **`recordUpdates` emitted `<inputReference>{!$Record}</inputReference>`** — merge syntax belongs in formula and text contexts only; an `inputReference` takes the bare element name, so this resolved to nothing.
624
+ - **A `fieldUpdates` formula was inlined as `<formula>` inside `<inputAssignments><value>`**, but `FlowElementReferenceOrValue` has no `formula` child. It is now hoisted into a real `<formulas>` resource the assignment references by name. New `fieldUpdates.formulaDataType` controls its return type — previously formulas were always `String`, and the field was read by the service but missing from the strict zod schema, so no caller could set it.
625
+ - **Latent ordering bug:** the approval-submit `actionCall` and the `fieldUpdates` `recordUpdate` were emitted outside the type-grouping logic, so combining `submitForApprovalProcessName` with an `ApexAction` or `SendEmailAlert` produced non-contiguous `<actionCalls>`, which MDAPI rejects. Both are now merged into their type groups.
626
+ - The `elements` description still advertised `Wait` and `PlatformEvent`, which v2.5.x removed from the enum — an LLM reading it would emit elements zod rejects.
627
+
628
+ **Final run: 142 passed, 0 failed** against `demo-org` — 10 flow types/structures, 20 record-trigger configurations, 22 variable/resource cases, 68 element cases, 12 connector and guardrail cases, 5 lifecycle (activate/versions/deactivate), and 5 runtime assertions proving before-save stamping, after-save related-record creation, entry-criteria suppression, Loop iteration, and Update Records all actually execute in the org.
629
+
630
+ One suite failure was a test bug, not a product defect, and is recorded as such: the entry-criteria check compared the `Industry` picklist with a bare `=`, which Salesforce rejects in favour of `ISPICKVAL`. The tool passed the caller's formula through verbatim and surfaced Salesforce's real reason — correct behavior, so the test was fixed rather than the code.
631
+
632
+ `server.json`'s description still claimed 219 tools; corrected to 221.
633
+
634
+ ## [2.8.2] - 2026-07-30
635
+
636
+ ### Fixed — 5 more real bugs found by full regression testing across all 221 tools
637
+
638
+ Ajay asked for thorough regression testing against every tool with edge cases, not just the tools touched in v2.8.0/2.8.1. Ran the repo's full `test-suite.mjs` (215 tests, all 221 tools either directly exercised or reviewed) against `demo-org`, plus the dedicated Flow QA suite (33 scenarios). Found and fixed:
639
+
640
+ - **`sf_create_global_value_set`** — schema didn't enforce or document the required `__gvs` fullName suffix; Salesforce rejects GlobalValueSet without it. Added regex validation matching the existing `__c`/`__mdt` pattern used by other metadata types, plus a description note.
641
+ - **`sf_create_business_process`** — XML included a `<label>` element that doesn't exist in the `BusinessProcess` Metadata API type at all (Salesforce: "label invalid at this location"). Removed it; `label` still feeds the `description` fallback.
642
+ - **`sf_create_dashboard`** — built XML inline without escaping (`ui.ts`), unlike every other metadata builder in the codebase. A `&` in the title broke the deploy outright. Applied the `x()` escaping helper to `fullName`, `title`, `description`, `runningUser`, and component `reportApiName`/`header`/`footer`.
643
+ - **`sf_create_escalation_rule`** — same missing-escaping issue (`automation.ts`) on `formula`, `assignedTo`, `notifyTo`, `template`, `ruleName`. `formula` is especially exposed since Salesforce formulas commonly contain `<`, `>`, `&&`.
644
+ - **`sf_create_field_dependency`** — two-layer bug. (1) Wrapped `controllingField`/`valueSettings` in a `<fieldDependency>` element that doesn't exist on `CustomField` (Salesforce: "fieldDependency invalid at this location") — they belong directly inside `<valueSet>`. (2) Even after that fix, a from-scratch SOAP update carrying only the new dependency info failed with "Could not resolve standard field's name" — Salesforce needs the field's full existing metadata (label, type, current values) present in the same update. Rewrote using the same Tooling-API read-merge-PATCH pattern already proven by `addPicklistValues`, instead of hand-building SOAP XML.
645
+
646
+ Also fixed **`getFreshTokenFromCLI`** (JWT/CLI auth path): didn't check `err.stdout` on a non-zero exit, so a transient CLI hiccup (e.g. an update-nag banner polluting stdout) discarded an otherwise-valid access token. Same root cause as the `execSfCli` fix in 2.8.1, applied to the one auth code path that predates it.
647
+
648
+ **Test coverage added:** `test-suite.mjs` gained a `sf_create_field_dependency` test (previously imported but never exercised) and its `sf_create_global_value_set`/`sf_delete_scratch_org` test data bugs were fixed (wrong fullName suffix, wrong parameter name — test bugs, not product bugs). `sf_update_dashboard`'s test no longer assumes a folder literally named "Dashboards" exists (org-specific sample-data assumption); it now discovers a real Dashboard folder first.
649
+
650
+ **Final confirmation run: 212 passed, 2 expected negative-test outcomes (deleting a nonexistent scratch org / installing a fake package ID — both now correctly surface the real Salesforce rejection reason instead of a generic error, proof the 2.8.1 error-handling fix works), 1 skipped (OmniStudio, not enabled in this org), 215 total.**
651
+
652
+ **Flow QA suite (33 scenarios, `qa-flow-test.mjs`) — fully resolved, zero code bugs found:** Initial run showed 18 passed / 15 failed. Root-caused every failure instead of assuming they were code defects: 11 traced to the test assuming specific Account/Opportunity records ("Apex Technologies", "Opp-Acct1-1") existed in the org — leftover from whatever org this suite was originally built against, absent in `demo-org` (which only has standard DE sample data like "Edge Communications"). Created the missing seed records; those 11 passed immediately with no code changes. 1 (T01) was pure infra flakiness — a Windows `cmd.exe` spawn timeout unrelated to Salesforce, passed clean on retry. The remaining 3 (`PROD_Get_Account_Overview` etc.) test pre-existing production flows this suite never creates — they don't exist in any org this suite would be pointed at fresh, confirmed out of scope. **Final: 30/30 real scenarios passing.** This also means the "Known Bugs Pending Fix" list in CLAUDE.md (Loop elements, cross-variable filters, Decision XML, GetRecords gaps, missing inline-XML support, missing `sf_create_flow_from_xml`) was entirely stale — none of it reproduced. Retired that list in CLAUDE.md with a dated note explaining why, rather than leaving inaccurate bug reports in the project's own docs.
653
+
654
+ Also fixed `qa-flow-test.mjs`'s own test harness bug: hardcoded `--target-org secondorg` in its Apex-verification helper, now reads `SF_ALIAS` with a fallback.
655
+
656
+ ## [2.8.1] - 2026-07-29
657
+
658
+ ### Fixed — 5 real bugs found by thorough live-org testing of v2.8.0, same day
659
+
660
+ Ajay pointed live testing at a working CLI-authenticated org (`demo-org`) after v2.8.0 shipped without it, then asked for thorough testing before this patch went out. Testing surfaced genuine defects:
661
+
662
+ - **`sf_run_code_scanner` returned 404 against every org** — its Tooling API query path double-prefixed `/services/data/v66.0` (the HTTP client's base URL already includes it). Copied this bug from `sf_scan_apex_antipatterns`.
663
+ - **`sf_scan_apex_antipatterns` has had the identical 404 bug since it shipped in v2.7.0 (2026-07-23)** — it was never tested against a live org before now, so this went undetected for 6 days across two releases. Fixed alongside the copy.
664
+ - **`sf_run_code_scanner`'s Java-fallback was cosmetic, not functional** — restricting `--rule-selector` to Java-free engines doesn't stop `code-analyzer` from still trying to *instantiate* the PMD/CPD/SFGE engines first, so it threw 3 "Critical" `UninstantiableEngineError` pseudo-violations even while the response's own `note` field claimed those engines "were skipped." Fixed by generating a `code-analyzer.yml` that explicitly sets `disable_engine: true` on each when Java isn't detected — verified clean (0 fake violations) against `demo-org`.
665
+ - **Every SF-CLI-wrapping function (`installPackage`, `uninstallPackage`, `createPackage`, `createPackageVersion`, `deleteScratchOrg`, `createNewScratchOrg`) swallowed the actual Salesforce error on failure** — Node's `execSync` puts the CLI's `--json` error payload on `err.stdout`, not `err.message`, so callers only ever saw a generic "Command failed: sf package ..." with no reason. Extracted a shared `execSfCli()` helper that parses `err.stdout` first; verified live by attempting to uninstall `demo-org`'s installed package and correctly receiving the real Salesforce reason ("Unable to delete custom app. Profiles are using this custom app as default...") instead of the useless wrapper text.
666
+ - **`sf_install_package` (pre-existing, not something this session touched originally) hung/force-failed on any package requesting third-party site access** — `sf package install` shows an interactive "Grant access?" confirmation for Remote Site Settings/CSP, which `execSync`'s non-interactive session can never answer, throwing `ExitPromptError`. Fixed by always passing `--no-prompt` (there's no scenario where an MCP tool call can answer an interactive prompt, so this is correct unconditionally, not just a default).
667
+
668
+ **All fixes verified against `demo-org` with real success AND failure paths, not just one or the other:**
669
+ - `sf_run_code_scanner` / `sf_scan_apex_antipatterns`: real findings returned (4 classes scanned, 0 violations, accurate `note`)
670
+ - `sf_uninstall_package`: failure path confirmed — real multi-component Salesforce rejection reason now surfaces correctly
671
+ - `sf_install_package`: **full success path confirmed** — real install against `demo-org` returned `Status: "SUCCESS"` end to end, only possible after the `--no-prompt` fix
672
+ - Broader regression sanity: unrelated, untouched tools (`sf_query_records`, `sf_describe_object`) spot-checked against the same org to confirm the shared auth/HTTP client plumbing wasn't affected
673
+
674
+ **Known remaining gap, documented not hidden:** `createNewScratchOrg`/`deleteScratchOrg`/`createPackage`/`createPackageVersion` could not be exercised live — `demo-org` is not Dev Hub-enabled, and creating one is out of scope for a same-day bug-fix pass. Their `execSfCli` usage is mechanically identical to `installPackage`'s (now proven), so risk is low, but this is explicitly unverified, not silently assumed fine.
675
+
3
676
  ## [2.8.0] - 2026-07-29
4
677
 
5
678
  ### Added — 2 new tools closing gaps found in a fresh competitive scan