product-feedback-mcp
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., "@product-feedback-mcpWhat themes came up in this week's feedback?"
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.
product-feedback-mcp
An MCP server that lets Claude triage product feedback: search it, group it into themes, tag its severity, and draft a PRD-style problem statement, all over a local dataset.
Why this exists
A PM who wants to know what customers are complaining about usually ends up pasting a spreadsheet of tickets and reviews into a chat window and asking for a summary. That works once. It does not scale to "which themes came up this week," it does not let you ask a follow-up question against the same data, and every teammate who wants an answer has to paste the spreadsheet again.
An MCP server fixes that by giving Claude actual tools: search_feedback,
list_themes, get_theme, tag_severity, severity_summary, and
draft_problem_statement, backed by one dataset that lives in the repo.
Ask "what should we look at this week" and Claude calls the tools
instead of guessing from whatever text you happened to paste in.
The dataset here is synthetic feedback for a fictional B2B shift
scheduling product, Shiftly, but the server does not know or care that
it is fictional. Point FEEDBACK_DATASET_PATH at a real export in the
same shape and every tool works the same way.
Related MCP server: pm-copilot
Demo
scripts/demo.py calls the server the same way a real MCP client would
(the SDK's in-memory Client, no subprocess) against the committed
200-item dataset. Real output, captured by running it:
$ uv run python scripts/demo.pylist_themes(min_items=5) returned six themes. Each label is a real
member's own opening clause, chosen from the cluster's most central
item, so a PM can read the list without opening anything. keywords
stays available for transparency and representative_item_id names the
item the label came from:
[
{ "theme_id": "theme-01", "label": "Really solid support experience this week", "size": 7, "representative_item_id": "fb-0032" },
{ "theme_id": "theme-02", "label": "The Shiftly app crashed constantly", "size": 6, "representative_item_id": "fb-0042" },
{ "theme_id": "theme-03", "label": "Sales call: wanted benchmark numbers before rolling this out to the kitchen team", "size": 6, "representative_item_id": "fb-0089" },
{ "theme_id": "theme-04", "label": "Notifications are hit or miss", "size": 5, "representative_item_id": "fb-0049" },
{ "theme_id": "theme-05", "label": "Two-factor rollout for the delivery drivers went smoothly", "size": 5, "representative_item_id": "fb-0035" },
{ "theme_id": "theme-06", "label": "Sales call: asked what reporting looks like out of the box", "size": 5, "representative_item_id": "fb-0036" }
](keywords omitted above for width; the full objects are in the real
output.)
draft_problem_statement("theme-02"), the first complaint-majority
theme. The evidence quotes are picked for diversity across phrasing,
source, and segment rather than taken in order, and kind tells a
client whether it is looking at a problem or a strength:
{
"theme_id": "theme-02",
"theme_label": "The Shiftly app crashed constantly",
"kind": "problem",
"representative_item_id": "fb-0042",
"who": [
{ "customer_segment": "small_business", "count": 4 },
{ "customer_segment": "enterprise", "count": 1 },
{ "customer_segment": "mid_market", "count": 1 }
],
"what": "Customers across 3 segment(s) repeatedly report: The Shiftly app crashed constantly (6 of 200 items, 3.0%).",
"evidence": [
{ "feedback_id": "fb-0014", "source": "nps_comment", "quote": "The app has crashed on the scheduling admin three times this week, this is getting old." },
{ "feedback_id": "fb-0084", "source": "support_ticket", "quote": "App crashed and the shift I published for our call center agents disappeared. Had to rebuild it from memory." },
{ "feedback_id": "fb-0033", "source": "support_ticket", "quote": "Ugh, the app crashed again this morning right as the nursing unit tried to punch in." },
{ "feedback_id": "fb-0042", "source": "nps_comment", "quote": "The Shiftly app crashed constantly when the delivery drivers tried to clock in for a holiday week." },
{ "feedback_id": "fb-0017", "source": "support_ticket", "quote": "App crashed and the shift I published for our support reps disappeared. Had to rebuild it from memory." }
],
"frequency": {
"count": 6,
"percent_of_dataset": 3.0,
"by_source": { "nps_comment": 2, "support_ticket": 4 },
"date_range": { "earliest": "2025-11-03", "latest": "2026-06-27" },
"average_rating": 2.0
},
"suggested_success_metric": "Reduce 'The Shiftly app crashed constantly' feedback volume from 6 items (mostly via support_ticket) to 1 or fewer over the same reporting period, with no critical- or high-severity item left unresolved for more than one release cycle."
}For a praise-majority theme such as theme-01, kind is "strength" and
the wording flips: the statement says what to protect and the metric is a
floor to hold, not a volume to reduce.
Full output, including severity_summary() and get_theme(), is in
scripts/demo.py; run it yourself to see all of it.
Architecture
flowchart LR
subgraph Client
C[Claude Desktop, Claude Code,\nor any MCP client]
end
subgraph Transport
T1[stdio\nlocal subprocess]
T2[streamable HTTP\n+ bearer token]
end
subgraph Server[product-feedback-mcp]
TOOLS[Tools\nsearch_feedback, list_themes,\nget_theme, tag_severity,\nseverity_summary,\ndraft_problem_statement]
RES[Resources\nfeedback://summary\nfeedback://item/id]
PROMPT[Prompt\ntriage_this_weeks_feedback]
end
DATA[(data/feedback.jsonl\n200 synthetic items)]
C --> T1 --> Server
C --> T2 --> Server
TOOLS --> DATA
RES --> DATAQuick start
Takes under five minutes, no API key required.
git clone https://github.com/25andresbernal/product-feedback-mcp.git
cd product-feedback-mcp
export PATH="$HOME/.local/bin:$PATH" # if uv is not already on PATH
uv venv --python 3.12
uv pip install -e ".[dev]"
# See it work end to end
uv run python scripts/demo.py
# Run the test suite
uv run pytest
# Run the server itself, over stdio, the way an MCP client launches it
uv run product-feedback-mcpThe dataset is already committed at data/feedback.jsonl. To regenerate
it (or make a different-sized sample):
uv run python scripts/generate_dataset.py --count 200 --seed 42Connect it to a client
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"product-feedback": {
"command": "uv",
"args": [
"run",
"--directory",
"/absolute/path/to/product-feedback-mcp",
"product-feedback-mcp"
]
}
}
}Claude Code:
claude mcp add product-feedback -- uv run --directory /absolute/path/to/product-feedback-mcp product-feedback-mcpCursor, or any other MCP client: the pattern is the same: point the
client's stdio server config at uv run --directory <path to this repo> product-feedback-mcp. No environment variables or auth are needed for
stdio (see "Auth" below).
Configuration
All configuration is environment variables; see .env.example.
Variable | Required | Default | Purpose |
| no |
| Which |
| only for | none | The bearer token clients must send. The server refuses to start over HTTP without it. |
CLI flags (product-feedback-mcp --help): --transport stdio\|http
(default stdio), --host, --port (HTTP transport only).
How it works
dataset.py: loads and validatesfeedback.jsonlintoFeedbackItemrecords, and caches the parsed result per path so every tool call in a session reuses the same in-memory data instead of re-reading the file.search.py: a from-scratch Okapi BM25 implementation (stdlib only) over the feedback text, used bysearch_feedback.themes.py: deterministic clustering with no LLM and no randomness. Each item gets its top TF-IDF keywords (unigrams and bigrams); two items merge into the same theme only if they share at least two of those keywords, using a union-find over the whole dataset. A theme's label is a real member's own words, not a synthesized phrase: pick the cluster's medoid (the member whose keyword set overlaps most, on average, with every other member's, using those same keyword sets), then keep that item's first clause (_first_clause, cut at the first comma, period, or standalone "and" after at least four words, capped at 14 words either way, cutting before a subordinating word or trailing function words when the cap hits mid-sentence). If the cluster mixes praise and complaints, the medoid search is restricted to whichever side is the majority (by median rating if any member has one, otherwise by a small positive/negative phrase list), so the label is never a positive sentence pulled from a cluster that reads as a complaint or vice versa.representative_item_idon every theme names exactly which item the label came from. See "Design decisions" below for why clustering and labeling both work this way.text.py: also hasselect_diverse_items, a greedy picker used byget_themeanddraft_problem_statementto choose representative quotes: start from the lowest-id member, then repeatedly add whichever remaining item shares the fewest tokens with everything already picked, breaking ties toward a source or customer_segment not yet represented. Without it, "first five members" tends to surface five near-duplicates of the same template with one word swapped.severity.py: rule-based severity tagging from keyword lists, the item's rating (if any), and its source, all in one short, readable function.problem_statement.py: assemblesdraft_problem_statement's output from a theme's own member items with plain arithmetic; nothing in it is generated by a model.server.py: registers all six tools, the two resources (feedback://summary,feedback://item/{id}), and thetriage_this_weeks_feedbackprompt on anMCPServerinstance.auth.py: the bearer-token middleware for the HTTP transport.cli.py: theproduct-feedback-mcpentry point.
Auth
stdio (the default, and what both client configs above use): no auth at all. The client launches the server as a local subprocess it already controls and talks to it over that subprocess's own stdin/stdout. There is no network socket for anyone else to reach, so there is nothing to authenticate.
streamable HTTP: reachable over a socket, so it needs a check before
it will run a tool. This server implements the simplest one that is
still real: a single static token read from MCP_AUTH_TOKEN, required
as Authorization: Bearer <token> on every request
(src/product_feedback_mcp/auth.py). The server refuses to start over
HTTP at all if the variable is unset.
That is a real tradeoff, not a shortcut taken by accident. A static
bearer token has no expiry, no per-client scoping, and no revocation
short of rotating the value and redeploying. The mcp SDK also ships a
full OAuth authorization flow (TokenVerifier, AuthSettings,
protected-resource metadata) for exactly the cases that need those
things: multiple clients with different permissions, tokens that expire,
a real identity provider. For a single-tenant demo server backed by a
static local file, that machinery is a lot of moving parts for no
practical gain. If this server ever needed multiple callers with
different access levels, that is the point to switch.
Run it:
export MCP_AUTH_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uv run product-feedback-mcp --transport http --port 8000tests/test_auth.py covers both paths: a request with no token or the
wrong token gets 401, and a request with the right token gets past the
middleware.
Design decisions
Deterministic keyword clustering instead of an LLM call inside the server.
list_themesandget_themeneed to return the same answer every time for the same dataset, cheaply and offline, since the test suite and the demo script both depend on that. An LLM call would make every theme-clustering test either mocked (testing nothing real) or slow, flaky, and dependent on a paid API key just to runpytest. The tradeoff: keyword overlap is a much blunter instrument than an embedding model or an LLM's judgment, and it will split or merge themes a human would draw differently.search_feedbackandtag_severityalso stay on the same offline, deterministic footing for the same reason.Requiring two shared keywords to merge two items, not one. The first version of
themes.pymerged any two items that shared even one top keyword, which chained unrelated complaints together through a single common word (a generic verb, or a word two different templates both happened to use) into one oversized catch-all cluster. Requiring real overlap fixed that at the cost of some recall: two items about the same underlying problem, phrased differently enough to share only one keyword, end up in separate themes instead of one. Widening the dataset's phrasing (more sentence frames per theme, concrete detail fillers like a role or a time) then undercut that same fix from the other direction: a short sentence's top keywords started skewing toward whatever rare filler word it happened to use instead of the theme's real anchor term, soTOP_KEYWORDS_PER_ITEMwent from 3 to 8 to give that anchor term room to make the top set alongside the filler, and the "who/when" noise list (NOISE_TERMS) grew to cover the day, time, count, and role filler vocabulary the same way it already covered team names, so a coincidence like two unrelated items both mentioning "Monday morning" cannot count as a shared keyword.A theme's label is a trimmed real quote, not assembled keywords. The first version built labels from the cluster's top TF-IDF unigrams joined with
" / "("answer / real / minutes"), which is exactly as informative as it sounds: readable only to someone who already knows what the cluster is about. Keyword fragments cannot describe a theme in language because they are not language._first_clauseon the cluster's medoid item fixes that, at a real cost: a sentence with no early comma gets cut at a hard word cap, and the cut is made before the last subordinating word inside the cap ("...crashed constantly" rather than "...tried to clock"), which can drop useful detail from the label. The full quote is always one call away throughrepresentative_item_id, so the label trades completeness for readability on purpose. A sentiment-aware medoid restriction is layered on top of that for the same reason: a straight "most central item" medoid on a mostly-positive cluster with one complaint mixed in could just as easily land on the complaint, producing a negative-sounding label for a theme that is mostly praise. Restricting the medoid search to the cluster's majority side (median rating, or a small phrase list when nobody has one) fixes that at the cost of occasionally picking a slightly less central item than the unrestricted medoid would have.Evidence quotes are picked greedily for diversity, not just taken in order.
select_diverse_itemsstarts from the lowest-id member and repeatedly adds whichever remaining item overlaps least with what is already picked. The tradeoff: greedy is not globally optimal (a different starting point could occasionally produce a more diverse set of five), and it is still just a token-overlap heuristic, not a read for semantic diversity. It is enough to stop five near-identical praise quotes from crowding out the one real complaint in a cluster, which is the failure mode that mattered here.BM25 implemented in stdlib instead of a
rank_bm25dependency. The formula is small and well known, and writing it out means there is nothing to configure or version-pin for a dataset this size (a couple hundred short documents). The tradeoff is obvious: a real search product would use a maintained library, or a real search engine, rather than hand-rolled ranking code.A tool interface, not a raw file the client reads itself. An MCP client could just read
feedback.jsonldirectly if it had filesystem access. Tools instead give itsearch_feedback,list_themes, and the rest, which means the ranking, clustering, and severity logic are defined once, tested once, and identical no matter which client or model is calling them.A single static file as the datastore. No database, no ingestion pipeline. That is right for a demo server whose whole point is showing tool design against a fixed, inspectable dataset, and wrong for anything that needs to ingest new feedback continuously; swapping
dataset.py's file read for a real data source would not require changing any tool's interface.
Roadmap
A
refresh_datasettool or resource subscription so a long-running server picks up an updatedfeedback.jsonlwithout restarting.An optional embedding-based clustering backend behind the same
list_themes/get_themeinterface, for datasets where keyword overlap clusters too coarsely.Pagination for
search_feedbackandlist_themeson much larger datasets than the couple hundred rows this one ships with.A
--transport httpexample using a real reverse proxy in front of it (TLS termination, rate limiting) to show the auth tradeoff section in practice rather than only in prose.
Contributing
Issues and pull requests are welcome. Before opening a pull request:
uv run ruff check .
uv run ruff format .
uv run pytestIf you change scripts/generate_dataset.py, regenerate
data/feedback.jsonl and confirm tests/test_dataset.py's determinism
tests still pass; the committed dataset should always match what the
script currently produces.
License
MIT. See LICENSE.
Available Tools
6 toolsdraft_problem_statementA
Draft a structured, PRD-style problem statement for one theme.
Args: theme_id: a theme id as returned by list_themes, e.g. "theme-01".
Returns who is affected (customer segments and counts), what the problem is (a generated one-line description), evidence (representative quotes with feedback ids), frequency (count, percent of dataset, source breakdown, date range, average rating), and a suggested success metric derived from the theme's own volume. Every field traces back to real items in the dataset; nothing is invented. Raises ValueError if the theme_id does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| theme_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and mostly succeeds: it details the exact return contents, guarantees that nothing is invented, and documents ValueError for invalid theme_id. It does not explicitly state whether the operation is read-only or has side effects, leaving a small gap.
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?
The purpose is front-loaded and the Args/Returns/error structure keeps the information organized. The longer return-value sentence is justified because it enumerates the generated statement's contents and includes the no-hallucination guarantee; there is no 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?
For a single-parameter tool with an output schema, the description covers input source, output semantics, and failure mode. The only notable omissions are explicit read-only/side-effect behavior and guidance on sibling selection, but overall an agent has enough to invoke it 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 0%, so the description must compensate. It fully defines theme_id by giving its provenance (returned by list_themes) and a concrete example ('theme-01'), adding real meaning beyond the bare string type in 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?
The first sentence names a specific verb and resource: 'Draft a structured, PRD-style problem statement for one theme.' The detailed output description (who is affected, evidence, frequency, success metric) makes clear this is a synthesis tool, distinct from siblings like list_themes, get_theme, search_feedback, and severity_summary.
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?
The description implies usage by saying theme_id comes from list_themes and by framing the tool as a problem-statement generator for one theme. However, it does not explicitly state when to prefer this over get_theme or severity_summary, nor does it give any when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_themeA
Get one theme's member items, representative quotes, and source breakdown.
Args: theme_id: a theme id as returned by list_themes, e.g. "theme-01". Works for any theme the clustering produced, even one smaller than list_themes' default min_items.
Raises ValueError (surfaced to the client as a tool error) if the theme_id does not exist for the current dataset.
| Name | Required | Description | Default |
|---|---|---|---|
| theme_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It discloses that it raises ValueError if theme_id doesn't exist, and that it works for any theme regardless of size. It doesn't mention side effects or read-only nature, but the name 'get' implies read-only. The error behavior is a key transparency element.
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?
The description is concise, starts with the action and key data, then explains the argument and error. No unnecessary words. Front-loaded.
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?
For a single-parameter tool with an output schema, the description covers the input semantics, the error case, and the scope. It also connects to list_themes. Nothing critical 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 provides no description for theme_id (0% coverage). The description fully compensates by specifying the source (list_themes), an example format, and the domain (any theme produced by clustering). This adds meaning 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 (Get) and resource (theme) and lists the exact data returned (member items, representative quotes, source breakdown). This clearly differentiates from the sibling list_themes by focusing on a single theme's details.
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?
Mentions that theme_id comes from list_themes, implying the intended workflow of listing first then getting details. Also notes it works for themes smaller than list_themes' default min_items, which is a usage nuance. However, it doesn't explicitly say when to use this vs alternatives, but the reference to list_themes is a strong cue.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_themesA
List feedback themes with at least min_items items, largest first.
Themes are computed deterministically from keyword and phrase overlap across feedback text (TF-IDF plus keyword-cluster grouping), with no LLM involved, so the same dataset always produces the same themes. Each theme's label is a trimmed first clause of its most representative real item (its "medoid"), not a synthetic phrase; representative_item_id names that item so you can look it up via feedback://item/{id}. Use get_theme(theme_id) for the full detail on one theme, including representative quotes.
| Name | Required | Description | Default |
|---|---|---|---|
| min_items | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so well. It explains that themes are computed deterministically (no LLM), that labels are trimmed medoids of real items, and that representative_item_id enables lookup. It also states the sort order. It does not mention potential side effects or permissions, but for a read-only list operation this is adequate.
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?
The description is well-structured: the core purpose is front-loaded in the first sentence, followed by necessary algorithmic detail and a routing hint. Each sentence adds value, with no filler. The technical explanation is compact and relevant, and the pointer to get_theme is efficient.
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?
Given the existence of an output schema (which presumably documents the return format), the description does not need to detail response structure. It does explain the meaning of key fields (label, representative_item_id) and provides a lookup mechanism. It does not mention pagination or limit behavior, but with a single optional parameter, the tool likely returns all matching themes, so the definition is adequate.
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?
The schema only defines `min_items` as an integer with a default, and schema description coverage is 0%. The description clarifies that `min_items` is a threshold for inclusion ('with at least `min_items` items'), adding meaningful semantics beyond the type definition. It does not discuss edge cases (e.g., negative values), but for a single parameter this is sufficient.
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?
The first sentence states a specific action (list feedback themes) with a clear condition (at least `min_items` items) and ordering (largest first). This is a precise verb+resource combination that distinguishes it from siblings like get_theme (single theme) and search_feedback (search), so an agent can immediately understand its scope.
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?
The description explicitly directs users to `get_theme(theme_id)` when full detail on one theme is needed, which is a helpful alternative. However, it does not mention other siblings like search_feedback or severity_summary, nor does it state when list_themes is preferable to them. The primary use case is clear, but exclusion or comparison guidance is incomplete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_feedbackA
Keyword search over feedback text, ranked with BM25.
Args: query: free-text search query, e.g. "shift swap approval". source: optional exact filter: support_ticket, app_review, sales_call_note, or nps_comment. segment: optional exact filter: small_business, mid_market, or enterprise. limit: maximum number of results to return (default 20).
Returns feedback items ordered by relevance (highest BM25 score first), each as a dict with id, source, created_at, customer_segment, text, rating (if present), and score.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| source | No | ||
| segment | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the ranking behavior (BM25), the ordering by relevance, and the exact return structure (dicts with id, source, created_at, customer_segment, text, rating, score). It does not mention auth or rate limits, but the read-only search nature is clear.
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?
The description is concise and front-loaded with a one-line summary, followed by structured argument documentation and a clear return description. Every sentence adds value with no redundancy.
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?
The description is complete for a read-only search tool. It covers all parameters, filters, ranking behavior, and return format. Even with an output schema present, the description adds necessary context about BM25 scoring and optional rating field.
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 description coverage is 0%, so the description must compensate. It provides detailed meaning for all four parameters: query with a concrete example, source and segment with explicit allowed values, and limit with its default and meaning. This is a strong compensation for the bare 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?
The description clearly states a specific verb ('search'), resource ('feedback text'), and method ('ranked with BM25'). This distinguishes it from sibling tools like list_themes, get_theme, and severity_summary, which focus on themes and severity rather than free-text search.
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?
There is no explicit guidance on when to use this tool versus alternatives. The purpose alone implies search tasks, but no exclusions or alternatives are mentioned. Siblings are not directly search-related, but the description could still note that this is the only tool for free-text feedback search.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
severity_summaryA
Summarize severity across the whole dataset.
Returns item counts by severity level, a breakdown by source and by customer segment, and the feedback ids of the ten highest- scoring critical/high items to look at first.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses meaningful behavior by detailing exactly what the tool returns, including item counts, breakdowns, and prioritized feedback IDs. It does not explicitly state that it is read-only, but 'summarize' and 'returns' strongly imply no mutation, so this is a minor omission.
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?
The description is two sentences, with the core purpose front-loaded in the first sentence and the output details packed efficiently into the second. Every sentence adds value and there is no redundant or filler text.
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?
For a zero-parameter aggregation tool with an output schema, the description is quite complete: it states scope and enumerates the returned information. It could additionally say when to use this instead of search_feedback or tag_severity, but that gap is largely covered by the visible sibling context and the clear aggregate nature of the tool.
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?
The input schema has zero parameters, so there is nothing for the description to clarify. The description's 'whole dataset' confirms the tool takes no filtering scope, which is consistent with the empty properties object. Baseline 4 is appropriate for a no-parameter tool.
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?
The description opens with a specific verb and resource: 'Summarize severity across the whole dataset.' It then enumerates concrete outputs (counts by severity level, source/customer breakdowns, top ten IDs), making it unmistakable what this tool does and distinguishing it from sibling tools like search_feedback or tag_severity.
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?
The phrase 'whole dataset' implies this is for a high-level overview rather than for searching individual feedback, tagging severity, or listing themes. However, it never explicitly states when to prefer this tool over its siblings or mentions any exclusions, so the usage context is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
tag_severityA
Tag one feedback item's severity (critical, high, medium, or low).
Args: feedback_id: an id from the dataset, e.g. "fb-0001".
Severity is rule-based, from keywords in the text, the item's rating if it has one, and its source. Returns the severity label, a numeric score, the keywords that matched, and a plain-language rationale. Raises ValueError if the id does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| feedback_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden. It explains that severity is rule-based from keywords, rating, and source, lists the exact return fields, and notes ValueError for missing IDs. It does not explicitly state whether the tag is persisted, which would be useful for a tool named 'tag,' but the read-like computed behavior is clear.
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?
Every sentence earns its place: the purpose is front-loaded, the argument is explained with an example, the rule mechanism and outputs are summarized, and the error case is included. There is no redundant or filler content.
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?
For a single-parameter tool with an output schema, the description is complete enough: it covers the input, the rule basis, the returned values, and the error behavior. It could add an explicit note about side effects and when to prefer severity_summary, but these are minor gaps given the tool's simplicity.
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?
The input schema only declares feedback_id as a string, so the description adds needed meaning by defining it as 'an id from the dataset, e.g. "fb-0001"'. This is sufficient compensation for the 0% schema description coverage on the single parameter.
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?
The description opens with a specific verb and resource: 'Tag one feedback item's severity (critical, high, medium, or low).' This makes the tool's individual-item scope clear and distinguishes it from the aggregate-sounding sibling severity_summary, though it does not explicitly name that alternative.
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?
Usage is implied through 'one feedback item,' suggesting this is for a single item rather than summary or search operations, but the description gives no explicit when-to-use or when-not-to-use guidance and does not mention alternate siblings like severity_summary or search_feedback.
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.
6 tool updates
v0.1.0- First observed
draft_problem_statement - First observed
get_theme - First observed
list_themes - First observed
search_feedback - First observed
severity_summary - First observed
tag_severity
TDQS
Scored across 6 tools
Each tool has a clearly distinct purpose: search, theme listing, theme detail, severity tagging, severity summary, and problem statement drafting. No two tools overlap in functionality, and descriptions make selection unambiguous.
All tools follow the same verb_noun snake_case pattern (search_feedback, list_themes, get_theme, tag_severity, severity_summary, draft_problem_statement). The naming is consistent and predictable.
Six tools is well within the ideal range for a focused product-feedback analysis server. Each tool covers a distinct stage of the analysis workflow, and none feel redundant or missing.
The tool surface covers the core analysis lifecycle: search, theme discovery, theme inspection, severity tagging, aggregate severity, and problem statement generation. A direct get_feedback_item is missing but search_feedback already returns full item details, so this is a minor gap.
Maintenance
Related MCP Connectors
Analyze customer feedback at scale — reviews, surveys, calls. AI-powered themes and sentiment.
- SquadOAuthai.meetsquad
Decision intelligence for product teams. Turn scattered feedback into signal you can act on.
Read your Usero feedback inbox and clusters, file feedback, build forms, and open an AI PR.
Turn raw customer feedback into evidence-cited specs (free, no key) plus 16 PM tools.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceTransforms scattered customer feedback from sources like Slack, Zoom, and JIRA into actionable product insights and AI-generated PRDs. It features over 50 tools for semantic clustering, sentiment analysis, and VOC-based prioritization to streamline product management workflows.1MIT
- AlicenseNot gradedqualityAmaintenanceProduct management copilot that connects Claude to HelpScout support tickets and ProductLift feature requests. Synthesizes customer feedback across sources, scores themes by convergence, and generates prioritized product plans.30MIT
- AlicenseNot gradedqualityCmaintenanceEnables semantic search and grounded answering over customer-research interviews, with every answer traceable to source quotes.MIT
- AlicenseNot gradedqualityAmaintenanceEnables Claude to search locally indexed exported chat history and project docs, retrieving full messages and conversation IDs via full-text search.1MIT