Skip to content

feat(mcp): expose get_competitor_comparison tool (with share of voice) + REST endpoint#116

Merged
gkhngyk merged 1 commit into
mainfrom
feat/mcp-get-competitor-comparison
May 28, 2026
Merged

feat(mcp): expose get_competitor_comparison tool (with share of voice) + REST endpoint#116
gkhngyk merged 1 commit into
mainfrom
feat/mcp-get-competitor-comparison

Conversation

@gkhngyk

@gkhngyk gkhngyk commented May 28, 2026

Copy link
Copy Markdown
Contributor

Closes #98. Third A-group MCP read tool — combines the competitor benchmark and share-of-voice surfaces into a single tool call.

What

  • New MCP tool: `get_competitor_comparison(brand_id, ...)` → brand + every tracked competitor with avg visibility / mentions / citations / appearance count, plus overall SoV and per (model_used, platform) SoV split.
  • New REST endpoint: `GET /api/mcp/competitor-comparison` — same data fn, query-param mirror of the MCP args.

Why

The assistant epic (#94) explicitly needs this tool to answer "who's gaining share of voice?" and "how do I compare to competitor X?" — both currently dashboard-only. Bundling competitor benchmark and SoV in one call also saves the assistant an extra round trip vs. two separate tools.

Shape of the response

```json
{
"brand": {
"id": "...", "name": "...",
"avg_visibility_score": 76,
"total_mentions": 26913, "total_citations": 20847,
"appearance_count": 5054
},
"competitors": [
{
"competitor_id": "...", "name": "...",
"avg_visibility_score": 81,
"total_mentions": 21788, "total_citations": 14201,
"appearance_count": 5054
}
],
"share_of_voice": {
"overall_sov_pct": 18.2,
"total_brand_mentions": 26913,
"total_competitor_mentions": 121335,
"by_platform": [
{ "model_used": "...", "platform": "...",
"brand_mentions": ..., "competitor_mentions": ..., "sov_pct": ... }
]
}
}
```

Auth + safety

Ownership check first via `supabaseAdmin` (same brand-belongs-to-org pattern as the other MCP data fns). Wrong-org or missing `brand_id` returns null (→ 404) before either RPC fires — no data leakage, no wasted DB work. Both RPCs (`competitor_aggregates`, `share_of_voice_aggregates`) are SECURITY DEFINER per the existing project pattern; the membership-enforcement question is tracked in #115 and applies to every aggregate RPC uniformly, not just this new tool.

Why a snapshot, not a delta

V1 returns a single-window snapshot — same shape contract as the existing `get_visibility_summary` / `list_topics` / `list_prompts` MCP tools. A caller that wants a delta (e.g. last 30 days vs the 30 before) makes two tool calls with different `date_from`/`date_to` and diffs in its own reducer. Building delta into the tool surface adds an opinionated 7-day default that wouldn't generalize cleanly across MCP clients.

Performance

Two existing DB-side aggregate RPCs in parallel (`Promise.all`). No JS-side row-by-row reduction; no `select('*')` from `prompt_results`. Brand + per-competitor / per-platform aggregates come back as JSONB. Same shape the dashboard already consumes since #114 — re-uses that work end-to-end.

Files

File Change
`web/src/lib/mcp/data.ts` New `getCompetitorComparisonFor` data fn + `CompetitorComparisonParams` / `CompetitorComparisonOutput` types
`web/src/lib/mcp/server.ts` Registers the `get_competitor_comparison` tool with a zod schema covering brand_id + optional window/region/model/topic filters
`web/src/app/api/mcp/competitor-comparison/route.ts` New GET route sharing the same data fn

How verified

Local e2e against the cloud project:

  • ✅ RPC field names match the consumed `CompetitorAggRpc` / `SoVAggRpc` interfaces (verified via `jsonb_object_keys` introspection on both aggregates)
  • ✅ End-to-end sample on the largest brand in prod (5054 prompt result appearances, 7 competitors, 10 (model, platform) combos) returns the documented shape with sane numbers — brand avg visibility 76, top competitor avg 81, overall SoV 18.2%
  • ✅ `yarn typecheck` + `yarn format:check` clean
  • ✅ Smoke curls cover: 200 happy path, 200 with `date_from` filter, 401 (missing key), 400 (missing `brand_id`), 404 (wrong-org brand)

Out of scope (per #98)

  • UI changes
  • Provider-name normalization (resolveProvider) — tool returns raw `model_used` / `platform`; the LLM can interpret or we add a provider-info helper later
  • Per-competitor SoV breakdown — overall + per-platform covers the "who's gaining" question; per-competitor SoV is a different lens that can ship later if needed

Up next

After this lands, #97 (`list_citations`) and #96 (`get_visibility_trend`) are the remaining A-group tools the assistant epic #94 needs.

…) + REST endpoint

Adds the third A-group MCP read tool from the assistant-readiness list,
after the content-opportunity tools shipped in #74 / #109 / #110.

Combines two existing RPCs (competitor_aggregates and
share_of_voice_aggregates, both DB-side aggregated since #114) so the
tool answers "how do I compare to my competitors?" and "who's gaining
share of voice?" in a single round trip. Ownership-check first via
supabaseAdmin (wrong-org brand → null → 404, no RPC fire). Snapshot
shape — deltas are out of scope for V1 and consistent with the existing
get_visibility_summary / list_topics / list_prompts shape; a caller that
wants a delta issues a second call with an earlier window.

- web/src/lib/mcp/data.ts: getCompetitorComparisonFor(auth, params) —
  parallel RPC calls, computes brand + per-competitor averages
  (when-present semantics matching the UI), and per-(model_used,
  platform) SoV split. Uses the same competitor display-name fallback
  pattern as the existing competitor logic (name ?? competitor_id) so
  unnamed competitors don't collide.

- web/src/lib/mcp/server.ts: registers the get_competitor_comparison
  tool with a description that covers both questions the tool answers
  and the snapshot-vs-delta caveat.

- web/src/app/api/mcp/competitor-comparison/route.ts: GET handler
  sharing the same data fn; query params mirror the MCP tool args.

Verified end-to-end against the largest brand in the cloud project
(5054 prompt result appearances, 7 competitors, 10 distinct model/
platform combos) — returned shape matches the documented interface,
SoV totals + per-platform breakdown reconcile with the existing UI.

Closes #98.
@gkhngyk gkhngyk merged commit 3ced376 into main May 28, 2026
4 checks passed
@gkhngyk gkhngyk deleted the feat/mcp-get-competitor-comparison branch May 28, 2026 19:34
gkhngyk added a commit that referenced this pull request May 31, 2026
Bundles the in-product Agent epic (#120, #121), the BYOK rollout on
cloud, the MCP toolkit expansion (#109, #110, #116, #117, #118), the
insights performance fix (#114), and the invite-accept flow rebuild
(#127, #129, #130) into a tagged release.

See CHANGELOG.md for the full notes.
gkhngyk added a commit that referenced this pull request May 31, 2026
Bundles the in-product Agent epic (#120, #121), the BYOK rollout on
cloud, the MCP toolkit expansion (#109, #110, #116, #117, #118), the
insights performance fix (#114), and the invite-accept flow rebuild
(#127, #129, #130) into a tagged release.

See CHANGELOG.md for the full notes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): expose get_competitor_comparison tool (with share of voice) + REST endpoint

1 participant