<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>The Firenook blog</title><description>How Firenook works, what it measures, and what comes next.</description><link>https://firenook.dev/blog</link><language>en</language><atom:link href="https://firenook.dev/blog.xml" rel="self" type="application/rss+xml"/><item><title>How Firenook writes 40× faster than the Firebase emulator</title><link>https://firenook.dev/blog/how-firenook-writes-40x-faster</link><guid isPermaLink="true">https://firenook.dev/blog/how-firenook-writes-40x-faster</guid><description>What a Firestore write costs in Firenook, read from the source: a journal write, a redb commit without a sync, a few index entries and a wake-up.</description><pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Under a real application’s traffic, Firenook acknowledged a Firestore
write in 5.1 ms at the median, where the official emulator took 208.2 ms.
This post explains that number from Firenook’s source: what a write does,
what it skips, what that costs in durability, and where the comparison stops
holding.&lt;/p&gt;
&lt;h2 id=&quot;what-the-40-measures&quot;&gt;What the 40× measures&lt;/h2&gt;
&lt;p&gt;The number comes from two hours of one application’s traffic against each
emulator in turn: two busy sessions writing, open listeners, a filtered query
every 15 seconds, a Firestore-triggered function and Storage uploads. The
client timed each of the 5,280 writes per emulator, from call to answer.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Median / 99th percentile&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Official&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Firenook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write, acknowledged&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;208.2 / 279.8 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5.1 / 50.6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listener update after a write&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;209 / 258 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;11 / 60 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It is not the cost of a write on an idle emulator. With each emulator alone,
writing a document and deleting it took 3.01 ms on the official emulator and
1.96 ms on Firenook at the median: 1.5×. The 40× is how the two behave under
load, so the real question is narrower than the title: why does a Firenook
write stay cheap while an application runs around it?&lt;/p&gt;
&lt;h2 id=&quot;what-a-write-does&quot;&gt;What a write does&lt;/h2&gt;
&lt;p&gt;A commit enters the disk store through &lt;code&gt;DiskStore::commit_observed&lt;/code&gt; in
&lt;code&gt;crates/core-store/src/disk.rs&lt;/code&gt;. It takes five steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Read what it touches.&lt;/strong&gt; The store reads the documents the commit writes
from redb, one key lookup each. It never reads the collection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan.&lt;/strong&gt; It applies each write to those documents in memory:
preconditions, transforms such as server timestamps and increments, and
the new revision. The result is a list of changes, each with the document
before and after.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Append to the journal.&lt;/strong&gt; The commit is framed (a magic number, its
length and a CRC32C) and appended to a journal file with a plain &lt;code&gt;write&lt;/code&gt;.
No &lt;code&gt;fsync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit to redb.&lt;/strong&gt; redb is an embedded key-value store written in Rust,
built on copy-on-write B-trees. One write transaction stores the document,
its entries in the collection, collection-group and ordered field
indexes, and the store’s revision. It commits with redb’s
&lt;code&gt;Durability::None&lt;/code&gt;, so it does not sync either.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Record and notify.&lt;/strong&gt; The change goes into an in-memory change log,
bounded at 4,096 changes or 64 MiB, and the commit’s observers hear about
it. Then the client gets its answer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The choice in step 4 is one function:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://firenook.dev/_astro/ec.dqibo.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://firenook.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF;--1:#7A4BD1&quot;&gt;redb_durability&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;(durability&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;DiskDurability&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;Durability&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; durability {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;DiskDurability&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;PerCommit&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;Durability&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;Immediate&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;DiskDurability&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;WriteBehind&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;Durability&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;const fn redb_durability(durability: DiskDurability) -&gt; Durability {    match durability {        DiskDurability::PerCommit =&gt; Durability::Immediate,        DiskDurability::WriteBehind { .. } =&gt; Durability::None,    }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The gRPC and WebChannel fronts, which the SDKs talk to, run disk commits one
at a time on a blocking thread, off the async workers that serve reads. None
of the five steps reads more than the documents being written, and none
depends on how many listeners are open. That is most of the answer.&lt;/p&gt;
&lt;h2 id=&quot;durability-write-behind-not-per-commit&quot;&gt;Durability: write-behind, not per commit&lt;/h2&gt;
&lt;p&gt;Skipping &lt;code&gt;fsync&lt;/code&gt; does not lose writes when the process dies. The journal
&lt;code&gt;write&lt;/code&gt; hands the bytes to the operating system, which keeps them, and the
next open replays every journal record newer than redb’s last durable
revision, stopping at a torn tail by its checksum. Once a second, a thread
called &lt;code&gt;firenook-disk-flusher&lt;/code&gt; syncs the journal, makes one empty durable redb
commit, which persists every earlier one, and truncates the journal; a clean
stop does the same. A killed process loses no acknowledged write. A kernel
crash or a power cut loses at most the last second.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--durability per-commit&lt;/code&gt; syncs on every commit instead. We measured the
difference on an Apple M2 Pro laptop with Firestore alone, in the shape of
the benchmark’s write check: 2,000 sequential writes through the Admin SDK,
each confirmed by a listener, then 4,000 writes from eight writers at once.
Two alternating rounds per mode, each on a fresh store:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Write-behind (default)&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Per commit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write, median&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0.22–0.24 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;14.7–14.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write, 99th percentile&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0.69–0.75 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;22.9–23.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listener update, median&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0.33–0.35 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;14.9–15.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eight writers&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;10,151–11,068 writes/s&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;66–68 writes/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Syncing every commit is right for data you cannot recreate. For emulator
data, a one-second window on a power cut is a better trade than 60 to 66
times the write latency. The official emulator writes its data only when it
exports, so a crash loses everything since the last export.&lt;/p&gt;
&lt;h2 id=&quot;indexes-on-the-write-path&quot;&gt;Indexes on the write path&lt;/h2&gt;
&lt;p&gt;Each write keeps three indexes. The collection and collection-group indexes
take one entry each. The ordered field index takes one for every top-level
field: its key is the collection and field, then the value encoded so that
its bytes sort in the query engine’s order, then the document’s id. Integers
and doubles share one numeric order, strings and bytes up to 64 bytes are
keyed exactly, and longer ones by a prefix and a 16-byte digest. Arrays,
maps and vectors are keyed by their type and whether they are empty, so an
order over a field still reaches every document holding it. On an update,
the commit reads the document’s current entries in the same transaction and
writes only the difference.&lt;/p&gt;
&lt;p&gt;That is a few small B-tree inserts per write. With Firestore alone on the
benchmark server, on fresh copies of the same store, a write took 0.62 to
0.67 ms at the median, and eight writers reached 3,078 to 3,374 writes a
second.&lt;/p&gt;
&lt;p&gt;The index pays off on reads. An &lt;code&gt;==&lt;/code&gt; or &lt;code&gt;in&lt;/code&gt; filter reads only the documents
holding the value, joining several filters by seeking each to the largest id
any of them is at. An &lt;code&gt;orderBy&lt;/code&gt;, a range or a cursor walks the field’s
entries from the right place and stops once it has enough, checking equality
filters on other fields against the index before it reads a document. In the
benchmark an equality on a string field took 20.0 ms against 124 ms.&lt;/p&gt;
&lt;h2 id=&quot;memory-that-does-not-grow-with-the-data&quot;&gt;Memory that does not grow with the data&lt;/h2&gt;
&lt;p&gt;Firenook keeps documents in redb on disk, behind a page cache with a 64 MiB
budget. After a commit, the store’s in-memory map of documents is empty
again; what stays is the revision, recent commit times and the bounded change
log. Over the two hours, Firenook’s process went from 1.64 GiB to 1.67 GiB.
The official emulator, which keeps all its data in its Java heap, went from
15.42 GiB to 22.57 GiB, and used 1,586 s of CPU time against Firenook’s
616 s.&lt;/p&gt;
&lt;p&gt;What the record does not show is where the official emulator’s 200 ms go. It
measures both emulators from outside, and we will not guess at the inside of
a JVM we did not profile. It does rule one explanation out: the official stack
paged to swap during the run, but its write median, 208 ms, is within a few
percent of an earlier run on the same server in which neither stack swapped.&lt;/p&gt;
&lt;h2 id=&quot;listener-fan-out&quot;&gt;Listener fan-out&lt;/h2&gt;
&lt;p&gt;A commit does not wait for listeners. Its observer publishes the new revision
on a watch channel and returns. Each listen stream wakes on that channel,
reads the change log since the revision it last saw, and for each key in its
targets’ scope checks whether the document now matches. The view is updated
in place, a document entering, changing or leaving, without running the query
again. Only a document leaving a full &lt;code&gt;limit&lt;/code&gt; window, or moving inside one,
makes a target run its query, since only the store knows which document fills
the gap. This work runs on a bounded pool of read threads, off the commit’s
path.&lt;/p&gt;
&lt;p&gt;In the benchmark, all 60,000 listener updates per emulator arrived, 11 ms
after the write at the median on Firenook and 209 ms on the official
emulator. One difference showed up: the official emulator notifies a listener
about a document its filter excludes, and Firenook does not. A journey of the
application that waited on such a notification ran longer on Firenook; it is
not an emulator latency, and the benchmark leaves it out.&lt;/p&gt;
&lt;h2 id=&quot;ordered-queries&quot;&gt;Ordered queries&lt;/h2&gt;
&lt;p&gt;Queries that order or range-filter a collection gain the most from the index.
Without it, such a query reads every document in its scope, 10,918 here; with
it, the walk stops at the 20 documents asked for, however large the
collection:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape, median&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Official&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Firenook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;orderBy an integer field, limit 20&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;96.3 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;12.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;orderBy a timestamp, descending, limit 20&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;109 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;24.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range on an integer field, limit 20&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;95.5 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;13.2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;==&lt;/code&gt; plus orderBy on another field&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;99.3 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;14.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Five cursor pages of 20&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;489 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;74.8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On the same data grown to 2.7 million documents, the four ordered and
filtered queries measured there answered in 3 to 6 ms. The official emulator,
on Java’s default settings, ran out of memory loading that data at the first
query; with a 24 GiB heap it answered each in 8 to 28 seconds.&lt;/p&gt;
&lt;p&gt;Firenook was faster on 25 of the 26 shapes and tied on the last: 500
document ids with no fields, 98.3 ms against 97.8 ms, because it reads each
document whole before dropping its fields.&lt;/p&gt;
&lt;h2 id=&quot;how-it-was-measured&quot;&gt;How it was measured&lt;/h2&gt;
&lt;p&gt;One AMD Ryzen 5 3600 server with 64 GiB and NVMe storage, on Ubuntu 24.04 and
Node 24.20.0, ran one emulator at a time from a clean start, with nothing
else running: firebase-tools 15.22.0 on Java 26 with its default settings,
then Firenook with its defaults. Both had the same data, the same
application revision and the same workload, and the run fails unless both
end with identical data. An independent audit recomputed every figure from
the raw samples.&lt;/p&gt;
&lt;p&gt;The numbers are kept in
&lt;a href=&quot;https://github.com/firenook-dev/firenook/blob/main/benchmarks/020-paired-acceptance.json&quot;&gt;&lt;code&gt;benchmarks/020-paired-acceptance.json&lt;/code&gt;&lt;/a&gt;,
and &lt;a href=&quot;https://firenook.dev/benchmarks&quot;&gt;the benchmarks page&lt;/a&gt; has the method and its limits. It is
one server and one application, so measure your own workload.&lt;/p&gt;
&lt;p&gt;To use these write semantics for your own data, read
&lt;a href=&quot;https://firenook.dev/docs/data/keep-data&quot;&gt;Keep data between runs&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Introducing Firenook</title><link>https://firenook.dev/blog/introducing-firenook</link><guid isPermaLink="true">https://firenook.dev/blog/introducing-firenook</guid><description>Firenook runs the Firebase emulators from one npm package, with your firebase.json, SDKs and ports, no Java, and data that survives restarts.</description><pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Firenook is a drop-in replacement for the Firebase Local Emulator Suite. It
reads your &lt;code&gt;firebase.json&lt;/code&gt;, listens on the same ports and keeps your data
between runs. 0.2.0 is its first release for public use. This post explains
why it exists, what it does today, what it does not do yet, and how to try it
on your project in three steps.&lt;/p&gt;
&lt;h2 id=&quot;a-day-with-the-emulator&quot;&gt;A day with the emulator&lt;/h2&gt;
&lt;p&gt;If you build on Firebase, the emulator suite is part of your day. You start it
in the morning, point your app and your tests at it, and leave it running. It
does its job: it behaves like production. But the day has costs you stop
noticing.&lt;/p&gt;
&lt;p&gt;Before the first start, you install Java, because the official Firestore
emulator runs on it. Then the data lives in the Java heap, and the heap grows.
In our benchmark of a real application, two hours of its traffic took the
official emulator from 15.42 GiB of memory to 22.57 GiB.&lt;/p&gt;
&lt;p&gt;When you stop it, the data is gone unless you exported it. Keeping it means
&lt;code&gt;--export-on-exit&lt;/code&gt; at every stop and &lt;code&gt;--import&lt;/code&gt; at every start. On that
application’s data, 211,205 Firestore documents and 6.7 GB in Storage, the
export took 54 to 58 seconds and the next start 30.7 to 31.7 seconds. A crash loses
everything since the last export.&lt;/p&gt;
&lt;p&gt;Writes slow down under load, too. With the application running, its listeners
open and its functions firing, the official emulator acknowledged a write in
208 ms at the median.&lt;/p&gt;
&lt;p&gt;None of this is a bug. An emulator that keeps its data in memory needs the
memory to hold it and an export to keep it. Firenook is what we built to
remove those costs without changing anything else in your project.&lt;/p&gt;
&lt;h2 id=&quot;what-firenook-is&quot;&gt;What Firenook is&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;One npm package.&lt;/strong&gt; Installing it adds a small launcher, and npm picks the
native engine for your machine from its optional dependencies: macOS on Apple
silicon or Intel, Linux x64 or arm64 with glibc 2.28 or newer, or Windows x64.
Nothing else is downloaded, at install or at start: no Java, no
firebase-tools, no emulator archives. Node runs your Functions, as it does in
production, so the emulators need Node 22, 24 or 26.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The same project.&lt;/strong&gt; Firenook reads &lt;code&gt;firebase.json&lt;/code&gt; and &lt;code&gt;.firebaserc&lt;/code&gt; the way
the Firebase CLI does, and serves each emulator on the port you gave it:
Firestore on 8080, Auth on 9099, Storage on 9199, Functions on 5001, Pub/Sub
on 8085, Cloud Tasks on 9499 and Eventarc on 9299 by default. Your client and
Admin SDKs connect unchanged, &lt;code&gt;connectFirestoreEmulator&lt;/code&gt; and
&lt;code&gt;FIRESTORE_EMULATOR_HOST&lt;/code&gt; included. It imports and exports the official
format, so your data moves both ways.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A native engine.&lt;/strong&gt; The services are written in Rust, and Firestore keeps its
data on disk, not in a heap. Add &lt;code&gt;--keep-data&lt;/code&gt; and Firestore, Auth and Storage
come back with all your data at the next start, with no export and no import.
Every acknowledged write is durable within a second;
&lt;a href=&quot;https://firenook.dev/blog/how-firenook-writes-40x-faster&quot;&gt;how Firenook writes 40× faster&lt;/a&gt; walks
through how, and how an ordered index answers &lt;code&gt;orderBy&lt;/code&gt;, ranges and cursors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A console.&lt;/strong&gt; The Emulator UI’s port serves Firenook’s console, at
&lt;code&gt;http://127.0.0.1:4000/console&lt;/code&gt;. It has a page for every service the suite
runs: Firestore and Storage to browse and edit, Auth users with an inbox of
the sign-in codes and email links Auth would have sent, and every function
call with what triggered it, its outcome, its duration and its logs. Pub/Sub,
Cloud Tasks and Eventarc have pages too, where the official UI has only its
log.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A terminal.&lt;/strong&gt; In a terminal, &lt;code&gt;emulators:start&lt;/code&gt; draws its own screen: the
start-up, a ready summary, and a line for every function call, with the stack
trace when one fails. Press &lt;code&gt;o&lt;/code&gt; to open the console, &lt;code&gt;r&lt;/code&gt; to reload your
functions and &lt;code&gt;q&lt;/code&gt; to stop, which saves your data first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A new project in one screen.&lt;/strong&gt; &lt;code&gt;npx firenook init&lt;/code&gt; in an empty folder asks
every question on one screen, with an answer already chosen for each: the
project id, the emulators, TypeScript or JavaScript Functions, and your coding
agent. Then it installs, builds and offers to start the emulators in the same
terminal. &lt;code&gt;--build&lt;/code&gt; compiles your TypeScript as you save.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An MCP server.&lt;/strong&gt; &lt;code&gt;firenook mcp&lt;/code&gt; gives a coding agent 54 tools over the
running suite, from Firestore queries to the line of your Security Rules that
denied a request.
&lt;a href=&quot;https://firenook.dev/blog/give-your-agent-the-emulator&quot;&gt;Give your coding agent the emulator&lt;/a&gt;
shows a session.&lt;/p&gt;
&lt;h2 id=&quot;what-we-measured&quot;&gt;What we measured&lt;/h2&gt;
&lt;p&gt;We ran one real application against each emulator in turn, on the same server,
with the same data and two hours of the same traffic. Then we ran each
emulator alone for starts, stops, 26 query shapes and a store of 2.7 million
documents. The official emulator was firebase-tools 15.22.0 on Java 26 with
its default settings, and Firenook ran with its defaults.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Official&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Firenook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Memory at peak&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;22.57 GiB&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;2.11 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write, median&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;208.2 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listener update after a write, median&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;209 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;11 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start again with the data&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;30.7–31.7 s&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0.59–0.60 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop with the data saved&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;54–58 s&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;0.05–0.26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;orderBy, limit 20, median&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;96.3 ms&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;12.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emulator CPU time over the two hours&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;1,586 s&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;616 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Firenook was faster on 25 of the 26 query shapes and tied on the last, 500
document ids with no fields. Queries that order or range-filter a collection
are served from an ordered index: on 2.7 million documents they take 3 to
6 ms, where the official emulator ran out of memory on Java’s defaults and
needed a 24 GiB heap and 8 to 28 seconds. Within the Storage cycle, downloads are still a little slower
on Firenook: 2.30 ms against 2.12 ms at the median.&lt;/p&gt;
&lt;p&gt;Every figure, the method and its limits are on
&lt;a href=&quot;https://firenook.dev/benchmarks&quot;&gt;the benchmarks page&lt;/a&gt;. It is one server and one application, so
measure your own workload before you rely on the numbers.&lt;/p&gt;
&lt;h2 id=&quot;what-it-does-not-do-yet&quot;&gt;What it does not do yet&lt;/h2&gt;
&lt;p&gt;0.2.0 is the first release we ask the public to use. Commit your lockfile
and upgrade on purpose, as you would any tool your tests depend on. Before
a release is published, it passes conformance replays
against recorded behaviour of the official emulator and a packed install on
all five platforms.&lt;/p&gt;
&lt;p&gt;Some things are not there yet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Realtime Database, Hosting, App Hosting and Data Connect are not emulated. A
&lt;code&gt;firebase.json&lt;/code&gt; that configures them still starts, with a warning, and the
rest of the suite runs.&lt;/li&gt;
&lt;li&gt;There is no &lt;code&gt;functions:shell&lt;/code&gt;. &lt;code&gt;firenook functions:invoke&lt;/code&gt; calls any
function on the running suite instead, background triggers included.&lt;/li&gt;
&lt;li&gt;There is no deploy or login command. Keep firebase-tools for deploying.&lt;/li&gt;
&lt;li&gt;Linux with musl (Alpine) and Windows on Arm are not qualified.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://firenook.dev/compatibility&quot;&gt;The compatibility page&lt;/a&gt; records what each service covers and
what it does not.&lt;/p&gt;
&lt;h2 id=&quot;try-it-in-three-steps&quot;&gt;Try it in three steps&lt;/h2&gt;
&lt;p&gt;Install it in your project, next to firebase-tools:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://firenook.dev/_astro/ec.dqibo.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://firenook.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame is-terminal not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;--save-dev&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;firenook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;npm install --save-dev firenook&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Check your project. This reads &lt;code&gt;firebase.json&lt;/code&gt; the way the Firebase CLI does
and changes nothing:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;console&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;$ npx firenook init --adopt --dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;Firenook init --adopt: ~/demo-shop/firebase.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;add     give Claude Code the Firenook MCP server: ~/demo-shop/.mcp.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;Dry run: nothing was written.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;$ npx firenook init --adopt --dry-runFirenook init --adopt: ~/demo-shop/firebase.json  add     give Claude Code the Firenook MCP server: ~/demo-shop/.mcp.jsonDry run: nothing was written.&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It names any service Firenook skips and any addition it would make, such as a
missing &lt;code&gt;emulators&lt;/code&gt; entry or the MCP server for the coding agent the project
uses. Without &lt;code&gt;--dry-run&lt;/code&gt;, it applies the additions, and edits your files as
text, so their formatting stays.&lt;/p&gt;
&lt;p&gt;Swap the command. The flags, the ports and the export format stay the same,
so your app and your data need no change:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;emulators&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;firebase emulators:start --import ./data --export-on-exit&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;test:integration&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;firebase emulators:exec &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#ba1e29&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;npm test&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#ba1e29&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;emulators&quot;&lt;/span&gt;&lt;span style=&quot;--0:#FFA198;--0fs:italic;--1:#82071E;--1fs:italic&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;firenook emulators:start --import ./data --export-on-exit&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;test:integration&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;firenook emulators:exec &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#bd1f2a&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;npm test&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#bd1f2a&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&amp;quot;scripts&amp;quot;: {  &amp;quot;emulators&amp;quot;: &amp;quot;firebase emulators:start --import ./data --export-on-exit&amp;quot;,  &amp;quot;test:integration&amp;quot;: &amp;quot;firebase emulators:exec \&amp;quot;npm test\&amp;quot;&amp;quot;  &amp;quot;emulators&amp;quot;: &amp;quot;firenook emulators:start --import ./data --export-on-exit&amp;quot;,  &amp;quot;test:integration&amp;quot;: &amp;quot;firenook emulators:exec \&amp;quot;npm test\&amp;quot;&amp;quot;}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Once that works, you can stop exporting. Replace &lt;code&gt;--export-on-exit&lt;/code&gt; with
&lt;code&gt;--keep-data&lt;/code&gt;: the first start imports &lt;code&gt;./data&lt;/code&gt; into &lt;code&gt;.firenook/state&lt;/code&gt;, every
later start picks up where you stopped, and &lt;code&gt;./data&lt;/code&gt; is never written. Add
&lt;code&gt;.firenook/&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Going back takes the same steps in reverse. While Firenook runs, export with
&lt;code&gt;npx firenook emulators:export ./export&lt;/code&gt;, stop it, and start the official CLI
on that export.&lt;/p&gt;
&lt;h2 id=&quot;open-source-and-independent&quot;&gt;Open source and independent&lt;/h2&gt;
&lt;p&gt;Firenook is open source under the Apache License 2.0, and the code is at
&lt;a href=&quot;https://github.com/firenook-dev/firenook&quot;&gt;github.com/firenook-dev/firenook&lt;/a&gt;.
It is an independent project, not affiliated with or endorsed by Google.
Firebase, Cloud Firestore and the other product names are trademarks of
Google LLC.&lt;/p&gt;
&lt;p&gt;Every change to Firenook is checked against recorded behaviour of the official
emulator, and the differences it keeps on purpose are written down. If you
find one that is not, or anything else that breaks on your project, open an
&lt;a href=&quot;https://github.com/firenook-dev/firenook/issues&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To start, &lt;a href=&quot;https://firenook.dev/docs/get-started/switch&quot;&gt;switch an existing project&lt;/a&gt; or
&lt;a href=&quot;https://firenook.dev/docs/get-started/new-project&quot;&gt;set up a new one&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Give your coding agent the emulator, not production</title><link>https://firenook.dev/blog/give-your-agent-the-emulator</link><guid isPermaLink="true">https://firenook.dev/blog/give-your-agent-the-emulator</guid><description>Firenook&apos;s MCP server gives a coding agent 54 tools over the local Firebase emulators: data, users, files, function calls, logs and Security Rules.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A coding agent working on a Firebase app needs data, function calls and rules
to look at before it can help. This post shows how to give it the local
emulators instead of production, with &lt;code&gt;firenook mcp&lt;/code&gt;: what the tools do, how
to set them up, a real session, and what stays on your machine.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;
&lt;p&gt;Ask an agent why a function fails, and it has two ways to answer. It can read
the code and guess, which gets you a confident answer about a request it
never saw. Or it can look for credentials, a service account key or a
logged-in CLI, and query production. That puts your customers’ data in a
prompt and lets an agent write where your real users are.&lt;/p&gt;
&lt;p&gt;The emulators already hold what the agent needs: data in the shape your app
writes it, the request that failed with its stack trace, the rule that denied
a write. What is missing is a way for the agent to ask them.&lt;/p&gt;
&lt;h2 id=&quot;what-firenook-mcp-serves&quot;&gt;What firenook mcp serves&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;firenook mcp&lt;/code&gt; is a Model Context Protocol server over stdio, part of the
Firenook package, with no dependencies of its own. It reads &lt;code&gt;firebase.json&lt;/code&gt;
and &lt;code&gt;.firebaserc&lt;/code&gt; from the folder the agent starts it in, so it needs no
project id. It finds the running suite again on every call, so you can
restart the emulators without restarting the agent.&lt;/p&gt;
&lt;p&gt;It serves 54 tools, in groups:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th style=&quot;text-align: right&quot;&gt;Tools&lt;/th&gt;
&lt;th&gt;What the agent can do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
&lt;td&gt;See how the project was found and which emulators run; export the data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firestore&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Get, query, count, write and delete documents; list collections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;13&lt;/td&gt;
&lt;td&gt;Create, list and delete users; read the email and SMS codes; mint ID tokens; set claims; disable users; list tenants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;7&lt;/td&gt;
&lt;td&gt;List, read, upload, copy, move and delete files; read metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
&lt;td&gt;List and invoke functions; read each call with its request, error and stack trace; replay a call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
&lt;td&gt;Search the suite’s log by service, level, function, call, text and time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rules&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;3&lt;/td&gt;
&lt;td&gt;Read the rules in force; ask whether a request would be allowed and which line decided; list recent denials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pub/Sub&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
&lt;td&gt;Publish; list topics and subscriptions; peek at a backlog without acknowledging it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks and schedules&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
&lt;td&gt;Read queue statistics and tasks; run a task or a schedule now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eventarc and Extensions&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
&lt;td&gt;Publish, list and replay events; list and reload Extensions instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reset&lt;/td&gt;
&lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
&lt;td&gt;Clear the data of the services you choose&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;firenook mcp --only firestore,auth&lt;/code&gt; serves a subset; the two status tools
are always there. &lt;a href=&quot;https://firenook.dev/docs/reference/mcp-tools&quot;&gt;The tool reference&lt;/a&gt; lists every
tool with its arguments.&lt;/p&gt;
&lt;h2 id=&quot;set-it-up&quot;&gt;Set it up&lt;/h2&gt;
&lt;p&gt;From the project folder, with Firenook installed in the project:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://firenook.dev/_astro/ec.dqibo.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://firenook.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame is-terminal not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657;--1:#953800&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;firenook&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;add&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;npx firenook mcp add&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It looks for Claude Code (a &lt;code&gt;.claude/&lt;/code&gt; folder, &lt;code&gt;CLAUDE.md&lt;/code&gt;, an &lt;code&gt;.mcp.json&lt;/code&gt;
or &lt;code&gt;claude&lt;/code&gt; on the PATH), Cursor (&lt;code&gt;.cursor/&lt;/code&gt;) and VS Code (&lt;code&gt;.vscode/&lt;/code&gt;), and
writes each one’s project file. In our demo project:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;console&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;$ npx firenook mcp add&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;Firenook mcp add: ~/demo-shop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;add     .mcp.json (Claude Code)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;add     .cursor/mcp.json (Cursor)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;add     .vscode/mcp.json (VS Code)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;Codex: codex mcp add firenook -- npx firenook mcp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;Commit these files so every teammate&apos;s agent has the server; restart the agent to load it.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;$ npx firenook mcp addFirenook mcp add: ~/demo-shop  add     .mcp.json (Claude Code)  add     .cursor/mcp.json (Cursor)  add     .vscode/mcp.json (VS Code)  Codex: codex mcp add firenook -- npx firenook mcpCommit these files so every teammate&amp;#x27;s agent has the server; restart the agent to load it.&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each file gets one entry that runs &lt;code&gt;npx firenook mcp&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;.mcp.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;firenook&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;firenook&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;mcp&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;mcpServers&amp;quot;: {    &amp;quot;firenook&amp;quot;: {      &amp;quot;command&amp;quot;: &amp;quot;npx&amp;quot;,      &amp;quot;args&amp;quot;: [        &amp;quot;firenook&amp;quot;,        &amp;quot;mcp&amp;quot;      ]    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;VS Code’s &lt;code&gt;.vscode/mcp.json&lt;/code&gt; names the same entry under &lt;code&gt;servers&lt;/code&gt;, with
&lt;code&gt;&quot;type&quot;: &quot;stdio&quot;&lt;/code&gt;. Other servers in those files are kept, and a second run
changes nothing: it prints &lt;code&gt;keep&lt;/code&gt; for each file that is already set.
&lt;code&gt;--dry-run&lt;/code&gt; shows what it would write, &lt;code&gt;--agent cursor,vscode&lt;/code&gt; picks the
agents, and &lt;code&gt;--force&lt;/code&gt; replaces another &lt;code&gt;firenook&lt;/code&gt; entry. An agent configured
only in your home folder, such as Codex above, gets the line to run, and no
file outside the project is edited.&lt;/p&gt;
&lt;p&gt;Start the emulators as usual with &lt;code&gt;npx firenook emulators:start&lt;/code&gt;, then
restart the agent so it loads the server.&lt;/p&gt;
&lt;h2 id=&quot;a-session&quot;&gt;A session&lt;/h2&gt;
&lt;p&gt;The home page shows an agent answering a question about the demo shop: eight
products, twelve customers, twelve orders through its &lt;code&gt;checkout&lt;/code&gt; function,
and one webhook sent without a signature. The question is an example; the
tool calls and the answers are from a run of the demo project, trimmed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Stripe webhook answers 500 on my machine. Why?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The agent calls &lt;code&gt;functions_list_calls&lt;/code&gt; with &lt;code&gt;{&quot;outcome&quot;: &quot;failed&quot;}&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;total&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;calls&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;e72ca3-25&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;function&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;us-central1-stripeWebhook&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;POST /&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;durationMs&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;1.15&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;failed&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;500&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;total&amp;quot;: 1,  &amp;quot;calls&amp;quot;: [    {      &amp;quot;id&amp;quot;: &amp;quot;e72ca3-25&amp;quot;,      &amp;quot;function&amp;quot;: &amp;quot;us-central1-stripeWebhook&amp;quot;,      &amp;quot;kind&amp;quot;: &amp;quot;http&amp;quot;,      &amp;quot;summary&amp;quot;: &amp;quot;POST /&amp;quot;,      &amp;quot;durationMs&amp;quot;: 1.15,      &amp;quot;outcome&amp;quot;: &amp;quot;failed&amp;quot;,      &amp;quot;status&amp;quot;: 500    }  ]}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then &lt;code&gt;functions_get_call&lt;/code&gt; with that id, for the call in full:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;Unhandled error Error: No signatures found matching the expected signature for payload&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;stack&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;Unhandled error Error: No signatures found …&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;    at functions/index.js:24:11&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;    …&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;host&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;127.0.0.1:5001&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;connection&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;keep-alive&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;accept&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;accept-language&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;sec-fetch-mode&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;cors&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;user-agent&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;accept-encoding&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;gzip, deflate&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;content-length&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;37&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;checkout.session.completed&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;Internal Server Error&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;error&amp;quot;: &amp;quot;Unhandled error Error: No signatures found matching the expected signature for payload&amp;quot;,  &amp;quot;stack&amp;quot;: &amp;quot;Unhandled error Error: No signatures found …\n    at functions/index.js:24:11\n    …&amp;quot;,  &amp;quot;request&amp;quot;: {    &amp;quot;method&amp;quot;: &amp;quot;POST&amp;quot;,    &amp;quot;path&amp;quot;: &amp;quot;/&amp;quot;,    &amp;quot;headers&amp;quot;: {      &amp;quot;host&amp;quot;: &amp;quot;127.0.0.1:5001&amp;quot;,      &amp;quot;connection&amp;quot;: &amp;quot;keep-alive&amp;quot;,      &amp;quot;content-type&amp;quot;: &amp;quot;application/json&amp;quot;,      &amp;quot;accept&amp;quot;: &amp;quot;*/*&amp;quot;,      &amp;quot;accept-language&amp;quot;: &amp;quot;*&amp;quot;,      &amp;quot;sec-fetch-mode&amp;quot;: &amp;quot;cors&amp;quot;,      &amp;quot;user-agent&amp;quot;: &amp;quot;node&amp;quot;,      &amp;quot;accept-encoding&amp;quot;: &amp;quot;gzip, deflate&amp;quot;,      &amp;quot;content-length&amp;quot;: &amp;quot;37&amp;quot;    },    &amp;quot;body&amp;quot;: { &amp;quot;type&amp;quot;: &amp;quot;checkout.session.completed&amp;quot; }  },  &amp;quot;response&amp;quot;: { &amp;quot;status&amp;quot;: 500, &amp;quot;body&amp;quot;: &amp;quot;Internal Server Error&amp;quot; }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The answer is in the request: it carries no &lt;code&gt;stripe-signature&lt;/code&gt; header, and
line 24 of &lt;code&gt;functions/index.js&lt;/code&gt; throws when the header is missing. The agent
found it without touching production.&lt;/p&gt;
&lt;p&gt;We ran a second session on the same project for this post, about rules. Its
Storage rules deny everything, and the question is whether a signed-in
customer can upload a product photo. The agent calls &lt;code&gt;rules_evaluate&lt;/code&gt; for a
&lt;code&gt;create&lt;/code&gt; of &lt;code&gt;products/mug.jpg&lt;/code&gt; as the customer’s uid, with a 48,000-byte JPEG:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;as&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;uid&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;a6oK9eDRtwAa9RTryMc2ZTjEjKTm&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;claims&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: {} },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;allowed&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;deny&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;decidedBy&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;allow read, write: if false;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;deny&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;as&amp;quot;: { &amp;quot;uid&amp;quot;: &amp;quot;a6oK9eDRtwAa9RTryMc2ZTjEjKTm&amp;quot;, &amp;quot;claims&amp;quot;: {} },  &amp;quot;allowed&amp;quot;: false,  &amp;quot;outcome&amp;quot;: &amp;quot;deny&amp;quot;,  &amp;quot;decidedBy&amp;quot;: [    { &amp;quot;line&amp;quot;: 9, &amp;quot;text&amp;quot;: &amp;quot;allow read, write: if false;&amp;quot;, &amp;quot;outcome&amp;quot;: &amp;quot;deny&amp;quot; }  ]}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It drafts a rule and passes it as &lt;code&gt;source&lt;/code&gt;, which evaluates the draft instead
of the rules in force:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;storage.rules (draft)&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;txt&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;rules_version = &apos;2&apos;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;service firebase.storage {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;match /b/{bucket}/o {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;match /products/{file} {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;allow read: if true;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;allow write: if request.auth != null&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;&amp;amp;&amp;amp; request.resource.contentType.matches(&apos;image/.*&apos;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;&amp;amp;&amp;amp; request.resource.size &amp;lt; 5 * 1024 * 1024;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e6edf3;--1:#1f2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;rules_version = &amp;#x27;2&amp;#x27;;service firebase.storage {  match /b/{bucket}/o {    match /products/{file} {      allow read: if true;      allow write: if request.auth != null        &amp;amp;&amp;amp; request.resource.contentType.matches(&amp;#x27;image/.*&amp;#x27;)        &amp;amp;&amp;amp; request.resource.size &lt; 5 * 1024 * 1024;    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The same request is now allowed, with the line that decided and the value of
every expression on the way:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;draft&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;allowed&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;allow&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;decidedBy&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;allow write: if request.auth != null&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;allow&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;steps&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;expression&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;request.auth != null&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;expression&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;request.resource.contentType.matches(&apos;image/.*&apos;)&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{ &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF;--1:#0550AE&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;expression&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;request.resource.size &amp;lt; 5 * 1024 * 1024&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;draft&amp;quot;: true,  &amp;quot;allowed&amp;quot;: true,  &amp;quot;outcome&amp;quot;: &amp;quot;allow&amp;quot;,  &amp;quot;decidedBy&amp;quot;: [    { &amp;quot;line&amp;quot;: 6, &amp;quot;text&amp;quot;: &amp;quot;allow write: if request.auth != null&amp;quot;, &amp;quot;outcome&amp;quot;: &amp;quot;allow&amp;quot; }  ],  &amp;quot;steps&amp;quot;: [    { &amp;quot;line&amp;quot;: 6, &amp;quot;expression&amp;quot;: &amp;quot;request.auth != null&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;true&amp;quot; },    { &amp;quot;line&amp;quot;: 7, &amp;quot;expression&amp;quot;: &amp;quot;request.resource.contentType.matches(&amp;#x27;image/.*&amp;#x27;)&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;true&amp;quot; },    { &amp;quot;line&amp;quot;: 8, &amp;quot;expression&amp;quot;: &amp;quot;request.resource.size &lt; 5 * 1024 * 1024&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;true&amp;quot; }  ]}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Without a uid, the same draft denies the request at line 6, where
&lt;code&gt;request.auth&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;. Nothing was written: your &lt;code&gt;storage.rules&lt;/code&gt; stays as
it is until you accept the change. In this session, every tool answered in
0.8 to 18 ms.&lt;/p&gt;
&lt;p&gt;Destructive tools keep the console’s safeguard. Asked to clear Firestore and
Auth at once, &lt;code&gt;emulators_clear&lt;/code&gt; refuses until the agent names the project:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame not-content&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787;--1:#116329&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;&quot;Clearing 2 services (firestore, auth) deletes all their data; to confirm, pass confirmProject: &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt;demo-shop&lt;/span&gt;&lt;span style=&quot;--0:#FF7B72;--1:#CA212D&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF;--1:#0A3069&quot;&gt; (the project id)&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3;--1:#1F2328&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;quot;error&amp;quot;: &amp;quot;Clearing 2 services (firestore, auth) deletes all their data; to confirm, pass confirmProject: \&amp;quot;demo-shop\&amp;quot; (the project id)&amp;quot;}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2 id=&quot;what-stays-on-your-machine&quot;&gt;What stays on your machine&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The server holds no credentials and contacts no cloud service. Every
request goes to the emulators, at the addresses the hub or &lt;code&gt;firebase.json&lt;/code&gt;
gives.&lt;/li&gt;
&lt;li&gt;It calls the emulators with the owner token the Admin SDK uses, so its
reads and writes skip Security Rules, as the Admin SDK’s do. To ask about
rules, the agent uses &lt;code&gt;rules_evaluate&lt;/code&gt;, which writes nothing.&lt;/li&gt;
&lt;li&gt;Each request carries an &lt;code&gt;x-firenook-client: firenook-mcp/&amp;lt;version&amp;gt;&lt;/code&gt;
header, and after each call the server tells the console which tool ran,
so the console’s Connect dialog can show when an agent last called.
Nothing else is recorded.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;demo-&lt;/code&gt; project id never reaches a real Firebase project. With a real
project id the suite still contacts nothing, but your own function code can
reach the real project if it holds credentials: Firenook is not a network
sandbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The server gives the agent a better place to look, not a cage: an agent with
a shell can still run what it likes. Treat what an agent writes through the
server as you would any emulator data, synthetic only.&lt;/p&gt;
&lt;p&gt;Read &lt;a href=&quot;https://firenook.dev/mcp&quot;&gt;how agents use Firenook&lt;/a&gt; for an overview, and
&lt;a href=&quot;https://firenook.dev/docs/tools/mcp&quot;&gt;Connect your coding agent&lt;/a&gt; for the setup of each agent.&lt;/p&gt;
</content:encoded></item></channel></rss>