@takashito/linode-mcp-server
Version:
MCP server for Linode API
653 lines (651 loc) • 28.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerObjectStorageTools = registerObjectStorageTools;
const client_1 = require("../../client");
const schemas_1 = require("../common/schemas");
const schemas = __importStar(require("./schemas"));
const errorHandler_1 = require("../common/errorHandler");
const fs = __importStar(require("fs"));
const axios_1 = __importDefault(require("axios"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const url_1 = require("url");
const mime = __importStar(require("mime-types"));
function registerObjectStorageTools(server) {
// Endpoints
server.addTool({
name: 'list_object_storage_endpoints',
description: 'Get a list of all Object Storage endpoints with their types',
parameters: (0, schemas_1.mcpInput)(schemas.listEndpointsSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getEndpoints(params);
return JSON.stringify(result, null, 2);
})
});
// Buckets
server.addTool({
name: 'list_object_storage_buckets',
description: 'Get a list of all Object Storage buckets',
parameters: (0, schemas_1.mcpInput)(schemas.listBucketsSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getBuckets(params);
return JSON.stringify(result.data, null, 2);
})
});
server.addTool({
name: 'get_object_storage_bucket',
description: 'Get details for a specific Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.getBucketSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
// The client implementation expects region (cluster in client code) and bucket name
const result = await (0, client_1.createClient)(context).objectStorage.getBucket(params.region, params.bucket // Use bucket parameter from the schema for lookup
);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'create_object_storage_bucket',
description: 'Create a new Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.createBucketSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
// Ensure parameters match the client's expectations
// The Linode API expects 'label' for the bucket name and 'region' for the location
const createParams = {
label: params.label,
region: params.region,
endpoint_type: params.endpoint_type,
acl: params.acl,
cors_enabled: params.cors_enabled
};
const result = await (0, client_1.createClient)(context).objectStorage.createBucket(createParams);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'delete_object_storage_bucket',
description: 'Delete an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.deleteBucketSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, ...data } = params;
await (0, client_1.createClient)(context).objectStorage.deleteBucket(region, bucket);
return {
content: [
{ type: 'text', text: `Bucket '${params.bucket}' in region '${params.region}' has been deleted.` },
],
};
})
});
server.addTool({
name: 'get_object_storage_bucket_access',
description: 'Get access configuration for an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.getBucketAccessSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, ...data } = params;
const result = await (0, client_1.createClient)(context).objectStorage.getBucketAccess(region, bucket);
return JSON.stringify(result, null, 2);
})
});
// Objects
server.addTool({
name: 'list_object_storage_objects',
description: 'List objects in an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.listObjectsSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, ...paginationParams } = params;
const result = await (0, client_1.createClient)(context).objectStorage.getObjects(region, bucket, paginationParams);
return JSON.stringify(result.data, null, 2);
})
});
// SSL/TLS certificates
server.addTool({
name: 'get_object_storage_bucket_certificate',
description: 'Get SSL/TLS certificate for an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.getBucketCertificateSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getBucketCertificate(params.region, params.bucket);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'upload_object_storage_bucket_certificate',
description: 'Upload SSL/TLS certificate for an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.uploadBucketCertificateSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, certificate, private_key } = params;
const result = await (0, client_1.createClient)(context).objectStorage.uploadBucketCertificate(region, bucket, {
certificate,
private_key,
});
return {
content: [
{ type: 'text', text: `Certificate uploaded for bucket '${bucket}' in region '${region}'.
Expires: ${new Date(result.expiry).toLocaleString()}` },
],
};
})
});
server.addTool({
name: 'delete_object_storage_bucket_certificate',
description: 'Delete SSL/TLS certificate for an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.deleteBucketCertificateSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
await (0, client_1.createClient)(context).objectStorage.deleteBucketCertificate(params.region, params.bucket);
return {
content: [
{ type: 'text', text: `Certificate for bucket '${params.bucket}' in region '${params.region}' has been deleted.` },
],
};
})
});
// Access keys
server.addTool({
name: 'list_object_storage_keys',
description: 'Get a list of all Object Storage keys',
parameters: (0, schemas_1.mcpInput)(schemas.listKeysSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getKeys(params);
return JSON.stringify(result.data, null, 2);
})
});
server.addTool({
name: 'get_object_storage_key',
description: 'Get details for a specific Object Storage key',
parameters: (0, schemas_1.mcpInput)(schemas.getKeySchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getKey(params.id);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'create_object_storage_key',
description: 'Create a new Object Storage key',
parameters: (0, schemas_1.mcpInput)(schemas.createKeySchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.createKey(params);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'update_object_storage_key',
description: 'Update an Object Storage key',
parameters: (0, schemas_1.mcpInput)(schemas.updateKeySchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { id, ...updateData } = params;
const result = await (0, client_1.createClient)(context).objectStorage.updateKey(id, updateData);
return JSON.stringify(result, null, 2);
})
});
server.addTool({
name: 'delete_object_storage_key',
description: 'Delete an Object Storage key',
parameters: (0, schemas_1.mcpInput)(schemas.deleteKeySchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
await (0, client_1.createClient)(context).objectStorage.deleteKey(params.id);
return {
content: [
{ type: 'text', text: `Object Storage key with ID ${params.id} has been deleted.` },
],
};
})
});
// Object ACL Management
server.addTool({
name: 'update_object_acl',
description: 'Update access control level (ACL) for an object in a bucket',
parameters: (0, schemas_1.mcpInput)(schemas.updateObjectACLSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, name, acl } = params;
await (0, client_1.createClient)(context).objectStorage.updateObjectACL(region, bucket, name, { acl });
return {
content: [
{ type: 'text', text: `ACL for object '${name}' in bucket '${bucket}' has been updated to '${acl}'.` },
],
};
})
});
// Object URL Generation
server.addTool({
name: 'generate_object_url',
description: 'Generate a pre-signed URL for an object in a bucket',
parameters: (0, schemas_1.mcpInput)(schemas.getObjectURLSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, name, ...urlParams } = params;
// Ensure we're using the right parameter names for the API
const apiParams = {
name,
...urlParams
};
const result = await (0, client_1.createClient)(context).objectStorage.getObjectURL(region, bucket, apiParams);
return {
content: [
{ type: 'text', text: `Pre-signed URL for object '${name}' in bucket '${bucket}':
${result.url}
Note: This URL is temporary and will expire after ${params.expires_in || 3600} seconds.` },
],
};
})
});
// Upload Object
server.addTool({
name: 'upload_object',
description: 'Upload and create an new object to an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.uploadObjectSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, object_path, source, content_type, acl, expires_in } = params;
// Determine content type from file extension or parameter
const determinedContentType = content_type || getContentType(object_path);
// Get pre-signed URL for PUT operation
const urlResult = await (0, client_1.createClient)(context).objectStorage.getObjectURL(region, bucket, {
name: object_path,
method: 'PUT',
expires_in: expires_in || 3600,
content_type: determinedContentType
});
// Get data from source based on type
let data;
let dataSource;
if (isURL(source)) {
data = await fetchFromURL(source);
dataSource = 'URL';
}
else if (isFilePath(source)) {
data = await readFromFile(source);
dataSource = 'File';
}
else {
// Treat as raw string content
data = Buffer.from(source);
dataSource = 'String content';
}
// Upload to pre-signed URL
await axios_1.default.put(urlResult.url, data, {
headers: {
'Content-Type': determinedContentType
}
});
// Set ACL if provided
if (acl) {
await (0, client_1.createClient)(context).objectStorage.updateObjectACL(region, bucket, object_path, { acl });
}
return {
content: [
{ type: 'text', text: `Successfully uploaded object '${object_path}' to bucket '${bucket}'.
Source: ${dataSource}
Size: ${formatBytes(data.length)}
Content Type: ${determinedContentType}
ACL: ${acl || 'Default bucket ACL'}` },
],
};
})
});
// Download Object
server.addTool({
name: 'download_object',
description: 'Download an object from a bucket and save it to a local file',
parameters: (0, schemas_1.mcpInput)(schemas.downloadObjectSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, object_path, destination, expires_in } = params;
// Get pre-signed URL for GET operation
const result = await (0, client_1.createClient)(context).objectStorage.getObjectURL(region, bucket, {
name: object_path,
method: 'GET',
expires_in: expires_in || 3600
});
// If destination is provided, download the file
if (destination) {
// Download the file to the specified location
const downloadResult = await downloadToFile(result.url, destination);
return {
content: [
{ type: 'text', text: `Successfully downloaded '${object_path}' from bucket '${bucket}'.
Saved to: ${downloadResult.filePath}
Size: ${formatBytes(downloadResult.size)}
Content Type: ${getContentType(object_path)}` },
],
};
}
// Get the appropriate download directory (Downloads folder or home directory)
const downloadDir = getDownloadDirectory();
const localFilename = path.basename(object_path);
const localPath = path.join(downloadDir, localFilename);
// Download the file
const downloadResult = await downloadToFile(result.url, localPath);
return {
content: [
{ type: 'text', text: `Successfully downloaded '${object_path}' from bucket '${bucket}'.
Saved to: ${downloadResult.filePath}
Size: ${formatBytes(downloadResult.size)}
Content Type: ${getContentType(object_path)}` },
],
};
})
});
// Delete Object
server.addTool({
name: 'delete_object',
description: 'Delete an object from an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.deleteObjectSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { region, bucket, object_path, expires_in } = params;
// Get pre-signed URL for DELETE operation
const result = await (0, client_1.createClient)(context).objectStorage.getObjectURL(region, bucket, {
name: object_path,
method: 'DELETE',
expires_in: expires_in || 3600
});
// Execute the DELETE request
await axios_1.default.delete(result.url);
return {
content: [
{ type: 'text', text: `Successfully deleted object '${object_path}' from bucket '${bucket}'.` },
],
};
})
});
// Per-region bucket listing
server.addTool({
name: 'list_object_storage_buckets_in_region',
description: 'List Object Storage buckets in a specific region',
parameters: (0, schemas_1.mcpInput)(schemas.listBucketsInRegionSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { regionId, ...paginationParams } = params;
const result = await (0, client_1.createClient)(context).objectStorage.getBucketsInRegion(regionId, paginationParams);
return {
content: [
{ type: 'text', text: JSON.stringify(result, null, 2) },
],
};
})
});
// Object ACL Get
server.addTool({
name: 'get_object_acl',
description: 'Get an Object Storage object ACL configuration',
parameters: (0, schemas_1.mcpInput)(schemas.getObjectACLSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { regionId, bucket, name } = params;
const result = await (0, client_1.createClient)(context).objectStorage.getObjectACL(regionId, bucket, name);
return {
content: [
{ type: 'text', text: JSON.stringify(result, null, 2) },
],
};
})
});
// Bucket Access Update (PUT)
server.addTool({
name: 'update_object_storage_bucket_access',
description: 'Update access to an Object Storage bucket',
parameters: (0, schemas_1.mcpInput)(schemas.putBucketAccessSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const { regionId, bucket, ...data } = params;
await (0, client_1.createClient)(context).objectStorage.putBucketAccess(regionId, bucket, data);
return {
content: [
{ type: 'text', text: JSON.stringify({ status: 'success', message: `Bucket access updated for '${bucket}' in region '${regionId}'.` }, null, 2) },
],
};
})
});
// Object Storage Quotas
server.addTool({
name: 'list_object_storage_quotas',
description: 'List Object Storage quotas',
parameters: (0, schemas_1.mcpInput)(schemas.listObjectStorageQuotasSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getQuotas(params);
return {
content: [
{ type: 'text', text: JSON.stringify(result, null, 2) },
],
};
})
});
server.addTool({
name: 'get_object_storage_quota',
description: 'Get an Object Storage quota',
parameters: (0, schemas_1.mcpInput)(schemas.getObjectStorageQuotaSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getQuota(params.objQuotaId);
return {
content: [
{ type: 'text', text: JSON.stringify(result, null, 2) },
],
};
})
});
server.addTool({
name: 'get_object_storage_quota_usage',
description: 'Get Object Storage quota usage data',
parameters: (0, schemas_1.mcpInput)(schemas.getObjectStorageQuotaUsageSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getQuotaUsage(params.objQuotaId);
return {
content: [
{ type: 'text', text: JSON.stringify(result, null, 2) },
],
};
})
});
// Transfer Statistics
server.addTool({
name: 'get_object_storage_transfer',
description: 'Get Object Storage transfer statistics',
parameters: (0, schemas_1.mcpInput)(schemas.getTransferStatsSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getTransferStats();
return JSON.stringify(result, null, 2);
})
});
// Object Storage Types
server.addTool({
name: 'list_object_storage_types',
description: 'Get a list of all available Object Storage types and prices, including any region-specific rates.',
parameters: (0, schemas_1.mcpInput)(schemas.listObjectStorageTypesSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
const result = await (0, client_1.createClient)(context).objectStorage.getTypes();
return JSON.stringify(result, null, 2);
})
});
// Service
server.addTool({
name: 'cancel_object_storage',
description: 'Cancel Object Storage service',
parameters: (0, schemas_1.mcpInput)(schemas.cancelObjectStorageSchema),
execute: (0, errorHandler_1.withErrorHandling)(async (params, context) => {
await (0, client_1.createClient)(context).objectStorage.cancelObjectStorage();
return {
content: [
{ type: 'text', text: 'Object Storage service has been cancelled.' },
],
};
})
});
}
/**
* Formats an Object Storage cluster for display
*/
/**
* Formats bytes into a human-readable string
*/
function formatBytes(bytes, decimals = 2) {
if (bytes === 0)
return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
/**
* Determines if a string is a URL
*/
function isURL(str) {
try {
new url_1.URL(str);
return str.startsWith('http://') || str.startsWith('https://');
}
catch {
return false;
}
}
/**
* Determines if a string is likely a file path
*/
function isFilePath(str) {
// Check for Unix-like paths
const unixPathPattern = str.startsWith('/') || str.includes('./') || str.includes('../');
// Check for Windows paths (drive letter or UNC path)
const windowsDrivePattern = /^[a-zA-Z]:[/\\]/.test(str);
const windowsUNCPattern = /^\\\\/.test(str);
return unixPathPattern || windowsDrivePattern || windowsUNCPattern;
}
/**
* Gets data from a URL
*/
async function fetchFromURL(url) {
try {
const response = await axios_1.default.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
}
catch (error) {
throw new Error(`Failed to fetch from URL: ${error.message}`);
}
}
/**
* Reads data from a file
*/
async function readFromFile(filePath) {
try {
return await fs.promises.readFile(filePath);
}
catch (error) {
throw new Error(`Failed to read file: ${error.message}`);
}
}
/**
* Downloads data from a URL and saves it to a file
*/
async function downloadToFile(url, destinationPath) {
try {
// Get the data from the URL
const response = await axios_1.default.get(url, { responseType: 'arraybuffer' });
// Make sure the directory exists
const dirname = path.dirname(destinationPath);
await fs.promises.mkdir(dirname, { recursive: true });
// Write the file
await fs.promises.writeFile(destinationPath, response.data);
return {
filePath: destinationPath,
size: response.data.length
};
}
catch (error) {
throw new Error(`Failed to download file: ${error.message}`);
}
}
/**
* Gets content type based on file path or URL
*/
function getContentType(source) {
if (source.includes('.')) {
const extension = source.split('.').pop()?.toLowerCase();
if (extension) {
const mimeType = mime.lookup(extension);
if (mimeType) {
return mimeType;
}
}
}
return 'application/octet-stream'; // Default if no extension or unknown type
}
/**
* Gets the most appropriate download directory path based on OS
*/
function getDownloadDirectory() {
const homeDir = os.homedir();
// Try standard download directories based on OS
if (process.platform === 'win32') {
// Windows: First try Downloads folder in user's home directory
const windowsDownloads = path.join(homeDir, 'Downloads');
if (fs.existsSync(windowsDownloads)) {
return windowsDownloads;
}
}
else if (process.platform === 'darwin') {
// macOS: Check standard Downloads directory
const macDownloads = path.join(homeDir, 'Downloads');
if (fs.existsSync(macDownloads)) {
return macDownloads;
}
}
else {
// Linux/Unix: Check standard XDG downloads directory or fallback
// Try XDG user directories first if defined
try {
// Check if XDG_DOWNLOAD_DIR is defined in user-dirs.dirs
const xdgConfigPath = path.join(homeDir, '.config', 'user-dirs.dirs');
if (fs.existsSync(xdgConfigPath)) {
const content = fs.readFileSync(xdgConfigPath, 'utf-8');
const match = content.match(/XDG_DOWNLOAD_DIR="([^"]+)"/);
if (match && match[1]) {
let downloadDir = match[1].replace('$HOME', homeDir);
// Handle tilde expansion
if (downloadDir.startsWith('~/')) {
downloadDir = path.join(homeDir, downloadDir.substring(2));
}
if (fs.existsSync(downloadDir)) {
return downloadDir;
}
}
}
}
catch (error) {
// Ignore errors reading XDG config and fall back
}
// Standard Linux fallback to ~/Downloads if it exists
const linuxDownloads = path.join(homeDir, 'Downloads');
if (fs.existsSync(linuxDownloads)) {
return linuxDownloads;
}
}
// Default fallback to user's home directory if no Downloads folder exists
return homeDir;
}
//# sourceMappingURL=tools.js.map