firestore-mcp-kit
v0.1.0
Published
Build secure, typed MCP tools backed by Firestore.
Maintainers
Readme
firestore-mcp-kit
Build secure, typed MCP tools backed by Firestore.
Node 20+. ESM-first. Early API.
firestore-mcp-kit is a minimal TypeScript library for defining explicit MCP tools in userland, validating them with Zod, and wiring them to document-centric Firestore operations.
It stays intentionally small:
- Firestore is storage, not the public contract
- schemas and policies live in app code
- writes should be narrow and explicit
- transports should stay thin
Tutorial
Install the package:
npm install firestore-mcp-kit zodBuild a minimal notes MCP server end-to-end from the example in examples/notes/src/index.ts.
The flow is:
- define a resource schema with Zod
- define explicit tool input/output schemas
- create a Firestore resource path function
- implement tools with
defineTool(...) - run them over stdio or HTTP
Minimal shape
const NoteSchema = z.object({
id: z.string(),
title: z.string(),
body: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
})
const notes = defineNotesTools(resource)Getting started
import { startHttpServer } from 'firestore-mcp-kit'
import {
createNotesResource,
type NotesContext,
defineNotesTools,
} from './examples/notes/src/index.js'
import { createMemoryFirestore } from './examples/notes/src/memory-firestore.js'
const { firestore } = createMemoryFirestore()
const tools = defineNotesTools(createNotesResource(firestore))
await startHttpServer<NotesContext>({
name: 'notes-example',
version: '0.1.0',
port: 8000,
tools,
getContext: async () => ({ actorId: 'local-user', canDelete: true }),
})If you are working in this repository, install dependencies first:
npm installRun the example:
npm run dev:stdio
npm run dev:httpUse a real Firestore project with a local service-account file:
export GOOGLE_APPLICATION_CREDENTIALS=./credentials.json
npm run dev:http:firestoreKeep credentials out of git. The repo ignores common credential file names by default.
Status: early and intentionally small. The API is designed to stay narrow, explicit, and example-driven.
HTTP default endpoint:
http://localhost:8000/mcp- health check:
http://localhost:8000/health
How-to guides
Add a Firestore-backed resource
- Define a resource schema in userland.
- Define explicit input/output schemas per tool.
- Create a
FirestoreResource:
const resource = createFirestoreResource(firestore, (id) => `notes/${id}`)- Implement tools with
defineTool(...). - Execute them directly or expose them through a transport.
Allow updates to only selected fields
Use createPatchSchema(...) and pickPatchedFields(...).
const NotePatchSchema = createPatchSchema(['title', 'body']).extend({
title: z.string().min(1).optional(),
body: z.string().optional(),
})This keeps update behavior explicit and avoids broad arbitrary patching.
Expose tools over stdio
await startStdioServer({
name: 'notes-example',
version: '0.1.0',
tools,
getContext: async () => ({ actorId: 'local-user', canDelete: true }),
})Expose tools over HTTP
await startHttpServer({
name: 'notes-example',
version: '0.1.0',
port: 8000,
tools,
getContext: async () => ({ actorId: 'local-user', canDelete: true }),
})Reference
Core tool API
defineTool(...)executeTool(...)createMcpServer(...)
Transport helpers
startStdioServer(...)startHttpServer(...)
Firestore helpers
getDocument(...)setDocument(...)updateDocument(...)deleteDocument(...)
Patch helpers
createPatchSchema(...)pickPatchedFields(...)
Errors
FirestoreMcpErrorAuthorizationErrorValidationErrorNotFoundError
Firestore interfaces
FirestoreClientFirestoreDocumentRef<TDocument>FirestoreDocumentSnapshot<TDocument>FirestoreResource
Explanation
These notes explain the design choices behind the library.
Why not expose raw Firestore directly?
Because MCP tools should expose narrow, typed operations instead of arbitrary database access.
Why do schemas live in userland?
Because applications own their resource shapes, policy checks, and dangerous-field rules.
Why are updates constrained?
Because unrestricted patching is too broad for the default path. Safe write behavior should feel deliberate.
Why keep transports thin?
Because the same tool definitions should work over stdio or HTTP without changing domain logic.
When should I use this instead of Firebase's MCP server?
Use this library when you want Firestore behind a narrow app contract instead of exposing broader platform capabilities directly. It is a better fit when you want tool names like notes.create or tickets.assign, app-owned Zod schemas, explicit authorization hooks, and constrained writes. Firebase's MCP server is a better fit when you want a more complete Firebase-integrated server with less userland code and are comfortable with a more platform-shaped surface area.
