A production-ready Salesforce API wrapper with Model Context Protocol (MCP) server that works seamlessly on both Deno and Cloudflare Workers. Perfect for integrating Salesforce with AI assistants, chatbots, and automation tools.
The MCP server wraps traditional REST API endpoints, providing both MCP tool interfaces for LLM integration and standard HTTP endpoints for direct API access. This dual-interface approach means you can use the same server for AI-driven interactions (via MCP) or traditional programmatic access (via REST APIs).
Key Features:
- π€ MCP Protocol Support - Works with Claude, Groq, and other MCP-compatible LLMs
- π REST API Endpoints - Standard HTTP endpoints available alongside MCP tools
- β‘ Session-Based Auth - Set credentials once, use across multiple calls
- π Dual Platform - Run on Deno locally or deploy to Cloudflare Workers
- π οΈ Complete CRUD - Full Salesforce object management (Leads, Contacts, Accounts, Opportunities)
- π SOQL Support - Execute custom queries for advanced use cases
- π OAuth 2.0 - Built-in OAuth flow or bring your own tokens
# Clone the repository
git clone <your-repo-url>
cd salesforce-mcp-wrapper
# Copy environment template
cp .env.example .env
# Edit .env with your credentials (see Setup Guide below)# Install Deno: https://deno.land/
deno task serve
# In another terminal, run a test
deno task testWhen running locally, LLM providers like Groq need a public URL to access your MCP server. Use Cloudflare Tunnel (or alternatives like ngrok):
# Install and start Cloudflare Tunnel
brew install cloudflared
cloudflared tunnel --url http://localhost:8000
# You'll get a public URL like: https://random-name.trycloudflare.com
# Update .env with: MCP_SERVER_URL=https://random-name.trycloudflare.com/mcpNow your tests will hit the tunneled endpoint and LLMs can access your local server!
# Install dependencies
npm install
# Set up secrets
npm run cf:setup
# Deploy
npm run deployCreate a .env file with the following variables:
# Salesforce Connected App (for OAuth)
SALESFORCE_CLIENT_ID=your_connected_app_client_id
SALESFORCE_CLIENT_SECRET=your_connected_app_client_secret
SALESFORCE_REDIRECT_URI=http://localhost:8000/callback
# Groq API (for LLM integration)
GROQ_API_KEY=your_groq_api_key
# MCP Server URL
MCP_SERVER_URL=http://localhost:8000/mcp # or your deployed URL
# Salesforce Test Credentials (for testing)
SALESFORCE_ACCESS_TOKEN=your_access_token
SALESFORCE_INSTANCE_URL=https://your-instance.my.salesforce.com
SALESFORCE_TOKEN_TYPE=Bearer- Go to workbench.developerforce.com
- Login with your Salesforce credentials
- Open Browser DevTools β Application β Cookies
- Copy the
sidvalue (this is your access token) - Your instance URL is shown in the browser's address bar
# Authenticate with your org
sf org login web
# Display org info including session ID
sf org display --target-org your-org-alias --verbose- Create a Connected App in Salesforce Setup
- Run the server:
deno task serve - Visit
http://localhost:8000/auth/login - Complete the OAuth flow
- Copy the credentials from the success page
For OAuth functionality, you need to create a Connected App in Salesforce:
- In Salesforce Setup, search for "App Manager"
- Click "New Connected App"
- Fill in basic information:
- Connected App Name: Your app name
- API Name: Auto-generated
- Contact Email: Your email
- Enable OAuth Settings:
- β Enable OAuth Settings
- Callback URL:
http://localhost:8000/callback(for local dev) - Selected OAuth Scopes: Add
api,refresh_token,offline_access
- Save and copy the Consumer Key (Client ID) and Consumer Secret
- Never commit
.envfiles (already in.gitignore) - Rotate tokens regularly
- Use separate credentials for testing and production
- Keep
.env.examplegeneric with no real credentials
The MCP server exposes Salesforce operations as tools that LLMs can call. Here's how to use it:
The server uses a session-based pattern for efficiency:
- Set Credentials Once: Call
sf_set_credentialsand get asession_id - Use Session ID: Pass the
session_idto subsequent tool calls - No Re-authentication: Credentials persist for the session
// 1. Set credentials and get session_id
const setCredsResult = await mcpClient.callTool("sf_set_credentials", {
access_token: "your_salesforce_token",
instance_url: "https://your-instance.salesforce.com"
});
const sessionId = setCredsResult.meta.session_id; // e.g., "mcp_1759872687750_y133q224f"
// 2. Use session_id for all subsequent calls
const leads = await mcpClient.callTool("sf_search_leads", {
session_id: sessionId,
limit: 10
});For creating leads:
Create a lead in Salesforce with these details:
- Access Token: {YOUR_SALESFORCE_TOKEN}
- Instance URL: {YOUR_SALESFORCE_INSTANCE_URL}
- First Name: {FIRST_NAME}
- Last Name: {LAST_NAME}
- Company: {COMPANY_NAME}
- Email: {EMAIL_ADDRESS}
- Title: {JOB_TITLE}
- Lead Source: {LEAD_SOURCE}
For searching leads:
Search for leads in Salesforce:
- Access Token: {YOUR_SALESFORCE_TOKEN}
- Instance URL: {YOUR_SALESFORCE_INSTANCE_URL}
- Company filter: {COMPANY_NAME}
- Limit: 10
For adding notes:
Add a note to Salesforce record:
- Access Token: {YOUR_SALESFORCE_TOKEN}
- Instance URL: {YOUR_SALESFORCE_INSTANCE_URL}
- Record ID: {RECORD_ID}
- Note Title: {NOTE_TITLE}
- Note Body: {NOTE_CONTENT}
Template 1: Set credentials and search leads
First, call sf_set_credentials with:
- Access Token: YOUR_TOKEN
- Instance URL: YOUR_INSTANCE_URL
Then use the session_id from the response to call sf_search_leads with limit: 10
Template 2: Create a lead
Call sf_create_lead with:
- Session ID: YOUR_SESSION_ID (from previous sf_set_credentials call)
- First Name: Sarah
- Last Name: Johnson
- Company: Innovation Labs Inc
- Email: sarah.johnson@innovationlabs.com
- Title: VP of Engineering
sf_set_credentials- Set Salesforce credentials and get a session_id for subsequent callssf_auth_status- Check current authentication status and get OAuth URL if not authenticatedsf_start_oauth- Start OAuth flow and get authorization URLsf_use_oauth_state- Use credentials from completed OAuth flowsf_getting_started- Get step-by-step guidance for authentication and setupsf_health_check- Check server health and list available toolssf_login- Get login URL for web-based OAuth flowsf_get_session_info- Get current session information
sf_create_lead- Create new leads with name, company, email, phone, etc.sf_search_leads- Search for leads with filters (company, email, status, etc.)sf_update_lead- Update existing lead fields (status, contact info, description, etc.)sf_convert_lead- Convert a lead to Account, Contact, and optionally Opportunity
sf_create_contact- Create new contacts with name, email, phone, account associationsf_search_contacts- Search for contacts with filters (account, email, name, etc.)
sf_create_account- Create new accounts (companies) with name, industry, address, etc.sf_search_accounts- Search for accounts with filters (name, type, industry, etc.)
sf_create_opportunity- Create sales opportunities with name, stage, amount, close datesf_search_opportunities- Search for opportunities with filters (account, stage, amount, etc.)
sf_create_task- Create follow-up tasks with subject, due date, priority, related recordssf_search_tasks- Search for tasks with filters (related records, status, subject, etc.)
sf_get_record- Get detailed information about any Salesforce record (Lead, Account, Contact, Opportunity, etc.)sf_update_record- Update any Salesforce record by providing object type, record ID, and fields
sf_create_note- Add notes to any Salesforce record (Leads, Accounts, Contacts, etc.)sf_search_notes- Search for notes with filters (parent record, title, etc.)sf_get_note- Get details of a specific note by IDsf_update_note- Update an existing note's title, body, or privacy settingsf_delete_note- Delete a note from Salesforce
sf_run_soql_query- Execute custom SOQL queries for complex data retrieval and filtering
Note: Most tools accept an optional
session_idparameter for credential persistence. Set credentials once withsf_set_credentials, then pass the returnedsession_idto subsequent tool calls. Alternatively, you can provideaccess_tokenandinstance_urlwith each call.
Update a Lead's Status:
sf_update_lead({
session_id: "your_session_id",
lead_id: "00Q...",
status: "Working - Contacted",
description: "Had initial call, interested in premium package"
})Add a Note to a Record:
sf_create_note({
session_id: "your_session_id",
parent_id: "00Q...", // Lead, Account, Contact, or Opportunity ID
title: "Follow-up Meeting Notes",
body: "Discussed pricing and implementation timeline. Next steps: send proposal.",
is_private: false
})Get All Notes for a Lead:
sf_search_notes({
session_id: "your_session_id",
parent_id: "00Q...", // Lead ID
limit: 20
})Update a Note:
sf_update_note({
session_id: "your_session_id",
note_id: "002...",
body: "Updated meeting notes with new action items"
})Get Full Record Details:
sf_get_record({
session_id: "your_session_id",
sobject_type: "Lead",
record_id: "00Q..."
})# Quick test (default)
deno task test
# Individual tests
deno task test:credentials # Test credential setting
deno task test:llm # Test LLM integration with Groq
deno task test:mcp # Test direct MCP client
deno task test:session # Test session flow
deno task test:oauth-direct # Test OAuth direct
deno task test:oauth-session # Test OAuth session
deno task test:debug # Test simple debug
# Or run directly:
deno run --allow-net --allow-read --allow-env ./tests/test-set-and-search.tsSuccessful test output should look like:
π§ͺ Testing Set Credentials + Search Leads
========================================
Session ID: mcp_1759872687750_y133q224f
Session Credentials: Present
β
Lead search successful:
Total records: 5
- John Doe, Acme Corp
- Jane Smith, TechCorp
...
β
Credential persistence SUCCESS - got Salesforce data!
| Issue | Solution |
|---|---|
Environment variables missing |
Make sure .env file exists and all variables are set |
Invalid access token |
Token expired - generate a new one from Salesforce |
Cannot connect to MCP server |
Ensure server is running and MCP_SERVER_URL is correct |
HTTP 530 error |
MCP server not reachable - check tunnel is running and MCP_SERVER_URL is correct |
Request timed out |
Tunnel may have expired - restart cloudflared/ngrok and update MCP_SERVER_URL |
| Tests fail with local server | Make sure you're using a tunnel URL in MCP_SERVER_URL, not localhost |
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β LLM βββββββΆβ MCP Server βββββββΆβ Salesforce β
β (Groq/etc) ββββββββ (This App) ββββββββ API β
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β
ββββββββΌβββββββ
β Session β
β Storage β
βββββββββββββββ
- LLM makes tool call β Groq/Claude sends MCP request
- MCP server receives β Routes to appropriate Salesforce function
- Session management β Retrieves credentials from session storage
- Salesforce API call β Makes authenticated REST/SOQL request
- Response formatting β Returns structured data to LLM
- LLM processes β Generates natural language response
| Platform | Status | Use Case |
|---|---|---|
| Deno | β Ready | Local development, self-hosted |
| Cloudflare Workers | β Ready | Production deployment, edge computing |
βββ main.ts # Deno entry point
βββ salesforce-mcp-server.ts # Main MCP server implementation
βββ worker/
β βββ index.ts # Cloudflare Worker entry point
βββ utils/ # Utility modules
β βββ salesforce-utils.ts # Salesforce API client
β βββ auth-utils.ts # Authentication utilities
β βββ mcp-utils.ts # MCP protocol handlers
β βββ route-utils.ts # HTTP/REST API endpoints
β βββ http-utils.ts # HTTP/CORS utilities
β βββ oauth-utils.ts # OAuth flow utilities
β βββ shared-ui.ts # Web UI components
β βββ html-templates.ts # HTML templates
βββ tests/ # Test files
β βββ test-credentials.ts
β βββ test-llm-mcp.ts
β βββ test-mcp-client.ts
β βββ test-session-flow.ts
β βββ ...
βββ .env.example # Environment template
βββ wrangler.jsonc # Cloudflare config
βββ deno.json # Deno config (includes test tasks)
βββ README.md # This file
# Development with hot reload
deno task serve
# Production with PM2 or systemd
deno run --allow-all main.ts# Install dependencies
npm install
# Set secrets
wrangler secret put SALESFORCE_CLIENT_ID
wrangler secret put SALESFORCE_CLIENT_SECRET
wrangler secret put GROQ_API_KEY
# Deploy
npm run deployYour worker will be available at: https://your-worker.your-account.workers.dev
await mcpClient.callTool("sf_run_soql_query", {
session_id: sessionId,
soql: "SELECT Id, Name, Email, (SELECT Subject FROM Tasks) FROM Lead WHERE Company = 'Acme Corp' LIMIT 10"
});The session-based pattern makes it easy to work with multiple Salesforce orgs:
// Org A
const sessionA = await setCredentials(orgAToken, orgAInstance);
const leadsA = await searchLeads(sessionA.session_id);
// Org B
const sessionB = await setCredentials(orgBToken, orgBInstance);
const leadsB = await searchLeads(sessionB.session_id);All tools return consistent error responses:
{
"error": {
"code": "INVALID_SESSION",
"message": "Session not found or expired",
"details": "..."
}
}In addition to MCP tools, the server exposes traditional REST endpoints:
- MCP Endpoint:
POST /mcp- MCP protocol endpoint for LLM tool calls - OAuth Flow:
GET /auth/login,GET /callback- Standard OAuth 2.0 flow - Salesforce Operations:
POST /salesforce/query,POST /leads/create, etc. - Web Interface:
GET /- Interactive UI for testing
All MCP tools have corresponding REST API endpoints for non-MCP integrations.
- AI Assistants: Enable Claude, ChatGPT, or Groq to interact with Salesforce
- Chatbots: Build Slack/Discord/Teams bots that create leads and log activities
- Automation: Trigger Salesforce actions from webhooks or scheduled jobs
- Data Sync: Sync data between Salesforce and other systems
- Analytics: Query Salesforce data for custom reporting
- Voice Apps: Build Alexa/Google Home skills that query Salesforce
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes (test on both Deno and Cloudflare)
- Submit a pull request
MIT - See LICENSE file for details