salesforce-metadata-mcp 2.12.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +253 -0
- package/README.md +129 -0
- package/SECURITY.md +25 -5
- package/dist/index.js +40 -3
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +1528 -1164
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +122 -29
- package/dist/schemas/index.js.map +1 -1
- package/dist/services/deployment.d.ts.map +1 -1
- package/dist/services/deployment.js +79 -5
- package/dist/services/deployment.js.map +1 -1
- package/dist/services/guard.d.ts +45 -0
- package/dist/services/guard.d.ts.map +1 -0
- package/dist/services/guard.js +177 -0
- package/dist/services/guard.js.map +1 -0
- package/dist/services/impact.d.ts.map +1 -1
- package/dist/services/impact.js +9 -1
- package/dist/services/impact.js.map +1 -1
- package/dist/services/salesforce.d.ts +29 -0
- package/dist/services/salesforce.d.ts.map +1 -1
- package/dist/services/salesforce.js +1391 -378
- package/dist/services/salesforce.js.map +1 -1
- package/dist/services/tooling.d.ts.map +1 -1
- package/dist/services/tooling.js +28 -3
- package/dist/services/tooling.js.map +1 -1
- package/dist/tools/automation.d.ts.map +1 -1
- package/dist/tools/automation.js +18 -6
- package/dist/tools/automation.js.map +1 -1
- package/dist/tools/data.d.ts.map +1 -1
- package/dist/tools/data.js +4 -2
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/experience.d.ts.map +1 -1
- package/dist/tools/experience.js +8 -1
- package/dist/tools/experience.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +47 -33
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ui.d.ts.map +1 -1
- package/dist/tools/ui.js +16 -4
- package/dist/tools/ui.js.map +1 -1
- package/dist/toolsets.d.ts +205 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +709 -0
- package/dist/toolsets.js.map +1 -0
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,258 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.2.0] - 2026-09-23
|
|
4
|
+
|
|
5
|
+
A full-surface QA release. Every one of the 233 tools was called individually over real MCP traffic
|
|
6
|
+
(`tools/call` → schema → service → org) against a Developer Edition org and a scratch org with
|
|
7
|
+
Communities, Service Cloud, Knowledge and Omni-Channel enabled, and each result was checked in the
|
|
8
|
+
org with the sf CLI — never by the server under test. **3.1.0 and 3.1.1 were never published to npm,
|
|
9
|
+
so this release also carries them** (lazy-toolset fallback, `sf_query_records` limit, `sf_find_tool`
|
|
10
|
+
search — see below).
|
|
11
|
+
|
|
12
|
+
Result on the scratch org: **170 PASS / 41 feature-unavailable / 4 org-limit / 17 open**, up from a
|
|
13
|
+
baseline of 100 PASS / 114 failing on the first sweep. None of the 17 open items is a tool sending a
|
|
14
|
+
wrong payload for a feature the org has: they are cascades (OmniStudio not installed, an org cap
|
|
15
|
+
reached earlier in the run), unlicensed features (Agentforce, Einstein, Digital Engagement) and
|
|
16
|
+
fixture gaps. **No tool claims success without doing the work.**
|
|
17
|
+
|
|
18
|
+
### Fixed: tools that silently replaced a whole component
|
|
19
|
+
`upsertMetadata` replaces what it names. Eight tools sent only the part they were changing, so they
|
|
20
|
+
either failed or would have wiped the rest: compact layouts / list views / search layouts, queue
|
|
21
|
+
routing config, connected-app OAuth policy, the five Workflow children (a `Workflow` upsert replaces
|
|
22
|
+
an object's entire workflow — the old 500s were the only reason nothing was lost), business hours,
|
|
23
|
+
holidays, LWC Jest tests, and forecasting settings. Each now addresses its component directly or
|
|
24
|
+
reads, modifies and writes it back.
|
|
25
|
+
|
|
26
|
+
### Fixed: tools that could not succeed at all
|
|
27
|
+
Around 50 tools, including: custom tabs, custom notification types, roles (access-level enum and
|
|
28
|
+
forecast-manager default), FlexiPages, SAML SSO, change data capture, quick/global actions, auth
|
|
29
|
+
providers with custom endpoints, sharing rules, dashboards, path assistants, scheduled flows,
|
|
30
|
+
duplicate rules (a bare Allow is rejected — Allow now carries Report), data category groups
|
|
31
|
+
(`KnowledgeArticleVersion`), service territories (operating hours), Experience sites (now created
|
|
32
|
+
through the Connect API from a real template), and package versions (`--installation-key-bypass`).
|
|
33
|
+
The full create → version → install → uninstall packaging chain is verified end to end.
|
|
34
|
+
|
|
35
|
+
### Fixed: errors nobody could act on
|
|
36
|
+
Bare 404s and 500s now name the cause: sandboxes and change sets on editions without them, DevOps
|
|
37
|
+
Center not installed, a connected app on an org that only allows External Client Apps, an Experience
|
|
38
|
+
page (Salesforce has no REST endpoint for creating one — the tool now says so), scheduled-job compile
|
|
39
|
+
errors, and upserts whose external-ID field is invisible to the running user.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- `sf_create_forecast_hierarchy`: `forecastingType` is now a string checked against the org (the old
|
|
43
|
+
enum offered names that do not exist). Unknown names return the org's list.
|
|
44
|
+
- `sf_create_experience_site` returns the site URL, Builder link and the Site API names Salesforce
|
|
45
|
+
chose (it derives them from the label).
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- `qa-full-sweep.mjs` and friends: the full-surface sweep, with a four-way verdict
|
|
49
|
+
(PASS / UNAVAIL / LIMIT / BUG). `QA_DEVHUB=<alias>` enables the real packaging chain.
|
|
50
|
+
- `qa-workflow-children.mjs`, `qa-translation-merge.mjs`, `qa-scratch-fixes.mjs`: pins for the
|
|
51
|
+
fixes above.
|
|
52
|
+
|
|
53
|
+
## [3.1.1] - 2026-09-09
|
|
54
|
+
|
|
55
|
+
Three bugs reported against a Claude Desktop session. **None were introduced by 3.1.0.** Two date to
|
|
56
|
+
the first public release (`f808b50`, v2.6.5) and one to the lazy-toolsets change in 3.0.0
|
|
57
|
+
(`479a40d`) — traced with `git log -S`/`-L`, not assumed.
|
|
58
|
+
|
|
59
|
+
### Fixed: `sf_query_records` ignored its own `limit` (since v2.6.5)
|
|
60
|
+
|
|
61
|
+
`limit` was only interpolated into the SOQL `queryRecords` *builds* from `objectApiName`/`fields`.
|
|
62
|
+
Every call through `sf_query_records` passes a `query` string, which takes the other branch, so the
|
|
63
|
+
parameter — and its documented default of 200 — did nothing. A caller setting `limit: 100` to stay
|
|
64
|
+
small got 1,323 rows and no warning; the failure was silent and failed open.
|
|
65
|
+
|
|
66
|
+
The cap is now applied by rewriting the outgoing SOQL, so it reduces what Salesforce sends rather
|
|
67
|
+
than only what the tool returns. **Precedence is `min(parameter, LIMIT in the query)`**: the
|
|
68
|
+
parameter is a guardrail, so a `LIMIT` in the query string may tighten it but never raise it, while
|
|
69
|
+
a caller who explicitly wrote `LIMIT 5` still gets 5.
|
|
70
|
+
|
|
71
|
+
A cap that is applied but not reported would just be a silent truncation — the same bug wearing a
|
|
72
|
+
different hat — so the response now carries `appliedLimit`, `limitSource` and `truncated`, and the
|
|
73
|
+
message says when rows were cut off. `truncated` is a fact, not a guess: one row beyond the cap is
|
|
74
|
+
fetched to distinguish "exactly N matched" from "more than N matched", then discarded. Aggregate
|
|
75
|
+
(`COUNT()`), `OFFSET` and `FOR UPDATE` queries are handled without being rewritten into invalid SOQL.
|
|
76
|
+
|
|
77
|
+
### Fixed: `sf_find_tool` returned nothing for multi-word queries (since v3.0.0)
|
|
78
|
+
|
|
79
|
+
The search required *every* token to appear in a tool name, so any natural-language phrase returned
|
|
80
|
+
`matches: []` — including two of the tool's own documented examples. That is the worst moment to
|
|
81
|
+
return an empty list: this tool is reached precisely when someone cannot find a capability and is
|
|
82
|
+
describing it in words, and an empty result reads as "no such tool exists" rather than "rephrase".
|
|
83
|
+
|
|
84
|
+
Matching is now scored rather than filtered — any token can match, ranked by how many hit, with an
|
|
85
|
+
exact tool name always first. Tool **titles and descriptions are searched too**, at a lower weight
|
|
86
|
+
than names, because a phrase like *"run some apex code in the org"* cannot be resolved from names
|
|
87
|
+
alone (`sf_run_apex_tests` wins on tokens while the caller means `sf_execute_anonymous_apex`).
|
|
88
|
+
Common filler words are dropped.
|
|
89
|
+
|
|
90
|
+
Two guards came with it, since a looser match could otherwise undo the point of lazy toolsets:
|
|
91
|
+
results are capped at the 25 best matches, and a single search auto-loads at most 3 toolsets — those
|
|
92
|
+
from the top-scoring band only. Lower-ranked matches are named in the response instead of loaded.
|
|
93
|
+
|
|
94
|
+
### Fixed: Salesforce error messages truncated mid-sentence (since v2.6.5)
|
|
95
|
+
|
|
96
|
+
API error bodies were cut with a flat `slice(0, 300)`, and Salesforce puts the actionable part at
|
|
97
|
+
the end — *"…be sure to append the '__c' after the entity name. Please refer to…"* arrived as
|
|
98
|
+
*"…Please ref"*. A cap is still wanted, since an error body can be a whole HTML page, so oversized
|
|
99
|
+
errors now keep a generous head **and** the tail, with a count of what was dropped.
|
|
100
|
+
|
|
101
|
+
### Notes
|
|
102
|
+
|
|
103
|
+
- `sf_find_tool` now attaches input schemas to the top 3 **ranked** matches rather than only when
|
|
104
|
+
the whole result set was ≤3. Broader matching pushed even exact-name queries past that threshold,
|
|
105
|
+
which cost them the schema `sf_call_tool` needs.
|
|
106
|
+
- **Inlined schemas are now bounded by size, not just count** — found by measuring the release
|
|
107
|
+
rather than from a report. A count is not a bound: `sf_create_flow` serialises to ~28KB, so
|
|
108
|
+
`sf_find_tool("create flow")` was returning ~8,500 tokens, most of what a whole default startup
|
|
109
|
+
costs. v3.0.0 had moved that tool out of the default toolset for exactly this reason, and inlining
|
|
110
|
+
it on search quietly put it back. Oversized schemas are now named with a pointer to
|
|
111
|
+
`sf_tool_schema` instead of being sent. Measured after: 8,532 → 1,721 tokens for the same call,
|
|
112
|
+
and every `sf_find_tool` response now lands between ~1.4k and ~2.5k tokens.
|
|
113
|
+
- New `qa-query-limit.mjs` (17 checks) covers the cap, its precedence, truncation reporting, the
|
|
114
|
+
aggregate/OFFSET edge cases and the error-text length. `qa-toolsets.mjs` gains natural-language
|
|
115
|
+
search coverage and the auto-load bounds (26 → 38 checks).
|
|
116
|
+
- Verified against `demo-org` before and after: `limit: 3` returned 1,764 rows before the fix and 3
|
|
117
|
+
after.
|
|
118
|
+
|
|
119
|
+
## [3.1.0] - 2026-09-09
|
|
120
|
+
|
|
121
|
+
### Fixed: loaded toolsets could be unreachable on clients that ignore `list_changed`
|
|
122
|
+
|
|
123
|
+
Reported against Claude Desktop: `sf_load_toolset(["apex","data"])` returned `success: true` with
|
|
124
|
+
`residentTools` moving 18 → 47, `sf_find_tool` confirmed the tools existed in a loaded toolset, and
|
|
125
|
+
not one of them was ever callable. The entire write surface of the server — anonymous Apex, record
|
|
126
|
+
creation, bulk insert — was unreachable for a whole session while every status payload reported
|
|
127
|
+
healthy. Reads kept working, because the initially-resident set was never affected.
|
|
128
|
+
|
|
129
|
+
The server was not violating the protocol. It declares `tools.listChanged`, emits exactly one
|
|
130
|
+
`notifications/tools/list_changed` per load, and `tools/list` genuinely changes — all now asserted
|
|
131
|
+
end-to-end in `qa-client-refetch.mjs`. The client never re-fetched.
|
|
132
|
+
|
|
133
|
+
Being protocol-correct was not enough. Lazy toolsets put 210 of 228 tools behind an *optional*
|
|
134
|
+
client behaviour with no fallback when it is absent, so v3.1.0 adds one that needs no re-fetch:
|
|
135
|
+
|
|
136
|
+
- **`sf_call_tool({ tool, arguments })`** — invokes any tool by name whether or not its toolset is
|
|
137
|
+
loaded and whether or not it appears in the client's tool list. Arguments are validated against
|
|
138
|
+
the same zod schema, and the handler it calls is the guard-wrapped one, so the production guard
|
|
139
|
+
applies exactly as on a direct call. It cannot invoke itself or the other meta-tools.
|
|
140
|
+
- **`sf_tool_schema({ tool })`** — returns any tool's input schema, loaded or not, so a model can
|
|
141
|
+
construct that call for a tool it cannot see.
|
|
142
|
+
- **`sf_find_tool`** now returns input schemas inline when a query matches three tools or fewer,
|
|
143
|
+
collapsing find → call into a single round trip. Wider searches still return names only, so this
|
|
144
|
+
does not reintroduce the context cost lazy toolsets exist to avoid.
|
|
145
|
+
- **`sf_load_toolset`** now checks whether the client re-fetched `tools/list` after the notification
|
|
146
|
+
and reports `clientDidNotRefresh: true` with instructions to use `sf_call_tool`, rather than
|
|
147
|
+
claiming an unqualified success for tools that have silently become unreachable.
|
|
148
|
+
|
|
149
|
+
With this, the full 228-tool surface is reachable from the handshake tool list alone, even under
|
|
150
|
+
`SF_TOOLSETS=none`.
|
|
151
|
+
|
|
152
|
+
The regression this adds is deliberately end-to-end. Every internal signal — `loadedToolsets`,
|
|
153
|
+
`residentTools`, `sf_find_tool` matches — was correct throughout the reported session, so asserting
|
|
154
|
+
on any of them would have passed cleanly while the server was unusable. `qa-client-refetch.mjs`
|
|
155
|
+
instead drives two simulated clients, one that honours `list_changed` and one that never re-fetches,
|
|
156
|
+
and asserts what the model can actually call.
|
|
157
|
+
|
|
158
|
+
Also fixed along the way: `sf_call_tool`'s first implementation assumed `inputSchema` was always a
|
|
159
|
+
`ZodRawShape`, but the shared schemas in `src/schemas` are built `ZodObject`s. Both forms are now
|
|
160
|
+
normalised, matching what the SDK itself does.
|
|
161
|
+
|
|
162
|
+
### Notes
|
|
163
|
+
|
|
164
|
+
- Meta-tool count goes from 3 to 5, so the default startup listing is 20 tools rather than 18 and
|
|
165
|
+
`SF_TOOLSETS=all` lists 233 rather than 231. No Salesforce tool was added, removed or renamed.
|
|
166
|
+
- `zod-to-json-schema` is now a direct dependency. It was already present transitively — the MCP SDK
|
|
167
|
+
uses it to serialise Zod v3 schemas — and this server now uses it for the same purpose.
|
|
168
|
+
|
|
169
|
+
## [3.0.0] - 2026-09-02
|
|
170
|
+
|
|
171
|
+
### Production write guard
|
|
172
|
+
|
|
173
|
+
This server hands an LLM a Salesforce credential and lets the LLM decide what to do with it. Any
|
|
174
|
+
text the model reads along the way — a case comment, a field description, a retrieved `.flow` file,
|
|
175
|
+
an email body — can carry an instruction, and nothing downstream can distinguish it from one the
|
|
176
|
+
user typed. Salesforce's own hosted MCP servers ship a blunt version of the same defence: they
|
|
177
|
+
create and update records but refuse to delete, with deletes behind a separate opt-in server.
|
|
178
|
+
|
|
179
|
+
Nine tools are now refused against a **production** org: `sf_delete_metadata`, `sf_delete_record`,
|
|
180
|
+
`sf_bulk_delete_records`, `sf_execute_anonymous_apex`, `sf_uninstall_package`, `sf_create_user`,
|
|
181
|
+
`sf_update_user`, `sf_reset_user_password`, `sf_freeze_user`. Set `SF_PRODUCTION_GUARD=strict` to
|
|
182
|
+
refuse every write instead, or `=off` to disable the guard.
|
|
183
|
+
|
|
184
|
+
**Metadata creation is deliberately untouched.** All 137 `sf_create_*` tools, `sf_deploy_metadata`
|
|
185
|
+
and `sf_retrieve_metadata` still work against production. `sf_deploy_metadata` was reviewed and
|
|
186
|
+
left open because it has no `destructiveChanges` path — it builds a package.xml from the components
|
|
187
|
+
passed to it and can only add or upsert. A guard that taxed metadata authoring would be turned off
|
|
188
|
+
on day one, which is worse than no guard because it would then be off for everything too.
|
|
189
|
+
|
|
190
|
+
Apex authoring (`sf_create_apex_class`, `sf_create_apex_trigger`) is also left open, even though a
|
|
191
|
+
trigger is arbitrary code running on every DML. The line drawn is auditability: created Apex is
|
|
192
|
+
metadata with a name, an author and a deploy record, and can be found and removed afterwards.
|
|
193
|
+
`sf_execute_anonymous_apex` leaves no artifact at all, which is why it is the Apex path that is
|
|
194
|
+
blocked. This is a real residual risk and is documented rather than papered over.
|
|
195
|
+
|
|
196
|
+
An org counts as production only when it is not a sandbox, not a Developer Edition org, and has no
|
|
197
|
+
trial expiry. **`IsSandbox = false` alone is not sufficient** — Developer Edition and scratch orgs
|
|
198
|
+
both report `false`, and gating those would have made the feature unusable for everyone developing
|
|
199
|
+
against a dev org. Org identity is resolved once per process and cached; if it cannot be determined,
|
|
200
|
+
the org is treated as production (fail closed).
|
|
201
|
+
|
|
202
|
+
The guard is a hard refusal rather than a confirmation prompt, because a prompt the calling agent
|
|
203
|
+
can satisfy by itself is not a control — and this server is routinely run under clients with
|
|
204
|
+
permissions bypassed. Only an environment variable set outside the conversation lifts it.
|
|
205
|
+
|
|
206
|
+
Implemented in `src/services/guard.ts` and wired through the single `ToolsetRegistry.capture()`
|
|
207
|
+
proxy, so every current and future tool passes through it without the 33 tool modules changing.
|
|
208
|
+
Covered by `qa-guard.mjs` (26 checks), including assertions that each metadata-creation tool stays
|
|
209
|
+
allowed and that Developer Edition / scratch / sandbox orgs are never treated as production.
|
|
210
|
+
|
|
211
|
+
### Security: sf_deploy_metadata could be turned into a metadata deletion primitive
|
|
212
|
+
|
|
213
|
+
Found by attacking the guard above rather than in review, and it was live in every published version
|
|
214
|
+
that shipped `sf_deploy_metadata` with the `componentsXml` parameter.
|
|
215
|
+
|
|
216
|
+
`inferMetadataPath`'s `default` branch (unrecognised metadata types) returned `` `${lower}s/${name}` ``
|
|
217
|
+
with no extension appended, so the caller-supplied `name` controlled the tail of the zip path
|
|
218
|
+
outright. A component of `{ type: "X", name: "../destructiveChanges.xml" }` produced the zip path
|
|
219
|
+
`xs/../destructiveChanges.xml`, which JSZip normalises to a **root-level `destructiveChanges.xml`** —
|
|
220
|
+
the manifest the Metadata API uses to *delete* every component listed in it. Confirmed by reading the
|
|
221
|
+
generated archive's entry list, not by inspection.
|
|
222
|
+
|
|
223
|
+
Impact: `sf_deploy_metadata` was documented and treated as additive-only (it is not in the production
|
|
224
|
+
guard's blocked set for exactly that reason), while in fact being able to delete arbitrary metadata
|
|
225
|
+
from any org the server could reach — bypassing the block on `sf_delete_metadata` completely.
|
|
226
|
+
|
|
227
|
+
Fixed by validating component names before they reach the zip: path separators and `..` are rejected,
|
|
228
|
+
as are the reserved manifest names `package.xml`, `destructiveChanges.xml`,
|
|
229
|
+
`destructiveChangesPre.xml` and `destructiveChangesPost.xml` (case-insensitively). The assembled path
|
|
230
|
+
is re-checked afterwards so a future branch that builds a path some other way cannot reintroduce this.
|
|
231
|
+
Salesforce component names cannot contain path separators in any metadata type, so nothing legitimate
|
|
232
|
+
is rejected. `sf_deploy_metadata` therefore stays available against production, as intended.
|
|
233
|
+
|
|
234
|
+
### Security: production guard evaluated the wrong org for tools taking an org override
|
|
235
|
+
|
|
236
|
+
`uninstallPackage()` ignores its `auth` argument entirely and shells out to
|
|
237
|
+
`sf package uninstall --target-org <params.targetOrg>`. The guard resolved production-ness from
|
|
238
|
+
`getAuth()` — a different org — so pointing `SF_INSTANCE_URL` at a dev org and passing
|
|
239
|
+
`targetOrg: "<prod-alias>"` walked a gated tool straight past a guard that believed it was protecting
|
|
240
|
+
production.
|
|
241
|
+
|
|
242
|
+
Gated tools called with an explicit `targetOrg`, `targetAlias`, `targetOrgAlias` or `orgAlias` are now
|
|
243
|
+
refused outright, since the named org cannot be verified from here without a second CLI round-trip.
|
|
244
|
+
Fail closed rather than guess.
|
|
245
|
+
|
|
246
|
+
### Security: package.xml manifest injection
|
|
247
|
+
|
|
248
|
+
`buildPackageXml` interpolated component names and types into XML unescaped, so a name containing `<`
|
|
249
|
+
closed the element early and appended attacker-chosen manifest entries. Lower severity than the two
|
|
250
|
+
above (a manifest cannot express deletion, and listed components must also be present in the zip), but
|
|
251
|
+
fixed with proper escaping. The `*` wildcard is unaffected.
|
|
252
|
+
|
|
253
|
+
Attack coverage for all three lives in `qa-guard.mjs` (43 checks) so they cannot regress silently.
|
|
254
|
+
|
|
255
|
+
|
|
3
256
|
## [2.12.0] - 2026-08-11
|
|
4
257
|
|
|
5
258
|
### Five new tools (223 → 228), and a dangerous pair of functions removed
|
package/README.md
CHANGED
|
@@ -45,6 +45,92 @@ Add to your MCP configuration (`claude_desktop_config.json` or `.claude/settings
|
|
|
45
45
|
|
|
46
46
|
See [SETUP.md](SETUP.md) for all authentication methods and detailed setup instructions.
|
|
47
47
|
|
|
48
|
+
### One-click install (Claude Desktop)
|
|
49
|
+
|
|
50
|
+
Download `salesforce-metadata-mcp-<version>.mcpb` from the [latest release](https://github.com/semwalajay83-sem/salesforce-metadata-mcp/releases/latest), then drag it into **Claude Desktop → Settings → Extensions**. It prompts for your org URL and credentials — no JSON editing.
|
|
51
|
+
|
|
52
|
+
The bundle resolves the server from npm at launch rather than embedding a copy, so it always runs the current published version and never needs re-downloading after an upgrade.
|
|
53
|
+
|
|
54
|
+
### Docker
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
docker build -t salesforce-metadata-mcp .
|
|
58
|
+
docker run -i --rm \
|
|
59
|
+
-e SF_INSTANCE_URL="https://your-org.my.salesforce.com" \
|
|
60
|
+
-e SF_ACCESS_TOKEN="your_access_token" \
|
|
61
|
+
salesforce-metadata-mcp
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The image builds from source, runs as a non-root user, and ships production dependencies only. Because this server speaks MCP over stdio, `-i` is required — the container is driven by its client, not run as a background service. `SF_ALIAS` will not work in a container: the Salesforce CLI's login flow needs a browser, so use token, JWT, refresh-token, or client-credentials variables instead.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Toolsets — loading 228 tools without burning your context
|
|
69
|
+
|
|
70
|
+
All 228 tools are always available. Most are not loaded into the model's context until something asks for them.
|
|
71
|
+
|
|
72
|
+
Listing every tool up front costs roughly **98,000 tokens** — about half a 200k context window, spent
|
|
73
|
+
before you type anything, whether or not the session ever touches OmniStudio or DevOps Center. A
|
|
74
|
+
228-candidate tool list also makes the model measurably worse at picking the right tool. So the server
|
|
75
|
+
starts with a small core loaded and pulls in the rest on demand:
|
|
76
|
+
|
|
77
|
+
| Startup | Tools listed | Approx. tokens |
|
|
78
|
+
|---------|-------------:|---------------:|
|
|
79
|
+
| Default (`core,metadata`) | 20 | **~9,900** |
|
|
80
|
+
| After loading two more toolsets | 43 | ~20,800 |
|
|
81
|
+
| `SF_TOOLSETS=all` | 233 | ~99,100 |
|
|
82
|
+
|
|
83
|
+
The default covers what nearly every session needs: describe/list objects, SOQL query,
|
|
84
|
+
deploy/retrieve/delete metadata, deploy status, and core schema creation (objects, fields, formula
|
|
85
|
+
fields, picklist values, validation rules, approval processes).
|
|
86
|
+
|
|
87
|
+
Five tools are always present and make everything else reachable:
|
|
88
|
+
|
|
89
|
+
- **`sf_find_tool`** — search all 228 tools by name and load whatever contains the matches, in one
|
|
90
|
+
call. Ask for *"create an omniscript"* and it finds the tools, loads `omnistudio`, and they are
|
|
91
|
+
callable immediately. This is usually all you or the model needs.
|
|
92
|
+
- **`sf_load_toolset`** — load named toolsets explicitly.
|
|
93
|
+
- **`sf_list_toolsets`** — browse all toolsets, their tool counts, and what is loaded.
|
|
94
|
+
- **`sf_call_tool`** — invoke any tool by name, loaded or not, without it having to appear in the
|
|
95
|
+
tool list first. Arguments are validated and the production guard applies exactly as on a direct
|
|
96
|
+
call.
|
|
97
|
+
- **`sf_tool_schema`** — return any tool's input schema, so `sf_call_tool` can be constructed for a
|
|
98
|
+
tool the client cannot see.
|
|
99
|
+
|
|
100
|
+
In practice you don't manage this by hand: ask for what you want, and the model loads what it needs.
|
|
101
|
+
|
|
102
|
+
### If loaded tools don't show up
|
|
103
|
+
|
|
104
|
+
Loading a toolset enables it on the server and emits `notifications/tools/list_changed`. A client is
|
|
105
|
+
supposed to re-fetch `tools/list` when it sees that. Some clients don't — and when that happens, the
|
|
106
|
+
load reports success, the tools really are enabled, and the model still cannot call any of them
|
|
107
|
+
because they never entered its tool list.
|
|
108
|
+
|
|
109
|
+
Two things handle this, so no client can lose access to a tool:
|
|
110
|
+
|
|
111
|
+
- `sf_load_toolset` checks whether the client re-fetched and says so in its response, instead of
|
|
112
|
+
reporting an unqualified success for tools that have become unreachable.
|
|
113
|
+
- `sf_call_tool` reaches every tool without needing a re-fetch at all. Combined with `sf_tool_schema`,
|
|
114
|
+
the full 228-tool surface is usable from the handshake tool list alone — even under
|
|
115
|
+
`SF_TOOLSETS=none`.
|
|
116
|
+
|
|
117
|
+
To restore the previous behaviour of loading everything at startup, set `SF_TOOLSETS=all`. To start
|
|
118
|
+
with only the five meta-tools, set `SF_TOOLSETS=none`. To pick your own core, pass a list:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{ "env": { "SF_TOOLSETS": "metadata,objects,automation,security" } }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Available toolsets: `core`, `metadata`, `objects`, `data`, `flows`, `automation`, `security`, `apex`,
|
|
125
|
+
`lwc`, `ui`, `pages`, `actions`, `agentforce`, `omnistudio`, `omnichannel`, `devops`, `deployment`,
|
|
126
|
+
`integrations`, `identity`, `reports`, `experience`, `admin`, `monitoring`, `audit`, `einstein`,
|
|
127
|
+
`knowledge`, `cpq`, `sandbox`, `streaming`, `visualforce`, `aura`, `comms`, `mcp`, `i18n`.
|
|
128
|
+
|
|
129
|
+
Flow tools live in their own `flows` toolset because `sf_create_flow` carries the full Flow element
|
|
130
|
+
schema — 15,266 bytes (~4,126 tokens) on its own, the largest tool definition in the server. Keeping
|
|
131
|
+
it out of the default means sessions that never build a Flow never pay for it, while sessions that do
|
|
132
|
+
still get the complete validated schema.
|
|
133
|
+
|
|
48
134
|
---
|
|
49
135
|
|
|
50
136
|
## Tools — 228 total
|
|
@@ -210,6 +296,49 @@ Highlights below; see [TOOLS.md](TOOLS.md) for the complete reference with param
|
|
|
210
296
|
| `SF_ACCESS_TOKEN` | Static access token (expires ~1hr) | For static |
|
|
211
297
|
| `PORT` | HTTP server port (default: 3000) | For HTTP mode |
|
|
212
298
|
| `TRANSPORT` | `stdio` or `http` (default: stdio) | Optional |
|
|
299
|
+
| `SF_TOOLSETS` | Toolsets to load at startup: `all`, `none`, or a comma-separated list (default: `core,metadata`) | Optional |
|
|
300
|
+
| `SF_TOOLSETS_VERBOSE` | Set to `1` to print the full toolset list to stderr on startup | Optional |
|
|
301
|
+
| `SF_PRODUCTION_GUARD` | `destructive` (default), `strict`, or `off` — see below | Optional |
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Production write guard
|
|
306
|
+
|
|
307
|
+
This server hands an LLM a Salesforce credential, and the LLM decides what to do with it. Any text
|
|
308
|
+
the model reads on the way — a case comment, a field description, a retrieved `.flow` file, an
|
|
309
|
+
email body — can carry an instruction, and nothing downstream can tell it apart from one you typed.
|
|
310
|
+
|
|
311
|
+
So against a **production org**, nine tools are refused by default:
|
|
312
|
+
|
|
313
|
+
| Blocked on production | Why |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `sf_delete_metadata` | Destroys metadata and every record in it |
|
|
316
|
+
| `sf_delete_record`, `sf_bulk_delete_records` | Destroy data |
|
|
317
|
+
| `sf_execute_anonymous_apex` | Arbitrary code that leaves no artifact behind |
|
|
318
|
+
| `sf_uninstall_package` | Removes a managed package and its data |
|
|
319
|
+
| `sf_create_user`, `sf_update_user` | Privilege escalation |
|
|
320
|
+
| `sf_reset_user_password`, `sf_freeze_user` | Account takeover / lockout |
|
|
321
|
+
|
|
322
|
+
**Metadata creation is not affected.** All 137 `sf_create_*` tools, `sf_deploy_metadata` and
|
|
323
|
+
`sf_retrieve_metadata` work against production exactly as before — authoring metadata by natural
|
|
324
|
+
language is the point of this package, and a guard that taxed it would just get switched off.
|
|
325
|
+
|
|
326
|
+
Apex authoring (`sf_create_apex_class`, `sf_create_apex_trigger`) is also **not** blocked, even
|
|
327
|
+
though a trigger runs on every DML. The line drawn is auditability: created Apex is metadata — it
|
|
328
|
+
has a name, an author and a deploy record, and can be found and removed. `sf_execute_anonymous_apex`
|
|
329
|
+
leaves nothing to find, which is why that one is blocked.
|
|
330
|
+
|
|
331
|
+
An org counts as production only when it is **not** a sandbox, **not** a Developer Edition org, and
|
|
332
|
+
**not** on a trial/scratch expiry. Sandboxes, dev orgs and scratch orgs are never gated.
|
|
333
|
+
|
|
334
|
+
```jsonc
|
|
335
|
+
{ "env": { "SF_PRODUCTION_GUARD": "strict" } } // refuse ALL writes on production
|
|
336
|
+
{ "env": { "SF_PRODUCTION_GUARD": "off" } } // no guard at all
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The guard is a hard refusal, not a confirmation prompt: a prompt the calling agent can approve by
|
|
340
|
+
itself is not a control, and this server is often run with client permissions bypassed. Only the
|
|
341
|
+
environment variable lifts it.
|
|
213
342
|
|
|
214
343
|
---
|
|
215
344
|
|
package/SECURITY.md
CHANGED
|
@@ -68,11 +68,31 @@ All tool inputs are validated by Zod schemas before being used:
|
|
|
68
68
|
|
|
69
69
|
## Supported Versions
|
|
70
70
|
|
|
71
|
-
| Version |
|
|
72
|
-
|
|
73
|
-
| 2.
|
|
74
|
-
| 2.
|
|
75
|
-
| 1
|
|
71
|
+
| Version | Status |
|
|
72
|
+
|---------|--------|
|
|
73
|
+
| 2.12.x | ✅ Supported — current npm `latest` |
|
|
74
|
+
| 2.11.2 | ✅ Contains all security fixes |
|
|
75
|
+
| 2.11.1 | ⚠️ Has the code fixes, but ships 5 production-dependency advisories resolved in 2.11.2 — upgrade |
|
|
76
|
+
| **≤ 2.8.7** | ❌ **Deprecated on npm (2026-08-26) — command injection (RCE), SOQL injection, credential leak** |
|
|
77
|
+
| 1.x | ❌ No longer supported |
|
|
78
|
+
|
|
79
|
+
### Deprecated versions — 2026-08-26
|
|
80
|
+
|
|
81
|
+
Every version **2.0.0 through 2.8.7** was deprecated on npm and now emits a warning on install.
|
|
82
|
+
|
|
83
|
+
The security audit in **v2.8.8** fixed a confirmed command injection (RCE), a SOQL injection, a
|
|
84
|
+
credential leak and a generated-code injection. **v2.8.8, v2.8.9, v2.9.0, v2.10.0 and v2.11.0 were
|
|
85
|
+
never published to npm**, so the first npm release carrying those fixes is **v2.11.1**. Anyone on
|
|
86
|
+
`2.8.7` or below — including the version that was npm `latest` at the time — is running unfixed code.
|
|
87
|
+
|
|
88
|
+
If you are pinned to any version at or below 2.8.7, upgrade:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
npm install salesforce-metadata-mcp@latest
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Note that pinning to an exact old version bypasses `latest` entirely; check your lockfile, Dockerfile
|
|
95
|
+
or MCP client config for a hardcoded version string.
|
|
76
96
|
|
|
77
97
|
---
|
|
78
98
|
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5
5
|
import { createServer } from "http";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
import { registerTools } from "./tools/index.js";
|
|
8
|
+
import { attachRefetchDetection, registerToolsetTools, toolsetSummary } from "./toolsets.js";
|
|
9
|
+
import { guardMode, DESTRUCTIVE_TOOLS } from "./services/guard.js";
|
|
8
10
|
// Single source of truth for the version. Hardcoding it here drifted from package.json across
|
|
9
11
|
// v2.11.0/v2.11.1 (the server advertised 2.10.0 to clients while the package said 2.11.1), so read
|
|
10
12
|
// it at runtime instead. dist/index.js → ../package.json resolves correctly both in the repo and
|
|
@@ -15,12 +17,47 @@ const server = new McpServer({
|
|
|
15
17
|
name: "salesforce-metadata-mcp",
|
|
16
18
|
version: VERSION,
|
|
17
19
|
});
|
|
18
|
-
registerTools(server);
|
|
20
|
+
const registry = registerTools(server);
|
|
21
|
+
registerToolsetTools(server, registry);
|
|
22
|
+
// Lets sf_load_toolset tell the user when their client ignored notifications/tools/list_changed —
|
|
23
|
+
// the 2026-09-09 failure mode, where loading a toolset succeeds but its tools never reach the model.
|
|
24
|
+
const refetchDetection = attachRefetchDetection(server, registry);
|
|
25
|
+
// Lazy toolsets: a full tools/list is ~362KB (~98k tokens) with everything loaded, which is about
|
|
26
|
+
// half a 200k context window spent before the user types anything — and a 228-candidate list also
|
|
27
|
+
// hurts tool-selection accuracy. Every tool stays registered and callable; tools outside the active
|
|
28
|
+
// set are simply hidden from tools/list until sf_load_toolset or sf_find_tool switches them on.
|
|
29
|
+
// Set SF_TOOLSETS=all to restore the pre-3.0 behaviour of loading everything up front.
|
|
30
|
+
const initialToolsets = registry.resolveInitial(process.env["SF_TOOLSETS"]);
|
|
31
|
+
registry.setActive(initialToolsets);
|
|
32
|
+
// ─── Startup banner ───────────────────────────────────────────────────────────
|
|
33
|
+
function logStartup(kind) {
|
|
34
|
+
console.error(`Salesforce Metadata MCP server v${VERSION} running on ${kind}`);
|
|
35
|
+
console.error(`Toolsets: ${registry.activeGroups().join(", ") || "none"} — ` +
|
|
36
|
+
`${registry.residentTools()} of ${registry.totalTools()} tools loaded. ` +
|
|
37
|
+
`Use sf_find_tool or sf_load_toolset to load more; SF_TOOLSETS=all loads everything. ` +
|
|
38
|
+
`Any tool is also callable directly via sf_call_tool, loaded or not.`);
|
|
39
|
+
if (!refetchDetection) {
|
|
40
|
+
console.error("Note: could not observe tools/list on this SDK build, so a client that ignores " +
|
|
41
|
+
"tools/list_changed cannot be detected. sf_call_tool still reaches every tool.");
|
|
42
|
+
}
|
|
43
|
+
// Announced at startup rather than only on refusal: a guard nobody knows is on reads as a bug
|
|
44
|
+
// the first time it fires, and the org lookup that decides production-ness is lazy, so this is
|
|
45
|
+
// the only point where the configured intent can be stated without an extra API call.
|
|
46
|
+
const mode = guardMode();
|
|
47
|
+
console.error(mode === "off"
|
|
48
|
+
? "Production guard: OFF — all tools permitted against production orgs."
|
|
49
|
+
: mode === "strict"
|
|
50
|
+
? "Production guard: strict — ALL write tools, including metadata creation, refused on production orgs. Set SF_PRODUCTION_GUARD=destructive or =off to change."
|
|
51
|
+
: `Production guard: destructive — ${DESTRUCTIVE_TOOLS.size} tools that delete data, run untraceable code or change org access are refused on production orgs. Metadata creation is unaffected. Set SF_PRODUCTION_GUARD to change.`);
|
|
52
|
+
if (process.env["SF_TOOLSETS_VERBOSE"] === "1") {
|
|
53
|
+
console.error(toolsetSummary(registry));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
19
56
|
// ─── Transport: stdio ─────────────────────────────────────────────────────────
|
|
20
57
|
async function runStdio() {
|
|
21
58
|
const transport = new StdioServerTransport();
|
|
22
59
|
await server.connect(transport);
|
|
23
|
-
|
|
60
|
+
logStartup("stdio");
|
|
24
61
|
}
|
|
25
62
|
// ─── Transport: HTTP ──────────────────────────────────────────────────────────
|
|
26
63
|
function readBody(req) {
|
|
@@ -78,7 +115,7 @@ async function runHTTP() {
|
|
|
78
115
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
79
116
|
});
|
|
80
117
|
httpServer.listen(port, host, () => {
|
|
81
|
-
|
|
118
|
+
logStartup(`http://${host}:${port}/mcp`);
|
|
82
119
|
});
|
|
83
120
|
}
|
|
84
121
|
// ─── Entry point ──────────────────────────────────────────────────────────────
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,YAAY,EAA6C,MAAM,MAAM,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|