Un servidor Model Context Protocol que le da a Claude acceso scopeado y consciente de auditoría a tu org de Salesforce. Lecturas de objeto, un endpoint SOQL solo-SELECT, tres helpers de RevOps (pipeline_by_stage, stale_opps, at_risk_commits), más dos escrituras que siempre pasan por un pipeline de justificación-y-auditoría. Méteselo a Claude Desktop o Claude Code y tu equipo puede preguntar “muéstrame las opps en stage Commit sin actividad esta semana” y “actualiza la close date en la opp 0061a, justificación: empujada por el cliente” sin salir del chat — y sin entregarle al modelo un botón de delete. El scaffold completo vive en el bundle de artefacto en apps/web/public/artifacts/mcp-server-salesforce-revops/, que entrega un README.md, pyproject.toml, y src/salesforce_revops_mcp/server.py listos para instalar con pip install -e ..
Cuándo usar esto
Recurre a esto cuando tu trabajo de RevOps y forecasting tiene un ritmo semanal claro — pipeline review, limpieza de higiene de stage, inspección de deals, correcciones de campos individuales — y el costo de context-switch a Salesforce por cada pregunta domina al costo de escribir el SOQL o encontrar el reporte correcto. El patrón funciona particularmente bien para dos roles. El lead de RevOps que solía vivir en una pestaña de browser con búsquedas guardadas ahora le pregunta a Claude en lenguaje natural, recibe una respuesta estructurada, y pega el resultado en un doc de forecast. El GTM engineer que solía escribir un bloque Apex anónimo one-off para empujar un par de campos viejos ahora le pide a Claude que llame a update_field con una justificación que aterriza como una fila en un objeto de auditoría custom, con los valores old y new preservados para el siguiente ciclo de auditoría.
También es el patrón correcto si ya lanzaste una versión de este workflow en HubSpot (la estructura del bundle del artefacto espeja el piloto del servidor CS de HubSpot) y quieres simetría entre sistemas-de-registro para que los prompts de Claude de tu equipo sean portables. Misma forma de tools, mismo estilo de respuesta, misma postura de auditoría.
Cuándo NO usar esto
Sáltatelo si cualquiera de los siguientes es cierto:
Tu org no ha acordado una policy de auditoría para escrituras dirigidas por AI. El scaffold hace que la auditoría sea barata; no la hace opcional. Si “quién cambió este campo y por qué” no es una conversación que Security ha tenido, lanza el subset solo-lectura (omite add_note y update_field de la lista de tools) y revísalo cuando aterrice la policy.
Necesitas DML masivo. Este servidor capea-fuerte las lecturas a 200 registros por request y expone solo escrituras de un registro. Updates masivos de miles de filas pertenecen a un job de Data Loader o un batch Apex apropiadamente gobernado — no a una herramienta de chat. El cap es un feature: detiene a Claude de “ayudosamente” reescribir la mitad de tu pipeline porque malinterpretó una pregunta.
Tu modelo de forecasting vive en una herramienta de terceros (Clari, BoostUp, Gong Forecast). Los hechos interesantes ya no están en Salesforce. Claude consultando el SoR devolverá rebanadas viejas de verdad y confundirá en vez de ayudar. Apunta a Claude a la API de la herramienta de forecasting en su lugar, o espera hasta que esa herramienta lance su propio MCP.
Solo tienes una o dos preguntas de pipeline-review por semana. El valor amortizado está por debajo del costo de setup y de tokens ongoing. Quédate con reportes guardados.
El régimen de compliance prohíbe el acceso del LLM a PII. Las industrias reguladas (salud, finanzas) frecuentemente prohíben empujar registros de clientes a un LLM de terceros, full stop. Esta es una pregunta de policy, no de arquitectura.
Setup
El README.md del bundle es la fuente de verdad; los pasos abajo son la orientación. Tiempo total hasta tener una tool registrada funcionando: cerca de noventa minutos si tu Connected App y objeto Cleanup_Audit__c ya existen, dos a tres horas si necesitas construirlos.
Instala el paquete. Clona el bundle, python -m venv .venv, activa, pip install -e .. Las dependencias son mcp>=1.2.0, httpx, pydantic, y simple-salesforce (mantenida disponible para el TODO de refresh-token).
Crea un Connected App en Salesforce Setup. Habilita OAuth, scopes api y refresh_token, offline_access, callback URL http://localhost:1717/callback (o donde sea que viva tu helper de OAuth). Espera los diez minutos mandados por Salesforce para propagación. Copia el Consumer Key y Consumer Secret.
Acuña un access token. Este scaffold lee SFDC_ACCESS_TOKEN directamente del entorno y documenta el flujo de refresh-token como un TODO; para desarrollo el camino fácil es sfdx auth:web:login seguido de sfdx force:org:display --verbose. Para producción, envuelve el servidor en un sidecar que maneje refresh y escriba el token actual al env.
Crea el objeto custom Cleanup_Audit__c. Fields: Object_Name__c (text), Record_Id__c (text 18), Field_Name__c (text), Old_Value__c (long text), New_Value__c (long text), Justification__c (long text), Performed_By__c (text). Otorga CRUD al integration user en este objeto.
Define env vars y registra con Claude Desktop.SFDC_INSTANCE_URL, SFDC_ACCESS_TOKEN, SFDC_API_VERSION (default v60.0), SFDC_AUDIT_OBJECT (default Cleanup_Audit__c), SFDC_COMMIT_STAGE_NAME (default Commit). Agrega el bloque JSON del README a claude_desktop_config.json. Reinicia Claude Desktop.
Sanity check. Pídele a Claude “Muéstrame pipeline by stage para los próximos noventa días” y compáralo contra el reporte de Pipeline equivalente en Salesforce. Después corre update_field contra una opportunity en sandbox con una justificación real y verifica que una fila de Cleanup_Audit__c fue escrita antes de que el campo cambiara.
Qué expone
Diez tools, agrupadas por intención para que Claude (y tú) puedan razonar sobre cuál usar.
Lecturas de objeto:get_account, get_opportunity, get_contact, get_lead. Campos estándar más owner donde sea relevante.
SOQL:query(soql, bypass_sharing=False). Solo un SELECT. Auto-inyecta WITH SECURITY_ENFORCED si te olvidaste; auto-capea LIMIT 200 si te olvidaste también de eso. Rechaza cualquier string que contenga INSERT, UPDATE, DELETE, UPSERT, MERGE, FIND, o EXEC. bypass_sharing=True raisea hoy, reservado para una futura integración con Tooling-API.
Helpers de RevOps:pipeline_by_stage(close_date_window_days, owner_id?), stale_opps(days_in_stage_threshold), at_risk_commits(quarter_end_date). Cada uno compone un string SOQL parametrizado, lo pasa por el mismo validador harden_soql, y devuelve datos agregados o a nivel de fila dependiendo de la intención.
Escrituras audit-aware:add_note(object_type, object_id, body) crea un ContentNote y lo linkea vía ContentDocumentLink. update_field(object_type, object_id, field_name, new_value, justification) requiere una justificación ≥ 10 caracteres, escribe una fila de Cleanup_Audit__c con el valor old antes del cambio, y después realiza el PATCH de campo único. Si el insert de auditoría falla, el update del campo nunca corre.
No hay tool de delete_*, no hay DML masivo, no hay shortcut de transición de stage, no hay merge, no hay convert. Si quieres que el workflow haga esas cosas, escribes una tool separada, nombrada, con su propia historia de auditoría. El principio: cada acción irreversible se gana su propio botón, nunca un comando de texto libre.
Postura de ingeniería
El scaffold hace unas pocas decisiones opinadas que vale la pena entender antes de adoptarlo.
Whitelist de SOQL por construcción, no regex. La tool query rechaza cualquier cosa que no empiece con SELECT, después rechaza cualquier match palabra-completa contra una keyword de DML o SOSL. SOQL en sí es solo-lectura — no hay UPDATE Opportunity SET … en el lenguaje — pero el rechazo explícito hace la intención ruidosa y atrapa el caso donde alguien intenta meter apex anónimo a través de la tool.
WITH SECURITY_ENFORCED es mandatorio. El endpoint REST /query de Salesforce salta silenciosamente la field-level security a menos que la pidas. El scaffold inyecta la cláusula si te olvidaste, así que un usuario sin read en un campo recibe un error claro de INSUFFICIENT_ACCESS en vez de una respuesta que silenciosamente omite la columna.
El cap de LIMIT es estructural. Cada helper compone un string SOQL con un LIMIT explícito; la tool query inyecta LIMIT 200 si falta. Las lecturas masivas que excedan doscientos registros pertenecen a la Bulk API, no acá. Esto a la vez mantiene los payloads de respuesta tratables para el modelo y hace predecible la cuota diaria de API.
Justificación obligatoria en escrituras.update_field requiere una justification de al menos diez caracteres y escribe la fila de auditoría antes de tocar el campo. El orden audit-first significa que un update fallido deja un registro de intención sin un registro de acción; la alternativa — update primero, audit segundo — deja cambios sin razón documentada si la escritura de auditoría falla. Reconcilia las filas de intención-fallida semanalmente.
Sin tools de delete, nunca. Los deletes se exponen solo a través de la propia UI de Salesforce y Data Loader, que ya tienen guardrails a nivel-de-organización. Agregar una tool de delete_* acá ruteara alrededor de esos guardrails por un ahorro de tiempo marginal. Vale menos que el blast radius.
Realidad de costo
Tres líneas de costo. Ninguna es enorme en aislamiento; juntas son reales.
Suscripción a Claude. Lo que sea que tu equipo ya paga por Claude Desktop o Claude Code (Pro a $20/usuario/mes, tiers Max $100-200/usuario/mes, o consumo de API). El servidor MCP en sí no cambia esto.
Self-host del servidor. El scaffold corre como un proceso Python local por usuario de Claude Desktop. Costo cero de infra en un laptop de desarrollador. Si lo envuelves como un servicio compartido (FastAPI delante de la misma lógica de dispatch) para que no-desarrolladores puedan usarlo, presupuesta una VM chica — $20-50/mes en cualquier cloud, menos si ya tienes un cluster de Kubernetes.
Cuota de API de Salesforce. El default es 15.000 calls de API por 24 horas por org Enterprise, más asignaciones por-usuario encima. Un lead de RevOps típico tirando del pipeline una vez por día e inspeccionando veinte deals por semana consume tal vez 200-300 calls/día. Pipeline review masivo entre el equipo puede picar a 1.000-2.000 calls/día. Cómodo hasta el día que no lo es — el cap de 200-registros de los helpers existe en parte para mantener la cuota predecible.
El costo de tokens del lado de Claude está dominado por los payloads de respuesta, no los prompts. Un pull de 200-registros de opportunity a tal vez 600 tokens por registro son ~120K tokens por call; al precio de Claude 3.5 Sonnet eso es alrededor de $0.36/call en input. Tres a cinco calls así por sesión de pipeline-review por lead de RevOps por semana, y estás mirando dólares de un dígito/usuario/mes encima de la suscripción. Redondea generosamente y llámalo $20/usuario/mes all-in.
Cómo se ve el éxito
Una señal medible un mes después del rollout: time-to-answer en preguntas semanales de pipeline-review cae de “cambia pestañas, abre el reporte, filtra, exporta” (digamos cinco minutos) a “pregúntale a Claude, lee la respuesta” (bajo treinta segundos). Multiplica por cuantas preguntas así pregunta tu equipo por semana. La señal más difícil-de-medir pero más-load-bearing: el equipo deja de mantener un backlog paralelo de “preguntas para hacerle a la persona de datos” porque responderlas ahora es barato.
Una segunda señal: la tabla Cleanup_Audit__c se llena con filas que parecen trabajo de cleanup real — correcciones de close-date, reasignaciones de owner, correcciones de stage — cada una con una justificación legible-por-humano del largo de una oración. Si esa tabla está vacía después de un mes, o nadie está usando las tools de escritura (bien — el valor solo-lectura por sí solo es real) o el requerimiento de justificación está siendo rodeado (no bien — investiga).
Versus las alternativas
Salesforce Einstein / Agentforce. First-party, se integra nativamente con la plataforma, sin proceso separado para hostear. Trade-off: el pricing es por-usuario-por-mes y empinado ($30-50/usuario/mes para los add-ons de Einstein; el pricing conversation-based de Agentforce varía), y la UX conversacional vive en Salesforce — tu equipo tiene que estar en Salesforce para usarla. El patrón del servidor MCP mantiene a Claude como la superficie de chat universal entre todos tus sistemas-de-registro. Elige Einstein si tu equipo vive en Salesforce; elige este servidor si vive en Claude.
Endpoints custom de Apex / REST alimentando un chatbot. Control máximo. También carga de mantenimiento máxima y sin historia de tool-discovery built-in. Tú construyes el JSON Schema para cada tool a mano, tú construyes el dispatch, tú construyes el sidecar de auth. El servidor MCP te da todo eso en ~400 líneas.
Dashboard de Tableau o CRM Analytics. Forma diferente de herramienta. Los dashboards sobresalen en el problema “quiero ver las mismas cinco vistas cada lunes”; este MCP sobresale en el problema “quiero hacer una pregunta para la que no he pre-construido una vista”. Son complementos, no alternativas.
Statu quo (reportes guardados + SOQL manual en la consola de desarrollador). Gratis. Lento. Envejece mal cuando la persona que escribió los reportes guardados se va. El servidor MCP le gana en time-to-answer y le gana más a medida que tu biblioteca de helper tools crece.
Watch-outs
El README los documenta en completo; la versión corta:
Disciplina de scope del Connected App. El token de OAuth puede leer todo lo que el usuario corriendo puede leer. Crea un integration user dedicado con un profile estrecho, revísalo trimestralmente. Guarda: fecha de revisión del profile del integration user escrita en el objeto de auditoría como una fila Performed_By__c=policy-review.
Estallido de governor-limit en lecturas masivas. El cap de 200-registros protege la cuota diaria de API, pero un query desconsiderado sobre una tabla de Lead de 500K-filas todavía puede masticar una porción de la cuota en unos minutos. Guarda: harden_soql inyecta LIMIT 200 incondicionalmente; enseña al equipo a usar los helpers, no SOQL crudo, para trabajo de rutina.
Riesgo de bypass de FLS. El REST /query no enforza field-level security por default. Guarda: el scaffold agrega WITH SECURITY_ENFORCED a cada query que la omite. Deshabilita esto solo con un cambio explícito y justificado a harden_soql.
Gap de audit-log en escrituras. Si el update del campo falla después de que la fila de auditoría fue escrita, tienes una intención registrada sin un cambio real. Guarda: la fila de auditoría se queda en su lugar; reconcilia semanalmente. Agrega Failed__c al objeto de auditoría (TODO #6 en el README) para marcar estos explícitamente.
Falla de refresh del token OAuth. Los tokens long-lived expiran y un 401 un viernes a las 4pm es el peor modo de falla. Guarda: ponle un refresh sidecar delante al servidor; falla-fuerte en 401, no retries silenciosamente.
Stack
Salesforce — sistema de registro
MCP Python SDK — el paquete mcp>=1.2.0; provee Server, stdio_server, y los decoradores de tool-registry
httpx — cliente REST async
simple-salesforce — mantenida disponible para el TODO de refresh-token (el scaffold en sí usa httpx crudo)
Claude Desktop o Claude Code — interfaz de lenguaje natural, tool caller
Cleanup_Audit__c — tu objeto custom de auditoría, el canario que prueba que las escrituras están documentadas
# mcp-server-salesforce-revops
An MCP server tuned for revenue-operations teams using Salesforce. Exposes account, opportunity, contact, and lead reads, a SELECT-only SOQL endpoint, three RevOps helpers (`pipeline_by_stage`, `stale_opps`, `at_risk_commits`), and two audit-aware light writes (`add_note`, `update_field`). Designed to make Claude useful for "what's actually in the forecast" conversations without handing it the keys to delete or bulk-mutate the org.
> **STATUS: scaffold — not runtime-tested.** The code below is structurally complete and follows the official `mcp` Python SDK conventions, but it has not been executed against a live Salesforce org. Treat it as a starting point you adapt to your org's object model, picklist values, and custom audit object name. Stage labels, owner role hierarchy, and the audit object schema vary by org.
## What it exposes
### Object reads (read-only)
- `get_account(account_id)` — full Account fields
- `get_opportunity(opp_id)` — full Opportunity fields + owner
- `get_contact(contact_id)` — full Contact fields
- `get_lead(lead_id)` — full Lead fields
### SOQL (read-only)
- `query(soql, bypass_sharing=False)` — runs the SOQL string against the REST `/query` endpoint. **Refuses any statement that is not a single `SELECT`.** Any `INSERT`, `UPDATE`, `DELETE`, `UPSERT`, `MERGE`, or DML keyword raises before a request is made. `bypass_sharing` defaults to `False`; when `True`, the helper appends nothing — sharing rules apply via the running user. The flag is reserved for future tooling-API integration and is rejected today with a clear error.
### RevOps helpers (read-only)
- `pipeline_by_stage(close_date_window_days=90, owner_id?)` — open opportunities closing in the window, aggregated by stage. Optionally filtered to one owner.
- `stale_opps(days_in_stage_threshold=30)` — open opportunities whose `LastStageChangeDate` is older than the threshold.
- `at_risk_commits(quarter_end_date)` — Commit-stage opportunities whose `LastActivityDate` is more than 14 days ago **or** whose `CloseDate` is within 14 days of `quarter_end_date` and have no future-dated `Event` on the account.
### Audit-aware light writes
- `add_note(object_type, object_id, body)` — creates a `ContentNote` and links it via `ContentDocumentLink` to the parent record.
- `update_field(object_type, object_id, field_name, new_value, justification)` — single-field update with **mandatory `justification` parameter** (rejected if blank or shorter than 10 chars). Writes a `Cleanup_Audit__c` row first (object name, record id, field, old value, new value, justification, who, when), then performs the field update. If the audit insert fails, the update never runs.
The server **does not** expose `delete_*` tools, bulk DML, or stage-transition shortcuts. All bulk reads cap at 200 records per request to stay inside REST's per-call envelope and to give callers a predictable token budget.
## Setup
### 1. Install
```bash
git clone <wherever you put this>
cd mcp-server-salesforce-revops
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -e .
```
### 2. Create a Salesforce Connected App (OAuth)
In Salesforce Setup: App Manager → New Connected App.
- **API (Enable OAuth Settings):** check.
- **Callback URL:** `http://localhost:1717/callback` (or any URL your OAuth helper accepts — the token is what we keep, not the redirect).
- **Scopes:** `api`, `refresh_token, offline_access`. Add `chatter_api` only if you also want to post Chatter feed items (out of scope for this scaffold).
- **Require Secret for Refresh Token Flow:** check.
- Save, wait 10 minutes for propagation, copy the **Consumer Key** and **Consumer Secret**.
Then run a one-time OAuth web-server flow to obtain a refresh token. The scaffold expects you to bring your own access token (refresh handling is on the TODO list, see below). For local development you can use the `sfdx auth:web:login` CLI to mint one and `sfdx force:org:display --verbose` to read it out.
**Auth choice for this scaffold.** We read a long-lived `SFDC_ACCESS_TOKEN` from env. Refresh-token rotation is documented as a TODO rather than implemented, because every team's secret-store choice (Vault, AWS Secrets Manager, 1Password, plain env) differs. The fields are split out so swapping in a refresh-flow client is a one-file change in `server.py`.
### 3. Configure environment
```bash
export SFDC_INSTANCE_URL="https://yourdomain.my.salesforce.com"
export SFDC_ACCESS_TOKEN="00D...!ARQAQ..."
export SFDC_API_VERSION="v60.0" # optional, default v60.0
export SFDC_AUDIT_OBJECT="Cleanup_Audit__c" # custom object for write audits
export SFDC_COMMIT_STAGE_NAME="Commit" # picklist label for commit-stage opps
```
The `Cleanup_Audit__c` object must exist with at least these custom fields: `Object_Name__c` (text), `Record_Id__c` (text 18), `Field_Name__c` (text), `Old_Value__c` (long text), `New_Value__c` (long text), `Justification__c` (long text), `Performed_By__c` (text). If you use a different object name, set `SFDC_AUDIT_OBJECT` accordingly.
### 4. Register with Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"salesforce-revops": {
"command": "python",
"args": ["-m", "salesforce_revops_mcp.server"],
"env": {
"SFDC_INSTANCE_URL": "https://yourdomain.my.salesforce.com",
"SFDC_ACCESS_TOKEN": "00D...!ARQAQ...",
"SFDC_API_VERSION": "v60.0",
"SFDC_AUDIT_OBJECT": "Cleanup_Audit__c",
"SFDC_COMMIT_STAGE_NAME": "Commit"
}
}
}
}
```
Restart Claude Desktop. You should see ~10 tools registered under `salesforce-revops`.
### 5. Sanity-check
Ask Claude: "Show me pipeline by stage for the next ninety days." Compare the per-stage totals against the equivalent Pipeline report in Salesforce. Then run `update_field` against a sandbox opportunity with a short justification and confirm both the field changed **and** a `Cleanup_Audit__c` row was written.
## Watch-outs
- **Connected App scope discipline.** A Connected App with `api` scope can read every object the running user can read. Create a dedicated integration user with a profile that exposes only the objects this server needs (Account, Opportunity, Contact, Lead, the audit object), and assign field-level permissions narrowly. Document this with your security team. **Guard:** integration-user profile reviewed quarterly; record the review date in the audit object.
- **Governor limits on bulk reads.** A naive `query("SELECT Id FROM Opportunity")` against a 500K-row org will paginate forever and exhaust your daily API quota. **Guard:** every helper caps `LIMIT` at 200; the `query` tool also injects `LIMIT 200` if the SOQL has none.
- **FLS bypass risk.** Salesforce's REST `/query` endpoint does **not** enforce field-level security unless you explicitly ask for `WITH SECURITY_ENFORCED`. **Guard:** the `query` tool appends `WITH SECURITY_ENFORCED` when missing, so a user without read on a field gets a clear error rather than silent data leakage.
- **Audit log gap on writes.** If the audit insert succeeds but the field update fails (or vice versa), you have a recorded change with no actual change (or a change with no record). **Guard:** the scaffold writes the audit row first; if the field update raises, the audit row is left in place tagged as `Failed__c=true` (you will need to add this field if you want to use the flag — TODO listed below). Reconcile via a weekly report.
- **OAuth token refresh failure.** Long-lived access tokens expire; a 401 on a Friday afternoon is the worst time to discover this. **Guard:** front the server with a token-refresh sidecar (or implement the refresh flow per the TODO). Fail loud on 401, do not silently retry.
## Limits and TODOs (before production use)
- [ ] Implement OAuth refresh-token flow so the server self-heals on 401, instead of crashing.
- [ ] Add request-level retries with exponential backoff (Salesforce returns 503 under maintenance windows).
- [ ] Write integration tests against a Salesforce sandbox org (Trailhead Playground works).
- [ ] Add structured logging via `python-json-logger`; emit one JSON line per tool call with name, arguments hash, duration, status.
- [ ] Wire optional Sentry / OpenTelemetry export.
- [ ] Add `Failed__c` boolean to `Cleanup_Audit__c` and flip it true if the post-audit field update raises.
- [ ] Validate `SFDC_AUDIT_OBJECT` and `SFDC_COMMIT_STAGE_NAME` against the org on first run; fail loud if either does not exist.
- [ ] Replace the long-lived token env var with a secret-store lookup (Vault, AWS Secrets Manager, 1Password CLI).
- [ ] Add a per-tool `--dry-run` flag that returns the SOQL/DML payload without executing.