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
3 changes: 1 addition & 2 deletions src/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ npx -y firebase-tools login
| storage_get_object_download_url | storage | Retrieves the download URL for an object in Firebase Storage. |
| messaging_send_message | messaging | Sends a message to a Firebase Cloud Messaging registration token or topic. ONLY ONE of `registration_token` or `topic` may be supplied in a specific call. |
| remoteconfig_get_template | remoteconfig | Retrieves a remote config template for the project |
| remoteconfig_publish_template | remoteconfig | Publishes a new remote config template for the project |
| remoteconfig_rollback_template | remoteconfig | Rollback to a specific version of Remote Config template for a project |
| remoteconfig_update_template | remoteconfig | Publishes a new remote config template or rolls back to a specific version for the project |
| crashlytics_add_note | crashlytics | Add a note to an issue from crashlytics. |
| crashlytics_delete_note | crashlytics | Delete a note from an issue in Crashlytics. |
| crashlytics_get_issue_details | crashlytics | Gets the details about a specific crashlytics issue. |
Expand Down
5 changes: 2 additions & 3 deletions src/mcp/tools/remoteconfig/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ServerTool } from "../../tool";
import { get_template } from "./get_template";
import { rollback_template } from "./rollback_template";
import { publish_template } from "./publish_template";
import { update_template } from "./update_template";

export const remoteConfigTools: ServerTool[] = [get_template, publish_template, rollback_template];
export const remoteConfigTools: ServerTool[] = [get_template, update_template];
38 changes: 0 additions & 38 deletions src/mcp/tools/remoteconfig/publish_template.ts

This file was deleted.

30 changes: 0 additions & 30 deletions src/mcp/tools/remoteconfig/rollback_template.ts

This file was deleted.

93 changes: 93 additions & 0 deletions src/mcp/tools/remoteconfig/update_template.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { expect } from "chai";
import * as sinon from "sinon";
import * as nock from "nock";
import * as api from "../../../api";
import { RemoteConfigTemplate } from "../../../remoteconfig/interfaces";
import { update_template } from "./update_template";
import { toContent } from "../../util";
import { McpContext } from "../../types";

const PROJECT_ID = "the-remote-config-project";
const TEMPLATE: RemoteConfigTemplate = {
conditions: [],
parameters: {},
parameterGroups: {},
etag: "whatever",
version: {
versionNumber: "1",
updateTime: "2020-01-01T12:00:00.000000Z",
updateUser: {
email: "someone@google.com",
},
updateOrigin: "CONSOLE",
updateType: "INCREMENTAL_UPDATE",
},
};

describe("update_template", () => {
let sandbox: sinon.SinonSandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
});

afterEach(() => {
sandbox.restore();
nock.cleanAll();
});

it("should publish the latest template", async () => {
nock(api.remoteConfigApiOrigin())
.put(`/v1/projects/${PROJECT_ID}/remoteConfig`)
.reply(200, TEMPLATE);

const result = await update_template.fn({ template: TEMPLATE }, {
projectId: PROJECT_ID,
} as any as McpContext);

Check warning on line 46 in src/mcp/tools/remoteconfig/update_template.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
expect(result).to.deep.equal(toContent(TEMPLATE));
});

it("should publish the latest template with * etag", async () => {
nock(api.remoteConfigApiOrigin())
.put(`/v1/projects/${PROJECT_ID}/remoteConfig`, undefined, {
reqheaders: {
"If-Match": "*",
},
})
.reply(200, TEMPLATE);

const result = await update_template.fn({ template: TEMPLATE, force: true }, {
projectId: PROJECT_ID,
} as any as McpContext);

Check warning on line 61 in src/mcp/tools/remoteconfig/update_template.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
expect(result).to.deep.equal(toContent(TEMPLATE));
});

it("should reject if the publish api call fails", async () => {
nock(api.remoteConfigApiOrigin()).put(`/v1/projects/${PROJECT_ID}/remoteConfig`).reply(404, {});

await expect(
update_template.fn({ template: TEMPLATE }, { projectId: PROJECT_ID } as any as McpContext),

Check warning on line 69 in src/mcp/tools/remoteconfig/update_template.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
).to.be.rejected;
});

it("should return a rollback to the version number specified", async () => {
nock(api.remoteConfigApiOrigin())
.post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=1`)
.reply(200, TEMPLATE);

const result = await update_template.fn({ version_number: 1 }, {
projectId: PROJECT_ID,
} as any as McpContext);

Check warning on line 80 in src/mcp/tools/remoteconfig/update_template.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
expect(result).to.deep.equal(toContent(TEMPLATE));
});

it("should reject if the rollback api call fails", async () => {
nock(api.remoteConfigApiOrigin())
.post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=1`)
.reply(404, {});

await expect(
update_template.fn({ version_number: 1 }, { projectId: PROJECT_ID } as any as McpContext),

Check warning on line 90 in src/mcp/tools/remoteconfig/update_template.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
).to.be.rejected;
});
});
62 changes: 62 additions & 0 deletions src/mcp/tools/remoteconfig/update_template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { z } from "zod";
import { tool } from "../../tool";
import { mcpError, toContent } from "../../util";
import { publishTemplate } from "../../../remoteconfig/publish";
import { rollbackTemplate } from "../../../remoteconfig/rollback";
import { RemoteConfigTemplate } from "../../../remoteconfig/interfaces";

export const update_template = tool(
{
name: "update_template",
description:
"Publishes a new remote config template or rolls back to a specific version for the project",
inputSchema: z
.object({
template: z.object({}).optional().describe("The Remote Config template object to publish."),
version_number: z
.number()
.positive()
.optional()
.describe("The version number to roll back to."),
force: z
.boolean()
.optional()
.describe(
"If true, the publish will bypass ETag validation and overwrite the current template. Defaults to false if not provided.",
),
})
.refine(
(data) =>
(data.template && !data.version_number) || (!data.template && data.version_number),
{
message:
"Either provide a template for publish, or a version number to rollback to, but not both.",
},
),
annotations: {
title: "Update Remote Config template",
readOnlyHint: false,
},
_meta: {
requiresAuth: true,
requiresProject: true,
},
},
async ({ template, version_number, force }, { projectId }) => {
if (version_number) {
return toContent(await rollbackTemplate(projectId, version_number!));

Check warning on line 47 in src/mcp/tools/remoteconfig/update_template.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Forbidden non-null assertion

Check warning on line 47 in src/mcp/tools/remoteconfig/update_template.ts

View workflow job for this annotation

GitHub Actions / lint (20)

This assertion is unnecessary since it does not change the type of the expression
}

if (template) {
if (force === undefined) {
return toContent(await publishTemplate(projectId, template as any as RemoteConfigTemplate));

Check warning on line 52 in src/mcp/tools/remoteconfig/update_template.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
}
return toContent(
await publishTemplate(projectId, template as any as RemoteConfigTemplate, { force }),

Check warning on line 55 in src/mcp/tools/remoteconfig/update_template.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
);
}

// This part should not be reached due to the refine validation, but as a safeguard:
return mcpError("Either a template or a version number must be specified.");
},
);
Loading