import { initializeApp, FirebaseApp } from 'firebase/app';
import { getFirestore, Firestore } from 'firebase/firestore';
import { getAuth, Auth, signInWithCustomToken, signOut } from 'firebase/auth';
interface FirebaseRuntimeConfig {
apiKey: string;
authDomain: string;
projectId: string;
storageBucket: string;
messagingSenderId: string;
appId: string;
}
function normalizeConfigValue(value: string | undefined): string {
return typeof value === 'string' ? value.trim() : '';
}
let firebaseConfig: FirebaseRuntimeConfig = {
apiKey: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_API_KEY),
authDomain: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_AUTH_DOMAIN),
projectId: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_PROJECT_ID),
storageBucket: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_STORAGE_BUCKET),
messagingSenderId: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_MESSAGING_SENDER_ID),
appId: normalizeConfigValue(process.env.ICE_PUZZLE_FIREBASE_APP_ID),
};
let app: FirebaseApp | null = null;
let db: Firestore | null = null;
let auth: Auth | null = null;
function hasRequiredFirebaseConfig(config: FirebaseRuntimeConfig): boolean {
return Boolean(config.apiKey && config.authDomain && config.projectId && config.appId);
}
export interface FirebaseOverrides {
apiKey?: string;
authDomain?: string;
projectId?: string;
storageBucket?: string;
messagingSenderId?: string;
appId?: string;
}
/**
* Apply runtime overrides before Firebase is initialized.
* Returns false if Firebase is already initialized.
*/
export function configureFirebase(overrides: FirebaseOverrides): boolean {
if (app || db || auth) {
return false;
}
firebaseConfig = {
...firebaseConfig,
...(overrides.apiKey?.trim() ? { apiKey: overrides.apiKey.trim() } : {}),
...(overrides.authDomain?.trim() ? { authDomain: overrides.authDomain.trim() } : {}),
...(overrides.projectId?.trim() ? { projectId: overrides.projectId.trim() } : {}),
...(overrides.storageBucket?.trim() ? { storageBucket: overrides.storageBucket.trim() } : {}),
...(overrides.messagingSenderId?.trim() ? { messagingSenderId: overrides.messagingSenderId.trim() } : {}),
...(overrides.appId?.trim() ? { appId: overrides.appId.trim() } : {}),
};
return true;
}
/**
* Lazily initialize Firebase. Only connects when a tool actually needs Firestore.
*/
export function initFirebase(): { app: FirebaseApp; db: Firestore; auth: Auth } | null {
if (app && db && auth) {
return { app, db, auth };
}
if (!hasRequiredFirebaseConfig(firebaseConfig)) {
return null;
}
try {
app = initializeApp(firebaseConfig, 'ice-puzzle-mcp');
db = getFirestore(app);
auth = getAuth(app);
return { app, db, auth };
} catch (error) {
console.error('[MCP Firebase] Initialization failed:', error);
return null;
}
}
/**
* Get Firestore instance. Returns null if not initialized.
*/
export function getDb(): Firestore | null {
if (!db) initFirebase();
return db;
}
/**
* Get Auth instance. Returns null if not initialized.
*/
export function getFirebaseAuth(): Auth | null {
if (!auth) initFirebase();
return auth;
}
/**
* Get Firebase app instance. Returns null if not initialized.
*/
export function getFirebaseApp(): FirebaseApp | null {
if (!app) initFirebase();
return app;
}
/**
* Check if Firebase is available and authenticated.
*/
export function isFirebaseAvailable(): boolean {
return db !== null && auth !== null;
}
/**
* Sign in with a custom token (from exchangeToken Cloud Function).
*/
export async function signInWithToken(customToken: string): Promise<string> {
const firebaseAuth = getFirebaseAuth();
if (!firebaseAuth) {
throw new Error('Firebase not initialized. Check your configuration.');
}
const credential = await signInWithCustomToken(firebaseAuth, customToken);
return credential.user.uid;
}
/**
* Sign out the current Firebase user (best-effort).
*/
export async function signOutCurrentUser(): Promise<void> {
const firebaseAuth = getFirebaseAuth();
if (!firebaseAuth || !firebaseAuth.currentUser) {
return;
}
await signOut(firebaseAuth);
}
/**
* Get the current user ID if signed in.
*/
export function getCurrentUserId(): string | null {
if (!auth?.currentUser) return null;
return auth.currentUser.uid;
}