@google-cloud/cloud-run-mcp 1.3.0 → 1.6.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 (44) hide show
  1. package/.c8rc.json +9 -0
  2. package/.github/images/deploy_from_apps.gif +0 -0
  3. package/.github/images/deploy_from_ide.gif +0 -0
  4. package/.github/images/deploycli.gif +0 -0
  5. package/.github/workflows/lint-checker.yml +17 -0
  6. package/.github/workflows/local-tests.yml +1 -1
  7. package/.kokoro/kokoro_build.sh +22 -0
  8. package/.kokoro/presubmit.cfg +21 -0
  9. package/.kokoro/run_tests.sh +31 -0
  10. package/CONTRIBUTING.md +29 -8
  11. package/README.md +65 -19
  12. package/REVIEWING.md +20 -0
  13. package/gemini-extension/GEMINI.md +3 -3
  14. package/lib/cloud-api/build.js +200 -0
  15. package/lib/{gcp-api-helpers.js → cloud-api/helpers.js} +12 -8
  16. package/lib/{gcp-projects.js → cloud-api/projects.js} +58 -28
  17. package/lib/cloud-api/registry.js +98 -0
  18. package/lib/{cloud-run-services.js → cloud-api/run.js} +103 -21
  19. package/lib/cloud-api/storage.js +123 -0
  20. package/lib/deployment/deployer.js +474 -0
  21. package/lib/util/archive.js +95 -0
  22. package/lib/util/helpers.js +42 -0
  23. package/mcp-server.js +29 -11
  24. package/package.json +9 -6
  25. package/test/local/cloud-api/build.test.js +436 -0
  26. package/test/local/cloud-api/projects.test.js +18 -0
  27. package/test/local/mcp-server-streamable-http.test.js +57 -0
  28. package/test/local/mcp-server.test.js +0 -1
  29. package/test/local/notifications.test.js +48 -0
  30. package/test/local/tools.test.js +143 -124
  31. package/test/need-gcp/cloud-run-services.test.js +57 -77
  32. package/test/need-gcp/deployer.test.js +71 -0
  33. package/test/need-gcp/gcp-auth-check.test.js +1 -1
  34. package/test/need-gcp/gcp-projects.test.js +6 -60
  35. package/test/need-gcp/test-helpers.js +175 -0
  36. package/test/need-gcp/workflows/deployment-workflows.test.js +100 -0
  37. package/tools/register-tools.js +612 -0
  38. package/tools/tools.js +48 -0
  39. package/lib/cloud-run-deploy.js +0 -917
  40. package/test/need-gcp/cloud-run-deploy.test.js +0 -158
  41. package/tools.js +0 -969
  42. /package/lib/{gcp-auth-check.js → cloud-api/auth.js} +0 -0
  43. /package/lib/{gcp-billing-accounts.js → cloud-api/billing.js} +0 -0
  44. /package/lib/{gcp-metadata.js → cloud-api/metadata.js} +0 -0
package/tools.js DELETED
@@ -1,969 +0,0 @@
1
- /*
2
- Copyright 2025 Google LLC
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- you may obtain a copy of the License at
7
-
8
- https://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */
16
-
17
- import { z } from 'zod';
18
- import { deploy, deployImage } from './lib/cloud-run-deploy.js';
19
- import {
20
- listServices,
21
- getService,
22
- getServiceLogs,
23
- } from './lib/cloud-run-services.js';
24
- import {
25
- listProjects,
26
- createProjectAndAttachBilling,
27
- } from './lib/gcp-projects.js';
28
-
29
- import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
30
-
31
- function gcpTool(gcpCredentialsAvailable, fn) {
32
- if (!gcpCredentialsAvailable) {
33
- return () => ({
34
- content: [
35
- {
36
- type: 'text',
37
- text: 'GCP credentials are not available. Please configure your environment.',
38
- },
39
- ],
40
- });
41
- }
42
- return fn;
43
- }
44
-
45
- export const registerTools = (
46
- server,
47
- {
48
- defaultProjectId,
49
- defaultRegion,
50
- defaultServiceName,
51
- skipIamCheck,
52
- gcpCredentialsAvailable,
53
- } = {}
54
- ) => {
55
- // Tool to list GCP projects
56
- server.registerTool(
57
- 'list_projects',
58
- {
59
- description: 'Lists available GCP projects',
60
- inputSchema: {},
61
- },
62
- gcpTool(gcpCredentialsAvailable, async () => {
63
- try {
64
- const projects = await listProjects();
65
- return {
66
- content: [
67
- {
68
- type: 'text',
69
- text: `Available GCP Projects:\n${projects.map((p) => `- ${p.id}`).join('\n')}`,
70
- },
71
- ],
72
- };
73
- } catch (error) {
74
- return {
75
- content: [
76
- {
77
- type: 'text',
78
- text: `Error listing GCP projects: ${error.message}`,
79
- },
80
- ],
81
- };
82
- }
83
- })
84
- );
85
-
86
- // Tool to create a new GCP project
87
- server.registerTool(
88
- 'create_project',
89
- {
90
- description:
91
- 'Creates a new GCP project and attempts to attach it to the first available billing account. A project ID can be optionally specified; otherwise it will be automatically generated.',
92
- inputSchema: {
93
- projectId: z
94
- .string()
95
- .optional()
96
- .describe(
97
- 'Optional. The desired ID for the new GCP project. If not provided, an ID will be auto-generated.'
98
- ),
99
- },
100
- },
101
- gcpTool(gcpCredentialsAvailable, async ({ projectId }) => {
102
- if (
103
- projectId !== undefined &&
104
- (typeof projectId !== 'string' || projectId.trim() === '')
105
- ) {
106
- return {
107
- content: [
108
- {
109
- type: 'text',
110
- text: 'Error: If provided, Project ID must be a non-empty string.',
111
- },
112
- ],
113
- };
114
- }
115
- try {
116
- const result = await createProjectAndAttachBilling(projectId);
117
- return {
118
- content: [
119
- {
120
- type: 'text',
121
- text: `Successfully created GCP project with ID "${result.projectId}". You can now use this project ID for deployments.`,
122
- },
123
- ],
124
- };
125
- } catch (error) {
126
- return {
127
- content: [
128
- {
129
- type: 'text',
130
- text: `Error creating GCP project or attaching billing: ${error.message}`,
131
- },
132
- ],
133
- };
134
- }
135
- })
136
- );
137
-
138
- // Listing Cloud Run services
139
- server.registerTool(
140
- 'list_services',
141
- {
142
- description: 'Lists Cloud Run services in a given project and region.',
143
- inputSchema: {
144
- project: z
145
- .string()
146
- .describe('Google Cloud project ID')
147
- .default(defaultProjectId),
148
- region: z
149
- .string()
150
- .describe('Region where the services are located')
151
- .default(defaultRegion),
152
- },
153
- },
154
- gcpTool(gcpCredentialsAvailable, async ({ project, region }) => {
155
- if (typeof project !== 'string') {
156
- return {
157
- content: [
158
- {
159
- type: 'text',
160
- text: 'Error: Project ID must be provided and be a non-empty string.',
161
- },
162
- ],
163
- };
164
- }
165
-
166
- try {
167
- const services = await listServices(project, region);
168
- const serviceList = services
169
- .map((s) => {
170
- const serviceName = s.name.split('/').pop();
171
- return `- ${serviceName} (URL: ${s.uri})`;
172
- })
173
- .join('\n');
174
- return {
175
- content: [
176
- {
177
- type: 'text',
178
- text: `Services in project ${project} (location ${region}):\n${serviceList}`,
179
- },
180
- ],
181
- };
182
- } catch (error) {
183
- return {
184
- content: [
185
- {
186
- type: 'text',
187
- text: `Error listing services for project ${project} (region ${region}): ${error.message}`,
188
- },
189
- ],
190
- };
191
- }
192
- })
193
- );
194
-
195
- // Dynamic resource for getting a specific service
196
- server.registerTool(
197
- 'get_service',
198
- {
199
- description: 'Gets details for a specific Cloud Run service.',
200
- inputSchema: {
201
- project: z
202
- .string()
203
- .describe('Google Cloud project ID containing the service')
204
- .default(defaultProjectId),
205
- region: z
206
- .string()
207
- .describe('Region where the service is located')
208
- .default(defaultRegion),
209
- service: z
210
- .string()
211
- .describe('Name of the Cloud Run service')
212
- .default(defaultServiceName),
213
- },
214
- },
215
- gcpTool(gcpCredentialsAvailable, async ({ project, region, service }) => {
216
- if (typeof project !== 'string') {
217
- return {
218
- content: [
219
- { type: 'text', text: 'Error: Project ID must be provided.' },
220
- ],
221
- };
222
- }
223
- if (typeof service !== 'string') {
224
- return {
225
- content: [
226
- { type: 'text', text: 'Error: Service name must be provided.' },
227
- ],
228
- };
229
- }
230
- try {
231
- const serviceDetails = await getService(project, region, service);
232
- if (serviceDetails) {
233
- return {
234
- content: [
235
- {
236
- type: 'text',
237
- text: `Name: ${service}\nRegion: ${region}\nProject: ${project}\nURL: ${serviceDetails.uri}\nLast deployed by: ${serviceDetails.lastModifier}`,
238
- },
239
- ],
240
- };
241
- } else {
242
- return {
243
- content: [
244
- {
245
- type: 'text',
246
- text: `Service ${service} not found in project ${project} (region ${region}).`,
247
- },
248
- ],
249
- };
250
- }
251
- } catch (error) {
252
- return {
253
- content: [
254
- {
255
- type: 'text',
256
- text: `Error getting service ${service} in project ${project} (region ${region}): ${error.message}`,
257
- },
258
- ],
259
- };
260
- }
261
- })
262
- );
263
-
264
- // Logs for a service
265
- server.registerTool(
266
- 'get_service_log',
267
- {
268
- description:
269
- 'Gets Logs and Error Messages for a specific Cloud Run service.',
270
- inputSchema: {
271
- project: z
272
- .string()
273
- .describe('Google Cloud project ID containing the service')
274
- .default(defaultProjectId),
275
- region: z
276
- .string()
277
- .describe('Region where the service is located')
278
- .default(defaultRegion),
279
- service: z
280
- .string()
281
- .describe('Name of the Cloud Run service')
282
- .default(defaultServiceName),
283
- },
284
- },
285
- gcpTool(gcpCredentialsAvailable, async ({ project, region, service }) => {
286
- let allLogs = [];
287
- let requestOptions;
288
- try {
289
- do {
290
- // Fetch a page of logs
291
- const response = await getServiceLogs(
292
- project,
293
- region,
294
- service,
295
- requestOptions
296
- );
297
-
298
- if (response.logs) {
299
- allLogs.push(response.logs);
300
- }
301
-
302
- // Set the requestOptions incl pagintion token for the next iteration
303
-
304
- requestOptions = response.requestOptions;
305
- } while (requestOptions); // Continue as long as there is a next page token
306
- return {
307
- content: [
308
- {
309
- type: 'text',
310
- text: allLogs.join('\n'),
311
- },
312
- ],
313
- };
314
- } catch (error) {
315
- return {
316
- content: [
317
- {
318
- type: 'text',
319
- text: `Error getting Logs for service ${service} in project ${project} (region ${region}): ${error.message}`,
320
- },
321
- ],
322
- };
323
- }
324
- })
325
- );
326
-
327
- server.registerTool(
328
- 'deploy_local_files',
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
-
403
- server.registerTool(
404
- 'deploy_local_folder',
405
- {
406
- description:
407
- 'Deploy a local folder to Cloud Run. Takes an absolute folder path from the local filesystem that will be deployed. Use this tool if the entire folder content needs to be deployed.',
408
- inputSchema: {
409
- project: z
410
- .string()
411
- .describe(
412
- 'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.'
413
- )
414
- .default(defaultProjectId),
415
- region: z
416
- .string()
417
- .optional()
418
- .default(defaultRegion)
419
- .describe('Region to deploy the service to'),
420
- service: z
421
- .string()
422
- .optional()
423
- .default(defaultServiceName)
424
- .describe('Name of the Cloud Run service to deploy to'),
425
- folderPath: z
426
- .string()
427
- .describe(
428
- 'Absolute path to the folder to deploy (e.g. "/home/user/project/src")'
429
- ),
430
- },
431
- },
432
- gcpTool(
433
- gcpCredentialsAvailable,
434
- async ({ project, region, service, folderPath }) => {
435
- if (typeof project !== 'string') {
436
- throw new Error(
437
- 'Project must be specified, please prompt the user for a valid existing Google Cloud project ID.'
438
- );
439
- }
440
- if (typeof folderPath !== 'string' || folderPath.trim() === '') {
441
- throw new Error(
442
- 'Folder path must be specified and be a non-empty string.'
443
- );
444
- }
445
-
446
- // Deploy to Cloud Run
447
- try {
448
- const response = await deploy({
449
- projectId: project,
450
- serviceName: service,
451
- region: region,
452
- files: [folderPath],
453
- skipIamCheck: skipIamCheck, // Pass the new flag
454
- });
455
- return {
456
- content: [
457
- {
458
- type: 'text',
459
- text: `Cloud Run service ${service} deployed from folder ${folderPath} in project ${project}\nCloud Console: https://console.cloud.google.com/run/detail/${region}/${service}?project=${project}\nService URL: ${response.uri}`,
460
- },
461
- ],
462
- };
463
- } catch (error) {
464
- return {
465
- content: [
466
- {
467
- type: 'text',
468
- text: `Error deploying folder to Cloud Run: ${error.message || error}`,
469
- },
470
- ],
471
- };
472
- }
473
- }
474
- )
475
- );
476
-
477
- server.registerTool(
478
- 'deploy_file_contents',
479
- {
480
- description:
481
- 'Deploy files to Cloud Run by providing their contents directly. Takes an array of file objects containing filename and content. Use this tool if the files only exist in the current chat context.',
482
- inputSchema: {
483
- project: z
484
- .string()
485
- .describe(
486
- '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
- )
488
- .default(defaultProjectId),
489
- region: z
490
- .string()
491
- .optional()
492
- .default(defaultRegion)
493
- .describe('Region to deploy the service to'),
494
- service: z
495
- .string()
496
- .optional()
497
- .default(defaultServiceName)
498
- .describe('Name of the Cloud Run service to deploy to'),
499
- files: z
500
- .array(
501
- z.object({
502
- filename: z
503
- .string()
504
- .describe(
505
- 'Name and path of the file (e.g. "src/index.js" or "data/config.json")'
506
- ),
507
- content: z
508
- .string()
509
- .optional()
510
- .describe('Text content of the file'),
511
- })
512
- )
513
- .describe('Array of file objects containing filename and content'),
514
- },
515
- },
516
- gcpTool(
517
- gcpCredentialsAvailable,
518
- async ({ project, region, service, files }) => {
519
- if (typeof project !== 'string') {
520
- throw new Error(
521
- 'Project must specified, please prompt the user for a valid existing Google Cloud project ID.'
522
- );
523
- }
524
- if (typeof files !== 'object' || !Array.isArray(files)) {
525
- throw new Error('Files must be specified');
526
- }
527
- if (files.length === 0) {
528
- throw new Error('No files specified for deployment');
529
- }
530
-
531
- // Validate that each file has either content
532
- for (const file of files) {
533
- if (!file.content) {
534
- throw new Error(`File ${file.filename} must have content`);
535
- }
536
- }
537
-
538
- // Deploy to Cloud Run
539
- try {
540
- const response = await deploy({
541
- projectId: project,
542
- serviceName: service,
543
- region: region,
544
- files: files,
545
- skipIamCheck: skipIamCheck, // Pass the new flag
546
- });
547
- return {
548
- content: [
549
- {
550
- type: 'text',
551
- 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}`,
552
- },
553
- ],
554
- };
555
- } catch (error) {
556
- return {
557
- content: [
558
- {
559
- type: 'text',
560
- text: `Error deploying to Cloud Run: ${error.message || error}`,
561
- },
562
- ],
563
- };
564
- }
565
- }
566
- )
567
- );
568
-
569
- server.registerTool(
570
- 'deploy_container_image',
571
- {
572
- description:
573
- 'Deploys a container image to Cloud Run. Use this tool if the user provides a container image URL.',
574
- inputSchema: {
575
- project: z
576
- .string()
577
- .describe(
578
- 'Google Cloud project ID. Do not select it yourself, make sure the user provides or confirms the project ID.'
579
- )
580
- .default(defaultProjectId),
581
- region: z
582
- .string()
583
- .optional()
584
- .default(defaultRegion)
585
- .describe('Region to deploy the service to'),
586
- service: z
587
- .string()
588
- .optional()
589
- .default(defaultServiceName)
590
- .describe('Name of the Cloud Run service to deploy to'),
591
- imageUrl: z
592
- .string()
593
- .describe(
594
- 'The URL of the container image to deploy (e.g. "gcr.io/cloudrun/hello")'
595
- ),
596
- },
597
- },
598
- gcpTool(
599
- gcpCredentialsAvailable,
600
- async ({ project, region, service, imageUrl }) => {
601
- if (typeof project !== 'string') {
602
- throw new Error(
603
- 'Project must specified, please prompt the user for a valid existing Google Cloud project ID.'
604
- );
605
- }
606
- if (typeof imageUrl !== 'string' || imageUrl.trim() === '') {
607
- throw new Error(
608
- 'Container image URL must be specified and be a non-empty string.'
609
- );
610
- }
611
-
612
- // Deploy to Cloud Run
613
- try {
614
- const response = await deployImage({
615
- projectId: project,
616
- serviceName: service,
617
- region: region,
618
- imageUrl: imageUrl,
619
- skipIamCheck: skipIamCheck,
620
- });
621
- return {
622
- content: [
623
- {
624
- type: 'text',
625
- 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}`,
626
- },
627
- ],
628
- };
629
- } catch (error) {
630
- return {
631
- content: [
632
- {
633
- type: 'text',
634
- text: `Error deploying to Cloud Run: ${error.message || error}`,
635
- },
636
- ],
637
- };
638
- }
639
- }
640
- )
641
- );
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
- }
939
-
940
- // Deploy to Cloud Run
941
- try {
942
- const response = await deployImage({
943
- projectId: currentProject,
944
- serviceName: service,
945
- region: region,
946
- imageUrl: imageUrl,
947
- skipIamCheck: skipIamCheck,
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
- );
969
- };