IKI Salesforce Product MCP Server
Provides tools for creating and updating Salesforce Product2 records via the Salesforce REST API, with field-name sanitization and confirm-before-write dry-run previews.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@IKI Salesforce Product MCP ServerCreate a new Salesforce product named 'Basic Plan' with price $10, confirm the write."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
IKI Salesforce Product MCP Server
A private remote Model Context Protocol (MCP)
server that lets Claude create and update Salesforce Product2 records
via the REST API. Deployed on Vercel. Write-only, scoped to Product2 —
it does not expose SOQL or any other object. That's intentional: it's a
sibling to a separate read-only server (soql_query, account_intel), kept
apart so the token that can mutate CRM data is distinct from — and smaller in
blast radius than — the one that only reads it.
Endpoint:
https://salesforce-product-mcp.vercel.app/api/mcp(Streamable HTTP)Tools:
create_product2,update_product2,start_bulk_product2_job,get_bulk_product2_job_statusTransport: Streamable HTTP only (no SSE, no Redis)
Runtime: Node.js (not Edge)
Why this shape
Salesforce here is Professional Edition + the paid API add-on. Salesforce's
hosted MCP servers require Enterprise+, and the local DX MCP server isn't the
target, so this server calls the Salesforce REST API directly (v66.0).
Auth to Salesforce uses an External Client App (ECA) with the Client
Credentials flow — new Connected Apps are blocked as of Spring '26.
Related MCP server: salesforce-mcp-jsforce
Architecture — two auth layers
Claude → this server: a shared bearer token (
MCP_BEARER_TOKEN). This token can write CRM data, so treat it as more sensitive than a read-only token and use a different one than any sibling read-only server. Requests without a valid token get a401(never a500).This server → Salesforce: the OAuth 2.0 Client Credentials flow. The server POSTs to
${SF_LOGIN_URL}/services/oauth2/token, caches the returnedaccess_token+instance_urlat module scope, and reuses that token across warm invocations to stay within the Professional Edition API quota. A401mid-flight triggers a single refresh-and-retry.
Tools
Both tools share two guards:
Field-name sanitization (
lib/product2.ts) — every key infieldsmust match a plain Salesforce API-name pattern (leading letter, alphanumerics/underscores, optional__ccustom-field suffix).Id,attributes, and relationship paths (e.g.Owner.Name) are rejected outright, so a write can only ever set flat scalar fields on the target record — never traverse into a related object or override the identity fields Salesforce manages itself.Confirm-before-write — without
confirm: true, both tools return a dry-run preview ({ dryRun: true, action, fields, ... }) showing exactly what would be sent, without calling Salesforce. Onlyconfirm: trueperforms the actualPOST/PATCH.
create_product2(fields, confirm?)
Creates a Product2 record. fields must include a non-empty Name
(Salesforce rejects a Product2 insert without one).
update_product2(id, fields, confirm?)
Updates an existing Product2 record by Id.
⚠️ Before using either tool, the ECA's Client Credentials "Run As" user needs Create + Edit object permissions on
Product2(and field-level security on any custom fields you intend to write) — set this via a Permission Set assigned to that user in Salesforce Setup. Object-level CRUD is normally gated by the Run As user's permissions rather than the ECA's OAuth scope list, but verify this against Salesforce Setup / current Salesforce docs for your org's edition before relying on it — this is an area that shifts across Salesforce releases.
Bulk import (Bulk API 2.0)
create_product2/update_product2 write one record per REST call — fine for
ad-hoc edits, but not for a multi-thousand-row CSV import (the kind
previously done via dataloader.io). start_bulk_product2_job and
get_bulk_product2_job_status add that via Salesforce's
Bulk API 2.0,
which handles orders of magnitude more data far more efficiently than looping
single-record calls (and without burning through the Professional Edition API
add-on's daily quota).
The actual CSV bytes never pass through an MCP tool call. A real import file (several thousand rows) is roughly 200k+ tokens of raw text — far too much to embed in a tool-call argument: slow, expensive, and it risks transcription errors as an LLM copies that much data through its own context. Instead:
Call
start_bulk_product2_jobwithoperation(insert/update/upsert), the totalrowCount, and a small sample (1-10 rows) of the real data for validation. Withoutconfirm: true, this only validates the sample and returns a preview — no Salesforce call is made.With
confirm: true, it opens a real Bulk API job and returns a relativeuploadPath(e.g./api/bulk/{jobId}/data).From a shell-capable session (Claude Code or similar — not the Desktop/claude.ai chat connector, which has no way to stream a local file outside the tool-call schema), stream the entire CSV file to that path in one request:
curl --data-binary @file.csv \ -H "Authorization: Bearer <MCP_BEARER_TOKEN>" \ -X PUT "https://salesforce-product-mcp.vercel.app/api/bulk/<jobId>/data"This closes the job and starts Salesforce processing it. The row data never enters the calling model's context —
curlreads it straight off disk.Poll
get_bulk_product2_job_status(an MCP tool call — cheap, small response) untilstateisJobComplete. If any rows failed, the result includes a capped summary (default: first 25 failures + a total count) — never a full multi-thousand-row result dump.
No chunking — one PUT per job, verified against a real org. An earlier
version of this doc (and the route) assumed Salesforce's Bulk API 2.0 would
accept multiple sequential PUT calls to build up a job's data before
closing it. Live testing proved that wrong: a second PUT .../batches call
on the same job is rejected outright ("Found multiple contents for job").
The entire CSV must go in a single request. The real, unavoidable
ceiling is Vercel's Node.js serverless request-body limit (~4.5 MB, not
tunable from Next.js config — that only applies to the legacy Pages API), so
a file needs to fit under roughly 4.4 MB to be safe. For context: a typical
Product2 row (a handful of fields) is well under 1 KB, so even a 6,500-row
file is normally a few hundred KB — comfortably under that ceiling. A file
that genuinely exceeds it would need to be split across multiple separate
Bulk API jobs (each with its own single PUT) — not built here, since it
wasn't needed for what this server was built for.
Row cap: start_bulk_product2_job rejects a declared rowCount above
MAX_ROW_COUNT (50,000, in lib/bulkProduct2.ts). This is a self-imposed
mistake-guard against pointing at the wrong file — not a Salesforce
limit; Bulk API 2.0 itself handles far more than that.
Operation-specific row rules (validated against the sample you pass to
start_bulk_product2_job, and enforced by Salesforce on the real file):
insert— rows must NOT include anIdcolumn.update— every row MUST include anIdcolumn (the record to update).upsert— every row MUST include theexternalIdFieldNamecolumn, and must NOT includeId.
⚠️ Upsert requires Salesforce-side setup that isn't done automatically: the field you pass as
externalIdFieldName(e.g.ProductCode) must already have the External ID checkbox enabled in Salesforce Object Manager → Product2 → Fields & Relationships. It is not on by default for standard fields likeProductCode— a job created with an unqualified field will fail Salesforce-side.
Environment variables
See .env.example. Copy it to .env.local for local dev;
never commit real secrets.
Variable | Purpose |
| Org My Domain URL (not |
| ECA Consumer Key |
| ECA Consumer Secret |
| Shared bearer token for Claude → server ( |
Local development
npm install
cp .env.example .env.local # fill in values
npm run dev # http://localhost:3000, endpoint /api/mcpVerify the build (no live credentials needed)
npm run typecheck # tsc --noEmit
npm run build # next build
npm test # vitest — mocks the SF token + REST responsesProve the auth gate
With the server running (npm run dev or npm run start):
MCP_BEARER_TOKEN=<your-token> npm run check-auth
# or against the live deployment:
MCP_BEARER_TOKEN=<your-token> node scripts/check-auth.mjs https://salesforce-product-mcp.vercel.appExpected: the request without a token returns 401; the request with
the correct token is accepted.
Inspect with the MCP Inspector
npx @modelcontextprotocol/inspectorPoint it at http://localhost:3000/api/mcp (Streamable HTTP) with an
Authorization: Bearer <MCP_BEARER_TOKEN> header. You should see
create_product2 and update_product2 listed. Try a call without
confirm: true first to see the dry-run preview before writing anything.
Deployment (Vercel)
This is IKI business use, so it must be deployed to a Vercel Pro team, not a personal/Hobby account (Hobby prohibits commercial use).
vercel link # link to the Pro team/project
vercel env add SF_LOGIN_URL # repeat for each variable, all environments
vercel env add SF_CLIENT_ID
vercel env add SF_CLIENT_SECRET
vercel env add MCP_BEARER_TOKEN
vercel # preview deploy — verify /api/mcp
vercel --prod # promote to productionFluid compute keeps functions warm, which helps the module-scoped Salesforce token cache survive between requests. Enable it in Project → Settings → Functions.
The URL registered as the Claude connector must be the stable production domain (
https://<project>.vercel.app/api/mcpor a custom domain). Preview URLs change on every deploy and would silently break the connector.
Connecting from Claude
Settings → Connectors → Add custom connector. Name it something that signals its blast radius — e.g. "Salesforce Product Write (USE CAUTION!)" — since it's easy to lose track of which connector can write CRM data once you have several. Use a bearer token distinct from the read-only sibling server's, so the two can be rotated/revoked independently.
The dialog's auth options vary by rollout:
If there's a "Request headers" field (beta, gradually rolled out): set header
AuthorizationtoBearer <MCP_BEARER_TOKEN>(space afterBearer), and use the plain endpoint URL.If the dialog only shows OAuth Client ID/Secret (no headers field — the common case as of this writing): leave those blank and instead bake the token into the URL itself, using the server's built-in
?token=fallback:https://salesforce-product-mcp.vercel.app/api/mcp?token=<MCP_BEARER_TOKEN>This isn't a workaround bolted onto the docs —
extractToken()inroute.tsaccepts the token via either theAuthorizationheader or a?token=/?key=query param specifically because claude.ai's connector dialog has historically had no bearer-token field for servers without an OAuth authorization server.
Under Connection methods, leave Individual sign-in on and skip Managed authorization. Managed authorization connects your whole workspace through your identity provider with no per-member opt-in — the opposite of what you want for a connector that can write to Product2. It also targets OAuth/SSO connectors; this server doesn't do OAuth, so it isn't a fit here regardless.
Once connected, run a dry-run call first (omit confirm) to confirm the
round trip works before doing a real write.
This connector setup covers create_product2/update_product2 fine, but
bulk imports (see above) need a shell-capable session, not this chat
connector — the curl-based upload step has no equivalent in a pure
chat interface.
Operational notes
Professional Edition's API add-on has a modest daily API call quota. This server makes only 1–2 REST calls per write and reuses its cached token rather than re-authenticating each request. Watch usage at:
Salesforce Setup → Company Information → API Requests, Last 24 Hours
Notes on dependencies
@modelcontextprotocol/sdkis pinned to 1.26.0 (mcp-handler's peer; ≥ 1.26.0 is required — earlier versions have a security vulnerability).
Project layout
app/
layout.tsx
page.tsx
api/
[transport]/route.ts # MCP handler: create_product2, update_product2,
# start_bulk_product2_job, get_bulk_product2_job_status
bulk/[jobId]/data/route.ts # plain HTTP PUT endpoint for streaming CSV bytes
# (not an MCP tool) -- one PUT per job, no chunking
lib/
auth.ts # shared bearer-token check, used by both routes above
salesforce.ts # Client Credentials auth, token cache, REST helper (sfFetch)
product2.ts # Field-name sanitization + single-record create/update
bulkProduct2.ts # Bulk API 2.0 client + row/field validation
scripts/
check-auth.mjs # proves the bearer gate (with/without token)
tests/
auth.test.ts # bearer-token extraction/comparison unit tests
salesforce.test.ts # mocked token + REST call smoke test
product2.test.ts # field sanitization + mocked create/update smoke test
bulkProduct2.test.ts # bulk validation rules + mocked Bulk API client callsThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Testing-phase Products Watch endpoint; public discovery remains while new onboarding is paused.
Manage products, EU Digital Product Passports, operator parties, and GS1 EPCIS supply-chain events.
Organization-scoped BrandPresence evidence and explicitly authorized product actions over MCP.
Project-scoped marketing ops for agents: Bearer dpk_/dpa_ or OAuth, read and supervised write.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceProvides read-only access to Salesforce orgs via MCP, enabling SOQL queries, sObject descriptions, and object listing using OAuth 2.0 Client Credentials.
- AlicenseAqualityAmaintenanceEnables SOQL queries, SOSL search, and record CRUD operations on a single Salesforce org via MCP, with optional read-only mode.978MIT
- FlicenseNot gradedqualityBmaintenanceEnables management of Shopify store via GraphQL, including products, orders, inventory, and discounts with security features like preview mode and write protection.
- FlicenseNot gradedqualityBmaintenanceEnables Microsoft 365 Copilot to interact with Salesforce through REST APIs, supporting queries, object descriptions, pipeline summaries, and anomaly detection.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ikimanderson/salesforce-product-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server