@google-cloud/cloud-run-mcp 1.8.0 → 1.10.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/.env.example ADDED
@@ -0,0 +1,13 @@
1
+ PORT = 3000
2
+ GCP_STDIO = false
3
+
4
+ OAUTH_ENABLED = true
5
+ GOOGLE_OAUTH_CLIENT_ID = YOUR_CLIENT_ID
6
+ GOOGLE_OAUTH_CLIENT_SECRET = YOUR_CLIENT_SECRET
7
+ GOOGLE_OAUTH_AUDIENCE = YOUR_AUDIENCE # generally, same as your client id
8
+ GOOGLE_OAUTH_REDIRECT_URI = http://localhost:7777/oauth/callback
9
+
10
+ OAUTH_PROTECTED_RESOURCE = http://localhost:${PORT}/mcp
11
+ OAUTH_AUTHORIZATION_SERVER = http://localhost:${PORT}/auth/google
12
+ OAUTH_AUTHORIZATION_ENDPOINT = https://accounts.google.com/o/oauth2/v2/auth
13
+ OAUTH_TOKEN_ENDPOINT = https://oauth2.googleapis.com/token
@@ -0,0 +1,13 @@
1
+ PORT = 3000
2
+ GCP_STDIO = false
3
+
4
+ OAUTH_ENABLED = true
5
+ GOOGLE_OAUTH_CLIENT_ID = 32555940559.apps.googleusercontent.com
6
+ GOOGLE_OAUTH_CLIENT_SECRET = ZmssLNjJy2998hD4CTg2ejr2
7
+ GOOGLE_OAUTH_AUDIENCE = 32555940559.apps.googleusercontent.com
8
+ GOOGLE_OAUTH_REDIRECT_URI = http://localhost:7777/oauth/callback
9
+
10
+ OAUTH_PROTECTED_RESOURCE = http://localhost:${PORT}/mcp
11
+ OAUTH_AUTHORIZATION_SERVER = http://localhost:${PORT}/auth/google
12
+ OAUTH_AUTHORIZATION_ENDPOINT = https://accounts.google.com/o/oauth2/v2/auth
13
+ OAUTH_TOKEN_ENDPOINT = https://oauth2.googleapis.com/token
package/README.md CHANGED
@@ -235,4 +235,42 @@ With this option, you will only be able to deploy code to the same Google Cloud
235
235
  }
236
236
  ```
237
237
 
238
+ ## Using MCP Server with OAuth
239
+
240
+ Cloud Run MCP server supports OAuth as an authentication mechanism. In order to use OAuth, create the OAuth client, and configure a `.env` file with the appropriate values pertaining to your OAuth client. A `.env.example` is provided for reference.
241
+
242
+ The Cloud Run MCP server works seamlessly with Google Cloud SDK OAuth client. In order to leverage the Google Cloud SDK OAuth client, you can use the `.env.gcloud-sdk-oauth` file as your `.env` file as follows:
243
+
244
+ ```bash
245
+ cp .env.gcloud-sdk-oauth .env
246
+ node mcp-server.js
247
+ ```
248
+
249
+ ### Configure MCP Server on Gemini CLI to use OAuth
250
+
251
+ When the Cloud Run MCP server is started in the OAuth mode, the MCP client should also be configured to use OAuth. You can setup the MCP server in OAuth mode in the Gemini CLI by using the following JSON in the `~/.gemini/settings.json` file:
252
+
253
+ ```json
254
+ {
255
+ "mcpServers": {
256
+ "cloud-run": {
257
+ "httpUrl": "http://localhost:3000/mcp",
258
+ "oauth": {
259
+ "enabled": true,
260
+ "clientId": "<OAUTH_CLIENT_ID>",
261
+ "clientSecret": "<OAUTH_CLIENT_SECRET>"
262
+ }
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ Post the configuration changes as shown above, start the Gemini CLI. You should authenticate the Cloud Run MCP server using the following prompt in the Gemini CLI:
269
+
270
+ ```
271
+ /mcp auth cloud-run
272
+ ```
273
+
274
+ This will take you to the authentication page on your browser, wherein you need to sign in using the appropriate gmail id, and accept the terms and conditions. Once the authentication is succcessful, you can come back to the Gemini CLI, and the Cloud Run MCP server will be ready to use.
275
+
238
276
  The Google Cloud Platform Terms of Service (available at https://cloud.google.com/terms/) and the Data Processing and Security Terms (available at https://cloud.google.com/terms/data-processing-terms) do not apply to any component of the Cloud Run MCP Server software.
package/constants.js ADDED
@@ -0,0 +1,8 @@
1
+ export const SCOPES = {
2
+ OPENID: 'openid',
3
+ EMAIL: 'https://www.googleapis.com/auth/userinfo.email',
4
+ CLOUD_PLATFORM: 'https://www.googleapis.com/auth/cloud-platform',
5
+ };
6
+ export const BEARER_METHODS_SUPPORTED = ['header'];
7
+ export const RESPONSE_TYPES_SUPPORTED = ['code'];
8
+ export const GCLOUD_AUTH = 'gcloud_auth';
@@ -7,5 +7,17 @@
7
7
  "args": ["-y", "@google-cloud/cloud-run-mcp"]
8
8
  }
9
9
  },
10
+ "settings": [
11
+ {
12
+ "name": "Project ID",
13
+ "description": "Google Cloud project ID to use for Cloud Run operations.",
14
+ "envVar": "GOOGLE_CLOUD_PROJECT"
15
+ },
16
+ {
17
+ "name": "Region",
18
+ "description": "Google Cloud region where Cloud Run services will be managed.",
19
+ "envVar": "GOOGLE_CLOUD_REGION"
20
+ }
21
+ ],
10
22
  "contextFileName": "gemini-extension/GEMINI.md"
11
23
  }
package/lib/clients.js CHANGED
@@ -14,9 +14,14 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import { OAuth2Client } from 'google-auth-library';
18
+ import { GCLOUD_AUTH } from '../constants.js';
19
+
20
+ const CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID;
21
+ const CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET;
22
+ const REDIRECT_URI = process.env.GOOGLE_OAUTH_REDIRECT_URI;
17
23
  const AUTHORIZATION_HEADER = 'Authorization';
18
24
  const BEARER_PREFIX = 'Bearer';
19
- const GCLOUD_AUTH = 'gcloud_auth';
20
25
 
21
26
  function keyGenerator(projectId, accessToken) {
22
27
  return accessToken !== GCLOUD_AUTH ? projectId + accessToken : projectId;
@@ -32,6 +37,7 @@ const clients = {
32
37
  logging: new Map(),
33
38
  billing: new Map(),
34
39
  projects: new Map(),
40
+ oauth: new Map(),
35
41
  };
36
42
 
37
43
  function getAuthClient(accessToken) {
@@ -44,6 +50,47 @@ function getAuthClient(accessToken) {
44
50
  };
45
51
  }
46
52
 
53
+ /**
54
+ * Wraps an OAuth2Client to be compatible with gRPC-based Google Cloud clients.
55
+ *
56
+ * Some Google Cloud SDK clients (like Cloud Run, Service Usage, etc.) use gRPC
57
+ * and expect the `getRequestHeaders()` method of the auth client to return a
58
+ * `Map` of headers. However, the standard `OAuth2Client` from `google-auth-library`
59
+ * returns a plain Javascript object (e.g., `{ Authorization: 'Bearer ...' }`).
60
+ *
61
+ * This wrapper uses a Proxy to intercept calls to `getRequestHeaders`. It calls
62
+ * the original method and converts the result from a plain object to a `Map`
63
+ * if necessary, ensuring compatibility with gRPC-based clients while maintaining
64
+ * the original behavior for other properties.
65
+ *
66
+ * @param {OAuth2Client} authClient - The original OAuth2Client instance.
67
+ * @returns {Proxy<OAuth2Client>} A proxy compatible with gRPC clients.
68
+ */
69
+ function wrapForGrpc(authClient) {
70
+ return new Proxy(authClient, {
71
+ get(target, prop, receiver) {
72
+ if (prop === 'getRequestHeaders') {
73
+ return async (...args) => {
74
+ const headers = await target.getRequestHeaders(...args);
75
+ if (headers instanceof Map) {
76
+ return headers;
77
+ }
78
+ // Convert plain object (from OAuth2Client) to Map (expected by grpc-js)
79
+ const headerMap = new Map();
80
+ for (const [k, v] of Object.entries(headers)) {
81
+ headerMap.set(k, v);
82
+ }
83
+ return headerMap;
84
+ };
85
+ }
86
+ return Reflect.get(target, prop, receiver);
87
+ },
88
+ });
89
+ }
90
+
91
+ // Services that use HTTP/REST (or prefer Object headers) instead of gRPC (Map headers)
92
+ const HTTP_SERVICES = ['storage', 'logging'];
93
+
47
94
  export async function getClient(
48
95
  service,
49
96
  key,
@@ -55,7 +102,14 @@ export async function getClient(
55
102
  const ClientClass = await loadClient();
56
103
  const finalOptions = { ...options };
57
104
  if (accessToken && accessToken !== GCLOUD_AUTH) {
58
- finalOptions.authClient = getAuthClient(accessToken);
105
+ const oauthClient = await getOAuthClient(accessToken);
106
+ // Storage and Logging use HTTP and expect the native OAuth2Client (headers as Object).
107
+ // Other services (Run, ServiceUsage, etc.) use gRPC and expect headers as Map.
108
+ if (HTTP_SERVICES.includes(service)) {
109
+ finalOptions.authClient = oauthClient;
110
+ } else {
111
+ finalOptions.authClient = wrapForGrpc(oauthClient);
112
+ }
59
113
  }
60
114
  clients[service].set(key, new ClientClass(finalOptions));
61
115
  }
@@ -220,3 +274,19 @@ export async function getProjectsClient(accessToken = GCLOUD_AUTH) {
220
274
  accessToken
221
275
  );
222
276
  }
277
+
278
+ /**
279
+ * Gets an OAuth2 Client for the specified access token.
280
+ * @param {string} accessToken - The access token.
281
+ * @returns {Promise<OAuth2Client>}
282
+ */
283
+ export async function getOAuthClient(accessToken) {
284
+ // Use the access token itself as the key since it's unique per session/user
285
+ if (!clients.oauth.has(accessToken)) {
286
+ const client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
287
+ client.setCredentials({ access_token: accessToken });
288
+ clients.oauth.set(accessToken, client);
289
+ }
290
+
291
+ return clients.oauth.get(accessToken);
292
+ }
@@ -23,6 +23,7 @@ import { GoogleAuth } from 'google-auth-library';
23
23
  * @async
24
24
  * @returns {Promise<boolean>} A promise that resolves to true if GCP auth are found, and false otherwise.
25
25
  */
26
+ //TODO: Rename ensureGCPCredentials to ensureADCCredentials
26
27
  export async function ensureGCPCredentials() {
27
28
  console.error('Checking for Google Cloud Application Default Credentials...');
28
29
  try {
@@ -30,8 +30,8 @@ import { getBillingClient } from '../clients.js';
30
30
  * @returns {Promise<Array<{name: string, displayName: string, open: boolean}>>} A promise that resolves to an array of billing account objects,
31
31
  * each with 'name', 'displayName', and 'open' status. Returns an empty array on error.
32
32
  */
33
- export async function listBillingAccounts() {
34
- const client = await getBillingClient();
33
+ export async function listBillingAccounts(accessToken) {
34
+ const client = await getBillingClient('global', accessToken);
35
35
  try {
36
36
  const [accounts] = await client.listBillingAccounts();
37
37
  if (!accounts || accounts.length === 0) {
@@ -59,9 +59,10 @@ export async function listBillingAccounts() {
59
59
  */
60
60
  export async function attachProjectToBillingAccount(
61
61
  projectId,
62
- billingAccountName
62
+ billingAccountName,
63
+ accessToken
63
64
  ) {
64
- const client = await getBillingClient();
65
+ const client = await getBillingClient(projectId, accessToken);
65
66
  const projectName = `projects/${projectId}`;
66
67
 
67
68
  if (!projectId) {
@@ -111,8 +112,8 @@ export async function attachProjectToBillingAccount(
111
112
  * @param {string} projectId - The ID of the project to check.
112
113
  * @returns {Promise<boolean>} A promise that resolves to true if billing is enabled, false otherwise.
113
114
  */
114
- export async function isBillingEnabled(projectId) {
115
- const client = await getBillingClient();
115
+ export async function isBillingEnabled(projectId, accessToken) {
116
+ const client = await getBillingClient(projectId, accessToken);
116
117
  const projectName = `projects/${projectId}`;
117
118
  try {
118
119
  // getProjectBillingInfo requires cloudbilling.googleapis.com API to check billing status
@@ -136,10 +137,14 @@ export async function isBillingEnabled(projectId) {
136
137
  * @param {string} projectId The project ID to check billing for.
137
138
  * @param {function} progressCallback A callback function for progress updates.
138
139
  */
139
- export async function ensureBillingEnabled(projectId, progressCallback) {
140
- if (!(await isBillingEnabled(projectId))) {
140
+ export async function ensureBillingEnabled(
141
+ projectId,
142
+ accessToken,
143
+ progressCallback
144
+ ) {
145
+ if (!(await isBillingEnabled(projectId, accessToken))) {
141
146
  // Billing is disabled, try to fix it.
142
- const accounts = await listBillingAccounts();
147
+ const accounts = await listBillingAccounts(accessToken);
143
148
 
144
149
  if (accounts && accounts.length === 1 && accounts[0].open) {
145
150
  // Exactly one open account found, try to attach it.
@@ -151,7 +156,8 @@ export async function ensureBillingEnabled(projectId, progressCallback) {
151
156
 
152
157
  const attachmentResult = await attachProjectToBillingAccount(
153
158
  projectId,
154
- account.name
159
+ account.name,
160
+ accessToken
155
161
  );
156
162
 
157
163
  if (!attachmentResult || !attachmentResult.billingEnabled) {
@@ -53,21 +53,23 @@ export async function triggerCloudBuild(
53
53
  targetRepoName,
54
54
  targetImageUrl,
55
55
  hasDockerfile,
56
+ accessToken,
56
57
  progressCallback
57
58
  ) {
58
59
  const serviceName = targetImageUrl.split('/').pop().split(/[:@]/)[0];
59
60
  const parent = `projects/${projectId}/locations/${location}`;
60
61
  const serviceFullName = `${parent}/services/${serviceName}`;
61
- const buildsClient = await getBuildsClient(projectId);
62
- const runClient = await getRunClient(projectId);
62
+ const buildsClient = await getBuildsClient(projectId, accessToken);
63
+ const runClient = await getRunClient(projectId, accessToken);
63
64
  const serviceExists = await checkCloudRunServiceExists(
64
65
  projectId,
65
66
  location,
66
67
  serviceName,
68
+ accessToken,
67
69
  progressCallback
68
70
  );
69
- const cloudBuildClient = await getCloudBuildClient(projectId);
70
- const loggingClient = await getLoggingClient(projectId);
71
+ const cloudBuildClient = await getCloudBuildClient(projectId, accessToken);
72
+ const loggingClient = await getLoggingClient(projectId, accessToken);
71
73
  let buildSteps;
72
74
 
73
75
  const servicePatch = {
@@ -100,8 +100,16 @@ async function checkAndEnableApi(
100
100
  * @returns {Promise<void>} A promise that resolves when the API is enabled.
101
101
  * @throws {Error} If the API fails to enable or if there's an issue checking its status.
102
102
  */
103
- export async function enableApiWithRetry(projectId, api, progressCallback) {
104
- const serviceUsageClient = await getServiceUsageClient(projectId);
103
+ export async function enableApiWithRetry(
104
+ projectId,
105
+ api,
106
+ accessToken,
107
+ progressCallback
108
+ ) {
109
+ const serviceUsageClient = await getServiceUsageClient(
110
+ projectId,
111
+ accessToken
112
+ );
105
113
  const serviceName = `projects/${projectId}/services/${api}`;
106
114
  try {
107
115
  await checkAndEnableApi(
@@ -148,20 +156,25 @@ export async function enableApiWithRetry(projectId, api, progressCallback) {
148
156
  * @throws {Error} If an API fails to enable or if there's an issue checking its status.
149
157
  * @returns {Promise<void>} A promise that resolves when all specified APIs are enabled.
150
158
  */
151
- export async function ensureApisEnabled(projectId, apis, progressCallback) {
159
+ export async function ensureApisEnabled(
160
+ projectId,
161
+ apis,
162
+ accessToken,
163
+ progressCallback
164
+ ) {
152
165
  for (const api of PREREQUISITE_APIS) {
153
- await enableApiWithRetry(projectId, api, progressCallback);
166
+ await enableApiWithRetry(projectId, api, accessToken, progressCallback);
154
167
  }
155
168
 
156
169
  // Before enabling other APIs, ensure billing is enabled.
157
- await ensureBillingEnabled(projectId, progressCallback);
170
+ await ensureBillingEnabled(projectId, accessToken, progressCallback);
158
171
 
159
172
  const message = 'Checking and enabling required APIs...';
160
173
  console.log(message);
161
174
  if (progressCallback) progressCallback({ level: 'info', data: message });
162
175
 
163
176
  for (const api of apis) {
164
- await enableApiWithRetry(projectId, api, progressCallback);
177
+ await enableApiWithRetry(projectId, api, accessToken, progressCallback);
165
178
  }
166
179
  const successMsg = 'All required APIs are enabled.';
167
180
  console.log(successMsg);
@@ -26,8 +26,8 @@ import { getProjectsClient } from '../clients.js';
26
26
  * @function listProjects
27
27
  * @returns {Promise<Array<{id: string}>>} A promise that resolves to an array of project objects, each with an 'id' property. Returns an empty array on error.
28
28
  */
29
- export async function listProjects() {
30
- const client = await getProjectsClient();
29
+ export async function listProjects(accessToken) {
30
+ const client = await getProjectsClient(accessToken);
31
31
  try {
32
32
  const [projects] = await client.searchProjects();
33
33
  return projects.map((project) => ({
@@ -71,8 +71,8 @@ export function generateProjectId() {
71
71
  * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
72
72
  * @returns {Promise<{projectId: string}|null>} A promise that resolves to an object containing the new project's ID.
73
73
  */
74
- export async function createProject(projectId, parent) {
75
- const client = await getProjectsClient();
74
+ export async function createProject(projectId, parent, accessToken) {
75
+ const client = await getProjectsClient(accessToken);
76
76
  let projectIdToUse = projectId;
77
77
 
78
78
  if (!projectIdToUse) {
@@ -112,10 +112,14 @@ export async function createProject(projectId, parent) {
112
112
  * @param {string} [parent] - Optional. The resource name of the parent under which the project is to be created. e.g., "organizations/123" or "folders/456".
113
113
  * @returns {Promise<{projectId: string, billingMessage: string}>} A promise that resolves to an object containing the project ID and a billing status message.
114
114
  */
115
- export async function createProjectAndAttachBilling(projectIdParam, parent) {
115
+ export async function createProjectAndAttachBilling(
116
+ projectIdParam,
117
+ parent,
118
+ accessToken
119
+ ) {
116
120
  let newProject;
117
121
  try {
118
- newProject = await createProject(projectIdParam, parent);
122
+ newProject = await createProject(projectIdParam, parent, accessToken);
119
123
  } catch (error) {
120
124
  throw new Error(`Failed to create project: ${error.message}`);
121
125
  }
@@ -128,7 +132,7 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
128
132
  let billingMessage = `Project ${projectId} created successfully.`;
129
133
 
130
134
  try {
131
- const billingAccounts = await listBillingAccounts();
135
+ const billingAccounts = await listBillingAccounts(accessToken);
132
136
  if (billingAccounts && billingAccounts.length > 0) {
133
137
  const firstBillingAccount = billingAccounts.find((acc) => acc.open); // Prefer an open account
134
138
  if (firstBillingAccount) {
@@ -137,7 +141,8 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
137
141
  );
138
142
  const billingInfo = await attachProjectToBillingAccount(
139
143
  projectId,
140
- firstBillingAccount.name
144
+ firstBillingAccount.name,
145
+ accessToken
141
146
  );
142
147
  if (billingInfo && billingInfo.billingEnabled) {
143
148
  billingMessage += ` It has been attached to billing account ${firstBillingAccount.displayName}.`;
@@ -171,8 +176,8 @@ export async function createProjectAndAttachBilling(projectIdParam, parent) {
171
176
  * @param {string} projectId - The ID of the project to delete.
172
177
  * @returns {Promise<void>} A promise that resolves when the delete operation is initiated.
173
178
  */
174
- export async function deleteProject(projectId) {
175
- const client = await getProjectsClient();
179
+ export async function deleteProject(projectId, accessToken) {
180
+ const client = await getProjectsClient(accessToken);
176
181
  try {
177
182
  console.log(`Attempting to delete project with ID: ${projectId}`);
178
183
  await client.deleteProject({ name: `projects/${projectId}` });
@@ -33,12 +33,16 @@ import { logAndProgress } from '../util/helpers.js';
33
33
  */
34
34
  export async function ensureArtifactRegistryRepoExists(
35
35
  projectId,
36
+ accessToken,
36
37
  location,
37
38
  repositoryId,
38
39
  format = 'DOCKER',
39
40
  progressCallback
40
41
  ) {
41
- const artifactRegistryClient = await getArtifactRegistryClient(projectId);
42
+ const artifactRegistryClient = await getArtifactRegistryClient(
43
+ projectId,
44
+ accessToken
45
+ );
42
46
  const parent = `projects/${projectId}/locations/${location}`;
43
47
  const repoPath = artifactRegistryClient.repositoryPath(
44
48
  projectId,
@@ -23,14 +23,14 @@ import {
23
23
  import { callWithRetry, ensureApisEnabled } from './helpers.js';
24
24
  import { logAndProgress } from '../util/helpers.js';
25
25
 
26
- async function listCloudRunLocations(projectId) {
26
+ async function listCloudRunLocations(projectId, accessToken) {
27
27
  const listLocationsRequest = {
28
28
  name: `projects/${projectId}`,
29
29
  };
30
30
 
31
31
  const availableLocations = [];
32
32
  try {
33
- const runClient = await getRunClient(projectId);
33
+ const runClient = await getRunClient(projectId, accessToken);
34
34
  console.log('Listing Cloud Run supported locations:');
35
35
  const iterable = runClient.listLocationsAsync(listLocationsRequest);
36
36
  for await (const location of iterable) {
@@ -51,11 +51,11 @@ async function listCloudRunLocations(projectId) {
51
51
  * @param {string} projectId - The Google Cloud project ID.
52
52
  * @returns {Promise<object>} - A promise that resolves to an object mapping region to list of service objects in that region.
53
53
  */
54
- export async function listServices(projectId) {
55
- const runClient = await getRunClient(projectId);
54
+ export async function listServices(projectId, accessToken) {
55
+ const runClient = await getRunClient(projectId, accessToken);
56
56
 
57
- await ensureApisEnabled(projectId, ['run.googleapis.com']);
58
- const locations = await listCloudRunLocations(projectId);
57
+ await ensureApisEnabled(projectId, ['run.googleapis.com'], accessToken);
58
+ const locations = await listCloudRunLocations(projectId, accessToken);
59
59
 
60
60
  const allServices = {};
61
61
  for (const location of locations) {
@@ -85,8 +85,8 @@ export async function listServices(projectId) {
85
85
  * @param {string} serviceId - The ID of the Cloud Run service.
86
86
  * @returns {Promise<object>} - A promise that resolves to the service object.
87
87
  */
88
- export async function getService(projectId, location, serviceId) {
89
- const runClient = await getRunClient(projectId);
88
+ export async function getService(projectId, location, serviceId, accessToken) {
89
+ const runClient = await getRunClient(projectId, accessToken);
90
90
 
91
91
  const servicePath = runClient.servicePath(projectId, location, serviceId);
92
92
 
@@ -125,10 +125,10 @@ export async function getServiceLogs(
125
125
  projectId,
126
126
  location,
127
127
  serviceId,
128
+ accessToken,
128
129
  requestOptions
129
130
  ) {
130
- const loggingClient = await getLoggingClient(projectId);
131
-
131
+ const loggingClient = await getLoggingClient(projectId, accessToken);
132
132
  try {
133
133
  const LOG_SEVERITY = 'DEFAULT'; // e.g., 'DEFAULT', 'INFO', 'WARNING', 'ERROR'
134
134
  const PAGE_SIZE = 100; // Number of log entries to retrieve per page
@@ -226,9 +226,10 @@ export async function checkCloudRunServiceExists(
226
226
  projectId,
227
227
  location,
228
228
  serviceId,
229
+ accessToken,
229
230
  progressCallback
230
231
  ) {
231
- const runClient = await getRunClient(projectId);
232
+ const runClient = await getRunClient(projectId, accessToken);
232
233
  const servicePath = runClient.servicePath(projectId, location, serviceId);
233
234
  try {
234
235
  await callWithRetry(
@@ -34,9 +34,10 @@ export async function ensureStorageBucketExists(
34
34
  projectId,
35
35
  bucketName,
36
36
  location = 'us',
37
+ accessToken,
37
38
  progressCallback
38
39
  ) {
39
- const storage = await getStorageClient(projectId);
40
+ const storage = await getStorageClient(projectId, accessToken);
40
41
  const bucket = storage.bucket(bucketName);
41
42
  try {
42
43
  const [exists] = await callWithRetry(
@@ -21,12 +21,14 @@ export const DEPLOYMENT_CONFIG = {
21
21
  IMAGE_TAG: 'latest',
22
22
  LABEL_CREATED_BY: 'cloud-run-mcp',
23
23
  DEFAULT_NODE_BASE_IMAGE: 'nodejs22',
24
+ DEFAULT_PYTHON_BASE_IMAGE: 'python314',
24
25
  NO_BUILD_IMAGE_TYPE: 'scratch',
25
26
  };
26
27
 
27
28
  export const TEMP_PATHS = {
28
29
  BASE: '.cloud-run-mcp',
29
30
  SUBDIR: 'source',
31
+ BIN_SUBDIR: 'bin',
30
32
  };
31
33
 
32
34
  export const REQUIRED_APIS = {
@@ -49,6 +51,7 @@ export const DEPLOYMENT_TYPES = {
49
51
 
50
52
  export const RUNTIMES = {
51
53
  NODEJS: 'nodejs',
54
+ PYTHON: 'python',
52
55
  };
53
56
 
54
57
  export const MAX_ALLOWED_DIRECT_SOURCE_SIZE_BYTES = 250 * 1024 * 1024; // 250 MiB