mcp-tool-definition-apex
Use this skill to implement custom Apex MCP tool classes by extending McpToolDefinition from the salesforce-mcp-lib package. Covers inputSchema(), validate(), and execute() override patterns, JSON schema construction, SOQL and DML inside tools, error handling, and tool registration in the Apex REST endpoint. Trigger keywords: McpToolDefinition, extend McpToolDefinition, Apex MCP tool, mcp-tool Apex, JSON-RPC tool, salesforce-mcp-lib tool class. NOT for the initial server installation and proxy setup (see salesforce-mcp-server-setup), NOT for MCP Resources or Prompts, NOT for OmniStudio or Flow-based tool definitions.
Best use case
mcp-tool-definition-apex is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use this skill to implement custom Apex MCP tool classes by extending McpToolDefinition from the salesforce-mcp-lib package. Covers inputSchema(), validate(), and execute() override patterns, JSON schema construction, SOQL and DML inside tools, error handling, and tool registration in the Apex REST endpoint. Trigger keywords: McpToolDefinition, extend McpToolDefinition, Apex MCP tool, mcp-tool Apex, JSON-RPC tool, salesforce-mcp-lib tool class. NOT for the initial server installation and proxy setup (see salesforce-mcp-server-setup), NOT for MCP Resources or Prompts, NOT for OmniStudio or Flow-based tool definitions.
Teams using mcp-tool-definition-apex should expect a more consistent output, faster repeated execution, less prompt rewriting, better workflow continuity with your supporting tools.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
- You already have the supporting tools or dependencies needed by this skill.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/mcp-tool-definition-apex/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How mcp-tool-definition-apex Compares
| Feature / Agent | mcp-tool-definition-apex | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use this skill to implement custom Apex MCP tool classes by extending McpToolDefinition from the salesforce-mcp-lib package. Covers inputSchema(), validate(), and execute() override patterns, JSON schema construction, SOQL and DML inside tools, error handling, and tool registration in the Apex REST endpoint. Trigger keywords: McpToolDefinition, extend McpToolDefinition, Apex MCP tool, mcp-tool Apex, JSON-RPC tool, salesforce-mcp-lib tool class. NOT for the initial server installation and proxy setup (see salesforce-mcp-server-setup), NOT for MCP Resources or Prompts, NOT for OmniStudio or Flow-based tool definitions.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# MCP Tool Definition in Apex
This skill activates when a practitioner needs to write Apex code that extends the `McpToolDefinition` abstract class from the salesforce-mcp-lib package to expose custom Salesforce org logic — SOQL queries, DML, callouts, or complex business calculations — as callable tools to an MCP-capable AI client such as Claude Desktop or Cursor.
The salesforce-mcp-lib package defines three overrideable methods that together form a complete tool contract. Getting each of these right is the central challenge this skill addresses.
---
## Before Starting
Gather this context before working on anything in this domain:
| Context | What to confirm |
|---|---|
| Package installed | `sf package installed list --target-org YOUR_ORG` shows salesforce-mcp-lib. The McpToolDefinition class will not exist without it. |
| Tool contract | Define before coding: (1) tool name (snake_case, used by the MCP client to invoke), (2) every required and optional parameter with its JSON Schema type, (3) return shape. |
| Governor limits | Each MCP tool invocation is one Apex transaction: SOQL (100/txn), DML (150 stmts), CPU (10s sync), heap (6MB sync). Async patterns must poll async results in a subsequent tool call. |
| Sharing context | The tool runs as the Connected App's run-as user. Decide whether to use that user's sharing context or bypass it. |
---
## Core Concepts
### The Three-Method Contract
Every `McpToolDefinition` subclass must override three methods:
**`inputSchema()`** — Returns a `Map<String, Object>` that is a valid JSON Schema object describing the tool's parameters. The MCP client uses this schema to populate the tool call's arguments and to validate user input before sending. The minimum required structure is `{ 'type' => 'object', 'properties' => { ... }, 'required' => [...] }`.
**`validate(Map<String, Object> params)`** — Called by the McpServer before `execute()`. Return `null` if params are valid. Return a non-null String error message if validation fails — the McpServer will return a JSON-RPC error response to the client without calling `execute()`. This is the correct place for required-field checks, format validation, and SOQL injection defense.
**`execute(Map<String, Object> params)`** — Called only if `validate()` returns `null`. Contains the actual Salesforce logic. Return any serializable Object (Map, List, SObject, String, Integer). The McpServer serializes the return value to JSON and wraps it in the JSON-RPC 2.0 response.
### JSON Schema for inputSchema()
The `inputSchema()` return value must be a valid JSON Schema object. Salesforce Apex does not have a JSON Schema library, so you construct it as nested `Map<String, Object>` and `List<Object>` literals:
```apex
global override Map<String, Object> inputSchema() {
return new Map<String, Object>{
'type' => 'object',
'properties' => new Map<String, Object>{
'recordId' => new Map<String, Object>{
'type' => 'string',
'description' => '18-character Salesforce Account ID'
},
'includeContacts' => new Map<String, Object>{
'type' => 'boolean',
'description' => 'Whether to include related Contacts in the response'
}
},
'required' => new List<Object>{ 'recordId' }
};
}
```
Note that `required` is a `List<Object>`, not `List<String>`. JSON serialization requires `Object` to avoid type coercion errors in some Apex JSON serializers.
### The Global Access Modifier Requirement
All overriding methods in a class that extends a global abstract class from a managed package must also use the `global` access modifier. Using `public override` will cause a compile error because the base class methods are `global abstract`.
---
## Common Patterns
### Pattern: Simple Record Lookup Tool
**When to use:** The most common MCP tool pattern — the AI agent provides a record ID and the tool returns structured data about the record.
**How it works:**
```apex
global class AccountDetailTool extends McpToolDefinition {
global override Map<String, Object> inputSchema() {
return new Map<String, Object>{
'type' => 'object',
'properties' => new Map<String, Object>{
'accountId' => new Map<String, Object>{
'type' => 'string',
'description' => '18-character Salesforce Account ID'
}
},
'required' => new List<Object>{ 'accountId' }
};
}
global override String validate(Map<String, Object> params) {
if (!params.containsKey('accountId')) return 'accountId is required';
String id = (String) params.get('accountId');
if (id == null || id.length() < 15) return 'accountId must be a valid Salesforce ID';
return null;
}
global override Object execute(Map<String, Object> params) {
String accountId = (String) params.get('accountId');
Account acc = [SELECT Id, Name, Industry, AnnualRevenue, Phone
FROM Account WHERE Id = :accountId LIMIT 1];
return new Map<String, Object>{
'id' => acc.Id,
'name' => acc.Name,
'industry' => acc.Industry,
'annualRevenue' => acc.AnnualRevenue,
'phone' => acc.Phone
};
}
}
```
**Why not the alternative:** Returning the raw SObject is tempting but fragile — JSON serialization of SObjects includes all queried fields and relationship metadata that confuses the MCP client. Return an explicit Map instead.
### Pattern: DML Write Tool with Explicit Error Handling
**When to use:** The AI agent needs to create or update Salesforce records based on conversation context.
**How it works:**
```apex
global class CreateCaseTool extends McpToolDefinition {
global override Map<String, Object> inputSchema() {
return new Map<String, Object>{
'type' => 'object',
'properties' => new Map<String, Object>{
'subject' => new Map<String, Object>{ 'type' => 'string', 'description' => 'Case subject line' },
'accountId' => new Map<String, Object>{ 'type' => 'string', 'description' => 'Related Account ID' },
'priority' => new Map<String, Object>{
'type' => 'string',
'enum' => new List<Object>{ 'Low', 'Medium', 'High' },
'description' => 'Case priority'
}
},
'required' => new List<Object>{ 'subject' }
};
}
global override String validate(Map<String, Object> params) {
if (!params.containsKey('subject') || String.isBlank((String) params.get('subject'))) {
return 'subject is required and cannot be blank';
}
return null;
}
global override Object execute(Map<String, Object> params) {
Case c = new Case();
c.Subject = (String) params.get('subject');
if (params.containsKey('accountId')) c.AccountId = (String) params.get('accountId');
if (params.containsKey('priority')) c.Priority = (String) params.get('priority');
try {
insert c;
return new Map<String, Object>{ 'success' => true, 'caseId' => c.Id, 'caseNumber' => [SELECT CaseNumber FROM Case WHERE Id = :c.Id].CaseNumber };
} catch (DmlException e) {
return new Map<String, Object>{ 'success' => false, 'error' => e.getDmlMessage(0) };
}
}
}
```
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Tool needs a single required param | Put it in `required` list in inputSchema() and check in validate() | validate() prevents execute() from running with missing params |
| Tool does SOQL with user-supplied input | Bind the variable with `:variable` syntax in SOQL | Prevents SOQL injection; never concatenate user input into SOQL strings |
| Tool needs to return a list of records | Return a `List<Map<String, Object>>` | Cleaner than returning raw SObject lists and avoids serialization surprises |
| Tool execution might hit governor limits | Design tool to be narrow in scope; one tool per operation | Governor limits apply per transaction; splitting logic across multiple tool calls is safer |
| Tool needs enum-constrained input | Add `'enum' => new List<Object>{ ... }` to the property in inputSchema() | MCP client validates against enum before calling; validate() can double-check |
| Tool result includes currency or date fields | Format as String with explicit format | JSON has no Currency or Date types; Apex auto-serializes to ISO 8601 for Dates |
---
## Recommended Workflow
Step-by-step instructions for an AI agent or practitioner working on this task:
1. **Define the tool contract** — before writing code, write out: the tool name (snake_case string returned by `getName()`), every parameter (name, JSON Schema type, required/optional, description), and the expected return shape (map keys, types).
2. **Implement `inputSchema()`** — construct the JSON Schema Map using `Map<String, Object>` literals. Include a `description` field for every property — MCP clients use this to explain the tool to the AI.
3. **Implement `validate()`** — check all required fields are present and non-null. Check formats (e.g. Salesforce ID length). Check enum values. Return a descriptive error string on failure; return `null` on success.
4. **Implement `execute()`** — write the SOQL/DML/callout logic. Use bind variables for all user input. Wrap DML in try/catch. Return an explicit Map rather than a raw SObject.
5. **Register the tool** — add `server.registerTool(new MyTool())` inside the Apex REST endpoint's `handlePost()` method before calling `handleRequest()`.
6. **Write an Apex test** — test `validate()` with missing params and invalid values. Test `execute()` with a mock record. Confirm at least 75% coverage on the tool class.
7. **Smoke-test via MCP client** — restart Claude Desktop, find the tool in the tools panel, run it with a real record ID, and confirm the response shape is correct.
---
## Review Checklist
Run through these before marking work in this area complete:
- [ ] Class uses `global` access modifier and all overriding methods use `global override`
- [ ] `inputSchema()` returns a valid JSON Schema with `type: object`, `properties`, and `required`
- [ ] `validate()` checks all required params and returns `null` (not empty string) on success
- [ ] `execute()` uses SOQL bind variables for all user-supplied input (no string concatenation)
- [ ] `execute()` returns an explicit Map or List, not a raw SObject
- [ ] Tool registered in the Apex endpoint's `handlePost()` method
- [ ] Apex test class written with at least 75% coverage
---
## Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
1. **`null` vs empty string in validate()** — The McpServer treats `null` return from `validate()` as success and any non-null String as a validation error. Returning an empty string `''` is treated as an error with an empty message — not as success. Always return `null` explicitly when validation passes.
2. **SOQL query with no results throws exception** — If `execute()` does `[SELECT ... WHERE Id = :id LIMIT 1]` and no record matches, Apex throws `System.QueryException: List has no rows for assignment`. Wrap single-record queries in a `List<SObject>` query and check `.isEmpty()` before accessing `[0]`.
3. **JSON serialization of SObjects** — Returning an `Account` or `Case` sObject directly from `execute()` will serialize correctly in basic cases but will include unexpected fields and relationship metadata if the object has been populated through relationship traversal. Always return an explicit `Map<String, Object>` to control the response shape.
4. **Governor limits per tool call** — Each MCP tool invocation is a single Apex transaction. If the tool does two SOQL queries (one in `validate()` and one in `execute()`), both count against the 100 SOQL query limit. Design validate() to do format checks only; do data lookups in execute().
---
## Output Artifacts
| Artifact | Description |
|---|---|
| McpToolDefinition Apex class | The tool implementation with inputSchema, validate, and execute |
| Updated Apex REST endpoint | The endpoint class with server.registerTool(new MyTool()) added |
| Apex test class | Test coverage for validate() branches and execute() happy path |
---
## Related Skills
- salesforce-mcp-server-setup — prerequisite: install the Apex package and configure the npm proxy before writing tool classes
- agentforce/custom-agent-actions-apex — native Agentforce Agent Actions as an alternative when MCP protocol compatibility is not required
---
## Official Sources Used
- salesforce-mcp-lib GitHub (MIT) — https://github.com/Damecek/salesforce-mcp-lib
- Apex Developer Guide: Apex REST Web Services — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest.htm
- Apex Developer Guide: Governor Execution Limits — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
- JSON Schema specification — https://json-schema.org/understanding-json-schema/
- Agentforce Developer Guide — https://developer.salesforce.com/docs/einstein/genai/guide/agentforce.html
- Salesforce Well-Architected Overview — https://architect.salesforce.com/docs/architect/well-architected/guide/overview.htmlRelated Skills
salesforce-mcp-server-setup
Use this skill to install and configure the salesforce-mcp-lib Apex package and npm stdio proxy so that an MCP client (Claude Desktop, Cursor, ChatGPT) can call Salesforce org data and logic via the Model Context Protocol. Trigger keywords: MCP server, salesforce-mcp-lib, Claude Desktop Salesforce, MCP proxy setup, Apex JSON-RPC, MCP Connected App. NOT for Salesforce Hosted MCP Servers (Agentforce-native hosted endpoints), NOT for Flow-based tool definitions, NOT for OmniStudio integrations.
apex-managed-sharing-patterns
Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.
lwc-imperative-apex
Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).
lwc-debugging-devtools
Use when you need to diagnose live Lightning Web Component behavior in the browser — setting breakpoints, stepping through @wire emits, inspecting component state with the Lightning Component Inspector, reading wire-adapter network traffic, and interpreting symptoms like silent render failures or 'works in dev but not in prod'. Triggers: 'how to set breakpoint in lwc', 'source maps missing', 'enable debug mode', 'lightning inspector chrome extension', 'lwc wire not emitting'. NOT for Jest test failures — use `lwc-testing` — and NOT for Apex debug logs, which are an Apex debugging concern. Specific runtime error messages (wire undefined, querySelector returns null, NavigationMixin errors) belong in `common-lwc-runtime-errors`; this skill covers the debug TOOLING itself.
dataweave-for-apex
Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).
flow-invocable-from-apex
Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).
flow-apex-defined-types
Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.
salesforce-devops-tooling-selection
Use when a team needs to choose a DevOps tooling platform for Salesforce — comparing options like Gearset, Copado, Flosum, AutoRABIT, Blue Canvas, and Salesforce DevOps Center across axes of hosting model, team composition, compliance posture, and budget. Trigger keywords: 'which DevOps tool for Salesforce', 'Gearset vs Copado', 'CI/CD tool comparison', 'DevOps platform selection', 'native vs third-party DevOps'. NOT for CI/CD pipeline configuration (use devops/continuous-integration-testing), NOT for Git branching strategy design (use devops/git-branching-for-salesforce), NOT for environment topology decisions (use devops/environment-strategy).
org-shape-and-scratch-definition
Use this skill when authoring, debugging, or optimizing a scratch org definition file (project-scratch-def.json): schema structure, features array, settings hierarchy, Org Shape sourcing, edition selection, orgPreferences-to-settings migration, and release pinning. NOT for scratch org lifecycle management (use scratch-org-management), CI pipeline design, or sandbox configuration.
data-loader-and-tools
Use this skill when selecting or configuring Salesforce data load tools: Data Loader, Data Import Wizard, Workbench, Salesforce Inspector, or Salesforce CLI for import/export. Trigger keywords: data loader configuration, import wizard, bulk data load, workbench SOQL, salesforce inspector, data export, csv import salesforce, data migration tool selection. NOT for Bulk API development — use the integration or apex skills for Bulk API coding.
nfr-definition-for-salesforce
Defining measurable non-functional requirements for Salesforce implementations: performance SLIs, scalability targets, availability SLAs, security and compliance requirements, usability benchmarks. Use when starting architecture design or preparing for go-live sign-off. NOT for technical implementation of those requirements. NOT for HA/DR planning (use ha-dr-architecture). NOT for individual governor limit investigation (use limits-and-scalability-planning). NOT for security controls implementation (use security-architecture-review).
tooling-api-patterns
Use when building external tooling, IDE-style automation, or scripts that talk to the Salesforce **Tooling API** — single-class compile-and-deploy via `MetadataContainer`/`ApexClassMember`/`ContainerAsyncRequest`, querying Tooling-only sObjects (`ApexClass`, `ApexCodeCoverage`, `FlowDefinition`, `EntityDefinition`/`FieldDefinition`), debug-log retrieval (`ApexLog`, `TraceFlag`, `DebugLevel`), heap-dump overlays (`ApexExecutionOverlayAction`, `ApexExecutionOverlayResult`), and anonymous Apex execution. Triggers: 'tooling api compile single apex class', 'MetadataContainer ContainerAsyncRequest', 'ApexCodeCoverageAggregate query', 'ApexLog retrieve via tooling api', 'TraceFlag debug level setup', 'EntityDefinition FieldDefinition queryable', 'tooling api vs metadata api', 'tooling api vs data api'. NOT for runtime Apex metadata operations (use `apex/apex-metadata-api` for the `Metadata.Operations` namespace), Metadata API zip-based deploys (use `devops/metadata-api-retrieve-deploy`), Data API SOQL fundamentals (use `apex/soql-fundamentals`), or general deployment-error diagnosis (use `devops/deployment-error-troubleshooting`).