| name | installing-mcp-access-control |
| description | Drives end-to-end installation of the Salesforce MCP Access Control plugin into a user's org: runs `sf` CLI deploy, walks Setup UI for the Permission Set, CMDT record, hosted MCP server, and External Client App, connects the MCP client, and verifies with the quickstart sample. Trigger this whenever the user asks to install, set up, deploy, configure, get started with, or 'walk through' this plugin — including indirect phrasings like 'how do I get this running in my org', 'connect Claude Desktop to this', 'wire up the MCP server', or 'help me try this out'. The user should not have to invoke it by name. Don't trigger when the user is writing Apex, running tests, or debugging code that already deployed — those belong to other skills. |
| compatibility | Salesforce CLI v2.x, this repo cloned as cwd |
| allowed-tools | Bash Read AskUserQuestion |
installing-mcp-access-control
End-to-end installer for this repo. The README documents the eight install steps in prose; this skill drives them, so the user doesn't have to context-switch between the README, the CLI, and Salesforce Setup. Some steps are pure CLI and the skill runs them itself; others (Permission Set, CMDT records, hosted MCP server, External Client App, MCP client) only exist behind Setup UI or vendor docs and the skill walks the user through those.
Don't paraphrase the README at length — point at it. The README is the source of truth for command flags and screenshots; this skill is the orchestration layer.
How to drive the install
Work through the sections in order. After each step, confirm with the user before moving on. The whole flow lives in README.md — don't re-narrate it; quote and link.
0. Preflight
Run these in parallel and report any failures:
sf --version
git --version
pwd
ls force-app sample config sfdx-project.json
Required: sf v2.x or newer, repo root as cwd. If sf is missing, send the user to https://developer.salesforce.com/tools/salesforce-cli and stop.
1. Pick the deploy path
Ask the user with AskUserQuestion:
- Scratch org — fastest eval, requires Dev Hub. Path A in README.
- Sandbox — validate-then-deploy. Path B.
- Production — deploy with pinned tests. Path B +
--test-level RunSpecifiedTests.
Capture the alias they want (default: mcp-ac for scratch, otherwise ask).
2. Step 1 — Deploy the package
Run the exact commands from README.md §Step 1 for the chosen path.
Scratch (Path A):
sf org login web --set-default-dev-hub --alias my-devhub
sf org create scratch --definition-file config/project-scratch-def.json --alias <alias> --duration-days 7 --set-default
sf project deploy start --source-dir force-app
Sandbox (Path B):
sf org login web --alias <alias> --instance-url https://test.salesforce.com
sf project deploy validate --source-dir force-app --target-org <alias>
sf project deploy start --source-dir force-app --target-org <alias>
Production (Path B + tests):
sf project deploy start \
--source-dir force-app \
--target-org <alias> \
--test-level RunSpecifiedTests \
--tests MCP_AccessControlServiceTest \
--tests MCP_AccessControlServiceIntegrationTest \
--tests MCP_GetObjectSchemaActionTest \
--tests MCP_SoqlQueryActionTest \
--tests MCP_UpdateRecordActionTest \
--tests MCP_SchemaServiceTest \
--tests MCP_ScenarioTest \
--tests MCP_SoqlParserTest
Then ask: deploy the quickstart sample? For scratch and sandbox first-time installs it's worth saying yes — it ships a ready-made Permission Set + CMDT record so the user can run the verify Apex (Step 10) immediately and confirm the runtime allowlist works before they invest in building their own PS. For production, decline on the user's behalf and quote the README's warning: sample/ is demo metadata that grants access to standard objects for demo purposes, and shipping it to production widens the agent's perimeter beyond what was reviewed. If a user insists, push back once and then defer.
sf project deploy start --source-dir sample --target-org <alias>
3. Step 2 — Create the agent's Permission Set (manual)
Cannot be automated — the PS shape is the user's call. Direct them to Setup → Users → Permission Sets → New (/lightning/setup/PermSets/home).
Tell them to use the in-repo example as the shape:
- File:
sample/main/default/permissionsets/MCP_AccessControl_Quickstart.permissionset-meta.xml
- Sections to fill: Object Settings (Read/Edit per object), Field Permissions (only the fields the agent needs), Apex Class Access for these six classes:
MCP_SoqlQueryAction
MCP_UpdateRecordAction
MCP_GetObjectSchemaAction
MCP_AccessControlService
MCP_SchemaService
MCP_SoqlParser
All six Apex Class Access grants matter: the action classes call into the service classes, so missing any one of them surfaces as a runtime "class not visible" error well after the install looks complete. Easier to grant all six up front than to debug it later.
Then ask the user: what API Name did you save it under? Capture it for Step 4.
4. Step 3 — Assign the PS to a user (manual)
Direct: Setup → Permission Sets → [your PS] → Manage Assignments → Add Assignments.
Ask the user: which user will the MCP client authenticate as? This is the user whose PS assignments determine the agent's perimeter at runtime, so it's typically the integration user the External Client App in Step 6 will be scoped to. The skill's job here is to make sure the user picks one identity and uses it consistently in Step 3, Step 6, and (if testing as themselves) Step 10. The single biggest source of "tools don't appear in my client" support questions traces back to a mismatch here: PS assigned to user A, ECA scoped to user B.
5. Step 4 — Register the PS via Custom Metadata (manual)
Direct: Setup → Custom Metadata Types (/lightning/setup/CustomMetadata/home) → MCP Access Control → Manage Records → New.
Ask with AskUserQuestion:
- Enforcement Level:
OBJECT_AND_FIELD (default) — field allowlist enforced, but the update action bypasses the allowlist for non-permissionable fields (Name, OwnerId, audit fields). It defers to USER_MODE FLS for those. This is the documented surprise surface — see docs/access-control-contract.md#non-permissionable-field-bypass.
OBJECT_AND_FIELD_STRICT — no bypass; non-permissionable fields are denied unconditionally.
OBJECT_ONLY — no field-level enforcement.
Pre-fill the form for the user:
- Permission Set Developer Name: the API Name captured in Step 2.
- Active: checked.
- Description: optional but recommended.
Note: if multiple active records ever exist for the running user, alphabetical-first by PermissionSet.Name wins.
6. Step 5 — Create the hosted MCP server (manual)
Direct: Setup → MCP (Quick Find) → New Server → name it → Add Server Assets → Add Tools → from Apex Actions → filter by "MCP" → add all three:
- MCP Get Object Schema
- MCP SOQL Query
- MCP Update Record
For the tool description field on each, paste the canonical text from docs/tool-descriptions.md. Read that file with the Read tool and print each block inline so the user can copy without leaving Claude.
Then Activate the server. Capture the server URL — needed in Step 7.
7. Step 6 — Create the External Client App (manual, docs are source of truth)
ECA setup evolves with the platform; don't try to script it. Send the user to:
https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/hosted-mcp-servers-overview.html
Tell them to capture three values when they're done:
- OAuth Client ID
- OAuth Client Secret
- The MCP server URL from Step 5
The ECA must be scoped to the same user assigned the Permission Set in Step 3. If those don't match, the tools won't appear in the client — this is the most common cause of "it didn't work".
8. Step 7 — Connect the MCP client
For Claude Desktop:
- Settings → Connectors → Add custom connector
- Paste the MCP server URL
- Advanced settings → paste OAuth Client ID + Client Secret
Other clients (Cursor, etc.) work the same once the ECA is wired. First call to each tool will prompt "Always allow" — accept it.
9. Step 8 — Smoke test
Ask the user to try these three prompts in their MCP client (each exercises one tool):
- "What objects and fields can you see?" → MCP Get Object Schema INDEX. Should return only allowlisted objects.
- "Find the five most recently modified Accounts and tell me their owner." → MCP SOQL Query.
- "Update Account ABC's annual revenue to 5,000,000." → MCP Update Record. Succeeds if
AnnualRevenue is in the field allowlist; non-permissionable fields like Name behave per the enforcement level chosen in Step 4.
If any fails, jump to the Troubleshooting section.
10. Verify with the quickstart sample (optional)
Only if sample/ was deployed.
Step 1: assign the MCP Access Control Quickstart PS to yourself in Setup.
Step 2: anonymous Apex.
MCP_GetObjectSchemaAction.Request req = new MCP_GetObjectSchemaAction.Request();
req.mode = 'INDEX';
List<MCP_GetObjectSchemaAction.Result> rs =
MCP_GetObjectSchemaAction.execute(new List<MCP_GetObjectSchemaAction.Request>{ req });
System.debug(rs[0]);
Save to a temp .apex file and run with sf apex run --file <path> --target-org <alias>. Expected: Account and Contact listed.
Step 3: full test suite.
sf apex run test \
--tests MCP_AccessControlServiceTest \
--tests MCP_AccessControlServiceIntegrationTest \
--tests MCP_GetObjectSchemaActionTest \
--tests MCP_SoqlQueryActionTest \
--tests MCP_UpdateRecordActionTest \
--tests MCP_SchemaServiceTest \
--tests MCP_ScenarioTest \
--tests MCP_SoqlParserTest \
--code-coverage --result-format human --wait 10 \
--target-org <alias>
All 115 tests should pass.
Troubleshooting cheatsheet
| Symptom | Likely cause | Fix |
|---|
No active access control record found for the running user. | CMDT inactive, PS name mismatch, or PS not assigned | Verify CMDT Active__c=true, Permission_Set_DeveloperName__c exactly matches PS API Name, and the user is assigned that PS. |
Object <X> not in allowlist. | Object missing from PS Object Settings | Grant Read (or Edit) on the object in the PS. |
Field <X.Y> not in allowlist. | Field missing from PS Field Permissions | Permissionable: enable on PS. Non-permissionable + STRICT: drop to default mode (or accept the field stays denied). |
System.NoAccessException: DML requires User Mode access | User's profile/PS doesn't grant what the allowlist let through | The allowlist is wider than the user's actual perimeter. Tighten the allowlist or grant on profile. |
| Tools don't appear in the MCP client | Server not activated / ECA not scoped to user / user not assigned PS | Verify all three. The user on the ECA must equal the user with the PS assignment. |
Full troubleshooting list is in README.md §Good to know and troubleshooting.
What this skill cannot automate
| Step | Why manual |
|---|
| 2 — Custom PS | Shape depends on the agent's actual use case — user-defined |
| 3 — PS assignment | sf has no first-class assign command; faster in Setup |
| 4 — CMDT record creation | Apex cannot insert CMDT rows; must be Setup or .md-meta.xml deploy |
| 5 — Hosted MCP server | No Metadata API surface yet |
| 6 — External Client App | OAuth scoping requires UI; Salesforce docs evolve |
| 7 — MCP client connector | Client-side, on the user's machine |
References
README.md — full install narrative, source of truth for command flags
docs/access-control-contract.md — enforcement levels, non-permissionable bypass
docs/tool-descriptions.md — canonical tool descriptions to paste into Step 5
sample/main/default/permissionsets/MCP_AccessControl_Quickstart.permissionset-meta.xml — example PS shape for Step 2
sample/main/default/customMetadata/MCP_Access_Control.MCP_AccessControl_Quickstart.md-meta.xml — example CMDT record for Step 4