#
tokens: 6411/50000 12/12 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── jest.setup.js
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   └── lib
│       └── firebase
│           ├── __tests__
│           │   ├── authClient.test.ts
│           │   ├── firestoreClient.test.ts
│           │   └── storageClient.test.ts
│           ├── authClient.ts
│           ├── firebaseConfig.ts
│           ├── firestoreClient.ts
│           └── storageClient.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*


# Compiled binary addons (https://nodejs.org/api/addons.html)
build/
dist/

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache


# Optional REPL history
.node_repl_history

# dotenv environment variable files
.env
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Firebase MCP Server

## Overview
This is a Firebase MCP (Model Context Protocol) server that provides a unified interface to interact with various Firebase services including Authentication, Firestore, and Storage.

## Setup
1. Clone and build the project:
   ```bash
   git clone https://github.com/gemini-dk/mcp-server-firebase
   cd mcp-server-firebase
   npm install
   npm run build
   ```

2. Get Firebase service account key:
   - Go to Firebase Console > Project Settings > Service accounts
   - Click "Generate new private key"
   - Save the JSON file to your project directory

3. Configure `mcp_settings.json`:
   ```json
   {
     "firebase-mcp": {
       "command": "node",
       "args": [
         "/path/to/mcp-server-firebase/dist/index.js"
       ],
       "env": {
         "SERVICE_ACCOUNT_KEY_PATH": "/path/to/serviceAccountKey.json"
       }
     }
   }
   ```
   Replace `/path/to/mcp-server-firebase` with the actual path where you cloned the repository.
   Replace `/path/to/serviceAccountKey.json` with the path to your service account key file.

## Available APIs
### Authentication
- Get user by ID or email

### Firestore
- Add/update/delete documents
- List collections/documents

### Storage
- List files in a directory
- Get File metadata and Download URL

## License
- MIT License

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------

```javascript
process.env.SERVICE_ACCOUNT_KEY_PATH = path.resolve(process.cwd(), 'serviceAccountKey.json');

global.console = {
  ...console,
  log: jest.fn((message) => process.stdout.write(message + '\\n')),
  info: jest.fn((message) => process.stdout.write(message + '\\n')),
  warn: jest.fn((message) => process.stdout.write(message + '\\n')),
  error: jest.fn((message) => process.stderr.write(message + '\\n')),
};

```

--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/storageClient.test.ts:
--------------------------------------------------------------------------------

```typescript
import { listDirectoryFiles, getFileInfo } from '../storageClient';

const testPath = '';
const testFilePath = 'testpath';

describe('listFiles', () => {
  it('should return a list of files in the specified path', async () => {
    const result = await listDirectoryFiles(testPath);
    console.log(result);
  });
});

describe('getFileInfo', () => {
  it('should return file metadata and download URL for the specified file', async () => {
    const result = await getFileInfo(testFilePath);
    console.log(result);
  });
});

```

--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/authClient.test.ts:
--------------------------------------------------------------------------------

```typescript
import { getUserByIdOrEmail } from '../authClient';

const testEmail = '[email protected]';
const testId = 'testid';

describe('getUserByIdOrEmail', () => {
  it('should return user data when a valid UID is provided', async () => {
    const result = await getUserByIdOrEmail(testId);
    expect(result.content[0].text).toContain(testId);
    expect(result.content[0].text).toContain(testEmail);
    console.log(result.content[0].text);
  });

  it('should return user data when a valid email is provided', async () => {
    const result = await getUserByIdOrEmail(testEmail);
    expect(result.content[0].text).toContain(testId);
    expect(result.content[0].text).toContain(testEmail);
    console.log(result.content[0].text);
  });
});

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "firebase-mcp",
  "version": "1.0.0",
  "description": "MCP server for Firestore operations",
  "main": "index.js",
  "scripts": {
    "build": "tsc",
    "test": "jest",
    "start": "node dist/index.js",
    "dev": "tsc && node dist/index.js"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node",
    "testMatch": [
      "**/__tests__/**/*.ts?(x)",
      "**/?(*.)+(spec|test).ts?(x)"
    ]
  },
  "directories": {
    "test": "test"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.5.0",
    "express": "^4.18.2",
    "firebase-admin": "^11.11.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.0",
    "@types/jest": "^27.0.0",
    "@types/node": "^22.13.4",
    "jest": "^29.0.0",
    "ts-jest": "^29.2.5",
    "typescript": "^5.7.3"
  }
}

```

--------------------------------------------------------------------------------
/src/lib/firebase/firebaseConfig.ts:
--------------------------------------------------------------------------------

```typescript
import * as admin from 'firebase-admin';
import fs from 'fs';

function initializeFirebase() {
  const serviceAccountPath = process.env.SERVICE_ACCOUNT_KEY_PATH;
  
  if (!serviceAccountPath) {
    console.error('Firebase initialization skipped: SERVICE_ACCOUNT_KEY_PATH is not set');
    return null;
  }

  try {
    const serviceAccount = require(serviceAccountPath);
    const projectId = getProjectId(serviceAccountPath);

    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
      storageBucket: `${projectId}.appspot.com`
    });

    return admin;
  } catch (error) {
    console.error('Firebase initialization failed:', error);
    return null;
  }
}

function getProjectId(serviceAccountPath?: string): string {
  if (!serviceAccountPath) {
    serviceAccountPath = process.env.SERVICE_ACCOUNT_KEY_PATH;
    if (!serviceAccountPath) {
      console.error('Cannot get project ID: SERVICE_ACCOUNT_KEY_PATH is not set');
      return '';
    }
  }
  
  try {
    const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountPath, 'utf8'));
    return serviceAccount.project_id;
  } catch (error) {
    console.error('Failed to get project ID:', error);
    return '';
  }
}

const adminApp = initializeFirebase();
const db = adminApp ? admin.firestore() : null;

export { db, admin, getProjectId };

```

--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/firestoreClient.test.ts:
--------------------------------------------------------------------------------

```typescript
import { listDocuments, addDocument, getDocument, updateDocument, deleteDocument, list_collections } from '../firestoreClient';

describe('Firebase Client', () => {
  const collectionName = '';
  const documentId = 'testid';


//  it('should add a document', async () => {
//    const result = await addDocument(collectionName, documentData);
//    console.log(result.content[0].text);
//  });

  it('should list collections', async () => {
    const result = await list_collections();
    console.log(result.content[0].text);
  });

  it('should list documents', async () => {
    const date = new Date('2025-02-20T00:00:00.000Z');
    const jstDate = new Date(date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' }));
    const result = await listDocuments(collectionName, [{ field: 'lastAccessTime', operator: '>=', value: jstDate.toISOString() }]);
    const jsonData = JSON.parse(result.content[0].text);
    const logobj:{totalCount:number,documents:{id:string,url:string}[]} = {totalCount:0,documents:[]};
    logobj.totalCount = jsonData.totalCount;
    for(const doc of jsonData.documents) {
        logobj.documents.push({id:doc.id,url:doc.url});
    }
    console.log(logobj);
  });

  it('should get a document', async () => {
    const result = await getDocument(collectionName, documentId);
    console.log(result.content[0].text);
  });

//  it('should update a document', async () => {
//    const updatedData = { field1: 'updatedValue' };
//    const result = await updateDocument(collectionName, documentId, updatedData);
//    console.log(result.content[0].text);
//  });
//
//  it('should delete a document', async () => {
//    const result = await deleteDocument(collectionName, documentId);
//    console.log(result.content[0].text);
//  });
});

```

--------------------------------------------------------------------------------
/src/lib/firebase/storageClient.ts:
--------------------------------------------------------------------------------

```typescript
import { admin, getProjectId } from './firebaseConfig';

//const storage = admin.storage().bucket();

/**
 * List files in a given path in Firebase Storage.
 * @param {string} [path] - The optional path to list files from. If not provided, the root is used.
 * @returns {Promise<{ content: { type: string , text: string }[] }>} - A promise that resolves to an object containing file names.
 */
export async function listDirectoryFiles(path?: string, pageSize: number = 10, pageToken?: string): Promise<{ content: { type: string , text: string }[] }> {
  const prefix = path ? (path === '' ? '' : (path.endsWith('/') ? path : `${path}/`)) : '';
  const [files, , apiResponse] = await admin.storage().bucket().getFiles({ 
    prefix, 
    delimiter: '/', 
    maxResults: pageSize,
    pageToken
  });
  const nextPageToken = apiResponse.nextPageToken as string || undefined;

  const fileNames = await Promise.all(files.map(async (file) => {
    const [signedUrl] = await file.getSignedUrl({
      action: 'read',
      expires: Date.now() + 1000 * 60 * 60 // 1 hour
    });
    return { type: "file", name: file.name, downloadURL: signedUrl };
  }));

  const projectId = getProjectId();
  const bucketName = admin.storage().bucket().name;
  const directoryNames = (apiResponse.prefixes || []).map((prefix:string) => {    
    //const encodedPrefix = encodeURIComponent(prefix).replace(/'/g, '%27').replace(/%20/g, '+');
    const tmpPrefix = prefix.replace(/\/$/, '');
    const encodedPrefix = `~2F${tmpPrefix.replace(/\//g, '~2F')}`;
    const consoleUrl = `https://console.firebase.google.com/project/${projectId}/storage/${bucketName}/files/${encodedPrefix}`;
    return { type: "directory", name: prefix, url: consoleUrl };
  });

    const result = { 
      nextPageToken: nextPageToken, 
      files: [...fileNames, ...directoryNames],
      hasMore: nextPageToken !== undefined
    };
  return {
    content:[
      {
        type: "text",
        text: JSON.stringify(result,null,2)
      }
    ]
  }
}

/**
 * Get file information including metadata and download URL.
 * @param {string} filePath - The path of the file to get information for.
 * @returns {Promise<{ content: object }>} - A promise that resolves to an object containing file metadata and download URL.
 */
export async function getFileInfo(filePath: string): Promise<{ content: { type: string , text: string }[] }> {
  const file = admin.storage().bucket().file(filePath);
  const [metadata] = await file.getMetadata();
  const [url] = await file.getSignedUrl({
    action: 'read',
    expires: Date.now() + 1000 * 60 * 60 // 1 hour
  });
  const result = { metadata, downloadUrl:url };
  return {
    content: [
      { type:'text', text: JSON.stringify(result,null,2) }
    ]
  };
}

```

--------------------------------------------------------------------------------
/src/lib/firebase/firestoreClient.ts:
--------------------------------------------------------------------------------

```typescript
import { Query, Timestamp } from 'firebase-admin/firestore';
import {db, getProjectId} from './firebaseConfig';
import fs from 'fs';
import path from 'path';

export async function list_collections(documentPath?: string, limit: number = 20, pageToken?: string) {
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    let collections;
    if (documentPath) {
      const docRef = db.doc(documentPath);
      collections = await docRef.listCollections();
    } else {
      collections = await db.listCollections();
    }
    
    // Sort collections by name
    collections.sort((a, b) => a.id.localeCompare(b.id));
    
    // Find start index
    const startIndex = pageToken ? collections.findIndex(c => c.id === pageToken) + 1 : 0;
    
    // Apply limit
    const paginatedCollections = collections.slice(startIndex, startIndex + limit);
    
    const projectId = getProjectId();
    const collectionData = paginatedCollections.map((collection) => {
      const collectionUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${documentPath}/${collection.id}`;
      return { name: collection.id, url: collectionUrl };
    });
    
    return { 
      content: [{
        type: 'text', 
        text: JSON.stringify({
          collections: collectionData,
          nextPageToken: collections.length > startIndex + limit ? 
            paginatedCollections[paginatedCollections.length - 1].id : null,
          hasMore: collections.length > startIndex + limit
        })
      }]
    };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error listing collections: ${(error as Error).message}` }], isError: true };
  }
}

function convertTimestampsToISO(data: any) {
  for (const key in data) {
    if (data[key] instanceof Timestamp) {
      data[key] = data[key].toDate().toISOString();
    }
  }
  return data;
}


export async function listDocuments(collection: string, filters: Array<{ field: string, operator: FirebaseFirestore.WhereFilterOp, value: any }> = [], limit: number = 20, pageToken?: string) {
  const projectId = getProjectId();
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    const collectionRef = db.collection(collection);
    let filteredQuery: Query = collectionRef;
    for (const filter of filters) {
      let filterValue = filter.value;
      if (typeof filterValue === 'string' && !isNaN(Date.parse(filterValue))) {
        filterValue = Timestamp.fromDate(new Date(filterValue));
      }
      filteredQuery = filteredQuery.where(filter.field, filter.operator, filterValue);
    }
    
    // Apply pageToken if provided
    if (pageToken) {
      const startAfterDoc = await collectionRef.doc(pageToken).get();
      filteredQuery = filteredQuery.startAfter(startAfterDoc);
    }

    // Get total count of documents matching the filter
    const countSnapshot = await filteredQuery.get();
    const totalCount = countSnapshot.size;

    // Get the first 'limit' documents
    const limitedQuery = filteredQuery.limit(limit);
    const snapshot = await limitedQuery.get();

    if (snapshot.empty) {
      return { content: [{ type: 'text', text: 'No matching documents found' }], isError: true };
    }
    
    const documents = snapshot.docs.map((doc: any) => {
      const data = doc.data();
      convertTimestampsToISO(data);
      const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${doc.id}`;
      return { id: doc.id, url: consoleUrl, document: data };
    });
    
    return { 
      content: [{
        type: 'text', 
        text: JSON.stringify({
          totalCount,
          documents,
          pageToken: documents.length > 0 ? documents[documents.length - 1].id : null,
          hasMore: totalCount > limit
        })
      }]
    };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error listing documents: ${(error as Error).message}` }], isError: true };
  }
}

export async function addDocument(collection: string, data: any) {
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    const docRef = await db.collection(collection).add(data);
    const projectId = getProjectId();
    convertTimestampsToISO(data);
    const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${docRef.id}`;
    return { content: [{ type: 'text', text: JSON.stringify({ id: docRef.id, url: consoleUrl, document: data }) }] };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error adding document: ${(error as Error).message}` }], isError: true };
  }
}

export async function getDocument(collection: string, id: string) {
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    const doc = await db.collection(collection).doc(id).get();
    if (!doc.exists) {
      return { content: [{ type: 'text', text: 'Document not found' }], isError: true };
    }
    const projectId = getProjectId();
    const data = doc.data();
    convertTimestampsToISO(data);
    const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${id}`;
    return { content: [{ type: 'text', text: JSON.stringify({ id, url: consoleUrl, document: data }) }] };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error getting document: ${(error as Error).message}` }], isError: true };
  }
}

export async function updateDocument(collection: string, id: string, data: any) {
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    await db.collection(collection).doc(id).update(data);
    const projectId = getProjectId();
    convertTimestampsToISO(data);
    const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${id}`;
    return { content: [{ type: 'text', text: JSON.stringify({ id, url: consoleUrl, document: data }) }] };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error updating document: ${(error as Error).message}` }], isError: true };
  }
}

export async function deleteDocument(collection: string, id: string) {
  try {
    if (!db) {
      return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
    }
    
    await db.collection(collection).doc(id).delete();
    return { content: [{ type: 'text', text: 'Document deleted successfully' }] };
  } catch (error) {
    return { content: [{ type: 'text', text: `Error deleting document: ${(error as Error).message}` }], isError: true };
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { addDocument, getDocument, updateDocument, deleteDocument, listDocuments, list_collections } from './lib/firebase/firestoreClient';
import { db } from './lib/firebase/firebaseConfig';
import { listDirectoryFiles, getFileInfo } from './lib/firebase/storageClient';
import { getUserByIdOrEmail } from './lib/firebase/authClient';

class FirebaseMcpServer {
  private server: Server;

  constructor() {
    this.server = new Server(
      {
        name: 'firebase-mcp-server',
        version: '0.9.0'
      },
      {
        capabilities: {
          tools: {}
        }
      }
    );

    this.setupToolHandlers();

    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'firestore_add_document',
          description: 'Add a document to a Firestore collection',
          inputSchema: {
            type: 'object',
            properties: {
              collection: {
                type: 'string',
                description: 'Collection name'
              },
              data: {
                type: 'object',
                description: 'Document data'
              }
            },
            required: ['collection', 'data']
          }
        },
        {
          name: 'firestore_list_collections',
          description: 'List collections in Firestore. If documentPath is provided, returns subcollections under that document; otherwise returns root collections.',
          inputSchema: {
            type: 'object',
            properties: {
            documentPath: {
              type: 'string',
              description: 'Optional parent document path'
            },
            limit: {
              type: 'number',
              description: 'Number of collections to return',
              default: 20
            },
            pageToken: {
              type: 'string',
              description: 'Token for pagination to get the next page of results'
            }
            },
            required: []
          }
        },
        {
          name: 'firestore_list_documents',
          description: 'List documents from a Firestore collection with optional filtering',
          inputSchema: {
            type: 'object',
            properties: {
              collection: {
                type: 'string',
                description: 'Collection name'
              },
              filters: {
                type: 'array',
                description: 'Array of filter conditions',
                items: {
                  type: 'object',
                  properties: {
                    field: {
                      type: 'string',
                      description: 'Field name to filter'
                    },
                    operator: {
                      type: 'string',
                      description: 'Comparison operator'
                    },
                    value: {
                      type: 'any',
                      description: 'Value to compare against (use ISO format for dates)'
                    }
                  },
                  required: ['field', 'operator', 'value']
                }
              },
            limit: {
              type: 'number',
              description: 'Number of documents to return',
              default: 20
            },
            pageToken: {
              type: 'string',
              description: 'Token for pagination to get the next page of results'
            }
            },
            required: ['collection']
          }
        },
        {
          name: 'firestore_get_document',
          description: 'Get a document from a Firestore collection',
          inputSchema: {
            type: 'object',
            properties: {
              collection: {
                type: 'string',
                description: 'Collection name'
              },
              id: {
                type: 'string',
                description: 'Document ID'
              }
            },
            required: ['collection', 'id']
          }
        },
        {
          name: 'firestore_update_document',
          description: 'Update a document in a Firestore collection',
          inputSchema: {
            type: 'object',
            properties: {
              collection: {
                type: 'string',
                description: 'Collection name'
              },
              id: {
                type: 'string',
                description: 'Document ID'
              },
              data: {
                type: 'object',
                description: 'Updated document data'
              }
            },
            required: ['collection', 'id', 'data']
          }
        },
        {
          name: 'firestore_delete_document',
          description: 'Delete a document from a Firestore collection',
          inputSchema: {
            type: 'object',
            properties: {
              collection: {
                type: 'string',
                description: 'Collection name'
              },
              id: {
                type: 'string',
                description: 'Document ID'
              }
            },
            required: ['collection', 'id']
          }
        },
        {
          name: "auth_get_user",
          description: "Get a user by ID or email from Firebase Authentication",
          inputSchema: {
            type: "object",
            properties: {
              identifier: {
                type: "string",
                description: "User ID or email address"
              }
            },
            required: ["identifier"]
          }
        },
        {
          "name": "storage_list_files",
          "description": "List files in a given path in Firebase Storage",
          "inputSchema": {
            "type": "object",
            "properties": {
              "directoryPath": {
                "type": "string",
                "description": "The optional path to list files from. If not provided, the root is used."
              }
            },
            "required": []
          }
        },
        {
          "name": "storage_get_file_info",
          "description": "Get file information including metadata and download URL",
          "inputSchema": {
            "type": "object",
            "properties": {
              "filePath": {
                "type": "string",
                "description": "The path of the file to get information for"
              }
            },
            "required": ["filePath"]
          }
        }
        ]
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args = {} } = request.params;
      switch (name) {
        case 'firestore_add_document':
          return addDocument(args.collection as string, args.data as object);
        case 'firestore_list_documents':
          return listDocuments(
            args.collection as string,
            args.filters as Array<{ field: string, operator: FirebaseFirestore.WhereFilterOp, value: any }>,
            args.limit as number,
            args.pageToken as string | undefined
          );
        case 'firestore_get_document':
          return getDocument(args.collection as string, args.id as string);
        case 'firestore_update_document':
          return updateDocument(args.collection as string, args.id as string, args.data as object);
        case 'firestore_delete_document':
          return deleteDocument(args.collection as string, args.id as string);
        case 'firestore_list_collections':
          return list_collections(
            args.documentPath as string | undefined,
            args.limit as number | undefined,
            args.pageToken as string | undefined
          );
        case 'auth_get_user':
          return getUserByIdOrEmail(args.identifier as string);
        case 'storage_list_files':
          return listDirectoryFiles(
            args.directoryPath as string | undefined,
            args.pageSize as number | undefined,
            args.pageToken as string | undefined
          );
        case 'storage_get_file_info':
          return getFileInfo(args.filePath as string);
        default:
          throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
      }
    });
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Firebase MCP server running on stdio');
  }
}

const server = new FirebaseMcpServer();
server.run().catch(console.error);

```