Problem
Today execute_anonymous is only registered when the server is launched with --allowed-orgs <list> (or
ALLOW_ALL_ORGS). This forces any host that wants the tool to either name every org up front or trust every
org. It's also coarse — the gate is "can the agent see the tool at all," not "can the agent run Apex against
this specific org right now."
For consumers like the Lana VS Code extension, neither default works:
- Omitting the flag hides a useful tool from agents.
- Setting
ALLOW_ALL_ORGS permits Apex execution against any org the developer is authed to, including
production. The realistic VS Code failure mode is users clicking "Always Allow" on the client-side per-call
prompt, which then waives consent for prod calls too.
Proposed policy
execute_anonymous is always registered. On invocation, the server classifies the target org and applies a
per-call policy that prefers MCP elicitation when the client supports it.
Classifier
Single one-shot SOQL per org per session, cached:
SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1
Pure function: (orgRecord) => 'sandbox' | 'scratch' | 'developer' | 'production'. Cache the classification
on the server instance keyed by connection.getUsername() (or org alias if available) so a given org's type
round-trips at most once per server lifetime.
Decision tree
Applied inside the execute_anonymous handler before any code is run:
1. Classify target org (cached lookup; query if first time).
2. Classification ∈ {sandbox, scratch, developer} → allow.
3. Classification = production AND --allow-production-orgs set → allow.
4. Classification = production AND client.capabilities.elicitation → send elicitation request:
prompt the user with the org
alias and the Apex source.
Allow on confirm; refuse on
cancel/decline.
5. Classification = production, no elicitation, no flag → refuse with documented error.
The client's elicitation capability is read from the initialize handshake
(params.capabilities.elicitation). Cache the boolean on the session once.
Elicitation request (path 4)
If the user confirms, the tool runs as normal. If they cancel/decline (any non-confirm: true response,
including timeout), return isError: true with User declined the production-org execution.
Refusal contract (path 5 — fallback when no elicitation)
isError: true with a content[] text block:
Cannot execute anonymous Apex against production org '<alias>'.
This MCP server blocks production targets by default to prevent accidental data
loss. To enable, restart the server with --allow-production-orgs, or use a client
that supports MCP elicitation for per-call confirmation.
The error names the org, the flag, AND the alternative path — agents are good at relaying actionable text to
humans, so the message is also the docs.
CLI surface change
- Remove
--allowed-orgs (or keep as a no-op deprecation that logs a warning to stderr for one minor
version).
- Add
--allow-production-orgs (boolean). When set, paths 4 and 5 are skipped — production behaves like
sandbox.
- Also consider allowed / banned lists of org with regex pattern matching which override the above behaviour.
E.g
'allowed': ['mydevOrg@dev.com' , '.*sbx@dev.com'],
'banned': ['.*.prod@dev.com', myprodorg@dev.com]
-
We may also want a flag to force always confirm? or flags to always allow the default
or even to disable this tool for safety?
Versioning
Breaking change for anyone today relying on --allowed-orgs to permit prod. Bump the major: 2.0.0.
CHANGELOG migration:
--allowed-orgs <comma list> → no flag for the common case.
ALLOW_ALL_ORGS → --allow-production-orgs only if prod targets are intentional.
Implementation notes
- A's refusal path and D's elicitation path share the same classifier and cache — D is purely an extra branch
after classification returns production. No parallel pipelines.
execute_anonymous registration moves out of the --allowed-orgs conditional and becomes unconditional.
Policy check lives inside the tool handler, before invoking the executeAnonymous SOAP/REST call.
- Org SOQL should reuse the existing Connection abstraction (likely
@salesforce/core Connection).
- The elicitation handler should respect the client's response timeout but enforce a server-side max wait
(60s) so a stuck client doesn't wedge the tool call.
lives inside the tool handler, before invoking the executeAnonymous SOAP/REST call.
- Org SOQL should reuse the existing Connection abstraction (likely
@salesforce/core Connection).
- The elicitation handler should respect the client's response timeout but enforce a server-side max wait (60s) so a stuck
client doesn't wedge the tool call.
Tests
- Unit: classifier function — sandbox, scratch (trial date set), DE, prod, edge cases where flags are unset or
OrganizationType is unfamiliar.
- Unit: cache — second classification of the same org/session issues no additional SOQL.
- Integration: client without elicitation, sandbox target → tool succeeds.
- Integration: client without elicitation, prod target, no flag →
isError: true with documented message.
- Integration: client without elicitation, prod target,
--allow-production-orgs set → tool succeeds.
- Integration: client with elicitation, prod target, user confirms → tool succeeds.
- Integration: client with elicitation, prod target, user cancels →
isError: true "User declined…".
- Integration: client with elicitation, prod target,
--allow-production-orgs set → tool runs without prompting (flag
short-circuits).
Acceptance criteria
- Server started with no flags:
tools/list includes execute_anonymous. Sandbox calls succeed silently.
- Prod call from a non-elicitation client → refused with the documented error.
- Prod call from an elicitation-capable client → user is prompted with the org alias and the Apex source; their choice is
honored.
--allow-production-orgs short-circuits both refuse and prompt paths.
- README: remove
--allowed-orgs from setup; add a "Production safety" section explaining classification, elicitation,
and the flag.
- CHANGELOG entry under 2.0.0 with the breaking-change migration.
Out of scope
- Per-call confirmation for non-prod targets (sandbox calls stay silent — the agent's per-call client-side gate is
sufficient at that risk level).
- Multi-target-org policy memory (e.g., "user already approved prod once this session" — keep it strict per-call until
there's evidence of friction).
Problem
Today
execute_anonymousis only registered when the server is launched with--allowed-orgs <list>(orALLOW_ALL_ORGS). This forces any host that wants the tool to either name every org up front or trust everyorg. It's also coarse — the gate is "can the agent see the tool at all," not "can the agent run Apex against
this specific org right now."
For consumers like the Lana VS Code extension, neither default works:
ALLOW_ALL_ORGSpermits Apex execution against any org the developer is authed to, includingproduction. The realistic VS Code failure mode is users clicking "Always Allow" on the client-side per-call
prompt, which then waives consent for prod calls too.
Proposed policy
execute_anonymousis always registered. On invocation, the server classifies the target org and applies aper-call policy that prefers MCP elicitation when the client supports it.
Classifier
Single one-shot SOQL per org per session, cached:
Pure function:
(orgRecord) => 'sandbox' | 'scratch' | 'developer' | 'production'. Cache the classificationon the server instance keyed by
connection.getUsername()(or org alias if available) so a given org's typeround-trips at most once per server lifetime.
Decision tree
Applied inside the
execute_anonymoushandler before any code is run:The client's elicitation capability is read from the
initializehandshake(
params.capabilities.elicitation). Cache the boolean on the session once.Elicitation request (path 4)
{ "method": "elicitation/create", "params": { "message": "About to execute anonymous Apex against PRODUCTION org '<alias>'.\n\nApex:\n```apex\n<source>\n```\n\nProceed?", "requestedSchema": { "type": "object", "properties": { "confirm": { "type": "boolean", "description": "true to run, false to cancel" } }, "required": ["confirm"] } } }If the user confirms, the tool runs as normal. If they cancel/decline (any non-
confirm: trueresponse,including timeout), return
isError: truewithUser declined the production-org execution.Refusal contract (path 5 — fallback when no elicitation)
isError: truewith acontent[]text block:The error names the org, the flag, AND the alternative path — agents are good at relaying actionable text to
humans, so the message is also the docs.
CLI surface change
--allowed-orgs(or keep as a no-op deprecation that logs a warning to stderr for one minorversion).
--allow-production-orgs(boolean). When set, paths 4 and 5 are skipped — production behaves likesandbox.
E.g
We may also want a flag to force always confirm? or flags to always allow the default
or even to disable this tool for safety?
Versioning
Breaking change for anyone today relying on
--allowed-orgsto permit prod. Bump the major:2.0.0.CHANGELOG migration:
--allowed-orgs <comma list>→ no flag for the common case.ALLOW_ALL_ORGS→--allow-production-orgsonly if prod targets are intentional.Implementation notes
after classification returns
production. No parallel pipelines.execute_anonymousregistration moves out of the--allowed-orgsconditional and becomes unconditional.Policy check lives inside the tool handler, before invoking the executeAnonymous SOAP/REST call.
@salesforce/coreConnection).(60s) so a stuck client doesn't wedge the tool call.
lives inside the tool handler, before invoking the executeAnonymous SOAP/REST call.
@salesforce/coreConnection).client doesn't wedge the tool call.
Tests
OrganizationTypeis unfamiliar.isError: truewith documented message.--allow-production-orgsset → tool succeeds.isError: true"User declined…".--allow-production-orgsset → tool runs without prompting (flagshort-circuits).
Acceptance criteria
tools/listincludesexecute_anonymous. Sandbox calls succeed silently.honored.
--allow-production-orgsshort-circuits both refuse and prompt paths.--allowed-orgsfrom setup; add a "Production safety" section explaining classification, elicitation,and the flag.
Out of scope
sufficient at that risk level).
there's evidence of friction).