salesforce-mcp
Provides read-only access to Salesforce orgs: run SOQL SELECT queries, describe sObjects (fields, labels, types, relationships), and list all sObjects.
Click on "Install 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., "@salesforce-mcpshow me all accounts in California"
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.
Salesforce MCP Server
An MCP server for your Salesforce org: run SOQL queries, describe sObjects, list all objects, and create reports. Authentication uses the OAuth 2.0 Client Credentials flow.
Data access is read-only -- SOQL is restricted to SELECT (INSERT/UPDATE/DELETE/UPSERT/EXECUTE are rejected). The only write operations are the report and dashboard create/update/delete tools, which are gated to run only when explicitly requested (see Tools).
Supports two transport modes:
stdio -- for local use with Claude Desktop
Streamable HTTP -- for remote deployment (Docker, Azure Container Apps, managed Claude agents)
Requirements
Python 3.10+
A Salesforce Connected App configured for the Client Credentials flow
Related MCP server: MCP Force
Connected App Setup (Salesforce)
In Salesforce: Setup > App Manager > New Connected App.
Enable OAuth Settings.
Under Selected OAuth Scopes, add Access and manage your data (api).
Enable Client Credentials Flow.
Save and note your Consumer Key and Consumer Secret.
Under Manage > Edit Policies, set the Client Credentials run-as user.
Environment Variables
Variable | Required | Description |
| Yes | Connected App Consumer Key |
| Yes | Connected App Consumer Secret |
| No |
|
| No | Server bind address (default: |
| No | Server port (default: |
| No | API key for Bearer token auth on the |
| No | Full-access API key (read + create/update/delete reports and dashboards). Setting it demotes |
| No | Comma-separated allowed Host headers, or |
| No | Transport mode when no CLI arg is given ( |
Running Locally (stdio)
pip install .
python -m salesforce_mcpThis starts the server in stdio mode, suitable for Claude Desktop.
Claude Desktop Configuration
{
"mcpServers": {
"salesforce": {
"command": "python",
"args": ["-m", "salesforce_mcp"],
"env": {
"SALESFORCE_CLIENT_ID": "<your_consumer_key>",
"SALESFORCE_CLIENT_SECRET": "<your_consumer_secret>",
"SALESFORCE_LOGIN_HOST": "<your_login_host>"
}
}
}
}Running Locally (Streamable HTTP)
python -m salesforce_mcp streamable-httpThe server starts on http://0.0.0.0:8765/mcp with a health check at /health.
Running with Docker
docker compose up --buildPass credentials via a .env file in the project root. The Dockerfile runs the server in Streamable HTTP mode by default.
Deploying to Azure Container Apps
Build and push the Docker image to your Azure Container Registry:
az acr login --name <your_acr> docker build --platform linux/amd64 -t <your_acr>.azurecr.io/salesforce-mcp:latest . docker push <your_acr>.azurecr.io/salesforce-mcp:latestCreate the container app:
az containerapp create \ --name salesforce-mcp \ --resource-group <your_rg> \ --environment <your_cae> \ --image <your_acr>.azurecr.io/salesforce-mcp:latest \ --registry-server <your_acr>.azurecr.io \ --target-port 8765 \ --ingress external \ --min-replicas 0 \ --max-replicas 3 \ --cpu 0.25 \ --memory 0.5Gi \ --env-vars \ SALESFORCE_CLIENT_ID=secretref:salesforce-client-id \ SALESFORCE_CLIENT_SECRET=secretref:salesforce-client-secret \ SALESFORCE_LOGIN_HOST=<your_login_host> \ MCP_HOST=0.0.0.0 \ MCP_PORT=8765 \ MCP_API_KEY=secretref:mcp-api-key \ MCP_ALLOWED_HOSTS="*" \ --secrets \ salesforce-client-id="<your_client_id>" \ salesforce-client-secret="<your_client_secret>" \ mcp-api-key="<your_api_key>"Connect your MCP client to the deployed endpoint:
{ "mcpServers": { "salesforce": { "type": "streamable-http", "url": "https://<your_fqdn>/mcp", "headers": { "Authorization": "Bearer <your_api_key>" } } } }
API Key Authentication
When MCP_API_KEY is set, all requests to /mcp must include an Authorization: Bearer <key> header. Requests without a valid key receive a 401 Unauthorized response.
The /health endpoint is always unauthenticated so that platform health probes (Azure, Docker, etc.) work without credentials.
When MCP_API_KEY is not set, all requests pass through without auth -- suitable for local development.
Tools
Tool | Description |
run_soql | Execute a SOQL SELECT query. INSERT/UPDATE/DELETE/UPSERT/EXECUTE are rejected. |
describe_sobject | Describe one sObject: fields, labels, types, relationships. |
list_objects | List all sObjects in the org: name, label, custom flag. |
list_report_types | List report types available in the org (API name + label, grouped by category). |
describe_report_type | Describe one report type: valid column names for detailColumns/groupings/filters, picklist filter values, and filter operators per data type. |
create_report | Create a new report via the Analytics REST API ( |
get_report | Fetch a report's saveable metadata ( |
run_report | Run a report and return its data ( |
report_to_soql | Convert a report definition to SOQL: detail query + (when possible) a GROUP BY aggregate query mirroring the report's groupings/aggregates, with a column mapping, unmapped report-only constructs (bucket fields, custom formulas), and caveats (frozen relative date ranges, unconverted cross filters, "my records" scope). Both queries are test-run with |
update_report | Update an existing report ( |
delete_report | Permanently delete a report ( |
create_dashboard | Create a new dashboard via the Analytics REST API ( |
get_dashboard | Fetch a dashboard's full definition ( |
update_dashboard | Update an existing dashboard ( |
delete_dashboard | Permanently delete a dashboard ( |
Access tiers
Two API keys map to two access tiers on the same deployment:
MCP_API_KEY(read-only): all read tools work (run_soql,run_report,report_to_soql,get_report,get_dashboard, describes, lists). The six write tools (create/update/delete_report,create/update/delete_dashboard) return a permission-denied error.MCP_ADMIN_API_KEY(full): everything, including writes.
If only MCP_API_KEY is configured, it grants full access (backward compatible with existing single-key deployments). Distribute the read-only key broadly; hand out the admin key only to people who should create or change report/dashboard assets in the org.
Default folders
When folderId is omitted, reports go to a public Claude Reports folder and dashboards to a public Claude Dashboards folder (looked up by DeveloperName at create time). Salesforce folders are typed (report vs. dashboard), so there are two. Create them once per org:
sf data create record --sobject Folder --values "Name='Claude Reports' DeveloperName='Claude_Reports' AccessType='Public' Type='Report'"
sf data create record --sobject Folder --values "Name='Claude Dashboards' DeveloperName='Claude_Dashboards' AccessType='Public' Type='Dashboard'"If the folder is missing, the tool returns an error that includes the command above. An explicit folderId in the metadata always takes precedence.
create_report
Creates a new report asset in the org. Pass report_metadata as the object placed under the request's reportMetadata key (name, reportType, and reportFormat are required; detailColumns is typical; folderId defaults to the Claude Reports folder):
{
"name": "Clay Audience Report",
"reportType": { "type": "AccountList" },
"reportFormat": "TABULAR",
"detailColumns": ["ACCOUNT.NAME", "URL", "EMPLOYEES"]
}To make the call one-shottable, the server validates the payload against the report type's describe before posting: unknown report types, column names (in detailColumns, groupings, or filters), and picklist filter values are rejected with the valid options (and close-match suggestions) in the error message. Salesforce API error bodies are also passed through verbatim so the caller can self-correct. The intended workflow is list_report_types -> describe_report_type -> create_report.
Report column names are report-type-specific (not SOQL field names). Multi-value picklist filters take one filter with comma-separated values ("value": "Net New,Cross-sell"). Groupings (groupingsDown/groupingsAcross, SUMMARY/MATRIX only) take {"name", "sortOrder", "dateGranularity"}; aggregates use prefixes (s!AMOUNT sum, a! avg, m! min, x! max, plus RowCount).
Scope and date range: Salesforce defaults new reports to the author's own records ("scope": "user") and a created-this-week date window -- both of which make the report (and any dashboard built on it) render empty for other users, since dashboards run as a fixed user. Unless the caller passes them, the server defaults scope to organization (when the report type supports it) and widens standardDateFilter to All Time (durationValue: "CUSTOM" with null dates). describe_report_type returns the valid scope values per report type.
This requires the Connected App's run-as user to have permission to create reports in the target folder.
create_dashboard / update_dashboard
Creates (or updates) a dashboard asset in the org. Pass dashboard_metadata as the full dashboard representation sent as the request body (no wrapper key -- unlike reports). name is required on create; folderId defaults to the Claude Dashboards folder; components reference existing report Ids, so create the reports first:
{
"name": "Sales Overview",
"components": [
{
"reportId": "00OXXXXXXXXXXXX",
"header": "Opportunities by Stage",
"properties": {
"visualizationType": "Column",
"aggregates": [{ "name": "s!AMOUNT" }],
"groupings": [{ "name": "STAGE_NAME", "sortOrder": "Asc" }]
}
}
],
"layout": {
"gridLayout": true,
"numColumns": 12,
"rowHeight": 36,
"components": [
{ "column": 0, "row": 0, "colspan": 6, "rowspan": 12 }
]
}
}This shape was verified empirically against v62.0 -- it is what GET /analytics/dashboards/<id>/describe returns, and it is NOT what some docs suggest. Salesforce's parser silently drops unknown fields, so a payload with a top-level gridLayout, a componentData object, or colIndex/rowSpan-style layout keys "succeeds" but creates an empty-shell dashboard with no components. To make the call one-shottable the server rejects unknown keys up front, validates every component against its report's describe (chart groupings/aggregates must exist on the report; FlexTable tableColumns must be report detail columns and can't mix with groupings/aggregates), fills sensible defaults (groupings/aggregates from the report, tableColumns from detailColumns, an automatic two-across grid when layout is omitted), and warns if Salesforce persists fewer components than were sent.
Chart components (Bar, Column, Line, Donut, Pie, Funnel, Scatter) plot a report's groupings, so they need a SUMMARY/MATRIX report. FlexTable (Lightning table) shows detail columns; Table is the classic auto-column table; Gauge/Metric show a single aggregate. layout.components has one {column, row, colspan, rowspan} entry per component, matched by index.
This requires the Connected App's run-as user to have permission to create dashboards in the target folder ("Create and Customize Dashboards", plus "View Dashboards in Public Folders" as applicable). Dashboards run as that user, so it also needs field access to the reports' columns.
License
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/NetchexCorp/salesforce-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server