Triaging Crashlytics from Claude Code with the Firebase MCP server

In one afternoon, Claude Code went from reading stack traces I pasted in to reading Crashlytics itself: about 20 issues across two products, sorted into fixed, planned, noise and “look closer”, with no copy-paste.

The app is a Kotlin Multiplatform calendar and note-taking app shipped as two products: Tools for Boox on Onyx e-ink tablets and Scribble Calendar on Android and iOS. Each product has its own Firebase project. Until now, triage meant opening the Firebase console, copying one stack trace at a time into Claude Code, and getting a verdict back: already fixed, or a live bug worth a GitLab work item.

That worked, but I was the bottleneck. Claude never saw event counts, version ranges, or the notes I had already left on an issue. The Firebase CLI’s MCP server closes that gap.

Setup: one read-only service account, one command

The Firebase CLI ships an MCP server, and Claude Code can start it through npx. Nothing is installed globally; it only needs Node.js. Instead of my own Google login, I gave it a service account that can read crash data and nothing else.

  1. Create the service account in Google Cloud console, IAM & Admin, Service Accounts. I named it claude-firebase, with a description saying what it is for. Leave “Principals with access” empty: that controls who can impersonate the account, not what it can do.
  2. Grant read-only roles in IAM: Firebase Crashlytics Viewer, plus Browser if a tool needs to list the project.
  3. Grant the same account in the second project. A service account can hold roles in any project, so one account and one key cover both products.
  4. Enable the Firebase Crashlytics API (firebasecrashlytics.googleapis.com) in each project. The Crashlytics product running is not the same as its Cloud API being enabled.
  5. Create a JSON key and keep it outside every repository, readable only by you:
mkdir -p ~/.config/firebase-mcp
mv ~/Downloads/<key>.json ~/.config/firebase-mcp/claude-firebase.json
chmod 600 ~/.config/firebase-mcp/claude-firebase.json
  1. Register the server with Claude Code at user scope, passing the key through the environment:
claude mcp add firebase -s user \
  -e GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/firebase-mcp/claude-firebase.json \
  -- npx -y firebase-tools@latest mcp

After a restart, Claude Code lists the Crashlytics tools: top-issues reports, issue details, sample events, and issue notes. @latest costs a little startup time, but these tools change quickly, and you get fixes without remembering to upgrade.

What tripped us up

Each of these cost a few minutes, and each had a clear cause once we looked.

SymptomCauseFix
Tools never appear right after a restartThe npx fetch made the server take about 25 seconds to connectWait and search for the tools again; the MCP log showed “Successfully connected”
HTTP 403, “Firebase Crashlytics API has not been used in project … or it is disabled”The Cloud API was off, even though Crashlytics was collecting crashesEnable firebasecrashlytics.googleapis.com; the service account itself can’t, it only has viewer roles
The same 403 once more, minutes after enablingThe change hadn’t reached every server yetRetry; the second call succeeded
Debug apps: empty, or HTTP 404Both platforms turn Crashlytics collection off in Debug builds (setCrashlyticsCollectionEnabled(!isDebug))Working as intended: query only the release apps
The second product’s apps missingThe MCP server only detects apps from the working directory’s Firebase configGrant the role in the second project, then pass that app’s ID explicitly; every Crashlytics tool takes one

One Android detail surfaced along the way. Collection is switched on in the first Activity’s onCreate, not at process start. So on the very first launch after an install, a crash in Application.onCreate is never reported.

Triage in practice

The first real run covered three release apps and 20 issues, and only two of them needed a closer look.

Claude starts with the topIssues report for each release app. It defaults to the last seven days and matches the console’s default view. Each row carries event and user counts, error type (fatal, non-fatal, ANR), the first and last version seen, and how many notes the issue has.

Notes are the triage state. I mark every triaged issue with two notes: the GitLab work item’s URL, and Fixed in <version> or Planned in <version>. Some issues get a single explanatory note instead, such as “Automated Play Store test”. Claude reads those notes first, so it skips what I’ve already handled and only works on issues without them. The service account is read-only, so Claude suggests notes and I add them.

App (7 days)Active usersIssuesAlready triagedNoiseNeeds a look
Tools for Boox, Android35815672
Scribble Calendar, Android1615500
Scribble Calendar, iOS–0 (also 0 over 90 days)–––
  • Already triaged: a pinch-zoom crash noted “Fixed in 1.9.1”, and Claude confirmed its last event came from 1.9.0. Four Google Drive network errors pointed at one planned work item. Three fatal crashes inside Google libraries came from Play’s pre-launch robot on one device, within the same minute.
  • Noise: non-fatal DNS and UnknownHostException failures from offline devices, a logged JobCancellationException, and an ANR in the ads SDK on 1.7.4. The ads were removed in 1.9.1.
  • Needs a look: an EOFException loading a Day page, and a FragmentNavigator crash on the Week view. Both crashing classes still exist in the current code, so neither can be closed as “gone with the rewrite” without reading the sample event against the release tag.

The last step is the one I used to do by hand. Claude fetches the sample event, finds the frame’s line in the matching release tag, checks the same path on development, and either gives a fixed verdict or files a GitLab work item.

Crashes, logs and work items in one place

Crashlytics was the missing piece. The same Claude Code session already reads the backend’s logs and metrics through Grafana’s MCP server, and GitLab work items through GitLab’s. A client-side crash, the server log line from the same minute, and the work item that tracks the fix can now be checked side by side without leaving the terminal.

What I’d tell someone setting this up:

  • Use a service account with viewer roles, not your own login. You can add roles later without touching the key, and you can revoke it in one click.
  • One service account can cover several Firebase projects. Grant it in each project’s IAM, enable the Crashlytics API there, and pass explicit app IDs.
  • Write your triage state where the tool can read it. Two notes per issue turned “what is new since last week?” into a filter, not a conversation.
  • Expect empty debug apps. If your builds disable collection in Debug, as they should, only release apps have data.

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top