Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5928982
Introduce Crashlytics MCP tool list_top_issues.
visumickey May 14, 2025
e950b6a
Address comments.
visumickey May 14, 2025
da2fa1d
Fixed a comment related to projectID validation.
visumickey May 14, 2025
5beea99
Try making the linter happy.
visumickey May 14, 2025
e52af4c
Fix the tab stop to make the code prettier.
visumickey May 14, 2025
4c68e74
Fix more linter/prettier issues.
visumickey May 14, 2025
fe8143e
Final batch of linter errors.
visumickey May 14, 2025
1214763
Fix the APi endpoint used for fetching top issues.
visumickey May 14, 2025
e7246f8
Address comments.
visumickey May 14, 2025
0d100ca
Fix linter errors.
visumickey May 14, 2025
1dd68f4
Few more linter errors.
visumickey May 14, 2025
16e99df
Add relevant headers and project ID to make things work E2E.
visumickey May 15, 2025
5594a69
Remove the lookup days parameter.
visumickey May 15, 2025
eb25a1f
Fix linters.
visumickey May 15, 2025
e559031
Fix the missing semicolon.
visumickey May 15, 2025
3621b6f
Add the new tool to the readme.
visumickey May 15, 2025
6eb2577
Fix the variable for Crashlytics URL.
visumickey May 15, 2025
5670061
Fix the verbiage.
visumickey May 15, 2025
dce4308
Remove the readme changes.
visumickey May 15, 2025
3cbfd1f
Bring back README
visumickey May 15, 2025
5d9d7e7
Restore README file.
visumickey May 15, 2025
3770de8
Merge branch 'master' into mcp_crashlytics
mbleigh May 15, 2025
c8d43eb
Merge branch 'master' into mcp_crashlytics
mbleigh May 15, 2025
7259399
Make the AppId optional.
visumickey May 15, 2025
2e5e30c
More cues to the model to find the default appId.
visumickey May 15, 2025
d6757f4
Linted.
visumickey May 15, 2025
a784ac7
Merge branch 'master' into mcp_crashlytics
mbleigh May 16, 2025
942e147
Merge branch 'master' into mcp_crashlytics
mbleigh May 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@

let commandScopes = new Set<string>();

export const authProxyOrigin = () =>

Check warning on line 8 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools");
// "In this context, the client secret is obviously not treated as a secret"
// https://developers.google.com/identity/protocols/OAuth2InstalledApp
export const clientId = () =>

Check warning on line 12 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride(
"FIREBASE_CLIENT_ID",
"563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
);
export const clientSecret = () =>

Check warning on line 17 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("FIREBASE_CLIENT_SECRET", "j9iVZfS8kkCEFUPaAeJV0sAi");
export const cloudbillingOrigin = () =>

Check warning on line 19 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com");
export const cloudloggingOrigin = () =>

Check warning on line 21 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("FIREBASE_CLOUDLOGGING_URL", "https://logging.googleapis.com");
export const cloudMonitoringOrigin = () =>

Check warning on line 23 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("CLOUD_MONITORING_URL", "https://monitoring.googleapis.com");
export const containerRegistryDomain = () =>

Check warning on line 25 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io");

export const developerConnectOrigin = () =>

Check warning on line 28 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("DEVELOPERCONNECT_URL", "https://developerconnect.googleapis.com");
export const developerConnectP4SADomain = () =>

Check warning on line 30 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("DEVELOPERCONNECT_P4SA_DOMAIN", "gcp-sa-devconnect.iam.gserviceaccount.com");

export const artifactRegistryDomain = () =>

Check warning on line 33 in src/api.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com");
export const appDistributionOrigin = () =>
utils.envOverride(
Expand Down Expand Up @@ -127,6 +127,8 @@
utils.envOverride("FIREBASE_REMOTE_CONFIG_URL", "https://firebaseremoteconfig.googleapis.com");
export const messagingApiOrigin = () =>
utils.envOverride("FIREBASE_MESSAGING_CONFIG_URL", "https://fcm.googleapis.com");
export const crashlyticsApiOrigin = () =>
utils.envOverride("FIREBASE_CRASHLYTICS_URL", "https://firebasecrashlytics.googleapis.com");
export const resourceManagerOrigin = () =>
utils.envOverride("FIREBASE_RESOURCEMANAGER_URL", "https://cloudresourcemanager.googleapis.com");
export const rulesOrigin = () =>
Expand Down
53 changes: 53 additions & 0 deletions src/crashlytics/listTopIssues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Client } from "../apiv2";
import { logger } from "../logger";
import { FirebaseError } from "../error";
import { crashlyticsApiOrigin } from "../api";

const TIMEOUT = 10000;

const apiClient = new Client({
urlPrefix: crashlyticsApiOrigin(),
apiVersion: "v1alpha",
});

export async function listTopIssues(
projectId: string,
appId: string,
issueCount: number,
): Promise<string> {
try {
const queryParams = new URLSearchParams();
queryParams.set("page_size", `${issueCount}`);

const requestProjectId = parseProjectId(appId);
if (requestProjectId === undefined) {
throw new FirebaseError("Unable to get the projectId from the AppId.");
}

const response = await apiClient.request<void, string>({
method: "GET",
headers: {
"Content-Type": "application/json",
},
path: `/projects/${requestProjectId}/apps/${appId}/reports/topIssues`,
queryParams: queryParams,
timeout: TIMEOUT,
});

return response.body;
} catch (err: any) {
logger.debug(err.message);
throw new FirebaseError(
`Failed to fetch the top issues for the Firebase Project ${projectId}, AppId ${appId}. Error: ${err}.`,
{ original: err },
);
}
}

function parseProjectId(appId: string): string | undefined {
const appIdParts = appId.split(":");
if (appIdParts.length > 1) {
return appIdParts[1];
}
return undefined;
}
4 changes: 4 additions & 0 deletions src/mcp/tools/crashlytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { ServerTool } from "../../tool.js";
import { list_top_issues } from "./list_top_issues.js";

export const crashlyticsTools: ServerTool[] = [list_top_issues];
38 changes: 38 additions & 0 deletions src/mcp/tools/crashlytics/list_top_issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { z } from "zod";
import { tool } from "../../tool.js";
import { mcpError, toContent } from "../../util.js";
import { listTopIssues } from "../../../crashlytics/listTopIssues.js";

export const list_top_issues = tool(
{
name: "list_top_issues",
description: "List the top crashes from crashlytics happening in the application.",
inputSchema: z.object({
app_id: z
.string()
.optional()
.describe(
"AppId for which the issues list is fetched. Defaults to the first appId provided by firebase_list_apps.",
),
issue_count: z
.number()
.optional()
.describe("Number of issues that needs to be fetched. Defaults to 10 if unspecified."),
}),
annotations: {
title: "List Top Crashlytics Issues.",
readOnlyHint: true,
},
_meta: {
requiresAuth: true,
requiresProject: true,
},
},
async ({ app_id, issue_count }, { projectId }) => {
if (!app_id) return mcpError(`Must specify 'app_id' parameter.`);

issue_count ??= 10;

return toContent(await listTopIssues(projectId!, app_id, issue_count));
},
);
2 changes: 2 additions & 0 deletions src/mcp/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { coreTools } from "./core/index.js";
import { storageTools } from "./storage/index.js";
import { messagingTools } from "./messaging/index.js";
import { remoteConfigTools } from "./remoteconfig/index.js";
import { crashlyticsTools } from "./crashlytics/index.js";

/** availableTools returns the list of MCP tools available given the server flags */
export function availableTools(activeFeatures?: ServerFeature[]): ServerTool[] {
Expand All @@ -28,6 +29,7 @@ const tools: Record<ServerFeature, ServerTool[]> = {
storage: addFeaturePrefix("storage", storageTools),
messaging: addFeaturePrefix("messaging", messagingTools),
remoteconfig: addFeaturePrefix("remoteconfig", remoteConfigTools),
crashlytics: addFeaturePrefix("crashlytics", crashlyticsTools),
};

function addFeaturePrefix(feature: string, tools: ServerTool[]): ServerTool[] {
Expand Down
1 change: 1 addition & 0 deletions src/mcp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const SERVER_FEATURES = [
"auth",
"messaging",
"remoteconfig",
"crashlytics",
] as const;
export type ServerFeature = (typeof SERVER_FEATURES)[number];

Expand Down
2 changes: 2 additions & 0 deletions src/mcp/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
messagingApiOrigin,
remoteConfigApiOrigin,
storageOrigin,
crashlyticsApiOrigin,
} from "../api";
import { check } from "../ensureApiEnabled";

Expand Down Expand Up @@ -85,6 +86,7 @@ const SERVER_FEATURE_APIS: Record<ServerFeature, string> = {
auth: authManagementOrigin(),
messaging: messagingApiOrigin(),
remoteconfig: remoteConfigApiOrigin(),
crashlytics: crashlyticsApiOrigin(),
};

/**
Expand Down
Loading