Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Salesforce MCP Library
https://github.com/damecek/salesforce-mcp-lib
Admin
Salesforce MCP Library is a local stdio bridge for Salesforce MCP endpoints using OAuth client
...
Tokens:
29,902
Snippets:
244
Trust Score:
7.5
Update:
1 week ago
Context
Skills
Chat
Benchmark
93.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Salesforce MCP Library Salesforce MCP Library is an AI-first Salesforce 2GP package providing a reusable Apex Model Context Protocol (MCP) library with an in-repo JSON-RPC 2.0 core. It enables building MCP servers in Apex that expose tools, resources, resource templates, and prompts to AI agents through standardized JSON-RPC endpoints. The library includes both the Apex runtime for Salesforce and a Node.js CLI bridge (`salesforce-mcp-lib`) that connects local MCP clients to remote Salesforce Apex REST endpoints using OAuth 2.0 client credentials authentication. This architecture allows AI agents to interact with Salesforce data and operations through the Model Context Protocol standard. ## CLI Bridge Usage The CLI bridge exposes remote Salesforce MCP servers over local stdio transport, enabling MCP clients to connect to Salesforce endpoints. ```bash # Basic usage with environment variables export SF_CLIENT_ID='your-consumer-key' export SF_CLIENT_SECRET='your-consumer-secret' npx salesforce-mcp-lib \ --url https://myorg.my.salesforce.com/services/apexrest/mcp/opportunity/ \ --client-id "$SF_CLIENT_ID" \ --client-secret "$SF_CLIENT_SECRET" # With optional scope npx salesforce-mcp-lib \ --url https://myorg.sandbox.my.salesforce.com/services/apexrest/mcp \ --client-id "$SF_CLIENT_ID" \ --client-secret "$SF_CLIENT_SECRET" \ --scope "api refresh_token" ``` ## McpServer - Core Server Class The `McpServer` class is the central registry and JSON-RPC execution entry point for MCP capabilities. It manages tool, resource, resource template, and prompt registrations and executes incoming JSON-RPC requests. ```apex // Create an MCP server with name and version McpServer server = new McpServer('My Salesforce MCP', '1.0.0'); // Register capabilities using fluent API McpToolDefinition toolDef = new McpToolDefinition('math.sum', 'Sums two integers.', SumArguments.class); toolDef.inputSchema = new Map<String, Object>{ 'type' => 'object', 'properties' => new Map<String, Object>{ 'a' => new Map<String, Object>{ 'type' => 'integer' }, 'b' => new Map<String, Object>{ 'type' => 'integer' } }, 'required' => new List<Object>{ 'a', 'b' } }; McpResourceDefinition resourceDef = new McpResourceDefinition('mcp://data/config', 'Configuration'); resourceDef.description = 'Returns application configuration.'; server .registerTool(toolDef, new SumToolExecutor()) .registerResource(resourceDef, new ConfigResourceExecutor()); // Execute JSON-RPC request String requestJson = '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"math.sum","arguments":{"a":2,"b":3}}}'; JsonRpcExecutionResult result = server.execute(requestJson); String responseJson = result.toJson(); // Response: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"5"}],"isError":false}} ``` ## McpHttpTransport - REST Endpoint Adapter The `McpHttpTransport` class provides a reusable Apex REST transport adapter that handles HTTP POST requests and delegates to an MCP server instance. ```apex @RestResource(urlMapping='/mcp/*') global inherited sharing class MyMcpEndpoint { @HttpPost global static void handlePost() { McpServer server = MyServerFactory.build(); McpHttpTransport.handlePost(server); } } // Server factory pattern for clean separation public inherited sharing class MyServerFactory { public static McpServer build() { McpToolDefinition toolDef = new McpToolDefinition( 'account.search', 'Search accounts by name.', AccountSearchArguments.class ); toolDef.inputSchema = new Map<String, Object>{ 'type' => 'object', 'properties' => new Map<String, Object>{ 'query' => new Map<String, Object>{ 'type' => 'string' } }, 'required' => new List<Object>{ 'query' } }; return new McpServer('Account MCP', '1.0.0') .registerTool(toolDef, new AccountSearchExecutor()); } } ``` ## McpToolDefinition and McpToolExecutor - Tool Implementation Tools represent callable operations that AI agents can invoke. Each tool requires a definition with metadata and an executor that implements the business logic. ```apex // Tool arguments DTO - must extend JsonRpcParamsBase public class OpportunityFetchArguments extends JsonRpcParamsBase { public String opportunityId; public override void validate() { if (String.isBlank(this.opportunityId)) { throw new McpExceptions.InvalidParamsException('opportunityId is required.'); } } } // Tool definition with annotations McpToolDefinition toolDef = new McpToolDefinition( 'opportunity.fetch', 'Fetches opportunity details by ID.', OpportunityFetchArguments.class ); toolDef.inputSchema = new Map<String, Object>{ 'type' => 'object', 'properties' => new Map<String, Object>{ 'opportunityId' => new Map<String, Object>{ 'type' => 'string', 'description' => 'Salesforce Opportunity ID' } }, 'required' => new List<Object>{ 'opportunityId' } }; toolDef.annotations = new McpToolAnnotations(); toolDef.annotations.readOnlyHint = true; toolDef.annotations.idempotentHint = true; // Tool executor implementation public class OpportunityFetchExecutor implements McpToolExecutor { public McpToolResult execute(JsonRpcParamsBase arguments, McpRequestContext context) { OpportunityFetchArguments typed = (OpportunityFetchArguments) arguments; Opportunity opp = [ SELECT Id, Name, StageName, Amount, CloseDate FROM Opportunity WHERE Id = :typed.opportunityId LIMIT 1 ]; McpToolResult result = new McpToolResult(); result.content.add(new McpTextContent(JSON.serialize(opp))); return result; } } // Register and use server.registerTool(toolDef, new OpportunityFetchExecutor()); ``` ## McpResourceDefinition and McpResourceExecutor - Resource Implementation Resources represent read-only data sources identified by URIs that AI agents can access. ```apex // Resource definition McpResourceDefinition resourceDef = new McpResourceDefinition( 'mcp://salesforce/user/current', 'Current User Info' ); resourceDef.description = 'Returns information about the currently authenticated user.'; resourceDef.mimeType = 'application/json'; // Resource executor implementation public class CurrentUserResource implements McpResourceExecutor { public McpResourceResult read(String resourceUri, McpRequestContext context) { User currentUser = [ SELECT Id, Name, Email, Profile.Name, UserRole.Name FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1 ]; McpResourceResult result = new McpResourceResult(); result.contents.add(new McpResourceContentItem( resourceUri, JSON.serialize(currentUser), 'application/json' )); return result; } } // Register resource server.registerResource(resourceDef, new CurrentUserResource()); // JSON-RPC request example // {"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"mcp://salesforce/user/current"}} ``` ## McpResourceTemplateDefinition and McpResourceTemplateExecutor - Template Implementation Resource templates are parameterized resources that generate content based on arguments. ```apex // Template arguments DTO public class AccountDetailsArguments extends JsonRpcParamsBase { public String accountId; public override void validate() { if (String.isBlank(this.accountId)) { throw new McpExceptions.InvalidParamsException('accountId is required.'); } } } // Template definition McpResourceTemplateDefinition templateDef = new McpResourceTemplateDefinition( 'account-details', 'mcp://salesforce/account/{accountId}', AccountDetailsArguments.class ); templateDef.description = 'Returns detailed account information.'; // Template executor implementation public class AccountDetailsTemplate implements McpResourceTemplateExecutor { public McpResourceResult resolve(JsonRpcParamsBase arguments, McpRequestContext context) { AccountDetailsArguments typed = (AccountDetailsArguments) arguments; Account acc = [ SELECT Id, Name, Industry, AnnualRevenue, NumberOfEmployees, BillingCity, BillingState, BillingCountry FROM Account WHERE Id = :typed.accountId LIMIT 1 ]; McpResourceResult result = new McpResourceResult(); result.contents.add(new McpResourceContentItem( 'mcp://salesforce/account/' + typed.accountId, JSON.serialize(acc), 'application/json' )); return result; } } // Register template server.registerResourceTemplate(templateDef, new AccountDetailsTemplate()); // JSON-RPC request example // {"jsonrpc":"2.0","id":1,"method":"resources/templates/call","params":{"name":"account-details","arguments":{"accountId":"001XX000003GYQE"}}} ``` ## McpPromptDefinition and McpPromptExecutor - Prompt Implementation Prompts provide pre-configured message templates that AI agents can use to generate contextual responses. ```apex // Prompt arguments DTO public class CustomerReplyArguments extends JsonRpcParamsBase { public String opportunityId; public override void validate() { if (String.isBlank(this.opportunityId)) { throw new McpExceptions.InvalidParamsException('opportunityId is required.'); } } } // Prompt definition with argument descriptors McpPromptDefinition promptDef = new McpPromptDefinition( 'opportunity.customer_reply_draft', 'Draft a customer reply based on opportunity data.', CustomerReplyArguments.class ); promptDef.arguments = new List<McpPromptArgumentDefinition>{ new McpPromptArgumentDefinition('opportunityId', 'Salesforce Opportunity ID', true) }; // Prompt executor implementation public class CustomerReplyPrompt implements McpPromptExecutor { public McpPromptResult resolve(JsonRpcParamsBase arguments, McpRequestContext context) { CustomerReplyArguments typed = (CustomerReplyArguments) arguments; Opportunity opp = [ SELECT Id, Name, StageName, CloseDate, Account.Name FROM Opportunity WHERE Id = :typed.opportunityId LIMIT 1 ]; String promptText = 'Draft a professional customer reply for the following opportunity:\n\n' + 'Account: ' + opp.Account.Name + '\n' + 'Opportunity: ' + opp.Name + '\n' + 'Stage: ' + opp.StageName + '\n' + 'Close Date: ' + opp.CloseDate + '\n\n' + 'Focus on current status, concrete next steps, and timeline clarity.'; McpPromptResult result = new McpPromptResult(); result.description = 'Customer reply context for ' + opp.Name; result.messages.add(McpPromptMessage.userText(promptText)); return result; } } // Register prompt server.registerPrompt(promptDef, new CustomerReplyPrompt()); // JSON-RPC request example // {"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"opportunity.customer_reply_draft","arguments":{"opportunityId":"006XX000003ABCD"}}} ``` ## JSON-RPC Protocol Methods The MCP server responds to standard JSON-RPC 2.0 methods for MCP protocol operations. ```json // Initialize - handshake with protocol version {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}} // Response: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"My MCP","version":"1.0.0"},"capabilities":{"tools":{},"resources":{},"prompts":{}}}} // List tools {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}} // Response: {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"math.sum","description":"Sums two integers.","inputSchema":{"type":"object"}}]}} // Call tool {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"math.sum","arguments":{"a":2,"b":5}}} // Response: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"7"}],"isError":false}} // List resources {"jsonrpc":"2.0","id":4,"method":"resources/list","params":{}} // Response: {"jsonrpc":"2.0","id":4,"result":{"resources":[{"uri":"mcp://secret/one","name":"Secret One"}]}} // Read resource {"jsonrpc":"2.0","id":5,"method":"resources/read","params":{"uri":"mcp://secret/one"}} // Response: {"jsonrpc":"2.0","id":5,"result":{"contents":[{"uri":"mcp://secret/one","text":"1","mimeType":"application/json"}]}} // List resource templates {"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}} // Response: {"jsonrpc":"2.0","id":6,"result":{"resourceTemplates":[{"name":"secret-sum","uriTemplate":"mcp://templates/secret-sum{?value}"}]}} // Call resource template {"jsonrpc":"2.0","id":7,"method":"resources/templates/call","params":{"name":"secret-sum","arguments":{"value":9}}} // Response: {"jsonrpc":"2.0","id":7,"result":{"contents":[{"uri":"mcp://templates/secret-sum","text":"10"}]}} // List prompts {"jsonrpc":"2.0","id":8,"method":"prompts/list","params":{}} // Response: {"jsonrpc":"2.0","id":8,"result":{"prompts":[{"name":"greet-target","description":"Greets a named target.","arguments":[{"name":"target","required":true}]}]}} // Get prompt {"jsonrpc":"2.0","id":9,"method":"prompts/get","params":{"name":"greet-target","arguments":{"target":"agent"}}} // Response: {"jsonrpc":"2.0","id":9,"result":{"messages":[{"role":"user","content":{"type":"text","text":"Hello agent"}}]}} // Batch requests (multiple operations in single call) [ {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}, {"jsonrpc":"2.0","id":2,"method":"resources/list","params":{}}, {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"math.sum","arguments":{"a":1,"b":2}}} ] ``` ## JsonRpcParamsBase - Typed Arguments Base Class All tool, template, and prompt arguments must extend `JsonRpcParamsBase` and implement validation. ```apex // Base class for typed parameters public virtual class JsonRpcParamsBase { public virtual void validate() { // Override to enforce required fields } } // Example typed arguments with validation public class CreateTaskArguments extends JsonRpcParamsBase { public String subject; public String whatId; public Date activityDate; public String priority; public override void validate() { if (String.isBlank(this.subject)) { throw new McpExceptions.InvalidParamsException('subject is required.'); } if (this.activityDate == null) { throw new McpExceptions.InvalidParamsException('activityDate is required.'); } if (String.isNotBlank(this.priority)) { Set<String> validPriorities = new Set<String>{ 'High', 'Normal', 'Low' }; if (!validPriorities.contains(this.priority)) { throw new McpExceptions.InvalidParamsException('priority must be High, Normal, or Low.'); } } } } ``` ## McpExceptions - Error Handling The library provides typed exceptions that map to JSON-RPC error codes. ```apex // Exception types and their JSON-RPC error codes public class McpExceptions { // -32601: Method not found (tool/resource/template/prompt not registered) public class NotFoundException extends Exception {} // -32602: Invalid params (argument validation failures) public class InvalidParamsException extends Exception {} // -32603: Internal error (executor runtime failures) public class InternalException extends Exception {} } // Usage in executor public class SafeToolExecutor implements McpToolExecutor { public McpToolResult execute(JsonRpcParamsBase arguments, McpRequestContext context) { MyArguments typed = (MyArguments) arguments; // Throw NotFoundException for missing resources Account acc = [SELECT Id, Name FROM Account WHERE Id = :typed.accountId LIMIT 1]; if (acc == null) { throw new McpExceptions.NotFoundException('Account not found: ' + typed.accountId); } // Throw InvalidParamsException for business validation if (acc.Name == 'BLOCKED') { throw new McpExceptions.InvalidParamsException('Account is blocked and cannot be accessed.'); } try { // Business logic McpToolResult result = new McpToolResult(); result.content.add(new McpTextContent(JSON.serialize(acc))); return result; } catch (Exception e) { // Throw InternalException for unexpected errors throw new McpExceptions.InternalException('Failed to process account: ' + e.getMessage()); } } } ``` ## SalesforceTokenClient - OAuth Authentication (Node.js) The Node.js bridge handles OAuth 2.0 client credentials flow automatically with token caching. ```typescript import { SalesforceTokenClient } from 'salesforce-mcp-lib'; // Token client configuration const tokenClient = new SalesforceTokenClient({ tokenUrl: new URL('https://myorg.my.salesforce.com/services/oauth2/token'), clientId: process.env.SF_CLIENT_ID, clientSecret: process.env.SF_CLIENT_SECRET, scope: 'api', // Optional resource: 'https://myorg.my.salesforce.com/services/apexrest/mcp' // Optional }); // Get access token (automatically cached and refreshed) const accessToken = await tokenClient.getAccessToken(); // Token response includes expiration // Tokens are cached and refreshed 30 seconds before expiry ``` ## RemoteMcpProxy - MCP Bridge (Node.js) The `RemoteMcpProxy` class forwards JSON-RPC messages to remote Salesforce MCP endpoints. ```typescript import { RemoteMcpProxy, SalesforceTokenClient } from 'salesforce-mcp-lib'; const tokenClient = new SalesforceTokenClient({ tokenUrl: new URL('https://myorg.my.salesforce.com/services/oauth2/token'), clientId: process.env.SF_CLIENT_ID, clientSecret: process.env.SF_CLIENT_SECRET }); const proxy = new RemoteMcpProxy({ serverUrl: new URL('https://myorg.my.salesforce.com/services/apexrest/mcp'), tokenProvider: tokenClient }); // Forward MCP message to Salesforce const request = { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'opportunity.fetch', arguments: { opportunityId: '006XX000003ABCD' } } }; const response = await proxy.forwardMessage(request); // Response from Salesforce MCP endpoint ``` ## Complete Endpoint Example A full example showing tool, resource, template, and prompt registration with typed arguments. ```apex @RestResource(urlMapping='/mcp-example') global inherited sharing class ExampleMcpEndpoint { @HttpPost global static void handlePost() { McpHttpTransport.handlePost(ExampleMcpServerFactory.build()); } } public inherited sharing class ExampleMcpServerFactory { public static McpServer build() { // Tool: Sum two integers McpToolDefinition sumTool = new McpToolDefinition('math.sum', 'Adds two integers.', SumArguments.class); sumTool.inputSchema = new Map<String, Object>{ 'type' => 'object', 'properties' => new Map<String, Object>{ 'a' => new Map<String, Object>{ 'type' => 'integer' }, 'b' => new Map<String, Object>{ 'type' => 'integer' } }, 'required' => new List<Object>{ 'a', 'b' } }; // Resource: Secret value McpResourceDefinition secretResource = new McpResourceDefinition('mcp://secret/one', 'Secret Integer One'); secretResource.description = 'Returns constant 1.'; // Template: Sum with secret McpResourceTemplateDefinition secretTemplate = new McpResourceTemplateDefinition( 'secret-sum', 'mcp://templates/secret-sum{?value}', SecretTemplateArguments.class ); secretTemplate.description = 'Adds argument value with the secret resource integer.'; return new McpServer('HTTP MCP Example', '1.0.0') .registerTool(sumTool, new SumTool()) .registerResource(secretResource, new SecretResource()) .registerResourceTemplate(secretTemplate, new SecretTemplate()); } private class SumTool implements McpToolExecutor { public McpToolResult execute(JsonRpcParamsBase arguments, McpRequestContext context) { SumArguments typed = (SumArguments) arguments; McpToolResult result = new McpToolResult(); result.content.add(new McpTextContent(String.valueOf(typed.a + typed.b))); return result; } } private class SecretResource implements McpResourceExecutor { public McpResourceResult read(String resourceUri, McpRequestContext context) { McpResourceResult result = new McpResourceResult(); result.contents.add(new McpResourceContentItem(resourceUri, '1', 'application/json')); return result; } } private class SecretTemplate implements McpResourceTemplateExecutor { public McpResourceResult resolve(JsonRpcParamsBase arguments, McpRequestContext context) { SecretTemplateArguments typed = (SecretTemplateArguments) arguments; McpResourceResult result = new McpResourceResult(); result.contents.add(new McpResourceContentItem( 'mcp://templates/secret-sum', String.valueOf(typed.value + 1), 'application/json' )); return result; } } } class SumArguments extends JsonRpcParamsBase { public Integer a; public Integer b; public override void validate() { if (this.a == null || this.b == null) { throw new McpExceptions.InvalidParamsException('math.sum requires integer a and b.'); } } } class SecretTemplateArguments extends JsonRpcParamsBase { public Integer value; public override void validate() { if (this.value == null) { throw new McpExceptions.InvalidParamsException('secret-sum requires integer value.'); } } } ``` ## Summary Salesforce MCP Library enables building Model Context Protocol servers in Apex that expose Salesforce data and operations to AI agents. The primary use cases include: exposing Salesforce business logic as MCP tools for AI-driven automation, providing read-only access to Salesforce data through resources and templates, and creating contextual prompts that help AI agents generate appropriate responses based on Salesforce record data. Integration patterns include: deploying the Apex MCP library as part of a Salesforce 2GP package, creating custom Apex REST endpoints that delegate to `McpHttpTransport`, using the Node.js CLI bridge to connect local MCP clients (like Claude Code or other AI tools) to remote Salesforce endpoints via OAuth client credentials, and implementing typed argument DTOs with validation for safe parameter handling. The library follows JSON-RPC 2.0 specification with MCP protocol version 2025-11-25.