Skip to content

Commit a2a73f9

Browse files
committed
Improving crashlytics mcp tools
- Moving crashlytics prompt into guides - Consolidating the report tools into a single get_report tool - Fixing bug where additional prompt said report result was empty - Including request filters in report tool response - Improving error handling and leveraging guides - Ignore gemini environment dotfile
1 parent 7c3170c commit a2a73f9

File tree

16 files changed

+519
-353
lines changed

16 files changed

+519
-353
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ lib/
3232
dev/
3333
clean/
3434
.gemini/
35+
.env

src/appUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export async function detectApps(dirPath: string): Promise<App[]> {
6666
const adminAndWebApps = (
6767
await Promise.all(packageJsonFiles.map((p) => packageJsonToAdminOrWebApp(dirPath, p)))
6868
).flat();
69+
6970
const flutterAppPromises = await Promise.all(
7071
pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)),
7172
);

src/crashlytics/filters.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@ import { FirebaseError } from "../error";
33

44
export const ApplicationIdSchema = z
55
.string()
6-
.describe(
7-
"Firebase app id. For an Android application, read the " +
8-
"mobilesdk_app_id value specified in the google-services.json file for " +
9-
"the current package name. For an iOS Application, read the GOOGLE_APP_ID " +
10-
"from GoogleService-Info.plist. If neither is available, ask the user to " +
11-
"provide the app id.",
12-
);
6+
.describe("Firebase App Id. Strictly required for all API calls.");
137

14-
export const IssueIdSchema = z.string().describe("Crashlytics issue id, as hexidecimal uuid");
8+
export const IssueIdSchema = z.string().describe("Crashlytics issue id, as hexidecimal UUID");
159

1610
export const EventFilterSchema = z
1711
.object({
@@ -109,12 +103,15 @@ export function filterToUrlSearchParams(filter: EventFilter): URLSearchParams {
109103
const displayNamePattern = /^[^()]+\s+\([^()]+\)$/; // Regular expression like "xxxx (yyy)"
110104

111105
/**
112-
* Perform some simplistic validation on filters.
106+
* Perform some simplistic validation on filters and fill missing values.
113107
* @param filter filters to validate
114108
* @throws FirebaseError if any of the filters are invalid.
115109
*/
116-
export function validateEventFilters(filter: EventFilter): void {
117-
if (!filter) return;
110+
export function validateEventFilters(filter: EventFilter = {}): EventFilter {
111+
if (!!filter.intervalStartTime && !filter.intervalEndTime) {
112+
// interval.end_time is required if interval.start_time is set but the agent likes to forget it
113+
filter.intervalEndTime = new Date().toISOString();
114+
}
118115
const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
119116
if (filter.intervalStartTime && new Date(filter.intervalStartTime) < ninetyDaysAgo) {
120117
throw new FirebaseError("intervalStartTime must be less than 90 days in the past");
@@ -140,4 +137,5 @@ export function validateEventFilters(filter: EventFilter): void {
140137
}
141138
});
142139
}
140+
return filter;
143141
}

src/crashlytics/reports.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe("getReport", () => {
140140
.reply(200, mockResponse);
141141

142142
const result = await getReport(
143-
CrashlyticsReport.TopIssues,
143+
CrashlyticsReport.TOP_ISSUES,
144144
appId,
145145
{ issueErrorTypes: [issueType] },
146146
pageSize,
@@ -153,7 +153,7 @@ describe("getReport", () => {
153153
it("should throw a FirebaseError if the appId is invalid", async () => {
154154
const invalidAppId = "invalid-app-id";
155155

156-
await expect(getReport(CrashlyticsReport.TopIssues, invalidAppId, {})).to.be.rejectedWith(
156+
await expect(getReport(CrashlyticsReport.TOP_ISSUES, invalidAppId, {})).to.be.rejectedWith(
157157
FirebaseError,
158158
"Unable to get the projectId from the AppId.",
159159
);

src/crashlytics/reports.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@ import {
99
EventFilterSchema,
1010
filterToUrlSearchParams,
1111
} from "./filters";
12+
import { FirebaseError } from "../error";
1213

1314
const DEFAULT_PAGE_SIZE = 10;
1415

16+
export enum CrashlyticsReport {
17+
TOP_ISSUES = "topIssues",
18+
TOP_VARIANTS = "topVariants",
19+
TOP_VERSIONS = "topVersions",
20+
TOP_OPERATING_SYSTEMS = "topOperatingSystems",
21+
TOP_APPLE_DEVICES = "topAppleDevices",
22+
TOP_ANDROID_DEVICES = "topAndroidDevices",
23+
}
24+
25+
export const CrashlyticsReportSchema = z.nativeEnum(CrashlyticsReport);
26+
1527
export const ReportInputSchema = z.object({
1628
appId: ApplicationIdSchema,
29+
report: CrashlyticsReportSchema,
1730
filter: EventFilterSchema,
1831
pageSize: z.number().optional().describe("Number of rows to return").default(DEFAULT_PAGE_SIZE),
1932
});
2033

2134
export type ReportInput = z.infer<typeof ReportInputSchema>;
2235

23-
export enum CrashlyticsReport {
24-
TopIssues = "topIssues",
25-
TopVariants = "topVariants",
26-
TopVersions = "topVersions",
27-
TopOperatingSystems = "topOperatingSystems",
28-
TopAppleDevices = "topAppleDevices",
29-
TopAndroidDevices = "topAndroidDevices",
30-
}
31-
3236
/**
3337
* Returns a report for Crashlytics events.
3438
* @param report One of the supported reports in the CrashlyticsReport enum
@@ -62,23 +66,26 @@ export function simplifyReport(report: Report): Report {
6266
}
6367

6468
export async function getReport(
65-
report: CrashlyticsReport,
69+
reportName: CrashlyticsReport,
6670
appId: string,
6771
filter: EventFilter,
6872
pageSize = DEFAULT_PAGE_SIZE,
6973
): Promise<Report> {
74+
if (!reportName) {
75+
throw new FirebaseError("Invalid Crashlytics report " + reportName);
76+
}
7077
const requestProjectNumber = parseProjectNumber(appId);
7178
const queryParams = filterToUrlSearchParams(filter);
7279
queryParams.set("page_size", `${pageSize}`);
7380
logger.debug(
74-
`[crashlytics] report ${report} called with appId: ${appId} filter: ${queryParams.toString()}, page_size: ${pageSize}`,
81+
`[crashlytics] report ${reportName} called with appId: ${appId} filter: ${queryParams.toString()}, page_size: ${pageSize}`,
7582
);
7683
const response = await CRASHLYTICS_API_CLIENT.request<void, Report>({
7784
method: "GET",
7885
headers: {
7986
"Content-Type": "application/json",
8087
},
81-
path: `/projects/${requestProjectNumber}/apps/${appId}/reports/${report}`,
88+
path: `/projects/${requestProjectNumber}/apps/${appId}/reports/${reportName}`,
8289
queryParams: queryParams,
8390
timeout: TIMEOUT,
8491
});

src/mcp/prompts/crashlytics/connect.ts

Lines changed: 16 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { prompt } from "../../prompt";
2+
import { RESOURCE_CONTENT as connectResourceContent } from "../../resources/guides/crashlytics_connect";
23

34
export const connect = prompt(
45
"crashlytics",
@@ -11,137 +12,31 @@ export const connect = prompt(
1112
},
1213
},
1314
async (unused, { accountEmail, firebaseCliCommand }) => {
15+
const loggedInInstruction = `
16+
**The user is logged into Firebase as ${accountEmail || ""}.
17+
`.trim();
18+
19+
const notLoggedInInstruction = `
20+
**Instruct the User to Log In**
21+
The user is not logged in to Firebase. None of the Crashlytics tools will be able to authenticate until the user has logged in. Instruct the user to run \`${firebaseCliCommand} login\` before continuing, then use the \`firebase_get_environment\` tool to verify that the user is logged in.
22+
`.trim();
23+
1424
return [
1525
{
1626
role: "user" as const,
1727
content: {
1828
type: "text",
1929
text: `
20-
You are going to help a developer prioritize and fix issues in their
21-
mobile application by accessing their Firebase Crashlytics data.
22-
23-
Active user: ${accountEmail || "<NONE>"}
24-
25-
General rules:
26-
**ASK THE USER WHAT THEY WOULD LIKE TO DO BEFORE TAKING ACTION**
27-
**ASK ONLY ONE QUESTION OF THE USER AT A TIME**
28-
**MAKE SURE TO FOLLOW THE INSTRUCTIONS, ESPECIALLY WHERE THEY ASK YOU TO CHECK IN WITH THE USER**
29-
**ADHERE TO SUGGESTED FORMATTING**
30-
31-
## Required first steps! Absolutely required! Incredibly important!
32-
33-
1. **Make sure the user is logged in. No Crashlytics tools will work if the user is not logged in.**
34-
a. Use the \`firebase_get_environment\` tool to verify that the user is logged in.
35-
b. If the Firebase 'Active user' is set to <NONE>, instruct the user to run \`${firebaseCliCommand} login\`
36-
before continuing. Ignore other fields that are set to <NONE>. We are just making sure the
37-
user is logged in.
38-
39-
2. **Get the app ID for the Firebase application.**
40-
a. **PRIORITIZE REMEMBERED APP ID ENTRIES** If an entry for this directory exists in the remembered app ids, use the remembered app id
41-
for this directory without presenting any additional options.
42-
i. If there are multiple remembered app ids for this directory, ask the user to choose one by providing
43-
a numbered list of all the package names. Tell them that these values came from memories and how they can modify those values.
44-
b. **IF THERE IS NO REMEMBERED ENTRY FOR THIS DIRECTORY** Use the app IDs from the \`firebase_get_environment\` tool.
45-
i. If you've already called this tool, use the previous response from context.
46-
ii. If the 'Detected App IDs' is set to <NONE>, ask the user for the value they want to use.
47-
iii. If there are multiple 'Detected App IDs', ask the user to choose one by providing
48-
a numbered list of all the package names and app ids.
49-
c. **IF THERE IS A REMEMBERED VALUE BUT IT DOES NOT MATCH ANY DETECTED APP IDS** Ask if the user would like to replace the value with one of
50-
the detected values.
51-
i. **Description:** A valid app ID to remember contains four colon (":") delimited parts: a version
52-
number (typically "1"), a project number, a platform type ("android", "ios", or "web"),
53-
and a sequence of hexadecimal characters.
54-
ii. Replace the value for this directory with this valid app id, the android package name or ios bundle identifier, and the project directory.
55-
c. **IF THERE IS NO REMEMBERED ENTRY FOR THIS DIRECTORY** Ask if the user would like to remember the app id selection
56-
i. **Description:** A valid app ID to remember contains four colon (":") delimited parts: a version
57-
number (typically "1"), a project number, a platform type ("android", "ios", or "web"),
58-
and a sequence of hexadecimal characters.
59-
ii. Store the valid app id value, the android package name or ios bundle identifier, and the project directory.
60-
61-
## Next steps
62-
63-
Once you have confirmed that the user is logged in to Firebase, confirmed the
64-
id for the application that they want to access, and asked if they want to remember the app id for this directory,
65-
ask the user what actions they would like to perform.
66-
67-
Use the following format to ask the user what actions they would like to perform:
68-
69-
1. Prioritize the most impactful stability issues
70-
2. Diagnose and propose a fix for a crash
71-
72-
Wait for their response before taking action.
73-
74-
## Instructions for Using Crashlytics Data
75-
76-
### How to prioritize issues
77-
78-
Follow these steps to fetch issues and prioritize them.
79-
80-
1. Use the 'crashlytics_get_top_issues' tool to fetch up to 20 issues.
81-
1a. Analyze the user's query and apply the appropriate filters.
82-
1b. If the user asks for crashes, then set the issueErrorType filter to *FATAL*.
83-
1c. If the user asks about a particular time range, then set both the intervalStartTime and intervalEndTime.
84-
2. Use the 'crashlytics_get_top_versions' tool to fetch the top versions for this app.
85-
3. If the user instructions include statements about prioritization, use those instructions.
86-
4. If the user instructions do not include statements about prioritization,
87-
then prioritize the returned issues using the following criteria:
88-
4a. The app versions for the issue include the most recent version of the app.
89-
4b. The number of users experiencing the issue across variants
90-
4c. The volume of crashes
91-
5. Return the top 5 issues, with a brief description each in a numerical list with the following format:
92-
1. Issue <issue id>
93-
* <the issue title>
94-
* <the issue subtitle>
95-
* **Description:** <a discription of the issue based on information from the tool response>
96-
* **Rationale:** <the reason this issue was prioritized in the way it was>
97-
6. Ask the user if they would like to diagnose and fix any of the issues presented
98-
99-
### How to diagnose and fix issues
100-
101-
Follow these steps to diagnose and fix issues.
30+
You will assist developers in investigating and resolving mobile application issues by leveraging Firebase Crashlytics data.
10231
103-
1. Make sure you have a good understanding of the code structure and where different functionality exists
104-
2. Use the 'crashlytics_get_issue' tool to get more context on the issue.
105-
3. Use the 'crashlytics_batch_get_events' tool to get an example crash for this issue. Use the event names in the sampleEvent fields.
106-
3a. If you need to read more events, use the 'crashlytics_list_events' tool.
107-
3b. Apply the same filtering criteria that you used to find the issue, so that you find a appropriate events.
108-
4. Read the files that exist in the stack trace of the issue to understand the crash deeply.
109-
5. Determine possible root causes for the crash - no more than 5 potential root causes.
110-
6. Critique your own determination, analyzing how plausible each scenario is given the crash details.
111-
7. Choose the most likely root cause given your analysis.
112-
8. Write out a plan for the most likely root cause using the following criteria:
113-
8a. Write out a description of the issue and including
114-
* A brief description of the cause of the issue
115-
* A determination of your level of confidence in the cause of the issue using your analysis.
116-
* A determination of which library is at fault, this codebase or a dependent library
117-
* A determination for how complex the fix will be
118-
8b. The plan should include relevant files to change
119-
8c. The plan should include a test plan for how the user might verify the fix
120-
8d. Use the following format for the plan:
32+
### Required First Steps
12133
122-
## Cause
123-
<A description of the root cause leading to the issue>
124-
- **Fault**: <a determination of whether this code base is at fault or a dependent library is at fault>
125-
- **Complexity**: <one of "simple", "moderately simple", "moderately hard", "hard", "oof, I don't know where to start">
126-
127-
## Fix
128-
<A description of the fix for this issue and a break down of the changes.>
129-
1. <Step 1>
130-
2. <Step 2>
34+
${accountEmail ? loggedInInstruction : notLoggedInInstruction}
13135
132-
## Test
133-
<A plan for how to test that the issue has been fixed and protect against regressions>
134-
1. <Test case 1>
135-
2. <Test case 2>
36+
**Obtain the Firebase App ID.**
37+
If an App ID is not readily available, consult this guide for selection: [Firebase App Id Guide](firebase://guides/app_id).
13638
137-
## Other potential causes
138-
1. <Another possible root cause>
139-
2. <Another possible root cause>
140-
141-
9. Present the plan to the user and get approval before making the change.
142-
10. Only if they approve the plan, create a fix for the issue.
143-
10a. Be mindful of API contracts and do not add fields to resources without a clear way to populate those fields
144-
10b. If there is not enough information in the crash report to find a root cause, describe why you cannot fix the issue instead of making a guess.
39+
${connectResourceContent}
14540
`.trim(),
14641
},
14742
},

src/mcp/resources/guides/app_id.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { resource } from "../../resource";
2+
3+
export const RESOURCE_CONTENT = `
4+
### Firebase App ID
5+
The Firebase App ID is used to identify a mobile or web client application to Firebase back end services such as Crashlytics or Remote Config. Use the information below to find the developer's App ID.
6+
7+
1. **PRIORITIZE REMEMBERED APP ID ENTRIES** If an entry for this directory exists in the remembered app ids, use the remembered app id
8+
for this directory without presenting any additional options.
9+
i. If there are multiple remembered app ids for this directory, ask the user to choose one by providing
10+
a numbered list of all the package names. Tell them that these values came from memories and how they can modify those values.
11+
2. **IF THERE IS NO REMEMBERED ENTRY FOR THIS DIRECTORY** Use the app IDs from the \`firebase_get_environment\` tool.
12+
i. If you've already called this tool, use the previous response from context.
13+
ii. If the 'Detected App IDs' is set to <NONE>, ask the user for the value they want to use.
14+
iii. If there are multiple 'Detected App IDs', ask the user to choose one by providing
15+
a numbered list of all the package names and app ids.
16+
3. **IF THERE IS A REMEMBERED VALUE BUT IT DOES NOT MATCH ANY DETECTED APP IDS** Ask if the user would like to replace the value with one of
17+
the detected values.
18+
i. **Description:** A valid app ID to remember contains four colon (":") delimited parts: a version
19+
number (typically "1"), a project number, a platform type ("android", "ios", or "web"),
20+
and a sequence of hexadecimal characters.
21+
ii. Replace the value for this directory with this valid app id, the android package name or ios bundle identifier, and the project directory.
22+
4. **IF THERE IS NO REMEMBERED ENTRY FOR THIS DIRECTORY** Ask if the user would like to remember the app id selection
23+
i. **Description:** A valid app ID to remember contains four colon (":") delimited parts: a version
24+
number (typically "1"), a project number, a platform type ("android", "ios", or "web"),
25+
and a sequence of hexadecimal characters.
26+
ii. Store the valid app id value, the android package name or ios bundle identifier, and the project directory.
27+
`.trim();
28+
29+
export const app_id = resource(
30+
{
31+
uri: "firebase://guides/app_id",
32+
name: "app_id_guide",
33+
title: "Firebase App Id Guide",
34+
description:
35+
"guides the coding agent through choosing a Firebase App ID in the current project",
36+
},
37+
async (uri) => {
38+
return {
39+
contents: [{ uri, type: "text", text: RESOURCE_CONTENT }],
40+
};
41+
},
42+
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { resource } from "../../resource";
2+
3+
export const RESOURCE_CONTENT = `
4+
### Instructions for Working with Firebase Crashlytics Tools
5+
6+
Only ask the user one question at a time. Do not proceed without user instructions. Upon receiving user instructions, refer to the relevant resources for guidance.
7+
8+
Use the \`firebase_read_resources\` tool to access the following guides.
9+
10+
1. [Firebase App Id Guide](firebase://guides/app_id)
11+
This guide provides crucial instructions for obtaining the application's App Id which is required for all API calls.
12+
13+
2. [Firebase Crashlytics Reports Guide](firebase://guides/crashlytics/reports)
14+
This guide details how to request and use aggregated numerical data from Crashlytics. The agent should read this guide before requesting any report.
15+
16+
3. [Firebase Crashlytics Issues Guide](firebase://guides/crashlytics/issues)
17+
This guide details how to work with issues within Crashlytics. The agent should read this guide before prioritizing issues or presenting issue data to the user.
18+
19+
4. [Investigating Crashlytics Issues](firebase://guides/crashlytics/investigations)
20+
This guide provides instructions on investigating the root causes of crashes and exceptions reported in Crashlytics issues.
21+
22+
### Check That You Are Connected
23+
24+
Verify that you can read the app's Crashlytics data by getting the topVersions report. This report will tell you which app versions have the most events.
25+
a. Call the \`firebase_get_environment\` tool if you need to find the app_id.
26+
b. Call the \`crashlytics_get_report\` tool to read the \`topVersions\` report.
27+
c. If you haven't read the reports guide, then the tool will include it in the response. This is OK. Simply call the tool again.
28+
d. Help the user resolve any issues that arise when trying to connect.
29+
30+
After confirming you can access Crashlytics, inquire about the desired actions. Your capabilities include:
31+
32+
- Reading Crashlytics reports.
33+
- Investigating bug reports using Crashlytics event data.
34+
- Proposing code changes to resolve identified bugs.
35+
`.trim();
36+
37+
export const crashlytics_connect = resource(
38+
{
39+
uri: "firebase://guides/crashlytics/connect",
40+
name: "crashlytics_connect_guide",
41+
title: "Firebase Crashlytics Connect Guide",
42+
description: "Guides the coding agent to connect to Firebase Crashlytics.",
43+
},
44+
async (uri) => {
45+
return {
46+
contents: [{ uri, type: "text", text: RESOURCE_CONTENT }],
47+
};
48+
},
49+
);

0 commit comments

Comments
 (0)