# Directory Structure ``` ├── .gitignore ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── server.ts │ └── types.ts ├── tsconfig.json └── tsconfig.tsbuildinfo ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ dist/ .env .DS_Store ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Sakura Cloud MCP Server A Model Context Protocol (MCP) server implementation for interacting with Sakura Cloud's API. ## What is MCP? The Model Context Protocol (MCP) is a standardized communication protocol that enables AI applications to securely interact with external systems and data sources. It follows a client-server architecture where LLM applications initiate connections to servers that expose resources and tools. ## Overview This project implements an MCP server that allows AI assistants to interact with Sakura Cloud infrastructure through a standardized interface. It enables AI assistants to: - Access Sakura Cloud resources like servers, disks, networks, and more - Use tools to list resources and retrieve detailed information about specific resources - Query public pricing information without authentication requirements - Manage AppRun containerized applications ## Prerequisites - Node.js (v16 or higher) - Sakura Cloud API credentials (token and secret) - Claude Desktop app for using with Claude (MCP is currently only supported in the desktop app) ## Installation ```bash # Clone the repository git clone https://github.com/hidenorigoto/sacloud-mcp.git cd sacloud-mcp # Install dependencies npm install # Build the project npm run build ``` ## Configuration Set the following environment variables: - `SACLOUD_API_TOKEN`: Your Sakura Cloud API token - `SACLOUD_API_SECRET`: Your Sakura Cloud API secret ## Usage ### Available Resources | Resource URI | Description | |--------------|-------------| | `sakura:///servers` | Lists all servers in your Sakura Cloud account | | `sakura:///switches` | Lists all switches in your Sakura Cloud account | | `sakura:///appliances` | Lists all appliances in your Sakura Cloud account | | `sakura:///disks` | Lists all disks in your Sakura Cloud account | | `sakura:///archives` | Lists all archives in your Sakura Cloud account | | `sakura:///cdrom` | Lists all ISO images (CD-ROMs) in your Sakura Cloud account | | `sakura:///bridge` | Lists all bridges in your Sakura Cloud account | | `sakura:///internet` | Lists all routers in your Sakura Cloud account | | `sakura:///interface` | Lists all network interfaces in your Sakura Cloud account | | `sakura:///icon` | Lists all icons in your Sakura Cloud account | | `sakura:///note` | Lists all startup scripts and notes in your Sakura Cloud account | | `sakura:///sshkey` | Lists all SSH keys in your Sakura Cloud account | | `sakura:///region` | Lists all regions in your Sakura Cloud account | | `sakura:///zone` | Lists all zones in your Sakura Cloud account | | `sakura:///product` | Lists all available products in your Sakura Cloud account | | `sakura:///commonserviceitem` | Lists all common service items (DNS, Simple Monitor, etc.) in your Sakura Cloud account | | `sakura:///license` | Lists all licenses in your Sakura Cloud account | | `sakura:///auth-status` | Shows current authentication status and permissions | | `sakura:///bill` | Shows monthly billing information | | `sakura:///bill-detail` | Shows detailed breakdown of billing information | | `sakura:///coupon` | Lists all available coupons | | `sakura:///privatehost` | Lists all private hosts in your Sakura Cloud account | | `sakura:///public-price` | Shows public pricing information for Sakura Cloud services (no authentication required) | | `sakura:///apprun` | Lists all AppRun applications in your Sakura Cloud account | ### Available Tools | Tool Name | Description | Required Parameters | |-----------|-------------|---------------------| | `get_server_list` | Retrieves list of all servers | None | | `get_server_info` | Retrieves detailed information about a specific server | `serverId` | | `get_switch_list` | Retrieves list of all switches | None | | `get_switch_info` | Retrieves detailed information about a specific switch | `switchId` | | `get_appliance_list` | Retrieves list of all appliances | None | | `get_appliance_info` | Retrieves detailed information about a specific appliance | `applianceId` | | `get_disk_list` | Retrieves list of all disks | None | | `get_disk_info` | Retrieves detailed information about a specific disk | `diskId` | | `get_archive_list` | Retrieves list of all archives | None | | `get_archive_info` | Retrieves detailed information about a specific archive | `archiveId` | | `get_cdrom_list` | Retrieves list of all ISO images | None | | `get_cdrom_info` | Retrieves detailed information about a specific ISO image | `cdromId` | | `get_bridge_list` | Retrieves list of all bridges | None | | `get_bridge_info` | Retrieves detailed information about a specific bridge | `bridgeId` | | `get_router_list` | Retrieves list of all routers | None | | `get_router_info` | Retrieves detailed information about a specific router | `routerId` | | `get_interface_list` | Retrieves list of all network interfaces | None | | `get_interface_info` | Retrieves detailed information about a specific network interface | `interfaceId` | | `get_icon_list` | Retrieves list of all icons | None | | `get_icon_info` | Retrieves detailed information about a specific icon | `iconId` | | `get_note_list` | Retrieves list of all notes and startup scripts | None | | `get_note_info` | Retrieves detailed information about a specific note or startup script | `noteId` | | `get_sshkey_list` | Retrieves list of all SSH keys | None | | `get_sshkey_info` | Retrieves detailed information about a specific SSH key | `sshkeyId` | | `get_region_list` | Retrieves list of all regions | None | | `get_region_info` | Retrieves detailed information about a specific region | `regionId` | | `get_zone_list` | Retrieves list of all zones | None | | `get_zone_info` | Retrieves detailed information about a specific zone | `zoneId` | | `get_product_info` | Retrieves detailed information about specific product offerings | `productType` | | `get_commonserviceitem_list` | Retrieves list of all common service items | None | | `get_commonserviceitem_info` | Retrieves detailed information about a specific common service item | `itemId` | | `get_license_list` | Retrieves list of all licenses | None | | `get_license_info` | Retrieves detailed information about a specific license | `licenseId` | | `get_bill_info` | Retrieves billing information for a specific month | `year`, `month` | | `get_bill_detail` | Retrieves detailed billing information for a specific month | `year`, `month` | | `get_coupon_info` | Retrieves information about a specific coupon | `couponId` | | `get_privatehost_info` | Retrieves detailed information about a specific private host | `privateHostId` | | `get_public_price` | Retrieves public pricing information for Sakura Cloud services | None | | `get_apprun_list` | Retrieves list of all AppRun applications | None | | `get_apprun_info` | Retrieves detailed information about a specific AppRun application | `appId` | | `create_apprun` | Creates a new AppRun application | `name`, `dockerImage`, `planId` | | `delete_apprun` | Deletes an AppRun application | `appId` | | `start_apprun` | Starts an AppRun application | `appId` | | `stop_apprun` | Stops an AppRun application | `appId` | | `update_apprun` | Updates an existing AppRun application | `appId` | | `get_apprun_logs` | Gets logs from an AppRun application | `appId` | ## AppRun Integration Sakura Cloud AppRun is a containerized application platform that allows you to run Docker containers without managing infrastructure. This MCP server provides full AppRun management capabilities: - View all your AppRun applications - Create new applications with custom Docker images - Update existing applications (change image, configuration, etc.) - Start and stop applications - View application logs - Delete applications when no longer needed When creating or updating an AppRun application, you can specify: - Application name and description - Docker image to use - Plan ID (determines resources allocated) - Environment variables as key-value pairs ## Zone Support All API calls support specifying a zone parameter to target specific Sakura Cloud data centers. The default zone is `tk1v` (Tokyo), but you can specify others such as: - `is1a` (Ishikari) - `tk1a` (Tokyo) - And more... Example URI with zone parameter: `sakura:///servers?zone=is1a` ## Integrating with Claude Claude Desktop app provides MCP support. Follow these steps to integrate this server with Claude: 1. Make sure the server is running locally or on an accessible host. 2. Create a `claude_desktop_config.json` file in the appropriate location for your OS: - Windows: `%APPDATA%\Claude\claude_desktop_config.json` - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Linux: `~/.config/Claude/claude_desktop_config.json` 3. Add the following configuration to the file: ```json { "sacloud-server": { "command": "node", "args": ["path/to/mcp/dist/server.js"], "env": { "SACLOUD_API_TOKEN": "your_token_here", "SACLOUD_API_SECRET": "your_secret_here" } } } ``` 4. Restart the Claude Desktop app to apply the configuration. 5. In a conversation with Claude, you can now access Sakura Cloud resources and tools. ## Security Considerations - This server handles sensitive API credentials - Never commit API tokens or secrets to version control - Use environment variables for all sensitive information - Implement proper access controls in production ## License ISC ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "es2016", "module": "commonjs", "rootDir": "./src", "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "sacloud-mcp", "version": "1.0.0", "main": "dist/server.js", "scripts": { "start": "node dist/server.js", "dev": "ts-node src/server.ts", "build": "tsc", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "devDependencies": { "@types/node": "^22.13.14", "ts-node": "^10.9.2", "typescript": "^5.8.2" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.8.0" } } ``` -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- ```typescript declare module '@modelcontextprotocol/sdk' { export interface ServerConfig { name: string; version: string; } export interface TransportConfig { transport: { type: 'http'; port: number; }; } export class Server { constructor(serverConfig: ServerConfig, transportConfig: TransportConfig); setRequestHandler<P, R>(schema: { params: P, result: R }, handler: (request: { params: P }) => Promise<R>): void; listen(): Promise<void>; close(): Promise<void>; } export const ListResourcesRequestSchema: { params: null; result: { resources: Array<{ uri: string; name: string; description?: string; mimeType?: string; }>; }; }; export const ReadResourceRequestSchema: { params: { uri: string; }; result: { contents: Array<{ uri: string; mimeType: string; text: string; }>; }; }; export const ListToolsRequestSchema: { params: null; result: { tools: Array<{ name: string; description: string; inputSchema: { type: string; properties: Record<string, any>; required?: string[]; }; }>; }; }; export const CallToolRequestSchema: { params: { name: string; arguments: Record<string, any>; }; result: { content: Array<{ type: string; text: string; }>; }; }; } ``` -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import https from 'https'; const SACLOUD_API_TOKEN = process.env.SACLOUD_API_TOKEN || ''; const SACLOUD_API_SECRET = process.env.SACLOUD_API_SECRET || ''; // Default zone to use if not specified const DEFAULT_ZONE = 'tk1v'; // Helper function to make API calls to Sakura Cloud async function fetchFromSakuraCloud(path: string, isPublicAPI: boolean = false, zone: string = DEFAULT_ZONE, method: string = 'GET', bodyData?: any): Promise<any> { return new Promise((resolve, reject) => { const basePath = isPublicAPI ? '/cloud/api/cloud/1.1' : `/cloud/zone/${zone}/api/cloud/1.1`; const options = { hostname: 'secure.sakura.ad.jp', port: 443, path: `${basePath}${path}`, method: method, headers: { 'Accept': 'application/json', 'Authorization': '', 'Content-Type': 'application/json' } }; // Add authorization for non-public APIs if (!isPublicAPI) { options.headers['Authorization'] = `Basic ${Buffer.from(`${SACLOUD_API_TOKEN}:${SACLOUD_API_SECRET}`).toString('base64')}`; } const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { if (data) { const parsedData = JSON.parse(data); resolve(parsedData); } else { resolve({}); } } catch (err) { reject(new Error(`Failed to parse response: ${err}`)); } }); }); req.on('error', (error) => { reject(error); }); if (bodyData && (method === 'POST' || method === 'PUT')) { req.write(JSON.stringify(bodyData)); } req.end(); }); } // Helper function to fetch data from AppRun API async function fetchFromAppRunAPI(path: string, method: string = 'GET', bodyData?: any): Promise<any> { return new Promise((resolve, reject) => { validateCredentials(); const options = { hostname: 'secure.sakura.ad.jp', port: 443, path: `/cloud/api/apprun/1.0/apprun/api${path}`, method: method, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': `Basic ${Buffer.from(`${SACLOUD_API_TOKEN}:${SACLOUD_API_SECRET}`).toString('base64')}` } }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { if (data) { const parsedData = JSON.parse(data); resolve(parsedData); } else { resolve({}); } } catch (err) { reject(new Error(`Failed to parse response: ${err}`)); } }); }); req.on('error', (error) => { reject(error); }); if (bodyData && (method === 'POST' || method === 'PUT')) { req.write(JSON.stringify(bodyData)); } req.end(); }); } // Check if API credentials are provided function validateCredentials(): void { if (!SACLOUD_API_TOKEN || !SACLOUD_API_SECRET) { throw new Error('Missing API credentials. Set SACLOUD_API_TOKEN and SACLOUD_API_SECRET environment variables.'); } } // Initialize MCP server const server = new Server( { name: 'sacloud-mcp-server', version: '1.0.0', port: 3001 }, { capabilities: { resources: {}, tools: {}, }, } ); // Register resources server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'sakura:///servers', name: 'Sakura Cloud Servers', description: 'List of all servers in Sakura Cloud' }, { uri: 'sakura:///switches', name: 'Sakura Cloud Switches', description: 'List of all switches in Sakura Cloud' }, { uri: 'sakura:///appliances', name: 'Sakura Cloud Appliances', description: 'List of all appliances in Sakura Cloud' }, { uri: 'sakura:///disks', name: 'Sakura Cloud Disks', description: 'List of all disks in Sakura Cloud' }, { uri: 'sakura:///archives', name: 'Sakura Cloud Archives', description: 'List of all archives in Sakura Cloud' }, { uri: 'sakura:///cdrom', name: 'Sakura Cloud ISO Images', description: 'List of all ISO images (CD-ROMs) in Sakura Cloud' }, { uri: 'sakura:///bridge', name: 'Sakura Cloud Bridges', description: 'List of all bridges in Sakura Cloud' }, { uri: 'sakura:///internet', name: 'Sakura Cloud Routers', description: 'List of all routers in Sakura Cloud' }, { uri: 'sakura:///interface', name: 'Sakura Cloud Interfaces', description: 'List of all network interfaces in Sakura Cloud' }, { uri: 'sakura:///icon', name: 'Sakura Cloud Icons', description: 'List of all icons in Sakura Cloud' }, { uri: 'sakura:///note', name: 'Sakura Cloud Notes', description: 'List of all startup scripts and notes in Sakura Cloud' }, { uri: 'sakura:///sshkey', name: 'Sakura Cloud SSH Keys', description: 'List of all SSH keys in Sakura Cloud' }, { uri: 'sakura:///region', name: 'Sakura Cloud Regions', description: 'List of all regions in Sakura Cloud' }, { uri: 'sakura:///zone', name: 'Sakura Cloud Zones', description: 'List of all zones in Sakura Cloud' }, { uri: 'sakura:///product', name: 'Sakura Cloud Products', description: 'List of all available products in Sakura Cloud' }, { uri: 'sakura:///commonserviceitem', name: 'Sakura Cloud Common Service Items', description: 'List of all common service items (DNS, Simple Monitor, etc.) in Sakura Cloud' }, { uri: 'sakura:///license', name: 'Sakura Cloud Licenses', description: 'List of all licenses in Sakura Cloud' }, { uri: 'sakura:///auth-status', name: 'Sakura Cloud Authentication Status', description: 'Current authentication status and permissions' }, { uri: 'sakura:///bill', name: 'Sakura Cloud Billing Information', description: 'Monthly billing information' }, { uri: 'sakura:///bill-detail', name: 'Sakura Cloud Billing Details', description: 'Detailed breakdown of billing information' }, { uri: 'sakura:///coupon', name: 'Sakura Cloud Coupons', description: 'List of all available coupons' }, { uri: 'sakura:///privatehost', name: 'Sakura Cloud Private Hosts', description: 'List of all private hosts in Sakura Cloud' }, { uri: 'sakura:///public-price', name: 'Sakura Cloud Public Price List', description: 'Public pricing information for Sakura Cloud services' }, { uri: 'sakura:///apprun', name: 'Sakura Cloud AppRun', description: 'List of all AppRun applications in Sakura Cloud' } ] }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; // Parse the URI to extract zone information if present // Format: sakura:///resource?zone=zoneName let zone = DEFAULT_ZONE; const uriParts = uri.split('?'); const resourcePath = uriParts[0]; if (uriParts.length > 1) { const queryParams = new URLSearchParams(uriParts[1]); if (queryParams.has('zone')) { zone = queryParams.get('zone') || DEFAULT_ZONE; } } if (resourcePath === 'sakura:///servers') { try { validateCredentials(); const serversData = await fetchFromSakuraCloud('/server', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(serversData, null, 2) } ] }; } catch (error) { console.error('Error fetching servers:', error); throw error; } } else if (resourcePath === 'sakura:///switches') { try { validateCredentials(); const switchesData = await fetchFromSakuraCloud('/switch', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(switchesData, null, 2) } ] }; } catch (error) { console.error('Error fetching switches:', error); throw error; } } else if (resourcePath === 'sakura:///appliances') { try { validateCredentials(); const appliancesData = await fetchFromSakuraCloud('/appliance', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(appliancesData, null, 2) } ] }; } catch (error) { console.error('Error fetching appliances:', error); throw error; } } else if (resourcePath === 'sakura:///disks') { try { validateCredentials(); const disksData = await fetchFromSakuraCloud('/disk', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(disksData, null, 2) } ] }; } catch (error) { console.error('Error fetching disks:', error); throw error; } } else if (resourcePath === 'sakura:///archives') { try { validateCredentials(); const archivesData = await fetchFromSakuraCloud('/archive', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(archivesData, null, 2) } ] }; } catch (error) { console.error('Error fetching archives:', error); throw error; } } else if (resourcePath === 'sakura:///cdrom') { try { validateCredentials(); const cdromData = await fetchFromSakuraCloud('/cdrom', false, zone); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(cdromData, null, 2) } ] }; } catch (error) { console.error('Error fetching ISO images:', error); throw error; } } else if (uri === 'sakura:///bridge') { try { validateCredentials(); const bridgeData = await fetchFromSakuraCloud('/bridge'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(bridgeData, null, 2) } ] }; } catch (error) { console.error('Error fetching bridges:', error); throw error; } } else if (uri === 'sakura:///internet') { try { validateCredentials(); const routerData = await fetchFromSakuraCloud('/internet'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(routerData, null, 2) } ] }; } catch (error) { console.error('Error fetching routers:', error); throw error; } } else if (uri === 'sakura:///interface') { try { validateCredentials(); const interfaceData = await fetchFromSakuraCloud('/interface'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(interfaceData, null, 2) } ] }; } catch (error) { console.error('Error fetching interfaces:', error); throw error; } } else if (uri === 'sakura:///icon') { try { validateCredentials(); const iconData = await fetchFromSakuraCloud('/icon'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(iconData, null, 2) } ] }; } catch (error) { console.error('Error fetching icons:', error); throw error; } } else if (uri === 'sakura:///note') { try { validateCredentials(); const noteData = await fetchFromSakuraCloud('/note'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(noteData, null, 2) } ] }; } catch (error) { console.error('Error fetching notes:', error); throw error; } } else if (uri === 'sakura:///sshkey') { try { validateCredentials(); const sshkeyData = await fetchFromSakuraCloud('/sshkey'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(sshkeyData, null, 2) } ] }; } catch (error) { console.error('Error fetching SSH keys:', error); throw error; } } else if (uri === 'sakura:///region') { try { validateCredentials(); const regionData = await fetchFromSakuraCloud('/region'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(regionData, null, 2) } ] }; } catch (error) { console.error('Error fetching regions:', error); throw error; } } else if (uri === 'sakura:///zone') { try { validateCredentials(); const zoneData = await fetchFromSakuraCloud('/zone'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(zoneData, null, 2) } ] }; } catch (error) { console.error('Error fetching zones:', error); throw error; } } else if (uri === 'sakura:///product') { try { validateCredentials(); // Fetch multiple product types and combine them const serverPlans = await fetchFromSakuraCloud('/product/server'); const diskPlans = await fetchFromSakuraCloud('/product/disk'); const internetPlans = await fetchFromSakuraCloud('/product/internet'); const licensePlans = await fetchFromSakuraCloud('/product/license'); const combinedProducts = { server: serverPlans, disk: diskPlans, internet: internetPlans, license: licensePlans }; return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(combinedProducts, null, 2) } ] }; } catch (error) { console.error('Error fetching products:', error); throw error; } } else if (uri === 'sakura:///commonserviceitem') { try { validateCredentials(); const commonServiceItemData = await fetchFromSakuraCloud('/commonserviceitem'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(commonServiceItemData, null, 2) } ] }; } catch (error) { console.error('Error fetching common service items:', error); throw error; } } else if (uri === 'sakura:///license') { try { validateCredentials(); const licenseData = await fetchFromSakuraCloud('/license'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(licenseData, null, 2) } ] }; } catch (error) { console.error('Error fetching licenses:', error); throw error; } } else if (uri === 'sakura:///auth-status') { try { validateCredentials(); const authStatusData = await fetchFromSakuraCloud('/auth-status'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(authStatusData, null, 2) } ] }; } catch (error) { console.error('Error fetching authentication status:', error); throw error; } } else if (uri === 'sakura:///bill') { try { validateCredentials(); const billData = await fetchFromSakuraCloud('/bill'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(billData, null, 2) } ] }; } catch (error) { console.error('Error fetching billing information:', error); throw error; } } else if (uri === 'sakura:///bill-detail') { try { validateCredentials(); const billDetailData = await fetchFromSakuraCloud('/bill/detail'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(billDetailData, null, 2) } ] }; } catch (error) { console.error('Error fetching billing details:', error); throw error; } } else if (uri === 'sakura:///coupon') { try { validateCredentials(); const couponData = await fetchFromSakuraCloud('/coupon'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(couponData, null, 2) } ] }; } catch (error) { console.error('Error fetching coupons:', error); throw error; } } else if (uri === 'sakura:///privatehost') { try { validateCredentials(); const privateHostData = await fetchFromSakuraCloud('/privatehost'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(privateHostData, null, 2) } ] }; } catch (error) { console.error('Error fetching private hosts:', error); throw error; } } else if (uri === 'sakura:///public-price') { try { // No authentication needed for public price API const priceData = await fetchFromSakuraCloud('/public/price', false); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(priceData, null, 2) } ] }; } catch (error) { console.error('Error fetching public price data:', error); throw error; } } else if (resourcePath === 'sakura:///apprun') { try { validateCredentials(); const appRunData = await fetchFromAppRunAPI('/applications'); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(appRunData, null, 2) } ] }; } catch (error) { console.error('Error fetching AppRun data:', error); throw error; } } throw new Error(`Resource not found: ${resourcePath}`); }); // Register tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_server_info', description: 'Get detailed information about a specific server', inputSchema: { type: 'object', properties: { serverId: { type: 'string', description: 'The ID of the server to retrieve' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['serverId'] } }, { name: 'get_server_list', description: 'Get list of servers', inputSchema: { type: 'object', properties: { zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, } }, { name: 'get_switch_list', description: 'Get list of switches', inputSchema: { type: 'object', properties: { zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, } }, { name: 'get_switch_info', description: 'Get detailed information about a specific switch', inputSchema: { type: 'object', properties: { switchId: { type: 'string', description: 'The ID of the switch to retrieve' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['switchId'] } }, { name: 'get_appliance_list', description: 'Get list of appliances', inputSchema: { type: 'object', properties: { zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, } }, { name: 'get_appliance_info', description: 'Get detailed information about a specific appliance', inputSchema: { type: 'object', properties: { applianceId: { type: 'string', description: 'The ID of the appliance to retrieve' } }, required: ['applianceId'] } }, { name: 'get_disk_list', description: 'Get list of disks', inputSchema: { type: 'object', properties: { zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, } }, { name: 'get_disk_info', description: 'Get detailed information about a specific disk', inputSchema: { type: 'object', properties: { diskId: { type: 'string', description: 'The ID of the disk to retrieve' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['diskId'] } }, { name: 'get_archive_list', description: 'Get list of archives', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_archive_info', description: 'Get detailed information about a specific archive', inputSchema: { type: 'object', properties: { archiveId: { type: 'string', description: 'The ID of the archive to retrieve' } }, required: ['archiveId'] } }, { name: 'get_cdrom_list', description: 'Get list of ISO images', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_cdrom_info', description: 'Get detailed information about a specific ISO image', inputSchema: { type: 'object', properties: { cdromId: { type: 'string', description: 'The ID of the ISO image to retrieve' } }, required: ['cdromId'] } }, { name: 'get_bridge_list', description: 'Get list of bridges', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_bridge_info', description: 'Get detailed information about a specific bridge', inputSchema: { type: 'object', properties: { bridgeId: { type: 'string', description: 'The ID of the bridge to retrieve' } }, required: ['bridgeId'] } }, { name: 'get_router_list', description: 'Get list of routers', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_router_info', description: 'Get detailed information about a specific router', inputSchema: { type: 'object', properties: { routerId: { type: 'string', description: 'The ID of the router to retrieve' } }, required: ['routerId'] } }, { name: 'get_interface_list', description: 'Get list of network interfaces', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_interface_info', description: 'Get detailed information about a specific network interface', inputSchema: { type: 'object', properties: { interfaceId: { type: 'string', description: 'The ID of the interface to retrieve' } }, required: ['interfaceId'] } }, { name: 'get_icon_list', description: 'Get list of icons', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_icon_info', description: 'Get detailed information about a specific icon', inputSchema: { type: 'object', properties: { iconId: { type: 'string', description: 'The ID of the icon to retrieve' } }, required: ['iconId'] } }, { name: 'get_note_list', description: 'Get list of notes and startup scripts', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_note_info', description: 'Get detailed information about a specific note or startup script', inputSchema: { type: 'object', properties: { noteId: { type: 'string', description: 'The ID of the note to retrieve' } }, required: ['noteId'] } }, { name: 'get_sshkey_list', description: 'Get list of SSH keys', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_sshkey_info', description: 'Get detailed information about a specific SSH key', inputSchema: { type: 'object', properties: { sshkeyId: { type: 'string', description: 'The ID of the SSH key to retrieve' } }, required: ['sshkeyId'] } }, { name: 'get_region_list', description: 'Get list of regions', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_region_info', description: 'Get detailed information about a specific region', inputSchema: { type: 'object', properties: { regionId: { type: 'string', description: 'The ID of the region to retrieve' } }, required: ['regionId'] } }, { name: 'get_zone_list', description: 'Get list of zones', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_zone_info', description: 'Get detailed information about a specific zone', inputSchema: { type: 'object', properties: { zoneId: { type: 'string', description: 'The ID of the zone to retrieve' } }, required: ['zoneId'] } }, { name: 'get_product_info', description: 'Get detailed information about specific product offerings', inputSchema: { type: 'object', properties: { productType: { type: 'string', description: 'The type of product to retrieve (server, disk, internet, license)', enum: ['server', 'disk', 'internet', 'license'] } }, required: ['productType'] } }, { name: 'get_commonserviceitem_list', description: 'Get list of common service items (DNS, Simple Monitor, etc.)', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_commonserviceitem_info', description: 'Get detailed information about a specific common service item', inputSchema: { type: 'object', properties: { itemId: { type: 'string', description: 'The ID of the common service item to retrieve' } }, required: ['itemId'] } }, { name: 'get_license_list', description: 'Get list of licenses', inputSchema: { type: 'object', properties: { }, } }, { name: 'get_license_info', description: 'Get detailed information about a specific license', inputSchema: { type: 'object', properties: { licenseId: { type: 'string', description: 'The ID of the license to retrieve' } }, required: ['licenseId'] } }, { name: 'get_bill_info', description: 'Get billing information for a specific month', inputSchema: { type: 'object', properties: { year: { type: 'string', description: 'The year (YYYY) of the billing period' }, month: { type: 'string', description: 'The month (MM) of the billing period' } }, required: ['year', 'month'] } }, { name: 'get_bill_detail', description: 'Get detailed billing information for a specific month', inputSchema: { type: 'object', properties: { year: { type: 'string', description: 'The year (YYYY) of the billing period' }, month: { type: 'string', description: 'The month (MM) of the billing period' } }, required: ['year', 'month'] } }, { name: 'get_coupon_info', description: 'Get information about a specific coupon', inputSchema: { type: 'object', properties: { couponId: { type: 'string', description: 'The ID of the coupon to retrieve' } }, required: ['couponId'] } }, { name: 'get_privatehost_info', description: 'Get detailed information about a specific private host', inputSchema: { type: 'object', properties: { privateHostId: { type: 'string', description: 'The ID of the private host to retrieve' } }, required: ['privateHostId'] } }, { name: 'get_public_price', description: 'Get public pricing information for Sakura Cloud services', inputSchema: { type: 'object', properties: {} } }, { name: 'get_apprun_list', description: 'Get list of all AppRun applications', inputSchema: { type: 'object', properties: { zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } } } }, { name: 'get_apprun_info', description: 'Get detailed information about a specific AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to retrieve' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } }, { name: 'create_apprun', description: 'Create a new AppRun application', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the AppRun application' }, description: { type: 'string', description: 'Description of the AppRun application' }, dockerImage: { type: 'string', description: 'Docker image to use for the AppRun application' }, planId: { type: 'string', description: 'Plan ID for the AppRun application' }, environment: { type: 'array', description: 'Environment variables for the AppRun application', items: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } } } }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['name', 'dockerImage', 'planId'] } }, { name: 'delete_apprun', description: 'Delete an AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to delete' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } }, { name: 'start_apprun', description: 'Start an AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to start' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } }, { name: 'stop_apprun', description: 'Stop an AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to stop' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } }, { name: 'update_apprun', description: 'Update an existing AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to update' }, name: { type: 'string', description: 'New name of the AppRun application' }, description: { type: 'string', description: 'New description of the AppRun application' }, dockerImage: { type: 'string', description: 'New Docker image to use for the AppRun application' }, planId: { type: 'string', description: 'New plan ID for the AppRun application' }, environment: { type: 'array', description: 'New environment variables for the AppRun application', items: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } } } }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } }, { name: 'get_apprun_logs', description: 'Get logs from an AppRun application', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The ID of the AppRun application to get logs from' }, offset: { type: 'number', description: 'Offset to start fetching logs from (default: 0)' }, limit: { type: 'number', description: 'Maximum number of log entries to fetch (default: 100)' }, zone: { type: 'string', description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.' } }, required: ['appId'] } } ] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === 'get_server_info') { try { validateCredentials(); const serverId = request.params.arguments?.serverId as string; if (!serverId) { throw new Error('Server ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const serverInfo = await fetchFromSakuraCloud(`/server/${serverId}`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(serverInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_server_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const serverList = await fetchFromSakuraCloud(`/server`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(serverList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_switch_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const switchList = await fetchFromSakuraCloud(`/switch`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(switchList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_switch_info') { try { validateCredentials(); const switchId = request.params.arguments?.switchId as string; if (!switchId) { throw new Error('Switch ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const switchInfo = await fetchFromSakuraCloud(`/switch/${switchId}`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(switchInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_appliance_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const applianceList = await fetchFromSakuraCloud(`/appliance`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(applianceList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_appliance_info') { try { validateCredentials(); const applianceId = request.params.arguments?.applianceId as string; if (!applianceId) { throw new Error('Appliance ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const applianceInfo = await fetchFromSakuraCloud(`/appliance/${applianceId}`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(applianceInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_disk_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const diskList = await fetchFromSakuraCloud(`/disk`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(diskList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_disk_info') { try { validateCredentials(); const diskId = request.params.arguments?.diskId as string; if (!diskId) { throw new Error('Disk ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const diskInfo = await fetchFromSakuraCloud(`/disk/${diskId}`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(diskInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_archive_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const archiveList = await fetchFromSakuraCloud(`/archive`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(archiveList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_archive_info') { try { validateCredentials(); const archiveId = request.params.arguments?.archiveId as string; if (!archiveId) { throw new Error('Archive ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const archiveInfo = await fetchFromSakuraCloud(`/archive/${archiveId}`, false, zone); return { content: [ { type: 'text', text: JSON.stringify(archiveInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_cdrom_list') { try { validateCredentials(); const cdromList = await fetchFromSakuraCloud(`/cdrom`); return { content: [ { type: 'text', text: JSON.stringify(cdromList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_cdrom_info') { try { validateCredentials(); const cdromId = request.params.arguments?.cdromId as string; if (!cdromId) { throw new Error('ISO Image ID is required'); } const cdromInfo = await fetchFromSakuraCloud(`/cdrom/${cdromId}`); return { content: [ { type: 'text', text: JSON.stringify(cdromInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_bridge_list') { try { validateCredentials(); const bridgeList = await fetchFromSakuraCloud(`/bridge`); return { content: [ { type: 'text', text: JSON.stringify(bridgeList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_bridge_info') { try { validateCredentials(); const bridgeId = request.params.arguments?.bridgeId as string; if (!bridgeId) { throw new Error('Bridge ID is required'); } const bridgeInfo = await fetchFromSakuraCloud(`/bridge/${bridgeId}`); return { content: [ { type: 'text', text: JSON.stringify(bridgeInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_router_list') { try { validateCredentials(); const routerList = await fetchFromSakuraCloud(`/internet`); return { content: [ { type: 'text', text: JSON.stringify(routerList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_router_info') { try { validateCredentials(); const routerId = request.params.arguments?.routerId as string; if (!routerId) { throw new Error('Router ID is required'); } const routerInfo = await fetchFromSakuraCloud(`/internet/${routerId}`); return { content: [ { type: 'text', text: JSON.stringify(routerInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_interface_list') { try { validateCredentials(); const interfaceList = await fetchFromSakuraCloud(`/interface`); return { content: [ { type: 'text', text: JSON.stringify(interfaceList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_interface_info') { try { validateCredentials(); const interfaceId = request.params.arguments?.interfaceId as string; if (!interfaceId) { throw new Error('Interface ID is required'); } const interfaceInfo = await fetchFromSakuraCloud(`/interface/${interfaceId}`); return { content: [ { type: 'text', text: JSON.stringify(interfaceInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_icon_list') { try { validateCredentials(); const iconList = await fetchFromSakuraCloud(`/icon`); return { content: [ { type: 'text', text: JSON.stringify(iconList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_icon_info') { try { validateCredentials(); const iconId = request.params.arguments?.iconId as string; if (!iconId) { throw new Error('Icon ID is required'); } const iconInfo = await fetchFromSakuraCloud(`/icon/${iconId}`); return { content: [ { type: 'text', text: JSON.stringify(iconInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_note_list') { try { validateCredentials(); const noteList = await fetchFromSakuraCloud(`/note`); return { content: [ { type: 'text', text: JSON.stringify(noteList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_note_info') { try { validateCredentials(); const noteId = request.params.arguments?.noteId as string; if (!noteId) { throw new Error('Note ID is required'); } const noteInfo = await fetchFromSakuraCloud(`/note/${noteId}`); return { content: [ { type: 'text', text: JSON.stringify(noteInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_sshkey_list') { try { validateCredentials(); const sshkeyList = await fetchFromSakuraCloud(`/sshkey`); return { content: [ { type: 'text', text: JSON.stringify(sshkeyList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_sshkey_info') { try { validateCredentials(); const sshkeyId = request.params.arguments?.sshkeyId as string; if (!sshkeyId) { throw new Error('SSH Key ID is required'); } const sshkeyInfo = await fetchFromSakuraCloud(`/sshkey/${sshkeyId}`); return { content: [ { type: 'text', text: JSON.stringify(sshkeyInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_region_list') { try { validateCredentials(); const regionList = await fetchFromSakuraCloud(`/region`); return { content: [ { type: 'text', text: JSON.stringify(regionList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_region_info') { try { validateCredentials(); const regionId = request.params.arguments?.regionId as string; if (!regionId) { throw new Error('Region ID is required'); } const regionInfo = await fetchFromSakuraCloud(`/region/${regionId}`); return { content: [ { type: 'text', text: JSON.stringify(regionInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_zone_list') { try { validateCredentials(); const zoneList = await fetchFromSakuraCloud(`/zone`); return { content: [ { type: 'text', text: JSON.stringify(zoneList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_zone_info') { try { validateCredentials(); const zoneId = request.params.arguments?.zoneId as string; if (!zoneId) { throw new Error('Zone ID is required'); } const zoneInfo = await fetchFromSakuraCloud(`/zone/${zoneId}`); return { content: [ { type: 'text', text: JSON.stringify(zoneInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_product_info') { try { validateCredentials(); const productType = request.params.arguments?.productType as string; if (!productType) { throw new Error('Product type is required'); } // Validate product type if (!['server', 'disk', 'internet', 'license'].includes(productType)) { throw new Error('Invalid product type. Must be one of: server, disk, internet, license'); } const productInfo = await fetchFromSakuraCloud(`/product/${productType}`); return { content: [ { type: 'text', text: JSON.stringify(productInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_commonserviceitem_list') { try { validateCredentials(); const commonServiceItemList = await fetchFromSakuraCloud(`/commonserviceitem`); return { content: [ { type: 'text', text: JSON.stringify(commonServiceItemList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_commonserviceitem_info') { try { validateCredentials(); const itemId = request.params.arguments?.itemId as string; if (!itemId) { throw new Error('Common Service Item ID is required'); } const itemInfo = await fetchFromSakuraCloud(`/commonserviceitem/${itemId}`); return { content: [ { type: 'text', text: JSON.stringify(itemInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_license_list') { try { validateCredentials(); const licenseList = await fetchFromSakuraCloud(`/license`); return { content: [ { type: 'text', text: JSON.stringify(licenseList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_license_info') { try { validateCredentials(); const licenseId = request.params.arguments?.licenseId as string; if (!licenseId) { throw new Error('License ID is required'); } const licenseInfo = await fetchFromSakuraCloud(`/license/${licenseId}`); return { content: [ { type: 'text', text: JSON.stringify(licenseInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_bill_info') { try { validateCredentials(); const year = request.params.arguments?.year as string; const month = request.params.arguments?.month as string; if (!year || !month) { throw new Error('Year and month are required for billing information'); } const billInfo = await fetchFromSakuraCloud(`/bill/${year}${month}`); return { content: [ { type: 'text', text: JSON.stringify(billInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_bill_detail') { try { validateCredentials(); const year = request.params.arguments?.year as string; const month = request.params.arguments?.month as string; if (!year || !month) { throw new Error('Year and month are required for billing details'); } const billDetailInfo = await fetchFromSakuraCloud(`/bill/${year}${month}/detail`); return { content: [ { type: 'text', text: JSON.stringify(billDetailInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_coupon_info') { try { validateCredentials(); const couponId = request.params.arguments?.couponId as string; if (!couponId) { throw new Error('Coupon ID is required'); } const couponInfo = await fetchFromSakuraCloud(`/coupon/${couponId}`); return { content: [ { type: 'text', text: JSON.stringify(couponInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_privatehost_info') { try { validateCredentials(); const privateHostId = request.params.arguments?.privateHostId as string; if (!privateHostId) { throw new Error('Private Host ID is required'); } const privateHostInfo = await fetchFromSakuraCloud(`/privatehost/${privateHostId}`); return { content: [ { type: 'text', text: JSON.stringify(privateHostInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_public_price') { try { // No authentication needed for public price API const priceData = await fetchFromSakuraCloud('/public/price', true); return { content: [ { type: 'text', text: JSON.stringify(priceData, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_apprun_list') { try { validateCredentials(); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const appRunList = await fetchFromAppRunAPI('/applications'); return { content: [ { type: 'text', text: JSON.stringify(appRunList, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_apprun_info') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const appRunInfo = await fetchFromAppRunAPI(`/applications/${appId}`); return { content: [ { type: 'text', text: JSON.stringify(appRunInfo, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'create_apprun') { try { validateCredentials(); const name = request.params.arguments?.name as string; const description = request.params.arguments?.description as string || ''; const dockerImage = request.params.arguments?.dockerImage as string; const planId = request.params.arguments?.planId as string; const environment = request.params.arguments?.environment as Array<{key: string, value: string}> || []; if (!name || !dockerImage || !planId) { throw new Error('Name, Docker image, and plan ID are required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const createBody = { name: name, description: description, planId: planId, image: dockerImage, environment: environment.map(env => ({ key: env.key, value: env.value })), }; const createResult = await fetchFromAppRunAPI('/applications', 'POST', createBody); return { content: [ { type: 'text', text: JSON.stringify(createResult, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'delete_apprun') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const deleteResult = await fetchFromAppRunAPI(`/applications/${appId}`, 'DELETE'); return { content: [ { type: 'text', text: JSON.stringify(deleteResult, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'start_apprun') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const startResult = await fetchFromAppRunAPI(`/applications/${appId}/start`, 'POST'); return { content: [ { type: 'text', text: JSON.stringify(startResult, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'stop_apprun') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const stopResult = await fetchFromAppRunAPI(`/applications/${appId}/stop`, 'POST'); return { content: [ { type: 'text', text: JSON.stringify(stopResult, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'update_apprun') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } // Get current application data first const currentApp = await fetchFromAppRunAPI(`/applications/${appId}`); // Extract current values const currentName = currentApp.name || ''; const currentDescription = currentApp.description || ''; const currentPlanId = currentApp.planId || ''; const currentDockerImage = currentApp.image || ''; const currentEnvironment = currentApp.environment || []; // Get update values from request or use current values const name = request.params.arguments?.name as string || currentName; const description = request.params.arguments?.description as string || currentDescription; const planId = request.params.arguments?.planId as string || currentPlanId; const dockerImage = request.params.arguments?.dockerImage as string || currentDockerImage; const environment = request.params.arguments?.environment as Array<{key: string, value: string}> || currentEnvironment.map((env: {Key: string, Value: string}) => ({ key: env.Key, value: env.Value })); const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const updateBody = { name: name, description: description, planId: planId, image: dockerImage, environment: environment.map(env => ({ key: env.key, value: env.value })), }; const updateResult = await fetchFromAppRunAPI(`/applications/${appId}`, 'PUT', updateBody); return { content: [ { type: 'text', text: JSON.stringify(updateResult, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } else if (request.params.name === 'get_apprun_logs') { try { validateCredentials(); const appId = request.params.arguments?.appId as string; if (!appId) { throw new Error('AppRun application ID is required'); } const offset = request.params.arguments?.offset as number || 0; const limit = request.params.arguments?.limit as number || 100; const zone = request.params.arguments?.zone as string || DEFAULT_ZONE; const logsQuery = `/applications/${appId}/logs?offset=${offset}&limit=${limit}`; const logs = await fetchFromAppRunAPI(logsQuery); return { content: [ { type: 'text', text: JSON.stringify(logs, null, 2) } ] }; } catch (error) { console.error('Error calling tool:', error); throw error; } } throw new Error(`Tool not found: ${request.params.name}`); }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Sacloud MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); ```