salesforce-metadata-mcp 2.11.2 → 3.0.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +117 -3
  3. package/SECURITY.md +25 -5
  4. package/TOOLS.md +326 -326
  5. package/dist/index.js +32 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/schemas/index.d.ts +1189 -1087
  8. package/dist/schemas/index.d.ts.map +1 -1
  9. package/dist/schemas/index.js +58 -10
  10. package/dist/schemas/index.js.map +1 -1
  11. package/dist/services/deployment.d.ts.map +1 -1
  12. package/dist/services/deployment.js +60 -2
  13. package/dist/services/deployment.js.map +1 -1
  14. package/dist/services/guard.d.ts +45 -0
  15. package/dist/services/guard.d.ts.map +1 -0
  16. package/dist/services/guard.js +177 -0
  17. package/dist/services/guard.js.map +1 -0
  18. package/dist/services/impact.d.ts +125 -0
  19. package/dist/services/impact.d.ts.map +1 -0
  20. package/dist/services/impact.js +707 -0
  21. package/dist/services/impact.js.map +1 -0
  22. package/dist/services/salesforce.d.ts +58 -3
  23. package/dist/services/salesforce.d.ts.map +1 -1
  24. package/dist/services/salesforce.js +376 -149
  25. package/dist/services/salesforce.js.map +1 -1
  26. package/dist/tools/apex.d.ts.map +1 -1
  27. package/dist/tools/apex.js +13 -2
  28. package/dist/tools/apex.js.map +1 -1
  29. package/dist/tools/data.d.ts.map +1 -1
  30. package/dist/tools/data.js +71 -2
  31. package/dist/tools/data.js.map +1 -1
  32. package/dist/tools/index.d.ts +2 -1
  33. package/dist/tools/index.d.ts.map +1 -1
  34. package/dist/tools/index.js +47 -33
  35. package/dist/tools/index.js.map +1 -1
  36. package/dist/tools/monitoring.d.ts.map +1 -1
  37. package/dist/tools/monitoring.js +17 -2
  38. package/dist/tools/monitoring.js.map +1 -1
  39. package/dist/toolsets.d.ts +106 -0
  40. package/dist/toolsets.d.ts.map +1 -0
  41. package/dist/toolsets.js +374 -0
  42. package/dist/toolsets.js.map +1 -0
  43. package/package.json +7 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,195 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Production write guard
6
+
7
+ This server hands an LLM a Salesforce credential and lets the LLM decide what to do with it. Any
8
+ text the model reads along the way — a case comment, a field description, a retrieved `.flow` file,
9
+ an email body — can carry an instruction, and nothing downstream can distinguish it from one the
10
+ user typed. Salesforce's own hosted MCP servers ship a blunt version of the same defence: they
11
+ create and update records but refuse to delete, with deletes behind a separate opt-in server.
12
+
13
+ Nine tools are now refused against a **production** org: `sf_delete_metadata`, `sf_delete_record`,
14
+ `sf_bulk_delete_records`, `sf_execute_anonymous_apex`, `sf_uninstall_package`, `sf_create_user`,
15
+ `sf_update_user`, `sf_reset_user_password`, `sf_freeze_user`. Set `SF_PRODUCTION_GUARD=strict` to
16
+ refuse every write instead, or `=off` to disable the guard.
17
+
18
+ **Metadata creation is deliberately untouched.** All 137 `sf_create_*` tools, `sf_deploy_metadata`
19
+ and `sf_retrieve_metadata` still work against production. `sf_deploy_metadata` was reviewed and
20
+ left open because it has no `destructiveChanges` path — it builds a package.xml from the components
21
+ passed to it and can only add or upsert. A guard that taxed metadata authoring would be turned off
22
+ on day one, which is worse than no guard because it would then be off for everything too.
23
+
24
+ Apex authoring (`sf_create_apex_class`, `sf_create_apex_trigger`) is also left open, even though a
25
+ trigger is arbitrary code running on every DML. The line drawn is auditability: created Apex is
26
+ metadata with a name, an author and a deploy record, and can be found and removed afterwards.
27
+ `sf_execute_anonymous_apex` leaves no artifact at all, which is why it is the Apex path that is
28
+ blocked. This is a real residual risk and is documented rather than papered over.
29
+
30
+ An org counts as production only when it is not a sandbox, not a Developer Edition org, and has no
31
+ trial expiry. **`IsSandbox = false` alone is not sufficient** — Developer Edition and scratch orgs
32
+ both report `false`, and gating those would have made the feature unusable for everyone developing
33
+ against a dev org. Org identity is resolved once per process and cached; if it cannot be determined,
34
+ the org is treated as production (fail closed).
35
+
36
+ The guard is a hard refusal rather than a confirmation prompt, because a prompt the calling agent
37
+ can satisfy by itself is not a control — and this server is routinely run under clients with
38
+ permissions bypassed. Only an environment variable set outside the conversation lifts it.
39
+
40
+ Implemented in `src/services/guard.ts` and wired through the single `ToolsetRegistry.capture()`
41
+ proxy, so every current and future tool passes through it without the 33 tool modules changing.
42
+ Covered by `qa-guard.mjs` (26 checks), including assertions that each metadata-creation tool stays
43
+ allowed and that Developer Edition / scratch / sandbox orgs are never treated as production.
44
+
45
+ ### Security: sf_deploy_metadata could be turned into a metadata deletion primitive
46
+
47
+ Found by attacking the guard above rather than in review, and it was live in every published version
48
+ that shipped `sf_deploy_metadata` with the `componentsXml` parameter.
49
+
50
+ `inferMetadataPath`'s `default` branch (unrecognised metadata types) returned `` `${lower}s/${name}` ``
51
+ with no extension appended, so the caller-supplied `name` controlled the tail of the zip path
52
+ outright. A component of `{ type: "X", name: "../destructiveChanges.xml" }` produced the zip path
53
+ `xs/../destructiveChanges.xml`, which JSZip normalises to a **root-level `destructiveChanges.xml`** —
54
+ the manifest the Metadata API uses to *delete* every component listed in it. Confirmed by reading the
55
+ generated archive's entry list, not by inspection.
56
+
57
+ Impact: `sf_deploy_metadata` was documented and treated as additive-only (it is not in the production
58
+ guard's blocked set for exactly that reason), while in fact being able to delete arbitrary metadata
59
+ from any org the server could reach — bypassing the block on `sf_delete_metadata` completely.
60
+
61
+ Fixed by validating component names before they reach the zip: path separators and `..` are rejected,
62
+ as are the reserved manifest names `package.xml`, `destructiveChanges.xml`,
63
+ `destructiveChangesPre.xml` and `destructiveChangesPost.xml` (case-insensitively). The assembled path
64
+ is re-checked afterwards so a future branch that builds a path some other way cannot reintroduce this.
65
+ Salesforce component names cannot contain path separators in any metadata type, so nothing legitimate
66
+ is rejected. `sf_deploy_metadata` therefore stays available against production, as intended.
67
+
68
+ ### Security: production guard evaluated the wrong org for tools taking an org override
69
+
70
+ `uninstallPackage()` ignores its `auth` argument entirely and shells out to
71
+ `sf package uninstall --target-org <params.targetOrg>`. The guard resolved production-ness from
72
+ `getAuth()` — a different org — so pointing `SF_INSTANCE_URL` at a dev org and passing
73
+ `targetOrg: "<prod-alias>"` walked a gated tool straight past a guard that believed it was protecting
74
+ production.
75
+
76
+ Gated tools called with an explicit `targetOrg`, `targetAlias`, `targetOrgAlias` or `orgAlias` are now
77
+ refused outright, since the named org cannot be verified from here without a second CLI round-trip.
78
+ Fail closed rather than guess.
79
+
80
+ ### Security: package.xml manifest injection
81
+
82
+ `buildPackageXml` interpolated component names and types into XML unescaped, so a name containing `<`
83
+ closed the element early and appended attacker-chosen manifest entries. Lower severity than the two
84
+ above (a manifest cannot express deletion, and listed components must also be present in the zip), but
85
+ fixed with proper escaping. The `*` wildcard is unaffected.
86
+
87
+ Attack coverage for all three lives in `qa-guard.mjs` (43 checks) so they cannot regress silently.
88
+
89
+
90
+ ## [2.12.0] - 2026-08-11
91
+
92
+ ### Five new tools (223 → 228), and a dangerous pair of functions removed
93
+
94
+ #### `sf_list_objects` — find objects without knowing their API name
95
+
96
+ `sf_describe_object` requires an exact API name, which is useless when the user does not yet know it
97
+ ("what objects handle cases here?"). This lists objects by partial name or label, filterable to
98
+ custom/standard/queryable, ranked exact match → prefix → substring so a search for `Account` returns
99
+ `Account` before `AccountBrandShare`.
100
+
101
+ #### `sf_get_metadata_dependencies` — impact analysis before you change anything
102
+
103
+ Read-only. Answers "what breaks if I change this?" for custom fields, objects, Apex classes and
104
+ triggers, flows, validation rules, layouts, permission sets, LWC/Aura bundles and static resources,
105
+ via the Tooling API's `MetadataComponentDependency`. For custom fields it additionally reports how
106
+ many records currently hold a value, which is usually the deciding factor in whether a change is safe.
107
+
108
+ Every response — **including empty ones** — carries a `blindSpots` list. `MetadataComponentDependency`
109
+ cannot see dynamic SOQL, field names built by string concatenation in Apex, managed-package internals,
110
+ label-based references, or anything outside the org (integrations, ETL, API clients). An unqualified
111
+ "0 dependencies found" reads as "safe to change", and for those categories that inference is wrong. A
112
+ tool that stays silent about its blind spots is more dangerous than one that says nothing at all,
113
+ because it manufactures false confidence.
114
+
115
+ #### `sf_update_custom_object` / `sf_update_custom_field` — risk-classified, with a confirmation gate
116
+
117
+ Every change is classified before anything is written:
118
+
119
+ | Tier | Examples | Behaviour |
120
+ |---|---|---|
121
+ | SAFE | label, description, help text, `trackHistory`, feature toggles, adding picklist values | applied immediately |
122
+ | GUARDED | `required`→true, `unique`→true, `externalId`, `defaultValue`, length/precision **increase**, `sharingModel`, `deploymentStatus` | impact report first |
123
+ | DESTRUCTIVE | length/precision/scale **reduction**, removing picklist values, restricting a picklist, repointing a lookup | impact report first |
124
+ | REFUSED | field `type` change, API-name rename | rejected outright |
125
+
126
+ GUARDED and DESTRUCTIVE changes do **not** apply on the first call. They return the dependency list,
127
+ the count of records holding data, and Salesforce's own `checkOnly` validate-only verdict; applying
128
+ requires a second call with `confirmImpact: true`. The REFUSED tier is policy, not limitation:
129
+ Salesforce's own UI performs type conversion through a multi-step wizard with data-loss warnings, and
130
+ renaming a field breaks every string-literal reference no dependency API can see. Neither belongs
131
+ behind a single chat message.
132
+
133
+ #### Removed: the previous `updateCustomObject` / `updateCustomField`
134
+
135
+ Both existed in `services/salesforce.ts` but were never wired to a tool. On inspection that was
136
+ fortunate. They did `readMetadata` → regex edits → `upsertMetadata`, and:
137
+
138
+ 1. **`upsertMetadata` is a full component replace, and a `readMetadata` of a CustomObject carries
139
+ every field, validation rule, list view and record type.** A one-word label change therefore
140
+ round-tripped every child component through Salesforce's serializer and wrote it back — anything
141
+ rendered imperfectly was silently damaged or dropped.
142
+ 2. Their regexes could not match self-closing tags, so `<description/>` fell through to the append
143
+ branch and emitted a **duplicate** element.
144
+ 3. Replacements were written unprefixed while appends were `met:`-prefixed, inside a `met:` wrapper.
145
+
146
+ The replacements in `services/impact.ts` write through a **scoped `deploy()`** instead. Deploy
147
+ *merges*: child components absent from the payload are left alone. A field write names exactly one
148
+ `<fields>` entry; an object write carries object-level properties only and structurally cannot contain
149
+ a field. The same deploy path is used for `checkOnly` validation as for the real write, so the thing
150
+ that was validated is the thing that gets applied.
151
+
152
+ **Live-org finding that shaped this:** the Tooling API exposes no `Metadata` for `CustomObject` —
153
+ absent from `/tooling/sobjects/CustomObject/<id>` and `SELECT Metadata FROM CustomObject` fails with
154
+ "No such column". Object reads therefore use `readMetadata`, but every child collection is stripped
155
+ from the XML before any scalar is parsed and the write payload is built from a whitelist, so the
156
+ failure mode above cannot recur. Tooling `CustomField` *does* expose `Metadata` and is used directly.
157
+
158
+ #### `sf_disable_debug_logs`
159
+
160
+ Deletes active `TraceFlag` records, completing enable/disable/read. Defaults to still-active flags
161
+ only (expired ones are already inert). The `DebugLevel` is deliberately left in place — they are
162
+ shared and reusable, and deleting one another flag still points at fails for no benefit.
163
+
164
+ ### Improved
165
+
166
+ - **`sf_get_apex_class` / `sf_get_apex_trigger` accept glob patterns.** `namePattern` supports `*` and
167
+ `?` (`Account*Controller`, `*Test`); triggers also accept `objectName` to answer "what triggers run
168
+ on Case?". Pattern results omit bodies — a broad match over a real org would otherwise return tens
169
+ of thousands of lines — so callers narrow down, then re-call with the exact name. Literal `%` and
170
+ `_` in the input are escaped before glob translation, so `Account_Helper` cannot act as a wildcard.
171
+ - **Two more auth strategies:** OAuth 2.0 client-credentials (`SF_CLIENT_ID` + `SF_CLIENT_SECRET`) and
172
+ username/password (`+ SF_USERNAME` + `SF_PASSWORD` + optional `SF_SECURITY_TOKEN`). Both are ordered
173
+ *after* every existing strategy, and client-credentials is gated on `SF_REFRESH_TOKEN` being absent,
174
+ so no existing configuration changes which user the server acts as.
175
+ - **`SF_API_VERSION`** overrides the API version (default `66.0`), with format validation — a newer org
176
+ may expose objects the default cannot see, and an org on a slower release track can reject a version
177
+ it does not yet serve. An unparseable value silently 404s every endpoint, so it falls back loudly
178
+ rather than being trusted.
179
+
180
+ ### Verified
181
+
182
+ `test-new-tools-v212.mjs` 32/32, `test-dataprobe-v212.mjs` 4/4, `qa-flow-comprehensive.mjs` 144/144,
183
+ `test-suite.mjs` 209/212 (the 2 failures probe a nonexistent scratch org and a dummy package ID on
184
+ purpose). Both transports driven: stdio lists 228 tools, HTTP serves `/health` and `initialize` over
185
+ `/mcp`, 404s unknown paths, and still binds `127.0.0.1`.
186
+
187
+ The gate is asserted against a live org rather than reasoned about: a DESTRUCTIVE change is withheld,
188
+ `type` changes and renames are refused, GUARDED applies only after `confirmImpact`, and — the
189
+ regression that motivated the rewrite — a bystander field survives both a field-level and an
190
+ object-level update. **Field survival is checked via the Tooling API, not REST describe**: describe's
191
+ schema cache lags minutes on a dev org and reports fields as missing when they demonstrably exist.
192
+
3
193
  ## [2.11.2] - 2026-08-06
4
194
 
5
195
  ### Security — resolved all 5 production-dependency advisories (2 high, 3 moderate)
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
6
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io)
7
7
 
8
- **The only Salesforce MCP server that builds Agentforce agents, OmniStudio components, and DevOps Center pipelines** — alongside a complete daily developer loop (schema describe, Apex read, debug logs) and 223 tools total for building, configuring, and automating Salesforce orgs directly from Claude or any MCP client.
8
+ **The only Salesforce MCP server that builds Agentforce agents, OmniStudio components, and DevOps Center pipelines** — alongside a complete daily developer loop (schema describe, Apex read, debug logs) and 228 tools total for building, configuring, and automating Salesforce orgs directly from Claude or any MCP client.
9
9
 
10
10
  ---
11
11
 
@@ -45,9 +45,75 @@ 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`) | 18 | **~9,400** |
80
+ | After loading two more toolsets | 41 | ~20,300 |
81
+ | `SF_TOOLSETS=all` | 231 | ~98,600 |
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
+ Three 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
+
95
+ In practice you don't manage this by hand: ask for what you want, and the model loads what it needs.
96
+
97
+ To restore the previous behaviour of loading everything at startup, set `SF_TOOLSETS=all`. To start
98
+ with only the three meta-tools, set `SF_TOOLSETS=none`. To pick your own core, pass a list:
99
+
100
+ ```json
101
+ { "env": { "SF_TOOLSETS": "metadata,objects,automation,security" } }
102
+ ```
103
+
104
+ Available toolsets: `core`, `metadata`, `objects`, `data`, `flows`, `automation`, `security`, `apex`,
105
+ `lwc`, `ui`, `pages`, `actions`, `agentforce`, `omnistudio`, `omnichannel`, `devops`, `deployment`,
106
+ `integrations`, `identity`, `reports`, `experience`, `admin`, `monitoring`, `audit`, `einstein`,
107
+ `knowledge`, `cpq`, `sandbox`, `streaming`, `visualforce`, `aura`, `comms`, `mcp`, `i18n`.
108
+
109
+ Flow tools live in their own `flows` toolset because `sf_create_flow` carries the full Flow element
110
+ schema — 15,266 bytes (~4,126 tokens) on its own, the largest tool definition in the server. Keeping
111
+ it out of the default means sessions that never build a Flow never pay for it, while sessions that do
112
+ still get the complete validated schema.
113
+
48
114
  ---
49
115
 
50
- ## Tools — 223 total
116
+ ## Tools — 228 total
51
117
 
52
118
  Highlights below; see [TOOLS.md](TOOLS.md) for the complete reference with parameters and example prompts.
53
119
 
@@ -69,6 +135,10 @@ Highlights below; see [TOOLS.md](TOOLS.md) for the complete reference with param
69
135
  | `sf_create_sharing_rule` | Create criteria or ownership sharing rules |
70
136
  | `sf_create_field_dependency` | Create controlling/dependent picklist dependency |
71
137
  | `sf_describe_object` | Read an object's full schema — fields, types, picklist values, child relationships, record types |
138
+ | `sf_list_objects` | **Find objects by partial name or label** — the discovery step before `sf_describe_object` |
139
+ | `sf_get_metadata_dependencies` | **Impact analysis: what references this component, and does the field hold data?** Read-only |
140
+ | `sf_update_custom_object` | Update object-level properties, risk-classified with a confirmation gate |
141
+ | `sf_update_custom_field` | Update a field's definition, risk-classified — destructive changes report impact before applying |
72
142
 
73
143
  ### Automation
74
144
  | Tool | Description |
@@ -120,6 +190,7 @@ Highlights below; see [TOOLS.md](TOOLS.md) for the complete reference with param
120
190
  | `sf_get_apex_class` | Read the source of an existing Apex class |
121
191
  | `sf_get_apex_trigger` | Read the source of an existing Apex trigger |
122
192
  | `sf_enable_debug_logs` | Turn on Apex debug logging for a user (TraceFlag) |
193
+ | `sf_disable_debug_logs` | Turn Apex debug logging back off (deletes active TraceFlags) |
123
194
  | `sf_get_debug_logs` | List recent Apex debug logs |
124
195
  | `sf_get_debug_log_body` | Read the full content of a debug log |
125
196
  | `sf_scan_apex_antipatterns` | Lightweight heuristic scan for SOQL/DML-in-loop, hardcoded IDs, debug statements |
@@ -205,13 +276,56 @@ Highlights below; see [TOOLS.md](TOOLS.md) for the complete reference with param
205
276
  | `SF_ACCESS_TOKEN` | Static access token (expires ~1hr) | For static |
206
277
  | `PORT` | HTTP server port (default: 3000) | For HTTP mode |
207
278
  | `TRANSPORT` | `stdio` or `http` (default: stdio) | Optional |
279
+ | `SF_TOOLSETS` | Toolsets to load at startup: `all`, `none`, or a comma-separated list (default: `core,metadata`) | Optional |
280
+ | `SF_TOOLSETS_VERBOSE` | Set to `1` to print the full toolset list to stderr on startup | Optional |
281
+ | `SF_PRODUCTION_GUARD` | `destructive` (default), `strict`, or `off` — see below | Optional |
282
+
283
+ ---
284
+
285
+ ## Production write guard
286
+
287
+ This server hands an LLM a Salesforce credential, and the LLM decides what to do with it. Any text
288
+ the model reads on the way — a case comment, a field description, a retrieved `.flow` file, an
289
+ email body — can carry an instruction, and nothing downstream can tell it apart from one you typed.
290
+
291
+ So against a **production org**, nine tools are refused by default:
292
+
293
+ | Blocked on production | Why |
294
+ |---|---|
295
+ | `sf_delete_metadata` | Destroys metadata and every record in it |
296
+ | `sf_delete_record`, `sf_bulk_delete_records` | Destroy data |
297
+ | `sf_execute_anonymous_apex` | Arbitrary code that leaves no artifact behind |
298
+ | `sf_uninstall_package` | Removes a managed package and its data |
299
+ | `sf_create_user`, `sf_update_user` | Privilege escalation |
300
+ | `sf_reset_user_password`, `sf_freeze_user` | Account takeover / lockout |
301
+
302
+ **Metadata creation is not affected.** All 137 `sf_create_*` tools, `sf_deploy_metadata` and
303
+ `sf_retrieve_metadata` work against production exactly as before — authoring metadata by natural
304
+ language is the point of this package, and a guard that taxed it would just get switched off.
305
+
306
+ Apex authoring (`sf_create_apex_class`, `sf_create_apex_trigger`) is also **not** blocked, even
307
+ though a trigger runs on every DML. The line drawn is auditability: created Apex is metadata — it
308
+ has a name, an author and a deploy record, and can be found and removed. `sf_execute_anonymous_apex`
309
+ leaves nothing to find, which is why that one is blocked.
310
+
311
+ An org counts as production only when it is **not** a sandbox, **not** a Developer Edition org, and
312
+ **not** on a trial/scratch expiry. Sandboxes, dev orgs and scratch orgs are never gated.
313
+
314
+ ```jsonc
315
+ { "env": { "SF_PRODUCTION_GUARD": "strict" } } // refuse ALL writes on production
316
+ { "env": { "SF_PRODUCTION_GUARD": "off" } } // no guard at all
317
+ ```
318
+
319
+ The guard is a hard refusal, not a confirmation prompt: a prompt the calling agent can approve by
320
+ itself is not a control, and this server is often run with client permissions bypassed. Only the
321
+ environment variable lifts it.
208
322
 
209
323
  ---
210
324
 
211
325
  ## Documentation
212
326
 
213
327
  - [SETUP.md](SETUP.md) — Prerequisites, authentication, Claude configuration
214
- - [TOOLS.md](TOOLS.md) — All 223 tools with full parameter documentation
328
+ - [TOOLS.md](TOOLS.md) — All 228 tools with full parameter documentation
215
329
  - [AGENTFORCE.md](AGENTFORCE.md) — Agentforce agent creation guide
216
330
  - [APEX_LWC.md](APEX_LWC.md) — Apex and LWC development guide
217
331
  - [CHANGELOG.md](CHANGELOG.md) — Version history
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 | Supported |
72
- |---------|-----------|
73
- | 2.1.x | ✅ Current |
74
- | 2.0.x | ⚠️ Upgrade recommended |
75
- | 1.x | No longer supported |
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