gcs-google-mcp-server 0.1.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/README.md +241 -0
- package/build/index.integration-with-mock.js +42 -0
- package/build/index.js +152 -0
- package/package.json +50 -0
- package/shared/gcs-client/gcs-client.d.ts +74 -0
- package/shared/gcs-client/gcs-client.integration-mock.d.ts +15 -0
- package/shared/gcs-client/gcs-client.integration-mock.js +131 -0
- package/shared/gcs-client/gcs-client.js +120 -0
- package/shared/index.d.ts +8 -0
- package/shared/index.js +9 -0
- package/shared/logging.d.ts +24 -0
- package/shared/logging.js +40 -0
- package/shared/resources.d.ts +3 -0
- package/shared/resources.js +65 -0
- package/shared/server.d.ts +46 -0
- package/shared/server.js +37 -0
- package/shared/state.d.ts +43 -0
- package/shared/state.js +67 -0
- package/shared/tools/copy-object.d.ts +59 -0
- package/shared/tools/copy-object.js +71 -0
- package/shared/tools/create-bucket.d.ts +45 -0
- package/shared/tools/create-bucket.js +82 -0
- package/shared/tools/delete-bucket.d.ts +38 -0
- package/shared/tools/delete-bucket.js +60 -0
- package/shared/tools/delete-object.d.ts +45 -0
- package/shared/tools/delete-object.js +64 -0
- package/shared/tools/get-object.d.ts +45 -0
- package/shared/tools/get-object.js +65 -0
- package/shared/tools/head-bucket.d.ts +38 -0
- package/shared/tools/head-bucket.js +58 -0
- package/shared/tools/list-buckets.d.ts +27 -0
- package/shared/tools/list-buckets.js +49 -0
- package/shared/tools/list-objects.d.ts +68 -0
- package/shared/tools/list-objects.js +80 -0
- package/shared/tools/put-object.d.ts +69 -0
- package/shared/tools/put-object.js +81 -0
- package/shared/tools.d.ts +30 -0
- package/shared/tools.js +154 -0
- package/shared/types.d.ts +1 -0
- package/shared/types.js +6 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export function createIntegrationMockGCSClient(mockData = {}) {
|
|
2
|
+
// Default mock data
|
|
3
|
+
const buckets = mockData.buckets || [
|
|
4
|
+
{ name: 'test-bucket', creationDate: new Date('2024-01-01') },
|
|
5
|
+
{ name: 'another-bucket', creationDate: new Date('2024-02-01') },
|
|
6
|
+
];
|
|
7
|
+
// Deep clone objects to avoid mutation issues
|
|
8
|
+
const objects = structuredClone(mockData.objects || {});
|
|
9
|
+
return {
|
|
10
|
+
async listBuckets() {
|
|
11
|
+
return { buckets };
|
|
12
|
+
},
|
|
13
|
+
async listObjects(bucket, options = {}) {
|
|
14
|
+
const bucketObjects = objects?.[bucket] || {};
|
|
15
|
+
let keys = Object.keys(bucketObjects);
|
|
16
|
+
// Apply prefix filter
|
|
17
|
+
if (options.prefix) {
|
|
18
|
+
keys = keys.filter((key) => key.startsWith(options.prefix));
|
|
19
|
+
}
|
|
20
|
+
// Handle delimiter for common prefixes
|
|
21
|
+
const commonPrefixes = [];
|
|
22
|
+
if (options.delimiter) {
|
|
23
|
+
const prefixSet = new Set();
|
|
24
|
+
keys = keys.filter((key) => {
|
|
25
|
+
const afterPrefix = options.prefix ? key.slice(options.prefix.length) : key;
|
|
26
|
+
const delimiterIndex = afterPrefix.indexOf(options.delimiter);
|
|
27
|
+
if (delimiterIndex >= 0) {
|
|
28
|
+
const prefix = (options.prefix || '') + afterPrefix.slice(0, delimiterIndex + 1);
|
|
29
|
+
prefixSet.add(prefix);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
commonPrefixes.push(...prefixSet);
|
|
35
|
+
}
|
|
36
|
+
// Apply maxResults limit
|
|
37
|
+
const maxResults = options.maxResults || 1000;
|
|
38
|
+
const isTruncated = keys.length > maxResults;
|
|
39
|
+
keys = keys.slice(0, maxResults);
|
|
40
|
+
return {
|
|
41
|
+
objects: keys.map((key) => ({
|
|
42
|
+
key,
|
|
43
|
+
size: bucketObjects[key]?.content?.length || 0,
|
|
44
|
+
lastModified: bucketObjects[key]?.lastModified || new Date(),
|
|
45
|
+
storageClass: 'STANDARD',
|
|
46
|
+
etag: '"mock-etag"',
|
|
47
|
+
})),
|
|
48
|
+
commonPrefixes,
|
|
49
|
+
isTruncated,
|
|
50
|
+
nextPageToken: isTruncated ? 'mock-token' : undefined,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
async getObject(bucket, key) {
|
|
54
|
+
const bucketObjects = objects?.[bucket];
|
|
55
|
+
if (!bucketObjects || !bucketObjects[key]) {
|
|
56
|
+
throw new Error(`Object not found: ${bucket}/${key}`);
|
|
57
|
+
}
|
|
58
|
+
const obj = bucketObjects[key];
|
|
59
|
+
return {
|
|
60
|
+
content: obj.content,
|
|
61
|
+
contentType: obj.contentType || 'text/plain',
|
|
62
|
+
contentLength: obj.content.length,
|
|
63
|
+
lastModified: obj.lastModified || new Date(),
|
|
64
|
+
etag: '"mock-etag"',
|
|
65
|
+
metadata: obj.metadata || {},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
async putObject(bucket, key, content, putOptions = {}) {
|
|
69
|
+
if (!objects) {
|
|
70
|
+
throw new Error('Mock data not initialized');
|
|
71
|
+
}
|
|
72
|
+
if (!objects[bucket]) {
|
|
73
|
+
objects[bucket] = {};
|
|
74
|
+
}
|
|
75
|
+
objects[bucket][key] = {
|
|
76
|
+
content,
|
|
77
|
+
contentType: putOptions.contentType,
|
|
78
|
+
metadata: putOptions.metadata,
|
|
79
|
+
lastModified: new Date(),
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
etag: '"mock-etag"',
|
|
83
|
+
generation: undefined,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
async deleteObject(bucket, key) {
|
|
87
|
+
if (objects?.[bucket]) {
|
|
88
|
+
delete objects[bucket][key];
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async createBucket(bucket) {
|
|
92
|
+
if (buckets.some((b) => b.name === bucket)) {
|
|
93
|
+
throw new Error(`Bucket already exists: ${bucket}`);
|
|
94
|
+
}
|
|
95
|
+
buckets.push({ name: bucket, creationDate: new Date() });
|
|
96
|
+
if (objects) {
|
|
97
|
+
objects[bucket] = {};
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
async deleteBucket(bucket) {
|
|
101
|
+
const index = buckets.findIndex((b) => b.name === bucket);
|
|
102
|
+
if (index === -1) {
|
|
103
|
+
throw new Error(`Bucket not found: ${bucket}`);
|
|
104
|
+
}
|
|
105
|
+
if (objects?.[bucket] && Object.keys(objects[bucket]).length > 0) {
|
|
106
|
+
throw new Error(`Bucket is not empty: ${bucket}`);
|
|
107
|
+
}
|
|
108
|
+
buckets.splice(index, 1);
|
|
109
|
+
if (objects) {
|
|
110
|
+
delete objects[bucket];
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async headBucket(bucket) {
|
|
114
|
+
return buckets.some((b) => b.name === bucket);
|
|
115
|
+
},
|
|
116
|
+
async copyObject(sourceBucket, sourceKey, destBucket, destKey) {
|
|
117
|
+
const sourceObj = objects?.[sourceBucket]?.[sourceKey];
|
|
118
|
+
if (!sourceObj) {
|
|
119
|
+
throw new Error(`Source object not found: ${sourceBucket}/${sourceKey}`);
|
|
120
|
+
}
|
|
121
|
+
if (!objects?.[destBucket]) {
|
|
122
|
+
throw new Error(`Destination bucket not found: ${destBucket}`);
|
|
123
|
+
}
|
|
124
|
+
objects[destBucket][destKey] = { ...sourceObj, lastModified: new Date() };
|
|
125
|
+
return {
|
|
126
|
+
etag: '"mock-etag"',
|
|
127
|
+
generation: undefined,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
export class GoogleCloudStorageClient {
|
|
3
|
+
storage;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
const storageOptions = {
|
|
6
|
+
projectId: config.projectId,
|
|
7
|
+
};
|
|
8
|
+
if (config.keyFilePath) {
|
|
9
|
+
storageOptions.keyFilename = config.keyFilePath;
|
|
10
|
+
}
|
|
11
|
+
else if (config.keyFileContents) {
|
|
12
|
+
try {
|
|
13
|
+
storageOptions.credentials = JSON.parse(config.keyFileContents);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error('Failed to parse GCS_SERVICE_ACCOUNT_KEY_JSON: invalid JSON. Ensure the value is a valid JSON string.');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// If neither is provided, uses Application Default Credentials (ADC)
|
|
20
|
+
this.storage = new Storage(storageOptions);
|
|
21
|
+
}
|
|
22
|
+
async listBuckets() {
|
|
23
|
+
const [buckets] = await this.storage.getBuckets();
|
|
24
|
+
return {
|
|
25
|
+
buckets: buckets.map((bucket) => ({
|
|
26
|
+
name: bucket.name,
|
|
27
|
+
creationDate: bucket.metadata.timeCreated
|
|
28
|
+
? new Date(bucket.metadata.timeCreated)
|
|
29
|
+
: undefined,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async listObjects(bucket, options = {}) {
|
|
34
|
+
const [files, , apiResponse] = await this.storage.bucket(bucket).getFiles({
|
|
35
|
+
prefix: options.prefix,
|
|
36
|
+
maxResults: options.maxResults,
|
|
37
|
+
pageToken: options.pageToken,
|
|
38
|
+
delimiter: options.delimiter,
|
|
39
|
+
autoPaginate: false,
|
|
40
|
+
});
|
|
41
|
+
const commonPrefixes = apiResponse?.prefixes?.map((p) => p) || [];
|
|
42
|
+
const nextPageToken = apiResponse?.nextPageToken;
|
|
43
|
+
return {
|
|
44
|
+
objects: files.map((file) => ({
|
|
45
|
+
key: file.name,
|
|
46
|
+
size: file.metadata.size ? Number(file.metadata.size) : undefined,
|
|
47
|
+
lastModified: file.metadata.updated ? new Date(file.metadata.updated) : undefined,
|
|
48
|
+
storageClass: file.metadata.storageClass,
|
|
49
|
+
etag: file.metadata.etag,
|
|
50
|
+
})),
|
|
51
|
+
commonPrefixes,
|
|
52
|
+
isTruncated: !!nextPageToken,
|
|
53
|
+
nextPageToken,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async getObject(bucket, key) {
|
|
57
|
+
const file = this.storage.bucket(bucket).file(key);
|
|
58
|
+
const [content] = await file.download();
|
|
59
|
+
const [metadata] = await file.getMetadata();
|
|
60
|
+
return {
|
|
61
|
+
content: content.toString('utf-8'),
|
|
62
|
+
contentType: metadata.contentType,
|
|
63
|
+
contentLength: metadata.size ? Number(metadata.size) : undefined,
|
|
64
|
+
lastModified: metadata.updated ? new Date(metadata.updated) : undefined,
|
|
65
|
+
etag: metadata.etag,
|
|
66
|
+
metadata: metadata.metadata || {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async putObject(bucket, key, content, options = {}) {
|
|
70
|
+
const file = this.storage.bucket(bucket).file(key);
|
|
71
|
+
await file.save(content, {
|
|
72
|
+
contentType: options.contentType,
|
|
73
|
+
metadata: options.metadata,
|
|
74
|
+
});
|
|
75
|
+
const [metadata] = await file.getMetadata();
|
|
76
|
+
return {
|
|
77
|
+
etag: metadata.etag,
|
|
78
|
+
generation: metadata.generation ? String(metadata.generation) : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async deleteObject(bucket, key) {
|
|
82
|
+
await this.storage.bucket(bucket).file(key).delete();
|
|
83
|
+
}
|
|
84
|
+
async createBucket(bucket, location) {
|
|
85
|
+
await this.storage.createBucket(bucket, {
|
|
86
|
+
location: location || 'US',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async deleteBucket(bucket) {
|
|
90
|
+
await this.storage.bucket(bucket).delete();
|
|
91
|
+
}
|
|
92
|
+
async headBucket(bucket) {
|
|
93
|
+
try {
|
|
94
|
+
await this.storage.bucket(bucket).getMetadata();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// Only return false for NotFound errors (bucket doesn't exist)
|
|
99
|
+
// Re-throw permission errors and other failures
|
|
100
|
+
if (typeof error === 'object' &&
|
|
101
|
+
error !== null &&
|
|
102
|
+
'code' in error &&
|
|
103
|
+
error.code === 404) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async copyObject(sourceBucket, sourceKey, destBucket, destKey) {
|
|
110
|
+
const sourceFile = this.storage.bucket(sourceBucket).file(sourceKey);
|
|
111
|
+
const destFile = this.storage.bucket(destBucket).file(destKey);
|
|
112
|
+
const [, apiResponse] = await sourceFile.copy(destFile);
|
|
113
|
+
const resource = apiResponse
|
|
114
|
+
?.resource;
|
|
115
|
+
return {
|
|
116
|
+
etag: resource?.etag,
|
|
117
|
+
generation: resource?.generation ? String(resource.generation) : undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { registerResources } from './resources.js';
|
|
2
|
+
export { createRegisterTools, type ToolGroup, parseEnabledToolGroups, parseToolFilters, getAllToolNames, } from './tools.js';
|
|
3
|
+
export { createMCPServer, type CreateMCPServerOptions, type GCSClientFactory, type IGCSClient, type GCSClientConfig, GoogleCloudStorageClient, } from './server.js';
|
|
4
|
+
export { type ListBucketsResult, type ListObjectsOptions, type ListObjectsResult, type GetObjectResult, type PutObjectOptions, type PutObjectResult, type CopyObjectResult, } from './gcs-client/gcs-client.js';
|
|
5
|
+
export { createIntegrationMockGCSClient, type MockGCSData, } from './gcs-client/gcs-client.integration-mock.js';
|
|
6
|
+
export { getSelectedResourceId, hasSelectedResource, isResourceLocked, getServerState, setSelectedResourceId, clearSelectedResource, initializeStateFromEnvironment, resetState, } from './state.js';
|
|
7
|
+
export { logServerStart, logError, logWarning, logInfo, logDebug } from './logging.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
package/shared/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Core server exports
|
|
2
|
+
export { registerResources } from './resources.js';
|
|
3
|
+
export { createRegisterTools, parseEnabledToolGroups, parseToolFilters, getAllToolNames, } from './tools.js';
|
|
4
|
+
export { createMCPServer, GoogleCloudStorageClient, } from './server.js';
|
|
5
|
+
export { createIntegrationMockGCSClient, } from './gcs-client/gcs-client.integration-mock.js';
|
|
6
|
+
// State management exports
|
|
7
|
+
export { getSelectedResourceId, hasSelectedResource, isResourceLocked, getServerState, setSelectedResourceId, clearSelectedResource, initializeStateFromEnvironment, resetState, } from './state.js';
|
|
8
|
+
// Logging exports (re-exported for convenience)
|
|
9
|
+
export { logServerStart, logError, logWarning, logInfo, logDebug } from './logging.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utilities for consistent output across MCP servers
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Log server startup message
|
|
6
|
+
*/
|
|
7
|
+
export declare function logServerStart(serverName: string, transport?: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Log an error with context
|
|
10
|
+
*/
|
|
11
|
+
export declare function logError(context: string, error: unknown): void;
|
|
12
|
+
/**
|
|
13
|
+
* Log a warning
|
|
14
|
+
*/
|
|
15
|
+
export declare function logWarning(context: string, message: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Log an informational message
|
|
18
|
+
*/
|
|
19
|
+
export declare function logInfo(context: string, message: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Log debug information (only in development)
|
|
22
|
+
*/
|
|
23
|
+
export declare function logDebug(context: string, message: string): void;
|
|
24
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utilities for consistent output across MCP servers
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Log server startup message
|
|
6
|
+
*/
|
|
7
|
+
export function logServerStart(serverName, transport = 'stdio') {
|
|
8
|
+
console.error(`MCP server ${serverName} running on ${transport}`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Log an error with context
|
|
12
|
+
*/
|
|
13
|
+
export function logError(context, error) {
|
|
14
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
16
|
+
console.error(`[ERROR] ${context}: ${message}`);
|
|
17
|
+
if (stack) {
|
|
18
|
+
console.error(stack);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Log a warning
|
|
23
|
+
*/
|
|
24
|
+
export function logWarning(context, message) {
|
|
25
|
+
console.error(`[WARN] ${context}: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Log an informational message
|
|
29
|
+
*/
|
|
30
|
+
export function logInfo(context, message) {
|
|
31
|
+
console.error(`[INFO] ${context}: ${message}`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Log debug information (only in development)
|
|
35
|
+
*/
|
|
36
|
+
export function logDebug(context, message) {
|
|
37
|
+
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
|
38
|
+
console.error(`[DEBUG] ${context}: ${message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { getServerState } from './state.js';
|
|
3
|
+
import { getAllToolNames } from './tools.js';
|
|
4
|
+
export function registerResources(server) {
|
|
5
|
+
// List available resources
|
|
6
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
7
|
+
return {
|
|
8
|
+
resources: [
|
|
9
|
+
{
|
|
10
|
+
uri: 'gcs://config',
|
|
11
|
+
name: 'Server Configuration',
|
|
12
|
+
description: 'Current server configuration and status. Shows enabled tool groups and tools.',
|
|
13
|
+
mimeType: 'application/json',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
// Read resource contents
|
|
19
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
20
|
+
const { uri } = request.params;
|
|
21
|
+
if (uri === 'gcs://config') {
|
|
22
|
+
const state = getServerState();
|
|
23
|
+
const config = {
|
|
24
|
+
server: {
|
|
25
|
+
name: 'gcs-mcp-server',
|
|
26
|
+
version: '0.0.0',
|
|
27
|
+
transport: 'stdio',
|
|
28
|
+
},
|
|
29
|
+
environment: {
|
|
30
|
+
GCS_PROJECT_ID: process.env.GCS_PROJECT_ID || 'not set',
|
|
31
|
+
GCS_SERVICE_ACCOUNT_KEY_FILE: process.env.GCS_SERVICE_ACCOUNT_KEY_FILE
|
|
32
|
+
? '***configured***'
|
|
33
|
+
: 'not set',
|
|
34
|
+
GCS_SERVICE_ACCOUNT_KEY_JSON: process.env.GCS_SERVICE_ACCOUNT_KEY_JSON
|
|
35
|
+
? '***configured***'
|
|
36
|
+
: 'not set',
|
|
37
|
+
GCS_ENABLED_TOOLGROUPS: process.env.GCS_ENABLED_TOOLGROUPS || 'all (default)',
|
|
38
|
+
GCS_ENABLED_TOOLS: process.env.GCS_ENABLED_TOOLS || 'not set',
|
|
39
|
+
GCS_DISABLED_TOOLS: process.env.GCS_DISABLED_TOOLS || 'not set',
|
|
40
|
+
GCS_BUCKET: process.env.GCS_BUCKET || 'not set',
|
|
41
|
+
SKIP_HEALTH_CHECKS: process.env.SKIP_HEALTH_CHECKS || 'false',
|
|
42
|
+
},
|
|
43
|
+
availableTools: getAllToolNames(),
|
|
44
|
+
state: {
|
|
45
|
+
selectedResourceId: state.selectedResourceId || 'none',
|
|
46
|
+
isResourceLocked: state.isResourceLocked,
|
|
47
|
+
},
|
|
48
|
+
capabilities: {
|
|
49
|
+
tools: true,
|
|
50
|
+
resources: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
contents: [
|
|
55
|
+
{
|
|
56
|
+
uri: 'gcs://config',
|
|
57
|
+
mimeType: 'application/json',
|
|
58
|
+
text: JSON.stringify(config, null, 2),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { type IGCSClient, GoogleCloudStorageClient, type GCSClientConfig } from './gcs-client/gcs-client.js';
|
|
3
|
+
export type { IGCSClient, GCSClientConfig };
|
|
4
|
+
export { GoogleCloudStorageClient };
|
|
5
|
+
export type GCSClientFactory = () => IGCSClient;
|
|
6
|
+
export interface CreateMCPServerOptions {
|
|
7
|
+
version: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createMCPServer(options: CreateMCPServerOptions): {
|
|
10
|
+
server: Server<{
|
|
11
|
+
method: string;
|
|
12
|
+
params?: {
|
|
13
|
+
[x: string]: unknown;
|
|
14
|
+
_meta?: {
|
|
15
|
+
[x: string]: unknown;
|
|
16
|
+
progressToken?: string | number | undefined;
|
|
17
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
18
|
+
taskId: string;
|
|
19
|
+
} | undefined;
|
|
20
|
+
} | undefined;
|
|
21
|
+
} | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
method: string;
|
|
24
|
+
params?: {
|
|
25
|
+
[x: string]: unknown;
|
|
26
|
+
_meta?: {
|
|
27
|
+
[x: string]: unknown;
|
|
28
|
+
progressToken?: string | number | undefined;
|
|
29
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
30
|
+
taskId: string;
|
|
31
|
+
} | undefined;
|
|
32
|
+
} | undefined;
|
|
33
|
+
} | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
[x: string]: unknown;
|
|
36
|
+
_meta?: {
|
|
37
|
+
[x: string]: unknown;
|
|
38
|
+
progressToken?: string | number | undefined;
|
|
39
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
40
|
+
taskId: string;
|
|
41
|
+
} | undefined;
|
|
42
|
+
} | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
registerHandlers: (server: Server, clientFactory?: GCSClientFactory) => Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=server.d.ts.map
|
package/shared/server.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { registerResources } from './resources.js';
|
|
3
|
+
import { createRegisterTools } from './tools.js';
|
|
4
|
+
import { GoogleCloudStorageClient, } from './gcs-client/gcs-client.js';
|
|
5
|
+
export { GoogleCloudStorageClient };
|
|
6
|
+
export function createMCPServer(options) {
|
|
7
|
+
const server = new Server({
|
|
8
|
+
name: 'gcs-mcp-server',
|
|
9
|
+
version: options.version,
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: {
|
|
12
|
+
resources: {},
|
|
13
|
+
tools: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const registerHandlers = async (server, clientFactory) => {
|
|
17
|
+
// Use provided factory or create default client from environment variables
|
|
18
|
+
const factory = clientFactory ||
|
|
19
|
+
(() => {
|
|
20
|
+
const projectId = process.env.GCS_PROJECT_ID;
|
|
21
|
+
const keyFilePath = process.env.GCS_SERVICE_ACCOUNT_KEY_FILE;
|
|
22
|
+
const keyFileContents = process.env.GCS_SERVICE_ACCOUNT_KEY_JSON;
|
|
23
|
+
if (!projectId) {
|
|
24
|
+
throw new Error('GCS_PROJECT_ID environment variable must be configured');
|
|
25
|
+
}
|
|
26
|
+
return new GoogleCloudStorageClient({
|
|
27
|
+
projectId,
|
|
28
|
+
keyFilePath,
|
|
29
|
+
keyFileContents,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
registerResources(server);
|
|
33
|
+
const registerTools = createRegisterTools(factory);
|
|
34
|
+
registerTools(server);
|
|
35
|
+
};
|
|
36
|
+
return { server, registerHandlers };
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server state interface.
|
|
3
|
+
*/
|
|
4
|
+
interface ServerState {
|
|
5
|
+
/** Currently selected resource ID (e.g., workspace, project, app) */
|
|
6
|
+
selectedResourceId: string | null;
|
|
7
|
+
/** Whether the resource ID is locked (e.g., from environment variable) */
|
|
8
|
+
isResourceLocked: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get the currently selected resource ID.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getSelectedResourceId(): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a resource is currently selected.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasSelectedResource(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check if the resource selection is locked (e.g., from environment variable).
|
|
20
|
+
*/
|
|
21
|
+
export declare function isResourceLocked(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Get the full server state (useful for debugging/config resource).
|
|
24
|
+
*/
|
|
25
|
+
export declare function getServerState(): Readonly<ServerState>;
|
|
26
|
+
/**
|
|
27
|
+
* Set the selected resource ID.
|
|
28
|
+
*/
|
|
29
|
+
export declare function setSelectedResourceId(resourceId: string, locked?: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* Clear the selected resource (only if not locked).
|
|
32
|
+
*/
|
|
33
|
+
export declare function clearSelectedResource(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize state from environment variables.
|
|
36
|
+
*/
|
|
37
|
+
export declare function initializeStateFromEnvironment(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Reset all state (useful for testing).
|
|
40
|
+
*/
|
|
41
|
+
export declare function resetState(): void;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=state.d.ts.map
|
package/shared/state.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// In-memory state (reset on server restart)
|
|
2
|
+
let state = {
|
|
3
|
+
selectedResourceId: null,
|
|
4
|
+
isResourceLocked: false,
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Get the currently selected resource ID.
|
|
8
|
+
*/
|
|
9
|
+
export function getSelectedResourceId() {
|
|
10
|
+
return state.selectedResourceId;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Check if a resource is currently selected.
|
|
14
|
+
*/
|
|
15
|
+
export function hasSelectedResource() {
|
|
16
|
+
return state.selectedResourceId !== null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if the resource selection is locked (e.g., from environment variable).
|
|
20
|
+
*/
|
|
21
|
+
export function isResourceLocked() {
|
|
22
|
+
return state.isResourceLocked;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the full server state (useful for debugging/config resource).
|
|
26
|
+
*/
|
|
27
|
+
export function getServerState() {
|
|
28
|
+
return { ...state };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set the selected resource ID.
|
|
32
|
+
*/
|
|
33
|
+
export function setSelectedResourceId(resourceId, locked = false) {
|
|
34
|
+
if (state.isResourceLocked && state.selectedResourceId !== resourceId) {
|
|
35
|
+
throw new Error(`Cannot change resource: current selection is locked to "${state.selectedResourceId}"`);
|
|
36
|
+
}
|
|
37
|
+
state.selectedResourceId = resourceId;
|
|
38
|
+
state.isResourceLocked = locked;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Clear the selected resource (only if not locked).
|
|
42
|
+
*/
|
|
43
|
+
export function clearSelectedResource() {
|
|
44
|
+
if (state.isResourceLocked) {
|
|
45
|
+
throw new Error('Cannot clear resource: selection is locked');
|
|
46
|
+
}
|
|
47
|
+
state.selectedResourceId = null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Initialize state from environment variables.
|
|
51
|
+
*/
|
|
52
|
+
export function initializeStateFromEnvironment() {
|
|
53
|
+
const envResourceId = process.env.GCS_PROJECT_ID;
|
|
54
|
+
if (envResourceId) {
|
|
55
|
+
state.selectedResourceId = envResourceId;
|
|
56
|
+
state.isResourceLocked = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reset all state (useful for testing).
|
|
61
|
+
*/
|
|
62
|
+
export function resetState() {
|
|
63
|
+
state = {
|
|
64
|
+
selectedResourceId: null,
|
|
65
|
+
isResourceLocked: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import type { GCSClientFactory } from '../server.js';
|
|
4
|
+
export declare const CopyObjectSchema: z.ZodObject<{
|
|
5
|
+
sourceBucket: z.ZodString;
|
|
6
|
+
sourceKey: z.ZodString;
|
|
7
|
+
destBucket: z.ZodString;
|
|
8
|
+
destKey: z.ZodString;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
sourceBucket: string;
|
|
11
|
+
sourceKey: string;
|
|
12
|
+
destBucket: string;
|
|
13
|
+
destKey: string;
|
|
14
|
+
}, {
|
|
15
|
+
sourceBucket: string;
|
|
16
|
+
sourceKey: string;
|
|
17
|
+
destBucket: string;
|
|
18
|
+
destKey: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function copyObjectTool(_server: Server, clientFactory: GCSClientFactory): {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object";
|
|
25
|
+
properties: {
|
|
26
|
+
sourceBucket: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: "The source bucket name (e.g., \"source-bucket\")";
|
|
29
|
+
};
|
|
30
|
+
sourceKey: {
|
|
31
|
+
type: string;
|
|
32
|
+
description: "The source object key (e.g., \"path/to/source-file.txt\")";
|
|
33
|
+
};
|
|
34
|
+
destBucket: {
|
|
35
|
+
type: string;
|
|
36
|
+
description: "The destination bucket name (e.g., \"dest-bucket\")";
|
|
37
|
+
};
|
|
38
|
+
destKey: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: "The destination object key (e.g., \"path/to/dest-file.txt\")";
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
required: string[];
|
|
44
|
+
};
|
|
45
|
+
handler: (args: unknown) => Promise<{
|
|
46
|
+
content: {
|
|
47
|
+
type: string;
|
|
48
|
+
text: string;
|
|
49
|
+
}[];
|
|
50
|
+
isError?: undefined;
|
|
51
|
+
} | {
|
|
52
|
+
content: {
|
|
53
|
+
type: string;
|
|
54
|
+
text: string;
|
|
55
|
+
}[];
|
|
56
|
+
isError: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=copy-object.d.ts.map
|