firebase-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +119 -0
  2. package/dist/access/index.d.ts +23 -0
  3. package/dist/access/index.d.ts.map +1 -0
  4. package/dist/access/index.js +35 -0
  5. package/dist/access/index.js.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +14 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/config/index.d.ts +51 -0
  11. package/dist/config/index.d.ts.map +1 -0
  12. package/dist/config/index.js +56 -0
  13. package/dist/config/index.js.map +1 -0
  14. package/dist/firebase/index.d.ts +21 -0
  15. package/dist/firebase/index.d.ts.map +1 -0
  16. package/dist/firebase/index.js +42 -0
  17. package/dist/firebase/index.js.map +1 -0
  18. package/dist/server/index.d.ts +23 -0
  19. package/dist/server/index.d.ts.map +1 -0
  20. package/dist/server/index.js +60 -0
  21. package/dist/server/index.js.map +1 -0
  22. package/dist/tools/firestore/aggregate_collection.d.ts +34 -0
  23. package/dist/tools/firestore/aggregate_collection.d.ts.map +1 -0
  24. package/dist/tools/firestore/aggregate_collection.js +86 -0
  25. package/dist/tools/firestore/aggregate_collection.js.map +1 -0
  26. package/dist/tools/firestore/count_documents.d.ts +25 -0
  27. package/dist/tools/firestore/count_documents.d.ts.map +1 -0
  28. package/dist/tools/firestore/count_documents.js +54 -0
  29. package/dist/tools/firestore/count_documents.js.map +1 -0
  30. package/dist/tools/firestore/get_collection_schema.d.ts +30 -0
  31. package/dist/tools/firestore/get_collection_schema.d.ts.map +1 -0
  32. package/dist/tools/firestore/get_collection_schema.js +129 -0
  33. package/dist/tools/firestore/get_collection_schema.js.map +1 -0
  34. package/dist/tools/firestore/get_document.d.ts +32 -0
  35. package/dist/tools/firestore/get_document.d.ts.map +1 -0
  36. package/dist/tools/firestore/get_document.js +62 -0
  37. package/dist/tools/firestore/get_document.js.map +1 -0
  38. package/dist/tools/firestore/get_many_documents.d.ts +33 -0
  39. package/dist/tools/firestore/get_many_documents.d.ts.map +1 -0
  40. package/dist/tools/firestore/get_many_documents.js +86 -0
  41. package/dist/tools/firestore/get_many_documents.js.map +1 -0
  42. package/dist/tools/firestore/index.d.ts +12 -0
  43. package/dist/tools/firestore/index.d.ts.map +1 -0
  44. package/dist/tools/firestore/index.js +28 -0
  45. package/dist/tools/firestore/index.js.map +1 -0
  46. package/dist/tools/firestore/list_collections.d.ts +24 -0
  47. package/dist/tools/firestore/list_collections.d.ts.map +1 -0
  48. package/dist/tools/firestore/list_collections.js +61 -0
  49. package/dist/tools/firestore/list_collections.js.map +1 -0
  50. package/dist/tools/firestore/list_documents.d.ts +24 -0
  51. package/dist/tools/firestore/list_documents.d.ts.map +1 -0
  52. package/dist/tools/firestore/list_documents.js +60 -0
  53. package/dist/tools/firestore/list_documents.js.map +1 -0
  54. package/dist/tools/firestore/list_indexes.d.ts +32 -0
  55. package/dist/tools/firestore/list_indexes.d.ts.map +1 -0
  56. package/dist/tools/firestore/list_indexes.js +99 -0
  57. package/dist/tools/firestore/list_indexes.js.map +1 -0
  58. package/dist/tools/firestore/normalize.d.ts +8 -0
  59. package/dist/tools/firestore/normalize.d.ts.map +1 -0
  60. package/dist/tools/firestore/normalize.js +36 -0
  61. package/dist/tools/firestore/normalize.js.map +1 -0
  62. package/dist/tools/firestore/query_collection.d.ts +34 -0
  63. package/dist/tools/firestore/query_collection.d.ts.map +1 -0
  64. package/dist/tools/firestore/query_collection.js +96 -0
  65. package/dist/tools/firestore/query_collection.js.map +1 -0
  66. package/dist/tools/firestore/query_collection_group.d.ts +34 -0
  67. package/dist/tools/firestore/query_collection_group.d.ts.map +1 -0
  68. package/dist/tools/firestore/query_collection_group.js +94 -0
  69. package/dist/tools/firestore/query_collection_group.js.map +1 -0
  70. package/dist/tools/firestore/read_collections.d.ts +44 -0
  71. package/dist/tools/firestore/read_collections.d.ts.map +1 -0
  72. package/dist/tools/firestore/read_collections.js +95 -0
  73. package/dist/tools/firestore/read_collections.js.map +1 -0
  74. package/dist/tools/firestore/types.d.ts +52 -0
  75. package/dist/tools/firestore/types.d.ts.map +1 -0
  76. package/dist/tools/firestore/types.js +75 -0
  77. package/dist/tools/firestore/types.js.map +1 -0
  78. package/dist/tools/index.d.ts +104 -0
  79. package/dist/tools/index.d.ts.map +1 -0
  80. package/dist/tools/index.js +121 -0
  81. package/dist/tools/index.js.map +1 -0
  82. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # firebase-mcp
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that exposes Firebase Firestore to AI agents. Built with [Effect](https://effect.website) and the Firebase Admin SDK, it runs over stdio and is designed to be wired directly into any MCP-compatible host (Cursor, Claude Desktop, etc.).
4
+
5
+ ## Features
6
+
7
+ - **11 Firestore read tools** covering collections, documents, queries, aggregations, and schema inference
8
+ - **Glob-based access control** — allow/deny rules evaluated per Firestore path before any read is performed
9
+ - **Pagination** on `query_collection` and `read_collection` via cursor-based `startAfter` / `nextPageCursor`
10
+ - **Batch fetching** with configurable `maxBatchFetchSize`
11
+ - **Schema inference** via `get_collection_schema` — samples documents and infers field types without reading the full collection
12
+ - Zero runtime state — each tool call hits Firestore directly through the Admin SDK
13
+
14
+ ## Tools
15
+
16
+ | Tool | Description |
17
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------- |
18
+ | `list_collections` | List root collections or subcollections of a document. Optionally include document counts. |
19
+ | `list_documents` | List all document IDs in a collection, including phantom documents. Optionally include subcollection names. |
20
+ | `read_collection` | Read documents from a collection with optional phantom-doc surfacing. |
21
+ | `get_document` | Fetch a single document by path. |
22
+ | `get_many_documents` | Batch-fetch documents by a list of paths or a collection + ID list. |
23
+ | `query_collection` | Query with filters, ordering, limit, and pagination. |
24
+ | `query_collection_group` | Query across all collections sharing the same name, regardless of parent path. |
25
+ | `count_documents` | Server-side document count with optional filters. |
26
+ | `aggregate_collection` | Native `sum()` and `avg()` aggregations without fetching documents. |
27
+ | `get_collection_schema` | Sample a collection from both ends and infer field types. |
28
+ | `list_indexes` | List Firestore indexes for the project. |
29
+
30
+ ## Requirements
31
+
32
+ - Node.js 18+
33
+ - A Firebase project with Firestore enabled
34
+ - A service account JSON key with Firestore read permissions
35
+
36
+ ## Setup
37
+
38
+ **1. Create `firebase-mcp.json`**
39
+
40
+ ```json
41
+ {
42
+ "firebase": {
43
+ "projectId": "your-project-id",
44
+ "serviceAccountPath": "secrets/serviceAccount.json"
45
+ },
46
+ "firestore": {
47
+ "rules": {
48
+ "allow": ["**"],
49
+ "deny": []
50
+ },
51
+ "maxCollectionReadSize": 10,
52
+ "maxBatchFetchSize": 200
53
+ }
54
+ }
55
+ ```
56
+
57
+ **2. Add your service account key**
58
+
59
+ Place your Firebase service account JSON at any path you prefer — you'll reference it in `firebase-mcp.json` above.
60
+
61
+ **3. Wire it into your MCP host**
62
+
63
+ See the [Connecting to Cursor](#connecting-to-cursor) section below — no installation step required when using `npx`.
64
+
65
+ ## Configuration
66
+
67
+ | Field | Type | Default | Description |
68
+ | --------------------------------- | ---------- | ------- | ---------------------------------------------------------- |
69
+ | `firebase.projectId` | `string` | — | Firebase project ID |
70
+ | `firebase.serviceAccountPath` | `string` | — | Path to service account JSON (relative to CWD or absolute) |
71
+ | `firestore.rules.allow` | `string[]` | — | Glob patterns for allowed Firestore paths |
72
+ | `firestore.rules.deny` | `string[]` | — | Glob patterns for denied Firestore paths (evaluated first) |
73
+ | `firestore.maxCollectionReadSize` | `number` | `10` | Default document limit for collection reads |
74
+ | `firestore.maxBatchFetchSize` | `number` | `200` | Maximum documents per batch fetch |
75
+
76
+ A custom config path can be passed at startup:
77
+
78
+ ```bash
79
+ node dist/cli/index.js --config /path/to/firebase-mcp.json
80
+ ```
81
+
82
+ ## Access Control
83
+
84
+ Firestore path access is governed by glob patterns evaluated with [micromatch](https://github.com/micromatch/micromatch). Deny rules take precedence over allow rules. Every tool call checks the target path before hitting Firestore.
85
+
86
+ ```json
87
+ {
88
+ "rules": {
89
+ "allow": ["users/**", "products/**"],
90
+ "deny": ["users/*/private/**"]
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Connecting to Cursor
96
+
97
+ Add to your MCP config (e.g. `.cursor/mcp.json`):
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "firebase": {
103
+ "command": "npx",
104
+ "args": [
105
+ "-y",
106
+ "firebase-mcp",
107
+ "--config",
108
+ "/path/to/firebase-mcp.json"
109
+ ]
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ `npx -y` will download and cache the package automatically on first run. No manual installation needed.
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,23 @@
1
+ import { Effect } from 'effect';
2
+ import { ConfigService } from '../config';
3
+ declare const AccessDeniedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "AccessDeniedError";
5
+ } & Readonly<A>;
6
+ export declare class AccessDeniedError extends AccessDeniedError_base<{
7
+ readonly path: string;
8
+ }> {
9
+ }
10
+ export declare const isAllowed: (path: string, rules: {
11
+ allow: readonly string[];
12
+ deny: readonly string[];
13
+ }) => boolean;
14
+ declare const AccessService_base: Effect.Service.Class<AccessService, "AccessService", {
15
+ readonly accessors: true;
16
+ readonly effect: Effect.Effect<{
17
+ check: (path: string) => Effect.Effect<void, never, never> | Effect.Effect<never, AccessDeniedError, never>;
18
+ }, never, ConfigService>;
19
+ }>;
20
+ export declare class AccessService extends AccessService_base {
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;;;;AAE1C,qBAAa,iBAAkB,SAAQ,uBAAsC;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;CAAG;AAEL,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,OAAO;IAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,KAC3D,OAIF,CAAC;;;;sBAWoB,MAAM;;;AAT5B,qBAAa,aAAc,SAAQ,kBAgBlC;CAAG"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AccessService = exports.isAllowed = exports.AccessDeniedError = void 0;
7
+ const effect_1 = require("effect");
8
+ const micromatch_1 = __importDefault(require("micromatch"));
9
+ const config_1 = require("../config");
10
+ class AccessDeniedError extends effect_1.Data.TaggedError('AccessDeniedError') {
11
+ }
12
+ exports.AccessDeniedError = AccessDeniedError;
13
+ const isAllowed = (path, rules) => {
14
+ if (micromatch_1.default.isMatch(path, [...rules.deny]))
15
+ return false;
16
+ if (micromatch_1.default.isMatch(path, [...rules.allow]))
17
+ return true;
18
+ return false;
19
+ };
20
+ exports.isAllowed = isAllowed;
21
+ class AccessService extends effect_1.Effect.Service()('AccessService', {
22
+ accessors: true,
23
+ effect: effect_1.Effect.gen(function* () {
24
+ const { config } = yield* config_1.ConfigService;
25
+ const rules = config.firestore.rules;
26
+ return {
27
+ check: (path) => (0, exports.isAllowed)(path, rules)
28
+ ? effect_1.Effect.void
29
+ : effect_1.Effect.fail(new AccessDeniedError({ path })),
30
+ };
31
+ }),
32
+ }) {
33
+ }
34
+ exports.AccessService = AccessService;
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAsC;AACtC,4DAAoC;AACpC,sCAA0C;AAE1C,MAAa,iBAAkB,SAAQ,aAAI,CAAC,WAAW,CAAC,mBAAmB,CAEzE;CAAG;AAFL,8CAEK;AAEE,MAAM,SAAS,GAAG,CACvB,IAAY,EACZ,KAA4D,EACnD,EAAE;IACX,IAAI,oBAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,oBAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAPW,QAAA,SAAS,aAOpB;AAEF,MAAa,aAAc,SAAQ,eAAM,CAAC,OAAO,EAAiB,CAChE,eAAe,EACf;IACE,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,sBAAa,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QAErC,OAAO;YACL,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CACtB,IAAA,iBAAS,EAAC,IAAI,EAAE,KAAK,CAAC;gBACpB,CAAC,CAAC,eAAM,CAAC,IAAI;gBACb,CAAC,CAAC,eAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SACnD,CAAC;IACJ,CAAC,CAAC;CACH,CACF;CAAG;AAhBJ,sCAgBI"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const platform_node_1 = require("@effect/platform-node");
5
+ const effect_1 = require("effect");
6
+ const server_1 = require("../server");
7
+ const program = effect_1.Effect.gen(function* () {
8
+ const mcp = yield* server_1.McpServerService;
9
+ yield* mcp.start();
10
+ yield* effect_1.Effect.sync(() => process.stderr.write('[firebase-mcp] Server running on stdio\n'));
11
+ yield* effect_1.Effect.never;
12
+ }).pipe(effect_1.Effect.provide(server_1.McpServerService.Default));
13
+ platform_node_1.NodeRuntime.runMain(program);
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;AAEA,yDAAoD;AACpD,mCAAgC;AAEhC,sCAA6C;AAE7C,MAAM,OAAO,GAAG,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,yBAAgB,CAAC;IACpC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,KAAK,CAAC,CAAC,eAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CACjE,CAAC;IACF,KAAK,CAAC,CAAC,eAAM,CAAC,KAAK,CAAC;AACtB,CAAC,CAAC,CAAC,IAAI,CAAC,eAAM,CAAC,OAAO,CAAC,yBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AAElD,2BAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { Effect, Schema } from 'effect';
2
+ declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "ConfigError";
4
+ } & Readonly<A>;
5
+ export declare class ConfigError extends ConfigError_base<{
6
+ readonly message: string;
7
+ readonly cause?: unknown;
8
+ }> {
9
+ }
10
+ declare const AppConfigSchema: Schema.Struct<{
11
+ firebase: Schema.Struct<{
12
+ projectId: typeof Schema.String;
13
+ serviceAccountPath: typeof Schema.String;
14
+ }>;
15
+ firestore: Schema.Struct<{
16
+ rules: Schema.Struct<{
17
+ allow: Schema.Array$<typeof Schema.String>;
18
+ deny: Schema.Array$<typeof Schema.String>;
19
+ }>;
20
+ maxCollectionReadSize: Schema.optionalWith<typeof Schema.Number, {
21
+ default: () => number;
22
+ }>;
23
+ maxBatchFetchSize: Schema.optionalWith<typeof Schema.Number, {
24
+ default: () => number;
25
+ }>;
26
+ }>;
27
+ }>;
28
+ export type AppConfig = Schema.Schema.Type<typeof AppConfigSchema>;
29
+ declare const ConfigService_base: Effect.Service.Class<ConfigService, "ConfigService", {
30
+ readonly accessors: true;
31
+ readonly effect: Effect.Effect<{
32
+ config: {
33
+ readonly firebase: {
34
+ readonly projectId: string;
35
+ readonly serviceAccountPath: string;
36
+ };
37
+ readonly firestore: {
38
+ readonly rules: {
39
+ readonly allow: readonly string[];
40
+ readonly deny: readonly string[];
41
+ };
42
+ readonly maxCollectionReadSize: number;
43
+ readonly maxBatchFetchSize: number;
44
+ };
45
+ };
46
+ }, ConfigError, never>;
47
+ }>;
48
+ export declare class ConfigService extends ConfigService_base {
49
+ }
50
+ export {};
51
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAK9C,qBAAa,WAAY,SAAQ,iBAAgC;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG;AAkBL,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;EAGnB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;AAEnE,qBAAa,aAAc,SAAQ,kBAoClC;CAAG"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfigService = exports.ConfigError = void 0;
7
+ const effect_1 = require("effect");
8
+ const minimist_1 = __importDefault(require("minimist"));
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ class ConfigError extends effect_1.Data.TaggedError('ConfigError') {
12
+ }
13
+ exports.ConfigError = ConfigError;
14
+ const FirebaseConfigSchema = effect_1.Schema.Struct({
15
+ projectId: effect_1.Schema.String,
16
+ serviceAccountPath: effect_1.Schema.String,
17
+ });
18
+ const FirestoreRulesSchema = effect_1.Schema.Struct({
19
+ allow: effect_1.Schema.Array(effect_1.Schema.String),
20
+ deny: effect_1.Schema.Array(effect_1.Schema.String),
21
+ });
22
+ const FirestoreConfigSchema = effect_1.Schema.Struct({
23
+ rules: FirestoreRulesSchema,
24
+ maxCollectionReadSize: effect_1.Schema.optionalWith(effect_1.Schema.Number, { default: () => 10 }),
25
+ maxBatchFetchSize: effect_1.Schema.optionalWith(effect_1.Schema.Number, { default: () => 200 }),
26
+ });
27
+ const AppConfigSchema = effect_1.Schema.Struct({
28
+ firebase: FirebaseConfigSchema,
29
+ firestore: FirestoreConfigSchema,
30
+ });
31
+ class ConfigService extends effect_1.Effect.Service()('ConfigService', {
32
+ accessors: true,
33
+ effect: effect_1.Effect.gen(function* () {
34
+ const args = (0, minimist_1.default)(process.argv.slice(2));
35
+ const configPath = args['config'] ?? './firebase-mcp.json';
36
+ const absolutePath = (0, node_path_1.resolve)(configPath);
37
+ const raw = yield* effect_1.Effect.try({
38
+ try: () => (0, node_fs_1.readFileSync)(absolutePath, 'utf-8'),
39
+ catch: (cause) => new ConfigError({
40
+ message: `Config file not found: ${absolutePath}`,
41
+ cause,
42
+ }),
43
+ });
44
+ const json = yield* effect_1.Effect.try({
45
+ try: () => JSON.parse(raw),
46
+ catch: (cause) => new ConfigError({ message: `Config file is not valid JSON`, cause }),
47
+ });
48
+ const config = yield* effect_1.Schema.decodeUnknown(AppConfigSchema)(json).pipe(effect_1.Effect.mapError((cause) => new ConfigError({ message: `Config validation failed`, cause })));
49
+ return {
50
+ config,
51
+ };
52
+ }),
53
+ }) {
54
+ }
55
+ exports.ConfigService = ConfigService;
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":";;;;;;AAAA,mCAA8C;AAC9C,wDAAgC;AAChC,qCAAuC;AACvC,yCAAoC;AAEpC,MAAa,WAAY,SAAQ,aAAI,CAAC,WAAW,CAAC,aAAa,CAG7D;CAAG;AAHL,kCAGK;AAEL,MAAM,oBAAoB,GAAG,eAAM,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,eAAM,CAAC,MAAM;IACxB,kBAAkB,EAAE,eAAM,CAAC,MAAM;CAClC,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,eAAM,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,eAAM,CAAC,KAAK,CAAC,eAAM,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,eAAM,CAAC,KAAK,CAAC,eAAM,CAAC,MAAM,CAAC;CAClC,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,eAAM,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,oBAAoB;IAC3B,qBAAqB,EAAE,eAAM,CAAC,YAAY,CAAC,eAAM,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IAChF,iBAAiB,EAAE,eAAM,CAAC,YAAY,CAAC,eAAM,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;CAC9E,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,eAAM,CAAC,MAAM,CAAC;IACpC,QAAQ,EAAE,oBAAoB;IAC9B,SAAS,EAAE,qBAAqB;CACjC,CAAC,CAAC;AAIH,MAAa,aAAc,SAAQ,eAAM,CAAC,OAAO,EAAiB,CAChE,eAAe,EACf;IACE,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAA,kBAAQ,EAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAW,IAAI,CAAC,QAAQ,CAAC,IAAI,qBAAqB,CAAC;QACnE,MAAM,YAAY,GAAG,IAAA,mBAAO,EAAC,UAAU,CAAC,CAAC;QAEzC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,GAAG,CAAC;YAC5B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAA,sBAAY,EAAC,YAAY,EAAE,OAAO,CAAC;YAC9C,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,WAAW,CAAC;gBACd,OAAO,EAAE,0BAA0B,YAAY,EAAE;gBACjD,KAAK;aACN,CAAC;SACL,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,GAAG,CAAC;YAC7B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY;YACrC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,KAAK,EAAE,CAAC;SACvE,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACpE,eAAM,CAAC,QAAQ,CACb,CAAC,KAAK,EAAE,EAAE,CACR,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,0BAA0B,EAAE,KAAK,EAAE,CAAC,CAClE,CACF,CAAC;QAEF,OAAO;YACL,MAAM;SACP,CAAC;IACJ,CAAC,CAAC;CACH,CACF;CAAG;AApCJ,sCAoCI"}
@@ -0,0 +1,21 @@
1
+ import { Effect } from 'effect';
2
+ import admin from 'firebase-admin';
3
+ import { ConfigService } from '../config';
4
+ declare const FirebaseInitError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
5
+ readonly _tag: "FirebaseInitError";
6
+ } & Readonly<A>;
7
+ export declare class FirebaseInitError extends FirebaseInitError_base<{
8
+ readonly message: string;
9
+ readonly cause?: unknown;
10
+ }> {
11
+ }
12
+ declare const FirebaseService_base: Effect.Service.Class<FirebaseService, "FirebaseService", {
13
+ readonly accessors: true;
14
+ readonly effect: Effect.Effect<{
15
+ firestore: () => admin.firestore.Firestore;
16
+ }, FirebaseInitError, ConfigService>;
17
+ }>;
18
+ export declare class FirebaseService extends FirebaseService_base {
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/firebase/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAGnC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;;;;AAE1C,qBAAa,iBAAkB,SAAQ,uBAAsC;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG;;;;;;;AAEL,qBAAa,eAAgB,SAAQ,oBAqCpC;CAAG"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FirebaseService = exports.FirebaseInitError = void 0;
7
+ const effect_1 = require("effect");
8
+ const firebase_admin_1 = __importDefault(require("firebase-admin"));
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const config_1 = require("../config");
12
+ class FirebaseInitError extends effect_1.Data.TaggedError('FirebaseInitError') {
13
+ }
14
+ exports.FirebaseInitError = FirebaseInitError;
15
+ class FirebaseService extends effect_1.Effect.Service()('FirebaseService', {
16
+ accessors: true,
17
+ effect: effect_1.Effect.gen(function* () {
18
+ const config = yield* config_1.ConfigService.config;
19
+ const serviceAccountPath = (0, node_path_1.resolve)(config.firebase.serviceAccountPath);
20
+ const serviceAccount = yield* effect_1.Effect.try({
21
+ try: () => JSON.parse((0, node_fs_1.readFileSync)(serviceAccountPath, 'utf-8')),
22
+ catch: (cause) => new FirebaseInitError({
23
+ message: `Service account not found: ${serviceAccountPath}`,
24
+ cause,
25
+ }),
26
+ });
27
+ yield* effect_1.Effect.try({
28
+ try: () => {
29
+ firebase_admin_1.default.initializeApp({
30
+ credential: firebase_admin_1.default.credential.cert(serviceAccount),
31
+ projectId: config.firebase.projectId,
32
+ });
33
+ },
34
+ catch: (cause) => new FirebaseInitError({ message: `Firebase init failed`, cause }),
35
+ });
36
+ const db = firebase_admin_1.default.firestore();
37
+ return { firestore: () => db };
38
+ }),
39
+ }) {
40
+ }
41
+ exports.FirebaseService = FirebaseService;
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/firebase/index.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAsC;AACtC,oEAAmC;AACnC,qCAAuC;AACvC,yCAAoC;AACpC,sCAA0C;AAE1C,MAAa,iBAAkB,SAAQ,aAAI,CAAC,WAAW,CAAC,mBAAmB,CAGzE;CAAG;AAHL,8CAGK;AAEL,MAAa,eAAgB,SAAQ,eAAM,CAAC,OAAO,EAAmB,CACpE,iBAAiB,EACjB;IACE,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,sBAAa,CAAC,MAAM,CAAC;QAE3C,MAAM,kBAAkB,GAAG,IAAA,mBAAO,EAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAEvE,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,GAAG,CAAC;YACvC,GAAG,EAAE,GAAG,EAAE,CACR,IAAI,CAAC,KAAK,CACR,IAAA,sBAAY,EAAC,kBAAkB,EAAE,OAAO,CAAC,CAClB;YAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,iBAAiB,CAAC;gBACpB,OAAO,EAAE,8BAA8B,kBAAkB,EAAE;gBAC3D,KAAK;aACN,CAAC;SACL,CAAC,CAAC;QAEH,KAAK,CAAC,CAAC,eAAM,CAAC,GAAG,CAAC;YAChB,GAAG,EAAE,GAAG,EAAE;gBACR,wBAAK,CAAC,aAAa,CAAC;oBAClB,UAAU,EAAE,wBAAK,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC;oBACjD,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS;iBACrC,CAAC,CAAC;YACL,CAAC;YACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC;SACpE,CAAC,CAAC;QAEH,MAAM,EAAE,GAAG,wBAAK,CAAC,SAAS,EAAE,CAAC;QAE7B,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IACjC,CAAC,CAAC;CACH,CACF;CAAG;AArCJ,0CAqCI"}
@@ -0,0 +1,23 @@
1
+ import { Effect, Layer } from 'effect';
2
+ import { AccessService } from '../access';
3
+ import { ConfigService } from '../config';
4
+ import { FirebaseService } from '../firebase';
5
+ declare const McpServerError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "McpServerError";
7
+ } & Readonly<A>;
8
+ export declare class McpServerError extends McpServerError_base<{
9
+ readonly message: string;
10
+ readonly cause?: unknown;
11
+ }> {
12
+ }
13
+ declare const McpServerService_base: Effect.Service.Class<McpServerService, "McpServerService", {
14
+ readonly accessors: true;
15
+ readonly dependencies: readonly [Layer.Layer<FirebaseService, import("../config").ConfigError | import("../firebase").FirebaseInitError, never>, Layer.Layer<AccessService, import("../config").ConfigError, never>, Layer.Layer<ConfigService, import("../config").ConfigError, never>];
16
+ readonly effect: Effect.Effect<{
17
+ start(): Effect.Effect<void, McpServerError, never>;
18
+ }, never, ConfigService | AccessService | FirebaseService>;
19
+ }>;
20
+ export declare class McpServerService extends McpServerService_base {
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAW,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;;;;AAG9C,qBAAa,cAAe,SAAQ,oBAAmC;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG;;;;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBA+DrC;CAAG"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.McpServerService = exports.McpServerError = void 0;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const effect_1 = require("effect");
8
+ const access_1 = require("../access");
9
+ const config_1 = require("../config");
10
+ const firebase_1 = require("../firebase");
11
+ const tools_1 = require("../tools");
12
+ class McpServerError extends effect_1.Data.TaggedError('McpServerError') {
13
+ }
14
+ exports.McpServerError = McpServerError;
15
+ class McpServerService extends effect_1.Effect.Service()('McpServerService', {
16
+ accessors: true,
17
+ dependencies: [
18
+ effect_1.Layer.provide(firebase_1.FirebaseService.Default, config_1.ConfigService.Default),
19
+ effect_1.Layer.provide(access_1.AccessService.Default, config_1.ConfigService.Default),
20
+ config_1.ConfigService.Default,
21
+ ],
22
+ effect: effect_1.Effect.gen(function* () {
23
+ const runtime = yield* effect_1.Effect.runtime();
24
+ const runPromise = effect_1.Runtime.runPromise(runtime);
25
+ const context = yield* effect_1.Effect.context();
26
+ const mcpServer = new mcp_js_1.McpServer({ name: 'firebase-mcp', version: '0.1.0' }, { capabilities: { tools: {} } });
27
+ mcpServer.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
28
+ tools: tools_1.allToolDefinitions,
29
+ }));
30
+ mcpServer.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
31
+ const args = request.params.arguments ?? {};
32
+ return runPromise((0, tools_1.dispatchTool)(request.params.name, args).pipe(effect_1.Effect.provide(context))).catch((cause) => ({
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: JSON.stringify({
37
+ success: false,
38
+ error: { code: 'INTERNAL_ERROR', message: String(cause) },
39
+ }),
40
+ },
41
+ ],
42
+ isError: true,
43
+ }));
44
+ });
45
+ return {
46
+ start() {
47
+ return effect_1.Effect.tryPromise({
48
+ try: () => mcpServer.server.connect(new stdio_js_1.StdioServerTransport()),
49
+ catch: (cause) => new McpServerError({
50
+ message: 'Failed to connect stdio transport',
51
+ cause,
52
+ }),
53
+ });
54
+ },
55
+ };
56
+ }),
57
+ }) {
58
+ }
59
+ exports.McpServerService = McpServerService;
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;AAAA,oEAAoE;AACpE,wEAAiF;AACjF,iEAG4C;AAC5C,mCAAsD;AACtD,sCAA0C;AAC1C,sCAA0C;AAC1C,0CAA8C;AAC9C,oCAA4D;AAE5D,MAAa,cAAe,SAAQ,aAAI,CAAC,WAAW,CAAC,gBAAgB,CAGnE;CAAG;AAHL,wCAGK;AAEL,MAAa,gBAAiB,SAAQ,eAAM,CAAC,OAAO,EAAoB,CACtE,kBAAkB,EAClB;IACE,SAAS,EAAE,IAAI;IACf,YAAY,EAAE;QACZ,cAAK,CAAC,OAAO,CAAC,0BAAe,CAAC,OAAO,EAAE,sBAAa,CAAC,OAAO,CAAC;QAC7D,cAAK,CAAC,OAAO,CAAC,sBAAa,CAAC,OAAO,EAAE,sBAAa,CAAC,OAAO,CAAC;QAC3D,sBAAa,CAAC,OAAO;KACtB;IACD,MAAM,EAAE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,gBAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAE/C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,OAAO,EAElC,CAAC;QAEJ,MAAM,SAAS,GAAG,IAAI,kBAAS,CAC7B,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,EAC1C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;QAEF,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YACtE,KAAK,EAAE,0BAAkB;SAC1B,CAAC,CAAC,CAAC;QAEJ,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAChC,gCAAqB,EACrB,KAAK,EAAE,OAAO,EAAE,EAAE;YAChB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;YAC5C,OAAO,UAAU,CACf,IAAA,oBAAY,EAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CAC1C,eAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CACxB,CACF,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAClB,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,OAAO,EAAE,KAAK;4BACd,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE;yBAC1D,CAAC;qBACH;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC,CAAC,CAAC;QACN,CAAC,CACF,CAAC;QAEF,OAAO;YACL,KAAK;gBACH,OAAO,eAAM,CAAC,UAAU,CAAC;oBACvB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,+BAAoB,EAAE,CAAC;oBAC/D,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,cAAc,CAAC;wBACjB,OAAO,EAAE,mCAAmC;wBAC5C,KAAK;qBACN,CAAC;iBACL,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;CACH,CACF;CAAG;AA/DJ,4CA+DI"}
@@ -0,0 +1,34 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import { Effect } from 'effect';
3
+ import { AggregateField } from 'firebase-admin/firestore';
4
+ import { AccessService } from '../../access';
5
+ import { FirebaseService } from '../../firebase';
6
+ import { QueryFilter } from './types';
7
+ declare const FirestoreAggregateError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
8
+ readonly _tag: "FirestoreAggregateError";
9
+ } & Readonly<A>;
10
+ export declare class FirestoreAggregateError extends FirestoreAggregateError_base<{
11
+ readonly message: string;
12
+ readonly cause?: unknown;
13
+ }> {
14
+ }
15
+ export declare const AGGREGATE_COLLECTION: "aggregate_collection";
16
+ export interface AggregationSpec {
17
+ alias: string;
18
+ type: 'sum' | 'avg' | 'count';
19
+ field?: string;
20
+ }
21
+ export interface AggregateCollectionArgs {
22
+ collection: string;
23
+ aggregations: AggregationSpec[];
24
+ filters?: QueryFilter[];
25
+ }
26
+ export declare const aggregateCollectionDefinition: Tool;
27
+ export declare const aggregateCollection: (input: AggregateCollectionArgs) => Effect.Effect<{
28
+ collection: string;
29
+ result: FirebaseFirestore.AggregateSpecData<{
30
+ [k: string]: AggregateField<number>;
31
+ }>;
32
+ }, import("../../access").AccessDeniedError | FirestoreAggregateError, AccessService | FirebaseService>;
33
+ export {};
34
+ //# sourceMappingURL=aggregate_collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate_collection.d.ts","sourceRoot":"","sources":["../../../src/tools/firestore/aggregate_collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC1D,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAsB,WAAW,EAAE,MAAM,SAAS,CAAC;;;;AAE1D,qBAAa,uBAAwB,SAAQ,6BAE3C;IACA,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG;AAEL,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,6BAA6B,EAAE,IA6C3C,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,uBAAuB;;;;;uGAwC9D,CAAC"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.aggregateCollection = exports.aggregateCollectionDefinition = exports.AGGREGATE_COLLECTION = exports.FirestoreAggregateError = void 0;
4
+ const effect_1 = require("effect");
5
+ const firestore_1 = require("firebase-admin/firestore");
6
+ const access_1 = require("../../access");
7
+ const firebase_1 = require("../../firebase");
8
+ const types_1 = require("./types");
9
+ class FirestoreAggregateError extends effect_1.Data.TaggedError('FirestoreAggregateError') {
10
+ }
11
+ exports.FirestoreAggregateError = FirestoreAggregateError;
12
+ exports.AGGREGATE_COLLECTION = 'aggregate_collection';
13
+ exports.aggregateCollectionDefinition = {
14
+ name: exports.AGGREGATE_COLLECTION,
15
+ description: 'Run native server-side sum(), avg(), and count() aggregations over a Firestore collection without fetching documents. Supports optional where-clause filters.',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ collection: {
20
+ type: 'string',
21
+ description: "Collection path, e.g. 'orders' or 'stores/abc/orders'",
22
+ },
23
+ aggregations: {
24
+ type: 'array',
25
+ description: 'One or more aggregations to compute in a single round-trip',
26
+ items: {
27
+ type: 'object',
28
+ properties: {
29
+ alias: {
30
+ type: 'string',
31
+ description: 'Key name for this result in the response, e.g. "totalRevenue"',
32
+ },
33
+ type: {
34
+ type: 'string',
35
+ enum: ['sum', 'avg', 'count'],
36
+ description: '"sum" and "avg" require a field; "count" does not',
37
+ },
38
+ field: {
39
+ type: 'string',
40
+ description: 'Field path to aggregate (required for sum and avg)',
41
+ },
42
+ },
43
+ required: ['alias', 'type'],
44
+ },
45
+ minItems: 1,
46
+ },
47
+ filters: {
48
+ type: 'array',
49
+ description: 'Optional where-clause filters to narrow the aggregation',
50
+ items: types_1.FILTER_SCHEMA_ITEM,
51
+ },
52
+ },
53
+ required: ['collection', 'aggregations'],
54
+ },
55
+ };
56
+ const aggregateCollection = (input) => effect_1.Effect.gen(function* () {
57
+ const access = yield* access_1.AccessService;
58
+ yield* access.check(input.collection);
59
+ const { firestore } = yield* firebase_1.FirebaseService;
60
+ const result = yield* effect_1.Effect.tryPromise({
61
+ try: () => {
62
+ let query = firestore().collection(input.collection);
63
+ for (const filter of input.filters ?? []) {
64
+ query = query.where(filter.field, filter.operator, filter.value);
65
+ }
66
+ const spec = Object.fromEntries(input.aggregations.map((agg) => {
67
+ if (agg.type === 'count')
68
+ return [agg.alias, firestore_1.AggregateField.count()];
69
+ if (agg.type === 'sum')
70
+ return [agg.alias, firestore_1.AggregateField.sum(agg.field)];
71
+ return [agg.alias, firestore_1.AggregateField.average(agg.field)];
72
+ }));
73
+ return query
74
+ .aggregate(spec)
75
+ .get()
76
+ .then((snap) => snap.data());
77
+ },
78
+ catch: (cause) => new FirestoreAggregateError({
79
+ message: `Failed to aggregate collection: ${input.collection}`,
80
+ cause,
81
+ }),
82
+ });
83
+ return { collection: input.collection, result };
84
+ });
85
+ exports.aggregateCollection = aggregateCollection;
86
+ //# sourceMappingURL=aggregate_collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate_collection.js","sourceRoot":"","sources":["../../../src/tools/firestore/aggregate_collection.ts"],"names":[],"mappings":";;;AACA,mCAAsC;AACtC,wDAA0D;AAE1D,yCAA6C;AAC7C,6CAAiD;AACjD,mCAA0D;AAE1D,MAAa,uBAAwB,SAAQ,aAAI,CAAC,WAAW,CAC3D,yBAAyB,CAIzB;CAAG;AALL,0DAKK;AAEQ,QAAA,oBAAoB,GAAG,sBAA+B,CAAC;AAcvD,QAAA,6BAA6B,GAAS;IACjD,IAAI,EAAE,4BAAoB;IAC1B,WAAW,EACT,+JAA+J;IACjK,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,UAAU,EAAE;gBACV,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,uDAAuD;aACrE;YACD,YAAY,EAAE;gBACZ,IAAI,EAAE,OAAO;gBACb,WAAW,EACT,4DAA4D;gBAC9D,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,+DAA+D;yBAClE;wBACD,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC;4BAC7B,WAAW,EAAE,mDAAmD;yBACjE;wBACD,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,oDAAoD;yBAClE;qBACF;oBACD,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;iBAC5B;gBACD,QAAQ,EAAE,CAAC;aACZ;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,OAAO;gBACb,WAAW,EAAE,yDAAyD;gBACtE,KAAK,EAAE,0BAAkB;aAC1B;SACF;QACD,QAAQ,EAAE,CAAC,YAAY,EAAE,cAAc,CAAC;KACzC;CACF,CAAC;AAEK,MAAM,mBAAmB,GAAG,CAAC,KAA8B,EAAE,EAAE,CACpE,eAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,sBAAa,CAAC;IACpC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAEtC,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,0BAAe,CAAC;IAE7C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,eAAM,CAAC,UAAU,CAAC;QACtC,GAAG,EAAE,GAAG,EAAE;YACR,IAAI,KAAK,GAA4B,SAAS,EAAE,CAAC,UAAU,CACzD,KAAK,CAAC,UAAU,CACjB,CAAC;YAEF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;gBACzC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACnE,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAC7B,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC7B,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;oBACtB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,0BAAc,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC7C,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;oBACpB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,0BAAc,CAAC,GAAG,CAAC,GAAG,CAAC,KAAM,CAAC,CAAC,CAAC;gBACrD,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,0BAAc,CAAC,OAAO,CAAC,GAAG,CAAC,KAAM,CAAC,CAAC,CAAC;YACzD,CAAC,CAAC,CACH,CAAC;YAEF,OAAO,KAAK;iBACT,SAAS,CAAC,IAAI,CAAC;iBACf,GAAG,EAAE;iBACL,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,uBAAuB,CAAC;YAC1B,OAAO,EAAE,mCAAmC,KAAK,CAAC,UAAU,EAAE;YAC9D,KAAK;SACN,CAAC;KACL,CAAC,CAAC;IAEH,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;AAClD,CAAC,CAAC,CAAC;AAxCQ,QAAA,mBAAmB,uBAwC3B"}