Skip to main content

sfdc-mcp-server

CI License Python 3.11+

read-only Salesforce tools driven over MCP

A production-grade, read-only MCP server for Salesforce: SOQL queries, record reads, full-text search, and schema discovery through the Salesforce REST API, from Claude Desktop, Claude Code, or any MCP client — with auth done properly (OAuth 2.0 device-code and client-credentials flows) and API-limit awareness (Salesforce's daily request-limit usage is surfaced, and throttling is retried honoring Retry-After), which the existing servers in this space skip.

Read-only by construction, not by convention. The HTTP client this server is built on exposes only GET and POST — there is no write call to the Salesforce data API — the only POST anywhere is the OAuth token exchange, which creates no records — so no tool here could write to Salesforce even by accident. SOQL queries are additionally guarded: soql_query rejects anything whose first keyword isn't SELECT before a request is ever sent.

sfdc-mcp-server is not affiliated with, endorsed by, or sponsored by Salesforce, Inc.

Quickstart

Requires a Salesforce Connected App or External Client App (see below) and uv. Don't have a Salesforce org to test against? Sign up for a free Developer Edition org — no credit card required.

uvx sfdc-mcp-server

That starts the server over stdio. Until sfdc-mcp-server is published to PyPI, run it from a local checkout instead:

uv run --directory <path-to-checkout> sfdc-mcp-server

In practice you'll point an MCP client at it instead of running it directly — for Claude Code:

Note: until sfdc-mcp-server is published to PyPI, replace uvx sfdc-mcp-server in the command below with the from-source form uvx --from git+https://github.com/inogen-ai/sfdc-mcp-server sfdc-mcp-server (see the run-from-checkout note above).

claude mcp add salesforce -e SFDC_MCP_CLIENT_ID=<your-consumer-key> -- uvx sfdc-mcp-server

For Claude Desktop, add to claude_desktop_config.json:

{
  "mcpServers": {
    "salesforce": {
      "command": "uvx",
      "args": ["sfdc-mcp-server"],
      "env": {
        "SFDC_MCP_CLIENT_ID": "<your-consumer-key>"
      }
    }
  }
}

The first tool call you make (e.g. soql_query) won't have a cached token yet — it returns device-code sign-in instructions (a URL and a short code) as the tool result instead of data. Open the URL, enter the code, sign in. The server finishes the login in the background as soon as you do, so the next tool call succeeds without any extra steps on your end.

Connected App setup

Salesforce's Spring '26 release disabled creating new classic Connected Apps in most orgs — use an External Client App instead (Salesforce's own successor to Connected Apps; existing Connected Apps still work, but a fresh Developer Edition org signed up today can't create a new one through the UI). The steps below use External Client Apps; if your org still has classic Connected App creation enabled, the same settings exist under Setup → App Manager → New Connected App instead.

Both auth modes start the same way: Setup → Quick Find → External Client App Manager → New External Client App → give it a name, API name, and contact email, then in the app's OAuth Settings check Enable OAuth.

Device flow (interactive — for a human signing in)

  1. Under OAuth Settings, check Enable for Device Flow. If a Callback URL field is required to save the form, any placeholder works (https://login.salesforce.com/services/oauth2/success is a common one) — it's never used by the device flow, which has no browser redirect.
  2. Under OAuth Scopes, add api (data access) and refresh_token (its alias offline_access works too) — move both to Selected OAuth Scopes. Without refresh_token, the server can't renew a session silently and you'll be asked to sign in again every couple of hours.
  3. Important: leave Require Secret for Refresh Token Flow unchecked. Device flow is a public client — it has no secret to send — and checking this box makes Salesforce reject the server's silent refresh-token renewal with invalid_client, forcing a fresh interactive login every time the access token expires.
  4. Save. On the app's detail page, reveal and copy the Consumer Key — this is SFDC_MCP_CLIENT_ID. Device flow is a public client, so there's no secret to copy and SFDC_MCP_CLIENT_SECRET stays unset.
  5. Set SFDC_MCP_AUTH=device_code (the default — this variable can be omitted) and SFDC_MCP_CLIENT_ID=<the consumer key from step 4>.

Client credentials (unattended — for service/automation use)

Read the warning under Auth modes before using this mode — it sees everything the run-as user is granted, with no per-caller access control.

  1. Under OAuth Settings, check Enable Client Credentials Flow and accept the security-risk warning. Under OAuth Scopes, add api and move it to Selected OAuth Scopes.
  2. Save, then from the app's Manage page → Edit Policies → under Client Credentials Flow, set Run As to an integration user with exactly the object- and field-level permissions you want this server to have — see the warning above.
  3. On the app's detail page, reveal and copy the Consumer Key and Consumer Secret — these are SFDC_MCP_CLIENT_ID and SFDC_MCP_CLIENT_SECRET.
  4. Client credentials flow requires a My Domain login URL — login.salesforce.com and test.salesforce.com aren't accepted for this flow. Find yours under Setup → My Domain (it looks like https://your-domain.my.salesforce.com); set SFDC_MCP_LOGIN_URL to it.
  5. Set SFDC_MCP_AUTH=client_credentials, SFDC_MCP_CLIENT_ID, SFDC_MCP_CLIENT_SECRET, and SFDC_MCP_LOGIN_URL from the steps above.

Tools

Tool Parameters Returns
soql_query query: str, limit: int = 25 Up to limit records from a read-only SOQL SELECT. Anything else (UPDATE, DELETE, leading comments hiding a non-SELECT statement, etc.) is rejected before any request is sent.
get_record sobject: str, record_id: str, fields: str = "" One record by Id, optionally limited to a comma-separated fields list.
search term: str, sobjects: str = "", limit: int = 25 Cross-object full-text search (name/email/phone fields), optionally scoped to a comma-separated sobjects list.
describe_sobject sobject: str Every field's name, type, and label, with up to 10 picklist values shown per picklist field.
list_sobjects (none) Every queryable object (standard and custom) the signed-in identity can see, as name — label pairs.

One line of SOQL to get you started: SELECT Id, Name FROM Account WHERE CreatedDate = LAST_N_DAYS:7 LIMIT 10. list_sobjects and describe_sobject are good starting points for exploring an org you haven't queried before.

Auth modes

device_code (interactive) client_credentials (unattended)
Who signs in A human, via browser device-code login Nobody — the Connected/External Client App itself
Effective access Whatever the signed-in user can see — object/field-level security and sharing rules respected by construction Whatever the run-as user configured on the app can see, with no per-caller access control — the server reads what that one identity reads, regardless of who's asking through the MCP client
Required env vars SFDC_MCP_CLIENT_ID SFDC_MCP_CLIENT_ID, SFDC_MCP_CLIENT_SECRET, SFDC_MCP_LOGIN_URL (a My Domain URL)
Use for Interactive use — Claude Desktop, Claude Code, a human at a terminal Service/automation scenarios only, where that run-as user's fixed, broad view of the org is an accepted tradeoff

client_credentials bypasses per-caller access control entirely — every tool call sees exactly what the run-as user configured on the app can see, whoever is actually asking through the MCP client. Scope that user's permission set as narrowly as the integration allows, and use this mode only for service scenarios, never as a convenience shortcut for interactive use.

Environment variables

All settings are prefixed SFDC_MCP_ and can be set in the environment or a .env file (see .env.example).

Variable Default Purpose
SFDC_MCP_AUTH device_code Auth mode: device_code or client_credentials.
SFDC_MCP_LOGIN_URL https://login.salesforce.com OAuth login endpoint. Sandboxes use https://test.salesforce.com; client_credentials requires a My Domain URL instead of either.
SFDC_MCP_CLIENT_ID (unset, required) Connected/External Client App's consumer key.
SFDC_MCP_CLIENT_SECRET (unset) Consumer secret; required for client_credentials only.
SFDC_MCP_API_VERSION 62.0 Salesforce REST API version.
SFDC_MCP_ITEM_LIMIT 25 Default max records/results returned per call before "showing N of M" is reported.
SFDC_MCP_TIMEOUT_SECONDS 30.0 HTTP timeout (seconds) per Salesforce request.
SFDC_MCP_TOKEN_CACHE_PATH ~/.sfdc-mcp/token_cache.json Where the device-code token cache is persisted (mode 600).

The Salesforce instance URL is never a setting here — it comes back from the OAuth token response (instance_url) once signed in.

API limits

Salesforce enforces a per-org daily API request allocation. This server tracks the Sforce-Limit-Info header off every response and appends a usage note to a tool's result once daily usage crosses 90% (e.g. Salesforce API usage: 14500/15000 daily calls.), so a client sees the ceiling coming rather than hitting it mid-session. A 429/503 is retried honoring Retry-After (clamped to at most 60s, up to 3 retries, exponential 1→2→4s backoff otherwise); a 403 with REQUEST_LIMIT_EXCEEDED (the daily allocation already exhausted) returns an actionable sentence about the 24-hour reset window, not a stack trace.

Security notes

  • Read-only by construction. The HTTP client exposes GET and POST, but there's no write call to the Salesforce data API for a tool to call even by mistake — the only POST anywhere is the OAuth token exchange (/services/oauth2/token), which creates no records. soql_query additionally rejects any query whose first keyword isn't SELECT.
  • Token cache on disk. The device-code token cache (it holds a refresh token, not just an access token) is written to ~/.sfdc-mcp/token_cache.json at mode 600, created with that mode rather than chmod'd after the fact.
  • API-limit awareness. See API limits — usage is surfaced before the daily ceiling is hit, never as a stack trace.
  • Not affiliated with, endorsed by, or sponsored by Salesforce, Inc.

Development

Requires Python 3.11+ and uv.

uv sync
uv run pytest -q
uv run ruff check .

No live Salesforce org is needed for the test suite — the REST API is faked at the httpx.MockTransport boundary for unit tests, and a real stdio round-trip runs against an in-process fake Salesforce API in tests/integration/. See docs/manual-verification.md for the live-org check a maintainer runs before releases.

Contributing

Contributions welcome — see CONTRIBUTING.md for dev setup and PR expectations, and SECURITY.md for reporting vulnerabilities privately.


Not affiliated with, endorsed by, or sponsored by Salesforce, Inc.

Part of InoGen's open-source portfolio: kilnworks (self-hostable RAG assistant) and the read-only MCP connectors m365, servicenow, salesforce, and hubspot.

Built and maintained by InoGen.

Metadata

Release files for sfdc-mcp-server 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sfdc-mcp-server 0.1.0
File Size Uploaded Provenance
sfdc_mcp_server-0.1.0.tar.gz 1.4 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sfdc-mcp-server 0.1.0
File Interpreter ABI Platform
sfdc_mcp_server-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / sfdc_mcp_server-0.1.0.tar.gz

Download URL sfdc_mcp_server-0.1.0.tar.gz
Size 1.4 MB
Tags Source
SHA-256 checksum
How to use checksums
c8191cd45d9af9f8a367e5a7c8999ada185b98cc38435ca140328a4e6b44a766
BLAKE2b-256 checksum
How to use checksums
5c3879ed93ba1585b7ef6b546026d5dc3a1687fb247c9d1c9a9ebc7eb4148f92
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / sfdc_mcp_server-0.1.0-py3-none-any.whl

Download URL sfdc_mcp_server-0.1.0-py3-none-any.whl
Size 32.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fa369dad9cb36afcaa06657459552d067e5e4957c206a178552d32aa7af6929c
BLAKE2b-256 checksum
How to use checksums
717dea2268e09f6ff8f74263d2cda9df41d10a6fd0beea23e0eec270dd2eb6fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for sfdc-mcp-server 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page