Creating MCP servers & client apps

Pinion ships the heavy pieces of a quick start: the tools (Pinion_*), a packaged External Client App (ECA) to connect through, and the permission set that gates who may connect. What Pinion can’t ship is the MCP server itself — Salesforce forbids packaging an McpServerDefinition — so even the quick start’s single all-tools server is something you create in Setup. The good news: because the tools and ECA are already there, it’s a few clicks — create the server, attach all the tools, activate — then connect your assistant and you’re answering questions about deals in minutes. AI Assistant Access walks that path end to end.

This page is for the step after the quick start: building your own MCP servers and client apps so different groups reach different tools. It’s the detailed, click-by-click companion to Composing MCP Servers, which covers what to put in a server and why; here we cover how to stand one up, and when it’s worth doing.

Three pieces, three jobs

An MCP deployment is three separate artifacts, each with a distinct job — and you design them together, as a matched set, not one all-encompassing bundle you inherit:

  • The MCP server (McpServerDefinition) defines functionwhich tools a connection can call.
  • The External Client App (ECA) defines accesswho may open the connection; its OAuth policy names the permission set(s) allowed to sign in.
  • The permission set is the eligibility gate — the key you assign to those people.

There is no “all-tools ECA”: an ECA carries no tools at all — tools live on the server. So you scope by building a server, its ECA, and its gating permission set together, targeted to your own business requirements. Pinion ships a convenience ECA and the packaged tools so your first connection is fast; the composition — how many servers, which tools on each, who reaches them — is yours to design.

What’s packaged and what you build

It helps to be precise about which pieces ship in the package and which you create in Setup, because they live in different places:

PieceShips in the package?Where it lives
The tools themselves (Pinion_* — Explain Price, Decide Approval, …)Yes — packaged ApexInvoked by any MCP server you attach them to
The quick-start ECA (PinionMcp)YesSetup → External Client App Manager
The connection-grant permission set (PinionMcpUser)YesSetup → Permission Sets
An McpServerDefinition (the server)No — you create itSetup → MCP Servers
Any additional ECA you make to gate a targeted serverNo — you create itSetup → External Client App Manager

The one that surprises people: MCP server definitions cannot be packaged. Salesforce doesn’t allow an McpServerDefinition to travel inside a managed package, so creating the server is always a manual Setup step — even for the quick start. Pinion packages the tools so that attaching them is a menu pick, but the server itself is yours to define.

Create an MCP server

  1. Setup → Quick Find → MCP Servers (listed under API Catalog).
  2. New (or New MCP Server). Give it a clear developer name that says what it’s for — e.g. Pinion_Insights, Pinion_Operate — not a generic MCP1. The name is what you’ll recognize when you wire an ECA to it later.
  3. Add its tools. Attach only the Pinion_* tools this server should expose. The tool a connection cannot see is the tool it cannot call — this list is the access boundary, so choose it deliberately. (For a read-only server, attach only the read tools; for an actioning server, add the write tools that group needs. The tool catalog maps every tool to its nature and the persona it supports.)
  4. Activate the server and confirm its tool list renders exactly as you intended.

Repeat for each server you want — one per trust tier or per persona (see best practices).

Create an External Client App

An AI client signs in to your org through an External Client App. The quick-start PinionMcp ECA is already built; you create an additional ECA when you want to gate a targeted server to a narrower set of people than the quick start’s audience (see the topology below for why a new server usually wants its own ECA).

The ECA is created manually in the Setup UI — it is not automatable and does not ship in the package.

  1. Setup → Quick Find → External ClientExternal Client App ManagerNew External Client App.

  2. Fill in Basic Information (name, contact email).

  3. Expand API (Enable OAuth Settings) and check Enable OAuth.

  4. Callback URL — your AI tool’s MCP-connector redirect URL. Each tool publishes its own; for Claude it is https://claude.ai/api/mcp/auth_callback. Check your client’s docs for the exact value.

  5. OAuth Scopes — grant only these two:

    • Access MCP servers (mcp_api)
    • Perform requests at any time (refresh_token)

    Do not add api, full, web, or any other scope. Least privilege starts here.

  6. Security settings — turn on Issue JSON Web Token (JWT)-based access tokens for named users and Require Proof Key for Code Exchange (PKCE). Leave the rest off.

After saving, copy the Consumer Key (the Client ID you paste into the AI tool). Note that a new ECA can take up to ~30 minutes to become operational — similar to DNS propagation — so a fresh app that “doesn’t work yet” may just need time.

Gate the app to a permission set

This is the control that decides who may connect at all:

  1. On the ECA, open Policies → OAuth Policies.
  2. Set Permitted Users to Admin approved users are pre-authorized.
  3. In the policy’s permission-set list, add the permission set(s) whose holders are allowed to connect through this app.
  4. Assign that permission set only to the people who should connect.

With Admin approved users are pre-authorized, only users who hold a named permission set can complete the OAuth flow — everyone else is refused at sign-in. That’s how “who can connect” becomes a permission-set assignment you already know how to manage.

One ECA per server

Before you gate multiple targeted servers, understand how ECAs and servers relate. Salesforce’s Hosted MCP model ties each MCP server configuration to one External Client App — the connection an assistant opens is per server, authorized by that server’s ECA. The practical consequence:

  • To gate different servers to different permission sets, create one ECA per server, and set each ECA’s OAuth policy to the permission set(s) for that server’s audience.
  • The permission set on an ECA’s policy is a connection-eligibility gate for that server — it does not, by itself, decide what the tools do at runtime. (That’s the persona’s own CRUD/FLS — see the ceiling below.)

So the shape of a scoped-down deployment is: N servers, each with its own ECA, each ECA’s policy naming the persona permission set(s) that server is for.

When to scope down from the all-tools server

The quick-start server is deliberately broad — it’s the fastest way to get value, and for a small, trusted, high-context group (a couple of admins and a power user or two) it may be all you ever need. You scope down when your audience grows or its trust varies. Here’s the reasoning, so you can decide for your own org.

Least privilege limits the blast radius

A connection can only ever call the tools on its server. An all-tools server maximizes what any connected user — or a prompt-injected model reading some record’s text — can reach. A targeted server shrinks that surface to exactly the tools its audience needs: a read-only server literally has no write for anything to reach, so it’s safe to grant broadly; an actioning server stays small and high-trust. Scoping the server is how a model mistake stays proportional to the trust you placed in that connection.

Runs-as-user is a ceiling, not a floor

Every tool runs as the signed-in user and enforces that user’s CRUD and FLS. That’s a real safety cap — MCP can never let someone do more through the assistant than they could already do in the UI. But a ceiling is not a floor: runs-as-user alone gives you no way to grant less. If a user can decide approvals in the UI, an all-tools connection lets them decide approvals through the AI too. Scoping the server is the only lever that says “read for everyone, action for a trusted few” — the runs-as-user guarantee can’t express that on its own.

Actioning for a trusted subset

Leadership often wants the actioning tools available to a narrower group than everyone who technically holds the underlying CRUD — an AI that can approve, renew, or amend is a bigger step than an AI that can answer questions, even when the same people could do all of it by hand. The pattern:

  1. Build a dedicated actioning server with only the write tools that subset needs.
  2. Give it its own ECA, and gate that ECA’s OAuth policy on a narrow, admin-created marker permission set — a connection-eligibility marker that grants no CRUD of its own. It exists only to say “this person may open the actioning connection.” The real authority still comes from the user’s persona at runtime (runs-as-user); the marker just decides who’s allowed to reach the actioning tools through AI.
  3. Assign that marker set to your trusted subset — a strict subset of the people who hold the underlying persona.

This is a pattern you build for your org, not a permission set Pinion ships. Pinion deliberately does not prescribe your trust tiers — you know who your trusted actioners are.

Best practices for your own servers

  • Compose by least privilege. The three-ring model in Composing MCP Servers — Insights (read-only), Operate (free-persona actions), Author (the tier-zero Configure Price tool) — is the recommended default shape. The persona-aligned alternative (one server per persona) works just as well; pick whichever matches how you think about your org.

  • Reuse your existing persona permission sets — don’t invent new ones. Gate each server’s ECA OAuth policy on the permission sets you already manage — PinionApprover, PinionUser, and the rest of the persona model. Because an OAuth policy can name several permission sets, a ring server can admit several personas at once. The only new permission set worth creating is the zero-authority marker for a trusted-actioner subset (above) — and only when you actually want actioning narrower than the persona itself.

  • Map each server to the persona(s) that gate it. A useful default:

    ServerGate its ECA onBecause
    Insights (read-only)Everyone who benefits from askingRead-only; nothing to protect beyond existing FLS
    Operate (free-persona actions)PinionApprover, RevOps Manager, Account Manager, Deal Desk, deal ownersThe decide/triage/lifecycle actions those personas already perform
    Author (Configure Price)PinionUser (paid seat), optionally a marker subsetThe one tier-zero, price-persisting tool — smallest, highest-trust grant
  • Keep Configure Price tightest. It’s the single tool that persists on the pricing save path — a mistake there can write a wrong price. Whatever else you do, don’t fold it into a broadly granted server.

  • Name servers for their purpose, and re-verify the tool list after any package upgrade (the re-activation gotcha above).

The through-line: Pinion provides the tools and the method; you own the design. The quick start gets you connected today; targeted servers let you match access to your org’s real trust tiers when you’re ready.