@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,353 @@
1
+ import { NetAppClientFactory } from '../../utils/netapp-client-factory.js';
2
+ import { logger } from '../../logger.js';
3
+ const log = logger.child({ module: 'volume-handler' });
4
+ // Helper to format volume data for responses
5
+ function formatVolumeData(volume) {
6
+ const result = {};
7
+ if (!volume)
8
+ return result;
9
+ if (volume.name) {
10
+ // Extract volumeId from name (last part after last slash)
11
+ const nameParts = volume.name.split('/');
12
+ result.name = volume.name;
13
+ result.volumeId = nameParts[nameParts.length - 1];
14
+ }
15
+ // Extract storage pool from name
16
+ if (volume.storagePool) {
17
+ result.storagePool = volume.storagePool;
18
+ }
19
+ // Copy basic properties
20
+ if (volume.capacityGib)
21
+ result.capacityGib = Number(volume.capacityGib);
22
+ if (volume.usedGib)
23
+ result.usedGib = Number(volume.usedGib);
24
+ if (volume.state)
25
+ result.state = volume.state;
26
+ if (volume.stateDetails)
27
+ result.stateDetails = volume.stateDetails;
28
+ if (volume.shareName)
29
+ result.shareName = volume.shareName;
30
+ if (volume.protocols)
31
+ result.protocols = volume.protocols;
32
+ if (volume.serviceLevel)
33
+ result.serviceLevel = volume.serviceLevel;
34
+ if (volume.network)
35
+ result.network = volume.network;
36
+ if (volume.psaRange)
37
+ result.psaRange = volume.psaRange;
38
+ if (volume.securityStyle)
39
+ result.securityStyle = volume.securityStyle;
40
+ // Format timestamps if they exist
41
+ if (volume.createTime) {
42
+ result.createTime = new Date(volume.createTime.seconds * 1000);
43
+ }
44
+ // Copy optional properties
45
+ if (volume.description)
46
+ result.description = volume.description;
47
+ if (volume.labels)
48
+ result.labels = volume.labels;
49
+ if (volume.unixPermissions)
50
+ result.unixPermissions = volume.unixPermissions;
51
+ if (volume.kmsConfig)
52
+ result.kmsConfig = volume.kmsConfig;
53
+ if (volume.encryptionType)
54
+ result.encryptionType = volume.encryptionType;
55
+ if (volume.backupConfig)
56
+ result.backupConfig = volume.backupConfig;
57
+ if (volume.tieringPolicy)
58
+ result.tieringPolicy = volume.tieringPolicy;
59
+ if (volume.throughputMibps !== undefined)
60
+ result.throughputMibps = volume.throughputMibps;
61
+ if (volume.replicaZone)
62
+ result.replicaZone = volume.replicaZone;
63
+ if (volume.zone)
64
+ result.zone = volume.zone;
65
+ if (volume.coldTierSizeGib !== undefined)
66
+ result.coldTierSizeGib = Number(volume.coldTierSizeGib);
67
+ if (volume.hotTierSizeUsedGib !== undefined)
68
+ result.hotTierSizeUsedGib = Number(volume.hotTierSizeUsedGib);
69
+ if (volume.largeCapacity !== undefined)
70
+ result.largeCapacity = volume.largeCapacity;
71
+ if (volume.multipleEndpoints !== undefined)
72
+ result.multipleEndpoints = volume.multipleEndpoints;
73
+ if (volume.hasReplication !== undefined)
74
+ result.hasReplication = volume.hasReplication;
75
+ if (volume.restrictedActions)
76
+ result.restrictedActions = volume.restrictedActions;
77
+ // Format mount points
78
+ if (volume.mountOptions && volume.mountOptions.length > 0) {
79
+ result.mountOptions = volume.mountOptions.map((mp) => ({
80
+ protocol: mp.protocol || '',
81
+ ipAddress: mp.ipAddress || '',
82
+ export: mp.export || '',
83
+ exportFull: mp.exportFull || '',
84
+ }));
85
+ }
86
+ // Surface export policy if present
87
+ if (volume.exportPolicy)
88
+ result.exportPolicy = volume.exportPolicy;
89
+ // Surface SMB settings if present
90
+ if (volume.smbSettings)
91
+ result.smbSettings = volume.smbSettings;
92
+ return result;
93
+ }
94
+ // Create Volume Handler
95
+ export const createVolumeHandler = async (args) => {
96
+ try {
97
+ const { projectId, location, storagePoolId, volumeId, capacityGib, protocols, description, labels, backupConfig, exportPolicy, shareName, } = args;
98
+ // Create a new NetApp client using the factory
99
+ const netAppClient = NetAppClientFactory.createClient();
100
+ // Format the parent path for the volume
101
+ const parent = `projects/${projectId}/locations/${location}`;
102
+ // Create the volume request
103
+ const request = {
104
+ parent,
105
+ volumeId,
106
+ volume: {
107
+ storagePool: storagePoolId,
108
+ capacityGib,
109
+ protocols: protocols || ['NFS3'],
110
+ description,
111
+ labels,
112
+ backupConfig,
113
+ shareName: shareName || volumeId,
114
+ exportPolicy,
115
+ },
116
+ };
117
+ log.info({ request }, 'Create Volume request');
118
+ // Call the API to create a volume
119
+ const [operation] = await netAppClient.createVolume(request);
120
+ log.info({ operation }, 'Create Volume operation');
121
+ return {
122
+ content: [
123
+ {
124
+ type: 'text',
125
+ text: `Created volume ${volumeId}. Operation ID: ${operation.name || ''}`,
126
+ },
127
+ ],
128
+ structuredContent: {
129
+ name: `projects/${projectId}/locations/${location}/volumes/${volumeId}`,
130
+ operationId: operation.name || '',
131
+ },
132
+ };
133
+ }
134
+ catch (error) {
135
+ log.error({ err: error }, 'Error creating volume');
136
+ return {
137
+ isError: true,
138
+ content: [
139
+ {
140
+ type: 'text',
141
+ text: `Error creating volume: ${error.message || 'Unknown error'}`,
142
+ },
143
+ ],
144
+ };
145
+ }
146
+ };
147
+ // Delete Volume Handler
148
+ export const deleteVolumeHandler = async (args) => {
149
+ try {
150
+ const { projectId, location, volumeId, force = false } = args;
151
+ // Create a new NetApp client using the factory
152
+ const netAppClient = NetAppClientFactory.createClient();
153
+ // Format the name for the volume
154
+ const name = `projects/${projectId}/locations/${location}/volumes/${volumeId}`;
155
+ // Call the API to delete the volume
156
+ const request = { name };
157
+ // Only add force if it's true to avoid API errors
158
+ if (force) {
159
+ request.force = true;
160
+ }
161
+ log.info({ request }, 'Delete Volume request');
162
+ const [operation] = await netAppClient.deleteVolume(request);
163
+ log.info({ operation }, 'Delete Volume operation');
164
+ return {
165
+ content: [
166
+ {
167
+ type: 'text',
168
+ text: JSON.stringify({
169
+ message: `Volume ${volumeId} deletion requested`,
170
+ operation: operation,
171
+ }, null, 2),
172
+ },
173
+ ],
174
+ structuredContent: {
175
+ success: true,
176
+ operationId: operation.name || '',
177
+ },
178
+ };
179
+ }
180
+ catch (error) {
181
+ log.error({ err: error }, 'Error deleting volume');
182
+ return {
183
+ isError: true,
184
+ content: [
185
+ {
186
+ type: 'text',
187
+ text: `Error deleting volume: ${error.message || 'Unknown error'}`,
188
+ },
189
+ ],
190
+ structuredContent: {
191
+ success: false,
192
+ },
193
+ };
194
+ }
195
+ };
196
+ // Get Volume Handler
197
+ export const getVolumeHandler = async (args) => {
198
+ try {
199
+ const { projectId, location, volumeId } = args;
200
+ // Create a new NetApp client using the factory
201
+ const netAppClient = NetAppClientFactory.createClient();
202
+ // Format the name for the volume
203
+ const name = `projects/${projectId}/locations/${location}/volumes/${volumeId}`;
204
+ // Call the API to get the volume
205
+ log.info({ name }, 'Get Volume request');
206
+ const [volume] = await netAppClient.getVolume({ name });
207
+ log.info({ volume }, 'Get Volume response');
208
+ // Format the volume data
209
+ const formattedVolume = formatVolumeData(volume);
210
+ return {
211
+ content: [
212
+ {
213
+ type: 'text',
214
+ text: JSON.stringify(volume, null, 2),
215
+ },
216
+ ],
217
+ structuredContent: { volume: formattedVolume },
218
+ };
219
+ }
220
+ catch (error) {
221
+ log.error({ err: error }, 'Error getting volume');
222
+ return {
223
+ isError: true,
224
+ content: [
225
+ {
226
+ type: 'text',
227
+ text: `Error getting volume: ${error.message || 'Unknown error'}`,
228
+ },
229
+ ],
230
+ };
231
+ }
232
+ };
233
+ // List Volumes Handler
234
+ export const listVolumesHandler = async (args) => {
235
+ try {
236
+ const { projectId, location, filter, pageSize, pageToken } = args;
237
+ // Create a new NetApp client using the factory
238
+ const netAppClient = NetAppClientFactory.createClient();
239
+ // Format the parent path
240
+ const parent = `projects/${projectId}/locations/${location}`;
241
+ // Create the request object
242
+ const request = { parent };
243
+ if (filter)
244
+ request.filter = filter;
245
+ if (pageSize)
246
+ request.pageSize = pageSize;
247
+ if (pageToken)
248
+ request.pageToken = pageToken;
249
+ // Call the API to list volumes
250
+ log.info({ request }, 'List Volumes request');
251
+ const [volumes, , nextPageToken] = await netAppClient.listVolumes(request);
252
+ log.info({ volumes, nextPageToken }, 'List Volumes response');
253
+ const formattedVolumes = volumes.map(formatVolumeData);
254
+ return {
255
+ content: [
256
+ {
257
+ type: 'text',
258
+ text: JSON.stringify({ volumes: volumes, nextPageToken: nextPageToken }, null, 2),
259
+ },
260
+ ],
261
+ structuredContent: {
262
+ volumes: formattedVolumes,
263
+ nextPageToken: pageToken || '',
264
+ },
265
+ };
266
+ }
267
+ catch (error) {
268
+ log.error({ err: error }, 'Error listing volumes');
269
+ return {
270
+ isError: true,
271
+ content: [
272
+ {
273
+ type: 'text',
274
+ text: `Error listing volumes: ${error.message || 'Unknown error'}`,
275
+ },
276
+ ],
277
+ };
278
+ }
279
+ };
280
+ // Update Volume Handler
281
+ // TODO: update is not tested
282
+ // FIX_ME: errors "reason: 'RESOURCE_PROJECT_INVALID'
283
+ export const updateVolumeHandler = async (args) => {
284
+ try {
285
+ const { projectId, location, volumeId, capacityGib, description, labels, backupConfig, exportPolicy, } = args;
286
+ // Create a new NetApp client using the factory
287
+ const netAppClient = NetAppClientFactory.createClient();
288
+ // Format the name for the volume
289
+ const name = `projects/${projectId}/locations/${location}/volumes/${volumeId}`;
290
+ // Create the update mask
291
+ const updateMask = [];
292
+ const volume = { name };
293
+ // Add fields to update mask if they're provided
294
+ if (capacityGib !== undefined) {
295
+ volume.capacityGib = capacityGib;
296
+ updateMask.push('capacity_gib');
297
+ }
298
+ if (description !== undefined) {
299
+ volume.description = description;
300
+ updateMask.push('description');
301
+ }
302
+ if (labels !== undefined) {
303
+ volume.labels = labels;
304
+ updateMask.push('labels');
305
+ }
306
+ if (exportPolicy !== undefined) {
307
+ volume.exportPolicy = exportPolicy;
308
+ updateMask.push('export_policy');
309
+ }
310
+ if (backupConfig !== undefined) {
311
+ volume.backupConfig = backupConfig;
312
+ // Update mask must use proto (snake_case) field names
313
+ if (backupConfig.backupPolicies !== undefined)
314
+ updateMask.push('backup_config.backup_policies');
315
+ if (backupConfig.backupVault !== undefined)
316
+ updateMask.push('backup_config.backup_vault');
317
+ if (backupConfig.scheduledBackupEnabled !== undefined)
318
+ updateMask.push('backup_config.scheduled_backup_enabled');
319
+ }
320
+ // Create the request
321
+ const request = {
322
+ volume,
323
+ updateMask: { paths: updateMask },
324
+ };
325
+ log.info({ request }, 'Update Volume request');
326
+ const [operation] = await netAppClient.updateVolume(request);
327
+ log.info({ operation }, 'Update Volume operation');
328
+ return {
329
+ content: [
330
+ {
331
+ type: 'text',
332
+ text: `Updated volume ${volumeId}. Operation ID: ${operation.name || ''}`,
333
+ },
334
+ ],
335
+ structuredContent: {
336
+ name,
337
+ operationId: operation.name || '',
338
+ },
339
+ };
340
+ }
341
+ catch (error) {
342
+ log.error({ err: error }, 'Error updating volume');
343
+ return {
344
+ isError: true,
345
+ content: [
346
+ {
347
+ type: 'text',
348
+ text: `Error updating volume: ${error.message || 'Unknown error'}`,
349
+ },
350
+ ],
351
+ };
352
+ }
353
+ };
@@ -0,0 +1,162 @@
1
+ import { z } from 'zod';
2
+ // Create KMS Config Tool
3
+ export const createKmsConfigTool = {
4
+ name: 'gcnv_kms_config_create',
5
+ title: 'Create KMS Config',
6
+ description: 'Creates a new KMS (Key Management Service) configuration',
7
+ inputSchema: {
8
+ projectId: z.string().describe('The ID of the Google Cloud project'),
9
+ location: z.string().describe('The location where the KMS config should be created'),
10
+ kmsConfigId: z.string().describe('The ID to assign to the KMS config'),
11
+ cryptoKeyName: z.string().describe('The full name of the crypto key'),
12
+ description: z.string().optional().describe('Optional description'),
13
+ labels: z.record(z.string()).optional().describe('Optional labels'),
14
+ instructions: z.string().optional().describe('Instructions for accessing the KMS'),
15
+ },
16
+ outputSchema: {
17
+ name: z.string().describe('The name of the created KMS config'),
18
+ operationId: z.string().describe('The ID of the long-running operation'),
19
+ },
20
+ };
21
+ // Delete KMS Config Tool
22
+ export const deleteKmsConfigTool = {
23
+ name: 'gcnv_kms_config_delete',
24
+ title: 'Delete KMS Config',
25
+ description: 'Deletes a KMS configuration',
26
+ inputSchema: {
27
+ projectId: z.string().describe('The ID of the Google Cloud project'),
28
+ location: z.string().describe('The location of the KMS config'),
29
+ kmsConfigId: z.string().describe('The ID of the KMS config to delete'),
30
+ },
31
+ outputSchema: {
32
+ success: z.boolean().describe('Whether the deletion was successful'),
33
+ operationId: z.string().optional().describe('The ID of the long-running operation'),
34
+ },
35
+ };
36
+ // Get KMS Config Tool
37
+ export const getKmsConfigTool = {
38
+ name: 'gcnv_kms_config_get',
39
+ title: 'Get KMS Config',
40
+ description: 'Gets details of a specific KMS configuration',
41
+ inputSchema: {
42
+ projectId: z.string().describe('The ID of the Google Cloud project'),
43
+ location: z.string().describe('The location of the KMS config'),
44
+ kmsConfigId: z.string().describe('The ID of the KMS config to retrieve'),
45
+ },
46
+ outputSchema: {
47
+ name: z.string().describe('The name of the KMS config'),
48
+ kmsConfigId: z.string().describe('The ID of the KMS config'),
49
+ cryptoKeyName: z.string().optional().describe('The name of the crypto key'),
50
+ state: z
51
+ .string()
52
+ .optional()
53
+ .describe('The current state (e.g., READY, KEY_CHECK_PENDING—run instructions if pending)'),
54
+ instructions: z
55
+ .string()
56
+ .optional()
57
+ .describe('Steps to grant the service account access when state is KEY_CHECK_PENDING'),
58
+ stateDetails: z
59
+ .string()
60
+ .optional()
61
+ .describe('Additional state details when the KMS config is not ready'),
62
+ createTime: z.date().optional().describe('The creation timestamp'),
63
+ description: z.string().optional().describe('Description'),
64
+ labels: z.record(z.string()).optional().describe('Labels'),
65
+ serviceAccount: z
66
+ .string()
67
+ .optional()
68
+ .describe('Service account used by NetApp to access the KMS key'),
69
+ },
70
+ };
71
+ // List KMS Configs Tool
72
+ export const listKmsConfigsTool = {
73
+ name: 'gcnv_kms_config_list',
74
+ title: 'List KMS Configs',
75
+ description: 'Lists all KMS configurations in the specified location',
76
+ inputSchema: {
77
+ projectId: z.string().describe('The ID of the Google Cloud project'),
78
+ location: z.string().describe('The location to list KMS configs from'),
79
+ filter: z.string().optional().describe('Filter expression'),
80
+ pageSize: z.number().optional().describe('Maximum number of items to return'),
81
+ pageToken: z.string().optional().describe('Page token from previous request'),
82
+ orderBy: z.string().optional().describe('Sort order'),
83
+ },
84
+ outputSchema: {
85
+ kmsConfigs: z
86
+ .array(z.object({
87
+ name: z.string().describe('The name of the KMS config'),
88
+ kmsConfigId: z.string().describe('The ID of the KMS config'),
89
+ cryptoKeyName: z.string().optional().describe('The name of the crypto key'),
90
+ state: z
91
+ .string()
92
+ .optional()
93
+ .describe('The current state (e.g., READY, KEY_CHECK_PENDING—run instructions if pending)'),
94
+ instructions: z
95
+ .string()
96
+ .optional()
97
+ .describe('Steps to grant the service account access when state is KEY_CHECK_PENDING'),
98
+ stateDetails: z
99
+ .string()
100
+ .optional()
101
+ .describe('Additional state details when the KMS config is not ready'),
102
+ createTime: z.date().optional().describe('The creation timestamp'),
103
+ description: z.string().optional().describe('Description'),
104
+ labels: z.record(z.string()).optional().describe('Labels'),
105
+ serviceAccount: z
106
+ .string()
107
+ .optional()
108
+ .describe('Service account used by NetApp to access the KMS key'),
109
+ }))
110
+ .describe('List of KMS configs'),
111
+ nextPageToken: z.string().optional().describe('Token for next page'),
112
+ },
113
+ };
114
+ // Update KMS Config Tool
115
+ export const updateKmsConfigTool = {
116
+ name: 'gcnv_kms_config_update',
117
+ title: 'Update KMS Config',
118
+ description: 'Updates a KMS configuration',
119
+ inputSchema: {
120
+ projectId: z.string().describe('The ID of the Google Cloud project'),
121
+ location: z.string().describe('The location of the KMS config'),
122
+ kmsConfigId: z.string().describe('The ID of the KMS config to update'),
123
+ cryptoKeyName: z.string().optional().describe('The name of the crypto key'),
124
+ description: z.string().optional().describe('New description'),
125
+ labels: z.record(z.string()).optional().describe('New labels'),
126
+ instructions: z.string().optional().describe('Instructions for accessing the KMS'),
127
+ },
128
+ outputSchema: {
129
+ name: z.string().describe('The name of the updated KMS config'),
130
+ operationId: z.string().describe('The ID of the long-running operation'),
131
+ },
132
+ };
133
+ // Verify KMS Config Tool
134
+ export const verifyKmsConfigTool = {
135
+ name: 'gcnv_kms_config_verify',
136
+ title: 'Verify KMS Config',
137
+ description: 'Verifies KMS config reachability after granting CMEK permissions; run this after applying the returned instructions so the service marks the config READY for pool creation',
138
+ inputSchema: {
139
+ projectId: z.string().describe('The ID of the Google Cloud project'),
140
+ location: z.string().describe('The location of the KMS config'),
141
+ kmsConfigId: z.string().describe('The ID of the KMS config to verify'),
142
+ },
143
+ outputSchema: {
144
+ reachable: z.boolean().optional().describe('Whether the KMS config is reachable'),
145
+ healthError: z.string().optional().describe('Health error if any'),
146
+ },
147
+ };
148
+ // Encrypt Volumes Tool
149
+ export const encryptVolumesTool = {
150
+ name: 'gcnv_kms_config_encrypt_volumes',
151
+ title: 'Encrypt Volumes',
152
+ description: 'Encrypts existing volumes without CMEK encryption with the desired KMS config',
153
+ inputSchema: {
154
+ projectId: z.string().describe('The ID of the Google Cloud project'),
155
+ location: z.string().describe('The location of the KMS config'),
156
+ kmsConfigId: z.string().describe('The ID of the KMS config to use for encryption'),
157
+ },
158
+ outputSchema: {
159
+ name: z.string().describe('The name of the KMS config'),
160
+ operationId: z.string().describe('The ID of the long-running operation'),
161
+ },
162
+ };
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ // Get Operation Tool
3
+ export const getOperationTool = {
4
+ name: 'gcnv_operation_get',
5
+ title: 'Get Operation',
6
+ description: 'Get details of a long-running operation by its ID',
7
+ inputSchema: {
8
+ operationName: z.string().describe('The full name of the operation to retrieve'),
9
+ },
10
+ outputSchema: {
11
+ name: z.string().describe('The name of the operation'),
12
+ done: z.boolean().describe('Whether the operation is complete'),
13
+ success: z.boolean().describe('True when done is true; done determines the final result'),
14
+ metadata: z.record(z.any()).optional().describe('Metadata about the operation'),
15
+ error: z.record(z.any()).optional().describe('Error details if the operation failed'),
16
+ response: z.record(z.any()).optional().describe('The response if the operation succeeded'),
17
+ createTime: z.string().optional().describe('When the operation was created'),
18
+ target: z.string().optional().describe('The target resource of the operation'),
19
+ verb: z.string().optional().describe('The operation verb (create, update, delete)'),
20
+ statusMessage: z.string().optional().describe('Current status message of the operation'),
21
+ cancelRequested: z.boolean().optional().describe('Whether cancellation was requested'),
22
+ apiVersion: z.string().optional().describe('API version used for the operation'),
23
+ },
24
+ };
25
+ // Cancel Operation Tool
26
+ export const cancelOperationTool = {
27
+ name: 'gcnv_operation_cancel',
28
+ title: 'Cancel Operation',
29
+ description: 'Cancels a long-running operation that is still in progress',
30
+ inputSchema: {
31
+ operationName: z.string().describe('The full name of the operation to cancel'),
32
+ },
33
+ outputSchema: {
34
+ success: z.boolean().describe('Whether the cancellation request was successful'),
35
+ message: z.string().describe('Status message about the cancellation attempt'),
36
+ },
37
+ };
38
+ // List Operations Tool
39
+ export const listOperationsTool = {
40
+ name: 'gcnv_operation_list',
41
+ title: 'List Operations',
42
+ description: 'Lists all active long-running operations in the project',
43
+ inputSchema: {
44
+ projectId: z.string().describe('The ID of the Google Cloud project'),
45
+ location: z.string().describe('The location to list operations from'),
46
+ filter: z.string().optional().describe('Filter expression for operations'),
47
+ pageSize: z.number().optional().describe('The maximum number of operations to return'),
48
+ pageToken: z.string().optional().describe('Page token from a previous list request'),
49
+ },
50
+ outputSchema: {
51
+ operations: z
52
+ .array(z.object({
53
+ name: z.string().describe('The name of the operation'),
54
+ done: z.boolean().describe('Whether the operation is complete'),
55
+ success: z.boolean().describe('True when done is true; done determines the final result'),
56
+ target: z.string().optional().describe('The target resource of the operation'),
57
+ verb: z.string().optional().describe('The operation verb (create, update, delete)'),
58
+ createTime: z.string().optional().describe('When the operation was created'),
59
+ statusMessage: z.string().optional().describe('Current status message of the operation'),
60
+ }))
61
+ .describe('List of operations'),
62
+ nextPageToken: z.string().optional().describe('Token to retrieve the next page of results'),
63
+ },
64
+ };