Skip to content

Slack /pr-* → MCP Migration

Owner: Brandon Shin Status: Planning — this doc only. No code in this PR.

This project retires the internal Slack app (/pr-* slash commands) in favor of the PaperRun MCP. The end state: every Slack command returns a static "use the MCP" message, the commands worth keeping have MCP-tool equivalents, and a later separate PR rips out the Slack app code entirely.

The work lands in three deliberately separated steps so each is small and reviewable:

  1. This doc — the migration map and rip-out plan.
  2. Stub PR — replace every /pr-* handler body with a static "use the MCP instead" response; ensure the commands worth porting have MCP coverage. No code deletion.
  3. Rip-out PR (later) — delete slack_app_routes.py, the Slack methods, block formatters, signature verification, config, and tests.

1. Why

The Slack commands duplicate data access that the MCP already does better. The trigger was LTM revenue: /pr-org computes it, but so does the MCP — both call the same generate_organization_audit_slim() (app/methods/audits/organization_audit.py), which already returns ltm_revenue/ltm_orders. Maintaining two front-ends over one computation is the cost we're removing. The MCP also has fuzzy org search, campaign stats, and reporting payloads that the Slack app never grew.

2. Current Slack surface

Routing: POST /api/v1/slack → signature verify → SLASH_COMMANDS dict dispatch (app/routes/slack_app_routes.py). Interactive buttons: POST /api/v1/slack/interactivehandle_block_action.

Command Handler (app/methods/slack.py) What it returns
/pr-hello handle_hello_command Connectivity greeting.
/pr-orgs handle_orgs_command First 50 orgs as a table (id, name, url).
/pr-org <org> handle_org_command Full org snapshot: basic info + audit (LTM revenue, company size, integrations, flow indicators) + CSM insights. Fuzzy-matches org name with confirm buttons.

Interactive: org_suggest_yes / org_suggest_no (fuzzy-match confirmation) → handle_block_action.

The /pr-org snapshot is assembled from three formatter blocks, each tangling computation with Slack-block rendering:

  • format_organization_basic_info — org identity, Retool/HubSpot/Stripe deep links, CSM, campaign list.
  • format_organization_auditformat_audit_summary — LTM revenue, get_company_size_tag, categorize_integrations, birthday/loyalty flags.
  • format_organization_insights — strong/weak campaign performers + new-flow suggestions.

3. Command → MCP mapping

Slack command Port? MCP equivalent Status
/pr-hello No — (connectivity check has no data value) Drop in stub PR.
/pr-orgs Yes list_organizations Already exists (app/mcp/server.py).
/pr-org basic info Yes get_organization + new get_organization_overview Overview tool not yet built — see §4.
/pr-org audit Yes get_organization_audit_slim Exists; LTM revenue already returned. Derived company_size / categorized_integrations to be folded in — see §4.
/pr-org insights Yes new get_organization_insights Not yet built — see §4.
Fuzzy org-name match + confirm buttons No (replaced) search_reporting_organizations (fuzzy name/store match) The MCP client resolves names; no interactive equivalent needed.

4. MCP coverage gap before stubbing

Stubbing /pr-org is only safe once its data is reachable through the MCP. The audit/overview/insights port is specced separately (local design + plan under docs/superpowers/, not published). Summary of what that work adds, all in app/methods/audits/organization_audit.py with thin MCP tools mirroring get_organization_audit_slim:

  • Derived audit fields — move get_company_size_tag / categorize_integrations (today in slack.py) into the audit module; add defaulted company_size + categorized_integrations fields to SlimAuditResponse. get_organization_audit_slim returns them for free.
  • build_organization_overviewget_organization_overview — org identity, Retool/HubSpot/Stripe links, CSM email, campaign list.
  • build_organization_insightsget_organization_insights — strong/weak/new-flow insights as structured data.

Sequencing: land the overview/insights tools, then the stub PR can safely point users at them.

5. Stub approach (next PR, not this one)

Keep the routes and signature verification so Slack doesn't 404, but replace each handler body with one shared deprecation response:

  • handle_hello_command, handle_orgs_command, handle_org_command → return a single block: "The /pr-* Slack commands are deprecated. Use the PaperRun MCP — e.g. ask for the org audit, overview, or org list there." (with the command→tool mapping inline).
  • handle_block_action (interactive) → same deprecation response.
  • Leave SLASH_COMMANDS wired so any command resolves to the stub; no new routes.

Nothing is deleted yet — this keeps the diff tiny and the rollback trivial.

6. Rip-out (later, separate PR)

Once the stub has shipped and a release has passed with no fallback need:

  • Delete app/routes/slack_app_routes.py and unregister its blueprint in app/main.py.
  • Delete app/methods/slack.py and its block-formatting helpers.
  • Remove Slack signature verification, rate-limit decorators, and Slack config.SLACK_* entries that are now unused.
  • Delete app/tests/methods/test_slack.py and any Slack route tests.
  • Grep for residual slack imports/usages and remove.

7. Open questions

  • Do any non-engineering stakeholders (CSMs) still rely on /pr-org in Slack day-to-day? If so, the stub message should name the exact MCP client/workflow to switch to before the rip-out.
  • Should list_organizations gain a fuzzy-name argument to fully cover /pr-orgs + the /pr-org name resolution, or is search_reporting_organizations sufficient?