ai newsDo., 22. Okt.
EilmeldungAnthropic previews Claude Duet, an AI agent that works in the background

From Raw SOQL to a Governed Semantic Layer: Four Ways I Connected Claude to Salesforce

Four ways I connected Claude to Salesforce with MCP, from a local server to Apex tools, and why agents should get business answers, not raw SOQL.

HackerNoonDonnerstag 10:19 · 22.10.8 Min. Lesezeit
Bild: HackerNoon

Signup Write New Story From Raw SOQL to a Governed Semantic Layer: Four Ways I Connected Claude to Salesforce by naresh goyal by naresh goyal | @ nareshg GTM systems architect who build AI-native infrastructure. He writes what actually happens when AI agents takes action in enterprise systems

GTM systems architect who build AI-native infrastructure. He writes what actually happens when AI agents takes action in enterprise systems

At Amplitude, I've connected Claude to Salesforce four different ways. Each version fixed something the last one got wrong, and what I learned in the end had little to do with hosting or transport. It was about what the model should be allowed to see.

The sales rep says, “How’s the Acme (removed real customer name) pipeline shaping up for this quarter?”

In order to figure out the answer, the person has to log on to Salesforce, look for the account, go through all the deals, and make an assessment. Or run a salesforce report. It takes ten minutes, and two people will often give two different answers. Now think the total time if there are 100+ reps have the similar type of questions every day.

I wanted Claude to answer that question in chat, using live CRM data, without showing anyone records they couldn't already see in Salesforce. MCP made that possible. Getting it right took a few tries.

The Model Context Protocol is an open protocol for connecting AI clients to tools. Claude queries the server about available tools, examines their descriptions, and then calls on the tools based on its own discretion. Once you create the server, you can use it with Claude Desktop, Claude Code, and claude.ai.

Two choices shape every MCP server: how the client reaches it (as a local process over stdio, or at a URL over HTTP), and whose identity it uses when it calls Salesforce. That second choice ended up mattering more than I expected

I started with a small Python server that Claude Desktop runs as a subprocess. No cloud and no OAuth. Credentials live in a local.env file, and the server calls Salesforce through simple-salesforce.

The first version had four tools and took an afternoon. There was even one mistake in it that I am a bit ashamed of: I created queries using Python f-strings ( WHERE Name LIKE '%{name}%' ), so a quoted name in the account would cause problems with my query. The only thing you can do differently from what I did is using bind variables.

The bigger worry was writes. Claude picks tools on its own, and I didn't want any chance of it changing CRM data. So the current version blocks writes in four places: a validator that only accepts SELECT queries, an allowlist that permits a single POST (running a report), a wrapper around the Salesforce client that raises an error on any create, update or delete, and a tool list with no write tools in it. SOQL can't modify data anyway, so the first check is mostly a backstop. The wrapper and the allowlist do the real work.

It now has seven tools covering queries, reports, search, record lookups and object metadata. My favorite is lookup_records, which maps plain words like "pipeline", "renewal" or "churn" to tuned SOQL, so Claude doesn't have to guess at the schema for common questions.

Setup is one block in claude_desktop_config.json. Use absolute paths, and add it to your existing config rather than replacing the file

{ "mcpServers": { "salesforce": { "command": "/Users/you/salesforce-mcp-setup/venv/bin/python3", "args": ["/Users/you/salesforce-mcp-setup/server.py"] } } } The catch is identity. It runs on one laptop with one login, so everyone using it sees whatever that login can see. That's fine for a personal tool, but not for a team.

This is the same server.py with no changes, registered with Claude Code instead of Claude Desktop. I use it when I'm building or testing tools from the terminal.

claude mcp add salesforce -- /Users/you/salesforce-mcp-setup/venv/bin/python3 /Users/you/salesforce-mcp-setup/server.py Add --scope user to make it available in every project, or --scope project to write a.mcp.json your team can commit. claude mcp list confirms it loaded.

The most useful part for me was the MCP Inspector. After pip install "mcp[cli]" in the venv, mcp dev server.py opens a browser page where you can call each tool by hand and see the raw output. It's the quickest way to check that the write guard actually fires, and to see exactly what Claude will see.

Once a few people wanted to use it, the local setup showed its limits. Everyone needed Python installed, and everyone shared one identity.

So I moved it to Google Cloud Run as a FastAPI and FastMCP app. A user adds one URL under Settings, Connectors in claude.ai, signs in with their own Salesforce account, and that's it. Every call runs as that user, so their sharing rules and field-level security apply.

The server acts as a small OAuth server in front of Salesforce. Claude finds its metadata at /.well-known/oauth-authorization-server, sends the user to sign in to Salesforce, and gets back two tokens. The access token is a JWE (an encrypted JWT) that carries the Salesforce session and expires after two hours, the same as a Salesforce session. The refresh token lasts for five days of inactivity.

The first three versions all gave Claude a raw query tool and let it work things out. That's fine for power users, but it bothered me more over time. Claude would write slightly different SOQL for the same question. It had no idea how we actually score deal risk, since that logic lives in Apex. And it had to interpret raw fields, which is where shaky conclusions came from.

Salesforce now hosts MCP servers itself. You can turn on prebuilt ones in Setup, like sobject-reads for general queries, or expose your own Apex as tools. Salesforce runs the endpoint and handles OAuth and sharing, so there's no infrastructure to manage.

I started treating each tool as a business question with a defined answer, instead of a window into the database. get_pipeline_intelligence (changed name) doesn't let Claude write SOQL. It calls one Apex method that applies our risk rules and returns the result. Each tool is a class with a single @InvocableMethod.. A trimmed version (changed confidential info)

global with sharing class PipelineIntelligenceMCP { @InvocableMethod( label='Get Pipeline Intelligence' description='Returns open opportunities with risk levels, weighted pipeline value and a health summary for one account. Accepts a full or partial account name.' ) global static List<PipelineOutput> run(List<PipelineInput> inputs) { List<PipelineOutput> results = new List<PipelineOutput>(); for (PipelineInput input: inputs) { results.add(analyze(input.accountName)); } return results; } private static PipelineOutput analyze(String name) { PipelineOutput out = new PipelineOutput(); String pattern = '%' + name.trim() + '%'; List<Account> matches = [ SELECT Id, Name FROM Account WHERE Name LIKE:pattern WITH USER_MODE ORDER BY Name LIMIT 5 ]; if (matches.size()!= 1) { out.success = false; out.message = matches.isEmpty()? 'No account matches ' + name: 'Several accounts match. Ask the user which one.'; return out; } Decimal weighted = 0; Integer highRisk = 0, total = 0; for (Opportunity o: [ SELECT Amount, Probability, CloseDate FROM Opportunity WHERE AccountId =:matches.Id AND IsClosed = false WITH USER_MODE LIMIT 50 ]) { Decimal prob = o.Probability == null?

0: o.Probability; weighted += (o.Amount == null? 0: o.Amount) * prob / 100; Integer days = Date.today().daysBetween(o.CloseDate); if (prob < 30 || days < 14) highRisk++; total++; } out.success = true; out.accountName = matches.Name; out.weightedPipelineValue = weighted; out.pipelineHealthSummary = total == 0? 'No open pipeline': (Decimal) highRisk / total > 0.5? 'At Risk': weighted > 500000? 'Strong': 'Moderate'; return out; } // PipelineInput and PipelineOutput carry @InvocableVariable descriptions, // which become the schema Claude reads.} The rule in this class is sharing. All the questions in this class have WITH USER_MODE in order for the representative to see what he is seeing on Salesforce.

In case there are several accounts for one name, the system informs about that, and it will be up to Claude to ask. And Claude receives the risk count, a weighted sum, and the health status instead of probabilities, which reduces the chances of misunderstanding significantly

For each tool, there is a small group of Apex services, which do the following, wrapping all queries in user mode with bind variables, validation of inputs, returning of errors in a certain format, and logging of each call as Platform Events in order to control the usage and failures. All tools are divided into servers by their roles (quoting, RevOps, sales)

From the user's side, connecting works the same as Approach 3: an External Client App for OAuth, the server URL as a custom connector, and one sign-in.

Which one fits depends mostly on who's using it and whether it ever needs to change data.

Engineers testing tools and building skills

If I started over, I'd still build the local version first. It's the fastest way to find out what people actually ask. But I'd plan for per-user sign-in and business-level tools from the beginning.

The part that matters to me now is simple: a rep can ask which deals are at risk this quarter and get the same answer the business would give, within their own permissions, without opening the CRM.

Next I'm looking at precomputed risk scores, so heavier analysis stays within limits, and a small test set that checks each tool's answers before a change ships. If you've built something similar, I'd really like to hear how you approached it.

Quelle: HackerNoon (HackerNoon). Original öffnen