PM Copilot
An MCP server that triangulates customer support tickets, feature requests, and AI support agent conversations to help PMs decide what to build next.
Real results: Analyzed 3,353 signals in one 30-day window — 1,678 support tickets, 276 feature requests, and 1,399 AI support agent conversations across 4 products. Top priority: Booking & Scheduling — 285 tickets + 74 feature requests + 347 chats pointing at the same problem, with the AI agent answering with low confidence in 48% of those chats.
The 1,399 chats are the point. None of them were visible to the analysis before v1.4.0, and they are 83% the volume of the ticket channel.
Read the full story: I built an MCP server that changed how I prioritize products — why I built this, how convergent signals work in practice, and what I learned building with Claude Code.
What Makes This Different
- Signal triangulation. Matches support tickets against feature requests to find convergent themes, then scores them with a weighted formula that gives convergent signals a 2x priority boost.
- The deflection blind spot. An AI support agent answers questions that never become tickets, so ticket-based prioritization undercounts every theme the bot handles — and the gap widens as the bot improves. Chatbase conversations are pulled in as a third signal class, with a per-theme
self_serve_failure_rateshowing where self-serve is failing. - Composability. Works alongside other MCP servers. Pass churn data from Metabase or traffic trends from Google Analytics into
generate_product_planviakpi_context, and the methodology adjusts priorities accordingly. - Built-in PM methodology. Opinionated scoring based on 7 years of product management across 9 products and 1M+ users. It's a real decision-making process exposed as an MCP resource, not a generic framework.
- PII scrubbing. Customer data never reaches the LLM unfiltered. SSNs, credit cards (Luhn-validated), emails, and phone numbers are redacted before analysis. Agent responses are filtered out of quotes.
Architecture
graph TD
A[Claude Desktop / Code] -->|stdio| B[pm-copilot]
A -->|stdio| C[Metabase MCP]
A -->|stdio| D[Google Analytics MCP]
B -->|Reactive| E[HelpScout: tickets]
B -->|Proactive| F[ProductLift: feature requests]
B -->|Deflected| I[Chatbase: AI agent chats]
C -->|Quantitative| G[Conversion, Churn, Revenue]
D -->|Acquisition| H[Traffic, Channels, Trends]
B -.->|kpi_context| AClaude orchestrates multiple MCP servers. PM Copilot handles qualitative customer signals. Other servers provide quantitative business metrics. The kpi_context parameter is the integration point — no point-to-point integrations required.
Quick Start
git clone https://github.com/dkships/pm-copilot.git
cd pm-copilot
npm install
cp .env.example .env # Edit with your credentials
npm run buildCredentials
HelpScout is required. ProductLift and Chatbase are both optional — configure either, both, or neither, and the analysis adapts.
| Variable | Required | Description |
|---|---|---|
HELPSCOUT_APP_ID | Yes | OAuth app ID from https://secure.helpscout.net/apps/custom/ |
HELPSCOUT_APP_SECRET | Yes | OAuth app secret |
PRODUCTLIFT_PORTALS | No | Multi-portal: name|url|key,name2|url2|key2 |
PRODUCTLIFT_PORTAL_URL | No | Single portal URL |
PRODUCTLIFT_API_KEY | No | Single portal Bearer token |
PRODUCTLIFT_PORTAL_NAME | No | Portal display name (default: default) |
CHATBASE_API_KEY | No | Account-wide secret key from Chatbase → Settings → API keys |
CHATBASE_AGENTS | No | Multi-agent: name|agentId,name2|agentId2 |
CHATBASE_AGENT_ID | No | Single agent id |
CHATBASE_AGENT_NAME | No | Single agent display name (default: default) |
Chatbase API access needs a Chatbase Standard plan or higher. On a lower plan the API returns 403 and the deflection signal is reported as a warning rather than failing the whole analysis. One agent per product is the useful shape — agents give you product-level attribution that a shared support mailbox does not.
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"pm-copilot": {
"command": "node",
"args": ["/absolute/path/to/pm-copilot/dist/index.js"]
}
}
}Claude Code
claude mcp add pm-copilot -- node /absolute/path/to/pm-copilot/dist/index.jsOr use the .mcp.json already in the project root — Claude Code picks it up automatically.
Tools
synthesize_feedback
Cross-references HelpScout tickets, ProductLift feature requests, and Chatbase conversations, returns theme-matched analysis with priority scores.
| Parameter | Type | Default | Description |
|---|---|---|---|
timeframe_days | number | 30 | Days to look back (1-90) |
top_voted_limit | number | 50 | Top-voted requests per portal; recent requests in the timeframe are always included on top |
mailbox_id | string | — | HelpScout mailbox filter (raw ID) |
mailbox_name | string | — | HelpScout mailbox name (case-insensitive); auto-resolved to an ID. Run list_sources to see names |
portal_name | string | — | ProductLift portal filter |
agent_name | string | — | Chatbase agent filter. Run list_sources to see names |
source_filter | string | — | Chatbase conversation source filter, comma-separated for multiple, e.g. Widget or Iframe or WhatsApp,API. Case-insensitive. Run list_sources for the valid values |
detail_level | string | "summary" | "summary", "standard", or "full". Output size scales with data volume — roughly 20KB / 100KB / 600KB |
Returns themes sorted by priority score, each with reactive/proactive counts, convergence flag, evidence summaries, and representative customer quotes.
generate_product_plan
Builds a prioritized product plan with evidence and customer quotes. Accepts external business metrics via kpi_context.
| Parameter | Type | Default | Description |
|---|---|---|---|
timeframe_days | number | 30 | Days to look back (1-90) |
top_voted_limit | number | 50 | Top-voted requests per portal; recent requests in the timeframe are always included on top |
mailbox_id | string | — | HelpScout mailbox filter (raw ID) |
mailbox_name | string | — | HelpScout mailbox name (case-insensitive); auto-resolved to an ID. Run list_sources to see names |
portal_name | string | — | ProductLift portal filter |
agent_name | string | — | Chatbase agent filter. Run list_sources to see names |
source_filter | string | — | Chatbase conversation source filter, comma-separated for multiple, e.g. Widget or Iframe or WhatsApp,API. Case-insensitive. Run list_sources for the valid values |
kpi_context | string | — | Business metrics from other MCP servers |
max_priorities | number | 5 | Number of priorities to return (1-10) |
preview_only | boolean | false | Audit mode: show what data would be sent |
detail_level | string | "summary" | "summary", "standard", or "full". Output size scales with data volume — for one 30-day mailbox, roughly 5KB / 21KB / 375KB |
format | string | "json" | "json" (structured, composable) or "markdown" (ready-to-read product brief) |
get_feature_requests
Raw ProductLift data access for browsing feature requests directly. Each request includes its
public url.
| Parameter | Type | Default | Description |
|---|---|---|---|
portal_name | string | — | Filter to a specific portal |
include_comments | boolean | true | Include comments on each request |
status | string | — | Filter to requests with this status (case-insensitive), e.g. open, planned, completed |
list_sources
Lists the data sources the server is connected to — HelpScout mailboxes (id + name),
ProductLift portals (name + url), and Chatbase agents (name + id) — so you can discover the
names to pass to mailbox_name / portal_name / agent_name. When Chatbase is configured it
also returns chatbase_conversation_sources, the values source_filter accepts (a fixed list
from the Chatbase docs, not queried per account). Read-only; never returns API keys or customer
data. Takes no parameters.
Signal classes
Three sources, three different things they tell you. Only the first two feed the convergence rule.
| Class | Source | What it means | Feeds |
|---|---|---|---|
| Reactive | HelpScout tickets | Something is broken | Frequency, severity, convergence |
| Proactive | ProductLift requests | Something is wanted | Frequency, vote momentum, convergence |
| Deflected | Chatbase conversations | Something was asked, and self-serve either handled it or did not | Frequency only |
Deflected signals count toward frequency and carry two evidence fields per theme, but they do not enter the severity or vote-momentum terms and do not change the 2x convergence boost. The formula is unchanged from v2.1:
deflected_count— conversations matching the themeself_serve_failure_rate— share of those conversations where the agent's lowest answer confidence fell below 0.5mean_answer_confidence— mean of that same score
A theme with high deflected_count and a high self_serve_failure_rate is one customers keep
asking about that self-serve does not resolve. Chatbase does not document what its min_score
field measures, so it is reported as evidence for the LLM to weigh rather than folded into the
priority score.
Conversations arrive from several channels (widget, WhatsApp, Messenger, API, …). The analysis
reports a chatbase_sources count per channel, and the source_filter parameter narrows a run
to one or more channels (comma-separated) — the filter is applied server-side by Chatbase. One
catch: counts can include channels the filter list does not cover, like Playground or
unknown (Chatbase omitted the source). A filter value outside the known list is passed
through with a warning rather than rejected, since zero matches usually means the value is
wrong, not that the channel went quiet.
Chatbase is optional. With no CHATBASE_API_KEY set, the deflection fields are simply absent
and the analysis behaves exactly as before.
Example output
A trimmed synthesize_feedback response at the default summary detail level. Values are illustrative; note the PII scrubbing applied to the customer quote.
{
"timeframe_days": 30,
"detail_level": "summary",
"portal_name": "all",
"fetched_at": "2026-06-01T16:00:00.000Z",
"pii_scrubbing_applied": true,
"pii_categories_redacted": ["email", "phone", "credit_card"],
"analysis": {
"total_data_points": 612,
"reactive_count": 548,
"proactive_count": 64,
"deflected_count": 312,
"chatbase_sources": {
"Widget or Iframe": 284,
"WhatsApp": 23,
"API": 5
},
"themes": [
{
"theme_id": "booking-scheduling",
"label": "Booking & Scheduling",
"category": "core",
"priority_score": 87.1,
"convergent": true,
"signal_type": "convergent",
"reactive_count": 211,
"proactive_count": 19,
"deflected_count": 96,
"self_serve_failure_rate": 0.48,
"mean_answer_confidence": 0.53,
"evidence_summary": "326 signals (211 suppor
…