Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fixes MCP server issue where `googleapis` is not available. (#9443)
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 @@ -174,6 +174,8 @@

export const appTestingOrigin = () =>
utils.envOverride("FIREBASE_APP_TESTING_URL", "https://firebaseapptesting.googleapis.com");
export const cloudTestingOrigin = () =>
utils.envOverride("CLOUD_TESTING_URL", "https://testing.googleapis.com");

/** Gets scopes that have been set. */
export function getScopes(): string[] {
Expand Down
35 changes: 35 additions & 0 deletions src/gcp/apptesting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Client } from "../apiv2";
import { cloudTestingOrigin } from "../api";

export const API_VERSION = "v1";

export const client = new Client({
urlPrefix: cloudTestingOrigin(),
auth: true,
apiVersion: API_VERSION,
});

type EnvironmentType =
| "ENVIRONMENT_TYPE_UNSPECIFIED"
| "ANDROID"
| "IOS"
| "NETWORK_CONFIGURATION"
| "PROVIDED_SOFTWARE"
| "DEVICE_IP_BLOCKS";

type TestEnvironmentCatalog = unknown;

/**
* Gets the catalog of supported test environments.
*/
export async function testEnvironmentCatalog(
projectId: string,
environmentType: EnvironmentType,
): Promise<unknown> {
Comment on lines +20 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using unknown for TestEnvironmentCatalog and the function's return type reduces type safety and makes the response harder to work with for consumers of this function. It would be beneficial to define a proper type for the TestEnvironmentCatalog based on the expected API response structure. Even a partial type definition with a TODO to fill it out later would be an improvement over unknown.

Suggested change
type TestEnvironmentCatalog = unknown;
/**
* Gets the catalog of supported test environments.
*/
export async function testEnvironmentCatalog(
projectId: string,
environmentType: EnvironmentType,
): Promise<unknown> {
interface TestEnvironmentCatalog {
// TODO: Define the properties of the test environment catalog based on the API response.
}
/**
* Gets the catalog of supported test environments.
*/
export async function testEnvironmentCatalog(
projectId: string,
environmentType: EnvironmentType,
): Promise<TestEnvironmentCatalog> {

const name = `testEnvironmentCatalog/${environmentType}`;

const queryParams: Record<string, string> = { projectId };
const res = await client.get<TestEnvironmentCatalog>(name, { queryParams });

return res.body;
}
15 changes: 7 additions & 8 deletions src/mcp/tools/apptesting/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { tool } from "../../tool";
import { toContent } from "../../util";
import { toAppName } from "../../../appdistribution/options-parser-util";
import { AppDistributionClient } from "../../../appdistribution/client";
import { google } from "googleapis";
import { getAccessToken } from "../../../apiv2";
import { testEnvironmentCatalog } from "../../../gcp/apptesting";

const TestDeviceSchema = z
.object({
Expand Down Expand Up @@ -98,20 +97,20 @@ export const check_status = tool(
title: "Check Remote Test",
readOnlyHint: true,
},
_meta: {
requiresAuth: true,
requiresProject: true,
},
},
async ({ release_test_name, getAvailableDevices }) => {
async ({ release_test_name, getAvailableDevices }, { projectId }) => {
let devices = undefined;
let releaseTest = undefined;
if (release_test_name) {
const client = new AppDistributionClient();
releaseTest = await client.getReleaseTest(release_test_name);
}
if (getAvailableDevices) {
const testing = google.testing("v1");
devices = await testing.testEnvironmentCatalog.get({
oauth_token: await getAccessToken(),
environmentType: "ANDROID",
});
devices = await testEnvironmentCatalog(projectId || "", "ANDROID");
}
Comment on lines 112 to 114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Passing an empty string for projectId when it's missing could lead to a cryptic backend error. Since this tool now explicitly requires a project, it's safer to fail fast with a clear error message if projectId is not available. Please add a check to ensure projectId is present before making the API call.

    if (getAvailableDevices) {
      if (!projectId) {
        throw new FirebaseError("Project ID is required to get available devices but is missing.");
      }
      devices = await testEnvironmentCatalog(projectId, "ANDROID");
    }


return toContent({
Expand Down
Loading