gcs-google-mcp-server 0.1.9 → 0.1.11

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,47 @@
1
+ import { logInfo } from '../shared/logging.js';
2
+ /**
3
+ * Thrown when a constrained bucket cannot be reached (does not exist or the
4
+ * service account lacks object-read access to it).
5
+ */
6
+ export class BucketNotAccessibleError extends Error {
7
+ constructor(bucket, cause) {
8
+ const causeMessage = cause instanceof Error ? cause.message : cause !== undefined ? String(cause) : undefined;
9
+ super(`Constrained bucket "${bucket}" does not exist or is not accessible` +
10
+ (causeMessage ? `: ${causeMessage}` : ''));
11
+ this.name = 'BucketNotAccessibleError';
12
+ }
13
+ }
14
+ /**
15
+ * Validate GCS credentials and connectivity.
16
+ *
17
+ * - When constrained to a single bucket (`GCS_BUCKET`), probe ONLY that bucket
18
+ * with an object-scoped list (`storage.objects.list`). This is the exact
19
+ * permission the server actually exercises for the constrained bucket, and a
20
+ * least-privilege, bucket-scoped service account is granted it. This path must
21
+ * NOT call `listBuckets()` (project-level `storage.buckets.list`) NOR a
22
+ * bucket-metadata probe like `headBucket`/`getMetadata` (bucket-level
23
+ * `storage.buckets.get`): a correctly scoped read-only SA can be denied BOTH
24
+ * of those while still being able to read objects in the bucket.
25
+ * - Without a constraint, validate by listing buckets, which legitimately
26
+ * requires project-level access.
27
+ *
28
+ * Throws on failure; callers decide how to surface it.
29
+ */
30
+ export async function validateGcsCredentials(client, constrainedBucket) {
31
+ if (constrainedBucket) {
32
+ try {
33
+ // A single object-list request validates `storage.objects.list` on the
34
+ // bucket without requiring any project- or bucket-level metadata
35
+ // permission. maxResults:1 keeps the probe cheap.
36
+ await client.listObjects(constrainedBucket, { maxResults: 1 });
37
+ }
38
+ catch (error) {
39
+ throw new BucketNotAccessibleError(constrainedBucket, error);
40
+ }
41
+ logInfo('healthcheck', `Constrained bucket "${constrainedBucket}" verified`);
42
+ }
43
+ else {
44
+ await client.listBuckets();
45
+ logInfo('healthcheck', 'GCS credentials validated successfully');
46
+ }
47
+ }
package/build/index.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
6
  import { createMCPServer, GoogleCloudStorageClient } from '../shared/index.js';
7
7
  import { logServerStart, logError, logWarning, logInfo } from '../shared/logging.js';
8
+ import { validateGcsCredentials } from './healthcheck.js';
8
9
  // Read version from package.json
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
11
  const packageJsonPath = join(__dirname, '..', 'package.json');
@@ -108,18 +109,12 @@ async function performHealthChecks() {
108
109
  keyFilePath,
109
110
  keyFileContents,
110
111
  });
111
- // Try to list buckets to validate credentials
112
- await client.listBuckets();
113
- logInfo('healthcheck', 'GCS credentials validated successfully');
114
- // If GCS_BUCKET is set, verify it exists and is accessible
115
- if (constrainedBucket) {
116
- const bucketExists = await client.headBucket(constrainedBucket);
117
- if (!bucketExists) {
118
- logError('healthcheck', `Constrained bucket "${constrainedBucket}" does not exist or is not accessible`);
119
- process.exit(1);
120
- }
121
- logInfo('healthcheck', `Constrained bucket "${constrainedBucket}" verified`);
122
- }
112
+ // Validate credentials. When constrained to a single bucket, this probes
113
+ // only that bucket via an object-scoped list (storage.objects.list) and
114
+ // never touches project-level (storage.buckets.list) or bucket-metadata
115
+ // (storage.buckets.get) permissions, so a least-privilege, bucket-scoped
116
+ // read-only service account can start.
117
+ await validateGcsCredentials(client, constrainedBucket);
123
118
  }
124
119
  catch (error) {
125
120
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcs-google-mcp-server",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server for Google Cloud Storage operations with fine-grained tool access control",
5
5
  "mcpName": "com.pulsemcp/gcs",
6
6
  "main": "build/index.js",