← All articles

GTM Engineering

MCP for RevOps: Giving Claude Governed Access to Your CRM

An MCP server is a door into your CRM for an AI. Build the door with a lock, a log, and a scope before you hand out the key. Here is the permission model.

· 13 min read

The pitch for an MCP server is that you can ask Claude “which enterprise deals slipped last quarter and why” and it queries your CRM and answers, no SOQL, no export, no analyst. That works. What the pitch skips is that you just gave a language model a connection to the system that runs your revenue, and the model will call whatever tool you expose with whatever arguments it decides on. If one of those tools is “update records” and the scope is “all objects,” a bad prompt or a confused chain rewrites production. The demo and the disaster are the same feature.

MCP, the Model Context Protocol, is a standard way to hand an AI a set of tools it can call: query this, read that, update the other. For RevOps that is enormous, because it turns the CRM from a thing you export into a thing you converse with. But a tool the model can call is a door into your data, and you do not hang a door without a lock, a log, and a limit on which rooms it opens. The teams that ship MCP safely design the permission model before they expose the first tool. That model is the whole job, and it is what this piece builds.

read-only
Where every MCP rollout should start
1 integration user
The scoped identity the server runs as
every call
Logged, replayable, attributable

Here is the build I run in place of “connect Claude to Salesforce.” Five rungs, bottom to top, each one a layer of governance that has to hold before the one above it lights up. This is the framework. Scroll it, then I will show you the tool schemas, the query guard, and the approval queue that make each rung real.

Read first, write lastThe governed MCP build
  1. L5Dangerous capabilities off the menuomit

    Bulk updates, deletes, and exports never become MCP tools. They stay in governed, human-triggered runs. The safest tool is the one you chose not to expose, and deciding what to leave off is as much of the job as scoping what is on.

  2. L4Writes as proposals to a queuequeue

    A write tool inserts to an approval queue and holds no update permission on real records. A human or a rule approves the batch before anything applies. The model proposes, the queue disposes, and the blast radius is a review list.

  3. L3Audit log on every callevery call

    Record tool, arguments, result size, and conversation for every call from day one. Live in read-only for weeks and read the log. It tells you what people ask, which write tools would matter, and whether any argument pattern looks wrong.

  4. L2Read tools with tight schemasread-only

    Every tool is a typed function: ID patterns, enums, limit caps, additionalProperties false. The model can only pass arguments you anticipated. Queries are parameterized and run WITH SECURITY_ENFORCED, never string-built from model output.

  5. L1Scoped integration user1 user

    The server acts as a dedicated integration user, never a human login or a system admin. A read-only profile with field-level security limited to the exact objects the tools touch. This identity, below the tool and below the prompt, is your real permission boundary, and it fails closed.

Locate your rollout on that ladder. Most teams that get burned skipped straight to L4 with an admin login and no log. Every rung below it is a wall a hostile or confused prompt hits before it reaches production. Build them in order and the demo and the disaster stop being the same feature.

The model calls the tools, you own the tools

The mental model that keeps you safe: you are not giving Claude your CRM. You are giving it a set of functions you wrote, each with a fixed shape, and the model can only do what those functions allow. The model does not have a Salesforce login. Your MCP server has the login, and the server decides what the model is allowed to ask for. Every guarantee lives in the server, not in the prompt, because the prompt is the one thing you do not control.

So the design question is never “what should Claude be able to do.” The real question is what tools you expose, what each one can touch, and who the server becomes when it makes the call. Get those three right and a hostile prompt hits a wall. Get them wrong and a helpful prompt does damage.

The MCP boundary Model to tools to a scoped integration user
Claudecalls toolstool callMCP servertool allow-listaudit logwrite approval gateacts asIntegration userread-only profileFLS on 2 objectsno delete, no export
The model never touches the CRM. It calls tools your server defines. The server runs as a scoped integration user with field-level permission, logs every call, and routes writes through an approval queue.

Start read-only, and stay there longer than you want to

The single highest-leverage decision is to launch with zero write tools. A read-only MCP server delivers most of the value, “answer questions about my CRM,” at a fraction of the risk, because the worst a read tool can do is show data to someone who should not see it, which your field-level security already governs. Writes are where the irreversible damage lives, and you do not need them to prove the thing works. Ship read-only, live in it for weeks, watch the audit log, and only then decide whether any write is worth building.

The rush to hand AI a write is worth resisting on the evidence too. Jason Lemkin’s 2025 SaaStr survey of AI agents in revenue teams found 83 percent of companies got nothing usable and only 3 percent got real revenue, and the pattern behind the 3 percent was a narrow, governed scope rather than a broad autonomous one. A read-only MCP server that answers pipeline questions reliably beats a write-enabled one that corrupts a forecast in month two. Start where the failure mode is “shows the wrong chart,” not “rewrites the quarter.”

The integration user is the second decision. The server acts as a dedicated integration user, never a human’s login and never a system admin. That user gets a read-only profile with field-level security scoped to the objects and fields the tools need, and nothing else. If a tool never reads Social Security numbers, the integration user cannot see them, so no prompt can extract them. Permission is enforced at the identity, below the tool, below the prompt, where the model has no reach.

The artifact: a tool definition with a scope

An MCP tool is a named function with a typed input schema. The scope lives in the schema and in the code behind it. Here is a read tool that answers “show me open opportunities for an account,” written so it physically cannot do anything else.

{
  "name": "get_open_opportunities",
  "description": "Return open opportunities for one account. Read-only.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "account_id": { "type": "string", "pattern": "^001[A-Za-z0-9]{15}$" },
      "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
    },
    "required": ["account_id"],
    "additionalProperties": false
  }
}

Three defenses are baked into that schema. The account_id pattern rejects anything that is not a real 18-character account ID, so the model cannot smuggle a SOQL fragment into the argument. The limit cap means no single call can pull the whole object. And additionalProperties: false means the model cannot pass a field you did not anticipate. The tool is a narrow slot, not an open query box.

Behind the schema, the query is parameterized and bounded, never string-built from model output:

// The tool builds the query. The model only supplies bound arguments.
List<Opportunity> getOpenOpps(Id accountId, Integer lim) {
    return [
        SELECT Id, Name, StageName, Amount, CloseDate
        FROM Opportunity
        WHERE AccountId = :accountId          // bound, never concatenated
          AND IsClosed = false
        WITH SECURITY_ENFORCED                // FLS honored, no bypass
        ORDER BY Amount DESC
        LIMIT :lim
    ];
}

WITH SECURITY_ENFORCED is the line that makes the integration user’s field-level security binding at query time: a field the user cannot see is not returned even if the SELECT names it. The model asks; the CRM decides what it is allowed to answer.

When you do add writes, add a queue, not a key

Eventually someone wants a write, “update the stage,” “log the call.” Do not give the write tool direct update permission. Route it through a proposal queue, the same seam I build for CRM agents with write access. The write tool does not update the record. It writes a proposed change to a queue, and a human or a rule approves the batch before anything applies. The model proposes, the queue disposes.

{
  "name": "propose_stage_update",
  "description": "Propose a stage change. Does NOT write. Queues for approval.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "opportunity_id": { "type": "string", "pattern": "^006[A-Za-z0-9]{15}$" },
      "new_stage": { "type": "string", "enum": ["Discovery","Qualified","Proposal","Negotiation","Closed Won","Closed Lost"] },
      "reason": { "type": "string", "minLength": 20 }
    },
    "required": ["opportunity_id", "new_stage", "reason"],
    "additionalProperties": false
  }
}

The enum on new_stage means the model cannot invent a stage. The minLength on reason forces an explanation into the audit trail. And the word “propose” is not decoration: this tool has no update permission at all, so even if the model calls it a thousand times, it fills a review queue, it does not touch a live record. That is the difference between a door with a lock and a door propped open.

The permission matrix

Every tool gets classified before it ships. This is the matrix I fill in for a rollout.

ToolAccessIntegration user canApproval needed
get_open_opportunitiesReadSELECT on Opp, FLS-scopedNone
search_accountsReadSELECT on Account, FLS-scopedNone
get_activity_historyReadSELECT on Task/EventNone
propose_stage_updateWrite-proposalINSERT to queue object onlyHuman approves batch
propose_field_updateWrite-proposalINSERT to queue object onlyHuman approves batch
bulk_updateNot exposedNothingNever build this as a tool

The bottom row is the point. Some capabilities do not get an MCP tool at all. A bulk update belongs in a governed data-loader run a human triggers, not in a function a model can call. Deciding what to leave off the menu is as important as scoping what is on it.

Read-only launch vs write-enabled launch

The two rollouts carry different risk, and the risk should decide the order.

Read-only launch Write-enabled launch
Worst-case damage Shows data FLS already governs Rewrites production records
Reversibility Nothing to reverse Only if you captured prior values
Value delivered Ask questions, get answers Same, plus queued actions
Integration user profile Read-only, FLS-scoped Read plus insert to a queue object only
Time to ship safely Days Weeks, after read-only proves out
Approval gate None needed Mandatory before any apply
Most of the value, almost none of the risk, sits on the left. Earn your way to the right after weeks of clean audit logs.

What the audit log is for

Every tool call gets logged: which tool, what arguments, what the integration user returned, and which conversation asked. The log earns its keep the day someone asks “why did the model return this account’s data” or “did any tool get called with an argument I did not expect,” and you can answer from a record instead of a shrug. Watch the log for weeks during the read-only phase and it tells you which write tools are even worth building, because it shows you what people ask the CRM to do.

MCP tool calls by type, first month read-only
The shape from a read-only rollout I ran: reads dominate a healthy one, and the tall read bars tell you which future write tools would earn their keep. Anomalous argument patterns show up here first.
View as table
ItemValue
Pipeline questions44%
Account lookups29%
Activity history17%
Forecast queries10%

Ship the first rung this week

The ladder is the shape of the finished system. You do not build all five rungs at once. You stand up L1 and L2 end to end, prove it on one question, and let the audit log earn the rungs above it. Here is the week-one cut.

Stand up L1 and L2 this week
  1. 1

    Create the integration user before any code

    A dedicated user, read-only profile, field-level security limited to the two or three objects your first tool reads and nothing else. Test that it fails closed: log in as that user and confirm it cannot see a field you did not scope. This is L1, and it is the boundary the prompt can never reach past.

  2. 2

    Ship exactly one read tool with a tight schema

    One tool, "get open opportunities for an account," with an ID pattern, a limit cap, and additionalProperties false. The query is parameterized and runs WITH SECURITY_ENFORCED. Resist the second tool until the first proves out.

  3. 3

    Turn on the audit log on call one

    Record tool, arguments, result size, and conversation for every call. You want the log running before the first real question, because the log from week one is what tells you which write tools, if any, are worth building later.

  4. 4

    Live in read-only and read the log for a month

    Ask questions, watch what the team asks, and watch for any argument pattern you did not expect. A month of clean logs is the evidence that earns L4, a write proposal queue. No log, no write.

  5. 5

    Write down what stays off the menu

    Before anyone asks, name the capabilities that will never be MCP tools: bulk update, delete, export. Put them in a governed, human-triggered run instead. Deciding the omissions up front is L5, and it is cheaper to write now than to walk back later.

MCP turns your CRM into something your team can talk to, and that is worth building. But you are handing an AI a door into the system that runs your revenue, and the door needs a lock, a log, and a limit before the key goes out. Start read-only, act as a scoped integration user, log everything, and route every write through a proposal queue. This week, stand up one integration user with a read-only profile and one read tool with a tight schema, and let a month of clean audit logs decide whether you ever build a write. Build the door before you hand out the key.

mcp ai governance

Keep reading

One email. Every week.

One email a week: a system I built or broke, with the config, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.

The newsletter opens soon.

Connect a provider in src/config.ts