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.
- package/CHANGELOG.md +190 -0
- package/README.md +117 -3
- package/SECURITY.md +25 -5
- package/TOOLS.md +326 -326
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +1189 -1087
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +58 -10
- package/dist/schemas/index.js.map +1 -1
- package/dist/services/deployment.d.ts.map +1 -1
- package/dist/services/deployment.js +60 -2
- 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 +125 -0
- package/dist/services/impact.d.ts.map +1 -0
- package/dist/services/impact.js +707 -0
- package/dist/services/impact.js.map +1 -0
- package/dist/services/salesforce.d.ts +58 -3
- package/dist/services/salesforce.d.ts.map +1 -1
- package/dist/services/salesforce.js +376 -149
- package/dist/services/salesforce.js.map +1 -1
- package/dist/tools/apex.d.ts.map +1 -1
- package/dist/tools/apex.js +13 -2
- package/dist/tools/apex.js.map +1 -1
- package/dist/tools/data.d.ts.map +1 -1
- package/dist/tools/data.js +71 -2
- package/dist/tools/data.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/monitoring.d.ts.map +1 -1
- package/dist/tools/monitoring.js +17 -2
- package/dist/tools/monitoring.js.map +1 -1
- package/dist/toolsets.d.ts +106 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +374 -0
- package/dist/toolsets.js.map +1 -0
- 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)
|
|
6
6
|
[](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
|
|
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 —
|
|
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
|
|
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 |
|
|
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
|
|