Salesforce's hosted MCP servers give Claude read access to your org. But most real business workflows aren't just queries — they create records, run logic, and coordinate across multiple objects. That's where custom MCP servers come in. You annotate an Apex class the same way you would for a Flow invocable action, register it in API Catalog, and Claude can call it directly. No middleware, no custom API layer.
What We Are Building
The demo scenario is new customer onboarding. A sales rep tells Claude: "Set up a new customer — Acme Corp, Technology industry. Primary contact is Sarah Jones, sarah@acme.com. Opportunity is Enterprise Deal, $50,000, closing end of quarter."
Claude calls the custom MCP tool. The Apex method creates the Account, links a Contact to it, and creates an Opportunity — all in one transaction. If anything fails, nothing is created. The rep gets back the three record IDs confirming everything went through.
Prerequisites
- Claude Desktop installed
- A Salesforce sandbox or Developer Edition org
- An External Client App already configured with MCP scopes — if you haven't done this yet, watch the Salesforce Hosted MCP Servers video first
The Apex Class
The class uses @InvocableMethod on the method and @InvocableVariable on every input and output field — the same annotations used for Flow invocable actions. Salesforce reads these annotations and builds the MCP tool schema automatically. You never write JSON schema by hand.
Three Things Worth Calling Out in This Class
1. The description on @InvocableMethod is what the agent reads
This is not a code comment for another developer. It is the text Claude reads to decide when to call this tool and what it does. A vague description like "creates customer records" will result in the tool being misused or ignored. Write it in plain language, describe what it creates, and include what happens on failure — as shown in the description above.
2. DML outside the loop — this is critical
The @InvocableMethod parameter is always a List, meaning Salesforce can pass multiple requests in a single call. If the insert statements were inside the loop, ten incoming requests would trigger thirty separate DML operations. Salesforce allows a maximum of 150 DML statements per transaction, so it breaks at scale.
The fix is what you see in the code: build all records into lists inside the loop, then insert each list once outside the loop. Request index three will always line up with Account index three, Contact index three, and Opportunity index three — because all three lists are built in the same loop iteration.
3. Class, inner classes, and all fields must be global
The outer class, both inner classes (NewCustomerSetupRequest and NewCustomerSetupResult), the method, and every @InvocableVariable field must use the global access modifier. public is not sufficient for MCP server exposure. Salesforce will not surface the method in the Tool picker if the visibility is wrong.
Register the MCP Server in Setup
Once the class is deployed, go to Setup and search for MCP Servers under Integrations, API Catalog. Click the Custom Servers tab and click New.
| Field | Value |
|---|---|
| Label | New Customer Setup |
| API Name | NewCustomerSetup |
| Description | Creates an Account, Contact, and Opportunity for a new customer in one step |
Under Tools, click Add Tool, select Apex Action, and search for NewCustomerSetupService. Salesforce reads the annotations and generates the input and output schema automatically.
Click Save, then Activate. Copy the Server URL shown on the detail page.
Connect to Claude Desktop
Open Claude Desktop. Click Customize in the left sidebar, then Connectors, then +, then Add custom connector.
| Field | Value |
|---|---|
| Name | Salesforce New Customer Setup |
| Remote MCP server URL | Your Server URL from the detail page |
| OAuth Client ID | Consumer Key from your External Client App |
| OAuth Client Secret | Leave empty |
Click Add, then Connect. Authenticate in Salesforce, approve access, and you are redirected back. The connector shows as Connected.
https://claude.ai/api/mcp/auth_callback with no trailing slash or extra characters. Also wait at least 10 to 15 minutes after creating the app before trying to connect.Demo Prompts
On the first prompt, Claude calls NewCustomerSetupService, creates the Account, Contact, and Opportunity in one transaction, and returns all three record IDs. On the second prompt, Claude recognises the required fields are missing and asks for them before calling the tool.
Key Takeaways
- Same annotations as Flow invocable actions:
@InvocableMethodon the method,@InvocableVariableon every input and output field - Everything must be
global— outer class, inner classes, method, and all fields - Method signature:
List<RequestType>in,List<ResultType>out — always - Never put DML inside the loop — build lists inside, insert once outside
- Write the
@InvocableMethoddescription for Claude, not for another developer - Register in Setup: Integrations, API Catalog, MCP Servers, Custom Servers, New
- Connect in Claude Desktop via Add custom connector with the Server URL and Consumer Key
- Claude asks for missing required fields before calling the tool — it does not guess
The pattern here applies to any Apex class you already have. Complex multi-object transactions, callouts to external APIs, business logic that plain data access cannot reach — all of it becomes callable from Claude in a natural language prompt. Annotate it, register it, connect it.
📄 Source: Expose Any Apex Class to Claude as a Custom MCP Server — SF Bolt YouTube | Source code — github.com/batra-kapil/salesforce-agentforce


0 Comments