npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

firestore-mcp-kit

v0.1.0

Published

Build secure, typed MCP tools backed by Firestore.

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 zod

Build a minimal notes MCP server end-to-end from the example in examples/notes/src/index.ts.

The flow is:

  1. define a resource schema with Zod
  2. define explicit tool input/output schemas
  3. create a Firestore resource path function
  4. implement tools with defineTool(...)
  5. 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 install

Run the example:

npm run dev:stdio
npm run dev:http

Use a real Firestore project with a local service-account file:

export GOOGLE_APPLICATION_CREDENTIALS=./credentials.json
npm run dev:http:firestore

Keep 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

  1. Define a resource schema in userland.
  2. Define explicit input/output schemas per tool.
  3. Create a FirestoreResource:
const resource = createFirestoreResource(firestore, (id) => `notes/${id}`)
  1. Implement tools with defineTool(...).
  2. 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

  • FirestoreMcpError
  • AuthorizationError
  • ValidationError
  • NotFoundError

Firestore interfaces

  • FirestoreClient
  • FirestoreDocumentRef<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.