Customer Health Intelligence MCP Server
by arghamza
README.md
# Customer Health Intelligence MCP Server
A small, self-contained MCP server for the Customer Success Orchestrator demo. It
exposes three tools, all backed by deterministic mock data (no real API keys, no
real customer data):
- `get_customer_health(accountId)` - health score, trend, sentiment summary, risk factors
- `get_product_usage(accountId)` - active users, login frequency, feature adoption, usage trend
- `get_recommended_playbook(accountId)` - playbook name, urgency, summary, next actions
- `ping()` - diagnostic-only, no real use case. Deliberately trivial (empty input, `{"status":"ok"}`
output) - added and kept registered as a minimal, reproducible repro case for the "any mcpTool://
action fails to publish" platform issue documented in AgentSpec.md's "MCP Integration Status". Not
meant to be wired into the actual agent.
The same `accountId` always resolves to the same one of six health archetypes
(thriving, steady, at-risk-usage, at-risk-sentiment, new-ramping,
champion-turnover) across all three tools and across repeated calls, so a live
demo tells one coherent story per account instead of contradicting itself between
calls. See `mock-data.js` for the archetypes.
Verified working end-to-end locally against the real MCP protocol (`initialize`,
`tools/list`, `tools/call`) before being handed off - see the bottom of this file
if you want to re-run that check yourself.
## Run it locally
```bash
npm install
npm start
```
Listens on `http://localhost:8080/mcp` (override with the `PORT` env var).
### Important: `MCP_ALLOWED_HOSTS`
`@modelcontextprotocol/express` includes DNS-rebinding protection that, once bound
to `0.0.0.0` (required for any external caller to reach it), only accepts requests
whose `Host` header matches an explicit allowlist - it has no implicit "allow
everything" mode. Set `MCP_ALLOWED_HOSTS` to a comma-separated list of the
hostname(s) the server will actually be reached at, or every request gets a `403`.
This has to match whatever the *current* public hostname is - it changes every time
you restart a Cloudflare quick tunnel, and will be a fixed different value once on
Render.
## Expose it publicly - Cloudflare Tunnel (no account, no billing, works immediately)
Both Google Cloud Run and Render hit billing/card walls during setup, so this is
the path that was actually used to get this server reachable for testing. No
signup of any kind required.
```bash
winget install --id Cloudflare.cloudflared -e
cloudflared tunnel --url http://localhost:8080
```
This prints a random `https://<random-words>.trycloudflare.com` URL - grab it from
the "Your quick Tunnel has been created!" output, then set it as `MCP_ALLOWED_HOSTS`
before starting the server:
```bash
MCP_ALLOWED_HOSTS="<random-words>.trycloudflare.com,localhost" npm start
```
**Caveat:** this is an account-less "quick tunnel" - Cloudflare gives no uptime
guarantee, and the URL changes every time you restart `cloudflared`. Fine for
testing and registering the connection now; re-run both commands (and re-register
the new URL in Salesforce) right before an actual live demo so the hostname is
fresh and warm.
## Deploy it somewhere permanent later - Google Cloud Run or Render
Both were attempted and both required linking a billing method/card before
deploying (Cloud Run needs a GCP billing account; Render's free web services
have prompted for a card on this account too) - neither is a hard blocker, just
something to budget time for if you want a stable, permanent URL instead of the
tunnel. If you go this route, remember to also set `MCP_ALLOWED_HOSTS` to that
service's assigned hostname (e.g. `--set-env-vars` on Cloud Run, or an environment
variable in Render's dashboard) - the server will 403 without it, same as above.
### Optional: protect the endpoint
Mock data means there's nothing sensitive to protect, but if you want light
protection anyway, set `MCP_API_KEY` to a secret of your choosing. With it set,
every request must include `Authorization: Bearer <that-secret>`, and Salesforce's
connection would need to be registered as an API-key/custom-header auth type
rather than No Authentication - check whether that option exists before relying
on it. Leave `MCP_API_KEY` unset to allow any caller, which is simplest and fine
for a demo.
## Register in Salesforce
- Server URL: `https://<your-public-hostname>/mcp`
- Authentication Method: **No Authentication** (unless you set `MCP_API_KEY` above)
Because there's no OAuth negotiation at all, this sidesteps the `client_credentials`
discovery failure hit earlier with Slack's official MCP server - Salesforce just
calls the URL directly.
## Re-verifying it works (what was actually run before handoff)
```bash
npm install
npm start
# in another terminal:
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_customer_health","arguments":{"accountId":"001aj00000AcmeManufacturing"}}}'
```
Should return a JSON-RPC response with a `content` array containing the health
payload as text. `initialize` and `tools/list` were also confirmed working - that
matters because those two calls are exactly what Salesforce's connection
discovery step runs first.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues