Salesforce automation has undergone a structural shift in 2025–2026. What was once a patchwork of sfdx scripts, middleware integrations, and brittle Selenium automation has converged into a coherent programmatic stack: the sf CLI for headless DevOps, a native MCP server layer for LLM tool calls, REST/Bulk APIs with JWT auth for data pipelines, and a growing ecosystem of AI agent frameworks that treat Salesforce as a set of callable tools rather than a UI to be scraped.
The inflection point was Salesforce’s Headless 360 initiative, announced at TrailheaDX 2026 (April 2026), which made explicit what practitioners had been doing piecemeal: the entire platform — CRM, Agentforce, Data Cloud, Slack — is now addressable as APIs, MCP tools, and CLI commands without ever opening a browser. Simultaneously, the Anthropic–Salesforce strategic partnership (announced October 2025, expanded February 2026) made Claude a first-class consumer of Salesforce MCP tools, with every Developer Edition org now shipping with hosted MCP servers and Claude Sonnet as the default coding model.
This note covers each layer of the automation stack with practitioner-level specificity: what works, what breaks, and where the real friction lives.
1. Salesforce CLI (sf / sfdx)
1.1 Migration State: sfdx → sf
The sfdx executable is deprecated. As of June 2024, Salesforce removed sfdx-style command reference documentation. All new automation should use the sf executable with sf-style commands. The two coexist on installed systems but diverge in output schema; scripts parsing sfdx JSON output will need updates.
Migration reference: sf migrate maps old commands to equivalents. The most common are:
| Old (sfdx) | New (sf) |
|---|---|
sfdx force:org:login:web |
sf org login web |
sfdx force:data:soql:query |
sf data query |
sfdx force:source:deploy |
sf project deploy start |
sfdx force:apex:test:run |
sf apex test run |
sfdx force:mdapi:retrieve |
sf project retrieve start |
1.2 What the CLI Can Do Headlessly
The sf CLI exposes 220+ commands across these functional areas relevant to automation:
Authentication
sf org login jwt— JWT Bearer flow; takes a certificate key file and connected app consumer key; no browser required; the correct auth method for CI/CD and agent pipelines.sf org login access-token— inject a pre-obtained access token directly; useful in container environments where the JWT private key is mounted as a secret.
Data Operations
sf data query --query "SELECT Id, Name FROM Account LIMIT 100" --result-format json— SOQL query, returns structured JSON; pipe tojqor parse directly in scripts.sf data export tree --query "SELECT ..." --output-dir ./export— exports records and relationships as JSON files for seeding, backup, or migration.sf data upsert bulk --sobject Account --file accounts.csv --external-id ExternalId__c— wraps Bulk API 2.0 for large-volume upsert; handles job lifecycle (create → upload → close → poll → results) automatically.sf data delete bulk --sobject Lead --query "SELECT Id FROM Lead WHERE Status = 'Unqualified'"— bulk delete via query.
Metadata & Deployment
sf project deploy start --source-dir force-app/main/default— deploys source-format metadata; supports--dry-runfor validation.sf project retrieve start --metadata ApexClass:MyClass— targeted retrieve by type and name.sf package create,sf package version create— managed/unlocked package lifecycle, scriptable end-to-end.
Apex Execution
sf apex run --file scripts/setup.apex— anonymous Apex execution; useful for data seeding, configuration, and one-off operations that need Apex governor access.sf apex test run --class-names MyTest --result-format json— runs specific test classes; returns pass/fail with coverage JSON.
Agentforce (GA in Summer '26)
sf agent preview start— starts an interactive agent test session programmatically.sf agent preview send --message "Summarize open opportunities for ACME Corp"— sends a message to the active session.sf agent preview end— terminates the session and returns transcript JSON.
This makes end-to-end Agentforce regression testing scriptable from a CI pipeline for the first time.
1.3 Security Breaking Change: Credentials in CLI Output (May 2026)
Critical for existing scripts. Starting May 27, 2026, sf removed access tokens, passwords, and SFDX Auth URLs from standard CLI output, including --json outputs. Scripts that grep for sfdxAuthUrl or parse accessToken out of sf org display --json will silently fail to get credentials after this date.
The migration path:
- Use
sf org display --verboseonly when explicitly needed in a dedicated step; output goes to a dedicated secure channel. - In CI/CD pipelines, mount the JWT private key as a secret and authenticate freshly per run rather than reusing captured tokens.
- The temporary env var workaround (
SF_ACCESS_TOKEN) is also decommissioned in Summer '26.
1.4 JSON-First Scripting Pattern
The CLI is designed for machine consumption via --json. Always use it in scripts:
# Example: query and extract Account IDs into a file
sf data query \
--query "SELECT Id, Name, AnnualRevenue FROM Account WHERE AnnualRevenue > 1000000" \
--result-format json \
--json | jq -r '.result.records[].Id' > account_ids.txt
The --json flag wraps every command output in a consistent envelope:
{
"status": 0,
"result": { ... },
"warnings": []
}
Non-zero status indicates failure; parse before trusting result. Error details appear in result.message and result.stack.
1.5 CI/CD Integration Patterns
Standard patterns for GitHub Actions / CircleCI / Jenkins:
- Store the JWT private key as a base64-encoded secret (
SF_PRIVATE_KEY_BASE64). - Decode at runtime:
echo $SF_PRIVATE_KEY_BASE64 | base64 -d > server.key. - Auth:
sf org login jwt --client-id $SF_CLIENT_ID --jwt-key-file server.key --username $SF_USERNAME --set-default. - Proceed with deploy/test/data operations.
- The org alias is scoped to the process; no global keychain pollution.
For sandbox refreshes and scratch org creation (common in automated testing pipelines):
sf org create scratch --definition-file config/project-scratch-def.json --alias ci-org --duration-days 1
sf project deploy start --target-org ci-org
sf apex test run --target-org ci-org --result-format json --code-coverage
sf org delete scratch --target-org ci-org --no-prompt
2. MCP Servers for Salesforce
2.1 Architecture Overview
Model Context Protocol (MCP) is the transport layer that exposes Salesforce capabilities as callable tools for LLMs. An MCP server runs as a local process (stdio transport) or a remote HTTP endpoint (SSE/streamable HTTP transport); an MCP client (Claude Desktop, Claude Code, Cursor, Windsurf, Codex) connects and calls tools by name with structured arguments.
For Salesforce, the ecosystem has split into three tiers: Salesforce’s official hosted MCP servers (Headless 360), the official DX MCP server for development workflows, and community-built servers for specific integration needs.
2.2 Official Salesforce MCP Servers (Headless 360)
Announced at TDX 2026 and reaching GA/Developer Preview through Summer '26, Headless 360 is the umbrella brand for Salesforce’s native MCP offering.
Salesforce DX MCP Server (@salesforce/mcp)
The primary development-focused server. Exposes 60+ tools organized into named toolsets:
| Toolset | Sample Tools |
|---|---|
metadata |
retrieve_metadata, deploy_metadata, list_metadata_types |
data |
run_soql_query, run_sosl_search, upsert_record, delete_record |
testing |
run_apex_tests, get_test_coverage, run_agent_test |
lwc |
create_lwc_component, lint_lwc, preview_lwc |
devops |
create_scratch_org, create_package_version, deploy_pipeline_stage |
analysis |
analyze_apex_class, detect_security_issues, explain_flow |
agentforce |
create_agent, test_agent, deploy_agent_to_production |
Configuration in Claude Code’s ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"salesforce-dx": {
"command": "npx",
"args": ["@salesforce/mcp", "--toolsets", "metadata,data,testing,devops"],
"env": {
"SF_TARGET_ORG": "my-sandbox-alias"
}
}
}
}
With this configured, a prompt like “Deploy the AccountTrigger class to my sandbox and run its tests” executes as a multi-tool call: retrieve_metadata → deploy_metadata → run_apex_tests → result.
Data 360 MCP Server
Released as open source in Developer Preview (May 2026). Connects Data Cloud APIs to any MCP client via stdio transport. Uses a “facade tool architecture” — roughly 200 API operations are exposed through a smaller set of meta-tools that use semantic routing internally, avoiding context window exhaustion.
Primary use case: natural language queries against Data Cloud unified profiles and calculated insights without writing SOQL/SQL directly.
Tableau MCP Server
Exposes Tableau REST API operations — publishing workbooks, querying data sources, triggering refreshes — as MCP tools. Relevant for automation pipelines that need to push fresh data and trigger dashboard refreshes without a human login.
MuleSoft MCP Server
Exposes API catalog discovery and runtime invocation. An LLM can ask “what APIs exist for order management” and get back a structured catalog, then invoke specific endpoints.
2.3 Community MCP Servers
Several community implementations precede and supplement the official servers. These are more useful for pure CRM data operations (CRUD on standard and custom objects) vs. the DX server’s development focus.
jpmonette/salesforce-mcp (GitHub)
- Minimal implementation using
jsforce(Node.js Salesforce library). - Tools: SOQL query, record CRUD on any SObject, describe object schema.
- Auth: username/password + security token, or connected app OAuth. Minimal overhead to set up.
- Best for: quick CRM read/write from Claude Desktop without the full DX stack.
tsmztech/mcp-server-salesforce (GitHub)
- More complete implementation; exposes metadata operations alongside data operations.
- Tools include:
query_salesforce,create_record,update_record,delete_record,describe_object,list_objects,get_metadata,deploy_metadata. - Handles both production and sandbox via configurable
SF_LOGIN_URL. - Auth: OAuth 2.0 with username/password flow or JWT Bearer.
advancedcommunities/salesforce-mcp-server (GitHub)
- Drives Salesforce via the
sfCLI rather than direct API calls. - Useful when the operator already has authenticated
sfsessions and doesn’t want to manage separate credentials for the MCP server. - Tools: SOQL via
sf data query, Apex execution viasf apex run, metadata deploy/retrieve, org info. - Limitation: slower than direct API calls because each tool call spawns a CLI subprocess.
smn2gnt/MCP-Salesforce (GitHub)
- Focused specifically on SOQL and SOSL search operations.
- Minimal footprint; useful as a read-only data access layer for LLMs that need CRM context without write access.
forcedotcom/mobile-mcp-tools (GitHub)
- Official Salesforce repository for mobile-specific MCP tools.
- Covers mobile app testing, Mobile SDK integration, and push notification testing.
- More niche; relevant for teams building Salesforce mobile apps.
2.4 What LLMs Can Do via MCP
With a Salesforce MCP server connected, an LLM can execute multi-step workflows in a single conversation turn without the human writing any SOQL or API calls:
CRM data retrieval:
“Show me all open opportunities over $500K that haven’t had activity in 30 days”
Resolves to: run_soql_query → SELECT Id, Name, Amount, LastActivityDate FROM Opportunity WHERE StageName != 'Closed Won' AND StageName != 'Closed Lost' AND Amount > 500000 AND LastActivityDate < LAST_N_DAYS:30
Record enrichment:
“Update the Industry field on all Accounts in the Technology sector that are missing it, using the Billing Address to infer”
Resolves to: run_soql_query to find candidates → multiple update_record calls with inferred values.
Deployment workflow:
“Create a new scratch org, deploy the feature/dashboard-redesign branch metadata to it, run all tests, and report back”
Resolves to: create_scratch_org → deploy_metadata → run_apex_tests → result summary.
Schema discovery:
“What custom fields exist on the Lead object that are not on any page layout?”
Resolves to: describe_object for Lead → get_metadata for page layouts → diff and report.
3. Browser-Use and Computer-Use Automation
3.1 When Browser Automation Is Necessary
The Salesforce API surface is comprehensive but not complete. Operations that have no API equivalent as of mid-2026:
- Certain Setup menu configurations (custom settings UI-only paths, some security controls)
- Report Builder interactive configuration (reports themselves can be queried via Analytics API, but creation through the UI has no equivalent)
- Lightning App Builder drag-and-drop layout configuration
- Some Einstein-specific setup flows
- Trailhead / myTrailhead administrative tasks
- Org-level UI configuration that predates API parity (legacy console setups)
For these, browser automation is the only programmatic path.
3.2 Playwright for Salesforce
Playwright is the baseline for Salesforce browser automation in 2026. Key characteristics that matter for Salesforce specifically:
Shadow DOM handling. Salesforce Lightning (LWC) uses Shadow DOM to encapsulate component internals. Playwright pierces shadow boundaries natively with locator.shadow() and page.locator('selector >> css=inner-selector'). This is the primary reason older Selenium-based automation broke on Lightning — Selenium required explicit shadow root traversal; Playwright handles it transparently.
Salesforce is migrating from Synthetic Shadow DOM (a polyfill) to Native Shadow DOM. Summer '26 includes components in “mixed shadow mode” (Beta). This migration can break automation that relied on synthetic shadow’s slightly more permissive traversal. Test against native shadow in a Developer Edition before it reaches production.
Authentication. The cleanest pattern for headless Playwright against Salesforce:
- Use the REST API to get an OAuth access token (JWT Bearer flow, no browser needed).
- Construct the Salesforce session URL:
https://<instance>.salesforce.com/secur/frontdoor.jsp?sid=<access_token>. - Navigate to this URL in Playwright — Salesforce recognizes the session and drops a valid cookie.
- Proceed with UI interactions; no username/password form handling required.
This avoids MFA prompts, login page variability, and the SF_USE_ADVANCED_API_SECURITY_FOR_HEADLESS_LOGIN requirement for direct username/password flows.
Practical script skeleton:
import asyncio
import requests
from playwright.async_api import async_playwright
async def salesforce_session(access_token: str, instance_url: str):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
# Session injection — bypasses MFA
await page.goto(f"{instance_url}/secur/frontdoor.jsp?sid={access_token}")
await page.wait_for_url("**/lightning/**")
# Proceed with UI automation
return page, browser
Locator strategy for Lightning components.
Lightning component selectors often look like c-custom-modal lightning-input[data-field-name="Email"]. The reliable pattern:
- Prefer
data-*attributes andaria-labelover CSS class-based selectors; Lightning generates random class names on re-render. - Use
page.get_by_role()andpage.get_by_label()— these survive UI refactors better than brittle CSS paths. - For inline-edit on list views and record pages, wait for
[data-target-reveals]attributes to appear rather than timing out.
3.3 Browser-Use Python Library
The browser-use library (50,000+ GitHub stars as of 2026) wraps Playwright with an LLM-driven agent loop: the model observes the DOM state, decides the next action, executes it via Playwright, and repeats until the task is complete. This is the closest thing to a general-purpose Salesforce UI agent without custom code.
Pattern for Salesforce:
from browser_use import Agent
from langchain_anthropic import ChatAnthropic
async def run_salesforce_task(task: str, salesforce_url: str):
agent = Agent(
task=task,
llm=ChatAnthropic(model="claude-sonnet-4-5"),
browser_config={"headless": True}
)
# Pre-seed session cookie
await agent.browser.navigate(f"{salesforce_url}/secur/frontdoor.jsp?sid={access_token}")
result = await agent.run()
return result
Strengths: Handles tasks that resist hand-coded automation — navigating unfamiliar page structures, recovering from unexpected modal dialogs, interpreting confirmation screens. Good for one-off administrative tasks.
Limitations:
- Expensive: each action requires an LLM call. A 10-step task might cost $0.05–0.20 depending on model and DOM complexity.
- Non-deterministic: the model may choose different paths on retries; unsuitable for high-volume production pipelines.
- Session stability: long-running tasks (>30 steps) risk Salesforce session timeout; build in checkpoint logic.
- Shadow DOM: as of 2026,
browser-usepasses the DOM tree as text context to the LLM; Shadow DOM boundaries may cause the model to miss elements. Workaround: inject a DOM-flattening script viapage.evaluate()before the agent loop starts.
3.4 AgentQL
AgentQL provides a query language for AI agents to extract structured data from web pages without CSS selectors or XPath. For Salesforce specifically:
import agentql
page = agentql.wrap(playwright_page)
# Natural language element selection
result = await page.query_elements("""
{
opportunity_table {
rows[] {
name
stage
amount
close_date
}
}
}
""")
AgentQL resolves the query semantically — it figures out which DOM elements correspond to “opportunity_table” and “amount” rather than requiring the developer to know the CSS structure. This is particularly useful for Salesforce because Lightning component class names change between releases.
Current limitation: AgentQL’s semantic resolution calls its own API, adding ~200–500ms latency per query. For automation scripts running thousands of queries, this is significant. Use it for low-frequency, high-brittleness scenarios; use explicit Playwright locators for high-volume paths.
3.5 Stagehand
Stagehand (Browserbase) is a higher-level browser automation framework that uses LLM function calls to drive Playwright. Three primitives:
act()— perform an action described in natural language:await page.act("Click the New Opportunity button")extract()— extract structured data:await page.extract({ schema: OpportunitySchema, instruction: "Extract all opportunities in the list" })observe()— get possible actions on the current page
For Salesforce, extract() is particularly valuable — it handles the Shadow DOM complexity internally and returns typed data without requiring the developer to write any selectors.
Stagehand vs. browser-use for Salesforce:
- Stagehand is faster at extraction tasks (single LLM call per
extract()); browser-use is better at multi-step navigation tasks. - Stagehand integrates with Browserbase’s hosted browser infrastructure, eliminating the need to manage Chromium instances in production.
- browser-use has a more active community and more Salesforce-specific examples in the wild.
4. Salesforce APIs for Automation
4.1 API Landscape Summary
| API | Best For | Max Volume | Auth |
|---|---|---|---|
| REST API | CRUD, <10K records, ad-hoc queries | 15K concurrent connections | OAuth 2.0 all flows |
| Bulk API 2.0 | Mass insert/upsert/delete/query | 100M records/24h | OAuth 2.0 all flows |
| Streaming API (CometD) | Real-time change events, CDC | Subscription-based | OAuth 2.0 all flows |
| Metadata API | Deploying/retrieving org configuration | Per-org deployment slots | OAuth 2.0 all flows |
| Tooling API | Apex execution, code coverage, debug | Org governor limits | OAuth 2.0 all flows |
| Connect API | Chatter, Experience Cloud, Files | Rate-limited per endpoint | OAuth 2.0 all flows |
| Analytics API | Reports, dashboards, Einstein Discovery | Report-specific | OAuth 2.0 all flows |
SOAP login() retires in Summer '27 (API versions 31.0–64.0). Any integration using username/password over SOAP will break. Migrate to OAuth JWT Bearer before the cutoff.
4.2 OAuth 2.0 JWT Bearer Flow (Headless Standard)
This is the canonical auth pattern for any automated system. No user interaction, no refresh token rotation, certificate-based.
Setup (one-time, done by Salesforce admin):
- Create a Connected App in Salesforce Setup.
- Enable OAuth; add scopes:
api,refresh_token(omit for pure JWT),offline_access. - Generate an RSA key pair (
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes). - Upload
server.crtto the Connected App’s “Use Digital Signatures” field. - Pre-authorize the integration user: Setup → Connected App → “Manage” → “Edit Policies” → “Permitted Users: Admin approved users are pre-authorized”.
- Assign the Connected App permission to the integration user profile or permission set.
Runtime flow:
- Build a JWT: header (
{"alg":"RS256","typ":"JWT"}) + payload ({"iss": "<consumer_key>", "sub": "<integration_username>", "aud": "https://login.salesforce.com", "exp": <now+300>}). - Sign with private key using RS256.
- POST to
https://login.salesforce.com/services/oauth2/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<signed_jwt>. - Response:
{"access_token": "...", "instance_url": "https://yourorg.salesforce.com", "token_type": "Bearer"}. - Use
access_tokenasAuthorization: Bearer <token>on subsequent API calls.
Access tokens expire after the Connected App’s session timeout (default 2 hours; configurable). Re-run the JWT flow to get a fresh token — there is no refresh token in the JWT Bearer flow by design.
Python implementation using simple-salesforce or jsforce handles this automatically if you provide the certificate path and consumer key.
4.3 REST API Practical Limits
- Daily limit: 100,000 + (1,000 × Enterprise users) + (5,000 × Unlimited users) API calls per 24-hour rolling window. This pool is shared by REST, SOAP, Bulk API, Connect API.
- Concurrent long-running requests: 25 simultaneous requests lasting ≥20 seconds in production; 5 in Developer Edition.
- HTTP 403
REQUEST_LIMIT_EXCEEDED: returned when daily limit is hit; no retry will succeed until the window rolls. Build circuit-breaker logic with the/services/data/v62.0/limitsendpoint — checkDailyApiRequests.Remainingbefore starting high-volume operations. - Composite API: bundle up to 25 subrequests in a single HTTP call. Use
services/data/v62.0/compositeto update parent + child records in one round trip. This is the most practical way to reduce API call consumption in agent pipelines. - sObject Collections: update/insert up to 200 records per call with
/services/data/v62.0/composite/sobjects. Far more efficient than looping individual record updates from an agent.
4.4 Bulk API 2.0
The correct choice for any operation involving >10,000 records or for agents that might trigger large data exports/imports.
Job lifecycle:
- Create job:
POST /services/data/v62.0/jobs/ingestwith{"object": "Account", "operation": "upsert", "externalIdFieldName": "ExternalId__c", "contentType": "CSV"} - Upload CSV:
PUT /services/data/v62.0/jobs/ingest/<jobId>/batcheswith CSV body. - Close job:
PATCH /services/data/v62.0/jobs/ingest/<jobId>with{"state": "UploadComplete"}. - Poll state:
GET /services/data/v62.0/jobs/ingest/<jobId>untilstateisJobCompleteorFailed. - Retrieve results:
GET /services/data/v62.0/jobs/ingest/<jobId>/successfulResultsand.../failedResults.
Key limits:
- 100 million records per rolling 24 hours.
- 150 million records if counting individual Bulk API 2.0 HTTP requests.
- Maximum 10,000 records per batch in Bulk API v1 (superseded); no per-batch limit in Bulk API 2.0 — CSV size limits apply (150MB per upload request).
- Set
columnDelimiter: PIPEif data contains commas.
4.5 Streaming API / Change Data Capture
For agent pipelines that need to react to Salesforce data changes in real time (vs. polling):
- Change Data Capture (CDC): Subscribe to
/data/AccountChangeEventor any enabled SObject’s change channel. Receives create/update/delete/undelete events within seconds. - Platform Events: Custom pub/sub events. Producers publish via REST API; consumers subscribe via CometD or Apex triggers. Useful for triggering agent workflows from Salesforce-side events.
- PushTopic Streaming (legacy): Being superseded by CDC; avoid for new implementations.
CometD client libraries exist for Python (cometd-asyncio), Node.js (faye), and Java. The Bayeux handshake requires an active OAuth token; implement token refresh logic for long-running subscriptions.
5. AI Agent Patterns
5.1 LangChain + Salesforce
The langchain-salesforce community integration package exposes Salesforce as a set of LangChain tools. The typical setup:
from langchain_anthropic import ChatAnthropic
from langchain_salesforce import SalesforceTool, SalesforceToolkit
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
# Initialize toolkit
toolkit = SalesforceToolkit.from_env() # reads SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN
tools = toolkit.get_tools()
# Tools: query_records, create_record, update_record, delete_record,
# describe_object, run_apex, get_object_list
llm = ChatAnthropic(model="claude-opus-4-5")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a Salesforce assistant. Use tools to answer questions about CRM data."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "Find all Accounts in California with no open Opportunities"})
Limitations of LangChain + Salesforce in production:
- The community toolkit uses username/password auth, not JWT Bearer. Blocked by MFA-enforced profiles.
- No Bulk API support; large data tasks will exhaust REST limits.
- No streaming; the agent waits for full SOQL results before continuing.
- Tool call errors from Salesforce (e.g., SOQL syntax errors) propagate as raw exception text; the LLM may retry with a malformed query in a loop.
5.2 LangGraph for Multi-Step Salesforce Workflows
LangGraph provides stateful, graph-based orchestration. The right choice when the Salesforce automation involves decision branches, human-in-the-loop approval, or multiple coordinated agents.
Architecture pattern for a lead enrichment pipeline:
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
class LeadEnrichmentState(TypedDict):
lead_ids: List[str]
raw_leads: List[dict]
enriched_leads: List[dict]
failed_leads: List[str]
updated_count: int
# Node definitions
async def fetch_leads(state):
"""Query Salesforce for unenriched leads"""
result = await sf.query("SELECT Id, FirstName, LastName, Company, Email FROM Lead WHERE Industry = null LIMIT 100")
return {"raw_leads": result["records"]}
async def enrich_with_llm(state):
"""Use LLM to infer missing fields"""
...
async def update_salesforce(state):
"""Bulk update enriched records"""
...
async def handle_failures(state):
"""Log failures, create tasks for manual review"""
...
# Graph
graph = StateGraph(LeadEnrichmentState)
graph.add_node("fetch", fetch_leads)
graph.add_node("enrich", enrich_with_llm)
graph.add_node("update", update_salesforce)
graph.add_node("handle_failures", handle_failures)
graph.add_edge("fetch", "enrich")
graph.add_conditional_edges("enrich", route_by_confidence, {
"high": "update",
"low": "handle_failures"
})
graph.add_edge("update", END)
graph.set_entry_point("fetch")
LangGraph’s key advantage here: if the update node hits a Salesforce API limit, the graph can checkpoint the state and resume after the window resets — without losing progress on the already-enriched leads.
5.3 Claude with Salesforce Function Tools (Direct API Pattern)
For operators who want to use Claude directly (via the Anthropic API) without LangChain overhead, the tool use pattern gives precise control:
import anthropic
import simple_salesforce as sf_lib
tools = [
{
"name": "soql_query",
"description": "Execute a SOQL query against Salesforce and return results as JSON",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The SOQL query to execute"}
},
"required": ["query"]
}
},
{
"name": "update_record",
"description": "Update fields on a Salesforce record",
"input_schema": {
"type": "object",
"properties": {
"sobject": {"type": "string"},
"record_id": {"type": "string"},
"fields": {"type": "object", "description": "Field name to value mapping"}
},
"required": ["sobject", "record_id", "fields"]
}
}
]
def execute_tool(tool_name, tool_input, sf):
if tool_name == "soql_query":
return sf.query_all(tool_input["query"])
elif tool_name == "update_record":
getattr(sf, tool_input["sobject"]).update(
tool_input["record_id"],
tool_input["fields"]
)
return {"success": True}
# Agentic loop
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Close all opportunities in 'Needs Analysis' stage that haven't had activity in 90 days as Lost, with reason 'Stale - no activity'"}]
while True:
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=4096,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
break
elif response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input, sf)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
This pattern is production-grade and gives full control over error handling, retry logic, and logging. The agent will naturally use multiple tool calls — first a SOQL query to identify stale opportunities, then a loop of update_record calls, then optionally a follow-up query to confirm the updates.
5.4 Composio Salesforce Integration
Composio provides a managed integration layer that exposes 250+ Salesforce actions as pre-built tools callable from LangChain, Claude, OpenAI, and other frameworks. The advantage over hand-rolling tool definitions is that Composio handles:
- OAuth credential management and refresh
- Tool definition schema generation from Salesforce object describe
- Rate limit awareness and retry logic
from composio_langchain import ComposioToolSet, App
toolset = ComposioToolSet()
tools = toolset.get_tools(apps=[App.SALESFORCE])
# Returns 250+ tools: SALESFORCE_QUERY_RECORDS, SALESFORCE_CREATE_LEAD,
# SALESFORCE_UPDATE_OPPORTUNITY, SALESFORCE_CREATE_TASK, etc.
The trade-off: Composio routes tool calls through its own API, adding latency and a data processing dependency. Not appropriate for orgs with strict data residency requirements.
6. Real-World Examples and Community Patterns
6.1 Documented Agent Implementations
LangChain + Salesforce + Groq (Salesforce Ben tutorial, 2025) A public walkthrough showing a conversational agent that answers natural language questions about Salesforce data using LangChain’s agent framework with Groq as the LLM provider. Pattern: user asks question → agent generates SOQL → queries Salesforce → formats response. Demonstrates the baseline but uses username/password auth (not production-appropriate).
Orchestrating Salesforce with LangGraph + OpenAI (Agentic Trails, September 2025)
Production-oriented example using LangGraph’s StateGraph to manage a multi-step opportunity update workflow. Key contribution: showing how to handle Salesforce’s UNABLE_TO_LOCK_ROW errors (record-level locking conflicts common in high-concurrency automation) with graph-level retry nodes rather than tight loops.
Claude Code + Salesforce API patterns (SyncGTM, 2026) A series of practitioner posts documenting Claude Code connected to Salesforce via MCP for pipeline management tasks: enriching contact records, running lead scoring queries, generating pipeline reports from SOQL in plain English. Demonstrates the MCP pattern without requiring the operator to write any Salesforce API code.
Salesforce Headless 360 + RevOps Automation (MindStudio, 2026) Documents how RevOps teams are using the Headless 360 MCP servers to automate tasks that previously required Salesforce admins: reassigning leads from departed reps, bulk-updating account tiers from enrichment data, creating follow-up tasks from email thread analysis.
6.2 GitHub Repositories of Note
salesforcecli/mcp— Official Salesforce DX MCP server. The canonical reference for the official tool schema.jpmonette/salesforce-mcp— Minimal jsforce-based MCP; good starting point for CRM-only use cases.tsmztech/mcp-server-salesforce— Covers metadata + data; more complete than jpmonette for mixed workloads.advancedcommunities/salesforce-mcp-server— CLI-backed MCP server; simplest auth story if you already have authenticatedsfsessions.forcedotcom/data-cloud-mcp— Official Data 360 MCP server (open-source Developer Preview).forcedotcom/mobile-mcp-tools— Official mobile MCP tools; niche but maintained.
6.3 Emerging Pattern: Claude in Slack → Salesforce
The Anthropic–Salesforce partnership delivered a concrete artifact: Claude inside Slack with Salesforce context. As of February 2026:
- Sales reps can ask Claude in Slack: “Summarize my ACME Corp account and list open tasks” — Claude queries Salesforce via MCP and returns a structured summary.
- Managers can request pipeline reports in natural language inside Slack channels — Claude generates SOQL, runs it against the org, and formats the results.
- Deal-room Slack channels can have Claude automatically log key decisions as Salesforce Activities.
This pattern matters because it eliminates the context switch between communication (Slack) and CRM (Salesforce). Practitioners are reporting that reps update Salesforce more consistently when the update happens via a Slack message to Claude than when it requires opening Salesforce directly.
7. Gotchas and Production Considerations
7.1 Connected App Setup Requirements
Every programmatic integration requires a Connected App. The most common automation breakdowns trace back to Connected App misconfiguration:
OAuth scopes must be explicitly granted. Common scopes needed for automation:
api— access to REST, SOAP, Bulk APIs.refresh_token/offline_access— needed for refresh token grant flows (not JWT Bearer).web— needed if you’re using the frontdoor.jsp session injection approach for browser automation.full— supersetsapi; use cautiously (grants all permissions the running user has).cdp_query_api/cdp_profile_api— needed for Data Cloud access.
Pre-authorization requirement for JWT Bearer. Without the “Admin approved users are pre-authorized” policy enabled and the integration user explicitly assigned to the Connected App, the JWT Bearer flow will return invalid_grant with no further explanation. This is the most common JWT setup failure.
IP Relaxation vs. Trusted IP Ranges. If you set Trusted IP Ranges on a Connected App (whitelisting specific IP blocks), the app will reject connections from any IP outside those ranges regardless of valid OAuth tokens. For agents running in cloud environments with dynamic IPs (Lambda, Cloud Run, GitHub Actions), either set IP Relaxation to “Relax IP Restrictions” or use Continuous IP Enforcement, which enforces IP ranges only at login, not on token refresh.
7.2 Connected App Usage Restrictions (September 2025)
Starting September 2025, uninstalled Connected Apps are blocked by default. Any integration using a Connected App that isn’t installed in the org will fail with INACTIVE_APP errors. Resolution:
- Install the Connected App in the org (go to AppExchange listing or install from package URL).
- Or assign the “Approve Uninstalled Connected Apps” or “Use Any API Client” permission to the integration user.
This change silently broke a large number of legacy integrations that were using Connected Apps from third-party tools without explicitly installing them.
7.3 API Governor Limits
The shared pool problem. REST API, SOAP API, Bulk API, and Connect API draw from the same daily call pool. An Apex trigger that makes an outbound callout per-record consumes the same pool as your agent pipeline. Audit all API consumers in the org before deploying agents that make high volumes of calls.
Identify limit consumption. Use the Limits endpoint:
curl -H "Authorization: Bearer <token>" \
"https://yourorg.salesforce.com/services/data/v62.0/limits" | \
jq '.DailyApiRequests'
# {"Max": 750000, "Remaining": 642000}
Alert at 20% remaining — not the standard 10% — to give time to throttle before hitting zero. At zero, all API calls return 403 until the 24-hour window resets.
Composite and Collections APIs are the primary mitigation. An agent that updates 500 records one at a time consumes 500 API calls. The same operation via composite/sobjects consumes 3 calls (200 records per call). Enforce this in the tool implementation layer, not in the prompt.
7.4 Record-Locking Conflicts
Salesforce uses row-level locking on record updates. In high-concurrency automation (multiple agents or pipeline stages touching the same records simultaneously), you’ll encounter UNABLE_TO_LOCK_ROW errors. This is not a transient network error — it is a deliberate lock that will not resolve by immediately retrying.
Pattern: implement exponential backoff with jitter specifically for this error code. A 2–10 second random sleep between retries (up to 3 attempts) resolves the conflict in most cases.
7.5 Session Timeout and Token Management
Salesforce access tokens expire based on the Connected App’s Session Timeout setting (default: 2 hours; configurable from 15 minutes to 24 hours or “until logout”). For long-running agent pipelines:
- Re-authenticate before each batch run rather than caching tokens across runs.
- For very long jobs (multi-hour Bulk API operations), implement a token refresh check before each batch step.
- The JWT Bearer flow makes refresh trivial — re-running the JWT flow takes under 200ms and always returns a fresh token.
Web session tokens (used by the frontdoor.jsp browser automation pattern) follow the org’s Session Settings timeout, which may be shorter than the Connected App timeout. For browser automation tasks exceeding 30 minutes, check session validity and re-inject if needed.
7.6 MFA and IP Restrictions for Browser Automation
Salesforce’s security posture in 2026 defaults to MFA for all human users. Browser automation against human user accounts will hit MFA prompts. Mitigations:
- Use a dedicated integration user with an integration-specific profile. Set the profile’s Session Security Level to bypass MFA for API/automation access. This requires a System Administrator to configure.
- Use the frontdoor.jsp injection pattern (described in Section 3.2) — this skips the login page entirely, including MFA. The token obtained via JWT Bearer flow is already authenticated; the session injection trades it for a web session cookie without triggering MFA.
- Never automate against a named human user account. This creates audit trail contamination (all automated actions show under the human’s name) and will break when the human user rotates their password or enables personal MFA.
7.7 Data Residency Considerations
Salesforce’s default instance assigns data to a regional pod (NA, EU, AP). For orgs in specific data residency configurations (Hyperforce on AWS/GCP/Azure in specific geographies), the instance_url returned by OAuth will reflect the regional endpoint — e.g., https://yourorg.eu.salesforce.com. Automation that hardcodes login.salesforce.com as the base URL may fail for:
- Sandbox logins (use
test.salesforce.com) - My Domain orgs (use the My Domain URL, obtained from the OAuth response
instance_url) - Government Cloud / Military Cloud (custom login endpoints)
Always use the instance_url from the OAuth response as the base for subsequent API calls, not a hardcoded domain.
For third-party integrations (Composio, LangChain Salesforce toolkit, community MCP servers), verify that tool calls route through the correct regional endpoint and that the integration vendor’s data processing practices comply with your org’s data residency commitments before sending production CRM data.
7.8 Salesforce CLI Security Changes (Summary)
The May 2026 security update requires changes to any script that:
- Parses
accessTokenorsfdxAuthUrlfromsf org display --json - Uses environment variables to capture credentials from CLI output
- Passes
--auth-urlarguments containing tokens inline
The new pattern: credentials are only retrievable via explicit, interactive commands. CI/CD pipelines must authenticate from scratch per run using stored secrets (JWT private key + consumer key), not reuse captured credentials.
8. Decision Framework: Which Approach for Which Task
| Task | Best Approach | Notes |
|---|---|---|
| Deploy metadata to sandbox | sf project deploy start |
Fastest; version-controllable |
| One-time large data load (>10K records) | sf data upsert bulk or Bulk API 2.0 directly |
Don’t use REST for this |
| LLM querying CRM in plain English | Salesforce DX MCP Server | Official; 60+ tools; best tool for devs |
| Read-only CRM context for Claude Desktop | jpmonette/salesforce-mcp |
Minimal setup; no DevOps overhead |
| Multi-step pipeline automation | LangGraph + Salesforce tools | Stateful; checkpointable; handles errors |
| UI-only configuration task | Playwright + frontdoor.jsp injection | No API equivalent; inject session to skip MFA |
| Reactive pipeline (event-triggered) | Streaming API / CDC + agent webhook | Real-time; avoids polling |
| Bulk record enrichment | LangGraph + Bulk API 2.0 | Checkpoint state; respect 100M/day limit |
| End-to-end Agentforce testing | sf agent preview commands |
GA in Summer '26; scriptable in CI |
| Exploratory data analysis by non-developer | Claude in Slack via Salesforce MCP | No SOQL required; works in existing workflow |
9. Forward-Looking Notes (Summer '26 and Beyond)
Agentforce Experience Layer (AXL) decouples agent logic from rendering surface. An agent built against the Salesforce platform can now render its output natively in Slack, Teams, Claude, or any MCP-compatible client without surface-specific code. This will accelerate agent deployment for teams who currently maintain separate Slack bot + Salesforce flow implementations.
SOAP login() retirement (Summer '27). Any integration still using SOAP username/password authentication has approximately 12 months to migrate to OAuth. The sf CLI, all MCP servers, and the REST API already default to OAuth. The stragglers are legacy middleware integrations (Mulesoft flows, older iPaaS connectors) that were set up before OAuth was mandatory.
Native Shadow DOM migration in Lightning components will progressively tighten browser automation. Teams relying on Playwright or browser-use for Salesforce UI automation should audit their locator strategies and switch to semantic locators (get_by_role, get_by_label, AgentQL queries) ahead of their org’s migration window.
Data Cloud MCP server (Developer Preview → GA). The facade architecture handling ~200 API operations is the model for how future Salesforce platform surface will be exposed to LLMs. Expect the same pattern applied to Einstein Analytics and Revenue Cloud in H2 2026.
Sources
- Salesforce CLI Command Reference (Summer '26)
- Connect Claude with Salesforce Hosted MCP Servers — Salesforce Developers Blog
- Introducing MCP Support Across Salesforce — Salesforce Developers Blog
- Introducing the Data 360 MCP Server (Developer Preview)
- New in Salesforce Developer Edition: Agentforce Vibes IDE, Claude 4.5, MCP
- Salesforce Headless 360: No Browser Required — Apex Hours
- Salesforce Headless 360 and Agentforce Vibes 2.0 — Salesforce Ben
- TDX 2026 Reporter’s Notebook: Salesforce Goes Headless
- Salesforce CLI Security Update: Secrets in CI/CD Pipelines
- OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration
- Bulk API 2.0 Developer Guide
- Salesforce APIs: REST, SOAP, Bulk, Tooling & Streaming Guide
- Connected App Usage Restrictions Change
- Salesforce API Rate Limits — Stacksync
- From Agentforce to LangChain to LangGraph — Medium
- How to Build a Salesforce AI Agent With LangChain and Groq — Salesforce Ben
- Orchestrating Salesforce with LangGraph + OpenAI — Agentic Trails
- GitHub: salesforcecli/mcp
- GitHub: jpmonette/salesforce-mcp
- GitHub: tsmztech/mcp-server-salesforce
- GitHub: advancedcommunities/salesforce-mcp-server
- GitHub: smn2gnt/MCP-Salesforce
- GitHub: forcedotcom/mobile-mcp-tools
- Salesforce Test Automation with Playwright — Testrip Technologies
- Salesforce Headless 360 and AI Agents: RevOps Automation — MindStudio
- Claude Code + Salesforce CRM: AI-Powered Pipeline Management — SyncGTM
- Salesforce + Claude AI Integration Guide — Codleo
- Salesforce and Anthropic Partnership Announcement
- The Summer '26 Developer Release Guide