@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.
- package/.github/workflows/local-tests.yml +17 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/CONTRIBUTING.md +12 -0
- package/README.md +5 -4
- package/gemini-extension/Gemini.md +57 -0
- package/gemini-extension.json +2 -1
- package/lib/cloud-run-deploy.js +96 -40
- package/lib/gcp-auth-check.js +4 -4
- package/mcp-server.js +5 -2
- package/package.json +9 -9
- package/test/{test-prompts.js → local/prompts.test.js} +1 -1
- package/test/{test-tools.js → local/tools.test.js} +62 -31
- package/test/{test-deploy.js → need-gcp/cloud-run-deploy.test.js} +1 -1
- package/test/{test-service-logs.js → need-gcp/cloud-run-services.test.js} +1 -1
- package/test/{test-gcp-auth.js → need-gcp/gcp-auth-check.test.js} +17 -106
- package/test/{test-create-project.js → need-gcp/gcp-projects.test.js} +1 -1
- package/tools.js +215 -182
- package/test/run-all-tests.js +0 -19
- /package/test/{test-mcp-server.js → local/mcp-server.test.js} +0 -0
|
@@ -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
|
|
121
|
+
## Use as a Gemini CLI extension
|
|
122
122
|
|
|
123
|
-
To install this as a
|
|
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
|
+
```
|
package/gemini-extension.json
CHANGED
package/lib/cloud-run-deploy.js
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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}`;
|
package/lib/gcp-auth-check.js
CHANGED
|
@@ -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
|
|
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<
|
|
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
|
-
|
|
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.
|
|
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/
|
|
13
|
-
"test:create-project": "node test/
|
|
14
|
-
"test:service-logs": "node test/
|
|
15
|
-
"test:gcp-auth": "node test/
|
|
16
|
-
"test:prompts": "node --test test/
|
|
17
|
-
"test:mcp": "node --test test/
|
|
18
|
-
"test:tools": "node --test test/
|
|
19
|
-
"test": "node test/
|
|
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 '
|
|
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('
|
|
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('
|
|
42
|
-
'
|
|
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('
|
|
73
|
-
'
|
|
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('
|
|
105
|
-
'
|
|
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('
|
|
139
|
-
'
|
|
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('
|
|
176
|
-
'
|
|
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('
|
|
227
|
-
'
|
|
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('
|
|
261
|
-
'
|
|
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('
|
|
296
|
-
'
|
|
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('
|
|
331
|
-
'
|
|
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('
|
|
366
|
-
'
|
|
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 '
|
|
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 '
|
|
19
|
+
import { getServiceLogs } from '../../lib/cloud-run-services.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Prompts the user for required service information
|