@gcnv/gcnv-mcp-server 1.0.3

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 (33) hide show
  1. package/GEMINI.md +200 -0
  2. package/LICENSE +201 -0
  3. package/README.md +374 -0
  4. package/build/index.js +185 -0
  5. package/build/logger.js +19 -0
  6. package/build/registry/register-tools.js +101 -0
  7. package/build/registry/tool-registry.js +27 -0
  8. package/build/tools/active-directory-tools.js +124 -0
  9. package/build/tools/backup-policy-tools.js +140 -0
  10. package/build/tools/backup-tools.js +178 -0
  11. package/build/tools/backup-vault-tools.js +147 -0
  12. package/build/tools/handlers/active-directory-handler.js +321 -0
  13. package/build/tools/handlers/backup-handler.js +451 -0
  14. package/build/tools/handlers/backup-policy-handler.js +275 -0
  15. package/build/tools/handlers/backup-vault-handler.js +370 -0
  16. package/build/tools/handlers/kms-config-handler.js +327 -0
  17. package/build/tools/handlers/operation-handler.js +254 -0
  18. package/build/tools/handlers/quota-rule-handler.js +411 -0
  19. package/build/tools/handlers/replication-handler.js +504 -0
  20. package/build/tools/handlers/snapshot-handler.js +320 -0
  21. package/build/tools/handlers/storage-pool-handler.js +346 -0
  22. package/build/tools/handlers/volume-handler.js +353 -0
  23. package/build/tools/kms-config-tools.js +162 -0
  24. package/build/tools/operation-tools.js +64 -0
  25. package/build/tools/quota-rule-tools.js +166 -0
  26. package/build/tools/replication-tools.js +227 -0
  27. package/build/tools/snapshot-tools.js +124 -0
  28. package/build/tools/storage-pool-tools.js +215 -0
  29. package/build/tools/volume-tools.js +216 -0
  30. package/build/types/tool.js +1 -0
  31. package/build/utils/netapp-client-factory.js +53 -0
  32. package/gemini-extension.json +12 -0
  33. package/package.json +66 -0
@@ -0,0 +1,451 @@
1
+ import { NetAppClientFactory } from '../../utils/netapp-client-factory.js';
2
+ import { logger } from '../../logger.js';
3
+ const log = logger.child({ module: 'backup-handler' });
4
+ // Helper to format backup data for responses
5
+ function formatBackupData(backup) {
6
+ const result = {};
7
+ if (!backup)
8
+ return result;
9
+ if (backup.name) {
10
+ // Extract backupId from name (last part after last slash)
11
+ const nameParts = backup.name.split('/');
12
+ result.name = backup.name;
13
+ result.backupId = nameParts[nameParts.length - 1];
14
+ // Extract backupVaultId from name
15
+ const backupVaultMatch = backup.name.match(/\/backupVaults\/([^/]+)\/backups\//);
16
+ if (backupVaultMatch && backupVaultMatch[1]) {
17
+ result.backupVaultId = backupVaultMatch[1];
18
+ }
19
+ }
20
+ // Map source volume
21
+ if (backup.sourceVolume) {
22
+ result.sourceVolume = backup.sourceVolume; // Map sourceName to sourceVolume for schema consistency
23
+ }
24
+ // Copy basic properties
25
+ if (backup.state)
26
+ result.state = backup.state;
27
+ // Map volume usage bytes
28
+ result.volumeUsagebytes = backup.volumeUsagebytes; // Keep original for compatibility
29
+ // Format timestamps if they exist
30
+ if (backup.createTime) {
31
+ result.createTime = new Date(backup.createTime.seconds * 1000);
32
+ }
33
+ // Copy optional properties according to schema
34
+ if (backup.description)
35
+ result.description = backup.description;
36
+ if (backup.backupType)
37
+ result.backupType = backup.backupType;
38
+ result.chainStoragebytes = backup.chainStoragebytes || 0;
39
+ if (backup.satisfiesPzs !== undefined)
40
+ result.satisfiesPzs = backup.satisfiesPzs;
41
+ if (backup.satisfiesPzi !== undefined)
42
+ result.satisfiesPzi = backup.satisfiesPzi;
43
+ if (backup.volumeRegion)
44
+ result.volumeRegion = backup.volumeRegion;
45
+ if (backup.backupRegion)
46
+ result.backupRegion = backup.backupRegion;
47
+ if (backup.enforcedRetentionEndTime)
48
+ result.enforcedRetentionEndTime = backup.enforcedRetentionEndTime;
49
+ result.sourceSnapshot = backup.sourceSnapshot;
50
+ if (backup.labels)
51
+ result.labels = backup.labels;
52
+ return result;
53
+ }
54
+ // Create Backup Handler
55
+ export const createBackupHandler = async (args) => {
56
+ try {
57
+ const { projectId, location, backupVaultId, backupId, sourceVolumeName, backupRegion, enforcedRetentionEndTime, description, labels, } = args;
58
+ // Create a new NetApp client using the factory
59
+ const netAppClient = NetAppClientFactory.createClient();
60
+ // Format the parent path for the backup
61
+ const parent = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}`;
62
+ // Construct the backup name
63
+ const backupName = `${parent}/backups/${backupId}`;
64
+ // Create the backup request
65
+ const request = {
66
+ parent,
67
+ backupId,
68
+ backup: {
69
+ name: backupName,
70
+ sourceVolume: sourceVolumeName,
71
+ backupRegion,
72
+ enforcedRetentionEndTime,
73
+ description,
74
+ labels,
75
+ },
76
+ };
77
+ log.info({ request }, 'Create Backup request');
78
+ // Create the backup
79
+ const [operation] = await netAppClient.createBackup(request);
80
+ // Extract the operation name for tracking
81
+ const operationName = operation.name;
82
+ log.info({ operationName }, 'Backup creation operation started');
83
+ return {
84
+ content: [
85
+ {
86
+ type: 'text',
87
+ text: `${operationName}`,
88
+ },
89
+ ],
90
+ structuredContent: {
91
+ name: backupName,
92
+ operationId: operationName,
93
+ },
94
+ };
95
+ }
96
+ catch (error) {
97
+ log.error({ err: error }, 'Error creating backup');
98
+ let errorMessage = `Failed to create backup: ${error.message}`;
99
+ // Handle specific error types and provide useful error messages
100
+ if (error.code === 6) {
101
+ // ALREADY_EXISTS
102
+ errorMessage = `Backup ${args.backupId} already exists in backup vault ${args.backupVaultId}`;
103
+ }
104
+ else if (error.code === 7) {
105
+ // PERMISSION_DENIED
106
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
107
+ }
108
+ else if (error.code === 5) {
109
+ // NOT_FOUND
110
+ errorMessage = `Backup vault or volume not found`;
111
+ }
112
+ else if (error.code === 3) {
113
+ // INVALID_ARGUMENT
114
+ errorMessage = `Invalid argument: ${error.message}`;
115
+ }
116
+ return {
117
+ isError: true,
118
+ content: [
119
+ {
120
+ type: 'text',
121
+ text: errorMessage,
122
+ },
123
+ ],
124
+ };
125
+ }
126
+ };
127
+ // Delete Backup Handler
128
+ export const deleteBackupHandler = async (args) => {
129
+ try {
130
+ const { projectId, location, backupVaultId, backupId } = args;
131
+ // Create a new NetApp client using the factory
132
+ const netAppClient = NetAppClientFactory.createClient();
133
+ // Format the name for the backup
134
+ const name = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}/backups/${backupId}`;
135
+ // Delete the backup
136
+ const [operation] = await netAppClient.deleteBackup({
137
+ name,
138
+ });
139
+ return {
140
+ content: [
141
+ {
142
+ type: 'text',
143
+ text: `Backup deletion initiated. Operation ID: ${operation.name}`,
144
+ },
145
+ ],
146
+ structuredContent: {
147
+ success: true,
148
+ operationId: operation.name,
149
+ },
150
+ };
151
+ }
152
+ catch (error) {
153
+ log.error({ err: error }, 'Error deleting backup');
154
+ let errorMessage = `Failed to delete backup: ${error.message}`;
155
+ // Handle specific error types
156
+ if (error.code === 5) {
157
+ // NOT_FOUND
158
+ errorMessage = `Backup not found: projects/${args.projectId}/locations/${args.location}/backupVaults/${args.backupVaultId}/backups/${args.backupId}`;
159
+ }
160
+ else if (error.code === 7) {
161
+ // PERMISSION_DENIED
162
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
163
+ }
164
+ return {
165
+ isError: true,
166
+ content: [
167
+ {
168
+ type: 'text',
169
+ text: errorMessage,
170
+ },
171
+ ],
172
+ };
173
+ }
174
+ };
175
+ // Get Backup Handler
176
+ export const getBackupHandler = async (args) => {
177
+ try {
178
+ const { projectId, location, backupVaultId, backupId } = args;
179
+ // Create a new NetApp client using the factory
180
+ const netAppClient = NetAppClientFactory.createClient();
181
+ // Format the name for the backup
182
+ const name = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}/backups/${backupId}`;
183
+ // Get the backup
184
+ const [backup] = await netAppClient.getBackup({ name });
185
+ log.info({ backup }, 'Raw backup data');
186
+ // Format the backup data
187
+ const formattedData = formatBackupData(backup);
188
+ log.info({ formattedData }, 'Formatted backup data');
189
+ // Ensure all required fields are present
190
+ if (!formattedData.state)
191
+ formattedData.state = 'UNKNOWN';
192
+ if (!formattedData.sourceVolume) {
193
+ // Create a default source volume name based on the backup name pattern
194
+ formattedData.sourceVolume = `projects/${projectId}/locations/${location}/storagePools/unknown/volumes/unknown`;
195
+ }
196
+ return {
197
+ content: [
198
+ {
199
+ type: 'text',
200
+ text: JSON.stringify(formattedData, null, 2),
201
+ },
202
+ ],
203
+ structuredContent: formattedData,
204
+ };
205
+ }
206
+ catch (error) {
207
+ log.error({ err: error }, 'Error getting backup');
208
+ let errorMessage = `Failed to get backup: ${error.message}`;
209
+ // Handle specific error types
210
+ if (error.code === 5) {
211
+ // NOT_FOUND
212
+ errorMessage = `Backup not found: projects/${args.projectId}/locations/${args.location}/backupVaults/${args.backupVaultId}/backups/${args.backupId}`;
213
+ }
214
+ else if (error.code === 7) {
215
+ // PERMISSION_DENIED
216
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
217
+ }
218
+ return {
219
+ isError: true,
220
+ content: [
221
+ {
222
+ type: 'text',
223
+ text: errorMessage,
224
+ },
225
+ ],
226
+ };
227
+ }
228
+ };
229
+ // List Backups Handler
230
+ export const listBackupsHandler = async (args) => {
231
+ try {
232
+ const { projectId, location, backupVaultId, filter, pageSize, pageToken } = args;
233
+ // Create a new NetApp client using the factory
234
+ const netAppClient = NetAppClientFactory.createClient();
235
+ // Format the parent path for listing backups
236
+ const parent = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}`;
237
+ // List the backups
238
+ const options = {
239
+ parent,
240
+ filter,
241
+ pageSize,
242
+ pageToken,
243
+ };
244
+ const [backups, , nextPageToken] = await netAppClient.listBackups(options);
245
+ log.info({ backups }, 'Raw backups data');
246
+ const formattedBackups = backups.map((backup) => formatBackupData(backup));
247
+ log.info({ formattedBackups }, 'Formatted backups data');
248
+ return {
249
+ content: [
250
+ {
251
+ type: 'text',
252
+ text: JSON.stringify(formattedBackups, null, 2),
253
+ },
254
+ ],
255
+ structuredContent: {
256
+ backups: formattedBackups,
257
+ nextPageToken: nextPageToken || undefined,
258
+ },
259
+ };
260
+ }
261
+ catch (error) {
262
+ log.error({ err: error }, 'Error listing backups');
263
+ let errorMessage = `Failed to list backups: ${error.message}`;
264
+ // Handle specific error types
265
+ if (error.code === 5) {
266
+ // NOT_FOUND
267
+ errorMessage = `Backup vault not found: projects/${args.projectId}/locations/${args.location}/backupVaults/${args.backupVaultId}`;
268
+ }
269
+ else if (error.code === 7) {
270
+ // PERMISSION_DENIED
271
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
272
+ }
273
+ else if (error.code === 3) {
274
+ // INVALID_ARGUMENT
275
+ errorMessage = `Invalid argument: ${error.message}`;
276
+ }
277
+ return {
278
+ isError: true,
279
+ content: [
280
+ {
281
+ type: 'text',
282
+ text: errorMessage,
283
+ },
284
+ ],
285
+ };
286
+ }
287
+ };
288
+ // Restore Backup Handler
289
+ export const restoreBackupHandler = async (args) => {
290
+ try {
291
+ const { projectId, location, backupVaultId, backupId, targetStoragePoolId, targetVolumeId, restoreOption, } = args;
292
+ // Create a new NetApp client using the factory
293
+ const netAppClient = NetAppClientFactory.createClient();
294
+ // Format the name for the backup
295
+ const name = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}/backups/${backupId}`;
296
+ // Format the target volume name
297
+ const targetVolumeName = `projects/${projectId}/locations/${location}/storagePools/${targetStoragePoolId}/volumes/${targetVolumeId}`;
298
+ // Create restore options
299
+ let requestOptions = {};
300
+ if (restoreOption === 'CREATE_NEW_VOLUME') {
301
+ requestOptions = {
302
+ targetVolumeName: targetVolumeName,
303
+ };
304
+ }
305
+ else if (restoreOption === 'OVERWRITE_EXISTING_VOLUME') {
306
+ requestOptions = {
307
+ targetVolumeName: targetVolumeName,
308
+ overwriteExistingVolume: true,
309
+ };
310
+ }
311
+ // Get the available methods from the client for debugging
312
+ log.debug({
313
+ methods: Object.keys(netAppClient).filter((k) => typeof netAppClient[k] === 'function'),
314
+ }, 'Available NetApp client methods');
315
+ // Attempt to use the backup client - we'll use a safe approach with any to avoid compile errors
316
+ // and log a proper error if the method doesn't exist
317
+ let operation;
318
+ try {
319
+ // Try the method that seems most likely
320
+ const client = netAppClient;
321
+ if (typeof client.restoreBackup === 'function') {
322
+ [operation] = await client.restoreBackup({
323
+ name,
324
+ ...requestOptions,
325
+ });
326
+ }
327
+ else if (typeof client.restoreVolumeBackup === 'function') {
328
+ [operation] = await client.restoreVolumeBackup({
329
+ name,
330
+ ...requestOptions,
331
+ });
332
+ }
333
+ else {
334
+ throw new Error('restoreBackup method not found on NetApp client');
335
+ }
336
+ }
337
+ catch (restoreError) {
338
+ log.error({ err: restoreError }, 'Error in restore operation');
339
+ throw restoreError;
340
+ }
341
+ return {
342
+ content: [
343
+ {
344
+ type: 'text',
345
+ text: `Backup restore initiated. Operation ID: ${operation.name}`,
346
+ },
347
+ ],
348
+ structuredContent: {
349
+ name: targetVolumeName,
350
+ operationId: operation.name,
351
+ },
352
+ };
353
+ }
354
+ catch (error) {
355
+ log.error({ err: error }, 'Error restoring backup');
356
+ let errorMessage = `Failed to restore backup: ${error.message}`;
357
+ // Handle specific error types
358
+ if (error.code === 5) {
359
+ // NOT_FOUND
360
+ errorMessage = `Backup or target storage pool not found`;
361
+ }
362
+ else if (error.code === 7) {
363
+ // PERMISSION_DENIED
364
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
365
+ }
366
+ else if (error.code === 6) {
367
+ // ALREADY_EXISTS
368
+ errorMessage = `Target volume already exists and overwrite option was not selected`;
369
+ }
370
+ else if (error.code === 9) {
371
+ // FAILED_PRECONDITION
372
+ errorMessage = `Failed precondition: ${error.message}`;
373
+ }
374
+ return {
375
+ isError: true,
376
+ content: [
377
+ {
378
+ type: 'text',
379
+ text: errorMessage,
380
+ },
381
+ ],
382
+ };
383
+ }
384
+ };
385
+ // Update Backup Handler
386
+ export const updateBackupHandler = async (args) => {
387
+ try {
388
+ const { projectId, location, backupVaultId, backupId, description, labels } = args;
389
+ // Create a new NetApp client using the factory
390
+ const netAppClient = NetAppClientFactory.createClient();
391
+ // Format the name for the backup
392
+ const name = `projects/${projectId}/locations/${location}/backupVaults/${backupVaultId}/backups/${backupId}`;
393
+ // Prepare the update mask based on provided fields
394
+ const updateMask = [];
395
+ const backup = { name };
396
+ if (description !== undefined) {
397
+ backup.description = description;
398
+ updateMask.push('description');
399
+ }
400
+ if (labels !== undefined) {
401
+ backup.labels = labels;
402
+ updateMask.push('labels');
403
+ }
404
+ // Call the API to update the backup
405
+ const request = {
406
+ backup,
407
+ updateMask: {
408
+ paths: updateMask,
409
+ },
410
+ };
411
+ log.info({ request }, 'Update Backup request');
412
+ const [operation] = await netAppClient.updateBackup(request);
413
+ log.info({ operation }, 'Update Backup operation');
414
+ return {
415
+ content: [
416
+ {
417
+ type: 'text',
418
+ text: JSON.stringify({
419
+ message: `Backup '${backupId}' update operation started`,
420
+ operation: operation,
421
+ }, null, 2),
422
+ },
423
+ ],
424
+ structuredContent: {
425
+ name: name,
426
+ operationId: operation.name || '',
427
+ },
428
+ };
429
+ }
430
+ catch (error) {
431
+ log.error({ err: error }, 'Error updating backup');
432
+ let errorMessage = `Failed to update backup: ${error.message}`;
433
+ if (error.code === 5) {
434
+ // NOT_FOUND
435
+ errorMessage = `Backup not found: projects/${args.projectId}/locations/${args.location}/backupVaults/${args.backupVaultId}/backups/${args.backupId}`;
436
+ }
437
+ else if (error.code === 7) {
438
+ // PERMISSION_DENIED
439
+ errorMessage = 'Permission denied. Please check your credentials and access rights.';
440
+ }
441
+ return {
442
+ isError: true,
443
+ content: [
444
+ {
445
+ type: 'text',
446
+ text: errorMessage,
447
+ },
448
+ ],
449
+ };
450
+ }
451
+ };