@slack/radar-mcp
v1.14.0
Published
MCP server and web dashboard for on-device debugging of the Slack Android app via ADB
Downloads
1,204
Readme
slack-radar-mcp
Platform-agnostic MCP server and web dashboard for on-device debugging of mobile apps.
Gives Claude Code direct access to network calls, RTM events, analytics (clogs), screenshots, logs, and more from a running debug build. Ships with Android support (ADB); the DeviceTransport interface makes adding iOS or other platforms straightforward.
For Slack engineers. The npm package is published publicly as a distribution convenience, but the source repository, issue tracker, and support live on Slack's internal GitHub Enterprise. External users can install the package but should not expect support or source access. The tool only functions against a Slack debug build, which is not distributed externally.
Installation
The recommended path is via the marketplace plugin:
/plugin install slack-radar@slack-android-marketplace
/slack-radar-setupOr register manually:
slack claude mcp add -s user slack-radar-mcp -- npx -y @slack/radar-mcp@latestRestart your Claude Code session for the tools to appear.
Web Dashboard
The package also ships a browser-based dashboard that shows the same live device data without Claude Code in the loop.
npx @slack/radar-mcp webOr with a global install:
npm install -g @slack/radar-mcp
slack-radarOpens http://localhost:8100 automatically. Set SLACK_RADAR_WEB_PORT to use a different port. (slack-radar-web still works as a direct alias.) Tabs:
- Live — network, RTM, analytics clogs, traces, and Slack-app logcat in one timeline
- Network — calls per second vs response time, every call listed, slow ones flagged
- RTM — real-time websocket events
- Clogs — analytics events, click one to see its payload
- Traces — performance trace spans with durations, click one for its timing and tags
- Logs — device logcat, filtered to the Slack app (switch app or clear the filter)
- Database — read-only browser for the on-device SQLite stores (pick a database, then a table, or run a read-only query). See the data-handling note below.
- Preferences — read-only browser for the on-device SharedPreferences files (pick a file, browse its keys, or search across every file at once).
- Custom — a tab Claude builds on request. Ask a Claude Code session in your terminal for a view the pre-built tabs do not cover (e.g. "graph calls per second and flag the slow ones"); it authors the view via the
open_radar_dashboardtool and it appears here. Persists until you refresh the page.
Each tab has a rate, sparkline, graph, a filter / sort / pause feed, and a payload inspector. The filter box takes space-separated terms that must all match, -term to exclude, /pattern/ for regex, and "quoted phrases" for spaces or a literal - or /. Committed terms turn into removable pills; a leading - is now the exclude operator (the old box matched it as plain text), and quoting restores the literal. Regex flags are optional and short (i/m/s/g, 1-2 letters only, so an ambiguous tail like /api/im reads as flags, noted in the tooltip); typos like /error/x become part of the pattern and match literally, and quoting any /…/ term matches it literally. Invalid regexes and incomplete typing (e.g. /foo() are ignored (the term turns amber) instead of blanking the feed. On the Live tab a kind name (log, clog, trace, rtm, network) also matches rows of that type, and its -kind form excludes by row type only, so -log hides logcat lines without touching a logout clog (quote it, -"log", to exclude by text instead). Switching tabs replays what you have already seen this session (the browser caches it, bounded by count and age) so the view does not reset to empty. ADB forwarding and device activation are handled for you; a reconnect control re-arms after a device sleep. The dashboard follows your system light/dark setting by default; the theme toggle in the header overrides it and is remembered.
Screen overlay
A ▣ Screen button opens a side panel that mirrors the live device screen, with a full-resolution screenshot and a screen recording. It is off by default and opens to a consent notice first, because it shows whatever is on the phone (open DMs, message content, notifications).
- Screenshot works on any machine (pure
screencap, no extra tools). - Live mirror and recording need
ffmpegon your machine. If it is missing, the panel shows the install command for your platform and the screenshot still works. ffmpeg is not bundled and is never installed for you.
Prerequisites
- Node.js 18+
- ADB with a single connected device (USB or wireless debug)
- A debug build of Slack installed on the device (must include the
slack-radarmodules)
Environment Variables
All optional. Set them on the process that runs the MCP server or the dashboard. One further variable, SLACK_RADAR_INITIAL_SPEC, is set automatically when a tool spawns the dashboard and is not meant to be set by hand.
| Variable | Effect |
|---|---|
| SLACK_RADAR_WEB_PORT | Port the dashboard listens on. Default 8100. |
| SLACK_RADAR_DEVICE_PORT | Host port forwarded to the device-side server. Default 8099. |
| SLACK_RADAR_NO_AUTO_CLOSE=1 | Keep the dashboard running forever instead of closing it once every tab is gone. |
| SLACK_RADAR_ALLOWED_HOSTS | Extra Host values the dashboard will answer, comma-separated (radar.local,myhost:8100). Only needed when you reach it by a name other than localhost. See Security. |
| SLACK_RADAR_NO_OPEN=1 | Do not open a browser window on launch. Set automatically when an MCP tool spawns the dashboard. |
| SLACK_RADAR_NO_FFMPEG=1 | Pretend ffmpeg is absent, so screen recording degrades to screenshots. |
Session Management
Radar starts disabled each session. All tools except ping return an error until enabled.
| Command | Effect |
|---|---|
| /radar-enable | Activates ADB forwarding, device broadcast, SSE stream. Tools become available. |
| /radar-disable | Tears down stream, clears local buffers. Tools return "not active" error. |
Auto-enable: When you ask Claude an obvious device question (clogs, network calls, screenshots, app state, RTM events, logs), Claude invokes /radar-enable automatically before calling the relevant tool.
Auto-disable: When the device-side radar shuts down after idle (default 5 min), the next tool call failure flips the session to disabled. Claude will tell you and offer to re-enable.
Tools
| Tool | Description |
|---|---|
| ping | Health check. Returns uptime, buffer sizes, remaining session time, stream status, the Radar MCP version (mcp_version), and which app/profile is bound (android_user_id, profile personal/secondary, package). Use timeout_minutes to extend sessions. |
| get_recent_network_calls | HTTP call summaries: method, URL, status code, duration. Filterable by URL substring and status code. |
| get_network_call_detail | Full request/response headers and bodies for a specific call (gzip/brotli decompressed). |
| get_recent_rtm_events | RTM event summaries: type, direction, channel, timestamp. Filterable by type, direction, channel. Uses subtypes (e.g. message_changed, not message). |
| get_rtm_event_detail | Full RTM event JSON payload for a specific event. |
| get_recent_clogs | Analytics event summaries: name and timestamp. Filterable by event name substring. Persists from app start. |
| get_clog_detail | Full clog JSON payload for a specific event. |
| get_recent_traces | Performance trace/span summaries: trace name, span name, duration. Filterable by name substring. |
| get_trace_detail | Full trace/span detail for a specific trace, including tags. |
| wait_for_events | Block until matching events arrive via SSE stream. Idle-timeout based, not polling. |
| search | Full-text search across network, RTM, clog, and trace buffers. |
| get_app_state | Current Activity/Fragment state via dumpsys. Shows visible screen, parameters, tab state. |
| get_recent_logs | Recent device logs, optionally filtered by tag (omit tag for all lines). Reads the app's own debug log buffer first, falling back to logcat; the result's source field (app_buffer / logcat_capture / logcat_ring) names which one served it. For Timber debug logs (FKDebug, SlackRadar, etc.). |
| list_profiles | List the Android users and profiles on the device (personal, work, and so on). |
| activate_for_user | Activate radar for a specific Android user or profile. |
| capture_snapshot | Bundle the current network, RTM, clog, and trace buffers, plus an optional screenshot, into one saved snapshot for a bug report or repro. |
| screenshot | Capture device screen as PNG. 720px default, full_res=true for full resolution. |
| record_screen | Record short screen video (MP4). Default 5s, max 30s. |
| list_databases | List the app's on-device SQLite databases. Read-only. |
| list_tables | List the tables in a given on-device database, with row counts. Read-only. |
| query_database | Run a read-only query (SELECT, WITH, EXPLAIN, VALUES, PRAGMA) against a copy of an on-device database pulled to the host. |
| list_prefs | List the app's on-device SharedPreferences files. Read-only. |
| get_prefs | Read a preferences file's keys and values, optionally filtered to specific keys. |
| search_prefs | Search preference keys and values across every file by a substring. |
| list_feature_flags | List every feature flag with its live effective value and override status. Optionally filtered to just the overridden ones. |
| search_feature_flags | Case-insensitive substring search on feature flag names, against the same live list as list_feature_flags. |
| get_feature_flag | Look up one feature flag by its exact key. Returns one row per scope (authenticated/unauthenticated). |
| clear_buffers | Clear all captured buffers (network, RTM, clog, trace) on the device and locally. |
| open_radar_dashboard | Author or refine a custom live dashboard tab from a spec (source, filters, widgets, alerts). Suggested when a request is not covered by the pre-built tabs; not auto-invoked. Returns the dashboard URL; debug builds only, local only. |
| _radar_session_enable | Enable radar for the session. Called by /radar-enable skill. |
| _radar_session_disable | Disable radar for the session. Called by /radar-disable skill. |
Architecture
Mobile debug build (device)
+-- Local socket (platform-specific)
+-- HTTP endpoints + SSE streaming
|
| port forwarding (adb forward / iproxy / etc.)
|
@slack/radar-mcp (Node.js, runs on developer machine)
+-- src/shared/ DeviceTransport, AndroidTransport, SSE stream, constants
+-- src/mcp/ MCP stdio server (Claude Code entry point)
+-- src/web/ HTTP proxy server + browser dashboard
+-- public/ Static HTML/CSS/JS served to the browserAll device-specific logic lives behind the DeviceTransport interface (src/shared/transport.ts). The MCP server, web dashboard, tools, SSE streaming, and session management are fully platform-agnostic.
Adding a new platform: implement DeviceTransport in src/shared/<platform>.ts, add a case to createTransport() in src/mcp/index.ts. No other files change.
Security
- No TCP port on device. Uses platform-specific local sockets, only reachable via port forwarding. Requires physical USB or explicit debug pairing.
- Debug builds only. Release builds bind a no-op implementation. Server code is eliminated at compile time.
- Lazy activation. Dormant until explicitly triggered.
- Auto-shutdown. Device-side server shuts down after idle timeout (default 5 minutes). Zero cost when unused.
- Dashboard auto-close. Once a dashboard has been opened in a browser, the process shuts itself down roughly 6 minutes after the last tab closes, so a dashboard you walked away from does not linger. Opening a dashboard again (
open_radar_dashboard, which writes a spec) pushes that deadline out by another grace window, but nothing else does: the other MCP tools read the device directly and never touch the dashboard, so a long agent session with no tab open will still close on schedule. Reopen it, or use the opt-out below, if you want it to stay. Two more exceptions: a dashboard that no tab has ever opened (one an agent spawned and nobody visited) stays up until you visit it once or stop it, because there is no "last tab closed" moment to measure from; and when you first upgrade to a version that has auto-close, reload any tab that was already open — page code from before this feature does not report itself as present, so it will not keep the process alive even while you are looking at it. Reloading once is enough; later upgrades do not need it. Click ⏻ Stop dashboard in the header to stop it immediately instead (click twice to confirm — it kills the process with no undo). SetSLACK_RADAR_NO_AUTO_CLOSE=1to turn auto-close off entirely. - Keep-alive is not forgeable from a web page. Only the dashboard's own client and the MCP can postpone auto-close. A write to
/setspecor/enablehas to carryX-Radar-Dashboard: 1, or a content-type whose type is exactlyapplication/json; the/healthpresence ping requires the header specifically, since a content-type says nothing about a request with no body. A cross-origin request cannot set either one without a CORS preflight it will fail. A request without either still works and still writes, it just does not extend the process lifetime, socurl -X POST .../setspec -H 'Content-Type: text/plain'sets the spec but will not stop the dashboard closing on schedule; use-H 'Content-Type: application/json'if you want it to count. This is a CSRF guard, not authentication: any local process can send either header, and the port is loopback-only by design. - Loopback
Hostonly. The dashboard answers a request only when itsHostheader names this machine (localhost,127.0.0.1,[::1],0.0.0.0, with or without a port). Anything else gets a 403 whose body names the host it refused and how to allow it. Binding to 127.0.0.1 alone is not enough: a page that resolves its own domain to 127.0.0.1 connects locally, counts as same-origin in the browser, and could otherwise set the header above and stop your dashboard. If you reach the dashboard by some other name — a/etc/hostsalias, a container or Codespaces forwarded URL, your machine's LAN hostname — list it inSLACK_RADAR_ALLOWED_HOSTS(comma-separated, host orhost:port). A plain SSH or VS Code port forward needs nothing, because theHoststayslocalhost.
Data Capture
What gets captured depends on the in-app server implementation. The MCP server is agnostic to the data source. The Android implementation captures:
- Network calls: OkHttp interceptor. Ring buffer: 400 calls.
- RTM events: WebSocket listener. Ring buffer: 2000 events.
- Clogs: Analytics interceptor. Ring buffer: 1000 events. Persists across radar activation cycles.
- Traces: Performance span observer. Ring buffer: 2000 spans. Persists across radar activation cycles.
- SQLite database (Database tab, web dashboard only): when you open a database, a read-only plaintext copy of that on-device store (message text, request/response bodies, user/channel/team IDs) is pulled to a temp directory on your own machine (
os.tmpdir()/slack-radar-db-<port>, files mode 0600). Auto-deleted when the dashboard process stops. - SharedPreferences (
list_prefs/get_prefs/search_prefs, and the Preferences tab): read directly off the device overadb, no local copy written. Files can contain feature flag overrides, cached team/org/channel IDs, and other app state. - Feature flags (
list_feature_flags/search_feature_flags/get_feature_flag): read directly off the device overadb, no local copy written. Shows every flag's live effective value and whether it is locally overridden.
The network, RTM, clog, and trace captures are in-memory ring buffers. The Database tab is the exception: it writes a plaintext copy of the selected on-device database to a temp file on your host for the duration of the session. Review before sharing anything from it.
What gets captured, in detail
This tool captures unredacted network traffic from your debug Slack build, including:
- URL query parameters (tokens can appear here)
Authorizationheaders and cookiesSet-Cookieresponse headers (session tokens)- Request and response bodies, up to 256 KB per body, for JSON, form, text, and XML content types
- RTM WebSocket payloads (every sent and received message)
- Analytics (clog) events serialized as JSON
- Screenshots and short screen recordings on demand
- Device logs (the app's own debug buffer, with a logcat fallback), optionally filtered by tag
- SharedPreferences files, including feature flag overrides and cached team/org/channel IDs
Auth tokens and session cookies will appear in captured data. Captured data flows to your MCP client (Claude Code), which may send relevant portions to the configured AI provider per its own data handling policy.
Use against workspaces you are signed into as yourself
The tool is intended for debugging the Slack Android app against your own sessions. Do not share captured data (screenshots, log exports, tool transcripts) externally without first redacting auth material. Treat anything radar captures with the same care you would treat a curl transcript of an authenticated API call.
Local Development
npm install
npm run build # Compile TypeScript + copy static assets to dist/
npm start # Run the MCP server (stdio transport)
npm run start:web # Run the web dashboard
npm run watch # TypeScript compiler in watch mode
npm test # Unit tests
npm run test:smoke # Smoke tests
npm run test:all # BothTroubleshooting
"ADB port forwarding failed"
Ensure a device is connected (adb devices should show exactly one device). If using wireless debug, confirm the pairing is active.
"Radar not responding"
The device-side server may have auto-shut down. Use /radar-enable to restart it.
"Session disabled"
Two consecutive connection failures disable the session. Use /radar-enable to reconnect.
Devices with more than one user (e.g. a work profile)
The MCP server auto-detects the Android user ID via ps and targets the broadcast accordingly. No manual --user flag needed. In the dashboard's Logs tab, the package filter narrows logcat to the Slack app; clear the chip or pick another package to widen it.
Tools not appearing after setup Restart your Claude Code session. MCP servers are registered at session start.
Port already in use (web dashboard)
If a Radar dashboard is already running on the port, launching again just opens it. If the port is held by another program, run Radar on a different port with SLACK_RADAR_WEB_PORT=8101 npx @slack/radar-mcp web.
