Skip to main content
Glama
subcollections.ts10.1 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { FirestoreClient } from "../firestore/client.js"; import { PermissionManager } from "../permissions/manager.js"; export function registerSubcollectionTools( server: McpServer, firestoreClient: FirestoreClient, permissionManager: PermissionManager ) { // List subcollections tool server.tool( "firestore-list-subcollections", "List subcollections of a document", { documentPath: z.string().describe("Full path to the document (e.g., 'users/userId1')") }, async ({ documentPath }) => { try { const collections = await firestoreClient.getSubcollections(documentPath); return { content: [{ type: "text", text: JSON.stringify(collections, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error listing subcollections of ${documentPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Get collection by path tool server.tool( "firestore-get-collection-by-path", "Get documents from a collection using full path (supports subcollections)", { collectionPath: z.string().describe("Full path to the collection (e.g., 'users/userId1/orders')") }, async ({ collectionPath }) => { // Extract collection name for permission check const pathParts = collectionPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'read')) { return { isError: true, content: [{ type: "text", text: `Access denied to collection: ${collectionPath}` }] }; } try { const documents = await firestoreClient.getCollectionByPath(collectionPath); return { content: [{ type: "text", text: JSON.stringify(documents, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting collection ${collectionPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Get document by path tool server.tool( "firestore-get-document-by-path", "Get a document using full path (supports subcollections)", { documentPath: z.string().describe("Full path to the document (e.g., 'users/userId1/orders/orderId1')") }, async ({ documentPath }) => { // Extract collection name for permission check const pathParts = documentPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'read')) { return { isError: true, content: [{ type: "text", text: `Access denied to document: ${documentPath}` }] }; } try { const document = await firestoreClient.getDocumentByPath(documentPath); if (!document) { return { isError: true, content: [{ type: "text", text: `Document not found: ${documentPath}` }] }; } return { content: [{ type: "text", text: JSON.stringify(document, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting document ${documentPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Create document by path tool server.tool( "firestore-create-document-by-path", "Create a document in a collection using full path (supports subcollections)", { collectionPath: z.string().describe("Full path to the collection (e.g., 'users/userId1/orders')"), data: z.record(z.any()).describe("Document data to create"), documentId: z.string().optional().describe("Optional document ID. If not provided, one will be generated") }, async ({ collectionPath, data, documentId }) => { // Extract collection name for permission check const pathParts = collectionPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'write')) { return { isError: true, content: [{ type: "text", text: `Access denied to create document in: ${collectionPath}` }] }; } try { const document = await firestoreClient.createDocumentByPath(collectionPath, data, documentId); return { content: [{ type: "text", text: JSON.stringify(document, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error creating document in ${collectionPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Update document by path tool server.tool( "firestore-update-document-by-path", "Update a document using full path (supports subcollections)", { documentPath: z.string().describe("Full path to the document (e.g., 'users/userId1/orders/orderId1')"), data: z.record(z.any()).describe("Document data to update") }, async ({ documentPath, data }) => { // Extract collection name for permission check const pathParts = documentPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'write')) { return { isError: true, content: [{ type: "text", text: `Access denied to update document: ${documentPath}` }] }; } try { const document = await firestoreClient.updateDocumentByPath(documentPath, data); return { content: [{ type: "text", text: JSON.stringify(document, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error updating document ${documentPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Delete document by path tool server.tool( "firestore-delete-document-by-path", "Delete a document using full path (supports subcollections)", { documentPath: z.string().describe("Full path to the document (e.g., 'users/userId1/orders/orderId1')") }, async ({ documentPath }) => { // Extract collection name for permission check const pathParts = documentPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'delete')) { return { isError: true, content: [{ type: "text", text: `Access denied to delete document: ${documentPath}` }] }; } try { const result = await firestoreClient.deleteDocumentByPath(documentPath); return { content: [{ type: "text", text: `Successfully deleted document ${documentPath}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error deleting document ${documentPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Query collection by path tool server.tool( "firestore-query-collection-by-path", "Query documents in a collection using full path (supports subcollections)", { collectionPath: z.string().describe("Full path to the collection (e.g., 'users/userId1/orders')"), filters: z.array(z.object({ field: z.string().describe("Field path to filter on"), operator: z.enum(["==", "!=", ">", ">=", "<", "<=", "array-contains", "in", "array-contains-any", "not-in"]) .describe("Operator for comparison"), value: z.any().optional().describe("Value to compare against") })).describe("Array of filter conditions"), limit: z.number().optional().describe("Maximum number of results to return"), orderBy: z.object({ field: z.string().describe("Field to order by"), direction: z.enum(["asc", "desc"]).describe("Sort direction") }).optional().describe("Order specification") }, async ({ collectionPath, filters, limit, orderBy }) => { // Extract collection name for permission check const pathParts = collectionPath.split('/'); const rootCollection = pathParts[0]; if (!permissionManager.hasPermission(rootCollection, 'query')) { return { isError: true, content: [{ type: "text", text: `Access denied to query collection: ${collectionPath}` }] }; } try { const documents = await firestoreClient.queryCollectionByPath( collectionPath, filters, limit, orderBy ); return { content: [{ type: "text", text: JSON.stringify(documents, null, 2) }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error querying collection ${collectionPath}: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/devlimelabs/firestore-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server