# 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:
--------------------------------------------------------------------------------
```
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 |
11 | # Compiled binary addons (https://nodejs.org/api/addons.html)
12 | build/
13 | dist/
14 |
15 | # Dependency directories
16 | node_modules/
17 | jspm_packages/
18 |
19 | # Snowpack dependency directory (https://snowpack.dev/)
20 | web_modules/
21 |
22 | # TypeScript cache
23 | *.tsbuildinfo
24 |
25 | # Optional npm cache directory
26 | .npm
27 |
28 | # Optional eslint cache
29 | .eslintcache
30 |
31 | # Optional stylelint cache
32 | .stylelintcache
33 |
34 |
35 | # Optional REPL history
36 | .node_repl_history
37 |
38 | # dotenv environment variable files
39 | .env
40 | .env.local
41 |
42 | # parcel-bundler cache (https://parceljs.org/)
43 | .cache
44 | .parcel-cache
45 |
46 | # Next.js build output
47 | .next
48 | out
49 |
50 | # Nuxt.js build / generate output
51 | .nuxt
52 | dist
53 |
54 | # Stores VSCode versions used for testing VSCode extensions
55 | .vscode-test
56 |
57 | # yarn v2
58 | .yarn/cache
59 | .yarn/unplugged
60 | .yarn/build-state.yml
61 | .yarn/install-state.gz
62 | .pnp.*
63 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Firebase MCP Server
2 |
3 | ## Overview
4 | 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.
5 |
6 | ## Setup
7 | 1. Clone and build the project:
8 | ```bash
9 | git clone https://github.com/gemini-dk/mcp-server-firebase
10 | cd mcp-server-firebase
11 | npm install
12 | npm run build
13 | ```
14 |
15 | 2. Get Firebase service account key:
16 | - Go to Firebase Console > Project Settings > Service accounts
17 | - Click "Generate new private key"
18 | - Save the JSON file to your project directory
19 |
20 | 3. Configure `mcp_settings.json`:
21 | ```json
22 | {
23 | "firebase-mcp": {
24 | "command": "node",
25 | "args": [
26 | "/path/to/mcp-server-firebase/dist/index.js"
27 | ],
28 | "env": {
29 | "SERVICE_ACCOUNT_KEY_PATH": "/path/to/serviceAccountKey.json"
30 | }
31 | }
32 | }
33 | ```
34 | Replace `/path/to/mcp-server-firebase` with the actual path where you cloned the repository.
35 | Replace `/path/to/serviceAccountKey.json` with the path to your service account key file.
36 |
37 | ## Available APIs
38 | ### Authentication
39 | - Get user by ID or email
40 |
41 | ### Firestore
42 | - Add/update/delete documents
43 | - List collections/documents
44 |
45 | ### Storage
46 | - List files in a directory
47 | - Get File metadata and Download URL
48 |
49 | ## License
50 | - MIT License
51 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "outDir": "./dist",
6 | "rootDir": "src",
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "forceConsistentCasingInFileNames": true
11 | },
12 | "include": ["src/**/*.ts"],
13 | "exclude": ["node_modules", "dist"]
14 | }
15 |
```
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
```javascript
1 | process.env.SERVICE_ACCOUNT_KEY_PATH = path.resolve(process.cwd(), 'serviceAccountKey.json');
2 |
3 | global.console = {
4 | ...console,
5 | log: jest.fn((message) => process.stdout.write(message + '\\n')),
6 | info: jest.fn((message) => process.stdout.write(message + '\\n')),
7 | warn: jest.fn((message) => process.stdout.write(message + '\\n')),
8 | error: jest.fn((message) => process.stderr.write(message + '\\n')),
9 | };
10 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/storageClient.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { listDirectoryFiles, getFileInfo } from '../storageClient';
2 |
3 | const testPath = '';
4 | const testFilePath = 'testpath';
5 |
6 | describe('listFiles', () => {
7 | it('should return a list of files in the specified path', async () => {
8 | const result = await listDirectoryFiles(testPath);
9 | console.log(result);
10 | });
11 | });
12 |
13 | describe('getFileInfo', () => {
14 | it('should return file metadata and download URL for the specified file', async () => {
15 | const result = await getFileInfo(testFilePath);
16 | console.log(result);
17 | });
18 | });
19 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/authClient.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { getUserByIdOrEmail } from '../authClient';
2 |
3 | const testEmail = '[email protected]';
4 | const testId = 'testid';
5 |
6 | describe('getUserByIdOrEmail', () => {
7 | it('should return user data when a valid UID is provided', async () => {
8 | const result = await getUserByIdOrEmail(testId);
9 | expect(result.content[0].text).toContain(testId);
10 | expect(result.content[0].text).toContain(testEmail);
11 | console.log(result.content[0].text);
12 | });
13 |
14 | it('should return user data when a valid email is provided', async () => {
15 | const result = await getUserByIdOrEmail(testEmail);
16 | expect(result.content[0].text).toContain(testId);
17 | expect(result.content[0].text).toContain(testEmail);
18 | console.log(result.content[0].text);
19 | });
20 | });
21 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "firebase-mcp",
3 | "version": "1.0.0",
4 | "description": "MCP server for Firestore operations",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "tsc",
8 | "test": "jest",
9 | "start": "node dist/index.js",
10 | "dev": "tsc && node dist/index.js"
11 | },
12 | "jest": {
13 | "preset": "ts-jest",
14 | "testEnvironment": "node",
15 | "testMatch": [
16 | "**/__tests__/**/*.ts?(x)",
17 | "**/?(*.)+(spec|test).ts?(x)"
18 | ]
19 | },
20 | "directories": {
21 | "test": "test"
22 | },
23 | "dependencies": {
24 | "@modelcontextprotocol/sdk": "^1.5.0",
25 | "express": "^4.18.2",
26 | "firebase-admin": "^11.11.1"
27 | },
28 | "devDependencies": {
29 | "@types/express": "^5.0.0",
30 | "@types/jest": "^27.0.0",
31 | "@types/node": "^22.13.4",
32 | "jest": "^29.0.0",
33 | "ts-jest": "^29.2.5",
34 | "typescript": "^5.7.3"
35 | }
36 | }
37 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/firebaseConfig.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as admin from 'firebase-admin';
2 | import fs from 'fs';
3 |
4 | function initializeFirebase() {
5 | const serviceAccountPath = process.env.SERVICE_ACCOUNT_KEY_PATH;
6 |
7 | if (!serviceAccountPath) {
8 | console.error('Firebase initialization skipped: SERVICE_ACCOUNT_KEY_PATH is not set');
9 | return null;
10 | }
11 |
12 | try {
13 | const serviceAccount = require(serviceAccountPath);
14 | const projectId = getProjectId(serviceAccountPath);
15 |
16 | admin.initializeApp({
17 | credential: admin.credential.cert(serviceAccount),
18 | storageBucket: `${projectId}.appspot.com`
19 | });
20 |
21 | return admin;
22 | } catch (error) {
23 | console.error('Firebase initialization failed:', error);
24 | return null;
25 | }
26 | }
27 |
28 | function getProjectId(serviceAccountPath?: string): string {
29 | if (!serviceAccountPath) {
30 | serviceAccountPath = process.env.SERVICE_ACCOUNT_KEY_PATH;
31 | if (!serviceAccountPath) {
32 | console.error('Cannot get project ID: SERVICE_ACCOUNT_KEY_PATH is not set');
33 | return '';
34 | }
35 | }
36 |
37 | try {
38 | const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountPath, 'utf8'));
39 | return serviceAccount.project_id;
40 | } catch (error) {
41 | console.error('Failed to get project ID:', error);
42 | return '';
43 | }
44 | }
45 |
46 | const adminApp = initializeFirebase();
47 | const db = adminApp ? admin.firestore() : null;
48 |
49 | export { db, admin, getProjectId };
50 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/__tests__/firestoreClient.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { listDocuments, addDocument, getDocument, updateDocument, deleteDocument, list_collections } from '../firestoreClient';
2 |
3 | describe('Firebase Client', () => {
4 | const collectionName = '';
5 | const documentId = 'testid';
6 |
7 |
8 | // it('should add a document', async () => {
9 | // const result = await addDocument(collectionName, documentData);
10 | // console.log(result.content[0].text);
11 | // });
12 |
13 | it('should list collections', async () => {
14 | const result = await list_collections();
15 | console.log(result.content[0].text);
16 | });
17 |
18 | it('should list documents', async () => {
19 | const date = new Date('2025-02-20T00:00:00.000Z');
20 | const jstDate = new Date(date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' }));
21 | const result = await listDocuments(collectionName, [{ field: 'lastAccessTime', operator: '>=', value: jstDate.toISOString() }]);
22 | const jsonData = JSON.parse(result.content[0].text);
23 | const logobj:{totalCount:number,documents:{id:string,url:string}[]} = {totalCount:0,documents:[]};
24 | logobj.totalCount = jsonData.totalCount;
25 | for(const doc of jsonData.documents) {
26 | logobj.documents.push({id:doc.id,url:doc.url});
27 | }
28 | console.log(logobj);
29 | });
30 |
31 | it('should get a document', async () => {
32 | const result = await getDocument(collectionName, documentId);
33 | console.log(result.content[0].text);
34 | });
35 |
36 | // it('should update a document', async () => {
37 | // const updatedData = { field1: 'updatedValue' };
38 | // const result = await updateDocument(collectionName, documentId, updatedData);
39 | // console.log(result.content[0].text);
40 | // });
41 | //
42 | // it('should delete a document', async () => {
43 | // const result = await deleteDocument(collectionName, documentId);
44 | // console.log(result.content[0].text);
45 | // });
46 | });
47 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/storageClient.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { admin, getProjectId } from './firebaseConfig';
2 |
3 | //const storage = admin.storage().bucket();
4 |
5 | /**
6 | * List files in a given path in Firebase Storage.
7 | * @param {string} [path] - The optional path to list files from. If not provided, the root is used.
8 | * @returns {Promise<{ content: { type: string , text: string }[] }>} - A promise that resolves to an object containing file names.
9 | */
10 | export async function listDirectoryFiles(path?: string, pageSize: number = 10, pageToken?: string): Promise<{ content: { type: string , text: string }[] }> {
11 | const prefix = path ? (path === '' ? '' : (path.endsWith('/') ? path : `${path}/`)) : '';
12 | const [files, , apiResponse] = await admin.storage().bucket().getFiles({
13 | prefix,
14 | delimiter: '/',
15 | maxResults: pageSize,
16 | pageToken
17 | });
18 | const nextPageToken = apiResponse.nextPageToken as string || undefined;
19 |
20 | const fileNames = await Promise.all(files.map(async (file) => {
21 | const [signedUrl] = await file.getSignedUrl({
22 | action: 'read',
23 | expires: Date.now() + 1000 * 60 * 60 // 1 hour
24 | });
25 | return { type: "file", name: file.name, downloadURL: signedUrl };
26 | }));
27 |
28 | const projectId = getProjectId();
29 | const bucketName = admin.storage().bucket().name;
30 | const directoryNames = (apiResponse.prefixes || []).map((prefix:string) => {
31 | //const encodedPrefix = encodeURIComponent(prefix).replace(/'/g, '%27').replace(/%20/g, '+');
32 | const tmpPrefix = prefix.replace(/\/$/, '');
33 | const encodedPrefix = `~2F${tmpPrefix.replace(/\//g, '~2F')}`;
34 | const consoleUrl = `https://console.firebase.google.com/project/${projectId}/storage/${bucketName}/files/${encodedPrefix}`;
35 | return { type: "directory", name: prefix, url: consoleUrl };
36 | });
37 |
38 | const result = {
39 | nextPageToken: nextPageToken,
40 | files: [...fileNames, ...directoryNames],
41 | hasMore: nextPageToken !== undefined
42 | };
43 | return {
44 | content:[
45 | {
46 | type: "text",
47 | text: JSON.stringify(result,null,2)
48 | }
49 | ]
50 | }
51 | }
52 |
53 | /**
54 | * Get file information including metadata and download URL.
55 | * @param {string} filePath - The path of the file to get information for.
56 | * @returns {Promise<{ content: object }>} - A promise that resolves to an object containing file metadata and download URL.
57 | */
58 | export async function getFileInfo(filePath: string): Promise<{ content: { type: string , text: string }[] }> {
59 | const file = admin.storage().bucket().file(filePath);
60 | const [metadata] = await file.getMetadata();
61 | const [url] = await file.getSignedUrl({
62 | action: 'read',
63 | expires: Date.now() + 1000 * 60 * 60 // 1 hour
64 | });
65 | const result = { metadata, downloadUrl:url };
66 | return {
67 | content: [
68 | { type:'text', text: JSON.stringify(result,null,2) }
69 | ]
70 | };
71 | }
72 |
```
--------------------------------------------------------------------------------
/src/lib/firebase/firestoreClient.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Query, Timestamp } from 'firebase-admin/firestore';
2 | import {db, getProjectId} from './firebaseConfig';
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | export async function list_collections(documentPath?: string, limit: number = 20, pageToken?: string) {
7 | try {
8 | if (!db) {
9 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
10 | }
11 |
12 | let collections;
13 | if (documentPath) {
14 | const docRef = db.doc(documentPath);
15 | collections = await docRef.listCollections();
16 | } else {
17 | collections = await db.listCollections();
18 | }
19 |
20 | // Sort collections by name
21 | collections.sort((a, b) => a.id.localeCompare(b.id));
22 |
23 | // Find start index
24 | const startIndex = pageToken ? collections.findIndex(c => c.id === pageToken) + 1 : 0;
25 |
26 | // Apply limit
27 | const paginatedCollections = collections.slice(startIndex, startIndex + limit);
28 |
29 | const projectId = getProjectId();
30 | const collectionData = paginatedCollections.map((collection) => {
31 | const collectionUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${documentPath}/${collection.id}`;
32 | return { name: collection.id, url: collectionUrl };
33 | });
34 |
35 | return {
36 | content: [{
37 | type: 'text',
38 | text: JSON.stringify({
39 | collections: collectionData,
40 | nextPageToken: collections.length > startIndex + limit ?
41 | paginatedCollections[paginatedCollections.length - 1].id : null,
42 | hasMore: collections.length > startIndex + limit
43 | })
44 | }]
45 | };
46 | } catch (error) {
47 | return { content: [{ type: 'text', text: `Error listing collections: ${(error as Error).message}` }], isError: true };
48 | }
49 | }
50 |
51 | function convertTimestampsToISO(data: any) {
52 | for (const key in data) {
53 | if (data[key] instanceof Timestamp) {
54 | data[key] = data[key].toDate().toISOString();
55 | }
56 | }
57 | return data;
58 | }
59 |
60 |
61 | export async function listDocuments(collection: string, filters: Array<{ field: string, operator: FirebaseFirestore.WhereFilterOp, value: any }> = [], limit: number = 20, pageToken?: string) {
62 | const projectId = getProjectId();
63 | try {
64 | if (!db) {
65 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
66 | }
67 |
68 | const collectionRef = db.collection(collection);
69 | let filteredQuery: Query = collectionRef;
70 | for (const filter of filters) {
71 | let filterValue = filter.value;
72 | if (typeof filterValue === 'string' && !isNaN(Date.parse(filterValue))) {
73 | filterValue = Timestamp.fromDate(new Date(filterValue));
74 | }
75 | filteredQuery = filteredQuery.where(filter.field, filter.operator, filterValue);
76 | }
77 |
78 | // Apply pageToken if provided
79 | if (pageToken) {
80 | const startAfterDoc = await collectionRef.doc(pageToken).get();
81 | filteredQuery = filteredQuery.startAfter(startAfterDoc);
82 | }
83 |
84 | // Get total count of documents matching the filter
85 | const countSnapshot = await filteredQuery.get();
86 | const totalCount = countSnapshot.size;
87 |
88 | // Get the first 'limit' documents
89 | const limitedQuery = filteredQuery.limit(limit);
90 | const snapshot = await limitedQuery.get();
91 |
92 | if (snapshot.empty) {
93 | return { content: [{ type: 'text', text: 'No matching documents found' }], isError: true };
94 | }
95 |
96 | const documents = snapshot.docs.map((doc: any) => {
97 | const data = doc.data();
98 | convertTimestampsToISO(data);
99 | const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${doc.id}`;
100 | return { id: doc.id, url: consoleUrl, document: data };
101 | });
102 |
103 | return {
104 | content: [{
105 | type: 'text',
106 | text: JSON.stringify({
107 | totalCount,
108 | documents,
109 | pageToken: documents.length > 0 ? documents[documents.length - 1].id : null,
110 | hasMore: totalCount > limit
111 | })
112 | }]
113 | };
114 | } catch (error) {
115 | return { content: [{ type: 'text', text: `Error listing documents: ${(error as Error).message}` }], isError: true };
116 | }
117 | }
118 |
119 | export async function addDocument(collection: string, data: any) {
120 | try {
121 | if (!db) {
122 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
123 | }
124 |
125 | const docRef = await db.collection(collection).add(data);
126 | const projectId = getProjectId();
127 | convertTimestampsToISO(data);
128 | const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${docRef.id}`;
129 | return { content: [{ type: 'text', text: JSON.stringify({ id: docRef.id, url: consoleUrl, document: data }) }] };
130 | } catch (error) {
131 | return { content: [{ type: 'text', text: `Error adding document: ${(error as Error).message}` }], isError: true };
132 | }
133 | }
134 |
135 | export async function getDocument(collection: string, id: string) {
136 | try {
137 | if (!db) {
138 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
139 | }
140 |
141 | const doc = await db.collection(collection).doc(id).get();
142 | if (!doc.exists) {
143 | return { content: [{ type: 'text', text: 'Document not found' }], isError: true };
144 | }
145 | const projectId = getProjectId();
146 | const data = doc.data();
147 | convertTimestampsToISO(data);
148 | const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${id}`;
149 | return { content: [{ type: 'text', text: JSON.stringify({ id, url: consoleUrl, document: data }) }] };
150 | } catch (error) {
151 | return { content: [{ type: 'text', text: `Error getting document: ${(error as Error).message}` }], isError: true };
152 | }
153 | }
154 |
155 | export async function updateDocument(collection: string, id: string, data: any) {
156 | try {
157 | if (!db) {
158 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
159 | }
160 |
161 | await db.collection(collection).doc(id).update(data);
162 | const projectId = getProjectId();
163 | convertTimestampsToISO(data);
164 | const consoleUrl = `https://console.firebase.google.com/project/${projectId}/firestore/data/${collection}/${id}`;
165 | return { content: [{ type: 'text', text: JSON.stringify({ id, url: consoleUrl, document: data }) }] };
166 | } catch (error) {
167 | return { content: [{ type: 'text', text: `Error updating document: ${(error as Error).message}` }], isError: true };
168 | }
169 | }
170 |
171 | export async function deleteDocument(collection: string, id: string) {
172 | try {
173 | if (!db) {
174 | return { content: [{ type: 'text', text: 'Firebase is not initialized. SERVICE_ACCOUNT_KEY_PATH environment variable is required.' }], isError: true };
175 | }
176 |
177 | await db.collection(collection).doc(id).delete();
178 | return { content: [{ type: 'text', text: 'Document deleted successfully' }] };
179 | } catch (error) {
180 | return { content: [{ type: 'text', text: `Error deleting document: ${(error as Error).message}` }], isError: true };
181 | }
182 | }
183 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3 | import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
4 | import { addDocument, getDocument, updateDocument, deleteDocument, listDocuments, list_collections } from './lib/firebase/firestoreClient';
5 | import { db } from './lib/firebase/firebaseConfig';
6 | import { listDirectoryFiles, getFileInfo } from './lib/firebase/storageClient';
7 | import { getUserByIdOrEmail } from './lib/firebase/authClient';
8 |
9 | class FirebaseMcpServer {
10 | private server: Server;
11 |
12 | constructor() {
13 | this.server = new Server(
14 | {
15 | name: 'firebase-mcp-server',
16 | version: '0.9.0'
17 | },
18 | {
19 | capabilities: {
20 | tools: {}
21 | }
22 | }
23 | );
24 |
25 | this.setupToolHandlers();
26 |
27 | this.server.onerror = (error) => console.error('[MCP Error]', error);
28 | process.on('SIGINT', async () => {
29 | await this.server.close();
30 | process.exit(0);
31 | });
32 | }
33 |
34 | private setupToolHandlers() {
35 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
36 | tools: [
37 | {
38 | name: 'firestore_add_document',
39 | description: 'Add a document to a Firestore collection',
40 | inputSchema: {
41 | type: 'object',
42 | properties: {
43 | collection: {
44 | type: 'string',
45 | description: 'Collection name'
46 | },
47 | data: {
48 | type: 'object',
49 | description: 'Document data'
50 | }
51 | },
52 | required: ['collection', 'data']
53 | }
54 | },
55 | {
56 | name: 'firestore_list_collections',
57 | description: 'List collections in Firestore. If documentPath is provided, returns subcollections under that document; otherwise returns root collections.',
58 | inputSchema: {
59 | type: 'object',
60 | properties: {
61 | documentPath: {
62 | type: 'string',
63 | description: 'Optional parent document path'
64 | },
65 | limit: {
66 | type: 'number',
67 | description: 'Number of collections to return',
68 | default: 20
69 | },
70 | pageToken: {
71 | type: 'string',
72 | description: 'Token for pagination to get the next page of results'
73 | }
74 | },
75 | required: []
76 | }
77 | },
78 | {
79 | name: 'firestore_list_documents',
80 | description: 'List documents from a Firestore collection with optional filtering',
81 | inputSchema: {
82 | type: 'object',
83 | properties: {
84 | collection: {
85 | type: 'string',
86 | description: 'Collection name'
87 | },
88 | filters: {
89 | type: 'array',
90 | description: 'Array of filter conditions',
91 | items: {
92 | type: 'object',
93 | properties: {
94 | field: {
95 | type: 'string',
96 | description: 'Field name to filter'
97 | },
98 | operator: {
99 | type: 'string',
100 | description: 'Comparison operator'
101 | },
102 | value: {
103 | type: 'any',
104 | description: 'Value to compare against (use ISO format for dates)'
105 | }
106 | },
107 | required: ['field', 'operator', 'value']
108 | }
109 | },
110 | limit: {
111 | type: 'number',
112 | description: 'Number of documents to return',
113 | default: 20
114 | },
115 | pageToken: {
116 | type: 'string',
117 | description: 'Token for pagination to get the next page of results'
118 | }
119 | },
120 | required: ['collection']
121 | }
122 | },
123 | {
124 | name: 'firestore_get_document',
125 | description: 'Get a document from a Firestore collection',
126 | inputSchema: {
127 | type: 'object',
128 | properties: {
129 | collection: {
130 | type: 'string',
131 | description: 'Collection name'
132 | },
133 | id: {
134 | type: 'string',
135 | description: 'Document ID'
136 | }
137 | },
138 | required: ['collection', 'id']
139 | }
140 | },
141 | {
142 | name: 'firestore_update_document',
143 | description: 'Update a document in a Firestore collection',
144 | inputSchema: {
145 | type: 'object',
146 | properties: {
147 | collection: {
148 | type: 'string',
149 | description: 'Collection name'
150 | },
151 | id: {
152 | type: 'string',
153 | description: 'Document ID'
154 | },
155 | data: {
156 | type: 'object',
157 | description: 'Updated document data'
158 | }
159 | },
160 | required: ['collection', 'id', 'data']
161 | }
162 | },
163 | {
164 | name: 'firestore_delete_document',
165 | description: 'Delete a document from a Firestore collection',
166 | inputSchema: {
167 | type: 'object',
168 | properties: {
169 | collection: {
170 | type: 'string',
171 | description: 'Collection name'
172 | },
173 | id: {
174 | type: 'string',
175 | description: 'Document ID'
176 | }
177 | },
178 | required: ['collection', 'id']
179 | }
180 | },
181 | {
182 | name: "auth_get_user",
183 | description: "Get a user by ID or email from Firebase Authentication",
184 | inputSchema: {
185 | type: "object",
186 | properties: {
187 | identifier: {
188 | type: "string",
189 | description: "User ID or email address"
190 | }
191 | },
192 | required: ["identifier"]
193 | }
194 | },
195 | {
196 | "name": "storage_list_files",
197 | "description": "List files in a given path in Firebase Storage",
198 | "inputSchema": {
199 | "type": "object",
200 | "properties": {
201 | "directoryPath": {
202 | "type": "string",
203 | "description": "The optional path to list files from. If not provided, the root is used."
204 | }
205 | },
206 | "required": []
207 | }
208 | },
209 | {
210 | "name": "storage_get_file_info",
211 | "description": "Get file information including metadata and download URL",
212 | "inputSchema": {
213 | "type": "object",
214 | "properties": {
215 | "filePath": {
216 | "type": "string",
217 | "description": "The path of the file to get information for"
218 | }
219 | },
220 | "required": ["filePath"]
221 | }
222 | }
223 | ]
224 | }));
225 |
226 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
227 | const { name, arguments: args = {} } = request.params;
228 | switch (name) {
229 | case 'firestore_add_document':
230 | return addDocument(args.collection as string, args.data as object);
231 | case 'firestore_list_documents':
232 | return listDocuments(
233 | args.collection as string,
234 | args.filters as Array<{ field: string, operator: FirebaseFirestore.WhereFilterOp, value: any }>,
235 | args.limit as number,
236 | args.pageToken as string | undefined
237 | );
238 | case 'firestore_get_document':
239 | return getDocument(args.collection as string, args.id as string);
240 | case 'firestore_update_document':
241 | return updateDocument(args.collection as string, args.id as string, args.data as object);
242 | case 'firestore_delete_document':
243 | return deleteDocument(args.collection as string, args.id as string);
244 | case 'firestore_list_collections':
245 | return list_collections(
246 | args.documentPath as string | undefined,
247 | args.limit as number | undefined,
248 | args.pageToken as string | undefined
249 | );
250 | case 'auth_get_user':
251 | return getUserByIdOrEmail(args.identifier as string);
252 | case 'storage_list_files':
253 | return listDirectoryFiles(
254 | args.directoryPath as string | undefined,
255 | args.pageSize as number | undefined,
256 | args.pageToken as string | undefined
257 | );
258 | case 'storage_get_file_info':
259 | return getFileInfo(args.filePath as string);
260 | default:
261 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
262 | }
263 | });
264 | }
265 |
266 | async run() {
267 | const transport = new StdioServerTransport();
268 | await this.server.connect(transport);
269 | console.error('Firebase MCP server running on stdio');
270 | }
271 | }
272 |
273 | const server = new FirebaseMcpServer();
274 | server.run().catch(console.error);
275 |
```