@exposify/firebase 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,178 @@
1
+ import { AuthResolver, WebMcpHandler } from '@exposify/core';
2
+ export { HostedAuthResolverConfig, completeMcpLogin, createHostedAuthResolver, hostedBroker } from '@exposify/core';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+
5
+ /** What `onRequest` hands its handler: Express's req/res, with the body already read. */
6
+ type FirebaseRequest = IncomingMessage & {
7
+ body?: unknown;
8
+ rawBody?: Buffer;
9
+ originalUrl?: string;
10
+ };
11
+ type NodeHandler = (req: FirebaseRequest, res: ServerResponse) => Promise<void>;
12
+ /**
13
+ * Adapts a web-standard handler to Node's `(req, res)`. Cloud Functions has
14
+ * already drained the request stream by the time the handler runs (it sets
15
+ * `rawBody` and a parsed `body`), so the body comes from those; a plain Node
16
+ * server's untouched stream is read directly. The response body is streamed
17
+ * chunk by chunk, since MCP answers over SSE.
18
+ */
19
+ declare function toNodeHandler(handle: (req: Request) => Promise<Response>): NodeHandler;
20
+
21
+ /**
22
+ * The JSON Schema subset accepted for `fields` and `functions[].input`
23
+ * (same as `@exposify/supabase`'s), plus `format: "date-time"`, which makes
24
+ * a string field a Firestore timestamp on writes and in filters.
25
+ */
26
+ interface JsonSchemaProperty {
27
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object";
28
+ description?: string;
29
+ format?: "date-time";
30
+ enum?: unknown[];
31
+ items?: JsonSchemaProperty;
32
+ properties?: Record<string, JsonSchemaProperty>;
33
+ required?: string[];
34
+ }
35
+ interface JsonSchemaObject {
36
+ type?: "object";
37
+ properties?: Record<string, JsonSchemaProperty>;
38
+ required?: string[];
39
+ }
40
+ interface CollectionConfig {
41
+ /** Becomes the tools' description. Say what a document is, for the agent. */
42
+ description?: string;
43
+ /**
44
+ * The document's fields. Optional: without it, filters accept any field
45
+ * name and writes accept any object. With it, filters and `select` are
46
+ * limited to these names and writes are typed.
47
+ */
48
+ fields?: Record<string, JsonSchemaProperty>;
49
+ /** Field names a `create_*` call must include. */
50
+ required?: string[];
51
+ /**
52
+ * The field holding the owning user's uid, for the common
53
+ * `resource.data.<owner> == request.auth.uid` rule: `list_*` filters on
54
+ * it, `create_*` sets it, `update_*` refuses to change it. A convenience,
55
+ * not a security boundary: your rules still decide.
56
+ */
57
+ owner?: string;
58
+ }
59
+ /** A registered callable function (`onCall`), invoked with the caller's ID token. */
60
+ interface FunctionToolConfig {
61
+ description: string;
62
+ input?: JsonSchemaObject;
63
+ /** Full URL override, e.g. a custom domain or a run.app URL. */
64
+ url?: string;
65
+ }
66
+ type WriteOp = "create" | "update" | "delete";
67
+ interface ExposifyFirebaseOptions {
68
+ /**
69
+ * The Firestore collections agents can use, keyed by collection path.
70
+ * Subcollections use `{param}` placeholders: `"users/{userId}/notes"`.
71
+ * A plain array is shorthand for `{ name: {} }` each.
72
+ */
73
+ collections?: Record<string, CollectionConfig> | string[];
74
+ /** `"<collection>.create"` / `.update` / `.delete` / `.*`. Reads need no opt-in. */
75
+ writes?: string[];
76
+ /** Callable functions (`onCall`) exposed as `call_<name>` tools. */
77
+ functions?: Record<string, FunctionToolConfig>;
78
+ /** Doc globs, relative to the functions source directory. Default: `["docs/**\/*.md"]`. */
79
+ docs?: string[];
80
+ /** Default `"public"`. `"required"` hides `search_docs` from callers with no token. */
81
+ docsAuth?: "public" | "required";
82
+ /** Cap on documents per `list_*` call regardless of the caller's `limit`. Default 100. */
83
+ maxDocs?: number;
84
+ /** Default: `GCLOUD_PROJECT` / `GOOGLE_CLOUD_PROJECT` / `FIREBASE_CONFIG.projectId`, set in every deployed function. */
85
+ projectId?: string;
86
+ /** Firestore database id. Default `"(default)"`. */
87
+ databaseId?: string;
88
+ /** Region of the callable functions in `functions`. Default `"us-central1"`. */
89
+ functionsRegion?: string;
90
+ /**
91
+ * Resolves the caller's headers into the headers used for Firestore and
92
+ * callable calls, e.g. `hostedBroker()` (Exposify's hosted broker, from `EXPOSIFY_KEY`).
93
+ * Only `authorization` from the result is used.
94
+ */
95
+ auth?: AuthResolver;
96
+ /**
97
+ * With `auth` set: 401 when the resolver returns no `authorization`, instead
98
+ * of running unauthenticated. Default true with the hosted broker (`EXPOSIFY_KEY`
99
+ * set, or `auth: hostedBroker()`), which also answers tokenless requests with
100
+ * Connect's sign-in challenge; false otherwise.
101
+ */
102
+ authRequired?: boolean;
103
+ /** The cold-start "MCP ready" line. Default: on, except under `NODE_ENV=test`. Failures always log. */
104
+ startupLog?: boolean;
105
+ /** Injectable for tests; defaults to global `fetch`. */
106
+ fetchImpl?: typeof fetch;
107
+ /** Overrides `process.env` (tests). */
108
+ env?: Record<string, string | undefined>;
109
+ /** Directory doc globs resolve against. Default `process.cwd()` (the functions source directory when deployed). */
110
+ docsRoot?: string;
111
+ rateLimit?: number;
112
+ rateWindowMs?: number;
113
+ }
114
+ interface ResolvedCollection {
115
+ /** The configured key, e.g. `"users/{userId}/notes"`. */
116
+ key: string;
117
+ /** Path segments; odd positions are collection ids, even positions are `{param}` or literal doc ids. */
118
+ segments: string[];
119
+ /** `{param}` names in order. */
120
+ params: string[];
121
+ /** Tool-name suffix, e.g. `"users_notes"`. */
122
+ suffix: string;
123
+ config: CollectionConfig;
124
+ writes: Set<WriteOp>;
125
+ }
126
+ interface ResolvedFirebaseConfig {
127
+ collections: ResolvedCollection[];
128
+ functions: Record<string, FunctionToolConfig>;
129
+ docs: string[];
130
+ docsAuth: "public" | "required";
131
+ maxDocs: number;
132
+ databaseId: string;
133
+ functionsRegion: string;
134
+ rateLimit?: number;
135
+ rateWindowMs?: number;
136
+ }
137
+
138
+ /**
139
+ * Collection keys are Firestore collection paths with `{param}` placeholders
140
+ * in document positions: `"todos"`, `"users/{userId}/notes"`. A key has an
141
+ * odd number of segments (it names a collection, not a document); collection
142
+ * ids (even indexes) must be literal.
143
+ */
144
+ interface ParsedKey {
145
+ segments: string[];
146
+ params: string[];
147
+ suffix: string;
148
+ }
149
+ /** Throws with a message naming the problem; config.ts turns that into a warning. */
150
+ declare function parseCollectionKey(key: string): ParsedKey;
151
+
152
+ /**
153
+ * The MCP endpoint for `onRequest`:
154
+ *
155
+ * ```ts
156
+ * export const mcp = onRequest(aiReady({ collections: { todos: { owner: "uid" } } }));
157
+ * ```
158
+ */
159
+ declare function aiReady(options?: ExposifyFirebaseOptions): NodeHandler;
160
+ /**
161
+ * The same endpoint as a web-standard `Request → Response` handler, for
162
+ * runtimes other than `onRequest` and for tests. Docs are read and indexed
163
+ * on the first request and cached for the instance's lifetime.
164
+ */
165
+ declare function createFirebaseMcpHandler(options?: ExposifyFirebaseOptions): WebMcpHandler;
166
+ /**
167
+ * The function's public URL from the URL a request arrived at. Behind
168
+ * `<region>-<project>.cloudfunctions.net/<name>` the function sees the path
169
+ * without its own name, so the name goes back in; a `run.app` URL already is
170
+ * the function's root.
171
+ */
172
+ declare function functionPublicUrl(own: string, functionName: string | undefined): string;
173
+ /** `https://<region>-<project>.cloudfunctions.net/<name>`, or the Functions emulator's URL. */
174
+ declare function callableUrl(name: string, projectId: string, region: string, env: Record<string, string | undefined>): string;
175
+ /** "7 tools (2 list, 2 get, 2 write, 1 callable)": only the kinds present. */
176
+ declare function firebaseToolsPhrase(cfg: ResolvedFirebaseConfig, hasDocs: boolean): string;
177
+
178
+ export { type CollectionConfig, type ExposifyFirebaseOptions, type FirebaseRequest, type FunctionToolConfig, type JsonSchemaObject, type JsonSchemaProperty, type NodeHandler, aiReady, callableUrl, createFirebaseMcpHandler, firebaseToolsPhrase, functionPublicUrl, parseCollectionKey, toNodeHandler };
@@ -0,0 +1,178 @@
1
+ import { AuthResolver, WebMcpHandler } from '@exposify/core';
2
+ export { HostedAuthResolverConfig, completeMcpLogin, createHostedAuthResolver, hostedBroker } from '@exposify/core';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+
5
+ /** What `onRequest` hands its handler: Express's req/res, with the body already read. */
6
+ type FirebaseRequest = IncomingMessage & {
7
+ body?: unknown;
8
+ rawBody?: Buffer;
9
+ originalUrl?: string;
10
+ };
11
+ type NodeHandler = (req: FirebaseRequest, res: ServerResponse) => Promise<void>;
12
+ /**
13
+ * Adapts a web-standard handler to Node's `(req, res)`. Cloud Functions has
14
+ * already drained the request stream by the time the handler runs (it sets
15
+ * `rawBody` and a parsed `body`), so the body comes from those; a plain Node
16
+ * server's untouched stream is read directly. The response body is streamed
17
+ * chunk by chunk, since MCP answers over SSE.
18
+ */
19
+ declare function toNodeHandler(handle: (req: Request) => Promise<Response>): NodeHandler;
20
+
21
+ /**
22
+ * The JSON Schema subset accepted for `fields` and `functions[].input`
23
+ * (same as `@exposify/supabase`'s), plus `format: "date-time"`, which makes
24
+ * a string field a Firestore timestamp on writes and in filters.
25
+ */
26
+ interface JsonSchemaProperty {
27
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object";
28
+ description?: string;
29
+ format?: "date-time";
30
+ enum?: unknown[];
31
+ items?: JsonSchemaProperty;
32
+ properties?: Record<string, JsonSchemaProperty>;
33
+ required?: string[];
34
+ }
35
+ interface JsonSchemaObject {
36
+ type?: "object";
37
+ properties?: Record<string, JsonSchemaProperty>;
38
+ required?: string[];
39
+ }
40
+ interface CollectionConfig {
41
+ /** Becomes the tools' description. Say what a document is, for the agent. */
42
+ description?: string;
43
+ /**
44
+ * The document's fields. Optional: without it, filters accept any field
45
+ * name and writes accept any object. With it, filters and `select` are
46
+ * limited to these names and writes are typed.
47
+ */
48
+ fields?: Record<string, JsonSchemaProperty>;
49
+ /** Field names a `create_*` call must include. */
50
+ required?: string[];
51
+ /**
52
+ * The field holding the owning user's uid, for the common
53
+ * `resource.data.<owner> == request.auth.uid` rule: `list_*` filters on
54
+ * it, `create_*` sets it, `update_*` refuses to change it. A convenience,
55
+ * not a security boundary: your rules still decide.
56
+ */
57
+ owner?: string;
58
+ }
59
+ /** A registered callable function (`onCall`), invoked with the caller's ID token. */
60
+ interface FunctionToolConfig {
61
+ description: string;
62
+ input?: JsonSchemaObject;
63
+ /** Full URL override, e.g. a custom domain or a run.app URL. */
64
+ url?: string;
65
+ }
66
+ type WriteOp = "create" | "update" | "delete";
67
+ interface ExposifyFirebaseOptions {
68
+ /**
69
+ * The Firestore collections agents can use, keyed by collection path.
70
+ * Subcollections use `{param}` placeholders: `"users/{userId}/notes"`.
71
+ * A plain array is shorthand for `{ name: {} }` each.
72
+ */
73
+ collections?: Record<string, CollectionConfig> | string[];
74
+ /** `"<collection>.create"` / `.update` / `.delete` / `.*`. Reads need no opt-in. */
75
+ writes?: string[];
76
+ /** Callable functions (`onCall`) exposed as `call_<name>` tools. */
77
+ functions?: Record<string, FunctionToolConfig>;
78
+ /** Doc globs, relative to the functions source directory. Default: `["docs/**\/*.md"]`. */
79
+ docs?: string[];
80
+ /** Default `"public"`. `"required"` hides `search_docs` from callers with no token. */
81
+ docsAuth?: "public" | "required";
82
+ /** Cap on documents per `list_*` call regardless of the caller's `limit`. Default 100. */
83
+ maxDocs?: number;
84
+ /** Default: `GCLOUD_PROJECT` / `GOOGLE_CLOUD_PROJECT` / `FIREBASE_CONFIG.projectId`, set in every deployed function. */
85
+ projectId?: string;
86
+ /** Firestore database id. Default `"(default)"`. */
87
+ databaseId?: string;
88
+ /** Region of the callable functions in `functions`. Default `"us-central1"`. */
89
+ functionsRegion?: string;
90
+ /**
91
+ * Resolves the caller's headers into the headers used for Firestore and
92
+ * callable calls, e.g. `hostedBroker()` (Exposify's hosted broker, from `EXPOSIFY_KEY`).
93
+ * Only `authorization` from the result is used.
94
+ */
95
+ auth?: AuthResolver;
96
+ /**
97
+ * With `auth` set: 401 when the resolver returns no `authorization`, instead
98
+ * of running unauthenticated. Default true with the hosted broker (`EXPOSIFY_KEY`
99
+ * set, or `auth: hostedBroker()`), which also answers tokenless requests with
100
+ * Connect's sign-in challenge; false otherwise.
101
+ */
102
+ authRequired?: boolean;
103
+ /** The cold-start "MCP ready" line. Default: on, except under `NODE_ENV=test`. Failures always log. */
104
+ startupLog?: boolean;
105
+ /** Injectable for tests; defaults to global `fetch`. */
106
+ fetchImpl?: typeof fetch;
107
+ /** Overrides `process.env` (tests). */
108
+ env?: Record<string, string | undefined>;
109
+ /** Directory doc globs resolve against. Default `process.cwd()` (the functions source directory when deployed). */
110
+ docsRoot?: string;
111
+ rateLimit?: number;
112
+ rateWindowMs?: number;
113
+ }
114
+ interface ResolvedCollection {
115
+ /** The configured key, e.g. `"users/{userId}/notes"`. */
116
+ key: string;
117
+ /** Path segments; odd positions are collection ids, even positions are `{param}` or literal doc ids. */
118
+ segments: string[];
119
+ /** `{param}` names in order. */
120
+ params: string[];
121
+ /** Tool-name suffix, e.g. `"users_notes"`. */
122
+ suffix: string;
123
+ config: CollectionConfig;
124
+ writes: Set<WriteOp>;
125
+ }
126
+ interface ResolvedFirebaseConfig {
127
+ collections: ResolvedCollection[];
128
+ functions: Record<string, FunctionToolConfig>;
129
+ docs: string[];
130
+ docsAuth: "public" | "required";
131
+ maxDocs: number;
132
+ databaseId: string;
133
+ functionsRegion: string;
134
+ rateLimit?: number;
135
+ rateWindowMs?: number;
136
+ }
137
+
138
+ /**
139
+ * Collection keys are Firestore collection paths with `{param}` placeholders
140
+ * in document positions: `"todos"`, `"users/{userId}/notes"`. A key has an
141
+ * odd number of segments (it names a collection, not a document); collection
142
+ * ids (even indexes) must be literal.
143
+ */
144
+ interface ParsedKey {
145
+ segments: string[];
146
+ params: string[];
147
+ suffix: string;
148
+ }
149
+ /** Throws with a message naming the problem; config.ts turns that into a warning. */
150
+ declare function parseCollectionKey(key: string): ParsedKey;
151
+
152
+ /**
153
+ * The MCP endpoint for `onRequest`:
154
+ *
155
+ * ```ts
156
+ * export const mcp = onRequest(aiReady({ collections: { todos: { owner: "uid" } } }));
157
+ * ```
158
+ */
159
+ declare function aiReady(options?: ExposifyFirebaseOptions): NodeHandler;
160
+ /**
161
+ * The same endpoint as a web-standard `Request → Response` handler, for
162
+ * runtimes other than `onRequest` and for tests. Docs are read and indexed
163
+ * on the first request and cached for the instance's lifetime.
164
+ */
165
+ declare function createFirebaseMcpHandler(options?: ExposifyFirebaseOptions): WebMcpHandler;
166
+ /**
167
+ * The function's public URL from the URL a request arrived at. Behind
168
+ * `<region>-<project>.cloudfunctions.net/<name>` the function sees the path
169
+ * without its own name, so the name goes back in; a `run.app` URL already is
170
+ * the function's root.
171
+ */
172
+ declare function functionPublicUrl(own: string, functionName: string | undefined): string;
173
+ /** `https://<region>-<project>.cloudfunctions.net/<name>`, or the Functions emulator's URL. */
174
+ declare function callableUrl(name: string, projectId: string, region: string, env: Record<string, string | undefined>): string;
175
+ /** "7 tools (2 list, 2 get, 2 write, 1 callable)": only the kinds present. */
176
+ declare function firebaseToolsPhrase(cfg: ResolvedFirebaseConfig, hasDocs: boolean): string;
177
+
178
+ export { type CollectionConfig, type ExposifyFirebaseOptions, type FirebaseRequest, type FunctionToolConfig, type JsonSchemaObject, type JsonSchemaProperty, type NodeHandler, aiReady, callableUrl, createFirebaseMcpHandler, firebaseToolsPhrase, functionPublicUrl, parseCollectionKey, toNodeHandler };