@google-cloud/cloud-run-mcp 1.6.0 → 1.7.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/npm-publish.yml +1 -1
- package/CONTRIBUTING.md +1 -1
- package/lib/cloud-api/billing.js +85 -0
- package/lib/cloud-api/helpers.js +73 -31
- package/lib/deployment/constants.js +54 -0
- package/lib/deployment/deployer.js +252 -105
- package/lib/deployment/helpers.js +194 -0
- package/lib/deployment/source-processor.js +124 -0
- package/lib/util/archive.js +15 -6
- package/package.json +2 -2
- package/test/local/deployment-helpers.test.js +231 -0
- package/test/local/mcp-server-stdio.test.js +59 -0
- package/test/local/mcp-server-streamable-http.test.js +25 -21
- package/test/local/source-processor.test.js +290 -0
- package/test/local/test-utils.js +51 -0
- package/test/need-gcp/workflows/deployment-workflows.test.js +3 -3
package/CONTRIBUTING.md
CHANGED
|
@@ -126,7 +126,7 @@ This script requires an existing Google Cloud Project ID to be provided when pro
|
|
|
126
126
|
|
|
127
127
|
## Publishing to npm
|
|
128
128
|
|
|
129
|
-
To update the [npm package]https://www.npmjs.com/package/@google-cloud/cloud-run-mcp)
|
|
129
|
+
To update the [npm package](https://www.npmjs.com/package/@google-cloud/cloud-run-mcp)
|
|
130
130
|
|
|
131
131
|
Run the following:
|
|
132
132
|
|
package/lib/cloud-api/billing.js
CHANGED
|
@@ -96,3 +96,88 @@ export async function attachProjectToBillingAccount(
|
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks if billing is enabled for a given project.
|
|
102
|
+
* @async
|
|
103
|
+
* @function isBillingEnabled
|
|
104
|
+
* @param {string} projectId - The ID of the project to check.
|
|
105
|
+
* @returns {Promise<boolean>} A promise that resolves to true if billing is enabled, false otherwise.
|
|
106
|
+
*/
|
|
107
|
+
export async function isBillingEnabled(projectId) {
|
|
108
|
+
const { CloudBillingClient } = await import('@google-cloud/billing');
|
|
109
|
+
const client = new CloudBillingClient();
|
|
110
|
+
const projectName = `projects/${projectId}`;
|
|
111
|
+
try {
|
|
112
|
+
// getProjectBillingInfo requires cloudbilling.googleapis.com API to check billing status
|
|
113
|
+
// https://docs.cloud.google.com/billing/docs/reference/rest/v1/projects/getBillingInfo
|
|
114
|
+
const [billingInfo] = await client.getProjectBillingInfo({
|
|
115
|
+
name: projectName,
|
|
116
|
+
});
|
|
117
|
+
return billingInfo.billingEnabled;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(
|
|
120
|
+
`Error checking billing status for project ${projectId}:`,
|
|
121
|
+
error.message || error
|
|
122
|
+
);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ensures billing is enabled for a project. If not, attempts to enable it
|
|
129
|
+
* by attaching the single open billing account if one is found.
|
|
130
|
+
* @param {object} context - The context object containing clients and other parameters.
|
|
131
|
+
* @param {string} projectId The project ID to check billing for.
|
|
132
|
+
* @param {function} progressCallback A callback function for progress updates.
|
|
133
|
+
*/
|
|
134
|
+
export async function ensureBillingEnabled(
|
|
135
|
+
context,
|
|
136
|
+
projectId,
|
|
137
|
+
progressCallback
|
|
138
|
+
) {
|
|
139
|
+
if (!(await isBillingEnabled(projectId))) {
|
|
140
|
+
// Billing is disabled, try to fix it.
|
|
141
|
+
const accounts = await listBillingAccounts();
|
|
142
|
+
|
|
143
|
+
if (accounts && accounts.length === 1 && accounts[0].open) {
|
|
144
|
+
// Exactly one open account found, try to attach it.
|
|
145
|
+
const account = accounts[0];
|
|
146
|
+
const attemptMessage = `Billing is not enabled for project ${projectId}. Found one open billing account: ${account.displayName} (${account.name}). Attempting to attach it...`;
|
|
147
|
+
console.log(attemptMessage);
|
|
148
|
+
if (progressCallback)
|
|
149
|
+
progressCallback({ level: 'info', data: attemptMessage });
|
|
150
|
+
|
|
151
|
+
const attachmentResult = await attachProjectToBillingAccount(
|
|
152
|
+
projectId,
|
|
153
|
+
account.name
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!attachmentResult || !attachmentResult.billingEnabled) {
|
|
157
|
+
const attachFailMessage = `Failed to automatically attach project ${projectId} to billing account ${account.name}. Please enable billing manually: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
|
|
158
|
+
if (progressCallback)
|
|
159
|
+
progressCallback({ level: 'error', data: attachFailMessage });
|
|
160
|
+
throw new Error(attachFailMessage);
|
|
161
|
+
}
|
|
162
|
+
const attachSuccessMessage = `Successfully attached project ${projectId} to billing account ${account.name}.`;
|
|
163
|
+
console.log(attachSuccessMessage);
|
|
164
|
+
if (progressCallback)
|
|
165
|
+
progressCallback({ level: 'info', data: attachSuccessMessage });
|
|
166
|
+
// If we get here, billing is now enabled, and we can proceed to API checks.
|
|
167
|
+
} else {
|
|
168
|
+
// Cannot auto-attach. Throw error.
|
|
169
|
+
let reason;
|
|
170
|
+
if (!accounts || accounts.length === 0) {
|
|
171
|
+
reason = 'no billing accounts were found';
|
|
172
|
+
} else if (accounts.length > 1) {
|
|
173
|
+
reason = 'multiple billing accounts were found';
|
|
174
|
+
} else {
|
|
175
|
+
reason = `the only available billing account '${accounts[0].displayName}' is not open`;
|
|
176
|
+
}
|
|
177
|
+
const errorMessage = `Billing is not enabled for project ${projectId}, and it could not be enabled automatically because ${reason}. Please enable billing to use Google Cloud services: https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
|
|
178
|
+
if (progressCallback)
|
|
179
|
+
progressCallback({ level: 'error', data: errorMessage });
|
|
180
|
+
throw new Error(errorMessage);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
package/lib/cloud-api/helpers.js
CHANGED
|
@@ -14,6 +14,13 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { ensureBillingEnabled } from './billing.js';
|
|
18
|
+
|
|
19
|
+
const PREREQUISITE_APIS = [
|
|
20
|
+
'serviceusage.googleapis.com',
|
|
21
|
+
'cloudbilling.googleapis.com',
|
|
22
|
+
];
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Calls a function with retry logic for GCP API calls.
|
|
19
26
|
* Retries on gRPC error code 7 (PERMISSION_DENIED).
|
|
@@ -84,6 +91,54 @@ async function checkAndEnableApi(
|
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Enables a single Google Cloud API with retry logic.
|
|
96
|
+
* @param {object} serviceUsageClient - The Service Usage client.
|
|
97
|
+
* @param {string} projectId - The Google Cloud project ID.
|
|
98
|
+
* @param {string} api - The API identifier (e.g., 'run.googleapis.com').
|
|
99
|
+
* @param {function(string, string=): void} progressCallback - A function to call with progress updates.
|
|
100
|
+
* @returns {Promise<void>} A promise that resolves when the API is enabled.
|
|
101
|
+
* @throws {Error} If the API fails to enable or if there's an issue checking its status.
|
|
102
|
+
*/
|
|
103
|
+
export async function enableApiWithRetry(
|
|
104
|
+
serviceUsageClient,
|
|
105
|
+
projectId,
|
|
106
|
+
api,
|
|
107
|
+
progressCallback
|
|
108
|
+
) {
|
|
109
|
+
const serviceName = `projects/${projectId}/services/${api}`;
|
|
110
|
+
try {
|
|
111
|
+
await checkAndEnableApi(
|
|
112
|
+
serviceUsageClient,
|
|
113
|
+
serviceName,
|
|
114
|
+
api,
|
|
115
|
+
progressCallback
|
|
116
|
+
);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// First attempt failed, log a warning and retry once after a delay.
|
|
119
|
+
const warnMsg = `Failed to check/enable ${api}, retrying in 1s...`;
|
|
120
|
+
console.warn(warnMsg);
|
|
121
|
+
if (progressCallback) progressCallback({ level: 'warn', data: warnMsg });
|
|
122
|
+
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
124
|
+
try {
|
|
125
|
+
await checkAndEnableApi(
|
|
126
|
+
serviceUsageClient,
|
|
127
|
+
serviceName,
|
|
128
|
+
api,
|
|
129
|
+
progressCallback
|
|
130
|
+
);
|
|
131
|
+
} catch (retryError) {
|
|
132
|
+
// If the retry also fails, throw an error.
|
|
133
|
+
const errorMessage = `Failed to ensure API [${api}] is enabled after retry. Please check manually.`;
|
|
134
|
+
console.error(errorMessage, retryError);
|
|
135
|
+
if (progressCallback)
|
|
136
|
+
progressCallback({ level: 'error', data: errorMessage });
|
|
137
|
+
throw new Error(errorMessage);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
87
142
|
/**
|
|
88
143
|
* Ensures that the specified Google Cloud APIs are enabled for the given project.
|
|
89
144
|
* If an API is not enabled, it attempts to enable it. Retries any failure once after 1s.
|
|
@@ -104,42 +159,29 @@ export async function ensureApisEnabled(
|
|
|
104
159
|
apis,
|
|
105
160
|
progressCallback
|
|
106
161
|
) {
|
|
162
|
+
for (const api of PREREQUISITE_APIS) {
|
|
163
|
+
await enableApiWithRetry(
|
|
164
|
+
context.serviceUsageClient,
|
|
165
|
+
projectId,
|
|
166
|
+
api,
|
|
167
|
+
progressCallback
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Before enabling other APIs, ensure billing is enabled.
|
|
172
|
+
await ensureBillingEnabled(context, projectId, progressCallback);
|
|
173
|
+
|
|
107
174
|
const message = 'Checking and enabling required APIs...';
|
|
108
175
|
console.log(message);
|
|
109
176
|
if (progressCallback) progressCallback({ level: 'info', data: message });
|
|
110
177
|
|
|
111
178
|
for (const api of apis) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
progressCallback
|
|
119
|
-
);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
// First attempt failed, log a warning and retry once after a delay.
|
|
122
|
-
const warnMsg = `Failed to check/enable ${api}, retrying in 1s...`;
|
|
123
|
-
console.warn(warnMsg);
|
|
124
|
-
if (progressCallback) progressCallback({ level: 'warn', data: warnMsg });
|
|
125
|
-
|
|
126
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
127
|
-
try {
|
|
128
|
-
await checkAndEnableApi(
|
|
129
|
-
context.serviceUsageClient,
|
|
130
|
-
serviceName,
|
|
131
|
-
api,
|
|
132
|
-
progressCallback
|
|
133
|
-
);
|
|
134
|
-
} catch (retryError) {
|
|
135
|
-
// If the retry also fails, throw an error.
|
|
136
|
-
const errorMessage = `Failed to ensure API [${api}] is enabled after retry. Please check manually.`;
|
|
137
|
-
console.error(errorMessage, retryError);
|
|
138
|
-
if (progressCallback)
|
|
139
|
-
progressCallback({ level: 'error', data: errorMessage });
|
|
140
|
-
throw new Error(errorMessage);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
179
|
+
await enableApiWithRetry(
|
|
180
|
+
context.serviceUsageClient,
|
|
181
|
+
projectId,
|
|
182
|
+
api,
|
|
183
|
+
progressCallback
|
|
184
|
+
);
|
|
143
185
|
}
|
|
144
186
|
const successMsg = 'All required APIs are enabled.';
|
|
145
187
|
console.log(successMsg);
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
export const DEPLOYMENT_CONFIG = {
|
|
18
|
+
REPO_NAME: 'mcp-cloud-run-deployments',
|
|
19
|
+
ZIP_FILE_NAME: 'source.zip',
|
|
20
|
+
TARGZ_FILE_NAME: 'source.tar.gz',
|
|
21
|
+
IMAGE_TAG: 'latest',
|
|
22
|
+
LABEL_CREATED_BY: 'cloud-run-mcp',
|
|
23
|
+
DEFAULT_NODE_BASE_IMAGE: 'nodejs22',
|
|
24
|
+
NO_BUILD_IMAGE_TYPE: 'scratch',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const TEMP_PATHS = {
|
|
28
|
+
BASE: '.cloud-run-mcp',
|
|
29
|
+
SUBDIR: 'source',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const REQUIRED_APIS = {
|
|
33
|
+
SOURCE_DEPLOY: [
|
|
34
|
+
'serviceusage.googleapis.com',
|
|
35
|
+
'iam.googleapis.com',
|
|
36
|
+
'storage.googleapis.com',
|
|
37
|
+
'cloudbuild.googleapis.com',
|
|
38
|
+
'artifactregistry.googleapis.com',
|
|
39
|
+
'run.googleapis.com',
|
|
40
|
+
],
|
|
41
|
+
IMAGE_DEPLOY: ['serviceusage.googleapis.com', 'run.googleapis.com'],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const DEPLOYMENT_TYPES = {
|
|
45
|
+
NO_BUILD: 'no-build',
|
|
46
|
+
IMAGE: 'image',
|
|
47
|
+
WITH_BUILD: 'with-build',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const RUNTIMES = {
|
|
51
|
+
NODEJS: 'nodejs',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const MAX_ALLOWED_DIRECT_SOURCE_SIZE_BYTES = 250 * 1024 * 1024; // 250 MiB
|