@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,327 @@
1
+ import { z } from 'zod';
2
+ import { NetAppClientFactory } from '../../utils/netapp-client-factory.js';
3
+ import { getKmsConfigTool, listKmsConfigsTool } from '../kms-config-tools.js';
4
+ import { logger } from '../../logger.js';
5
+ const log = logger.child({ module: 'kms-config-handler' });
6
+ const getKmsConfigOutputSchema = z.object(getKmsConfigTool.outputSchema);
7
+ const listKmsConfigsOutputSchema = z.object(listKmsConfigsTool.outputSchema);
8
+ // Helper to format KMS config data
9
+ function formatKmsConfigData(config) {
10
+ const result = {};
11
+ if (!config)
12
+ return result;
13
+ if (config.name) {
14
+ const nameParts = config.name.split('/');
15
+ result.name = config.name;
16
+ result.kmsConfigId = nameParts[nameParts.length - 1];
17
+ }
18
+ if (config.cryptoKeyName)
19
+ result.cryptoKeyName = config.cryptoKeyName;
20
+ if (config.state)
21
+ result.state = config.state;
22
+ if (config.stateDetails)
23
+ result.stateDetails = config.stateDetails;
24
+ if (config.instructions)
25
+ result.instructions = config.instructions;
26
+ if (config.serviceAccount)
27
+ result.serviceAccount = config.serviceAccount;
28
+ if (config.createTime) {
29
+ result.createTime = new Date(config.createTime.seconds * 1000);
30
+ }
31
+ if (config.description)
32
+ result.description = config.description;
33
+ if (config.labels)
34
+ result.labels = config.labels;
35
+ return result;
36
+ }
37
+ // Create KMS Config Handler
38
+ export const createKmsConfigHandler = async (args) => {
39
+ try {
40
+ const { projectId, location, kmsConfigId, cryptoKeyName, description, labels, instructions } = args;
41
+ const netAppClient = NetAppClientFactory.createClient();
42
+ const parent = `projects/${projectId}/locations/${location}`;
43
+ const kmsConfig = {};
44
+ if (cryptoKeyName)
45
+ kmsConfig.cryptoKeyName = cryptoKeyName;
46
+ if (description)
47
+ kmsConfig.description = description;
48
+ if (labels)
49
+ kmsConfig.labels = labels;
50
+ if (instructions)
51
+ kmsConfig.instructions = instructions;
52
+ const request = {
53
+ parent,
54
+ kmsConfigId,
55
+ kmsConfig,
56
+ };
57
+ log.info({ request }, 'Create KMS Config request');
58
+ const [operation] = await netAppClient.createKmsConfig(request);
59
+ log.info({ operation }, 'Create KMS Config operation');
60
+ return {
61
+ content: [
62
+ {
63
+ type: 'text',
64
+ text: JSON.stringify({
65
+ name: `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`,
66
+ operation: operation,
67
+ }, null, 2),
68
+ },
69
+ ],
70
+ structuredContent: {
71
+ name: `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`,
72
+ operationId: operation.name || '',
73
+ },
74
+ };
75
+ }
76
+ catch (error) {
77
+ log.error({ err: error }, 'Error creating KMS config');
78
+ return {
79
+ isError: true,
80
+ content: [
81
+ {
82
+ type: 'text',
83
+ text: `Error creating KMS config: ${error.message || 'Unknown error'}`,
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ };
89
+ // Delete KMS Config Handler
90
+ export const deleteKmsConfigHandler = async (args) => {
91
+ try {
92
+ const { projectId, location, kmsConfigId } = args;
93
+ const netAppClient = NetAppClientFactory.createClient();
94
+ const name = `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`;
95
+ const [operation] = await netAppClient.deleteKmsConfig({ name });
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: JSON.stringify({
101
+ message: `KMS config ${kmsConfigId} deletion requested`,
102
+ operation: operation,
103
+ }, null, 2),
104
+ },
105
+ ],
106
+ structuredContent: {
107
+ success: true,
108
+ operationId: operation.name || '',
109
+ },
110
+ };
111
+ }
112
+ catch (error) {
113
+ log.error({ err: error }, 'Error deleting KMS config');
114
+ return {
115
+ isError: true,
116
+ content: [
117
+ {
118
+ type: 'text',
119
+ text: `Error deleting KMS config: ${error.message || 'Unknown error'}`,
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ };
125
+ // Get KMS Config Handler
126
+ export const getKmsConfigHandler = async (args) => {
127
+ try {
128
+ const { projectId, location, kmsConfigId } = args;
129
+ const netAppClient = NetAppClientFactory.createClient();
130
+ const name = `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`;
131
+ const [kmsConfig] = await netAppClient.getKmsConfig({ name });
132
+ const formatted = formatKmsConfigData(kmsConfig);
133
+ const structuredContent = getKmsConfigOutputSchema.parse(formatted);
134
+ return {
135
+ content: [
136
+ {
137
+ type: 'text',
138
+ text: JSON.stringify(structuredContent, null, 2),
139
+ },
140
+ ],
141
+ structuredContent,
142
+ };
143
+ }
144
+ catch (error) {
145
+ log.error({ err: error }, 'Error getting KMS config');
146
+ return {
147
+ isError: true,
148
+ content: [
149
+ {
150
+ type: 'text',
151
+ text: `Error getting KMS config: ${error.message || 'Unknown error'}`,
152
+ },
153
+ ],
154
+ };
155
+ }
156
+ };
157
+ // List KMS Configs Handler
158
+ export const listKmsConfigsHandler = async (args) => {
159
+ try {
160
+ const { projectId, location, filter, pageSize, pageToken, orderBy } = args;
161
+ const netAppClient = NetAppClientFactory.createClient();
162
+ const parent = `projects/${projectId}/locations/${location}`;
163
+ const request = { parent };
164
+ if (filter)
165
+ request.filter = filter;
166
+ if (pageSize)
167
+ request.pageSize = pageSize;
168
+ if (pageToken)
169
+ request.pageToken = pageToken;
170
+ if (orderBy)
171
+ request.orderBy = orderBy;
172
+ const [kmsConfigs, , response] = await netAppClient.listKmsConfigs(request);
173
+ const structuredContent = listKmsConfigsOutputSchema.parse({
174
+ kmsConfigs: kmsConfigs.map(formatKmsConfigData),
175
+ nextPageToken: response?.nextPageToken,
176
+ });
177
+ return {
178
+ content: [
179
+ {
180
+ type: 'text',
181
+ text: JSON.stringify(structuredContent, null, 2),
182
+ },
183
+ ],
184
+ structuredContent,
185
+ };
186
+ }
187
+ catch (error) {
188
+ log.error({ err: error }, 'Error listing KMS configs');
189
+ return {
190
+ isError: true,
191
+ content: [
192
+ {
193
+ type: 'text',
194
+ text: `Error listing KMS configs: ${error.message || 'Unknown error'}`,
195
+ },
196
+ ],
197
+ };
198
+ }
199
+ };
200
+ // Update KMS Config Handler
201
+ export const updateKmsConfigHandler = async (args) => {
202
+ try {
203
+ const { projectId, location, kmsConfigId, cryptoKeyName, description, labels, instructions } = args;
204
+ const netAppClient = NetAppClientFactory.createClient();
205
+ const name = `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`;
206
+ const updateMask = [];
207
+ const kmsConfig = { name };
208
+ if (cryptoKeyName !== undefined) {
209
+ kmsConfig.cryptoKeyName = cryptoKeyName;
210
+ updateMask.push('crypto_key_name');
211
+ }
212
+ if (description !== undefined) {
213
+ kmsConfig.description = description;
214
+ updateMask.push('description');
215
+ }
216
+ if (labels !== undefined) {
217
+ kmsConfig.labels = labels;
218
+ updateMask.push('labels');
219
+ }
220
+ if (instructions !== undefined) {
221
+ kmsConfig.instructions = instructions;
222
+ updateMask.push('instructions');
223
+ }
224
+ const request = {
225
+ kmsConfig,
226
+ updateMask: { paths: updateMask },
227
+ };
228
+ log.info({ request }, 'Update KMS Config request');
229
+ const [operation] = await netAppClient.updateKmsConfig(request);
230
+ return {
231
+ content: [
232
+ {
233
+ type: 'text',
234
+ text: JSON.stringify({
235
+ message: `KMS config ${kmsConfigId} update requested`,
236
+ operation: operation,
237
+ }, null, 2),
238
+ },
239
+ ],
240
+ structuredContent: {
241
+ name: name,
242
+ operationId: operation.name || '',
243
+ },
244
+ };
245
+ }
246
+ catch (error) {
247
+ log.error({ err: error }, 'Error updating KMS config');
248
+ return {
249
+ isError: true,
250
+ content: [
251
+ {
252
+ type: 'text',
253
+ text: `Error updating KMS config: ${error.message || 'Unknown error'}`,
254
+ },
255
+ ],
256
+ };
257
+ }
258
+ };
259
+ // Verify KMS Config Handler
260
+ export const verifyKmsConfigHandler = async (args) => {
261
+ try {
262
+ const { projectId, location, kmsConfigId } = args;
263
+ const netAppClient = NetAppClientFactory.createClient();
264
+ const name = `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`;
265
+ const [response] = await netAppClient.verifyKmsConfig({ name });
266
+ return {
267
+ content: [
268
+ {
269
+ type: 'text',
270
+ text: JSON.stringify(response, null, 2),
271
+ },
272
+ ],
273
+ structuredContent: {
274
+ reachable: response.healthy,
275
+ healthError: response.healthError,
276
+ },
277
+ };
278
+ }
279
+ catch (error) {
280
+ log.error({ err: error }, 'Error verifying KMS config');
281
+ return {
282
+ isError: true,
283
+ content: [
284
+ {
285
+ type: 'text',
286
+ text: `Error verifying KMS config: ${error.message || 'Unknown error'}`,
287
+ },
288
+ ],
289
+ };
290
+ }
291
+ };
292
+ // Encrypt Volumes Handler
293
+ export const encryptVolumesHandler = async (args) => {
294
+ try {
295
+ const { projectId, location, kmsConfigId } = args;
296
+ const netAppClient = NetAppClientFactory.createClient();
297
+ const name = `projects/${projectId}/locations/${location}/kmsConfigs/${kmsConfigId}`;
298
+ const [operation] = await netAppClient.encryptVolumes({ name });
299
+ return {
300
+ content: [
301
+ {
302
+ type: 'text',
303
+ text: JSON.stringify({
304
+ message: `Volume encryption initiated for KMS config ${kmsConfigId}`,
305
+ operation: operation,
306
+ }, null, 2),
307
+ },
308
+ ],
309
+ structuredContent: {
310
+ name: name,
311
+ operationId: operation.name || '',
312
+ },
313
+ };
314
+ }
315
+ catch (error) {
316
+ log.error({ err: error }, 'Error encrypting volumes');
317
+ return {
318
+ isError: true,
319
+ content: [
320
+ {
321
+ type: 'text',
322
+ text: `Error encrypting volumes: ${error.message || 'Unknown error'}`,
323
+ },
324
+ ],
325
+ };
326
+ }
327
+ };
@@ -0,0 +1,254 @@
1
+ import { NetAppClientFactory } from '../../utils/netapp-client-factory.js';
2
+ import axios from 'axios';
3
+ import { logger } from '../../logger.js';
4
+ const log = logger.child({ module: 'operation-handler' });
5
+ /**
6
+ * Parse metadata from a Google Cloud operation
7
+ * @param operation The operation object
8
+ * @returns Formatted metadata
9
+ */
10
+ function parseOperationMetadata(operation) {
11
+ const done = operation.done === true;
12
+ const result = {
13
+ name: operation.name || '',
14
+ done,
15
+ success: done,
16
+ };
17
+ // Try to get metadata from the operation
18
+ if (operation.metadata) {
19
+ try {
20
+ // Add metadata as-is to the result
21
+ result.metadata = operation.metadata;
22
+ // Try to extract specific fields that might be useful
23
+ const metadata = operation.metadata;
24
+ if (metadata.createTime) {
25
+ result.createTime = metadata.createTime;
26
+ }
27
+ // These fields might be present depending on the operation type
28
+ if (metadata.target)
29
+ result.target = metadata.target;
30
+ if (metadata.verb)
31
+ result.verb = metadata.verb;
32
+ if (metadata.statusMessage)
33
+ result.statusMessage = metadata.statusMessage;
34
+ if (metadata.apiVersion)
35
+ result.apiVersion = metadata.apiVersion;
36
+ if (metadata.requestedCancellation !== undefined) {
37
+ result.cancelRequested = metadata.requestedCancellation;
38
+ }
39
+ }
40
+ catch (err) {
41
+ log.warn({ err }, 'Error parsing operation metadata');
42
+ }
43
+ }
44
+ // Add error information if operation failed
45
+ if (operation.done && operation.error) {
46
+ result.error = {
47
+ code: operation.error.code,
48
+ message: operation.error.message,
49
+ };
50
+ }
51
+ // Add response data if operation succeeded
52
+ if (operation.done && operation.response) {
53
+ result.response = operation.response;
54
+ }
55
+ return result;
56
+ }
57
+ // Get Operation Handler
58
+ export const getOperationHandler = async (args) => {
59
+ try {
60
+ const { operationName } = args;
61
+ // Create a new NetApp client using the factory
62
+ const netAppClient = NetAppClientFactory.createClient();
63
+ // Make a direct request to the operations API
64
+ // Using the Google API client's credentials
65
+ const auth = netAppClient.auth;
66
+ // Make a direct API call using axios
67
+ const response = await axios.request({
68
+ url: `https://netapp.googleapis.com/v1/${operationName}`,
69
+ method: 'GET',
70
+ headers: {
71
+ Authorization: `Bearer ${await auth.getAccessToken()}`,
72
+ },
73
+ });
74
+ const operation = response.data;
75
+ // Parse the operation data
76
+ const formattedResponse = parseOperationMetadata(operation);
77
+ return {
78
+ content: [
79
+ {
80
+ type: 'text',
81
+ text: `Retrieved details for operation '${operationName}'\n${JSON.stringify(formattedResponse, null, 2)}`,
82
+ },
83
+ ],
84
+ structuredContent: formattedResponse,
85
+ };
86
+ }
87
+ catch (error) {
88
+ log.error({ err: error }, 'Error getting operation');
89
+ return {
90
+ content: [
91
+ {
92
+ type: 'text',
93
+ text: `Error getting operation: ${error.message || 'Unknown error'}`,
94
+ },
95
+ ],
96
+ structuredContent: {
97
+ error: error.message || 'Unknown error',
98
+ },
99
+ };
100
+ }
101
+ };
102
+ // Cancel Operation Handler
103
+ export const cancelOperationHandler = async (args) => {
104
+ try {
105
+ const { operationName } = args;
106
+ // Create a new NetApp client using the factory
107
+ const netAppClient = NetAppClientFactory.createClient();
108
+ // Access the auth client to make direct API calls
109
+ const auth = netAppClient.auth;
110
+ // First, get the current operation state
111
+ const getResponse = await axios.request({
112
+ url: `https://netapp.googleapis.com/v1/${operationName}`,
113
+ method: 'GET',
114
+ headers: {
115
+ Authorization: `Bearer ${await auth.getAccessToken()}`,
116
+ },
117
+ });
118
+ const operation = getResponse.data;
119
+ // Check if operation is already completed
120
+ if (operation.done) {
121
+ return {
122
+ content: [
123
+ {
124
+ type: 'text',
125
+ text: `Operation '${operationName}' is already completed and cannot be cancelled.`,
126
+ },
127
+ ],
128
+ structuredContent: {
129
+ success: false,
130
+ message: 'Operation already completed',
131
+ },
132
+ };
133
+ }
134
+ // Cancel the operation using direct API call
135
+ await axios.request({
136
+ url: `https://netapp.googleapis.com/v1/${operationName}:cancel`,
137
+ method: 'POST',
138
+ headers: {
139
+ Authorization: `Bearer ${await auth.getAccessToken()}`,
140
+ },
141
+ });
142
+ return {
143
+ content: [
144
+ {
145
+ type: 'text',
146
+ text: `Successfully requested cancellation of operation '${operationName}'.`,
147
+ },
148
+ ],
149
+ structuredContent: {
150
+ success: true,
151
+ message: 'Cancellation request submitted successfully',
152
+ },
153
+ };
154
+ }
155
+ catch (error) {
156
+ log.error({ err: error }, 'Error cancelling operation');
157
+ return {
158
+ content: [
159
+ {
160
+ type: 'text',
161
+ text: `Error cancelling operation: ${error.message || 'Unknown error'}`,
162
+ },
163
+ ],
164
+ structuredContent: {
165
+ success: false,
166
+ message: error.message || 'Unknown error',
167
+ },
168
+ };
169
+ }
170
+ };
171
+ // List Operations Handler
172
+ export const listOperationsHandler = async (args) => {
173
+ try {
174
+ const { projectId, location, filter, pageSize, pageToken } = args;
175
+ // Create a new NetApp client using the factory
176
+ const netAppClient = NetAppClientFactory.createClient();
177
+ // Make a direct API call using the client's auth credentials
178
+ const auth = netAppClient.auth;
179
+ // Build the request parameters
180
+ const params = {};
181
+ if (filter)
182
+ params.filter = filter;
183
+ if (pageSize)
184
+ params.pageSize = pageSize;
185
+ if (pageToken)
186
+ params.pageToken = pageToken;
187
+ // Make the API request
188
+ const response = await axios.request({
189
+ url: `https://netapp.googleapis.com/v1/projects/${projectId}/locations/${location}/operations`,
190
+ method: 'GET',
191
+ params,
192
+ headers: {
193
+ Authorization: `Bearer ${await auth.getAccessToken()}`,
194
+ },
195
+ });
196
+ log.info({ responseData: response.data }, 'List operations response');
197
+ // Type assertion for response data
198
+ const responseData = response.data;
199
+ const operations = responseData.operations || [];
200
+ const nextPageToken = responseData.nextPageToken;
201
+ // Format the operations
202
+ const formattedOperations = operations.map((op) => {
203
+ const done = op.done === true;
204
+ const result = {
205
+ name: op.name || '',
206
+ done,
207
+ success: done,
208
+ };
209
+ // Extract metadata
210
+ if (op.metadata) {
211
+ if (op.metadata.createTime) {
212
+ result.createTime = op.metadata.createTime;
213
+ }
214
+ if (op.metadata.target) {
215
+ result.target = op.metadata.target;
216
+ }
217
+ if (op.metadata.verb) {
218
+ result.verb = op.metadata.verb;
219
+ }
220
+ if (op.metadata.statusMessage) {
221
+ result.statusMessage = op.metadata.statusMessage;
222
+ }
223
+ }
224
+ return result;
225
+ });
226
+ return {
227
+ content: [
228
+ {
229
+ type: 'text',
230
+ text: JSON.stringify({ operations: response.data, nextPageToken: nextPageToken }, null, 2),
231
+ },
232
+ ],
233
+ structuredContent: {
234
+ operations: formattedOperations,
235
+ nextPageToken: nextPageToken,
236
+ },
237
+ };
238
+ }
239
+ catch (error) {
240
+ log.error({ err: error }, 'Error listing operations');
241
+ return {
242
+ content: [
243
+ {
244
+ type: 'text',
245
+ text: `Error listing operations: ${error.message || 'Unknown error'}`,
246
+ },
247
+ ],
248
+ structuredContent: {
249
+ operations: [],
250
+ error: error.message || 'Unknown error',
251
+ },
252
+ };
253
+ }
254
+ };