simple-salesforce-mcp
This Salesforce MCP server lets you query and search records, inspect metadata/org context, and create/update/delete records with confirmation for destructive writes.
Run raw read-only SOQL queries, including pagination via
next_url.Full-text search across records, optionally restricted by object types and returned fields.
Fetch a single record by Id, with optional field selection.
Create records by supplying object type and field values.
Update records only with
confirm: trueafter user approval.Delete records only with
confirm: trueafter user approval.Describe objects to see fields, types, required flags, and picklist values, with basic or full detail.
List org objects with optional filtering and system-object inclusion.
Get current user, org, instance URL, and API version context.
Provides tools for interacting with the Salesforce REST API, including running SOQL queries, searching records, getting/creating/updating/deleting records, describing objects, listing objects, and fetching org information. Update and delete operations require explicit confirmation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@simple-salesforce-mcp Run a SOQL query to get the 10 most recent accounts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
simple-salesforce-mcp
A lightweight Salesforce MCP server. Talks straight to the
Salesforce REST API over stdio with two runtime dependencies (mcp, httpx) — built as a
fast-cold-start replacement for the official @salesforce/mcp DX server, whose dependency
tree is too heavy for sandboxed environments.
Tools
Tool | Writes? | Notes |
| no | Raw SOQL escape hatch with pagination ( |
| no | Cross-object full-text search (parameterized search API) |
| no | Fetch one record by Id, optional field list |
| yes | Create a record |
| yes, confirm-gated | Requires literal |
| yes, confirm-gated | Requires literal |
| no | Trimmed object metadata (fields, types, required, picklists) |
| no | Org objects, system noise filtered by default |
| no | Current user, org, instance URL, API version |
| no | Files, legacy Attachments, and (for Cases) email attachments on a record |
| no | Streams a file to disk and returns a link or path, never the bytes |
| yes | Hosted sandboxes only: reserves a single-use upload slot and returns a link to PUT the file to |
| yes, | Attaches a file to a record as a Salesforce File; the bytes never pass through the call |
Every tool carries MCP annotations (readOnlyHint / destructiveHint / idempotentHint).
update_record and delete_record refuse to act unless the call includes confirm: true,
and the refusal message instructs the model to present the exact change to the user and get
approval first. upload_file is gated the same way when given a local_path, since that
reads a model-chosen path on the user's machine. SOQL itself cannot modify data, so the
escape hatch stays read-only.
Related MCP server: Salesforce MCP Server
Authentication
The server performs no OAuth flow itself; it consumes an existing access token, resolved on every tool call (so an external process may rotate the token at any time):
Salesforce CLI auth store under
$HOME:~/.sf/config.json→{"target-org": "<username>"}(or legacy~/.sfdx/sfdx-config.json→{"defaultusername": ...})~/.sfdx/<username>.json→{"accessToken": "...", "instanceUrl": "https://..."}
This is the shape written by
sf org login, and the shape the JustParent platform materialises into sandboxes (with the platform refreshing the access token before each call). Refresh tokens and client secrets in these files are ignored — never read into memory, stored, or logged.Environment variables (fallback, for local/standalone use):
SALESFORCE_ACCESS_TOKENandSALESFORCE_INSTANCE_URL.
On a rejected token (HTTP 401 / INVALID_SESSION_ID) the server re-reads the auth store
once and retries, then reports that the connection needs to be re-established.
The REST API version defaults to 62.0; override with SALESFORCE_API_VERSION.
Downloading files
download_file never puts file content in a tool result, so any file size works and tool-call
logs only ever hold a small JSON envelope (file_name, content_type, size, sha256, plus
where to get the file). It accepts ContentVersion (068…), ContentDocument (069…, the latest
version is used) and Attachment (00P…) Ids, and streams the blob to disk in chunks.
Hosted sandbox (Harriet). When
HARRIET_FILE_OUTBOX_DIRis set, the file is streamed into a hidden.staging-*directory with a.meta.jsonalongside, then renamed to<outbox>/<token>/<file name>once complete.tokenis<expires_at>.<random>: the Unix expiry time (now +HARRIET_FILE_OUTBOX_TTL_SECONDS, default 120) and 256 random bits. The result carriesdownload_url=<base>/files/<token>(with<base>read from<outbox>/.base_url) andexpires_at. The sandbox bridge serves that URL once, then deletes the file. It refuses the link once the expiry in the token has passed or the file on disk is older than the TTL, since each check works without the other, and it sweeps expired files in the background. The client fetches the link straight into its workspace, e.g.curl -fsSL -o scan.jpg "<download_url>".Local (standalone). Otherwise the file is saved under
SALESFORCE_DOWNLOAD_DIR(default<tmp>/simple-salesforce-mcp) and the result carrieslocal_path.
Downloads above SALESFORCE_MAX_DOWNLOAD_BYTES (default 500 MB) are refused.
Uploading files
upload_file attaches a file (PDF, Word document, image, ...) to a record such as an Account
or Case. It creates a Salesforce File (a ContentVersion) with FirstPublishLocationId set to
the record, so the file appears under the record's Files with no Apex, Flow or Connected App
involved. As with downloads, file content never travels through a tool call: the file streams
from disk to Salesforce as multipart/form-data, and the result is a small JSON envelope
(content_version_id, file_name, size, linked_to). Files above
SALESFORCE_MAX_UPLOAD_BYTES (default 100 MB) are refused, and a very large file may not
finish inside the platform's per-tool-call timeout.
Hosted sandbox (Harriet). The model's workspace is a different machine from the sandbox, so the file goes through a single-use inbox, the mirror image of the download outbox. Set
HARRIET_FILE_INBOX_DIR, and the flow is:prepare_file_upload(file_name)creates<inbox>/<token>/holding.meta.json({"file_name": ..., "max_bytes": ...}), built under a hidden.staging-*name and renamed into place.tokenis<expires_at>.<random>like the outbox's, valid forHARRIET_FILE_INBOX_TTL_SECONDS(default 600). The result carriesupload_tokenandupload_url=<base>/uploads/<token>(with<base>read from<inbox>/.base_url, which must behttps://). Expired slots are swept whenever this tool runs.The client PUTs the file from its workspace, e.g.
curl -fsS -T report.pdf "<upload_url>".upload_file(record_id, upload_token)streams<inbox>/<token>/<file name>to Salesforce and deletes the slot. A failed upload keeps the slot, so it can be retried until the token expires.
The sandbox bridge must implement the PUT side (it lives outside this repo). Accept exactly one
PUT /uploads/<token>per slot, refusing it if the expiry in the token has passed, if<inbox>/<token>/.meta.jsondoes not exist, or if the slot already holds a file. Stream the body into a hidden file in the slot (refuse and discard anything abovemax_bytesfrom.meta.json), and only once it is complete rename it to<inbox>/<token>/<file_name>, takingfile_namefrom.meta.json.upload_fileonly ever reads that final name, so it never sees a partial upload. WithoutHARRIET_FILE_INBOX_DIRa sandbox reports that uploads are not available yet.Local (standalone). Otherwise the server runs on the user's machine, and
upload_filereadslocal_pathdirectly. Because that is a model-chosen path, the call needsconfirm: trueafter the user has approved the exact file and record.local_pathis refused wheneverHARRIET_FILE_INBOX_DIRorHARRIET_FILE_OUTBOX_DIRis set, since a sandbox's disk holds the platform-written Salesforce credentials.
Running
# Standalone with env vars (get a token via: sf org display --json)
SALESFORCE_ACCESS_TOKEN=... \
SALESFORCE_INSTANCE_URL=https://yourorg.my.salesforce.com \
uvx --from git+https://github.com/JustParent/simple-salesforce-mcp simple-salesforce-mcpClaude Desktop / generic MCP client config:
{
"mcpServers": {
"salesforce": {
"command": "uvx",
"args": ["--from", "git+https://github.com/JustParent/simple-salesforce-mcp", "simple-salesforce-mcp"],
"env": {
"SALESFORCE_ACCESS_TOKEN": "...",
"SALESFORCE_INSTANCE_URL": "https://yourorg.my.salesforce.com"
}
}
}
}Smoke test (no network, prints version, credential status, and tools; exits 0):
uvx --from . simple-salesforce-mcp --testDevelopment
uv run pytest # unit tests, no network needed
uv run ruff check . # lint
uv run ruff format . # format (--check to verify only)
uv run mypy # type check (src and tests)
uv run simple-salesforce-mcp --test
npx @modelcontextprotocol/inspector uv run simple-salesforce-mcp # interactiveCI (.github/workflows/tests.yml) runs on every push and pull request: the test
suite on Python 3.10–3.13, ruff lint and format checks plus mypy, and a packaging
job that installs the package the way sandboxes do (uvx --from .) to catch
packaging breakage.
Security notes
Access-token only: the server never requests, reads, or persists refresh tokens.
Destructive operations (update/delete) are gated behind an explicit
confirm=trueargument with model-facing guidance to obtain user approval first.SOQL pagination cursors (
next_url) are validated to be/services/data/...paths, and object/record identifiers are validated before being placed in URLs.Responses are size-capped with explicit truncation notices.
File downloads never travel through a tool result; hosted download links are single-use, short-lived, and unguessable, and file names are reduced to a safe single path component.
File uploads never carry bytes in a tool call either. In a sandbox only a file the bridge placed in a server-created, single-use slot is read (tokens must match a strict format and symlinks are refused), and a model-chosen
local_pathis refused there and confirm-gated everywhere else.
Available Tools
9 toolscreate_recordA
Create a new Salesforce record. Use describe_object first to find the required fields and valid picklist values.
Inputs: data (required), object_type (required).
delete_recordADestructiveIdempotent
Permanently delete a Salesforce record. Requires confirm=true: first present the deletion to the user and get their approval.
Inputs: confirm (required), record_id (required), object_type (required).
describe_objectARead-onlyIdempotent
Get a Salesforce object's fields: API names, types, required flags, and picklist values. Use before writing SOQL or creating/updating records.
Inputs: object_type (required), detail.
get_org_infoARead-onlyIdempotent
Current Salesforce user, org id/name/type, instance URL, and API version in use. Call this first to establish context.
Inputs: no named parameters.
get_recordARead-onlyIdempotent
Fetch a single Salesforce record by Id.
Inputs: record_id (required), object_type (required), fields.
list_objectsARead-onlyIdempotent
List the org's Salesforce objects (API name and label). System noise (Share/History/Feed/ChangeEvent tables, non-queryable objects) is excluded by default.
Inputs: filter, include_system.
run_soql_queryARead-onlyIdempotent
Run a raw SOQL query against the Salesforce org. Read-only — SOQL cannot modify data. Use this as the escape hatch for anything the other tools don't cover: filtering, joins via relationship fields, aggregates, ORDER BY. Always include a LIMIT for exploratory queries. If a field or object name errors, check the exact API names with describe_object.
Inputs: query, next_url.
search_recordsARead-onlyIdempotent
Full-text search across Salesforce records (name, email, phone, and other searchable fields). Good for 'find the record for X' when you don't know the object or Id. For precise filtering use run_soql_query instead.
Inputs: search_term (required), limit, fields, object_types.
update_recordADestructiveIdempotent
Update fields on an existing Salesforce record. Requires confirm=true: first present the exact change to the user and get their approval.
Inputs: data (required), confirm (required), record_id (required), object_type (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool targets a distinct operation: SOQL query, full-text search, record CRUD by Id, object metadata, and org context. The descriptions explicitly contrast run_soql_query with search_records, and get_record with run_soql_query, leaving no ambiguity.
All tool names follow a clear verb_noun pattern in snake_case: run_soql_query, search_records, get_record, create_record, update_record, delete_record, describe_object, list_objects, get_org_info. The naming is perfectly uniform and predictable.
Nine tools is a well-scoped size for a Salesforce MCP server, covering query, search, CRUD, schema introspection, and org context. Every tool has a clear purpose with no redundant or filler tools.
The set provides complete CRUD lifecycle coverage (create, get, update, delete), plus two read/search mechanisms, object metadata discovery, and org context. It covers the full range of common Salesforce interactions an agent would need, with no glaring dead ends.
Maintenance
Related MCP Connectors
One OAuth connector for your team's remote MCP servers, with Tool Packs, guardrails and logs.
mtdt.io MCP: Salesforce DevOps platform connector (metadata deployments, backups and more)
Unified MCP Server is a remote MCP connector for AI agents and vertical AI products that provides access to 22,000+ authorized SaaS tools across 400+ integrations and 24 categories directly inside LLMs (Claude, GPT, Gemini, Cohere). Tools operate only on explicitly authorized customer connections, enabling agents to safely read and write against live third-party systems.
Hosted MCP server for AI-driven data ops. Create apps, manage schemas, and CRUD structured data.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceA focused MCP server for Salesforce CRM that exposes read/write access to standard CRM objects via six tools, designed for multi-tenant gateway deployments.39 npmMIT
- FlicenseBqualityDmaintenanceA customizable MCP server for integrating Salesforce APIs with GenAI applications, supporting SOQL queries, record CRUD, metadata access, and more.138-
- AlicenseAqualityCmaintenanceEnables SOQL queries, SOSL search, and record CRUD operations on a single Salesforce org via MCP, with optional read-only mode.9209 npmMIT
- AlicenseAqualityDmaintenanceEnables AI agents to interact with Salesforce through MCP, supporting queries, records, metadata, and bulk operations with flexible OAuth authentication.16MIT