Announcing CIMD support for MCP Client registration
Learn more

Query Salesforce with Natural Language Using a Claude Agent

Saif Ali Shaik
Founding Developer Advocate

TL;DR

A complete working agent (Python + TypeScript) that translates natural language into SOQL, updates Salesforce records, and logs activities — per user, not via a shared org connection — using Scalekit to handle all auth.

  • User connects Salesforce once via OAuth (Scalekit vaults the token)
  • Your agent calls list_scoped_tools / listScopedTools to get Salesforce tools in Anthropic's native format, including SOQL execute
  • Standard Claude tool-use loop: Claude writes and executes the SOQL query from plain English; your code calls execute_tool with a user identifier; your code never touches a query string directly

Salesforce stores your pipeline, accounts, and activity history, but getting data out means writing SOQL. Object names, field paths, filter syntax, date literals: SOQL has its own vocabulary, and remembering it correctly takes time. Your Claude agent can handle that. Describe what you want in plain English, and the agent constructs the query, executes it, and returns the results.

This post shows a working Claude agent connected to Salesforce through Scalekit. You get copy-paste Python and TypeScript code, three workflow demos including a SOQL deep dive, and a full list of available Salesforce tools.

Prerequisites

  • Python 3.9+ or Node.js 18+
  • pip install anthropic scalekit-sdk-python python-dotenv (Python) or npm install @anthropic-ai/sdk @scalekit-sdk/node (TypeScript)
  • A Scalekit account with a Salesforce connection configured (Salesforce connector setup)
  • A Salesforce account for testing

How It Fits Together

Your code only calls Scalekit. Scalekit handles Salesforce OAuth, token storage, and refresh on behalf of each user.

The Agent Script

The complete agent. Copy it, set your environment variables, and run it.

Python

""" Claude agent with Scalekit-authenticated Salesforce tools. """ import os import anthropic import scalekit.client from dotenv import find_dotenv, load_dotenv from google.protobuf.json_format import MessageToDict load_dotenv(find_dotenv()) scalekit_client = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) actions = scalekit_client.actions client = anthropic.Anthropic( base_url=os.getenv("ANTHROPIC_BASE_URL"), api_key=os.getenv("ANTHROPIC_API_KEY"), ) identifier = os.getenv("USER_IDENTIFIER", "user_123") response = actions.get_or_create_connected_account( connection_name="salesforce", identifier=identifier, ) if response.connected_account.status != "ACTIVE": link = actions.get_authorization_link(connection_name="salesforce", identifier=identifier) print("Authorize Salesforce:", link.link) print("Re-run this script after completing the OAuth flow.") exit(0) scoped_response, _ = actions.tools.list_scoped_tools( identifier=identifier, filter={"connection_names": ["salesforce"]}, ) llm_tools = [ { "name": MessageToDict(tool.tool).get("definition", {}).get("name"), "description": MessageToDict(tool.tool).get("definition", {}).get("description", ""), "input_schema": MessageToDict(tool.tool).get("definition", {}).get("input_schema", {}), } for tool in scoped_response.tools ] print(f"Connected account for {identifier} is active.") print(f"Discovered {len(llm_tools)} tools") messages = [{"role": "user", "content": "Find all open opportunities closing this quarter with value over $50,000"}] while True: response = client.messages.create( model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-6"), max_tokens=1024, tools=llm_tools, messages=messages, ) if response.stop_reason == "end_turn": print(response.content[0].text) break tool_results = [] for block in response.content: if block.type == "tool_use": result = actions.execute_tool( tool_name=block.name, identifier=identifier, tool_input=block.input, ) tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": str(result.data), } ) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})

Run it

Environment variables:

SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.cloud SCALEKIT_CLIENT_ID=skc_... SCALEKIT_CLIENT_SECRET=sks_... USER_IDENTIFIER=user_123 ANTHROPIC_API_KEY=sk-ant-...

Python:

python agent.py

Expected output:

Connected account for user_123 is active. Discovered N tools [Tool calls happen here — agent executes SOQL queries against your Salesforce org] Found 5 open opportunities closing this quarter with value over $50,000: Acme Corp ($120,000, closes Sep 28), ...

Exact tool count and output will vary. Run against your Salesforce connection to see your data.

What Your Agent Can Do

These three workflows show the range of tasks the agent handles. Each starts with a natural language prompt and ends with a concrete CRM action.

Natural language to SOQL

"Find all open opportunities closing this quarter with value over $50,000"

The agent translates the request into a SOQL query and executes it against your Salesforce org:

SELECT Id, Name, Amount, CloseDate, StageName FROM Opportunity WHERE IsClosed = false AND CloseDate = THIS_QUARTER AND Amount > 50000

You get back the matching records with name, amount, and close date. The agent handles the object name (Opportunity), the date literal (THIS_QUARTER), and the field paths. You just describe what you want.

Record update

"Move the Acme Corp opportunity to the Negotiation stage"

The agent locates the record by name and calls the record update tool with the new stage value. You get back: "Opportunity 'Acme Corp — Enterprise Plan' updated to Negotiation stage."

Activity logging

"Create a follow-up task on the Acme Corp account for next Monday — subject: send contract draft"

The agent creates a task linked to the account with the due date resolved to next Monday. You get back: "Task created: 'Send contract draft', due [next Monday's date], linked to Acme Corp."

The SOQL demo is also the default prompt in the agent script above. Change it to any of these or write your own.

Available Salesforce Tools

Category
Capability
Notes
Records
Retrieve accounts, contacts, leads, opportunities, and cases by ID or search
Covers the core CRM objects
SOQL Queries
Execute arbitrary SOQL for custom data retrieval
Agent writes SOQL from natural language
Activities
Create tasks and events linked to any CRM record
Cross-object Search
Find records by name, email, phone, or any field value
Metadata
Inspect and modify Salesforce org metadata via SOAP proxy

Full list in the Salesforce connector docs.

Why This Already Handles Multiple Users

The USER_IDENTIFIER env var (the identifier variable in the code) is how this scales from one user to many. Change it per user and each person gets their own Salesforce connection with their own permissions. Scalekit stores and refreshes OAuth tokens separately for each identifier.

In production, resolve the identifier from your authenticated session: after your app verifies who is making the request via session cookie, JWT, or database lookup. Never accept it from client input. The same agent code serves every user without modification.

This pattern works whether you are building an internal sales tool used by multiple team members, or a product where your customers connect their own Salesforce orgs. For a full walkthrough of the multi-user setup, see how access control works for multi-tenant AI agents.

Explore More

Other connectors: HubSpot, Gmail, Slack, Linear, GitHub, Notion, and more: full connector list

Other frameworks: LangChain, Mastra, CrewAI, Vercel AI SDK, Google ADK: framework examples

No items found.
Agent Auth Quickstart
On this page
Share this article
Agent Auth Quickstart

Acquire enterprise customers with zero upfront cost

Every feature unlocked. No hidden fees.
Start Free
$0
/ month
1 million Monthly Active Users
100 Monthly Active Organizations
1 SSO connection
1 SCIM connection
10K Connected Accounts
Unlimited Dev & Prod environments