Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2b87a86
Add Firebase App provisioning API integration
TrCaM Sep 19, 2025
74f3687
Firebase MCP init tool schema change to support provisioning
TrCaM Sep 22, 2025
6c2b93a
Add methods to detect existing local apps from directories
TrCaM Sep 22, 2025
31e6327
Add methods to ensure local file locations for app provisioning
TrCaM Sep 22, 2025
b68727f
Wire up provisioning logic with fake API service - Verify logic first…
TrCaM Sep 22, 2025
1c2cc10
Archive mock provisioning service for future reference
TrCaM Sep 24, 2025
d411761
Complete first working version of revamped firebase init MCP tool
TrCaM Sep 24, 2025
a1cd048
Merge master into feature branch
TrCaM Sep 24, 2025
647eb87
Fix lint/formating - Add unit test for new firebase init tool
TrCaM Sep 24, 2025
3309d71
Fix lint/formating - Add unit test for new firebase init tool
TrCaM Sep 24, 2025
0a0af59
Minor pr fixes
joehan Sep 24, 2025
193d196
Adding missing files
joehan Sep 24, 2025
5f3a6b9
Merge branch 'master' into jh-provisioning
joehan Sep 24, 2025
e3afdee
Merge branch 'master' into jh-provisioning
joehan Sep 24, 2025
9814ef9
Merge branch 'master' into jh-provisioning
joehan Sep 24, 2025
6bedcaf
Reduce provisioning integration scope for only AI Logic
TrCaM Sep 25, 2025
265d339
Complete orchestration API provisioning to AI Logic feature (Both CLI…
TrCaM Sep 25, 2025
8b0ff79
Merge remote-tracking branch 'origin/master' into caot/mcp/ailogic
TrCaM Sep 25, 2025
d032ff7
Fix lint and unit tests
TrCaM Sep 25, 2025
82d5443
Address Gemini code assist comments
TrCaM Sep 25, 2025
c3b33c6
Simplify ailogic init feature to only use existing project and app
TrCaM Sep 27, 2025
a7de790
Merge remote-tracking branch 'origin/master' into caot-mcp-ailogic
TrCaM Sep 27, 2025
ade68ce
Fix linting
TrCaM Sep 27, 2025
f1906de
Addresses comments
TrCaM Sep 30, 2025
671cec2
Merge remote-tracking branch 'origin/master' into caot-mcp-ailogic
TrCaM Sep 30, 2025
9dc2299
Check projectId is non-empty for ailogic feature (MCP tool)
TrCaM Sep 30, 2025
ac25b2e
Fixing app display name update when provision ailogic
TrCaM Oct 1, 2025
28f1d34
Merge origin/master into caot-mcp-ailogic with display name functiona…
TrCaM Oct 1, 2025
a3625d0
Fix provisioning tests to use app.displayName instead of project.disp…
TrCaM Oct 1, 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
1 change: 1 addition & 0 deletions src/init/features/ailogic/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe("init ailogic", () => {
expect(mockSetup.featureInfo).to.have.property("ailogic");
expect(mockSetup.featureInfo?.ailogic).to.deep.equal({
appId: "1:123456789:android:abcdef123456",
displayName: "Test Android App",
});
});

Expand Down
10 changes: 7 additions & 3 deletions src/init/features/ailogic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {

export interface AiLogicInfo {
appId: string;
displayName?: string;
}

function checkForApps(apps: AppMetadata[]): void {
Expand Down Expand Up @@ -69,25 +70,29 @@ export async function askQuestions(setup: Setup): Promise<void> {

setup.featureInfo.ailogic = {
appId: selectedApp.appId,
displayName: selectedApp.displayName,
};
}

function getAppOptions(appInfo: AppInfo): ProvisionAppOptions {
function getAppOptions(appInfo: AppInfo, displayName?: string): ProvisionAppOptions {
switch (appInfo.platform) {
case AppPlatform.IOS:
return {
platform: AppPlatform.IOS,
appId: appInfo.appId,
displayName,
};
case AppPlatform.ANDROID:
return {
platform: AppPlatform.ANDROID,
appId: appInfo.appId,
displayName,
};
case AppPlatform.WEB:
return {
platform: AppPlatform.WEB,
appId: appInfo.appId,
displayName,
};
default:
throw new FirebaseError(`Unsupported platform ${appInfo.platform}`, { exit: 1 });
Expand Down Expand Up @@ -115,10 +120,9 @@ export async function actuate(setup: Setup): Promise<void> {
// Build provision options and call API directly
const provisionOptions: ProvisionFirebaseAppOptions = {
project: {
displayName: "Firebase Project",
parent: { type: "existing_project", projectId: setup.projectId },
},
app: getAppOptions(appInfo),
app: getAppOptions(appInfo, ailogicInfo.displayName),
features: {
firebaseAiLogicInput: {},
},
Expand Down
44 changes: 30 additions & 14 deletions src/init/features/ailogic/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ describe("ailogic utils", () => {
});

describe("validateAppExists", () => {
let getAppConfigStub: sinon.SinonStub;
let listFirebaseAppsStub: sinon.SinonStub;

beforeEach(() => {
getAppConfigStub = sandbox.stub(apps, "getAppConfig");
listFirebaseAppsStub = sandbox.stub(apps, "listFirebaseApps");
});

it("should not throw when app exists for web platform", async () => {
Expand All @@ -151,10 +151,14 @@ describe("ailogic utils", () => {
appId: "1:123456789:web:abcdef",
platform: AppPlatform.WEB,
};
getAppConfigStub.resolves({ mockConfig: true });
const mockApps = [
{ appId: "1:123456789:web:abcdef", displayName: "Test App", platform: AppPlatform.WEB },
];
listFirebaseAppsStub.resolves(mockApps);

await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
sinon.assert.calledWith(getAppConfigStub, "1:123456789:web:abcdef", AppPlatform.WEB);
const result = await utils.validateAppExists(appInfo, "test-project");
expect(result).to.deep.equal(mockApps[0]);
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.WEB);
});

it("should not throw when app exists for ios platform", async () => {
Expand All @@ -163,10 +167,14 @@ describe("ailogic utils", () => {
appId: "1:123456789:ios:abcdef",
platform: AppPlatform.IOS,
};
getAppConfigStub.resolves({ mockConfig: true });
const mockApps = [
{ appId: "1:123456789:ios:abcdef", displayName: "Test iOS App", platform: AppPlatform.IOS },
];
listFirebaseAppsStub.resolves(mockApps);

await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
sinon.assert.calledWith(getAppConfigStub, "1:123456789:ios:abcdef", AppPlatform.IOS);
const result = await utils.validateAppExists(appInfo, "test-project");
expect(result).to.deep.equal(mockApps[0]);
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.IOS);
});

it("should not throw when app exists for android platform", async () => {
Expand All @@ -175,10 +183,18 @@ describe("ailogic utils", () => {
appId: "1:123456789:android:abcdef",
platform: AppPlatform.ANDROID,
};
getAppConfigStub.resolves({ mockConfig: true });
const mockApps = [
{
appId: "1:123456789:android:abcdef",
displayName: "Test Android App",
platform: AppPlatform.ANDROID,
},
];
listFirebaseAppsStub.resolves(mockApps);

await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
sinon.assert.calledWith(getAppConfigStub, "1:123456789:android:abcdef", AppPlatform.ANDROID);
const result = await utils.validateAppExists(appInfo, "test-project");
expect(result).to.deep.equal(mockApps[0]);
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.ANDROID);
});

it("should throw when app does not exist", async () => {
Expand All @@ -187,11 +203,11 @@ describe("ailogic utils", () => {
appId: "1:123456789:web:nonexistent",
platform: AppPlatform.WEB,
};
getAppConfigStub.throws(new Error("App not found"));
listFirebaseAppsStub.resolves([]);

await expect(utils.validateAppExists(appInfo)).to.be.rejectedWith(
await expect(utils.validateAppExists(appInfo, "test-project")).to.be.rejectedWith(
FirebaseError,
"App 1:123456789:web:nonexistent does not exist or is not accessible.",
"App 1:123456789:web:nonexistent does not exist in project test-project.",
);
});
});
Expand Down
19 changes: 16 additions & 3 deletions src/init/features/ailogic/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AppPlatform, getAppConfig } from "../../../management/apps";
import { AppPlatform, listFirebaseApps, AppMetadata } from "../../../management/apps";
import { FirebaseError } from "../../../error";
import { FirebaseProjectMetadata } from "../../../types/project";

Expand Down Expand Up @@ -81,10 +81,23 @@ export function validateProjectNumberMatch(
/**
* Validate that app exists
*/
export async function validateAppExists(appInfo: AppInfo): Promise<void> {
export async function validateAppExists(appInfo: AppInfo, projectId: string): Promise<AppMetadata> {
try {
await getAppConfig(appInfo.appId, appInfo.platform);
// Get apps list to find the specific app with metadata
const apps = await listFirebaseApps(projectId, appInfo.platform);
const app = apps.find((a) => a.appId === appInfo.appId);

if (!app) {
throw new FirebaseError(`App ${appInfo.appId} does not exist in project ${projectId}.`, {
exit: 1,
});
}

return app;
} catch (error) {
if (error instanceof FirebaseError) {
throw error;
}
throw new FirebaseError(`App ${appInfo.appId} does not exist or is not accessible.`, {
exit: 1,
original: error instanceof Error ? error : new Error(String(error)),
Expand Down
2 changes: 1 addition & 1 deletion src/management/provisioning/provision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function buildProvisionRequest(

return {
appNamespace: buildAppNamespace(options.app),
displayName: options.project.displayName,
displayName: options.app.displayName,
...(options.project.parent && { parent: buildParentString(options.project.parent) }),
...(options.features?.location && { location: options.features.location }),
...(options.requestId && { requestId: options.requestId }),
Expand Down
1 change: 1 addition & 0 deletions src/management/provisioning/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AppPlatform } from "../apps";
interface BaseProvisionAppOptions {
platform: AppPlatform;
appId?: string;
displayName?: string;
}

interface IosAppOptions extends BaseProvisionAppOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/mcp/tools/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ export const init = tool(
const appInfo = parseAppId(features.ailogic.app_id);
const projectInfo = await getFirebaseProject(projectId);
validateProjectNumberMatch(appInfo, projectInfo);
await validateAppExists(appInfo);
const appData = await validateAppExists(appInfo, projectId);

featuresList.push("ailogic");
featureInfo.ailogic = {
appId: features.ailogic.app_id,
displayName: appData.displayName,
};
}
const setup: Setup = {
Expand Down
Loading