@wisefoxme/salesforce-copilot-admin-mcp
v1.1.1
Published
MCP server for Salesforce admin operations
Downloads
21
Maintainers
Readme
@wisefoxme/salesforce-copilot-admin-mcp
An MCP (Model Context Protocol) server that exposes Salesforce admin operations to compatible clients (e.g. Cursor). It talks to your org via the Salesforce CLI (sf) for authentication and the REST/Tooling APIs for work.
Published on npm as @wisefoxme/salesforce-copilot-admin-mcp. The CLI binary name is salesforce-admin-mcp (see package.json → bin); npx runs that binary from the package.
Prerequisites
- Node.js (current LTS is fine)
- Salesforce CLI (
sf) installed and an authenticated org (sf org login webor your usual flow) - A CLI default target org that is Connected or Active (see below), or set
SF_TARGET_ORG/SALESFORCE_USERNAME, or passtarget_orgon each tool call
Install and run (from npm)
No clone required. Use npx to run the published package (add -y to skip the install prompt, e.g. in automation):
npx -y @wisefoxme/salesforce-copilot-admin-mcpConfigure the MCP client (Cursor)
Add a server that runs this process over stdio (this server uses StdioServerTransport).
- Open Cursor Settings → MCP (or edit your MCP config file, depending on your Cursor version).
- Register a server that inherits your shell environment so
sfworks (same user/org as your terminal).
Recommended (registry package, no cwd):
{
"mcpServers": {
"salesforce-admin": {
"command": "npx",
"args": ["-y", "@wisefoxme/salesforce-copilot-admin-mcp"]
}
}
}Alternatives: local clone
If you develop from a clone or prefer an explicit path to the built file:
{
"mcpServers": {
"salesforce-admin": {
"command": "node",
"args": ["/absolute/path/to/salesforce-copilot-admin-mcp/dist/index.js"]
}
}
}From the project directory after npm install and npm run build, npm exposes the local binary:
npm exec salesforce-admin-mcpMCP config using that:
{
"mcpServers": {
"salesforce-admin": {
"command": "npm",
"args": ["exec", "--", "salesforce-admin-mcp"],
"cwd": "/absolute/path/to/salesforce-copilot-admin-mcp"
}
}
}Note: The server shells out to sf org list and sf org display. Ensure the environment where the MCP process runs can see the same Salesforce CLI auth as when you run sf in a terminal (same machine user, PATH includes sf, and org is authorized).
Troubleshooting: Cannot find module '.../dist/index.js'
This applies when using a local path in MCP config. Node must point at this repository’s dist/index.js (e.g. .../salesforce-copilot-admin-mcp/dist/index.js), not a parent folder. Update Cursor Settings → MCP so args uses the full path, then restart the server.
If the path is correct but the error persists, run npm run build from the repo root so dist/index.js exists.
Which org is used
Priority:
target_orgon the tool invocation (alias or username), if provided.SF_TARGET_ORGorSALESFORCE_USERNAME, if set.- Otherwise the CLI default target org from
sf org list --json: the org withisDefaultUsernameandconnectedStatusConnectedorActive(case-insensitive).
The default Dev Hub (isDefaultDevHubUsername) is not used as a fallback for API calls. There is no “first org in the list” fallback.
If none of the above yields an org, tools fail with an error until you set a default (sf config set target-org <alias> / sf org set default), set an env var, or pass target_org.
Environment variables
| Variable | Purpose |
|----------|---------|
| SF_TARGET_ORG or SALESFORCE_USERNAME | Explicit org alias or username (skips implicit default resolution). |
| SF_API_VERSION | API version for REST/Tooling calls (default v62.0). |
| SALESFORCE_ADMIN_MCP_TELEMETRY_ENABLED | Set to false (case-insensitive) to disable Sentry initialization and MCP instrumentation. Unset, empty, true, or any other value allows telemetry (DSN is fixed in the package). |
| SENTRY_ENVIRONMENT | Optional. Sets the Sentry environment tag (e.g. development). If unset, Sentry uses its default. |
Telemetry content: When enabled, Sentry records errors and request timing for MCP operations. Tool arguments and outputs are not attached to spans (including no Apex debug log text). Outgoing HTTP breadcrumbs are disabled so Salesforce URLs (e.g. anonymous Apex query strings) are not captured in Sentry.
Tools exposed
Every tool accepts an optional target_org argument (org alias or username) to override the org for that call.
The server obtains an access token and instance URL via sf org display (not via REST). Data calls use the Salesforce REST API at /services/data/{version}/ unless noted. SOQL is issued with GET .../query?q=.... SObject create/update/delete use POST / PATCH / DELETE on sobjects/{Type} or sobjects/{Type}/{id}.
| Tool | What it does |
|------|----------------|
| run_anonymous_apex | Runs anonymous Apex in the org. Default: Tooling REST GET .../tooling/executeAnonymous/?anonymousBody=... — returns compile/run status and duration (no debug log). show_output: true: Apex SOAP executeAnonymous with DebuggingHeader so a debug log is returned; optional log_line_filters (e.g. USER_DEBUG) keeps only lines matching those log categories. |
| list_permission_sets | SOQL query on PermissionSet (custom sets only). Optional nameFilter narrows results client-side. |
| get_permission_set | SOQL query on PermissionSet by Id or Name (nameOrId). |
| create_permission_set | REST POST sobjects/PermissionSet with Name, Label, and optional fields. |
| update_permission_set | REST PATCH sobjects/PermissionSet/{id}. |
| list_user_permission_sets | SOQL on PermissionSetAssignment (with PermissionSet fields) where AssigneeId = user. |
| list_permission_set_users | SOQL on PermissionSetAssignment (with user fields) filtered by permissionSetId or permissionSetName. |
| assign_permission_set | REST POST sobjects/PermissionSetAssignment (user Id + permission set Id). |
| remove_permission_set_assignment | REST DELETE sobjects/PermissionSetAssignment/{assignmentId}. |
| list_object_permissions | SOQL on ObjectPermissions where ParentId = permission set Id. |
| upsert_object_permission | SOQL to see if a row exists for that permission set + object; then REST PATCH sobjects/ObjectPermissions/{id} or POST sobjects/ObjectPermissions. |
| remove_object_permission | REST DELETE sobjects/ObjectPermissions/{objectPermissionId}. |
| list_field_permissions | SOQL on FieldPermissions for the permission set; optional filter by object API name. |
| upsert_field_permission | SOQL lookup by permission set + field; then REST PATCH or POST sobjects/FieldPermissions. |
| remove_field_permission | REST DELETE sobjects/FieldPermissions/{fieldPermissionId}. |
| list_objects | REST GET sobjects/ (global describe). Optional filter is applied client-side to names/labels. |
| list_object_fields | REST GET sobjects/{objectName}/describe/ (per-object describe). Optional filter is applied client-side. |
| object_access_check | Read-only. SOQL on ObjectPermissions (with parent PermissionSet / Profile). mode: grants — lists permission sets (including profile-backed PermissionSet rows) matching object_type and optional CRUD / view-all / modify-all flags (if none are set, read is required). mode: users — users who receive that access via profile (User.ProfileId) or permission set assignment; optional for_user_id narrows to one user; optional max_users (default 200) caps listing. |
| field_access_check | Read-only. SOQL on FieldPermissions for field (Object.Field). mode: grants / mode: users same idea as object_access_check. If neither require_read nor require_edit is set, read is required. Only explicit FieldPermissions rows are considered (no inference from object permissions or missing FLS rows). |
Limits: Access delivered only through Permission Set Groups is not evaluated (no PermissionSetGroup / PermissionSetGroupAssignment logic). SOQL queries use a 2000-row cap on permission rows; user listing respects max_users. The REST client does not page nextRecordsUrl for arbitrary queries.
Development
From a clone of the repository:
npm install
npm run build # tsc → dist/ (prepack runs this before pack; npm publish also runs prepublishOnly: build + Sentry source map upload)
npm run dev # run TypeScript directly (tsx)
npm run start # node dist/index.js
npm test
npm run lint # tsc --noEmitPublishing: ensure you are logged in to npm with access to @wisefoxme, and set SENTRY_AUTH_TOKEN. Prefer an Organization token (Settings → Developer Settings → Organization Auth Tokens on de.sentry.io) — it is meant for CI / sentry-cli and has the right permissions by default. If you use a Personal or Internal integration token instead, enable at least project:releases and org:read (Sentry permissions; sentry-cli release uploads need both). The upload script always uses the EU API host https://de.sentry.io (do not put SENTRY_URL=https://sentry.io in .env for uploads — that targets the US cloud and tends to cause 403). To override the API host only, set SENTRY_UPLOAD_URL. Optional: SENTRY_ORG, SENTRY_PROJECT if your Sentry org slug or project slug differs from wisefox / salesforce-copilot-admin-mcp. Run npm publish from the repo root (prepublishOnly builds and uploads source maps first). To verify the tarball before publishing: npm pack --dry-run and confirm dist/index.js appears (source .map files are not published to npm; they are uploaded to Sentry only). The Sentry release is package.json name + @ + version, with / in the npm name replaced by - (Sentry does not allow slashes in release ids); this matches what the SDK sends at runtime. If stack traces in Sentry do not map to TypeScript, confirm the release matches the version you published.
Dependencies include fast-xml-parser for parsing Apex SOAP responses when run_anonymous_apex is called with show_output: true.
