@google-cloud/cloud-run-mcp 1.8.0 → 1.9.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,92 @@
1
+ import { getOAuthClient } from '../clients.js';
2
+ import { extractAccessToken } from '../util/helpers.js';
3
+
4
+ const TOOLS_CALL_METHOD = 'tools/call';
5
+
6
+ /**
7
+ * Verifies the validity of an access token and checks the audience.
8
+ * @param {string} accessToken - The access token to verify.
9
+ * @param {string} [audience] - Audience to check against the token's audience.
10
+ * @returns {Promise<object>} - The token info if valid.
11
+ * @throws {Error} - If the token is invalid or the audience does not match.
12
+ */
13
+ async function verifyAccessToken(accessToken, audience) {
14
+ try {
15
+ const client = await getOAuthClient(accessToken);
16
+ const tokenInfo = await client.getTokenInfo(accessToken);
17
+
18
+ //An expired token will not have audience
19
+ if (audience && tokenInfo.aud !== audience) {
20
+ throw new Error(`Invalid audience: expected ${audience}`);
21
+ }
22
+
23
+ console.log('Access token verified successfully.');
24
+ return tokenInfo;
25
+ } catch (error) {
26
+ console.error('Error verifying access token:', error);
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Ensures that a valid OAuth token is present in the request headers
33
+ * and verifies it against the configured audience.
34
+ * @param {object} req - The request object.
35
+ * @param {object} res - The response object.
36
+ * @throws {Error} - If the token is missing or invalid.
37
+ */
38
+ async function ensureOAuthTokenInEnv(req, res) {
39
+ try {
40
+ const audience = process.env.GOOGLE_OAUTH_AUDIENCE;
41
+ console.log('Verifying token');
42
+ if (req.headers.authorization === undefined) {
43
+ console.log('No authorization header found in request');
44
+ throw new Error('No authorization header');
45
+ }
46
+ console.log('Authorization header found.. Verifying token');
47
+ await verifyAccessToken(
48
+ extractAccessToken(req.headers.authorization),
49
+ audience
50
+ );
51
+ console.log('Token verified');
52
+ } catch (error) {
53
+ console.error('Authentication failed: ', error);
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Middleware to check for OAuth token if OAuth is enabled.
60
+ * If OAUTH_ENABLED is 'true', it verifies the Authorization header.
61
+ *
62
+ * @param {import('express').Request} req
63
+ * @param {import('express').Response} res
64
+ * @param {import('express').NextFunction} next
65
+ */
66
+ export const oauthMiddleware = async (req, res, next) => {
67
+ //If OAUTH_ENABLED is not true or the request is not a tools/call, skip the middleware
68
+ if (
69
+ process.env.OAUTH_ENABLED !== 'true' ||
70
+ req.body.method !== TOOLS_CALL_METHOD
71
+ ) {
72
+ return next();
73
+ }
74
+
75
+ try {
76
+ await ensureOAuthTokenInEnv(req, res);
77
+ next();
78
+ } catch (error) {
79
+ console.error('OAuth Middleware Error:', error);
80
+ // ensureOAuthTokenInEnv throws an error if auth fails.
81
+ // We catch it and send a 401 response.
82
+ res.status(401).json({
83
+ jsonrpc: '2.0',
84
+ error: {
85
+ code: -32001, // Custom auth error code or standard -32000
86
+ message: 'Authentication failed',
87
+ data: error.message,
88
+ },
89
+ id: null,
90
+ });
91
+ }
92
+ };
@@ -40,3 +40,15 @@ export async function logAndProgress(
40
40
  progressCallback({ level: severity, data: message });
41
41
  }
42
42
  }
43
+
44
+ /**
45
+ * Extracts the access token from the Authorization header.
46
+ * @param {string} authorizationHeader - The Authorization header string.
47
+ * @returns {string | undefined} - The extracted access token or undefined if not found.
48
+ */
49
+ export function extractAccessToken(authorizationHeader) {
50
+ if (!authorizationHeader) {
51
+ return undefined;
52
+ }
53
+ return authorizationHeader.split(' ')[1];
54
+ }
package/mcp-server.js CHANGED
@@ -28,7 +28,18 @@ import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js';
28
28
  import { registerPrompts } from './prompts.js';
29
29
  import { checkGCP } from './lib/cloud-api/metadata.js';
30
30
  import { ensureGCPCredentials } from './lib/cloud-api/auth.js';
31
- import 'dotenv/config';
31
+ import { extractAccessToken } from './lib/util/helpers.js';
32
+ import { oauthMiddleware } from './lib/middleware/oauth.js';
33
+ import { config } from '@dotenvx/dotenvx';
34
+ import {
35
+ SCOPES,
36
+ GCLOUD_AUTH,
37
+ BEARER_METHODS_SUPPORTED,
38
+ RESPONSE_TYPES_SUPPORTED,
39
+ } from './constants.js';
40
+
41
+ //Suppress the warning related to missing .env file in case of non-OAuth mode
42
+ config({ quiet: true, ignore: ['MISSING_ENV_FILE'] });
32
43
 
33
44
  const gcpInfo = await checkGCP();
34
45
  let gcpCredentialsAvailable = false;
@@ -63,7 +74,7 @@ const allowedHosts = process.env.ALLOWED_HOSTS
63
74
  ? process.env.ALLOWED_HOSTS.split(',')
64
75
  : undefined;
65
76
 
66
- async function getServer() {
77
+ async function getServer(accessToken = GCLOUD_AUTH) {
67
78
  // Create an MCP server with implementation details
68
79
  const server = new McpServer(
69
80
  {
@@ -97,6 +108,7 @@ async function getServer() {
97
108
  defaultServiceName,
98
109
  skipIamCheck,
99
110
  gcpCredentialsAvailable,
111
+ accessToken,
100
112
  });
101
113
  } else {
102
114
  console.log(
@@ -109,6 +121,7 @@ async function getServer() {
109
121
  defaultServiceName,
110
122
  skipIamCheck,
111
123
  gcpCredentialsAvailable,
124
+ accessToken,
112
125
  });
113
126
  }
114
127
 
@@ -118,7 +131,28 @@ async function getServer() {
118
131
  return server;
119
132
  }
120
133
 
121
- // stdio
134
+ const getOAuthProtectedResource = (req, res) => {
135
+ res.json({
136
+ resource: process.env.OAUTH_PROTECTED_RESOURCE,
137
+ authorization_servers: [process.env.OAUTH_AUTHORIZATION_SERVER],
138
+ scopes_supported: [SCOPES.OPENID, SCOPES.EMAIL, SCOPES.CLOUD_PLATFORM],
139
+ bearer_methods_supported: [...BEARER_METHODS_SUPPORTED],
140
+ });
141
+ res.status(200).send();
142
+ };
143
+
144
+ const getOAuthAuthorizationServer = (req, res) => {
145
+ res.json({
146
+ issuer: process.env.OAUTH_PROTECTED_RESOURCE,
147
+ authorization_endpoint: process.env.OAUTH_AUTHORIZATION_ENDPOINT,
148
+ token_endpoint: process.env.OAUTH_TOKEN_ENDPOINT,
149
+ scopes_supported: [SCOPES.OPENID, SCOPES.EMAIL, SCOPES.CLOUD_PLATFORM],
150
+ response_types_supported: [...RESPONSE_TYPES_SUPPORTED],
151
+ });
152
+ res.status(200).send();
153
+ };
154
+
155
+ // stdio mode
122
156
  if (shouldStartStdio()) {
123
157
  gcpCredentialsAvailable = await ensureGCPCredentials();
124
158
  const stdioTransport = new StdioServerTransport();
@@ -128,7 +162,8 @@ if (shouldStartStdio()) {
128
162
  } else {
129
163
  // non-stdio mode
130
164
  console.log('Stdio transport mode is turned off.');
131
- gcpCredentialsAvailable = await ensureGCPCredentials();
165
+ gcpCredentialsAvailable =
166
+ process.env.OAUTH_ENABLED === 'true' || (await ensureGCPCredentials());
132
167
 
133
168
  const app = enableHostValidation
134
169
  ? createMcpExpressApp({ allowedHosts })
@@ -141,9 +176,17 @@ if (shouldStartStdio()) {
141
176
  );
142
177
  }
143
178
 
144
- app.post('/mcp', async (req, res) => {
179
+ app.get('/.well-known/oauth-protected-resource', getOAuthProtectedResource);
180
+
181
+ app.get(
182
+ '/.well-known/oauth-authorization-server',
183
+ getOAuthAuthorizationServer
184
+ );
185
+
186
+ app.post('/mcp', oauthMiddleware, async (req, res) => {
145
187
  console.log('/mcp Received:', req.body);
146
- const server = await getServer();
188
+ const accessToken = extractAccessToken(req.headers.authorization);
189
+ const server = await getServer(accessToken);
147
190
  try {
148
191
  const transport = new StreamableHTTPServerTransport({
149
192
  sessionIdGenerator: undefined,
@@ -204,7 +247,8 @@ if (shouldStartStdio()) {
204
247
  // Legacy SSE endpoint for older clients
205
248
  app.get('/sse', async (req, res) => {
206
249
  console.log('/sse Received:', req.body);
207
- const server = await getServer();
250
+ const accessToken = extractAccessToken(req.headers.authorization);
251
+ const server = await getServer(accessToken);
208
252
  // Create SSE transport for legacy clients
209
253
  const transport = new SSEServerTransport('/messages', res);
210
254
  sseTransports[transport.sessionId] = transport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@google-cloud/cloud-run-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "description": "Cloud Run MCP deployment tool",
6
6
  "main": "mcp-server.js",
@@ -50,7 +50,7 @@
50
50
  "@google-cloud/storage": "^7.16.0",
51
51
  "@modelcontextprotocol/sdk": "^1.24.3",
52
52
  "archiver": "^7.0.1",
53
- "dotenv": "^17.2.1",
53
+ "@dotenvx/dotenvx": "^1.52.0",
54
54
  "express": "^5.1.0",
55
55
  "google-proto-files": "^5.0.0",
56
56
  "zod": "^3.25.76"
@@ -27,7 +27,9 @@ describe('getClient Helper', () => {
27
27
  assert.strictEqual(client.options.projectId, projectId);
28
28
  assert.ok(client.options.authClient);
29
29
 
30
- const headers = client.options.authClient.getRequestHeaders();
30
+ const headers = await client.options.authClient.getRequestHeaders();
31
+ // 'run' is a gRPC service, so it should be wrapped to return a Map
32
+ assert.ok(headers instanceof Map);
31
33
  assert.strictEqual(headers.get('Authorization'), 'Bearer fake-token-1');
32
34
  });
33
35
 
@@ -96,10 +98,12 @@ describe('getClient Helper', () => {
96
98
 
97
99
  assert.notStrictEqual(client1, client2);
98
100
 
99
- const h1 = client1.options.authClient.getRequestHeaders();
101
+ const h1 = await client1.options.authClient.getRequestHeaders();
102
+ assert.ok(h1 instanceof Map);
100
103
  assert.strictEqual(h1.get('Authorization'), 'Bearer token-A');
101
104
 
102
- const h2 = client2.options.authClient.getRequestHeaders();
105
+ const h2 = await client2.options.authClient.getRequestHeaders();
106
+ assert.ok(h2 instanceof Map);
103
107
  assert.strictEqual(h2.get('Authorization'), 'Bearer token-B');
104
108
  });
105
109
 
@@ -126,6 +130,32 @@ describe('getClient Helper', () => {
126
130
  assert.notStrictEqual(runClient, storageClient); // Different maps
127
131
  assert.ok(runClient.options.authClient);
128
132
  assert.ok(storageClient.options.authClient);
133
+
134
+ const runHeaders = await runClient.options.authClient.getRequestHeaders();
135
+ assert.ok(runHeaders instanceof Map, 'Run client headers should be a Map');
136
+
137
+ const storageHeaders =
138
+ await storageClient.options.authClient.getRequestHeaders();
139
+ assert.ok(
140
+ !(storageHeaders instanceof Map),
141
+ 'Storage client headers should NOT be a Map'
142
+ );
143
+ assert.strictEqual(storageHeaders.Authorization, `Bearer ${accessToken}`);
144
+
145
+ const loggingClient = await getClient(
146
+ 'logging',
147
+ projectId + accessToken,
148
+ async () => MockClient,
149
+ { projectId },
150
+ accessToken
151
+ );
152
+ const loggingHeaders =
153
+ await loggingClient.options.authClient.getRequestHeaders();
154
+ assert.ok(
155
+ !(loggingHeaders instanceof Map),
156
+ 'Logging client headers should NOT be a Map'
157
+ );
158
+ assert.strictEqual(loggingHeaders.Authorization, `Bearer ${accessToken}`);
129
159
  });
130
160
 
131
161
  test('passes additional options correctly', async () => {
@@ -13,7 +13,6 @@ describe('MCP Server stdio startup', () => {
13
13
  stderr = '';
14
14
  serverProcess = spawn('node', ['mcp-server.js'], {
15
15
  cwd: process.cwd(),
16
- env: { ...process.env, GCP_STDIO: 'true' },
17
16
  });
18
17
  stderr = await waitForString(serverProcess.stderr, stdioMsg);
19
18
  });
@@ -4,6 +4,11 @@ import { test, describe, before, after } from 'node:test';
4
4
  import assert from 'node:assert/strict';
5
5
  import { spawn } from 'child_process';
6
6
  import { waitForString } from './test-utils.js';
7
+ import {
8
+ SCOPES,
9
+ BEARER_METHODS_SUPPORTED,
10
+ RESPONSE_TYPES_SUPPORTED,
11
+ } from '../../constants.js';
7
12
 
8
13
  class MCPClient {
9
14
  client = null;
@@ -59,3 +64,67 @@ describe('MCP Server in Streamble HTTP mode', () => {
59
64
  });
60
65
  });
61
66
  });
67
+
68
+ describe('OAuth Endpoints', () => {
69
+ let serverProcess;
70
+ let stdout = '';
71
+ const httpMsg = 'Cloud Run MCP server listening on port 3001';
72
+ const oauthProtectedResource = 'https://example.com/resource';
73
+ const oauthAuthorizationServer = 'https://example.com/auth';
74
+ const oauthAuthorizationEndpoint = 'https://example.com/auth/authorize';
75
+ const oauthTokenEndpoint = 'https://example.com/auth/token';
76
+
77
+ before(async () => {
78
+ stdout = '';
79
+ serverProcess = spawn('node', ['mcp-server.js'], {
80
+ cwd: process.cwd(),
81
+ env: {
82
+ ...process.env,
83
+ GCP_STDIO: 'false',
84
+ PORT: '3001',
85
+ OAUTH_PROTECTED_RESOURCE: oauthProtectedResource,
86
+ OAUTH_AUTHORIZATION_SERVER: oauthAuthorizationServer,
87
+ OAUTH_AUTHORIZATION_ENDPOINT: oauthAuthorizationEndpoint,
88
+ OAUTH_TOKEN_ENDPOINT: oauthTokenEndpoint,
89
+ },
90
+ });
91
+ stdout = await waitForString(serverProcess.stdout, httpMsg);
92
+ });
93
+
94
+ after(() => {
95
+ if (serverProcess) {
96
+ serverProcess.kill();
97
+ }
98
+ });
99
+
100
+ test('should return correct OAuth protected resource configuration', async () => {
101
+ const response = await fetch(
102
+ 'http://localhost:3001/.well-known/oauth-protected-resource'
103
+ );
104
+ assert.equal(response.status, 200);
105
+ const data = await response.json();
106
+
107
+ assert.deepStrictEqual(data, {
108
+ resource: oauthProtectedResource,
109
+ authorization_servers: [oauthAuthorizationServer],
110
+ scopes_supported: [SCOPES.OPENID, SCOPES.EMAIL, SCOPES.CLOUD_PLATFORM],
111
+ bearer_methods_supported: [...BEARER_METHODS_SUPPORTED],
112
+ });
113
+ });
114
+
115
+ test('should return correct OAuth authorization server configuration', async () => {
116
+ const response = await fetch(
117
+ 'http://localhost:3001/.well-known/oauth-authorization-server'
118
+ );
119
+ assert.equal(response.status, 200);
120
+ const data = await response.json();
121
+
122
+ assert.deepStrictEqual(data, {
123
+ issuer: oauthProtectedResource,
124
+ authorization_endpoint: oauthAuthorizationEndpoint,
125
+ token_endpoint: oauthTokenEndpoint,
126
+ scopes_supported: [SCOPES.OPENID, SCOPES.EMAIL, SCOPES.CLOUD_PLATFORM],
127
+ response_types_supported: [...RESPONSE_TYPES_SUPPORTED],
128
+ });
129
+ });
130
+ });
@@ -11,6 +11,7 @@ describe('MCP Server in stdio mode', () => {
11
11
  transport = new StdioClientTransport({
12
12
  command: 'node',
13
13
  args: ['mcp-server.js'],
14
+ env: { ...process.env, GCP_STDIO: 'true' },
14
15
  });
15
16
  client = new Client({
16
17
  name: 'test-client',
@@ -0,0 +1,156 @@
1
+ import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import esmock from 'esmock';
4
+
5
+ describe('oauthMiddleware', () => {
6
+ let req;
7
+ let res;
8
+ let next;
9
+ let originalEnv;
10
+
11
+ beforeEach(() => {
12
+ originalEnv = process.env;
13
+ process.env = { ...originalEnv };
14
+ req = {
15
+ headers: {},
16
+ body: {},
17
+ };
18
+ res = {
19
+ headersSent: false,
20
+ status: mock.fn(() => res),
21
+ json: mock.fn(),
22
+ };
23
+ next = mock.fn();
24
+ });
25
+
26
+ afterEach(() => {
27
+ process.env = originalEnv;
28
+ mock.restoreAll();
29
+ });
30
+
31
+ it('should call next() if OAUTH_ENABLED is not "true"', async () => {
32
+ process.env.OAUTH_ENABLED = 'false';
33
+ const { oauthMiddleware } = await esmock(
34
+ '../../../lib/middleware/oauth.js',
35
+ {}
36
+ );
37
+
38
+ await oauthMiddleware(req, res, next);
39
+
40
+ assert.strictEqual(next.mock.callCount(), 1);
41
+ assert.strictEqual(res.status.mock.callCount(), 0);
42
+ });
43
+
44
+ it('should call next() if method is not tools/call', async () => {
45
+ process.env.OAUTH_ENABLED = 'true';
46
+ req.body.method = 'other/method';
47
+
48
+ // We don't verify token if it's not a tool call (based on current implementation)
49
+ // So we don't need to mock successful verification here
50
+ const { oauthMiddleware } = await esmock(
51
+ '../../../lib/middleware/oauth.js',
52
+ {}
53
+ );
54
+
55
+ await oauthMiddleware(req, res, next);
56
+
57
+ assert.strictEqual(next.mock.callCount(), 1);
58
+ });
59
+
60
+ it('should return 401 if Authorization header is missing for tool call', async () => {
61
+ process.env.OAUTH_ENABLED = 'true';
62
+ req.body.method = 'tools/call';
63
+
64
+ const { oauthMiddleware } = await esmock(
65
+ '../../../lib/middleware/oauth.js',
66
+ {}
67
+ );
68
+
69
+ await oauthMiddleware(req, res, next);
70
+
71
+ assert.strictEqual(next.mock.callCount(), 0);
72
+ assert.strictEqual(res.status.mock.callCount(), 1);
73
+ assert.deepStrictEqual(res.status.mock.calls[0].arguments, [401]);
74
+ assert.strictEqual(res.json.mock.callCount(), 1);
75
+ });
76
+
77
+ it('should verify token and call next() for valid tool call', async () => {
78
+ process.env.OAUTH_ENABLED = 'true';
79
+ req.body.method = 'tools/call';
80
+ req.headers.authorization = 'Bearer valid-token';
81
+
82
+ const mockGetTokenInfo = mock.fn(async () => ({ aud: 'valid-audience' }));
83
+ const mockGetOAuthClient = mock.fn(async () => ({
84
+ getTokenInfo: mockGetTokenInfo,
85
+ }));
86
+
87
+ const { oauthMiddleware } = await esmock(
88
+ '../../../lib/middleware/oauth.js',
89
+ {
90
+ '../../../lib/clients.js': {
91
+ getOAuthClient: mockGetOAuthClient,
92
+ },
93
+ }
94
+ );
95
+
96
+ await oauthMiddleware(req, res, next);
97
+
98
+ assert.strictEqual(next.mock.callCount(), 1);
99
+ assert.strictEqual(mockGetOAuthClient.mock.callCount(), 1);
100
+ assert.strictEqual(mockGetTokenInfo.mock.callCount(), 1);
101
+ });
102
+
103
+ it('should return 401 if token verification fails', async () => {
104
+ process.env.OAUTH_ENABLED = 'true';
105
+ req.body.method = 'tools/call';
106
+ req.headers.authorization = 'Bearer invalid-token';
107
+
108
+ const mockGetOAuthClient = mock.fn(async () => ({
109
+ getTokenInfo: async () => {
110
+ throw new Error('Invalid token');
111
+ },
112
+ }));
113
+
114
+ const { oauthMiddleware } = await esmock(
115
+ '../../../lib/middleware/oauth.js',
116
+ {
117
+ '../../../lib/clients.js': {
118
+ getOAuthClient: mockGetOAuthClient,
119
+ },
120
+ }
121
+ );
122
+
123
+ await oauthMiddleware(req, res, next);
124
+
125
+ assert.strictEqual(next.mock.callCount(), 0);
126
+ assert.strictEqual(res.status.mock.callCount(), 1);
127
+ assert.deepStrictEqual(res.status.mock.calls[0].arguments, [401]);
128
+ });
129
+
130
+ it('should return 401 if audience does not match', async () => {
131
+ process.env.OAUTH_ENABLED = 'true';
132
+ process.env.GOOGLE_OAUTH_AUDIENCE = 'expected-audience';
133
+ req.body.method = 'tools/call';
134
+ req.headers.authorization = 'Bearer valid-token-wrong-audience';
135
+
136
+ const mockGetTokenInfo = mock.fn(async () => ({ aud: 'wrong-audience' }));
137
+ const mockGetOAuthClient = mock.fn(async () => ({
138
+ getTokenInfo: mockGetTokenInfo,
139
+ }));
140
+
141
+ const { oauthMiddleware } = await esmock(
142
+ '../../../lib/middleware/oauth.js',
143
+ {
144
+ '../../../lib/clients.js': {
145
+ getOAuthClient: mockGetOAuthClient,
146
+ },
147
+ }
148
+ );
149
+
150
+ await oauthMiddleware(req, res, next);
151
+
152
+ assert.strictEqual(next.mock.callCount(), 0);
153
+ assert.strictEqual(res.status.mock.callCount(), 1);
154
+ assert.deepStrictEqual(res.status.mock.calls[0].arguments, [401]);
155
+ });
156
+ });
@@ -42,7 +42,7 @@ describe('registerTools', () => {
42
42
  {},
43
43
  {
44
44
  '../../lib/cloud-api/projects.js': {
45
- listProjects: () =>
45
+ listProjects: (token) =>
46
46
  Promise.resolve([{ id: 'project1' }, { id: 'project2' }]),
47
47
  },
48
48
  }
@@ -433,7 +433,7 @@ describe('registerTools', () => {
433
433
  content: [
434
434
  {
435
435
  type: 'text',
436
- text: 'GCP credentials are not available. Please configure your environment.',
436
+ text: 'GCP credentials are not available. Please configure your environment using OAuth or `gcloud auth`.',
437
437
  },
438
438
  ],
439
439
  });
@@ -41,7 +41,7 @@ function gcpTool(gcpCredentialsAvailable, fn) {
41
41
  content: [
42
42
  {
43
43
  type: 'text',
44
- text: 'GCP credentials are not available. Please configure your environment.',
44
+ text: 'GCP credentials are not available. Please configure your environment using OAuth or `gcloud auth`.',
45
45
  },
46
46
  ],
47
47
  });
@@ -59,7 +59,7 @@ function registerListProjectsTool(server, options) {
59
59
  },
60
60
  gcpTool(options.gcpCredentialsAvailable, async () => {
61
61
  try {
62
- const projects = await listProjects();
62
+ const projects = await listProjects(options.accessToken);
63
63
  return {
64
64
  content: [
65
65
  {
@@ -113,7 +113,11 @@ function registerCreateProjectTool(server, options) {
113
113
  };
114
114
  }
115
115
  try {
116
- const result = await createProjectAndAttachBilling(projectId);
116
+ const result = await createProjectAndAttachBilling(
117
+ projectId,
118
+ undefined,
119
+ options.accessToken
120
+ );
117
121
  return {
118
122
  content: [
119
123
  {
@@ -161,7 +165,7 @@ function registerListServicesTool(server, options) {
161
165
  };
162
166
  }
163
167
  try {
164
- const allServices = await listServices(project);
168
+ const allServices = await listServices(project, options.accessToken);
165
169
  const content = [];
166
170
  for (const region of Object.keys(allServices)) {
167
171
  const serviceList = allServices[region];
@@ -230,7 +234,12 @@ function registerGetServiceTool(server, options) {
230
234
  };
231
235
  }
232
236
  try {
233
- const serviceDetails = await getService(project, region, service);
237
+ const serviceDetails = await getService(
238
+ project,
239
+ region,
240
+ service,
241
+ options.accessToken
242
+ );
234
243
  if (serviceDetails) {
235
244
  return {
236
245
  content: [
@@ -299,6 +308,7 @@ function registerGetServiceLogTool(server, options) {
299
308
  project,
300
309
  region,
301
310
  service,
311
+ options.accessToken,
302
312
  requestOptions
303
313
  );
304
314
 
@@ -394,6 +404,7 @@ function registerDeployLocalFolderTool(server, options) {
394
404
  region: region,
395
405
  files: [folderPath],
396
406
  skipIamCheck: options.skipIamCheck, // Pass the new flag
407
+ accessToken: options.accessToken,
397
408
  progressCallback,
398
409
  });
399
410
  return {
@@ -495,6 +506,7 @@ function registerDeployFileContentsTool(server, options) {
495
506
  region: region,
496
507
  files: files,
497
508
  skipIamCheck: options.skipIamCheck, // Pass the new flag
509
+ accessToken: options.accessToken,
498
510
  progressCallback,
499
511
  });
500
512
  return {
@@ -575,6 +587,7 @@ function registerDeployContainerImageTool(server, options) {
575
587
  region: region,
576
588
  imageUrl: imageUrl,
577
589
  skipIamCheck: options.skipIamCheck,
590
+ accessToken: options.accessToken,
578
591
  progressCallback,
579
592
  });
580
593
  return {
@@ -1,22 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Copyright 2022 Google LLC
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- # Code under repo is checked out to ${KOKORO_ARTIFACTS_DIR}/git.
18
- # The final directory name in this path is determined by the scm name specified
19
- # in the job configuration.
20
-
21
- cd "${KOKORO_ARTIFACTS_DIR}/github/cloud-run-mcp"
22
- .kokoro/run_tests.sh