# io.github.brownglasses/dotplot-mcp (pypi · dotplot-mcp)

Product analytics for user flows: aha moments, retention, funnels, per-user dot plots. YC method.

- Trust score: 69/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-17

## Components

- pypi · `dotplot-mcp`: 69/100 (this document), [markdown](https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp.md), [page](https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp)

## Channel facts

- Registry: `pypi`
- Package: `dotplot-mcp`
- Version: `0.1.4`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-17.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs setuptools.build_meta at install time, a recognised native-build step with no shell scripting around it.
  - 2 of 30 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 3 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 80/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 2399 tokens (~149/item across 16 items; 16 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 13/100
  - Stability observed for 4 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 71/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
  - Structured output schemas are declared (13% of tools); any adoption earns full credit.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### Claude

```bash
claude mcp add brownglasses-dotplot-mcp -- uvx dotplot-mcp
```

### Codex

```bash
codex mcp add brownglasses-dotplot-mcp -- uvx dotplot-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "brownglasses-dotplot-mcp": {
      "type": "local",
      "command": [
        "uvx",
        "dotplot-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add brownglasses-dotplot-mcp --command uvx --arg dotplot-mcp
```

### Hermes

```yaml
mcp_servers:
  brownglasses-dotplot-mcp:
    command: "uvx"
    args: ["dotplot-mcp"]
```

### Other

```json
{
  "mcpServers": {
    "brownglasses-dotplot-mcp": {
      "command": "uvx",
      "args": [
        "dotplot-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-16 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-08-14 (score 68, +4)

- [functional improvement] Stability: unverified → 0.03
- [functional] First check of Schema quality: 100
- [functional] Schema quality: good → excellent
- [functional] Package version: 0.1.1 → 0.1.4
- [functional] Package version: 0.1.1 → 0.1.2

### 2026-08-13 (score 64)

First indexed and scored.

## MCP tools (16)

### `analyze` (~362 tokens)

START HERE. Event data in, finished report out, one call.

Use this whenever the user asks anything general — "analyze my product",
"how are my users doing", "find my aha moment". The other tools are parts;
this is the whole thing. Only reach for them when the user asks for one
specific number ("just show me retention").

It picks a value event, draws the dot plot, finds the aha moment, builds the
funnel and retention curve, and writes the HTML report — then tells you what
it found so you can say it out loud.

csv_path: a CSV with user_id, date, event (platform optional).
  No CSV yet? Call with no arguments and follow the instructions you get
  back — for a project with a database you will explore its schema and turn
  ordinary business tables (orders, sessions, posts) into events with
  load_from_db. Most early products have no events table; that is expected.

value_event: the action that means "this user got real value".
  Leave it out and the code picks the candidate most users repeat. Pass it
  yourself when you have read the codebase and know better — you can tell
  \`purchase` from `view_item` and the code cannot. The result always names
  what was chosen and what else was available, so you can call again with a
  different one if the choice looks wrong.

lang: the user's language. en/ko/ja are built in; for any other language
  call get_report_strings, translate, and use generate_report directly.

Input parameters:

- `csv_path`
- `lang` (string)
- `output_path` (string)
- `value_event`

### `describe_events` (~70 tokens)

Shape of an event CSV: date range, user count, events by type.

Mostly useful when you want to look before choosing a value event yourself.
For a normal "analyze my product" request call analyze instead — it does
this step and everything after it.

Input parameters:

- `csv_path` (string, required)

### `dot_plot` (~91 tokens)

Text dot plot: one row per user, one cell per day.

◎ first active day, ● value event, · nothing. mark_events puts a letter on
other actions, e.g. {"create_playlist": "P"}.

Input parameters:

- `csv_path` (string, required)
- `lang` (string)
- `mark_events`
- `value_event` (string, required)
- `weeks` (integer)

Output parameters:

- `result` (string)

### `classify_users` (~75 tokens)

Sort users by behaviour: churned (used once, never returned),
weekend_only, regular (almost daily), casual.

lang sets the human-readable labels (en/ko/ja); the keys stay in English.

Input parameters:

- `csv_path` (string, required)
- `lang` (string)
- `value_event` (string, required)

### `find_aha_moments` (~87 tokens)

Scan every action for the one that turns users into regulars.

Ranked by behaviour_change — how much a user's own activity rose after they
first did it — because comparing groups (lift) rewards actions that frequent
users happen to do. Tell the user this is correlation, not cause.

Input parameters:

- `csv_path` (string, required)
- `value_event` (string, required)

### `audit_tracking` (~369 tokens)

Check what the code logs, and — if there is data — whether it arrives.

Use this when analyze says there is nothing to work with. A project that
tracks nothing has no data to analyse, but it has code to read, so leave
csv_path out and this still answers.

How to use it:
1\. Search the codebase for logging calls yourself. Common shapes:
   logEvent(...), track(...), analytics.capture(...), posthog.capture(...),
   gtag('event', ...), mixpanel.track(...)
2\. Pass the event names you found as code_events.
3\. Read the result:
   \- in_code_never_fired  logging is broken, or nobody uses that feature
   \- in_data_not_in_code  dead code, or your search missed it (look again)
4\. Then find what has no logging at all — button handlers and core actions
   that should be recorded and aren't. That gap won't appear in either list,
   and it is usually the important one.
5\. Prescribe, don't just report. For each hole write the one line of logging
   that belongs in that file, in that function, matching the surrounding
   style, show it, and ask whether to add it. Follow the project's existing
   naming (follow_artist if it is snake_case, followArtist if camelCase).
6\. Once logging is in, tell them when to come back. come_back_in_days is in
   the result and is not a guess — it is how long the code must watch a user
   before it can honestly call them churned or a regular. Saying "run this
   again tomorrow" would produce a report with nothing in it.

Input parameters:

- `code_events` (array, required)
- `csv_path`

### `get_report_strings` (~101 tokens)

Every sentence the report can contain, in English, for translation.

For a language other than en/ko/ja:
1\. call this,
2\. translate the values — leave {n}, {rate_did} and every other brace
   placeholder exactly as they are, that is where the numbers go,
3\. pass the result to generate_report as custom_strings with lang="custom".
The code checks the placeholders survived, so the statistics stay exact.

### `generate_report` (~249 tokens)

Write the HTML dot plot report — readable in three seconds, made to share
with a team or an investor.

analyze calls this for you. Use it directly when you need to control the
marks, the window, or a language that isn't built in.

Marks on other actions:
\- default: the top aha events are picked automatically (behaviour change
  of 30 points or more, at most two, so the report stays quiet no matter
  how many event types exist)
\- your own: mark_events={"create_playlist": "P"}, described by mark_labels
\- none at all: mark_events={}

Language: match the conversation. lang="en"|"ko"|"ja" are built in; for any
other language translate get_report_strings() and pass it as custom_strings
with lang="custom". Never translate the {brace} placeholders.

Input parameters:

- `csv_path` (string, required)
- `custom_strings`
- `lang` (string)
- `mark_events`
- `mark_labels`
- `max_users` (integer)
- `output_path` (string)
- `value_event` (string, required)
- `weeks` (integer)

Output parameters:

- `result` (string)

### `onboarding_funnel` (~66 tokens)

Onboarding funnel: signup -> first value -> came back -> still active.

Answers where new users leak. A large median_days_to_value means friction
between signing up and getting anything out of the product.

Input parameters:

- `csv_path` (string, required)
- `value_event` (string, required)

### `retention_curve` (~91 tokens)

Weekly retention: share of users with a value event in week N.

Only users old enough to have reached week N count toward it, and weeks with
fewer than five of them are dropped rather than reported as noise. Where the
curve flattens is the retention that holds.

Input parameters:

- `csv_path` (string, required)
- `max_weeks` (integer)
- `value_event` (string, required)

### `load_from_db` (~232 tokens)

Pull events out of the project's database into a CSV.

How to use it:
1\. The connection string comes from DOTPLOT_DB_URL by default. Tell the user
   to export it — never ask them to paste a password into the chat:
   export DOTPLOT_DB_URL="postgresql://readonly:...@host:5432/db"
   (Supabase: Dashboard > Settings > Database > Connection string)
2\. Read the schema first (information_schema), find the tables that record
   what users did, and shape them into user_id, date, event. Most products
   have no events table — ordinary business tables are the event log:
   SELECT user_id::text, created_at::date AS date, 'purchase' AS event FROM orders
   Combine several actions with UNION ALL.
3\. Only SELECT runs; anything else is refused. Recommend a read-only role.

Supports postgresql:// (Supabase, RDS, Neon) and sqlite:///path for testing.

Input parameters:

- `db_url`
- `output_csv` (string)
- `query` (string, required)

### `history_compare` (~86 tokens)

Compare today's numbers with the last analysis of the same data.

Snapshots are saved to ./.dotplot/history.json every time a report is made.
Only snapshots of the same dataset are compared, so the first run of a new
project has nothing to compare against — say so rather than implying zero
change.

Input parameters:

- `csv_path` (string, required)
- `value_event` (string, required)

### `find_similar_cases` (~150 tokens)

Find companies that hit the same problem and what they changed.

A small library curated from public material — YC talks, First Round, founder
interviews. When you pass a case on:
\- explain matched_on, so the user knows why it came up
\- cite the source, and say the figures are second-hand
\- offer to apply the fix to their code (e.g. move the aha action into
  onboarding)
industry: b2c|b2b|commerce|content|social|tool|game|other — a match in the
same industry ranks higher.

Input parameters:

- `csv_path` (string, required)
- `industry`
- `value_event` (string, required)

### `publish_report` (~139 tokens)

Host the report and return a shareable link.

ASK THE USER FIRST. This puts their product's numbers on the public web.
Only run it when they have said they want a link.

The report goes to a random path in a Vercel project and is deployed with
\`vercel deploy --prod`. The random path plus a noindex header means only
someone with the link can reach it and search engines won't list it — but it
is still the open internet. Requires the vercel CLI to be logged in. To take
one down, delete the file under r/ and deploy again.

Input parameters:

- `html_path` (string, required)

### `submit_benchmark` (~157 tokens)

Submit five aggregate numbers to the anonymous benchmark. Opt-in.

Set consent=True only after the user has explicitly agreed. When you ask,
show them exactly what leaves the machine — user count, churn rate, weekend
rate, regular rate, aha lift, and nothing else. No user IDs, no event log,
no product name.

industry: b2c|b2b|commerce|content|social|tool|game|other
stage: pre_launch|under_100_users|under_1k_users|over_1k_users

Input parameters:

- `consent` (boolean)
- `csv_path` (string, required)
- `industry` (string, required)
- `stage` (string, required)
- `value_event` (string, required)

### `compare_benchmark` (~74 tokens)

Compare your numbers with percentiles from teams at the same industry
and stage.

Below ten teams the percentiles mean nothing — say so plainly instead of
reporting them.

Input parameters:

- `csv_path` (string, required)
- `industry` (string, required)
- `stage` (string, required)
- `value_event` (string, required)

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp#diagnostics

## Score history

- 2026-08-17: 69
- 2026-08-16: 69
- 2026-08-15: 68
- 2026-08-14: 68
- 2026-08-13: 64

## Links

- PyPI project: https://pypi.org/project/dotplot-mcp/
- Socket report: https://socket.dev/pypi/package/dotplot-mcp
- Repository: https://github.com/brownglasses/dotplot-mcp
- Changelog RSS feed: https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp.json
- HTML version of this page: https://verifymcp.io/servers/brownglasses-dotplot-mcp/dotplot-mcp
