PRD · Roster · Phase 1

Roster MCP Server — Claude Connector

A remote MCP server at mcp.getroster.com exposing eleven curated, read-only tools over the existing v2 public API, fronted by a new OAuth 2.1 authorization service that reuses Brand Portal login for consent. Launched as a documented custom-connector URL brands add to Claude themselves — no tokens, no Anthropic approval gate.

Author Jeff Poulton Created 2026-06-09 Updated 2026-06-10 Status Draft Tool specs tool-specs.html MCP & OAuth mcp-and-oauth.html Research research.md
Tools
11 · read-only
curated, task-shaped
Auth
OAuth 2.1
DCR + PKCE — Claude requires it
Surfaces
claude.ai · Cowork
+ Desktop, Claude Code
Adoption target
20 brands
within 60 days of launch
01

Problem & solution

Problem

Brands increasingly work inside Claude (claude.ai, Claude Cowork) and want their Roster data there — today their only options are manual CSV exports or hand-rolled scripts against the v2 API with pasted tokens. Claude's hosted surfaces don't support static API keys at all, so without an OAuth-capable MCP server, Roster simply cannot be connected to Claude.

Solution

A decoupled MCP + OAuth stack that acts as a normal client of the existing v2 API. Brands click Add custom connector, log in with their existing Brand Portal credentials, approve read-only access, and ask Claude things like "what's my ambassador program ROI this quarter?" — the #1 churn-cited gap.

02

Success

GoalMetricTarget
Adoption Brands with an active Claude connection 20 brands within 60 days of launch
Engagement Brands with ≥1 MCP tool call in trailing 7 days 50% of connected brands
Retention signal Connected brands citing ROI-visibility in cancellation requests Directional decrease (qualitative; baseline 27%)
Trust Tool results disagreeing with portal report numbers 0 known discrepancies — numbers come from the same API the portal uses
03

Scope

In
  • New remote MCP server (Streamable HTTP, public HTTPS) with a curated read-only tool catalog
  • New OAuth 2.1 authorization service (DCR, PKCE, RFC 8414/9728 metadata) with Brand Portal login + brand picker as the consent flow
  • Server-side credential bridge: OAuth grant → Roster API session, held by the MCP service, never exposed to the client
  • Connection visibility + revocation in the Brand Portal's existing API integration page
  • Usage logging/analytics per brand and per tool
  • Help-doc article + internal CSM enablement notes
Out — later phases
  • Anthropic Connectors Directory submission (Phase 2 — but Phase 1 must not block it: tool annotations, privacy-policy link, and OAuth shape are directory-compliant from day one)
  • Write tools (tagging, segmenting, contact updates) and any write scopes
  • Cowork plugin bundle, Claude Code plugin, ChatGPT/other MCP clients (should work incidentally; not tested or supported)
  • Fine-grained per-resource scopes (single read-only scope in Phase 1)
  • MCP prompts/resources beyond tools; scheduled/push reports

Areas affected

Brand Portal Ambassador Portal REST API Backend Services Integrations Post-deploy Scripts Support Docs
04

Architecture

The MCP + OAuth stack is fully decoupled from Roster core: it's a normal API client with no database or shard access. The dashed green path runs once per connection (the OAuth grant and credential bridge); the solid path runs on every tool call.

Claude claude.ai · Cowork · Desktop MCP server mcp.getroster.com · 11 tools OAuth 2.1 service DCR · PKCE · consent + brand picker Brand Portal login existing credentials · no new store v2 Open API api.getroster.com · App.WebApi.Open Credential bridge bridged ApiSession · encrypted Global DB + shards ApiSession · MCP source flag tool calls Bearer · bridged token SELECT-scoped to brand load credential authorize + PKCE login + consent mint grant → bridge token

Solid grey = per-tool-call request path. Dashed green = one-time OAuth grant: Claude discovers the authorization service via RFC 9728/8414 metadata, registers via DCR, the user logs in with portal credentials and approves; the service mints its own short-lived JWT + rotating refresh token for Claude and bridges the grant to a Roster ApiSession held server-side only.

05

Tool catalog

All tools are scoped to the connected brand — no brand parameter; identity rides on the token, making cross-tenant leaks structurally impossible at the tool layer. Date filters are explicit ISO dates — Claude resolves relative ranges in the user's timezone before calling (Decision 5); omitted dates default to UTC last 30 days to match the portal. Data must come from the same v2 API paths the portal reports use — never recomputed in the MCP layer. Every tool declares readOnlyHint: true. Full per-tool definitions (input schemas, response shapes, upstream mappings): tool-specs.html.

Portal tie-back (required on every report/dashboard tool): the response includes a portal_source object — the human name of the portal surface it mirrors ("Program Dashboard", "Sales Attribution report"), the resolved date range, and a deep link to the corresponding portal page (e.g. app.getroster.com/programs/{id}/dashboard) — so a brand can verify any number in one click and always knows exactly where it came from. The catalog deliberately favors report/dashboard-shaped tools over raw-row endpoints: each performance tool maps 1:1 to a named portal surface.

#ToolWhat it returns / key paramsUse case
1 list_programs Programs with member counts, applicant counts, join requirements. Cheap — and Claude needs it to resolve "my VIP program" → program_id for other tools' filters. Id resolution for filtered queries
2 get_program_performance NEW API The portal's Program Dashboard for one program (/programs/{id}/dashboard), mirroring the brand's saved dashboard configuration (Decision 6): the same cards the brand sees when they log in (typically applicants, members, first-time logins, post mentions/engagements/impressions, EMV, points, referred revenue), each with a time series, period total, and delta vs the preceding period. Params: program_id, start_date, end_date (default last 30 days, matching the dashboard), optional metrics filter. Runs the same per-card queries as the dashboard — no new SQL. Program health / ROI narrative with exact portal tie-back — primary use case
3 list_campaigns Campaigns with status, dates, participant counts. Navigation / id resolution
4 get_campaign_performance NEW API Single campaign, mirroring the portal's campaign overview (/campaigns/{id}/analytics/overview — Decision 7; the all-campaigns "Campaign Performance Dashboard" is explicitly not used): invite funnel (added, emails sent/opened, joined, completed, participation/completion rates), content generated (posts/stories/uploads with likes/comments/views), reach and engagement by network, EMV, and reward fulfillment counts. No attributed revenue (Decision 8 — not a Roster concept at campaign level; revenue questions route to get_sales_attribution_report). Campaign post-mortems & comparisons
5 list_ambassadors NEW API Paginated contact search (wraps the portal's contact search): query (name/email), program_id, tag, status, joined_after/before, sort (referral_revenue | posts | joined_date | followers | engagement — no EMV sort, Decision 9; EMV leaderboards come from get_social_posts_report). Full fields: email, phone, social handles with follower counts, tags, custom properties, program memberships, lifetime referral revenue. The existing /v2/contacts has no search/sort/performance fields. Leaderboards; list export to SMS/Klaviyo
6 get_ambassador NEW API Single contact by id or email, mirroring the portal's contact detail page: profile, socials, tags, custom properties, program memberships plus performance (attributed orders/revenue, total commission, posts + engagement, rewards earned/redeemed, referral links and discount codes, last activity date). The existing /contact/{contactId} endpoint carries no performance data and isn't useful on its own. Ambassador deep-dive, "who went quiet?"
7 get_sales_attribution_report NEW API The portal's Sales Attribution report: one row per ambassador with activity in range — link clicks, new customers, referred orders/revenue, commissions and points, personal orders/revenue — plus grand totals. Params: start/end_date, program_id, contact_id, tag, attribution_methods (emailAddress | rewardCode | referralLink | discountCode | recurringOrder — rows always carry both referred and personal columns, matching the portal), sort. Aggregate report rows, not raw orders. Attribution drill-down, revenue questions, exec reporting
8 get_social_posts_report NEW API The portal's Social Posts report: one row per ambassador who posted in the range (Decision 9 — portal parity; per-post rows are get_social_feed_posts), with post/story counts, reach, impressions, likes, comments, shares, saves, engagement rate, and EMV (confirmed available — stored per-post in UserSocialListeningPostEMV), plus grand totals. Params: start/end_date, platform, program_id, campaign_id, contact_id, tag, sort (any metric incl. emv), pagination. Content analysis, EMV leaderboards, campaign comparisons
9 get_social_feed_posts NEW API Recent posts from the portal's Social Feed: post URL/media, caption, platform, post type (post/story/reel), ambassador, program, posted date, engagement metrics and per-post EMV. Params: start/end_date, platform, program_id, contact_id, sort, pagination. "What are my ambassadors posting right now?" — live-feed complement to the report view
10 search_help_docs Keyword search over the published help-docs corpus (docs.getroster.com), returning excerpts + canonical URLs. Setup/troubleshooting copilot; deflects "how do I…" mid-analysis
11 get_connection_info No params. Returns the connected brand (name, domain), the authorizing user, the access scope (read-only), and grant date. Brand-context visibility for multi-brand users — "which brand is this connection?" — and a cheap connectivity check

NEW API = requires a new v2 Open API endpoint (confirmed scope — Decision 2; the full audited list E1–E9 with sizes is in tool-specs §13). Dropped from Phase 1 (revisit on demand): list_orders and list_commissions — raw-row tools superseded by get_sales_attribution_report for the aggregate need; the commission-override audit ask (Intercom conv 61134) is the one mined use case this leaves uncovered, and list_commissions is the Phase 2 answer if it recurs.

06

Requirements — MCP server

Transport & protocol mcp.getroster.com

Upstream API usage

07

Requirements — OAuth authorization service

Concrete implementation blueprint (stack, libraries, consent sequence, storage, revocation, environments): the MCP & OAuth writeup. The blueprint recommends serving the AS and the MCP server from the single host mcp.getroster.com.

Server metadata & registration mcp.getroster.com

Consent flow

  • Portal-hosted (D-10): /authorize stores the pending request and redirects to a new app.getroster.com/connect/claude route in the Brand Portal SPA. Login uses the existing portal login (password, social SSO, lockout) — the OAuth service never handles credentials, SSO-required brands can connect, and already-logged-in users skip straight to consent.
  • Users with access to multiple brands pick exactly one brand per grant (see Brand context model below). Single-brand users skip the picker.
  • On Approve, the portal hands the OAuth service a one-time connect ticket (single-use, ~60s TTL, bound to user + brand + request) via redirect; the service validates it server-side, re-checks the brand allowlist fail-closed, and completes the grant.
  • Deny returns the standard OAuth error redirect; no partial grants.
Consent screen — content, not pixel-final

Brand context model

A Roster login is user-level; API tokens are brand-level. The OAuth grant is the bridge between the two:

Tokens & credential bridge

08

Requirements — platform changes

REST API api-brand-portal

Brand Portal — Integrations → API integration page

Backend services / observability

Support docs

Data model & migration

09

Decisions & open questions

Four decisions on 2026-06-09; five more on 2026-06-10 from the tool-specs deep dive (details in tool-specs §14). Two questions remain open before sprint start; two were resolved by the deep dive's verification work.

Decisions — 2026-06-09

D-1 · Token lifetime RESOLVED
Private API tokens are effectively non-expiring in practice (CLIENT_API_TOKEN_EXPIRE_YEARS = 30 is the real behavior; same brand token in active use for four years). The credential bridge mints a standard long-lived private ApiSession per grant. The customer's 1-hour-JWT report (Intercom conv 215473520540411) does not reflect private-token behavior — worth a support follow-up, but not a design input.
D-2 · Endpoint gaps — build new APIs RESOLVED
The rollup/commissions gaps are real; close them with new v2 Open API endpoints. Precedent exists: the Brand Portal UI already surfaces all of this data, so the new endpoints wrap the same queries the portal reports use.
D-3 · Stack — solution-agnostic; Cloudflare favored for speed RESOLVED
Requirements stay implementation-neutral, but Cloudflare Workers + workers-oauth-provider is the fastest proven route (Linear, Sentry, and Intercom launched on it), and Roster already runs parts of its stack on Cloudflare — adopting it here is not a stretch. Eng makes the final call.
D-4 · Brand context — selected at consent, bound to the token RESOLVED
Roster logins are user-level; API tokens are brand-level. The OAuth grant bridges the two: one brand per grant, chosen at consent, carried by the token on every call. See the Brand context model in section 07.

Decisions — 2026-06-10 (from the tool-specs deep dive)

D-5 · Timezone — follow the platform pattern RESOLVED
Roster stores UTC and resolves to the user's browser locale at display; there is no brand-timezone field. The MCP analog: Claude (which knows the user's timezone) resolves relative ranges to explicit ISO dates before calling tools; omitted dates default to UTC last-30-days. Nothing stored at consent; no server-side timezone state.
D-6 · get_program_performance mirrors the brand's saved Program Dashboard RESOLVED
Dashboard layouts are stored per brand per program (UserPage/UserPageComponent); the new endpoint loads the brand's saved cards (default-template fallback) and runs the existing dashboard dispatcher SP per card — output matches exactly what the brand sees in the portal. No new SQL.
D-7 · Campaign tool sources: Campaigns List + per-campaign overview only RESOLVED
The separate "Campaign Performance Dashboard" (/dashboards/campaign-performance) must not be used or referenced anywhere in this project. get_campaign_performance is built from /campaigns and /campaigns/{id}/analytics/overview.
D-8 · No per-campaign attributed revenue RESOLVED
Campaign-attributed revenue does not exist in any portal surface or query — dropped from get_campaign_performance; the tool description routes revenue questions to get_sales_attribution_report.
D-9 · Social Posts report is per-ambassador; no EMV sort on contact search RESOLVED
get_social_posts_report returns per-ambassador rollups (portal parity; per-post rows live in get_social_feed_posts). list_ambassadors cannot sort by EMV (not in the search index) — the EMV leaderboard is get_social_posts_report with sort: emv.

Decisions — 2026-06-11/12 (implementation; details in CHANGES.md)

D-10 · Consent is portal-hosted RESOLVED
Brands with USER_SETTING_SSO_REQUIREMENT reject password auth outright (isSSOError), so an OAuth-service-rendered login form can't serve them — and it would put portal passwords on a second surface. Instead, /authorize redirects to a new app.getroster.com/connect/claude portal route: existing login (password + SSO), consent + brand picker in the portal, one-time connect ticket back to the OAuth service. Supersedes the Worker-rendered consent screen in this PRD's earlier draft and the writeup's original §4.2.
D-11 · No Claude-side disconnect signal — idle-grant sweep RESOLVED
Verified live against the deployed staging skeleton: disconnecting the connector in claude.ai sends nothing (no RFC 7009, no grant deletion). Cleanup is a daily sweep deleting grants with no token activity for 35 days (past the 30-day refresh TTL) and expiring their bridged tokens. Residual risk accepted: a disconnected grant's unused, server-held token survives ≤35 days; portal revoke is the immediate cutoff.

Open questions

OQ-1 · Agency simultaneous access
Is re-auth-to-switch-brands acceptable for top agency customers, or do they need simultaneous multi-brand access in one Claude account? If the latter, Phase 2 explores multiple grants — not a brand parameter on tools (see Brand context model).
Decide with · CSM team
OQ-2 · Privacy policy
Does the existing getroster.com privacy policy cover "customer-authorized AI assistant access to account data," or does legal need an update? Required for the Phase 2 directory submission; cheap to start now.
Decide with · legal

Resolved by the deep dive (2026-06-09/10 · evidence in tool-specs §1)

Rate limits — already isolated RESOLVED
Roster rate limiting keys its counter per token (RateLimitService); the limit value (default 100/interval, subscription item 149) is per brand. A bridged MCP ApiSession token automatically gets its own pool and cannot starve a brand's existing integration token.
EMV availability — exists, stored per-post, not in the v2 API today RESOLVED
UserSocialListeningPostEMV stores per-post EMV (Ayzenberg components + computed total); brand-customizable rates in EmvPlatformDefault/UserEmvConfig. Exposed via the new report endpoints (E1, E7, E8).
10

Rollout

Gated server-side by a brand allowlist in the OAuth service — consent fails closed with a friendly "not enabled for your account yet" for non-allowlisted brands. No portal feature flag needed in Phase 1.

Internal

Internal testing

Connect Claude (claude.ai + Cowork + Claude Code) to a demo brand; run the 10-use-case prompt suite from research.md; verify report-number parity against the portal for the same date ranges.

Beta

Limited release — 5–10 brands

Hand-picked from active API/export askers in Intercom evidence, plus at least one agency. CSM-guided setup; weekly check-ins; watch usage logs.

GA

General availability

Publish the help article, announce in-product/newsletter, arm CSMs. Begin Phase 2 (directory submission) prep in parallel.

Rollback plan

Disable the OAuth /authorize endpoint (no new grants) and/or revoke all bridged ApiSession tokens to cut access instantly. The MCP service is fully decoupled — turning it off cannot affect the portal or existing API customers.

11

Automated testing

Unit tests

Integration tests

End-to-end tests

12

Manual test cases

Test case 1
First-time connect — single-brand user

On claude.ai (Pro), Settings → Connectors → Add custom connector → enter URL → Roster login → consent shows read-only copy → Allow → back in Claude, run "How is my ambassador program performing over the last 30 days?" → numbers match the Program Dashboard for the same range, and the response cites its portal_source deep link.

Test case 2
Multi-brand agency user

User with 3 brands connects → brand picker shows exactly the brands they can access → pick brand B → all tool results are brand B only → disconnect in Claude → reconnect choosing brand C → results switch accordingly; brand B's bridged token is revoked.

Test case 3
Revocation from the portal

With an active Claude session mid-conversation, revoke the connection in Brand Portal → next tool call fails with re-auth prompt, no stale data served → "Connected apps" row disappears.

Test case 4
Team org + Cowork

Team org Owner adds the connector org-wide → a non-owner member connects with their own credentials → same member uses it in Cowork → tools work identically; a member without Brand Portal access for that brand cannot complete consent.

Test case 5
Limits & errors

Request 5 years of orders → tool returns truncation/narrow-the-range guidance, not a timeout. Hammer tools past the rate limit → clean retryable error in Claude, portal/API usage for the brand unaffected.