@google-cloud/cloud-run-mcp 1.2.0 → 1.5.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.
- package/.github/images/deploy_from_apps.gif +0 -0
- package/.github/images/deploy_from_ide.gif +0 -0
- package/.github/images/deploycli.gif +0 -0
- package/.github/workflows/local-tests.yml +1 -1
- package/CONTRIBUTING.md +8 -0
- package/README.md +22 -17
- package/gemini-extension/{Gemini.md → GEMINI.md} +2 -2
- package/gemini-extension.json +1 -1
- package/lib/cloud-run-deploy.js +162 -215
- package/lib/cloud-run-services.js +16 -4
- package/lib/gcp-api-helpers.js +143 -0
- package/lib/gcp-projects.js +1 -1
- package/mcp-server.js +8 -1
- package/package.json +3 -3
- package/test/local/gemini-extension.test.js +35 -0
- package/test/local/mcp-server.test.js +0 -1
- package/test/local/notifications.test.js +45 -0
- package/test/local/npx.test.js +36 -0
- package/test/local/tools.test.js +40 -65
- package/test/need-gcp/cloud-run-deploy.test.js +101 -135
- package/test/need-gcp/cloud-run-services.test.js +57 -77
- package/test/need-gcp/gcp-projects.test.js +14 -56
- package/{tools.js → tools/register-tools.js} +95 -461
- package/tools/tools.js +55 -0
|
@@ -3,7 +3,7 @@ Copyright 2025 Google LLC
|
|
|
3
3
|
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
6
|
-
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
7
|
|
|
8
8
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
9
|
|
|
@@ -15,18 +15,18 @@ limitations under the License.
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { z } from 'zod';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
import { listProjects, createProjectAndAttachBilling } from '../lib/gcp-projects.js';
|
|
19
|
+
import { listServices, getService, getServiceLogs } from '../lib/cloud-run-services.js';
|
|
20
|
+
import { deploy, deployImage } from '../lib/cloud-run-deploy.js';
|
|
21
|
+
|
|
22
|
+
function createProgressCallback(sendNotification) {
|
|
23
|
+
return (progress) => {
|
|
24
|
+
sendNotification({
|
|
25
|
+
method: 'notifications/message',
|
|
26
|
+
params: { level: progress.level || 'info', data: progress.data },
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
30
|
|
|
31
31
|
function gcpTool(gcpCredentialsAvailable, fn) {
|
|
32
32
|
if (!gcpCredentialsAvailable) {
|
|
@@ -42,24 +42,15 @@ function gcpTool(gcpCredentialsAvailable, fn) {
|
|
|
42
42
|
return fn;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
defaultProjectId,
|
|
49
|
-
defaultRegion,
|
|
50
|
-
defaultServiceName,
|
|
51
|
-
skipIamCheck,
|
|
52
|
-
gcpCredentialsAvailable,
|
|
53
|
-
} = {}
|
|
54
|
-
) => {
|
|
55
|
-
// Tool to list GCP projects
|
|
45
|
+
// Tool to list GCP projects
|
|
46
|
+
function registerListProjectsTool(server, options) {
|
|
56
47
|
server.registerTool(
|
|
57
48
|
'list_projects',
|
|
58
49
|
{
|
|
59
50
|
description: 'Lists available GCP projects',
|
|
60
51
|
inputSchema: {},
|
|
61
52
|
},
|
|
62
|
-
gcpTool(gcpCredentialsAvailable, async () => {
|
|
53
|
+
gcpTool(options.gcpCredentialsAvailable, async () => {
|
|
63
54
|
try {
|
|
64
55
|
const projects = await listProjects();
|
|
65
56
|
return {
|
|
@@ -82,8 +73,10 @@ export const registerTools = (
|
|
|
82
73
|
}
|
|
83
74
|
})
|
|
84
75
|
);
|
|
76
|
+
}
|
|
85
77
|
|
|
86
|
-
|
|
78
|
+
// Tool to create a new GCP project
|
|
79
|
+
function registerCreateProjectTool(server, options) {
|
|
87
80
|
server.registerTool(
|
|
88
81
|
'create_project',
|
|
89
82
|
{
|
|
@@ -98,7 +91,7 @@ export const registerTools = (
|
|
|
98
91
|
),
|
|
99
92
|
},
|
|
100
93
|
},
|
|
101
|
-
gcpTool(gcpCredentialsAvailable, async ({ projectId }) => {
|
|
94
|
+
gcpTool(options.gcpCredentialsAvailable, async ({ projectId }) => {
|
|
102
95
|
if (
|
|
103
96
|
projectId !== undefined &&
|
|
104
97
|
(typeof projectId !== 'string' || projectId.trim() === '')
|
|
@@ -134,8 +127,10 @@ export const registerTools = (
|
|
|
134
127
|
}
|
|
135
128
|
})
|
|
136
129
|
);
|
|
130
|
+
}
|
|
137
131
|
|
|
138
|
-
|
|
132
|
+
// Listing Cloud Run services
|
|
133
|
+
function registerListServicesTool(server, options) {
|
|
139
134
|
server.registerTool(
|
|
140
135
|
'list_services',
|
|
141
136
|
{
|
|
@@ -144,14 +139,14 @@ export const registerTools = (
|
|
|
144
139
|
project: z
|
|
145
140
|
.string()
|
|
146
141
|
.describe('Google Cloud project ID')
|
|
147
|
-
.default(defaultProjectId),
|
|
142
|
+
.default(options.defaultProjectId),
|
|
148
143
|
region: z
|
|
149
144
|
.string()
|
|
150
145
|
.describe('Region where the services are located')
|
|
151
|
-
.default(defaultRegion),
|
|
146
|
+
.default(options.defaultRegion),
|
|
152
147
|
},
|
|
153
148
|
},
|
|
154
|
-
gcpTool(gcpCredentialsAvailable, async ({ project, region }) => {
|
|
149
|
+
gcpTool(options.gcpCredentialsAvailable, async ({ project, region }) => {
|
|
155
150
|
if (typeof project !== 'string') {
|
|
156
151
|
return {
|
|
157
152
|
content: [
|
|
@@ -191,8 +186,10 @@ export const registerTools = (
|
|
|
191
186
|
}
|
|
192
187
|
})
|
|
193
188
|
);
|
|
189
|
+
}
|
|
194
190
|
|
|
195
|
-
|
|
191
|
+
// Dynamic resource for getting a specific service
|
|
192
|
+
function registerGetServiceTool(server, options) {
|
|
196
193
|
server.registerTool(
|
|
197
194
|
'get_service',
|
|
198
195
|
{
|
|
@@ -201,18 +198,18 @@ export const registerTools = (
|
|
|
201
198
|
project: z
|
|
202
199
|
.string()
|
|
203
200
|
.describe('Google Cloud project ID containing the service')
|
|
204
|
-
.default(defaultProjectId),
|
|
201
|
+
.default(options.defaultProjectId),
|
|
205
202
|
region: z
|
|
206
203
|
.string()
|
|
207
204
|
.describe('Region where the service is located')
|
|
208
|
-
.default(defaultRegion),
|
|
205
|
+
.default(options.defaultRegion),
|
|
209
206
|
service: z
|
|
210
207
|
.string()
|
|
211
208
|
.describe('Name of the Cloud Run service')
|
|
212
|
-
.default(defaultServiceName),
|
|
209
|
+
.default(options.defaultServiceName),
|
|
213
210
|
},
|
|
214
211
|
},
|
|
215
|
-
gcpTool(gcpCredentialsAvailable, async ({ project, region, service }) => {
|
|
212
|
+
gcpTool(options.gcpCredentialsAvailable, async ({ project, region, service }) => {
|
|
216
213
|
if (typeof project !== 'string') {
|
|
217
214
|
return {
|
|
218
215
|
content: [
|
|
@@ -260,8 +257,10 @@ export const registerTools = (
|
|
|
260
257
|
}
|
|
261
258
|
})
|
|
262
259
|
);
|
|
260
|
+
}
|
|
263
261
|
|
|
264
|
-
|
|
262
|
+
// Get logs for a service
|
|
263
|
+
function registerGetServiceLogTool(server, options) {
|
|
265
264
|
server.registerTool(
|
|
266
265
|
'get_service_log',
|
|
267
266
|
{
|
|
@@ -271,18 +270,18 @@ export const registerTools = (
|
|
|
271
270
|
project: z
|
|
272
271
|
.string()
|
|
273
272
|
.describe('Google Cloud project ID containing the service')
|
|
274
|
-
.default(defaultProjectId),
|
|
273
|
+
.default(options.defaultProjectId),
|
|
275
274
|
region: z
|
|
276
275
|
.string()
|
|
277
276
|
.describe('Region where the service is located')
|
|
278
|
-
.default(defaultRegion),
|
|
277
|
+
.default(options.defaultRegion),
|
|
279
278
|
service: z
|
|
280
279
|
.string()
|
|
281
280
|
.describe('Name of the Cloud Run service')
|
|
282
|
-
.default(defaultServiceName),
|
|
281
|
+
.default(options.defaultServiceName),
|
|
283
282
|
},
|
|
284
283
|
},
|
|
285
|
-
gcpTool(gcpCredentialsAvailable, async ({ project, region, service }) => {
|
|
284
|
+
gcpTool(options.gcpCredentialsAvailable, async ({ project, region, service }) => {
|
|
286
285
|
let allLogs = [];
|
|
287
286
|
let requestOptions;
|
|
288
287
|
try {
|
|
@@ -323,83 +322,10 @@ export const registerTools = (
|
|
|
323
322
|
}
|
|
324
323
|
})
|
|
325
324
|
);
|
|
325
|
+
}
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{
|
|
330
|
-
description:
|
|
331
|
-
'Deploy local files to Cloud Run. Takes an array of absolute file paths from the local filesystem that will be deployed. Use this tool if the files exists on the user local filesystem.',
|
|
332
|
-
inputSchema: {
|
|
333
|
-
project: z
|
|
334
|
-
.string()
|
|
335
|
-
.describe(
|
|
336
|
-
'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.'
|
|
337
|
-
)
|
|
338
|
-
.default(defaultProjectId),
|
|
339
|
-
region: z
|
|
340
|
-
.string()
|
|
341
|
-
.optional()
|
|
342
|
-
.default(defaultRegion)
|
|
343
|
-
.describe('Region to deploy the service to'),
|
|
344
|
-
service: z
|
|
345
|
-
.string()
|
|
346
|
-
.optional()
|
|
347
|
-
.default(defaultServiceName)
|
|
348
|
-
.describe('Name of the Cloud Run service to deploy to'),
|
|
349
|
-
files: z
|
|
350
|
-
.array(z.string())
|
|
351
|
-
.describe(
|
|
352
|
-
'Array of absolute file paths to deploy (e.g. ["/home/user/project/src/index.js", "/home/user/project/package.json"])'
|
|
353
|
-
),
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
gcpTool(
|
|
357
|
-
gcpCredentialsAvailable,
|
|
358
|
-
async ({ project, region, service, files }) => {
|
|
359
|
-
if (typeof project !== 'string') {
|
|
360
|
-
throw new Error(
|
|
361
|
-
'Project must specified, please prompt the user for a valid existing Google Cloud project ID.'
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
if (typeof files !== 'object' || !Array.isArray(files)) {
|
|
365
|
-
throw new Error('Files must specified');
|
|
366
|
-
}
|
|
367
|
-
if (files.length === 0) {
|
|
368
|
-
throw new Error('No files specified for deployment');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Deploy to Cloud Run
|
|
372
|
-
try {
|
|
373
|
-
// TODO: Should we return intermediate progress messages? we'd need to use sendNotification for that, see https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/examples/server/jsonResponseStreamableHttp.ts#L46C24-L46C41
|
|
374
|
-
const response = await deploy({
|
|
375
|
-
projectId: project,
|
|
376
|
-
serviceName: service,
|
|
377
|
-
region: region,
|
|
378
|
-
files: files,
|
|
379
|
-
skipIamCheck: skipIamCheck, // Pass the new flag
|
|
380
|
-
});
|
|
381
|
-
return {
|
|
382
|
-
content: [
|
|
383
|
-
{
|
|
384
|
-
type: 'text',
|
|
385
|
-
text: `Cloud Run service ${service} deployed in project ${project}\nCloud Console: https://console.cloud.google.com/run/detail/${region}/${service}?project=${project}\nService URL: ${response.uri}`,
|
|
386
|
-
},
|
|
387
|
-
],
|
|
388
|
-
};
|
|
389
|
-
} catch (error) {
|
|
390
|
-
return {
|
|
391
|
-
content: [
|
|
392
|
-
{
|
|
393
|
-
type: 'text',
|
|
394
|
-
text: `Error deploying to Cloud Run: ${error.message || error}`,
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
)
|
|
401
|
-
);
|
|
402
|
-
|
|
327
|
+
// Tool to deploy to Cloud Run from local folder
|
|
328
|
+
function registerDeployLocalFolderTool(server, options) {
|
|
403
329
|
server.registerTool(
|
|
404
330
|
'deploy_local_folder',
|
|
405
331
|
{
|
|
@@ -411,16 +337,16 @@ export const registerTools = (
|
|
|
411
337
|
.describe(
|
|
412
338
|
'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.'
|
|
413
339
|
)
|
|
414
|
-
.default(defaultProjectId),
|
|
340
|
+
.default(options.defaultProjectId),
|
|
415
341
|
region: z
|
|
416
342
|
.string()
|
|
417
343
|
.optional()
|
|
418
|
-
.default(defaultRegion)
|
|
344
|
+
.default(options.defaultRegion)
|
|
419
345
|
.describe('Region to deploy the service to'),
|
|
420
346
|
service: z
|
|
421
347
|
.string()
|
|
422
348
|
.optional()
|
|
423
|
-
.default(defaultServiceName)
|
|
349
|
+
.default(options.defaultServiceName)
|
|
424
350
|
.describe('Name of the Cloud Run service to deploy to'),
|
|
425
351
|
folderPath: z
|
|
426
352
|
.string()
|
|
@@ -430,8 +356,11 @@ export const registerTools = (
|
|
|
430
356
|
},
|
|
431
357
|
},
|
|
432
358
|
gcpTool(
|
|
433
|
-
gcpCredentialsAvailable,
|
|
434
|
-
async (
|
|
359
|
+
options.gcpCredentialsAvailable,
|
|
360
|
+
async (
|
|
361
|
+
{ project, region, service, folderPath },
|
|
362
|
+
{ sendNotification }
|
|
363
|
+
) => {
|
|
435
364
|
if (typeof project !== 'string') {
|
|
436
365
|
throw new Error(
|
|
437
366
|
'Project must be specified, please prompt the user for a valid existing Google Cloud project ID.'
|
|
@@ -443,14 +372,20 @@ export const registerTools = (
|
|
|
443
372
|
);
|
|
444
373
|
}
|
|
445
374
|
|
|
375
|
+
const progressCallback = createProgressCallback(sendNotification);
|
|
376
|
+
|
|
446
377
|
// Deploy to Cloud Run
|
|
447
378
|
try {
|
|
379
|
+
await progressCallback({
|
|
380
|
+
data: `Starting deployment of local folder for service ${service} in project ${project}...`,
|
|
381
|
+
});
|
|
448
382
|
const response = await deploy({
|
|
449
383
|
projectId: project,
|
|
450
384
|
serviceName: service,
|
|
451
385
|
region: region,
|
|
452
386
|
files: [folderPath],
|
|
453
|
-
skipIamCheck: skipIamCheck, // Pass the new flag
|
|
387
|
+
skipIamCheck: options.skipIamCheck, // Pass the new flag
|
|
388
|
+
progressCallback,
|
|
454
389
|
});
|
|
455
390
|
return {
|
|
456
391
|
content: [
|
|
@@ -473,7 +408,10 @@ export const registerTools = (
|
|
|
473
408
|
}
|
|
474
409
|
)
|
|
475
410
|
);
|
|
411
|
+
}
|
|
476
412
|
|
|
413
|
+
// Tool to deploy to Cloud Run from file contents
|
|
414
|
+
function registerDeployFileContentsTool(server, options) {
|
|
477
415
|
server.registerTool(
|
|
478
416
|
'deploy_file_contents',
|
|
479
417
|
{
|
|
@@ -485,16 +423,16 @@ export const registerTools = (
|
|
|
485
423
|
.describe(
|
|
486
424
|
'Google Cloud project ID. Leave unset for the app to be deployed in a new project. If provided, make sure the user confirms the project ID they want to deploy to.'
|
|
487
425
|
)
|
|
488
|
-
.default(defaultProjectId),
|
|
426
|
+
.default(options.defaultProjectId),
|
|
489
427
|
region: z
|
|
490
428
|
.string()
|
|
491
429
|
.optional()
|
|
492
|
-
.default(defaultRegion)
|
|
430
|
+
.default(options.defaultRegion)
|
|
493
431
|
.describe('Region to deploy the service to'),
|
|
494
432
|
service: z
|
|
495
433
|
.string()
|
|
496
434
|
.optional()
|
|
497
|
-
.default(defaultServiceName)
|
|
435
|
+
.default(options.defaultServiceName)
|
|
498
436
|
.describe('Name of the Cloud Run service to deploy to'),
|
|
499
437
|
files: z
|
|
500
438
|
.array(
|
|
@@ -514,8 +452,8 @@ export const registerTools = (
|
|
|
514
452
|
},
|
|
515
453
|
},
|
|
516
454
|
gcpTool(
|
|
517
|
-
gcpCredentialsAvailable,
|
|
518
|
-
async ({ project, region, service, files }) => {
|
|
455
|
+
options.gcpCredentialsAvailable,
|
|
456
|
+
async ({ project, region, service, files }, { sendNotification }) => {
|
|
519
457
|
if (typeof project !== 'string') {
|
|
520
458
|
throw new Error(
|
|
521
459
|
'Project must specified, please prompt the user for a valid existing Google Cloud project ID.'
|
|
@@ -535,14 +473,20 @@ export const registerTools = (
|
|
|
535
473
|
}
|
|
536
474
|
}
|
|
537
475
|
|
|
476
|
+
const progressCallback = createProgressCallback(sendNotification);
|
|
477
|
+
|
|
538
478
|
// Deploy to Cloud Run
|
|
539
479
|
try {
|
|
480
|
+
await progressCallback({
|
|
481
|
+
data: `Starting deployment of file contents for service ${service} in project ${project}...`,
|
|
482
|
+
});
|
|
540
483
|
const response = await deploy({
|
|
541
484
|
projectId: project,
|
|
542
485
|
serviceName: service,
|
|
543
486
|
region: region,
|
|
544
487
|
files: files,
|
|
545
|
-
skipIamCheck: skipIamCheck, // Pass the new flag
|
|
488
|
+
skipIamCheck: options.skipIamCheck, // Pass the new flag
|
|
489
|
+
progressCallback,
|
|
546
490
|
});
|
|
547
491
|
return {
|
|
548
492
|
content: [
|
|
@@ -565,7 +509,10 @@ export const registerTools = (
|
|
|
565
509
|
}
|
|
566
510
|
)
|
|
567
511
|
);
|
|
512
|
+
}
|
|
568
513
|
|
|
514
|
+
// Tool to deploy to Cloud Run from container image
|
|
515
|
+
function registerDeployContainerImageTool(server, options) {
|
|
569
516
|
server.registerTool(
|
|
570
517
|
'deploy_container_image',
|
|
571
518
|
{
|
|
@@ -577,16 +524,16 @@ export const registerTools = (
|
|
|
577
524
|
.describe(
|
|
578
525
|
'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.'
|
|
579
526
|
)
|
|
580
|
-
.default(defaultProjectId),
|
|
527
|
+
.default(options.defaultProjectId),
|
|
581
528
|
region: z
|
|
582
529
|
.string()
|
|
583
530
|
.optional()
|
|
584
|
-
.default(defaultRegion)
|
|
531
|
+
.default(options.defaultRegion)
|
|
585
532
|
.describe('Region to deploy the service to'),
|
|
586
533
|
service: z
|
|
587
534
|
.string()
|
|
588
535
|
.optional()
|
|
589
|
-
.default(defaultServiceName)
|
|
536
|
+
.default(options.defaultServiceName)
|
|
590
537
|
.describe('Name of the Cloud Run service to deploy to'),
|
|
591
538
|
imageUrl: z
|
|
592
539
|
.string()
|
|
@@ -596,8 +543,8 @@ export const registerTools = (
|
|
|
596
543
|
},
|
|
597
544
|
},
|
|
598
545
|
gcpTool(
|
|
599
|
-
gcpCredentialsAvailable,
|
|
600
|
-
async ({ project, region, service, imageUrl }) => {
|
|
546
|
+
options.gcpCredentialsAvailable,
|
|
547
|
+
async ({ project, region, service, imageUrl }, { sendNotification }) => {
|
|
601
548
|
if (typeof project !== 'string') {
|
|
602
549
|
throw new Error(
|
|
603
550
|
'Project must specified, please prompt the user for a valid existing Google Cloud project ID.'
|
|
@@ -609,6 +556,8 @@ export const registerTools = (
|
|
|
609
556
|
);
|
|
610
557
|
}
|
|
611
558
|
|
|
559
|
+
const progressCallback = createProgressCallback(sendNotification);
|
|
560
|
+
|
|
612
561
|
// Deploy to Cloud Run
|
|
613
562
|
try {
|
|
614
563
|
const response = await deployImage({
|
|
@@ -616,7 +565,8 @@ export const registerTools = (
|
|
|
616
565
|
serviceName: service,
|
|
617
566
|
region: region,
|
|
618
567
|
imageUrl: imageUrl,
|
|
619
|
-
skipIamCheck: skipIamCheck,
|
|
568
|
+
skipIamCheck: options.skipIamCheck,
|
|
569
|
+
progressCallback,
|
|
620
570
|
});
|
|
621
571
|
return {
|
|
622
572
|
content: [
|
|
@@ -639,331 +589,15 @@ export const registerTools = (
|
|
|
639
589
|
}
|
|
640
590
|
)
|
|
641
591
|
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
export const registerToolsRemote = async (
|
|
645
|
-
server,
|
|
646
|
-
{
|
|
647
|
-
defaultProjectId,
|
|
648
|
-
defaultRegion,
|
|
649
|
-
defaultServiceName,
|
|
650
|
-
skipIamCheck = false,
|
|
651
|
-
gcpCredentialsAvailable,
|
|
652
|
-
} = {}
|
|
653
|
-
) => {
|
|
654
|
-
// We no longer call checkGCP here; the effective defaults are passed in.
|
|
655
|
-
const currentProject = defaultProjectId; // Use the passed effective project ID
|
|
656
|
-
const currentRegion = defaultRegion; // Use the passed effective region
|
|
657
|
-
|
|
658
|
-
if (!currentProject) {
|
|
659
|
-
throw new Error(
|
|
660
|
-
'Cannot register remote tools: GCP project ID could not be determined. Please ensure GOOGLE_CLOUD_PROJECT environment variable is set or the server is running on GCP.'
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Listing Cloud Run services (Remote)
|
|
665
|
-
server.registerTool(
|
|
666
|
-
'list_services',
|
|
667
|
-
{
|
|
668
|
-
description: `Lists Cloud Run services in GCP project ${currentProject} and a given region.`,
|
|
669
|
-
inputSchema: {
|
|
670
|
-
region: z
|
|
671
|
-
.string()
|
|
672
|
-
.describe('Region where the services are located')
|
|
673
|
-
.default(currentRegion),
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
gcpTool(gcpCredentialsAvailable, async ({ region }) => {
|
|
677
|
-
try {
|
|
678
|
-
const services = await listServices(currentProject, region);
|
|
679
|
-
const serviceList = services
|
|
680
|
-
.map((s) => {
|
|
681
|
-
const serviceName = s.name.split('/').pop();
|
|
682
|
-
return `- ${serviceName} (URL: ${s.uri})`;
|
|
683
|
-
})
|
|
684
|
-
.join('\n');
|
|
685
|
-
return {
|
|
686
|
-
content: [
|
|
687
|
-
{
|
|
688
|
-
type: 'text',
|
|
689
|
-
text: `Services in project ${currentProject} (location ${region}):\n${serviceList}`,
|
|
690
|
-
},
|
|
691
|
-
],
|
|
692
|
-
};
|
|
693
|
-
} catch (error) {
|
|
694
|
-
return {
|
|
695
|
-
content: [
|
|
696
|
-
{
|
|
697
|
-
type: 'text',
|
|
698
|
-
text: `Error listing services for project ${currentProject} (region ${currentRegion}): ${error.message}`,
|
|
699
|
-
},
|
|
700
|
-
],
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
})
|
|
704
|
-
);
|
|
705
|
-
|
|
706
|
-
// Dynamic resource for getting a specific service (Remote)
|
|
707
|
-
server.registerTool(
|
|
708
|
-
'get_service',
|
|
709
|
-
{
|
|
710
|
-
description: `Gets details for a specific Cloud Run service in GCP project ${currentProject}.`,
|
|
711
|
-
inputSchema: {
|
|
712
|
-
region: z
|
|
713
|
-
.string()
|
|
714
|
-
.describe('Region where the service is located')
|
|
715
|
-
.default(currentRegion),
|
|
716
|
-
service: z
|
|
717
|
-
.string()
|
|
718
|
-
.describe('Name of the Cloud Run service')
|
|
719
|
-
.default(defaultServiceName),
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
gcpTool(gcpCredentialsAvailable, async ({ region, service }) => {
|
|
723
|
-
if (typeof service !== 'string') {
|
|
724
|
-
return {
|
|
725
|
-
content: [
|
|
726
|
-
{ type: 'text', text: 'Error: Service name must be provided.' },
|
|
727
|
-
],
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
try {
|
|
731
|
-
const serviceDetails = await getService(
|
|
732
|
-
currentProject,
|
|
733
|
-
region,
|
|
734
|
-
service
|
|
735
|
-
);
|
|
736
|
-
if (serviceDetails) {
|
|
737
|
-
return {
|
|
738
|
-
content: [
|
|
739
|
-
{
|
|
740
|
-
type: 'text',
|
|
741
|
-
text: `Name: ${service}\nRegion: ${region}\nProject: ${currentProject}\nURL: ${serviceDetails.uri}\nLast deployed by: ${serviceDetails.lastModifier}`,
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
};
|
|
745
|
-
} else {
|
|
746
|
-
return {
|
|
747
|
-
content: [
|
|
748
|
-
{
|
|
749
|
-
type: 'text',
|
|
750
|
-
text: `Service ${service} not found in project ${currentProject} (region ${currentRegion}).`,
|
|
751
|
-
},
|
|
752
|
-
],
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
} catch (error) {
|
|
756
|
-
return {
|
|
757
|
-
content: [
|
|
758
|
-
{
|
|
759
|
-
type: 'text',
|
|
760
|
-
text: `Error getting service ${service} in project ${currentProject} (region ${currentRegion}): ${error.message}`,
|
|
761
|
-
},
|
|
762
|
-
],
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
})
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
// Logs for a service
|
|
769
|
-
server.registerTool(
|
|
770
|
-
'get_service_log',
|
|
771
|
-
{
|
|
772
|
-
description:
|
|
773
|
-
'Gets Logs and Error Messages for a specific Cloud Run service.',
|
|
774
|
-
inputSchema: {
|
|
775
|
-
project: z
|
|
776
|
-
.string()
|
|
777
|
-
.describe('Google Cloud project ID containing the service')
|
|
778
|
-
.default(currentProject), // Use currentProject
|
|
779
|
-
region: z
|
|
780
|
-
.string()
|
|
781
|
-
.describe('Region where the service is located')
|
|
782
|
-
.default(currentRegion), // Use currentRegion
|
|
783
|
-
service: z
|
|
784
|
-
.string()
|
|
785
|
-
.describe('Name of the Cloud Run service')
|
|
786
|
-
.default(defaultServiceName),
|
|
787
|
-
},
|
|
788
|
-
},
|
|
789
|
-
gcpTool(gcpCredentialsAvailable, async ({ project, region, service }) => {
|
|
790
|
-
let allLogs = [];
|
|
791
|
-
let requestOptions;
|
|
792
|
-
try {
|
|
793
|
-
do {
|
|
794
|
-
// Fetch a page of logs
|
|
795
|
-
const response = await getServiceLogs(
|
|
796
|
-
project,
|
|
797
|
-
region,
|
|
798
|
-
service,
|
|
799
|
-
requestOptions
|
|
800
|
-
);
|
|
801
|
-
|
|
802
|
-
if (response.logs) {
|
|
803
|
-
allLogs.push(response.logs);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// Set the requestOptions incl pagintion token for the next iteration
|
|
807
|
-
|
|
808
|
-
requestOptions = response.requestOptions;
|
|
809
|
-
} while (requestOptions); // Continue as long as there is a next page token
|
|
810
|
-
return {
|
|
811
|
-
content: [
|
|
812
|
-
{
|
|
813
|
-
type: 'text',
|
|
814
|
-
text: allLogs.join('\n'),
|
|
815
|
-
},
|
|
816
|
-
],
|
|
817
|
-
};
|
|
818
|
-
} catch (error) {
|
|
819
|
-
return {
|
|
820
|
-
content: [
|
|
821
|
-
{
|
|
822
|
-
type: 'text',
|
|
823
|
-
text: `Error getting Logs for service ${service} in project ${project} (region ${region}): ${error.message}`,
|
|
824
|
-
},
|
|
825
|
-
],
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
})
|
|
829
|
-
);
|
|
830
|
-
|
|
831
|
-
// Deploy file contents to Cloud Run (Remote)
|
|
832
|
-
server.registerTool(
|
|
833
|
-
'deploy_file_contents',
|
|
834
|
-
{
|
|
835
|
-
description: `Deploy files to Cloud Run by providing their contents directly to the GCP project ${currentProject}.`,
|
|
836
|
-
inputSchema: {
|
|
837
|
-
region: z
|
|
838
|
-
.string()
|
|
839
|
-
.optional()
|
|
840
|
-
.default(currentRegion)
|
|
841
|
-
.describe('Region to deploy the service to'),
|
|
842
|
-
service: z
|
|
843
|
-
.string()
|
|
844
|
-
.optional()
|
|
845
|
-
.default(defaultServiceName)
|
|
846
|
-
.describe('Name of the Cloud Run service to deploy to'), // Use defaultServiceName
|
|
847
|
-
files: z
|
|
848
|
-
.array(
|
|
849
|
-
z.object({
|
|
850
|
-
filename: z
|
|
851
|
-
.string()
|
|
852
|
-
.describe(
|
|
853
|
-
'Name and path of the file (e.g. "src/index.js" or "data/config.json")'
|
|
854
|
-
),
|
|
855
|
-
content: z.string().describe('Text content of the file'),
|
|
856
|
-
})
|
|
857
|
-
)
|
|
858
|
-
.describe('Array of file objects containing filename and content'),
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
gcpTool(gcpCredentialsAvailable, async ({ region, service, files }) => {
|
|
862
|
-
console.log(
|
|
863
|
-
`New deploy request (remote): ${JSON.stringify({ project: currentProject, region, service, files })}`
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
if (
|
|
867
|
-
typeof files !== 'object' ||
|
|
868
|
-
!Array.isArray(files) ||
|
|
869
|
-
files.length === 0
|
|
870
|
-
) {
|
|
871
|
-
throw new Error('Files must be specified');
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// Validate that each file has content
|
|
875
|
-
for (const file of files) {
|
|
876
|
-
if (!file.content) {
|
|
877
|
-
throw new Error(`File ${file.filename} must have content`);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Deploy to Cloud Run
|
|
882
|
-
try {
|
|
883
|
-
const response = await deploy({
|
|
884
|
-
projectId: currentProject,
|
|
885
|
-
serviceName: service,
|
|
886
|
-
region: region,
|
|
887
|
-
files: files,
|
|
888
|
-
skipIamCheck: skipIamCheck, // Pass the new flag
|
|
889
|
-
});
|
|
890
|
-
return {
|
|
891
|
-
content: [
|
|
892
|
-
{
|
|
893
|
-
type: 'text',
|
|
894
|
-
text: `Cloud Run service ${service} deployed in project ${currentProject}\nCloud Console: https://console.cloud. google.com/run/detail/${region}/${service}?project=${currentProject}\nService URL: ${response.uri}`,
|
|
895
|
-
},
|
|
896
|
-
],
|
|
897
|
-
};
|
|
898
|
-
} catch (error) {
|
|
899
|
-
return {
|
|
900
|
-
content: [
|
|
901
|
-
{
|
|
902
|
-
type: 'text',
|
|
903
|
-
text: `Error deploying to Cloud Run: ${error.message || error}`,
|
|
904
|
-
},
|
|
905
|
-
],
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
})
|
|
909
|
-
);
|
|
910
|
-
|
|
911
|
-
server.registerTool(
|
|
912
|
-
'deploy_container_image',
|
|
913
|
-
{
|
|
914
|
-
description: `Deploys a container image to Cloud Run in the GCP project ${currentProject}. Use this tool if the user provides a container image URL.`,
|
|
915
|
-
inputSchema: {
|
|
916
|
-
region: z
|
|
917
|
-
.string()
|
|
918
|
-
.optional()
|
|
919
|
-
.default(currentRegion)
|
|
920
|
-
.describe('Region to deploy the service to'),
|
|
921
|
-
service: z
|
|
922
|
-
.string()
|
|
923
|
-
.optional()
|
|
924
|
-
.default(defaultServiceName)
|
|
925
|
-
.describe('Name of the Cloud Run service to deploy to'),
|
|
926
|
-
imageUrl: z
|
|
927
|
-
.string()
|
|
928
|
-
.describe(
|
|
929
|
-
'The URL of the container image to deploy (e.g. "gcr.io/cloudrun/hello")'
|
|
930
|
-
),
|
|
931
|
-
},
|
|
932
|
-
},
|
|
933
|
-
gcpTool(gcpCredentialsAvailable, async ({ region, service, imageUrl }) => {
|
|
934
|
-
if (typeof imageUrl !== 'string' || imageUrl.trim() === '') {
|
|
935
|
-
throw new Error(
|
|
936
|
-
'Container image URL must be specified and be a non-empty string.'
|
|
937
|
-
);
|
|
938
|
-
}
|
|
592
|
+
}
|
|
939
593
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
return {
|
|
950
|
-
content: [
|
|
951
|
-
{
|
|
952
|
-
type: 'text',
|
|
953
|
-
text: `Cloud Run service ${service} deployed in project ${currentProject}\nCloud Console: https://console.cloud. google.com/run/detail/${region}/${service}?project=${currentProject}\nService URL: ${response.uri}`,
|
|
954
|
-
},
|
|
955
|
-
],
|
|
956
|
-
};
|
|
957
|
-
} catch (error) {
|
|
958
|
-
return {
|
|
959
|
-
content: [
|
|
960
|
-
{
|
|
961
|
-
type: 'text',
|
|
962
|
-
text: `Error deploying to Cloud Run: ${error.message || error}`,
|
|
963
|
-
},
|
|
964
|
-
],
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
})
|
|
968
|
-
);
|
|
594
|
+
export {
|
|
595
|
+
registerListProjectsTool,
|
|
596
|
+
registerCreateProjectTool,
|
|
597
|
+
registerListServicesTool,
|
|
598
|
+
registerGetServiceTool,
|
|
599
|
+
registerGetServiceLogTool,
|
|
600
|
+
registerDeployLocalFolderTool,
|
|
601
|
+
registerDeployFileContentsTool,
|
|
602
|
+
registerDeployContainerImageTool,
|
|
969
603
|
};
|