Customer Health MCP
Incorporates support ticket signals shaped like Intercom tickets, including severity, status, and open duration, into account health scoring.
Provides customer health and churn-risk analysis based on subscription data shaped like Stripe subscription objects, including plan, MRR, payment status, and downgrades.
Incorporates support ticket signals shaped like Zendesk tickets, including severity, status, and open duration, into account health scoring.
Click on "Deploy 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., "@Customer Health MCPwhich accounts are most at risk of churning this week, and why?"
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.
customer-health-mcp
Read the build log → A write-up covering the architecture, the scoring methodology, and three real bugs caught along the way.
An MCP (Model Context Protocol) server that lets an AI assistant answer customer-success questions like "which accounts are at risk of churning?" or "give me a health summary for Acme Corp", by synthesizing data shaped like three real B2B SaaS systems:
Billing/subscription data, shaped like a Stripe subscription object (plan, MRR, payment status, downgrades)
Support ticket data, shaped like an Intercom/Zendesk ticket (severity, status, open duration)
Product usage data, shaped like a product analytics tool (daily active seats over 90 days, feature adoption, login recency)
This is a portfolio project: it runs with zero external API keys or databases, using deterministic synthetic data generated from a seed at startup. The goal is to demonstrate production-grade MCP server design (tool granularity, input validation, structured output, error handling, and an inspectable scoring methodology) on top of realistic (if fictional) data.
Why this exists
Customer success teams live at the intersection of three systems that rarely talk to each other: billing, support, and product usage. A churn signal is almost never visible in just one of them: an account can have healthy MRR and no open tickets while quietly not logging in for a month. This server models that synthesis problem directly: it doesn't just expose three raw data sources as tools, it also computes and explains a single composite risk score from all three.
Related MCP server: Customer Health Intelligence MCP Server
Architecture
┌────────────────────────────────────────┐
│ MCP Client │
│ (Claude Desktop / Claude Code) │
└────────────────────────────────────────┘
│
│ stdio · JSON-RPC
▼
┌────────────────────────────────────────┐
│ data/generator.ts │
│ seeded PRNG → 32 fictional accounts, │
│ generated once at boot │
└────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ 32 accounts, held in memory │
│ billing + usage + support tickets │
└────────────────────────────────────────┘
│
│ re-read on every tool call
▼
┌────────────────────────────────────────┐
│ scoring/riskScore.ts │
│ weights + thresholds → score, band, │
│ drivers, never cached │
└────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ tools/ + resources/ │
│ list_accounts · get_account_details │
│ list_at_risk_accounts │
│ get_account_risk_summary │
│ methodology://risk-scoring │
└────────────────────────────────────────┘Data flows one direction: the generator builds the in-memory dataset once at startup, the scorer derives a risk score from it on demand (never cached, so it's always consistent with the underlying data), and the tools are thin adapters that filter/shape that data and the scorer's output for MCP clients.
Setup
Requires Node.js 22.12+ or 24+ (this repo pins lts/* via .nvmrc).
npm install
npm run buildRun it directly to confirm it starts (it will sit waiting for stdio input;
Ctrl+C to exit):
npm startRun the test suite:
npm testPrint a quick human-readable dump of the generated dataset without starting the MCP server:
npm run sample-dataReproducible data with --seed
The dataset is generated once at startup from a seed string, so the same seed always produces the same 32 accounts:
node dist/index.js --seed my-custom-seedOmit --seed to use the built-in default seed.
Wiring into Claude Desktop
Add an entry to your claude_desktop_config.json (on macOS:
~/Library/Application Support/Claude/claude_desktop_config.json; on
Windows: %APPDATA%\Claude\claude_desktop_config.json), pointing at the
built dist/index.js with an absolute path:
{
"mcpServers": {
"customer-health": {
"command": "node",
"args": ["/absolute/path/to/customer-health-mcp/dist/index.js"]
}
}
}Restart Claude Desktop after saving. You should see customer-health listed
among the connected MCP servers, with its four tools and one resource
available.
Example prompts
Once connected, try asking Claude things like:
"Which of our accounts are most at risk this week, and why?"
"Give me a health summary for Tidewater Insurance."
"List every enterprise account that's past due on payment."
"How is churn risk actually calculated here?" (Claude can read the
methodology://risk-scoringresource to answer this precisely, instead of guessing.)
Design decisions
Tool granularity: raw data tools + one synthesis tool
Three of the four tools (list_accounts, get_account_details,
list_at_risk_accounts) return data that's close to the underlying model:
they filter, sort, and reshape, but they don't interpret. The fourth,
get_account_risk_summary, is the "synthesis" tool: it's the only one that
combines all three data sources into a single judgment call (a score, a band,
and a plain-language explanation).
This split matters for how a calling model uses the server. Raw-data tools
let the model verify a claim or dig into specifics ("show me the actual
tickets"); the synthesis tool lets it answer a fuzzy question quickly without
re-deriving the scoring logic itself in-context (which it would likely do
inconsistently). Keeping them separate, rather than only exposing the
synthesized view, also makes the scoring auditable: a model (or a human
reading its answer) can always cross-check a risk summary against
get_account_details for the same account.
Scoring methodology
computeRiskScore (in src/scoring/riskScore.ts)
returns a 0-100 score, where higher means more risk, built from four
independently-capped categories (usage trend/dormancy, seat adoption,
support signal, and billing signal), each of which contributes plain-language
"drivers" rather than just a number. Two choices are worth calling out:
Billing is the highest-leverage category. A canceled subscription alone is enough to reach the "at_risk" band, and a past-due payment alone is enough to reach "watch," because in practice a billing failure is a much harder churn signal than a soft dip in usage or a few open tickets. Usage and support signals still matter, but they combine more gradually.
Drivers are sorted by point contribution, not by category order. The first item in
driversis always whatever single signal contributed the most to the score, so a model summarizing "why" an account is at risk leads with the actual biggest factor rather than an arbitrary category ordering.
The exact weights and thresholds are documented in code
(WEIGHTS/BAND_THRESHOLDS in riskScore.ts) and re-rendered live as the
methodology://risk-scoring MCP resource, so the two can never drift out of
sync with each other.
Project structure
src/
data/
types.ts Account/BillingRecord/Ticket/UsageProfile types
rng.ts seeded PRNG utilities
generator.ts seeded synthetic data generation
printSample.ts `npm run sample-data` entrypoint
scoring/
riskScore.ts composite health/risk scoring logic
riskScore.test.ts unit tests (4+ scenarios)
tools/
shared.ts shared accountId lookup/error helper
listAccounts.ts
getAccountDetails.ts
listAtRiskAccounts.ts
getAccountRiskSummary.ts
resources/
methodology.ts exposes scoring methodology as an MCP resource
server.ts wires up tools/resources into an McpServer
index.ts stdio entrypoint (`--seed` flag)
tests/
integration.test.ts real client/server round trip over InMemoryTransportAvailable Tools
4 toolsget_account_detailsGet full account detailA
Fetch the complete raw record for one account: its Stripe-style billing/subscription record, its full 90-day product-usage time series and feature adoption, and its full support ticket history. Use this when you need the underlying data behind a risk score, not just the summary. Returns a clear error if the accountId does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | Yes | The account's unique identifier, e.g. "acct_0007" |
Output Schema
| Name | Required | Description |
|---|---|---|
| usage | Yes | |
| billing | Yes | |
| support | Yes | |
| accountId | Yes | |
| companyName | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does reasonably well: it discloses the breadth of data returned (full history and time series, implying a heavy response) and the error behavior when accountId does not exist. It does not state read-only status or any permission/rate considerations, leaving a gap for a data-heavy read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tightly written sentences with zero filler; the verb and resource come first, followed by contents, then the routing condition, then error behavior. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be spelled out, yet the description usefully previews the payload shape. For a single-parameter read tool with full schema coverage, nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single accountId parameter is already documented with an example format, so the schema does the heavy lifting. The description adds only an error condition for a nonexistent accountId, which is useful but not syntax or format detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Fetch') and resource ('the complete raw record for one account'), then enumerates exactly what the record contains: billing/subscription, 90-day usage time series, and support ticket history. This lets an agent distinguish it from the summary-oriented sibling get_account_risk_summary without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear selection condition: 'Use this when you need the underlying data behind a risk score, not just the summary,' which implicitly contrasts with the risk-summary sibling. It stops short of naming the alternative tool explicitly or stating any exclusions/prerequisites, so it falls just below the top band.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_account_risk_summaryGet account risk summaryA
The flagship synthesis tool: computes an account's churn-risk score and band from its billing, usage, and support signals, lists the specific drivers behind the number, and generates a short human-readable summary paragraph. Use this when someone asks 'why is this account at risk?' or 'give me a health summary for '. Returns a clear error if the accountId does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | Yes | The account's unique identifier, e.g. "acct_0007" |
Output Schema
| Name | Required | Description |
|---|---|---|
| drivers | Yes | |
| summary | Yes | |
| riskBand | Yes | |
| accountId | Yes | |
| riskScore | Yes | |
| companyName | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that the result is a computed synthesis across billing/usage/support, that it includes explanatory drivers and a generated paragraph, and that a missing accountId yields a clear error. It omits cost/latency/auth considerations for what is presumably an expensive aggregation, keeping it out of 5 territory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core capability, then usage triggers, then the error contract — a logical ordering with no filler sentences. Slightly verbose in stacking three output claims, but each earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value detail is not the description's job. The description still covers purpose, synthesis inputs, when to invoke, and the error case, leaving nothing an agent needs in order to call this single-parameter tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single accountId parameter, so the baseline is 3. The description adds real semantic value beyond the schema by specifying the failure mode — a clear error when the accountId does not exist — which tells the agent what to expect for invalid input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource pair (computes churn-risk score and band) plus the three signal sources it synthesizes, and enumerates its outputs (score, drivers, summary paragraph). The 'flagship synthesis tool' framing plus the multi-source computation clearly distinguishes it from list_accounts, get_account_details, and list_at_risk_accounts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit trigger phrases ('why is this account at risk?', 'give me a health summary for <company>'), which is clear usage context. It stops short of naming when NOT to use it or pointing to siblings (e.g., list_at_risk_accounts for a roster), so it lacks the exclusion/alternative half of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_accountsList customer accountsA
List all customer accounts with their plan, MRR, payment status, and computed churn-risk band. Supports optional filtering by plan and payment status. Use this to get an overview of the customer base before drilling into a specific account with get_account_details or get_account_risk_summary.
| Name | Required | Description | Default |
|---|---|---|---|
| plan | No | Only return accounts on this subscription plan | |
| paymentStatus | No | Only return accounts with this billing payment status |
Output Schema
| Name | Required | Description |
|---|---|---|
| accounts | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are present, so the description carries the full disclosure burden. It reveals what is returned and that filtering is optional, but says nothing about read-only semantics, result-set size limits, pagination, or ordering for what is clearly a collection endpoint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with purpose and return fields, then capability, then routing. Every clause carries information and nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the response shape need not be explained, and the routing guidance to siblings is a genuine plus. The only gap is operational detail (pagination/limits) for a list endpoint expected to return the whole customer base.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and both parameters are enum-constrained with their own descriptions, so the schema does the heavy lifting. The description only restates that filtering by plan and payment status is optional, adding no semantics beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List all customer accounts') and enumerates the returned fields (plan, MRR, payment status, churn-risk band), which cleanly separates it from the detail-oriented siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly frames the use case as getting a customer-base overview before drilling down, and names get_account_details and get_account_risk_summary as the follow-on tools. It lacks an explicit statement of when NOT to use it, so it stops just short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_at_risk_accountsList accounts at risk of churningA
List customer accounts whose computed churn-risk band is at or above a minimum threshold (defaults to 'watch', so both 'watch' and 'at_risk' accounts are included), sorted by risk score descending. This is the fastest way to answer 'which accounts are at risk this week?' For the reasoning behind a specific account's score, follow up with get_account_risk_summary.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of accounts to return (default: all matches) | |
| minBand | No | Minimum risk band to include (default: 'watch') |
Output Schema
| Name | Required | Description |
|---|---|---|
| accounts | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full burden. It discloses the default threshold behavior, the inclusive band semantics, and descending sort order, which is genuinely useful. However, it says nothing about pagination, permissions, or result volume, which matters for a list endpoint with a limit parameter.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: scope/defaults first, use case second, follow-up routing third. Front-loaded with the most important information and free of filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-value explanation is unnecessary, and the description covers scope, defaults, sorting, and the natural follow-up tool. The only gap is that no annotations exist to cover safety/permission characteristics and the description does not compensate for them.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: it explains that a 'watch' minimum band therefore includes BOTH 'watch' and 'at_risk' accounts, clarifying the ordering semantics of the enum that the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (customer accounts) narrowed by a computed churn-risk band threshold, which cleanly separates it from the sibling list_accounts that has no risk filter. The default behavior and sort order are stated up front.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly frames the use case ('fastest way to answer which accounts are at risk this week?') and routes the agent to get_account_risk_summary when it needs score reasoning. It does not explicitly say when to prefer list_accounts or get_account_details, so it stops short of full when/when-not coverage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
get_account_details - First observed
get_account_risk_summary - First observed
list_accounts - First observed
list_at_risk_accounts
TDQS
Scored across 4 tools
list_accounts and list_at_risk_accounts both return account lists and could be momentarily confused, but the descriptions make the filtering intent distinct (plan/payment vs risk band). get_account_details (raw data) vs get_account_risk_summary (synthesized score/drivers) are cleanly separated.
All four tools follow a consistent snake_case verb_noun pattern using list_/get_ prefixes and predictable noun phrases. No mixing of conventions or stylistic outliers.
Four read-only tools is well-scoped for a churn-risk analytics surface, with no redundant entries. It is slightly lean—no mutation or annotation tools—but each tool earns its place.
The read-only lifecycle is well covered: overview, at-risk ranking, raw detail, and risk synthesis with explicit follow-up pathways. Minor gaps exist (no way to list support tickets globally, no account-level write/annotation), but core workflows have no dead ends.
Maintenance
Related MCP Connectors
Your AI copilot for customer onboarding — projects, KPIs, tasks, and safe writes.
Sandbox workspace tools: search, file read, DB queries, integrations. Returns synthetic data.
Read-only, deterministic AI triage and readiness tools implementing Sophon's published rubrics.
Read-only account intelligence for Customer Success and Account Management teams.
Related MCP Servers
AlicenseAqualityBmaintenanceEnables AI agents to query customer churn risk, MRR at risk, and customer health profiles from Retain, and take actions like adding notes and marking alerts contacted.824 npm1MIT- FlicenseNot gradedqualityCmaintenanceProvides deterministic mock customer health, product usage, and recommended playbook data via three MCP tools for a Customer Success Orchestrator demo.-
- FlicenseNot gradedqualityCmaintenanceExposes simulated Salesforce CRM data as MCP tools for Claude Desktop, enabling customer success managers to prioritize at-risk accounts, review opportunities, cases, and Gong notes via natural language.-
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to query internal business data for insights into customers, revenue, subscriptions, sales, and churn through controlled, read-only MCP tools.-