@theihtisham/mcp-server-firebase 1.0.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +79 -0
  5. package/dist/services/firebase.d.ts +14 -0
  6. package/dist/services/firebase.js +163 -0
  7. package/dist/tools/auth.d.ts +3 -0
  8. package/dist/tools/auth.js +346 -0
  9. package/dist/tools/firestore.d.ts +3 -0
  10. package/dist/tools/firestore.js +802 -0
  11. package/dist/tools/functions.d.ts +3 -0
  12. package/dist/tools/functions.js +168 -0
  13. package/dist/tools/index.d.ts +10 -0
  14. package/dist/tools/index.js +30 -0
  15. package/dist/tools/messaging.d.ts +3 -0
  16. package/dist/tools/messaging.js +296 -0
  17. package/dist/tools/realtime-db.d.ts +4 -0
  18. package/dist/tools/realtime-db.js +271 -0
  19. package/dist/tools/storage.d.ts +3 -0
  20. package/dist/tools/storage.js +279 -0
  21. package/dist/tools/types.d.ts +11 -0
  22. package/dist/tools/types.js +3 -0
  23. package/dist/utils/cache.d.ts +16 -0
  24. package/dist/utils/cache.js +75 -0
  25. package/dist/utils/errors.d.ts +15 -0
  26. package/dist/utils/errors.js +94 -0
  27. package/dist/utils/index.d.ts +5 -0
  28. package/dist/utils/index.js +37 -0
  29. package/dist/utils/pagination.d.ts +28 -0
  30. package/dist/utils/pagination.js +75 -0
  31. package/dist/utils/validation.d.ts +22 -0
  32. package/dist/utils/validation.js +172 -0
  33. package/package.json +53 -0
  34. package/src/index.ts +94 -0
  35. package/src/services/firebase.ts +140 -0
  36. package/src/tools/auth.ts +375 -0
  37. package/src/tools/firestore.ts +931 -0
  38. package/src/tools/functions.ts +189 -0
  39. package/src/tools/index.ts +24 -0
  40. package/src/tools/messaging.ts +324 -0
  41. package/src/tools/realtime-db.ts +307 -0
  42. package/src/tools/storage.ts +314 -0
  43. package/src/tools/types.ts +10 -0
  44. package/src/utils/cache.ts +82 -0
  45. package/src/utils/errors.ts +110 -0
  46. package/src/utils/index.ts +4 -0
  47. package/src/utils/pagination.ts +105 -0
  48. package/src/utils/validation.ts +212 -0
  49. package/tests/cache.test.ts +139 -0
  50. package/tests/errors.test.ts +132 -0
  51. package/tests/firebase-service.test.ts +46 -0
  52. package/tests/pagination.test.ts +26 -0
  53. package/tests/tools.test.ts +226 -0
  54. package/tests/validation.test.ts +216 -0
  55. package/tsconfig.json +26 -0
  56. package/vitest.config.ts +15 -0
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authTools = void 0;
4
+ const firebase_js_1 = require("../services/firebase.js");
5
+ const index_js_1 = require("../utils/index.js");
6
+ // ============================================================
7
+ // AUTH TOOLS
8
+ // ============================================================
9
+ exports.authTools = [
10
+ // ── auth_create_user ──────────────────────────────────
11
+ {
12
+ name: 'auth_create_user',
13
+ description: 'Create a new Firebase Authentication user. At minimum provide email or phoneNumber.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ email: { type: 'string', description: 'User email address' },
18
+ emailVerified: { type: 'boolean', description: 'Whether email is verified (default: false)' },
19
+ phoneNumber: { type: 'string', description: 'Phone number in E.164 format (e.g., "+15555550100")' },
20
+ password: { type: 'string', description: 'Password (min 6 characters)' },
21
+ displayName: { type: 'string', description: 'Display name' },
22
+ photoURL: { type: 'string', description: 'Photo URL' },
23
+ disabled: { type: 'boolean', description: 'Whether account is disabled (default: false)' },
24
+ uid: { type: 'string', description: 'Custom UID (auto-generated if not provided)' },
25
+ },
26
+ },
27
+ handler: async (args) => {
28
+ try {
29
+ const auth = (0, firebase_js_1.getAuth)();
30
+ const createOptions = {};
31
+ if (args['email']) {
32
+ createOptions.email = (0, index_js_1.validateEmail)(args['email']);
33
+ }
34
+ if (args['emailVerified'] !== undefined) {
35
+ createOptions.emailVerified = args['emailVerified'];
36
+ }
37
+ if (args['phoneNumber']) {
38
+ createOptions.phoneNumber = args['phoneNumber'].trim();
39
+ }
40
+ if (args['password']) {
41
+ const password = args['password'];
42
+ if (password.length < 6) {
43
+ throw new Error('Password must be at least 6 characters long.');
44
+ }
45
+ createOptions.password = password;
46
+ }
47
+ if (args['displayName']) {
48
+ createOptions.displayName = args['displayName'].trim();
49
+ }
50
+ if (args['photoURL']) {
51
+ createOptions.photoURL = args['photoURL'].trim();
52
+ }
53
+ if (args['disabled'] !== undefined) {
54
+ createOptions.disabled = args['disabled'];
55
+ }
56
+ if (args['uid']) {
57
+ createOptions.uid = (0, index_js_1.validateUid)(args['uid']);
58
+ }
59
+ const userRecord = await auth.createUser(createOptions);
60
+ return (0, index_js_1.formatSuccess)({
61
+ uid: userRecord.uid,
62
+ email: userRecord.email,
63
+ phoneNumber: userRecord.phoneNumber,
64
+ displayName: userRecord.displayName,
65
+ photoURL: userRecord.photoURL,
66
+ disabled: userRecord.disabled,
67
+ emailVerified: userRecord.emailVerified,
68
+ metadata: {
69
+ creationTime: userRecord.metadata.creationTime,
70
+ lastSignInTime: userRecord.metadata.lastSignInTime,
71
+ },
72
+ });
73
+ }
74
+ catch (err) {
75
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'create_user');
76
+ }
77
+ },
78
+ },
79
+ // ── auth_get_user ─────────────────────────────────────
80
+ {
81
+ name: 'auth_get_user',
82
+ description: 'Get a Firebase user by UID, email, or phone number.',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ uid: { type: 'string', description: 'User UID' },
87
+ email: { type: 'string', description: 'User email (alternative to uid)' },
88
+ phoneNumber: { type: 'string', description: 'User phone number (alternative to uid)' },
89
+ },
90
+ },
91
+ handler: async (args) => {
92
+ try {
93
+ const auth = (0, firebase_js_1.getAuth)();
94
+ let userRecord;
95
+ if (args['uid']) {
96
+ userRecord = await auth.getUser((0, index_js_1.validateUid)(args['uid']));
97
+ }
98
+ else if (args['email']) {
99
+ userRecord = await auth.getUserByEmail((0, index_js_1.validateEmail)(args['email']));
100
+ }
101
+ else if (args['phoneNumber']) {
102
+ userRecord = await auth.getUserByPhoneNumber(args['phoneNumber'].trim());
103
+ }
104
+ else {
105
+ throw new Error('Provide at least one of: uid, email, or phoneNumber.');
106
+ }
107
+ return (0, index_js_1.formatSuccess)({
108
+ uid: userRecord.uid,
109
+ email: userRecord.email,
110
+ phoneNumber: userRecord.phoneNumber,
111
+ displayName: userRecord.displayName,
112
+ photoURL: userRecord.photoURL,
113
+ disabled: userRecord.disabled,
114
+ emailVerified: userRecord.emailVerified,
115
+ customClaims: userRecord.customClaims,
116
+ tenantId: userRecord.tenantId,
117
+ providerData: userRecord.providerData.map((p) => ({
118
+ providerId: p.providerId,
119
+ uid: p.uid,
120
+ email: p.email,
121
+ displayName: p.displayName,
122
+ phoneNumber: p.phoneNumber,
123
+ photoURL: p.photoURL,
124
+ })),
125
+ metadata: {
126
+ creationTime: userRecord.metadata.creationTime,
127
+ lastSignInTime: userRecord.metadata.lastSignInTime,
128
+ },
129
+ });
130
+ }
131
+ catch (err) {
132
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'get_user');
133
+ }
134
+ },
135
+ },
136
+ // ── auth_list_users ───────────────────────────────────
137
+ {
138
+ name: 'auth_list_users',
139
+ description: 'List Firebase Auth users with pagination. Returns user summaries.',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ pageSize: { type: 'number', description: 'Results per page (1-1000, default 100)' },
144
+ pageToken: { type: 'string', description: 'Next page token from previous result' },
145
+ },
146
+ },
147
+ handler: async (args) => {
148
+ try {
149
+ const auth = (0, firebase_js_1.getAuth)();
150
+ const pageSize = (0, index_js_1.validatePageSize)(args['pageSize'] || 100);
151
+ const pageToken = args['pageToken'];
152
+ const result = await auth.listUsers(pageSize, pageToken);
153
+ const users = result.users.map((u) => ({
154
+ uid: u.uid,
155
+ email: u.email,
156
+ displayName: u.displayName,
157
+ disabled: u.disabled,
158
+ emailVerified: u.emailVerified,
159
+ creationTime: u.metadata.creationTime,
160
+ lastSignInTime: u.metadata.lastSignInTime,
161
+ }));
162
+ return (0, index_js_1.formatListResult)(users, result.pageToken);
163
+ }
164
+ catch (err) {
165
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'list_users');
166
+ }
167
+ },
168
+ },
169
+ // ── auth_update_user ──────────────────────────────────
170
+ {
171
+ name: 'auth_update_user',
172
+ description: 'Update an existing Firebase user. Only provided fields are updated.',
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ uid: { type: 'string', description: 'User UID to update' },
177
+ email: { type: 'string', description: 'New email address' },
178
+ emailVerified: { type: 'boolean', description: 'Email verification status' },
179
+ phoneNumber: { type: 'string', description: 'New phone number (null to remove)' },
180
+ password: { type: 'string', description: 'New password (min 6 characters)' },
181
+ displayName: { type: 'string', description: 'New display name' },
182
+ photoURL: { type: 'string', description: 'New photo URL' },
183
+ disabled: { type: 'boolean', description: 'Whether to disable the account' },
184
+ },
185
+ required: ['uid'],
186
+ },
187
+ handler: async (args) => {
188
+ try {
189
+ const auth = (0, firebase_js_1.getAuth)();
190
+ const uid = (0, index_js_1.validateUid)(args['uid']);
191
+ const updateOptions = {};
192
+ if (args['email'] !== undefined) {
193
+ updateOptions.email = (0, index_js_1.validateEmail)(args['email']);
194
+ }
195
+ if (args['emailVerified'] !== undefined) {
196
+ updateOptions.emailVerified = args['emailVerified'];
197
+ }
198
+ if (args['phoneNumber'] !== undefined) {
199
+ updateOptions.phoneNumber = args['phoneNumber'];
200
+ }
201
+ if (args['password'] !== undefined) {
202
+ const password = args['password'];
203
+ if (password.length < 6) {
204
+ throw new Error('Password must be at least 6 characters long.');
205
+ }
206
+ updateOptions.password = password;
207
+ }
208
+ if (args['displayName'] !== undefined) {
209
+ updateOptions.displayName = args['displayName'];
210
+ }
211
+ if (args['photoURL'] !== undefined) {
212
+ updateOptions.photoURL = args['photoURL'];
213
+ }
214
+ if (args['disabled'] !== undefined) {
215
+ updateOptions.disabled = args['disabled'];
216
+ }
217
+ const userRecord = await auth.updateUser(uid, updateOptions);
218
+ return (0, index_js_1.formatSuccess)({
219
+ uid: userRecord.uid,
220
+ email: userRecord.email,
221
+ displayName: userRecord.displayName,
222
+ photoURL: userRecord.photoURL,
223
+ disabled: userRecord.disabled,
224
+ emailVerified: userRecord.emailVerified,
225
+ message: `User "${uid}" updated successfully.`,
226
+ });
227
+ }
228
+ catch (err) {
229
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'update_user');
230
+ }
231
+ },
232
+ },
233
+ // ── auth_delete_user ──────────────────────────────────
234
+ {
235
+ name: 'auth_delete_user',
236
+ description: 'Delete a Firebase user by UID.',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ uid: { type: 'string', description: 'User UID to delete' },
241
+ },
242
+ required: ['uid'],
243
+ },
244
+ handler: async (args) => {
245
+ try {
246
+ const auth = (0, firebase_js_1.getAuth)();
247
+ const uid = (0, index_js_1.validateUid)(args['uid']);
248
+ await auth.deleteUser(uid);
249
+ return (0, index_js_1.formatSuccess)({
250
+ uid,
251
+ message: `User "${uid}" deleted successfully.`,
252
+ });
253
+ }
254
+ catch (err) {
255
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'delete_user');
256
+ }
257
+ },
258
+ },
259
+ // ── auth_verify_token ─────────────────────────────────
260
+ {
261
+ name: 'auth_verify_token',
262
+ description: 'Verify a Firebase ID token and return the decoded token with user info.',
263
+ inputSchema: {
264
+ type: 'object',
265
+ properties: {
266
+ idToken: { type: 'string', description: 'Firebase ID token to verify' },
267
+ checkRevoked: { type: 'boolean', description: 'Check if token was revoked (default: true)' },
268
+ },
269
+ required: ['idToken'],
270
+ },
271
+ handler: async (args) => {
272
+ try {
273
+ const auth = (0, firebase_js_1.getAuth)();
274
+ const idToken = args['idToken'].trim();
275
+ const checkRevoked = args['checkRevoked'] ?? true;
276
+ if (idToken.length === 0) {
277
+ throw new Error('ID token cannot be empty.');
278
+ }
279
+ const decodedToken = await auth.verifyIdToken(idToken, checkRevoked);
280
+ return (0, index_js_1.formatSuccess)({
281
+ uid: decodedToken.uid,
282
+ email: decodedToken.email,
283
+ emailVerified: decodedToken.email_verified,
284
+ name: decodedToken.name,
285
+ picture: decodedToken.picture,
286
+ iss: decodedToken.iss,
287
+ aud: decodedToken.aud,
288
+ authTime: new Date(decodedToken.auth_time * 1000).toISOString(),
289
+ issuedAt: new Date(decodedToken.iat * 1000).toISOString(),
290
+ expiresAt: new Date(decodedToken.exp * 1000).toISOString(),
291
+ signInProvider: decodedToken.firebase?.sign_in_provider,
292
+ valid: true,
293
+ });
294
+ }
295
+ catch (err) {
296
+ const error = err;
297
+ return (0, index_js_1.formatSuccess)({
298
+ valid: false,
299
+ error: error.message,
300
+ suggestion: 'Ensure the token is a valid, non-expired Firebase ID token.',
301
+ });
302
+ }
303
+ },
304
+ },
305
+ // ── auth_set_custom_claims ────────────────────────────
306
+ {
307
+ name: 'auth_set_custom_claims',
308
+ description: 'Set custom claims on a user for role-based access control.',
309
+ inputSchema: {
310
+ type: 'object',
311
+ properties: {
312
+ uid: { type: 'string', description: 'User UID' },
313
+ customClaims: { type: 'object', description: 'Custom claims object (max 1000 bytes serialized)' },
314
+ },
315
+ required: ['uid', 'customClaims'],
316
+ },
317
+ handler: async (args) => {
318
+ try {
319
+ const auth = (0, firebase_js_1.getAuth)();
320
+ const uid = (0, index_js_1.validateUid)(args['uid']);
321
+ const customClaims = args['customClaims'];
322
+ const claimsSize = Buffer.byteLength(JSON.stringify(customClaims));
323
+ if (claimsSize > 1000) {
324
+ throw new Error(`Custom claims exceed 1000 bytes (got ${claimsSize}). ` +
325
+ 'Reduce the number or size of claims.');
326
+ }
327
+ const reservedKeys = ['iss', 'aud', 'auth_time', 'sub', 'iat', 'exp', 'email', 'email_verified', 'firebase'];
328
+ for (const key of Object.keys(customClaims)) {
329
+ if (reservedKeys.includes(key)) {
330
+ throw new Error(`Cannot set reserved claim key: "${key}".`);
331
+ }
332
+ }
333
+ await auth.setCustomUserClaims(uid, customClaims);
334
+ return (0, index_js_1.formatSuccess)({
335
+ uid,
336
+ customClaims,
337
+ message: `Custom claims set for user "${uid}". Claims take effect on next token refresh.`,
338
+ });
339
+ }
340
+ catch (err) {
341
+ (0, index_js_1.handleFirebaseError)(err, 'auth', 'set_custom_claims');
342
+ }
343
+ },
344
+ },
345
+ ];
346
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from './types.js';
2
+ export declare const firestoreTools: ToolDefinition[];
3
+ //# sourceMappingURL=firestore.d.ts.map