@google-cloud/cloud-run-mcp 1.0.0 → 1.2.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.
@@ -0,0 +1,17 @@
1
+ name: Run Local Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version: 20
16
+ - run: npm ci
17
+ - run: npm test
@@ -0,0 +1,23 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish-npm:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ node-version: 20
15
+ - run: npm ci
16
+ # Now configure with the publish service for install.
17
+ - uses: actions/setup-node@v4
18
+ with:
19
+ node-version: 20
20
+ registry-url: https://wombat-dressing-room.appspot.com/
21
+ - run: npm publish
22
+ env:
23
+ NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
package/CONTRIBUTING.md CHANGED
@@ -105,3 +105,15 @@ npm run test:deploy
105
105
  ```
106
106
 
107
107
  This script requires an existing Google Cloud Project ID to be provided when prompted or as a command-line argument.
108
+
109
+ ## Publishing to npm
110
+
111
+ To update the [npm package]https://www.npmjs.com/package/@google-cloud/cloud-run-mcp)
112
+
113
+ Run the following:
114
+
115
+ * `npm test`
116
+ * `npm version minor`
117
+ * `git push --follow-tags`
118
+
119
+ Then a [GitHub Action]https://github.com/GoogleCloudPlatform/cloud-run-mcp/blob/main/.github/workflows/npm-publish.yml) automatically publishes to npm when new releases are pushes.
package/README.md CHANGED
@@ -118,13 +118,14 @@ See Docker's [MCP catalog](https://hub.docker.com/mcp/server/cloud-run-mcp/overv
118
118
  }
119
119
  ```
120
120
 
121
- ## Use as a `gemini-cli` extension
121
+ ## Use as a Gemini CLI extension
122
122
 
123
- To install this as a `gemini-cli` extension, run the following command:
123
+ To install this as a [Gemini CLI](https://github.com/google-gemini/gemini-cli) extension, run the following command:
124
124
 
125
125
  ```bash
126
- mkdir -p ~/.gemini/extensions/cloud-run && \
127
- curl -s -L https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-run-mcp/main/gemini-extension.json > ~/.gemini/extensions/cloud-run/gemini-extension.json
126
+ mkdir -p ~/.gemini/extensions/cloud-run/gemini-extension && \
127
+ curl -s -L https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-run-mcp/main/gemini-extension.json > ~/.gemini/extensions/cloud-run/gemini-extension.json && \
128
+ curl -s -L https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-run-mcp/main/gemini-extension/Gemini.md > ~/.gemini/extensions/cloud-run/gemini-extension/Gemini.md
128
129
  ```
129
130
 
130
131
  ## Use as remote MCP server
@@ -0,0 +1,57 @@
1
+ # Cloud Run MCP Server
2
+
3
+ ## Code that can be deployed
4
+
5
+ Only web servers can be deployed using this MCP server.
6
+ The code needs to listen for HTTP reqeusts on the port defined by the $PORT environment variable or 8080.
7
+
8
+ ### Supported languages
9
+
10
+ - If the code is in Node.js, Python, Go, Java, .NET, PHP, Ruby, a Dockerfile is not needed.
11
+ - If the code is in another language, or has any custom dependency needs, a Dockerfile is needed.
12
+
13
+ ### Static-only apps
14
+
15
+ To deploy static-only applications, create a Dockerfile that serves these static files. For example using `nginx`:
16
+
17
+ `Dockerfile`
18
+
19
+ ```
20
+ FROM nginx:stable
21
+
22
+ COPY ./static /var/www
23
+ COPY ./nginx.conf /etc/nginx/conf.d/default.conf
24
+
25
+ CMD ["nginx", "-g", "daemon off;"]
26
+ ```
27
+
28
+ `nginx.conf`:
29
+
30
+ ```
31
+ server {
32
+ listen 8080;
33
+ server_name _;
34
+
35
+ root /var/www/;
36
+ index index.html;
37
+
38
+ # Force all paths to load either itself (js files) or go through index.html.
39
+ location / {
40
+ try_files $uri /index.html;
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Google Cloud pre-requisities
46
+
47
+ The user must have an existing Google Cloud account with billing set up, and ideally an existing Google Cloud project.
48
+
49
+ If deployment fails because of an access or IAM error, it is likely that the user doesn't have Google Cloud credentials on the local machine.
50
+ The user mush follow these steps:
51
+
52
+ 1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) and authenticate with their Google account.
53
+
54
+ 2. Set up application credentials using the command:
55
+ ```bash
56
+ gcloud auth application-default login
57
+ ```
@@ -6,5 +6,6 @@
6
6
  "command": "npx",
7
7
  "args": ["-y", "https://github.com/GoogleCloudPlatform/cloud-run-mcp"]
8
8
  }
9
- }
9
+ },
10
+ "contextFileName": "gemini-extension/GEMINI.md"
10
11
  }
@@ -63,9 +63,40 @@ function logAndProgress(message, progressCallback, severity = 'info') {
63
63
  }
64
64
  }
65
65
 
66
+ /**
67
+ * Checks if a single Google Cloud API is enabled and enables it if not.
68
+ *
69
+ * @param {object} serviceUsageClient - The Service Usage client.
70
+ * @param {string} serviceName - The full name of the service (e.g., 'projects/my-project/services/run.googleapis.com').
71
+ * @param {string} api - The API identifier (e.g., 'run.googleapis.com').
72
+ * @param {function(string, string=): void} progressCallback - A function to call with progress updates.
73
+ * @returns {Promise<void>} A promise that resolves when the API is enabled.
74
+ * @throws {Error} If the API fails to enable or if there's an issue checking its status.
75
+ */
76
+ async function checkAndEnableApi(
77
+ serviceUsageClient,
78
+ serviceName,
79
+ api,
80
+ progressCallback
81
+ ) {
82
+ const [service] = await serviceUsageClient.getService({
83
+ name: serviceName,
84
+ });
85
+ if (service.state !== 'ENABLED') {
86
+ logAndProgress(
87
+ `API [${api}] is not enabled. Enabling...`,
88
+ progressCallback
89
+ );
90
+ const [operation] = await serviceUsageClient.enableService({
91
+ name: serviceName,
92
+ });
93
+ await operation.promise();
94
+ }
95
+ }
96
+
66
97
  /**
67
98
  * Ensures that the specified Google Cloud APIs are enabled for the given project.
68
- * If an API is not enabled, it attempts to enable it.
99
+ * If an API is not enabled, it attempts to enable it. Retries any failure once after 1s.
69
100
  * Throws an error if an API cannot be enabled.
70
101
  *
71
102
  * @async
@@ -84,24 +115,34 @@ async function ensureApisEnabled(projectId, apis, progressCallback) {
84
115
  for (const api of apis) {
85
116
  const serviceName = `projects/${projectId}/services/${api}`;
86
117
  try {
87
- const [service] = await serviceUsageClient.getService({
88
- name: serviceName,
89
- });
90
- if (service.state !== 'ENABLED') {
91
- logAndProgress(
92
- `API [${api}] is not enabled. Enabling...`,
118
+ await checkAndEnableApi(
119
+ serviceUsageClient,
120
+ serviceName,
121
+ api,
122
+ progressCallback
123
+ );
124
+ } catch (error) {
125
+ // First attempt failed, log a warning and retry once after a delay.
126
+ logAndProgress(
127
+ `Failed to check/enable ${api}, retrying in 1s...`,
128
+ progressCallback,
129
+ 'warn'
130
+ );
131
+ await new Promise((resolve) => setTimeout(resolve, 1000));
132
+ try {
133
+ await checkAndEnableApi(
134
+ serviceUsageClient,
135
+ serviceName,
136
+ api,
93
137
  progressCallback
94
138
  );
95
- const [operation] = await serviceUsageClient.enableService({
96
- name: serviceName,
97
- });
98
- await operation.promise();
139
+ } catch (retryError) {
140
+ // If the retry also fails, throw an error.
141
+ const errorMessage = `Failed to ensure API [${api}] is enabled after retry. Please check manually.`;
142
+ console.error(errorMessage, retryError);
143
+ logAndProgress(errorMessage, progressCallback, 'error');
144
+ throw new Error(errorMessage);
99
145
  }
100
- } catch (error) {
101
- const errorMessage = `Failed to ensure API [${api}] is enabled. Please check manually.`;
102
- console.error(errorMessage, error);
103
- logAndProgress(errorMessage, progressCallback, 'error');
104
- throw new Error(errorMessage);
105
146
  }
106
147
  }
107
148
  logAndProgress('All required APIs are enabled.', progressCallback);
@@ -500,30 +541,45 @@ async function ensureArtifactRegistryRepoExists(
500
541
  const repositoryToCreate = {
501
542
  format: format,
502
543
  };
503
- try {
504
- const [operation] = await artifactRegistryClient.createRepository({
505
- parent: parent,
506
- repository: repositoryToCreate,
507
- repositoryId: repositoryId,
508
- });
509
- logAndProgress(
510
- `Creating Artifact Registry repository ${repositoryId}...`,
511
- progressCallback
512
- );
513
- const [result] = await operation.promise();
514
- logAndProgress(
515
- `Artifact Registry repository ${result.name} created successfully.`,
516
- progressCallback
517
- );
518
- return result;
519
- } catch (createError) {
520
- const errorMessage = `Failed to create Artifact Registry repository ${repositoryId}. Error details: ${createError.message}`;
521
- console.error(
522
- `Failed to create Artifact Registry repository ${repositoryId}. Error details:`,
523
- createError
524
- );
525
- logAndProgress(errorMessage, progressCallback, 'error');
526
- throw createError;
544
+ const maxRetries = 3;
545
+ const retryDelay = 10000; // 10s
546
+
547
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
548
+ try {
549
+ const [operation] = await artifactRegistryClient.createRepository({
550
+ parent: parent,
551
+ repository: repositoryToCreate,
552
+ repositoryId: repositoryId,
553
+ });
554
+ logAndProgress(
555
+ `Creating Artifact Registry repository ${repositoryId}...`,
556
+ progressCallback
557
+ );
558
+ const [result] = await operation.promise();
559
+ logAndProgress(
560
+ `Artifact Registry repository ${result.name} created successfully.`,
561
+ progressCallback
562
+ );
563
+ return result;
564
+ } catch (createError) {
565
+ // PERMISSION_DENIED is code 7 for gRPC. Retry on this error.
566
+ if (createError.code === 7 && attempt < maxRetries) {
567
+ logAndProgress(
568
+ `Attempt ${attempt} to create repository failed. This can happen on new projects. Retrying in 10s...`,
569
+ progressCallback,
570
+ 'warn'
571
+ );
572
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
573
+ } else {
574
+ const errorMessage = `Failed to create Artifact Registry repository ${repositoryId}. Error details: ${createError.message}`;
575
+ console.error(
576
+ `Failed to create Artifact Registry repository ${repositoryId}. Error details:`,
577
+ createError
578
+ );
579
+ logAndProgress(errorMessage, progressCallback, 'error');
580
+ throw createError;
581
+ }
582
+ }
527
583
  }
528
584
  } else {
529
585
  const errorMessage = `Error checking/creating repository ${repositoryId}: ${error.message}`;
@@ -19,10 +19,9 @@ import { GoogleAuth } from 'google-auth-library';
19
19
  /**
20
20
  * Checks for the presence of properly setup Google Cloud Platform (GCP) authentication
21
21
  * using the official Google Auth Library for Node.js. If GCP auth is not found, it logs an
22
- * error message and exits the process, instructing the user on how to set them up.
23
- * This check is primarily for local/stdio environments where explicit setup is often needed.
22
+ * error message and returns false.
24
23
  * @async
25
- * @returns {Promise<void>} A promise that resolves if GCP auth are found, or rejects and exits the process if not.
24
+ * @returns {Promise<boolean>} A promise that resolves to true if GCP auth are found, and false otherwise.
26
25
  */
27
26
  export async function ensureGCPCredentials() {
28
27
  console.error('Checking for Google Cloud Application Default Credentials...');
@@ -35,6 +34,7 @@ export async function ensureGCPCredentials() {
35
34
  await client.getAccessToken();
36
35
 
37
36
  console.log('Application Default Credentials found.');
37
+ return true;
38
38
  } catch (error) {
39
39
  console.error(
40
40
  'ERROR: Google Cloud Application Default Credentials are not set up.'
@@ -74,6 +74,6 @@ export async function ensureGCPCredentials() {
74
74
  if (error.stack) {
75
75
  console.error('Error stack:', error.stack);
76
76
  }
77
- // process.exit(1);
77
+ return false;
78
78
  }
79
79
  }
package/mcp-server.js CHANGED
@@ -30,6 +30,7 @@ import { ensureGCPCredentials } from './lib/gcp-auth-check.js';
30
30
  import 'dotenv/config';
31
31
 
32
32
  const gcpInfo = await checkGCP();
33
+ let gcpCredentialsAvailable = false;
33
34
 
34
35
  /**
35
36
  * Ensure that console.log and console.error are compatible with stdio.
@@ -52,7 +53,6 @@ function shouldStartStdio() {
52
53
 
53
54
  if (shouldStartStdio()) {
54
55
  makeLoggingCompatibleWithStdio();
55
- await ensureGCPCredentials();
56
56
  }
57
57
 
58
58
  // Read default configurations from environment variables
@@ -88,6 +88,7 @@ async function getServer() {
88
88
  defaultRegion: effectiveRegion,
89
89
  defaultServiceName,
90
90
  skipIamCheck,
91
+ gcpCredentialsAvailable,
91
92
  });
92
93
  } else {
93
94
  console.log(
@@ -99,6 +100,7 @@ async function getServer() {
99
100
  defaultRegion: effectiveRegion,
100
101
  defaultServiceName,
101
102
  skipIamCheck,
103
+ gcpCredentialsAvailable,
102
104
  });
103
105
  }
104
106
 
@@ -110,13 +112,14 @@ async function getServer() {
110
112
 
111
113
  // stdio
112
114
  if (shouldStartStdio()) {
115
+ gcpCredentialsAvailable = await ensureGCPCredentials();
113
116
  const stdioTransport = new StdioServerTransport();
114
117
  const server = await getServer();
115
118
  await server.connect(stdioTransport);
116
119
  console.log('Cloud Run MCP server stdio transport connected');
117
120
  } else {
118
121
  console.log('Running on GCP, stdio transport will not be started.');
119
- await ensureGCPCredentials();
122
+ gcpCredentialsAvailable = await ensureGCPCredentials();
120
123
  const app = express();
121
124
  app.use(express.json());
122
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@google-cloud/cloud-run-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Cloud Run MCP deployment tool",
6
6
  "main": "mcp-server.js",
@@ -9,14 +9,14 @@
9
9
  },
10
10
  "scripts": {
11
11
  "deploy": "gcloud run deploy cloud-run-mcp --source . --no-invoker-iam-check",
12
- "test:deploy": "node test/test-deploy.js",
13
- "test:create-project": "node test/test-create-project.js",
14
- "test:service-logs": "node test/test-service-logs.js",
15
- "test:gcp-auth": "node test/test-gcp-auth.js",
16
- "test:prompts": "node --test test/test-prompts.js",
17
- "test:mcp": "node --test test/test-mcp-server.js",
18
- "test:tools": "node --test test/test-tools.js",
19
- "test": "node test/run-all-tests.js",
12
+ "test:deploy": "node test/need-gcp/cloud-run-deploy.test.js",
13
+ "test:create-project": "node test/need-gcp/gcp-projects.test.js",
14
+ "test:service-logs": "node test/need-gcp/cloud-run-services.test.js",
15
+ "test:gcp-auth": "node test/need-gcp/gcp-auth-check.test.js",
16
+ "test:prompts": "node --test test/local/prompts.test.js",
17
+ "test:mcp": "node --test test/local/mcp-server.test.js",
18
+ "test:tools": "node --test test/local/tools.test.js",
19
+ "test": "node --test test/local/*.test.js",
20
20
  "mcp-inspector": "npx @modelcontextprotocol/inspector node mcp-server.js",
21
21
  "start": "node mcp-server.js",
22
22
  "lint:check": "prettier --check .",
@@ -16,7 +16,7 @@ limitations under the License.
16
16
 
17
17
  import assert from 'node:assert/strict';
18
18
  import { describe, it, mock } from 'node:test';
19
- import { registerPrompts } from '../prompts.js';
19
+ import { registerPrompts } from '../../prompts.js';
20
20
 
21
21
  describe('registerPrompts', () => {
22
22
  it('should register deploy and logs prompts', () => {
@@ -8,7 +8,7 @@ describe('registerTools', () => {
8
8
  registerTool: mock.fn(),
9
9
  };
10
10
 
11
- const { registerTools } = await esmock('../tools.js', {});
11
+ const { registerTools } = await esmock('../../tools.js', {});
12
12
 
13
13
  registerTools(server);
14
14
 
@@ -38,14 +38,14 @@ describe('registerTools', () => {
38
38
  registerTool: mock.fn(),
39
39
  };
40
40
 
41
- const { registerTools } = await esmock('../tools.js', {
42
- '../lib/gcp-projects.js': {
41
+ const { registerTools } = await esmock('../../tools.js', {
42
+ '../../lib/gcp-projects.js': {
43
43
  listProjects: () =>
44
44
  Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
45
45
  },
46
46
  });
47
47
 
48
- registerTools(server);
48
+ registerTools(server, { gcpCredentialsAvailable: true });
49
49
 
50
50
  const handler = server.registerTool.mock.calls.find(
51
51
  (call) => call.arguments[0] === 'list_projects'
@@ -69,8 +69,8 @@ describe('registerTools', () => {
69
69
  registerTool: mock.fn(),
70
70
  };
71
71
 
72
- const { registerTools } = await esmock('../tools.js', {
73
- '../lib/gcp-projects.js': {
72
+ const { registerTools } = await esmock('../../tools.js', {
73
+ '../../lib/gcp-projects.js': {
74
74
  createProjectAndAttachBilling: (projectId) =>
75
75
  Promise.resolve({
76
76
  projectId: projectId,
@@ -79,7 +79,7 @@ describe('registerTools', () => {
79
79
  },
80
80
  });
81
81
 
82
- registerTools(server);
82
+ registerTools(server, { gcpCredentialsAvailable: true });
83
83
 
84
84
  const handler = server.registerTool.mock.calls.find(
85
85
  (call) => call.arguments[0] === 'create_project'
@@ -101,8 +101,8 @@ describe('registerTools', () => {
101
101
  registerTool: mock.fn(),
102
102
  };
103
103
 
104
- const { registerTools } = await esmock('../tools.js', {
105
- '../lib/gcp-projects.js': {
104
+ const { registerTools } = await esmock('../../tools.js', {
105
+ '../../lib/gcp-projects.js': {
106
106
  createProjectAndAttachBilling: () =>
107
107
  Promise.resolve({
108
108
  projectId: 'generated-project',
@@ -111,7 +111,7 @@ describe('registerTools', () => {
111
111
  },
112
112
  });
113
113
 
114
- registerTools(server);
114
+ registerTools(server, { gcpCredentialsAvailable: true });
115
115
 
116
116
  const handler = server.registerTool.mock.calls.find(
117
117
  (call) => call.arguments[0] === 'create_project'
@@ -135,8 +135,8 @@ describe('registerTools', () => {
135
135
  registerTool: mock.fn(),
136
136
  };
137
137
 
138
- const { registerTools } = await esmock('../tools.js', {
139
- '../lib/cloud-run-services.js': {
138
+ const { registerTools } = await esmock('../../tools.js', {
139
+ '../../lib/cloud-run-services.js': {
140
140
  listServices: () =>
141
141
  Promise.resolve([
142
142
  { name: 'service1', uri: 'uri1' },
@@ -145,7 +145,7 @@ describe('registerTools', () => {
145
145
  },
146
146
  });
147
147
 
148
- registerTools(server);
148
+ registerTools(server, { gcpCredentialsAvailable: true });
149
149
 
150
150
  const handler = server.registerTool.mock.calls.find(
151
151
  (call) => call.arguments[0] === 'list_services'
@@ -172,8 +172,8 @@ describe('registerTools', () => {
172
172
  registerTool: mock.fn(),
173
173
  };
174
174
 
175
- const { registerTools } = await esmock('../tools.js', {
176
- '../lib/cloud-run-services.js': {
175
+ const { registerTools } = await esmock('../../tools.js', {
176
+ '../../lib/cloud-run-services.js': {
177
177
  getService: () =>
178
178
  Promise.resolve({
179
179
  name: 'my-service',
@@ -183,7 +183,7 @@ describe('registerTools', () => {
183
183
  },
184
184
  });
185
185
 
186
- registerTools(server);
186
+ registerTools(server, { gcpCredentialsAvailable: true });
187
187
 
188
188
  const handler = server.registerTool.mock.calls.find(
189
189
  (call) => call.arguments[0] === 'get_service'
@@ -223,13 +223,13 @@ describe('registerTools', () => {
223
223
  return Promise.resolve({ logs: 'log3\nlog4', requestOptions: null });
224
224
  };
225
225
 
226
- const { registerTools } = await esmock('../tools.js', {
227
- '../lib/cloud-run-services.js': {
226
+ const { registerTools } = await esmock('../../tools.js', {
227
+ '../../lib/cloud-run-services.js': {
228
228
  getServiceLogs: getServiceLogs,
229
229
  },
230
230
  });
231
231
 
232
- registerTools(server);
232
+ registerTools(server, { gcpCredentialsAvailable: true });
233
233
 
234
234
  const handler = server.registerTool.mock.calls.find(
235
235
  (call) => call.arguments[0] === 'get_service_log'
@@ -257,13 +257,13 @@ describe('registerTools', () => {
257
257
  registerTool: mock.fn(),
258
258
  };
259
259
 
260
- const { registerTools } = await esmock('../tools.js', {
261
- '../lib/cloud-run-deploy.js': {
260
+ const { registerTools } = await esmock('../../tools.js', {
261
+ '../../lib/cloud-run-deploy.js': {
262
262
  deploy: () => Promise.resolve({ uri: 'my-uri' }),
263
263
  },
264
264
  });
265
265
 
266
- registerTools(server);
266
+ registerTools(server, { gcpCredentialsAvailable: true });
267
267
 
268
268
  const handler = server.registerTool.mock.calls.find(
269
269
  (call) => call.arguments[0] === 'deploy_local_files'
@@ -292,13 +292,13 @@ describe('registerTools', () => {
292
292
  registerTool: mock.fn(),
293
293
  };
294
294
 
295
- const { registerTools } = await esmock('../tools.js', {
296
- '../lib/cloud-run-deploy.js': {
295
+ const { registerTools } = await esmock('../../tools.js', {
296
+ '../../lib/cloud-run-deploy.js': {
297
297
  deploy: () => Promise.resolve({ uri: 'my-uri' }),
298
298
  },
299
299
  });
300
300
 
301
- registerTools(server);
301
+ registerTools(server, { gcpCredentialsAvailable: true });
302
302
 
303
303
  const handler = server.registerTool.mock.calls.find(
304
304
  (call) => call.arguments[0] === 'deploy_local_folder'
@@ -327,13 +327,13 @@ describe('registerTools', () => {
327
327
  registerTool: mock.fn(),
328
328
  };
329
329
 
330
- const { registerTools } = await esmock('../tools.js', {
331
- '../lib/cloud-run-deploy.js': {
330
+ const { registerTools } = await esmock('../../tools.js', {
331
+ '../../lib/cloud-run-deploy.js': {
332
332
  deploy: () => Promise.resolve({ uri: 'my-uri' }),
333
333
  },
334
334
  });
335
335
 
336
- registerTools(server);
336
+ registerTools(server, { gcpCredentialsAvailable: true });
337
337
 
338
338
  const handler = server.registerTool.mock.calls.find(
339
339
  (call) => call.arguments[0] === 'deploy_file_contents'
@@ -362,13 +362,13 @@ describe('registerTools', () => {
362
362
  registerTool: mock.fn(),
363
363
  };
364
364
 
365
- const { registerTools } = await esmock('../tools.js', {
366
- '../lib/cloud-run-deploy.js': {
365
+ const { registerTools } = await esmock('../../tools.js', {
366
+ '../../lib/cloud-run-deploy.js': {
367
367
  deployImage: () => Promise.resolve({ uri: 'my-uri' }),
368
368
  },
369
369
  });
370
370
 
371
- registerTools(server);
371
+ registerTools(server, { gcpCredentialsAvailable: true });
372
372
 
373
373
  const handler = server.registerTool.mock.calls.find(
374
374
  (call) => call.arguments[0] === 'deploy_container_image'
@@ -390,4 +390,35 @@ describe('registerTools', () => {
390
390
  });
391
391
  });
392
392
  });
393
+
394
+ describe('when gcp credentials are not available', () => {
395
+ it('should return an error for all tools', async () => {
396
+ const server = {
397
+ registerTool: mock.fn(),
398
+ };
399
+
400
+ const { registerTools } = await esmock('../../tools.js', {});
401
+
402
+ registerTools(server, { gcpCredentialsAvailable: false });
403
+
404
+ const toolNames = server.registerTool.mock.calls.map(
405
+ (call) => call.arguments[0]
406
+ );
407
+
408
+ for (const toolName of toolNames) {
409
+ const handler = server.registerTool.mock.calls.find(
410
+ (call) => call.arguments[0] === toolName
411
+ ).arguments[2];
412
+ const result = await handler({});
413
+ assert.deepStrictEqual(result, {
414
+ content: [
415
+ {
416
+ type: 'text',
417
+ text: 'GCP credentials are not available. Please configure your environment.',
418
+ },
419
+ ],
420
+ });
421
+ }
422
+ });
423
+ });
393
424
  });
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { deploy, deployImage } from '../lib/cloud-run-deploy.js';
17
+ import { deploy, deployImage } from '../../lib/cloud-run-deploy.js';
18
18
  import readline from 'readline/promises';
19
19
  import { stdin as input, stdout as output } from 'process';
20
20
  import fs from 'fs/promises';
@@ -16,7 +16,7 @@ limitations under the License.
16
16
 
17
17
  import readline from 'readline/promises';
18
18
  import { stdin as input, stdout as output } from 'process';
19
- import { getServiceLogs } from '../lib/cloud-run-services.js';
19
+ import { getServiceLogs } from '../../lib/cloud-run-services.js';
20
20
 
21
21
  /**
22
22
  * Prompts the user for required service information