#
tokens: 26438/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── server.ts
│   └── types.ts
├── tsconfig.json
└── tsconfig.tsbuildinfo
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | dist/
3 | .env
4 | .DS_Store
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Sakura Cloud MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server implementation for interacting with Sakura Cloud's API.
  4 | 
  5 | ## What is MCP?
  6 | 
  7 | 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.
  8 | 
  9 | ## Overview
 10 | 
 11 | 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:
 12 | 
 13 | - Access Sakura Cloud resources like servers, disks, networks, and more
 14 | - Use tools to list resources and retrieve detailed information about specific resources
 15 | - Query public pricing information without authentication requirements
 16 | - Manage AppRun containerized applications
 17 | 
 18 | ## Prerequisites
 19 | 
 20 | - Node.js (v16 or higher)
 21 | - Sakura Cloud API credentials (token and secret)
 22 | - Claude Desktop app for using with Claude (MCP is currently only supported in the desktop app)
 23 | 
 24 | ## Installation
 25 | 
 26 | ```bash
 27 | # Clone the repository
 28 | git clone https://github.com/hidenorigoto/sacloud-mcp.git
 29 | cd sacloud-mcp
 30 | 
 31 | # Install dependencies
 32 | npm install
 33 | 
 34 | # Build the project
 35 | npm run build
 36 | ```
 37 | 
 38 | ## Configuration
 39 | 
 40 | Set the following environment variables:
 41 | 
 42 | - `SACLOUD_API_TOKEN`: Your Sakura Cloud API token
 43 | - `SACLOUD_API_SECRET`: Your Sakura Cloud API secret
 44 | 
 45 | ## Usage
 46 | 
 47 | ### Available Resources
 48 | 
 49 | | Resource URI | Description |
 50 | |--------------|-------------|
 51 | | `sakura:///servers` | Lists all servers in your Sakura Cloud account |
 52 | | `sakura:///switches` | Lists all switches in your Sakura Cloud account |
 53 | | `sakura:///appliances` | Lists all appliances in your Sakura Cloud account |
 54 | | `sakura:///disks` | Lists all disks in your Sakura Cloud account |
 55 | | `sakura:///archives` | Lists all archives in your Sakura Cloud account |
 56 | | `sakura:///cdrom` | Lists all ISO images (CD-ROMs) in your Sakura Cloud account |
 57 | | `sakura:///bridge` | Lists all bridges in your Sakura Cloud account |
 58 | | `sakura:///internet` | Lists all routers in your Sakura Cloud account |
 59 | | `sakura:///interface` | Lists all network interfaces in your Sakura Cloud account |
 60 | | `sakura:///icon` | Lists all icons in your Sakura Cloud account |
 61 | | `sakura:///note` | Lists all startup scripts and notes in your Sakura Cloud account |
 62 | | `sakura:///sshkey` | Lists all SSH keys in your Sakura Cloud account |
 63 | | `sakura:///region` | Lists all regions in your Sakura Cloud account |
 64 | | `sakura:///zone` | Lists all zones in your Sakura Cloud account |
 65 | | `sakura:///product` | Lists all available products in your Sakura Cloud account |
 66 | | `sakura:///commonserviceitem` | Lists all common service items (DNS, Simple Monitor, etc.) in your Sakura Cloud account |
 67 | | `sakura:///license` | Lists all licenses in your Sakura Cloud account |
 68 | | `sakura:///auth-status` | Shows current authentication status and permissions |
 69 | | `sakura:///bill` | Shows monthly billing information |
 70 | | `sakura:///bill-detail` | Shows detailed breakdown of billing information |
 71 | | `sakura:///coupon` | Lists all available coupons |
 72 | | `sakura:///privatehost` | Lists all private hosts in your Sakura Cloud account |
 73 | | `sakura:///public-price` | Shows public pricing information for Sakura Cloud services (no authentication required) |
 74 | | `sakura:///apprun` | Lists all AppRun applications in your Sakura Cloud account |
 75 | 
 76 | ### Available Tools
 77 | 
 78 | | Tool Name | Description | Required Parameters |
 79 | |-----------|-------------|---------------------|
 80 | | `get_server_list` | Retrieves list of all servers | None |
 81 | | `get_server_info` | Retrieves detailed information about a specific server | `serverId` |
 82 | | `get_switch_list` | Retrieves list of all switches | None |
 83 | | `get_switch_info` | Retrieves detailed information about a specific switch | `switchId` |
 84 | | `get_appliance_list` | Retrieves list of all appliances | None |
 85 | | `get_appliance_info` | Retrieves detailed information about a specific appliance | `applianceId` |
 86 | | `get_disk_list` | Retrieves list of all disks | None |
 87 | | `get_disk_info` | Retrieves detailed information about a specific disk | `diskId` |
 88 | | `get_archive_list` | Retrieves list of all archives | None |
 89 | | `get_archive_info` | Retrieves detailed information about a specific archive | `archiveId` |
 90 | | `get_cdrom_list` | Retrieves list of all ISO images | None |
 91 | | `get_cdrom_info` | Retrieves detailed information about a specific ISO image | `cdromId` |
 92 | | `get_bridge_list` | Retrieves list of all bridges | None |
 93 | | `get_bridge_info` | Retrieves detailed information about a specific bridge | `bridgeId` |
 94 | | `get_router_list` | Retrieves list of all routers | None |
 95 | | `get_router_info` | Retrieves detailed information about a specific router | `routerId` |
 96 | | `get_interface_list` | Retrieves list of all network interfaces | None |
 97 | | `get_interface_info` | Retrieves detailed information about a specific network interface | `interfaceId` |
 98 | | `get_icon_list` | Retrieves list of all icons | None |
 99 | | `get_icon_info` | Retrieves detailed information about a specific icon | `iconId` |
100 | | `get_note_list` | Retrieves list of all notes and startup scripts | None |
101 | | `get_note_info` | Retrieves detailed information about a specific note or startup script | `noteId` |
102 | | `get_sshkey_list` | Retrieves list of all SSH keys | None |
103 | | `get_sshkey_info` | Retrieves detailed information about a specific SSH key | `sshkeyId` |
104 | | `get_region_list` | Retrieves list of all regions | None |
105 | | `get_region_info` | Retrieves detailed information about a specific region | `regionId` |
106 | | `get_zone_list` | Retrieves list of all zones | None |
107 | | `get_zone_info` | Retrieves detailed information about a specific zone | `zoneId` |
108 | | `get_product_info` | Retrieves detailed information about specific product offerings | `productType` |
109 | | `get_commonserviceitem_list` | Retrieves list of all common service items | None |
110 | | `get_commonserviceitem_info` | Retrieves detailed information about a specific common service item | `itemId` |
111 | | `get_license_list` | Retrieves list of all licenses | None |
112 | | `get_license_info` | Retrieves detailed information about a specific license | `licenseId` |
113 | | `get_bill_info` | Retrieves billing information for a specific month | `year`, `month` |
114 | | `get_bill_detail` | Retrieves detailed billing information for a specific month | `year`, `month` |
115 | | `get_coupon_info` | Retrieves information about a specific coupon | `couponId` |
116 | | `get_privatehost_info` | Retrieves detailed information about a specific private host | `privateHostId` |
117 | | `get_public_price` | Retrieves public pricing information for Sakura Cloud services | None |
118 | | `get_apprun_list` | Retrieves list of all AppRun applications | None |
119 | | `get_apprun_info` | Retrieves detailed information about a specific AppRun application | `appId` |
120 | | `create_apprun` | Creates a new AppRun application | `name`, `dockerImage`, `planId` |
121 | | `delete_apprun` | Deletes an AppRun application | `appId` |
122 | | `start_apprun` | Starts an AppRun application | `appId` |
123 | | `stop_apprun` | Stops an AppRun application | `appId` |
124 | | `update_apprun` | Updates an existing AppRun application | `appId` |
125 | | `get_apprun_logs` | Gets logs from an AppRun application | `appId` |
126 | 
127 | ## AppRun Integration
128 | 
129 | 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:
130 | 
131 | - View all your AppRun applications
132 | - Create new applications with custom Docker images
133 | - Update existing applications (change image, configuration, etc.)
134 | - Start and stop applications
135 | - View application logs
136 | - Delete applications when no longer needed
137 | 
138 | When creating or updating an AppRun application, you can specify:
139 | - Application name and description
140 | - Docker image to use
141 | - Plan ID (determines resources allocated)
142 | - Environment variables as key-value pairs
143 | 
144 | ## Zone Support
145 | 
146 | 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:
147 | - `is1a` (Ishikari)
148 | - `tk1a` (Tokyo)
149 | - And more...
150 | 
151 | Example URI with zone parameter: `sakura:///servers?zone=is1a`
152 | 
153 | ## Integrating with Claude
154 | 
155 | Claude Desktop app provides MCP support. Follow these steps to integrate this server with Claude:
156 | 
157 | 1. Make sure the server is running locally or on an accessible host.
158 | 
159 | 2. Create a `claude_desktop_config.json` file in the appropriate location for your OS:
160 |    - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
161 |    - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
162 |    - Linux: `~/.config/Claude/claude_desktop_config.json`
163 | 
164 | 3. Add the following configuration to the file:
165 | 
166 | ```json
167 | {
168 |   "sacloud-server": {
169 |     "command": "node",
170 |     "args": ["path/to/mcp/dist/server.js"],
171 |     "env": {
172 |       "SACLOUD_API_TOKEN": "your_token_here",
173 |       "SACLOUD_API_SECRET": "your_secret_here"
174 |     }
175 |   }
176 | }
177 | ```
178 | 
179 | 4. Restart the Claude Desktop app to apply the configuration.
180 | 
181 | 5. In a conversation with Claude, you can now access Sakura Cloud resources and tools.
182 | 
183 | ## Security Considerations
184 | 
185 | - This server handles sensitive API credentials
186 | - Never commit API tokens or secrets to version control
187 | - Use environment variables for all sensitive information
188 | - Implement proper access controls in production
189 | 
190 | ## License
191 | 
192 | ISC
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2016",
 4 |     "module": "commonjs",
 5 |     "rootDir": "./src",
 6 |     "outDir": "./dist",
 7 |     "esModuleInterop": true,
 8 |     "forceConsistentCasingInFileNames": true,
 9 |     "strict": true,
10 |     "skipLibCheck": true
11 |   },
12 |   "include": ["src/**/*"],
13 |   "exclude": ["node_modules"]
14 | }
15 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "sacloud-mcp",
 3 |   "version": "1.0.0",
 4 |   "main": "dist/server.js",
 5 |   "scripts": {
 6 |     "start": "node dist/server.js",
 7 |     "dev": "ts-node src/server.ts",
 8 |     "build": "tsc",
 9 |     "test": "echo \"Error: no test specified\" && exit 1"
10 |   },
11 |   "keywords": [],
12 |   "author": "",
13 |   "license": "ISC",
14 |   "description": "",
15 |   "devDependencies": {
16 |     "@types/node": "^22.13.14",
17 |     "ts-node": "^10.9.2",
18 |     "typescript": "^5.8.2"
19 |   },
20 |   "dependencies": {
21 |     "@modelcontextprotocol/sdk": "^1.8.0"
22 |   }
23 | }
24 | 
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | declare module '@modelcontextprotocol/sdk' {
 2 |   export interface ServerConfig {
 3 |     name: string;
 4 |     version: string;
 5 |   }
 6 | 
 7 |   export interface TransportConfig {
 8 |     transport: {
 9 |       type: 'http';
10 |       port: number;
11 |     };
12 |   }
13 | 
14 |   export class Server {
15 |     constructor(serverConfig: ServerConfig, transportConfig: TransportConfig);
16 |     setRequestHandler<P, R>(schema: { params: P, result: R }, handler: (request: { params: P }) => Promise<R>): void;
17 |     listen(): Promise<void>;
18 |     close(): Promise<void>;
19 |   }
20 | 
21 |   export const ListResourcesRequestSchema: {
22 |     params: null;
23 |     result: {
24 |       resources: Array<{
25 |         uri: string;
26 |         name: string;
27 |         description?: string;
28 |         mimeType?: string;
29 |       }>;
30 |     };
31 |   };
32 | 
33 |   export const ReadResourceRequestSchema: {
34 |     params: {
35 |       uri: string;
36 |     };
37 |     result: {
38 |       contents: Array<{
39 |         uri: string;
40 |         mimeType: string;
41 |         text: string;
42 |       }>;
43 |     };
44 |   };
45 | 
46 |   export const ListToolsRequestSchema: {
47 |     params: null;
48 |     result: {
49 |       tools: Array<{
50 |         name: string;
51 |         description: string;
52 |         inputSchema: {
53 |           type: string;
54 |           properties: Record<string, any>;
55 |           required?: string[];
56 |         };
57 |       }>;
58 |     };
59 |   };
60 | 
61 |   export const CallToolRequestSchema: {
62 |     params: {
63 |       name: string;
64 |       arguments: Record<string, any>;
65 |     };
66 |     result: {
67 |       content: Array<{
68 |         type: string;
69 |         text: string;
70 |       }>;
71 |     };
72 |   };
73 | }
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env node
   2 | 
   3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
   4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
   5 | import {
   6 |   ListResourcesRequestSchema,
   7 |   ReadResourceRequestSchema,
   8 |   ListToolsRequestSchema,
   9 |   CallToolRequestSchema
  10 | } from '@modelcontextprotocol/sdk/types.js';
  11 | import https from 'https';
  12 | 
  13 | const SACLOUD_API_TOKEN = process.env.SACLOUD_API_TOKEN || '';
  14 | const SACLOUD_API_SECRET = process.env.SACLOUD_API_SECRET || '';
  15 | 
  16 | // Default zone to use if not specified
  17 | const DEFAULT_ZONE = 'tk1v';
  18 | 
  19 | // Helper function to make API calls to Sakura Cloud
  20 | async function fetchFromSakuraCloud(path: string, isPublicAPI: boolean = false, zone: string = DEFAULT_ZONE, method: string = 'GET', bodyData?: any): Promise<any> {
  21 |   return new Promise((resolve, reject) => {
  22 |     const basePath = isPublicAPI ? '/cloud/api/cloud/1.1' : `/cloud/zone/${zone}/api/cloud/1.1`;
  23 |     
  24 |     const options = {
  25 |       hostname: 'secure.sakura.ad.jp',
  26 |       port: 443,
  27 |       path: `${basePath}${path}`,
  28 |       method: method,
  29 |       headers: {
  30 |         'Accept': 'application/json',
  31 |         'Authorization': '',
  32 |         'Content-Type': 'application/json'
  33 |       }
  34 |     };
  35 |     
  36 |     // Add authorization for non-public APIs
  37 |     if (!isPublicAPI) {
  38 |       options.headers['Authorization'] = `Basic ${Buffer.from(`${SACLOUD_API_TOKEN}:${SACLOUD_API_SECRET}`).toString('base64')}`;
  39 |     }
  40 | 
  41 |     const req = https.request(options, (res) => {
  42 |       let data = '';
  43 |       
  44 |       res.on('data', (chunk) => {
  45 |         data += chunk;
  46 |       });
  47 |       
  48 |       res.on('end', () => {
  49 |         try {
  50 |           if (data) {
  51 |             const parsedData = JSON.parse(data);
  52 |             resolve(parsedData);
  53 |           } else {
  54 |             resolve({});
  55 |           }
  56 |         } catch (err) {
  57 |           reject(new Error(`Failed to parse response: ${err}`));
  58 |         }
  59 |       });
  60 |     });
  61 |     
  62 |     req.on('error', (error) => {
  63 |       reject(error);
  64 |     });
  65 |     
  66 |     if (bodyData && (method === 'POST' || method === 'PUT')) {
  67 |       req.write(JSON.stringify(bodyData));
  68 |     }
  69 |     
  70 |     req.end();
  71 |   });
  72 | }
  73 | 
  74 | // Helper function to fetch data from AppRun API
  75 | async function fetchFromAppRunAPI(path: string, method: string = 'GET', bodyData?: any): Promise<any> {
  76 |   return new Promise((resolve, reject) => {
  77 |     validateCredentials();
  78 |     
  79 |     const options = {
  80 |       hostname: 'secure.sakura.ad.jp',
  81 |       port: 443,
  82 |       path: `/cloud/api/apprun/1.0/apprun/api${path}`,
  83 |       method: method,
  84 |       headers: {
  85 |         'Accept': 'application/json',
  86 |         'Content-Type': 'application/json',
  87 |         'Authorization': `Basic ${Buffer.from(`${SACLOUD_API_TOKEN}:${SACLOUD_API_SECRET}`).toString('base64')}`
  88 |       }
  89 |     };
  90 | 
  91 |     const req = https.request(options, (res) => {
  92 |       let data = '';
  93 |       
  94 |       res.on('data', (chunk) => {
  95 |         data += chunk;
  96 |       });
  97 |       
  98 |       res.on('end', () => {
  99 |         try {
 100 |           if (data) {
 101 |             const parsedData = JSON.parse(data);
 102 |             resolve(parsedData);
 103 |           } else {
 104 |             resolve({});
 105 |           }
 106 |         } catch (err) {
 107 |           reject(new Error(`Failed to parse response: ${err}`));
 108 |         }
 109 |       });
 110 |     });
 111 |     
 112 |     req.on('error', (error) => {
 113 |       reject(error);
 114 |     });
 115 |     
 116 |     if (bodyData && (method === 'POST' || method === 'PUT')) {
 117 |       req.write(JSON.stringify(bodyData));
 118 |     }
 119 |     
 120 |     req.end();
 121 |   });
 122 | }
 123 | 
 124 | // Check if API credentials are provided
 125 | function validateCredentials(): void {
 126 |   if (!SACLOUD_API_TOKEN || !SACLOUD_API_SECRET) {
 127 |     throw new Error('Missing API credentials. Set SACLOUD_API_TOKEN and SACLOUD_API_SECRET environment variables.');
 128 |   }
 129 | }
 130 | 
 131 | // Initialize MCP server
 132 | const server = new Server(
 133 |   {
 134 |     name: 'sacloud-mcp-server',
 135 |     version: '1.0.0',
 136 |     port: 3001
 137 |   },
 138 |   {
 139 |     capabilities: {
 140 |       resources: {},
 141 |       tools: {},
 142 |     },
 143 |   }
 144 | );
 145 | 
 146 | // Register resources
 147 | server.setRequestHandler(ListResourcesRequestSchema, async () => {
 148 |   return {
 149 |     resources: [
 150 |       {
 151 |         uri: 'sakura:///servers',
 152 |         name: 'Sakura Cloud Servers',
 153 |         description: 'List of all servers in Sakura Cloud'
 154 |       },
 155 |       {
 156 |         uri: 'sakura:///switches',
 157 |         name: 'Sakura Cloud Switches',
 158 |         description: 'List of all switches in Sakura Cloud'
 159 |       },
 160 |       {
 161 |         uri: 'sakura:///appliances',
 162 |         name: 'Sakura Cloud Appliances',
 163 |         description: 'List of all appliances in Sakura Cloud'
 164 |       },
 165 |       {
 166 |         uri: 'sakura:///disks',
 167 |         name: 'Sakura Cloud Disks',
 168 |         description: 'List of all disks in Sakura Cloud'
 169 |       },
 170 |       {
 171 |         uri: 'sakura:///archives',
 172 |         name: 'Sakura Cloud Archives',
 173 |         description: 'List of all archives in Sakura Cloud'
 174 |       },
 175 |       {
 176 |         uri: 'sakura:///cdrom',
 177 |         name: 'Sakura Cloud ISO Images',
 178 |         description: 'List of all ISO images (CD-ROMs) in Sakura Cloud'
 179 |       },
 180 |       {
 181 |         uri: 'sakura:///bridge',
 182 |         name: 'Sakura Cloud Bridges',
 183 |         description: 'List of all bridges in Sakura Cloud'
 184 |       },
 185 |       {
 186 |         uri: 'sakura:///internet',
 187 |         name: 'Sakura Cloud Routers',
 188 |         description: 'List of all routers in Sakura Cloud'
 189 |       },
 190 |       {
 191 |         uri: 'sakura:///interface',
 192 |         name: 'Sakura Cloud Interfaces',
 193 |         description: 'List of all network interfaces in Sakura Cloud'
 194 |       },
 195 |       {
 196 |         uri: 'sakura:///icon',
 197 |         name: 'Sakura Cloud Icons',
 198 |         description: 'List of all icons in Sakura Cloud'
 199 |       },
 200 |       {
 201 |         uri: 'sakura:///note',
 202 |         name: 'Sakura Cloud Notes',
 203 |         description: 'List of all startup scripts and notes in Sakura Cloud'
 204 |       },
 205 |       {
 206 |         uri: 'sakura:///sshkey',
 207 |         name: 'Sakura Cloud SSH Keys',
 208 |         description: 'List of all SSH keys in Sakura Cloud'
 209 |       },
 210 |       {
 211 |         uri: 'sakura:///region',
 212 |         name: 'Sakura Cloud Regions',
 213 |         description: 'List of all regions in Sakura Cloud'
 214 |       },
 215 |       {
 216 |         uri: 'sakura:///zone',
 217 |         name: 'Sakura Cloud Zones',
 218 |         description: 'List of all zones in Sakura Cloud'
 219 |       },
 220 |       {
 221 |         uri: 'sakura:///product',
 222 |         name: 'Sakura Cloud Products',
 223 |         description: 'List of all available products in Sakura Cloud'
 224 |       },
 225 |       {
 226 |         uri: 'sakura:///commonserviceitem',
 227 |         name: 'Sakura Cloud Common Service Items',
 228 |         description: 'List of all common service items (DNS, Simple Monitor, etc.) in Sakura Cloud'
 229 |       },
 230 |       {
 231 |         uri: 'sakura:///license',
 232 |         name: 'Sakura Cloud Licenses',
 233 |         description: 'List of all licenses in Sakura Cloud'
 234 |       },
 235 |       {
 236 |         uri: 'sakura:///auth-status',
 237 |         name: 'Sakura Cloud Authentication Status',
 238 |         description: 'Current authentication status and permissions'
 239 |       },
 240 |       {
 241 |         uri: 'sakura:///bill',
 242 |         name: 'Sakura Cloud Billing Information',
 243 |         description: 'Monthly billing information'
 244 |       },
 245 |       {
 246 |         uri: 'sakura:///bill-detail',
 247 |         name: 'Sakura Cloud Billing Details',
 248 |         description: 'Detailed breakdown of billing information'
 249 |       },
 250 |       {
 251 |         uri: 'sakura:///coupon',
 252 |         name: 'Sakura Cloud Coupons',
 253 |         description: 'List of all available coupons'
 254 |       },
 255 |       {
 256 |         uri: 'sakura:///privatehost',
 257 |         name: 'Sakura Cloud Private Hosts',
 258 |         description: 'List of all private hosts in Sakura Cloud'
 259 |       },
 260 |       {
 261 |         uri: 'sakura:///public-price',
 262 |         name: 'Sakura Cloud Public Price List',
 263 |         description: 'Public pricing information for Sakura Cloud services'
 264 |       },
 265 |       {
 266 |         uri: 'sakura:///apprun',
 267 |         name: 'Sakura Cloud AppRun',
 268 |         description: 'List of all AppRun applications in Sakura Cloud'
 269 |       }
 270 |     ]
 271 |   };
 272 | });
 273 | 
 274 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
 275 |   const uri = request.params.uri;
 276 |   
 277 |   // Parse the URI to extract zone information if present
 278 |   // Format: sakura:///resource?zone=zoneName
 279 |   let zone = DEFAULT_ZONE;
 280 |   const uriParts = uri.split('?');
 281 |   const resourcePath = uriParts[0];
 282 |   
 283 |   if (uriParts.length > 1) {
 284 |     const queryParams = new URLSearchParams(uriParts[1]);
 285 |     if (queryParams.has('zone')) {
 286 |       zone = queryParams.get('zone') || DEFAULT_ZONE;
 287 |     }
 288 |   }
 289 |   
 290 |   if (resourcePath === 'sakura:///servers') {
 291 |     try {
 292 |       validateCredentials();
 293 |       const serversData = await fetchFromSakuraCloud('/server', false, zone);
 294 |       
 295 |       return {
 296 |         contents: [
 297 |           {
 298 |             uri,
 299 |             mimeType: 'application/json',
 300 |             text: JSON.stringify(serversData, null, 2)
 301 |           }
 302 |         ]
 303 |       };
 304 |     } catch (error) {
 305 |       console.error('Error fetching servers:', error);
 306 |       throw error;
 307 |     }
 308 |   } else if (resourcePath === 'sakura:///switches') {
 309 |     try {
 310 |       validateCredentials();
 311 |       const switchesData = await fetchFromSakuraCloud('/switch', false, zone);
 312 |       
 313 |       return {
 314 |         contents: [
 315 |           {
 316 |             uri,
 317 |             mimeType: 'application/json',
 318 |             text: JSON.stringify(switchesData, null, 2)
 319 |           }
 320 |         ]
 321 |       };
 322 |     } catch (error) {
 323 |       console.error('Error fetching switches:', error);
 324 |       throw error;
 325 |     }
 326 |   } else if (resourcePath === 'sakura:///appliances') {
 327 |     try {
 328 |       validateCredentials();
 329 |       const appliancesData = await fetchFromSakuraCloud('/appliance', false, zone);
 330 |       
 331 |       return {
 332 |         contents: [
 333 |           {
 334 |             uri,
 335 |             mimeType: 'application/json',
 336 |             text: JSON.stringify(appliancesData, null, 2)
 337 |           }
 338 |         ]
 339 |       };
 340 |     } catch (error) {
 341 |       console.error('Error fetching appliances:', error);
 342 |       throw error;
 343 |     }
 344 |   } else if (resourcePath === 'sakura:///disks') {
 345 |     try {
 346 |       validateCredentials();
 347 |       const disksData = await fetchFromSakuraCloud('/disk', false, zone);
 348 |       
 349 |       return {
 350 |         contents: [
 351 |           {
 352 |             uri,
 353 |             mimeType: 'application/json',
 354 |             text: JSON.stringify(disksData, null, 2)
 355 |           }
 356 |         ]
 357 |       };
 358 |     } catch (error) {
 359 |       console.error('Error fetching disks:', error);
 360 |       throw error;
 361 |     }
 362 |   } else if (resourcePath === 'sakura:///archives') {
 363 |     try {
 364 |       validateCredentials();
 365 |       const archivesData = await fetchFromSakuraCloud('/archive', false, zone);
 366 |       
 367 |       return {
 368 |         contents: [
 369 |           {
 370 |             uri,
 371 |             mimeType: 'application/json',
 372 |             text: JSON.stringify(archivesData, null, 2)
 373 |           }
 374 |         ]
 375 |       };
 376 |     } catch (error) {
 377 |       console.error('Error fetching archives:', error);
 378 |       throw error;
 379 |     }
 380 |   } else if (resourcePath === 'sakura:///cdrom') {
 381 |     try {
 382 |       validateCredentials();
 383 |       const cdromData = await fetchFromSakuraCloud('/cdrom', false, zone);
 384 |       
 385 |       return {
 386 |         contents: [
 387 |           {
 388 |             uri,
 389 |             mimeType: 'application/json',
 390 |             text: JSON.stringify(cdromData, null, 2)
 391 |           }
 392 |         ]
 393 |       };
 394 |     } catch (error) {
 395 |       console.error('Error fetching ISO images:', error);
 396 |       throw error;
 397 |     }
 398 |   } else if (uri === 'sakura:///bridge') {
 399 |     try {
 400 |       validateCredentials();
 401 |       const bridgeData = await fetchFromSakuraCloud('/bridge');
 402 |       
 403 |       return {
 404 |         contents: [
 405 |           {
 406 |             uri,
 407 |             mimeType: 'application/json',
 408 |             text: JSON.stringify(bridgeData, null, 2)
 409 |           }
 410 |         ]
 411 |       };
 412 |     } catch (error) {
 413 |       console.error('Error fetching bridges:', error);
 414 |       throw error;
 415 |     }
 416 |   } else if (uri === 'sakura:///internet') {
 417 |     try {
 418 |       validateCredentials();
 419 |       const routerData = await fetchFromSakuraCloud('/internet');
 420 |       
 421 |       return {
 422 |         contents: [
 423 |           {
 424 |             uri,
 425 |             mimeType: 'application/json',
 426 |             text: JSON.stringify(routerData, null, 2)
 427 |           }
 428 |         ]
 429 |       };
 430 |     } catch (error) {
 431 |       console.error('Error fetching routers:', error);
 432 |       throw error;
 433 |     }
 434 |   } else if (uri === 'sakura:///interface') {
 435 |     try {
 436 |       validateCredentials();
 437 |       const interfaceData = await fetchFromSakuraCloud('/interface');
 438 |       
 439 |       return {
 440 |         contents: [
 441 |           {
 442 |             uri,
 443 |             mimeType: 'application/json',
 444 |             text: JSON.stringify(interfaceData, null, 2)
 445 |           }
 446 |         ]
 447 |       };
 448 |     } catch (error) {
 449 |       console.error('Error fetching interfaces:', error);
 450 |       throw error;
 451 |     }
 452 |   } else if (uri === 'sakura:///icon') {
 453 |     try {
 454 |       validateCredentials();
 455 |       const iconData = await fetchFromSakuraCloud('/icon');
 456 |       
 457 |       return {
 458 |         contents: [
 459 |           {
 460 |             uri,
 461 |             mimeType: 'application/json',
 462 |             text: JSON.stringify(iconData, null, 2)
 463 |           }
 464 |         ]
 465 |       };
 466 |     } catch (error) {
 467 |       console.error('Error fetching icons:', error);
 468 |       throw error;
 469 |     }
 470 |   } else if (uri === 'sakura:///note') {
 471 |     try {
 472 |       validateCredentials();
 473 |       const noteData = await fetchFromSakuraCloud('/note');
 474 |       
 475 |       return {
 476 |         contents: [
 477 |           {
 478 |             uri,
 479 |             mimeType: 'application/json',
 480 |             text: JSON.stringify(noteData, null, 2)
 481 |           }
 482 |         ]
 483 |       };
 484 |     } catch (error) {
 485 |       console.error('Error fetching notes:', error);
 486 |       throw error;
 487 |     }
 488 |   } else if (uri === 'sakura:///sshkey') {
 489 |     try {
 490 |       validateCredentials();
 491 |       const sshkeyData = await fetchFromSakuraCloud('/sshkey');
 492 |       
 493 |       return {
 494 |         contents: [
 495 |           {
 496 |             uri,
 497 |             mimeType: 'application/json',
 498 |             text: JSON.stringify(sshkeyData, null, 2)
 499 |           }
 500 |         ]
 501 |       };
 502 |     } catch (error) {
 503 |       console.error('Error fetching SSH keys:', error);
 504 |       throw error;
 505 |     }
 506 |   } else if (uri === 'sakura:///region') {
 507 |     try {
 508 |       validateCredentials();
 509 |       const regionData = await fetchFromSakuraCloud('/region');
 510 |       
 511 |       return {
 512 |         contents: [
 513 |           {
 514 |             uri,
 515 |             mimeType: 'application/json',
 516 |             text: JSON.stringify(regionData, null, 2)
 517 |           }
 518 |         ]
 519 |       };
 520 |     } catch (error) {
 521 |       console.error('Error fetching regions:', error);
 522 |       throw error;
 523 |     }
 524 |   } else if (uri === 'sakura:///zone') {
 525 |     try {
 526 |       validateCredentials();
 527 |       const zoneData = await fetchFromSakuraCloud('/zone');
 528 |       
 529 |       return {
 530 |         contents: [
 531 |           {
 532 |             uri,
 533 |             mimeType: 'application/json',
 534 |             text: JSON.stringify(zoneData, null, 2)
 535 |           }
 536 |         ]
 537 |       };
 538 |     } catch (error) {
 539 |       console.error('Error fetching zones:', error);
 540 |       throw error;
 541 |     }
 542 |   } else if (uri === 'sakura:///product') {
 543 |     try {
 544 |       validateCredentials();
 545 |       // Fetch multiple product types and combine them
 546 |       const serverPlans = await fetchFromSakuraCloud('/product/server');
 547 |       const diskPlans = await fetchFromSakuraCloud('/product/disk');
 548 |       const internetPlans = await fetchFromSakuraCloud('/product/internet');
 549 |       const licensePlans = await fetchFromSakuraCloud('/product/license');
 550 |       
 551 |       const combinedProducts = {
 552 |         server: serverPlans,
 553 |         disk: diskPlans,
 554 |         internet: internetPlans,
 555 |         license: licensePlans
 556 |       };
 557 |       
 558 |       return {
 559 |         contents: [
 560 |           {
 561 |             uri,
 562 |             mimeType: 'application/json',
 563 |             text: JSON.stringify(combinedProducts, null, 2)
 564 |           }
 565 |         ]
 566 |       };
 567 |     } catch (error) {
 568 |       console.error('Error fetching products:', error);
 569 |       throw error;
 570 |     }
 571 |   } else if (uri === 'sakura:///commonserviceitem') {
 572 |     try {
 573 |       validateCredentials();
 574 |       const commonServiceItemData = await fetchFromSakuraCloud('/commonserviceitem');
 575 |       
 576 |       return {
 577 |         contents: [
 578 |           {
 579 |             uri,
 580 |             mimeType: 'application/json',
 581 |             text: JSON.stringify(commonServiceItemData, null, 2)
 582 |           }
 583 |         ]
 584 |       };
 585 |     } catch (error) {
 586 |       console.error('Error fetching common service items:', error);
 587 |       throw error;
 588 |     }
 589 |   } else if (uri === 'sakura:///license') {
 590 |     try {
 591 |       validateCredentials();
 592 |       const licenseData = await fetchFromSakuraCloud('/license');
 593 |       
 594 |       return {
 595 |         contents: [
 596 |           {
 597 |             uri,
 598 |             mimeType: 'application/json',
 599 |             text: JSON.stringify(licenseData, null, 2)
 600 |           }
 601 |         ]
 602 |       };
 603 |     } catch (error) {
 604 |       console.error('Error fetching licenses:', error);
 605 |       throw error;
 606 |     }
 607 |   } else if (uri === 'sakura:///auth-status') {
 608 |     try {
 609 |       validateCredentials();
 610 |       const authStatusData = await fetchFromSakuraCloud('/auth-status');
 611 |       
 612 |       return {
 613 |         contents: [
 614 |           {
 615 |             uri,
 616 |             mimeType: 'application/json',
 617 |             text: JSON.stringify(authStatusData, null, 2)
 618 |           }
 619 |         ]
 620 |       };
 621 |     } catch (error) {
 622 |       console.error('Error fetching authentication status:', error);
 623 |       throw error;
 624 |     }
 625 |   } else if (uri === 'sakura:///bill') {
 626 |     try {
 627 |       validateCredentials();
 628 |       const billData = await fetchFromSakuraCloud('/bill');
 629 |       
 630 |       return {
 631 |         contents: [
 632 |           {
 633 |             uri,
 634 |             mimeType: 'application/json',
 635 |             text: JSON.stringify(billData, null, 2)
 636 |           }
 637 |         ]
 638 |       };
 639 |     } catch (error) {
 640 |       console.error('Error fetching billing information:', error);
 641 |       throw error;
 642 |     }
 643 |   } else if (uri === 'sakura:///bill-detail') {
 644 |     try {
 645 |       validateCredentials();
 646 |       const billDetailData = await fetchFromSakuraCloud('/bill/detail');
 647 |       
 648 |       return {
 649 |         contents: [
 650 |           {
 651 |             uri,
 652 |             mimeType: 'application/json',
 653 |             text: JSON.stringify(billDetailData, null, 2)
 654 |           }
 655 |         ]
 656 |       };
 657 |     } catch (error) {
 658 |       console.error('Error fetching billing details:', error);
 659 |       throw error;
 660 |     }
 661 |   } else if (uri === 'sakura:///coupon') {
 662 |     try {
 663 |       validateCredentials();
 664 |       const couponData = await fetchFromSakuraCloud('/coupon');
 665 |       
 666 |       return {
 667 |         contents: [
 668 |           {
 669 |             uri,
 670 |             mimeType: 'application/json',
 671 |             text: JSON.stringify(couponData, null, 2)
 672 |           }
 673 |         ]
 674 |       };
 675 |     } catch (error) {
 676 |       console.error('Error fetching coupons:', error);
 677 |       throw error;
 678 |     }
 679 |   } else if (uri === 'sakura:///privatehost') {
 680 |     try {
 681 |       validateCredentials();
 682 |       const privateHostData = await fetchFromSakuraCloud('/privatehost');
 683 |       
 684 |       return {
 685 |         contents: [
 686 |           {
 687 |             uri,
 688 |             mimeType: 'application/json',
 689 |             text: JSON.stringify(privateHostData, null, 2)
 690 |           }
 691 |         ]
 692 |       };
 693 |     } catch (error) {
 694 |       console.error('Error fetching private hosts:', error);
 695 |       throw error;
 696 |     }
 697 |   } else if (uri === 'sakura:///public-price') {
 698 |     try {
 699 |       // No authentication needed for public price API
 700 |       const priceData = await fetchFromSakuraCloud('/public/price', false);
 701 |       
 702 |       return {
 703 |         contents: [
 704 |           {
 705 |             uri,
 706 |             mimeType: 'application/json',
 707 |             text: JSON.stringify(priceData, null, 2)
 708 |           }
 709 |         ]
 710 |       };
 711 |     } catch (error) {
 712 |       console.error('Error fetching public price data:', error);
 713 |       throw error;
 714 |     }
 715 |   } else if (resourcePath === 'sakura:///apprun') {
 716 |     try {
 717 |       validateCredentials();
 718 |       const appRunData = await fetchFromAppRunAPI('/applications');
 719 |       
 720 |       return {
 721 |         contents: [
 722 |           {
 723 |             uri,
 724 |             mimeType: 'application/json',
 725 |             text: JSON.stringify(appRunData, null, 2)
 726 |           }
 727 |         ]
 728 |       };
 729 |     } catch (error) {
 730 |       console.error('Error fetching AppRun data:', error);
 731 |       throw error;
 732 |     }
 733 |   }
 734 |   
 735 |   throw new Error(`Resource not found: ${resourcePath}`);
 736 | });
 737 | 
 738 | // Register tools
 739 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 740 |   return {
 741 |     tools: [
 742 |       {
 743 |         name: 'get_server_info',
 744 |         description: 'Get detailed information about a specific server',
 745 |         inputSchema: {
 746 |           type: 'object',
 747 |           properties: {
 748 |             serverId: {
 749 |               type: 'string',
 750 |               description: 'The ID of the server to retrieve'
 751 |             },
 752 |             zone: {
 753 |               type: 'string',
 754 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 755 |             }
 756 |           },
 757 |           required: ['serverId']
 758 |         }
 759 |       },
 760 |       {
 761 |         name: 'get_server_list',
 762 |         description: 'Get list of servers',
 763 |         inputSchema: {
 764 |           type: 'object',
 765 |           properties: {
 766 |             zone: {
 767 |               type: 'string',
 768 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 769 |             }
 770 |           },
 771 |         }
 772 |       },
 773 |       {
 774 |         name: 'get_switch_list',
 775 |         description: 'Get list of switches',
 776 |         inputSchema: {
 777 |           type: 'object',
 778 |           properties: {
 779 |             zone: {
 780 |               type: 'string',
 781 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 782 |             }
 783 |           },
 784 |         }
 785 |       },
 786 |       {
 787 |         name: 'get_switch_info',
 788 |         description: 'Get detailed information about a specific switch',
 789 |         inputSchema: {
 790 |           type: 'object',
 791 |           properties: {
 792 |             switchId: {
 793 |               type: 'string',
 794 |               description: 'The ID of the switch to retrieve'
 795 |             },
 796 |             zone: {
 797 |               type: 'string',
 798 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 799 |             }
 800 |           },
 801 |           required: ['switchId']
 802 |         }
 803 |       },
 804 |       {
 805 |         name: 'get_appliance_list',
 806 |         description: 'Get list of appliances',
 807 |         inputSchema: {
 808 |           type: 'object',
 809 |           properties: {
 810 |             zone: {
 811 |               type: 'string',
 812 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 813 |             }
 814 |           },
 815 |         }
 816 |       },
 817 |       {
 818 |         name: 'get_appliance_info',
 819 |         description: 'Get detailed information about a specific appliance',
 820 |         inputSchema: {
 821 |           type: 'object',
 822 |           properties: {
 823 |             applianceId: {
 824 |               type: 'string',
 825 |               description: 'The ID of the appliance to retrieve'
 826 |             }
 827 |           },
 828 |           required: ['applianceId']
 829 |         }
 830 |       },
 831 |       {
 832 |         name: 'get_disk_list',
 833 |         description: 'Get list of disks',
 834 |         inputSchema: {
 835 |           type: 'object',
 836 |           properties: {
 837 |             zone: {
 838 |               type: 'string',
 839 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 840 |             }
 841 |           },
 842 |         }
 843 |       },
 844 |       {
 845 |         name: 'get_disk_info',
 846 |         description: 'Get detailed information about a specific disk',
 847 |         inputSchema: {
 848 |           type: 'object',
 849 |           properties: {
 850 |             diskId: {
 851 |               type: 'string',
 852 |               description: 'The ID of the disk to retrieve'
 853 |             },
 854 |             zone: {
 855 |               type: 'string',
 856 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
 857 |             }
 858 |           },
 859 |           required: ['diskId']
 860 |         }
 861 |       },
 862 |       {
 863 |         name: 'get_archive_list',
 864 |         description: 'Get list of archives',
 865 |         inputSchema: {
 866 |           type: 'object',
 867 |           properties: {
 868 |           },
 869 |         }
 870 |       },
 871 |       {
 872 |         name: 'get_archive_info',
 873 |         description: 'Get detailed information about a specific archive',
 874 |         inputSchema: {
 875 |           type: 'object',
 876 |           properties: {
 877 |             archiveId: {
 878 |               type: 'string',
 879 |               description: 'The ID of the archive to retrieve'
 880 |             }
 881 |           },
 882 |           required: ['archiveId']
 883 |         }
 884 |       },
 885 |       {
 886 |         name: 'get_cdrom_list',
 887 |         description: 'Get list of ISO images',
 888 |         inputSchema: {
 889 |           type: 'object',
 890 |           properties: {
 891 |           },
 892 |         }
 893 |       },
 894 |       {
 895 |         name: 'get_cdrom_info',
 896 |         description: 'Get detailed information about a specific ISO image',
 897 |         inputSchema: {
 898 |           type: 'object',
 899 |           properties: {
 900 |             cdromId: {
 901 |               type: 'string',
 902 |               description: 'The ID of the ISO image to retrieve'
 903 |             }
 904 |           },
 905 |           required: ['cdromId']
 906 |         }
 907 |       },
 908 |       {
 909 |         name: 'get_bridge_list',
 910 |         description: 'Get list of bridges',
 911 |         inputSchema: {
 912 |           type: 'object',
 913 |           properties: {
 914 |           },
 915 |         }
 916 |       },
 917 |       {
 918 |         name: 'get_bridge_info',
 919 |         description: 'Get detailed information about a specific bridge',
 920 |         inputSchema: {
 921 |           type: 'object',
 922 |           properties: {
 923 |             bridgeId: {
 924 |               type: 'string',
 925 |               description: 'The ID of the bridge to retrieve'
 926 |             }
 927 |           },
 928 |           required: ['bridgeId']
 929 |         }
 930 |       },
 931 |       {
 932 |         name: 'get_router_list',
 933 |         description: 'Get list of routers',
 934 |         inputSchema: {
 935 |           type: 'object',
 936 |           properties: {
 937 |           },
 938 |         }
 939 |       },
 940 |       {
 941 |         name: 'get_router_info',
 942 |         description: 'Get detailed information about a specific router',
 943 |         inputSchema: {
 944 |           type: 'object',
 945 |           properties: {
 946 |             routerId: {
 947 |               type: 'string',
 948 |               description: 'The ID of the router to retrieve'
 949 |             }
 950 |           },
 951 |           required: ['routerId']
 952 |         }
 953 |       },
 954 |       {
 955 |         name: 'get_interface_list',
 956 |         description: 'Get list of network interfaces',
 957 |         inputSchema: {
 958 |           type: 'object',
 959 |           properties: {
 960 |           },
 961 |         }
 962 |       },
 963 |       {
 964 |         name: 'get_interface_info',
 965 |         description: 'Get detailed information about a specific network interface',
 966 |         inputSchema: {
 967 |           type: 'object',
 968 |           properties: {
 969 |             interfaceId: {
 970 |               type: 'string',
 971 |               description: 'The ID of the interface to retrieve'
 972 |             }
 973 |           },
 974 |           required: ['interfaceId']
 975 |         }
 976 |       },
 977 |       {
 978 |         name: 'get_icon_list',
 979 |         description: 'Get list of icons',
 980 |         inputSchema: {
 981 |           type: 'object',
 982 |           properties: {
 983 |           },
 984 |         }
 985 |       },
 986 |       {
 987 |         name: 'get_icon_info',
 988 |         description: 'Get detailed information about a specific icon',
 989 |         inputSchema: {
 990 |           type: 'object',
 991 |           properties: {
 992 |             iconId: {
 993 |               type: 'string',
 994 |               description: 'The ID of the icon to retrieve'
 995 |             }
 996 |           },
 997 |           required: ['iconId']
 998 |         }
 999 |       },
1000 |       {
1001 |         name: 'get_note_list',
1002 |         description: 'Get list of notes and startup scripts',
1003 |         inputSchema: {
1004 |           type: 'object',
1005 |           properties: {
1006 |           },
1007 |         }
1008 |       },
1009 |       {
1010 |         name: 'get_note_info',
1011 |         description: 'Get detailed information about a specific note or startup script',
1012 |         inputSchema: {
1013 |           type: 'object',
1014 |           properties: {
1015 |             noteId: {
1016 |               type: 'string',
1017 |               description: 'The ID of the note to retrieve'
1018 |             }
1019 |           },
1020 |           required: ['noteId']
1021 |         }
1022 |       },
1023 |       {
1024 |         name: 'get_sshkey_list',
1025 |         description: 'Get list of SSH keys',
1026 |         inputSchema: {
1027 |           type: 'object',
1028 |           properties: {
1029 |           },
1030 |         }
1031 |       },
1032 |       {
1033 |         name: 'get_sshkey_info',
1034 |         description: 'Get detailed information about a specific SSH key',
1035 |         inputSchema: {
1036 |           type: 'object',
1037 |           properties: {
1038 |             sshkeyId: {
1039 |               type: 'string',
1040 |               description: 'The ID of the SSH key to retrieve'
1041 |             }
1042 |           },
1043 |           required: ['sshkeyId']
1044 |         }
1045 |       },
1046 |       {
1047 |         name: 'get_region_list',
1048 |         description: 'Get list of regions',
1049 |         inputSchema: {
1050 |           type: 'object',
1051 |           properties: {
1052 |           },
1053 |         }
1054 |       },
1055 |       {
1056 |         name: 'get_region_info',
1057 |         description: 'Get detailed information about a specific region',
1058 |         inputSchema: {
1059 |           type: 'object',
1060 |           properties: {
1061 |             regionId: {
1062 |               type: 'string',
1063 |               description: 'The ID of the region to retrieve'
1064 |             }
1065 |           },
1066 |           required: ['regionId']
1067 |         }
1068 |       },
1069 |       {
1070 |         name: 'get_zone_list',
1071 |         description: 'Get list of zones',
1072 |         inputSchema: {
1073 |           type: 'object',
1074 |           properties: {
1075 |           },
1076 |         }
1077 |       },
1078 |       {
1079 |         name: 'get_zone_info',
1080 |         description: 'Get detailed information about a specific zone',
1081 |         inputSchema: {
1082 |           type: 'object',
1083 |           properties: {
1084 |             zoneId: {
1085 |               type: 'string',
1086 |               description: 'The ID of the zone to retrieve'
1087 |             }
1088 |           },
1089 |           required: ['zoneId']
1090 |         }
1091 |       },
1092 |       {
1093 |         name: 'get_product_info',
1094 |         description: 'Get detailed information about specific product offerings',
1095 |         inputSchema: {
1096 |           type: 'object',
1097 |           properties: {
1098 |             productType: {
1099 |               type: 'string',
1100 |               description: 'The type of product to retrieve (server, disk, internet, license)',
1101 |               enum: ['server', 'disk', 'internet', 'license']
1102 |             }
1103 |           },
1104 |           required: ['productType']
1105 |         }
1106 |       },
1107 |       {
1108 |         name: 'get_commonserviceitem_list',
1109 |         description: 'Get list of common service items (DNS, Simple Monitor, etc.)',
1110 |         inputSchema: {
1111 |           type: 'object',
1112 |           properties: {
1113 |           },
1114 |         }
1115 |       },
1116 |       {
1117 |         name: 'get_commonserviceitem_info',
1118 |         description: 'Get detailed information about a specific common service item',
1119 |         inputSchema: {
1120 |           type: 'object',
1121 |           properties: {
1122 |             itemId: {
1123 |               type: 'string',
1124 |               description: 'The ID of the common service item to retrieve'
1125 |             }
1126 |           },
1127 |           required: ['itemId']
1128 |         }
1129 |       },
1130 |       {
1131 |         name: 'get_license_list',
1132 |         description: 'Get list of licenses',
1133 |         inputSchema: {
1134 |           type: 'object',
1135 |           properties: {
1136 |           },
1137 |         }
1138 |       },
1139 |       {
1140 |         name: 'get_license_info',
1141 |         description: 'Get detailed information about a specific license',
1142 |         inputSchema: {
1143 |           type: 'object',
1144 |           properties: {
1145 |             licenseId: {
1146 |               type: 'string',
1147 |               description: 'The ID of the license to retrieve'
1148 |             }
1149 |           },
1150 |           required: ['licenseId']
1151 |         }
1152 |       },
1153 |       {
1154 |         name: 'get_bill_info',
1155 |         description: 'Get billing information for a specific month',
1156 |         inputSchema: {
1157 |           type: 'object',
1158 |           properties: {
1159 |             year: {
1160 |               type: 'string',
1161 |               description: 'The year (YYYY) of the billing period'
1162 |             },
1163 |             month: {
1164 |               type: 'string',
1165 |               description: 'The month (MM) of the billing period'
1166 |             }
1167 |           },
1168 |           required: ['year', 'month']
1169 |         }
1170 |       },
1171 |       {
1172 |         name: 'get_bill_detail',
1173 |         description: 'Get detailed billing information for a specific month',
1174 |         inputSchema: {
1175 |           type: 'object',
1176 |           properties: {
1177 |             year: {
1178 |               type: 'string',
1179 |               description: 'The year (YYYY) of the billing period'
1180 |             },
1181 |             month: {
1182 |               type: 'string',
1183 |               description: 'The month (MM) of the billing period'
1184 |             }
1185 |           },
1186 |           required: ['year', 'month']
1187 |         }
1188 |       },
1189 |       {
1190 |         name: 'get_coupon_info',
1191 |         description: 'Get information about a specific coupon',
1192 |         inputSchema: {
1193 |           type: 'object',
1194 |           properties: {
1195 |             couponId: {
1196 |               type: 'string',
1197 |               description: 'The ID of the coupon to retrieve'
1198 |             }
1199 |           },
1200 |           required: ['couponId']
1201 |         }
1202 |       },
1203 |       {
1204 |         name: 'get_privatehost_info',
1205 |         description: 'Get detailed information about a specific private host',
1206 |         inputSchema: {
1207 |           type: 'object',
1208 |           properties: {
1209 |             privateHostId: {
1210 |               type: 'string',
1211 |               description: 'The ID of the private host to retrieve'
1212 |             }
1213 |           },
1214 |           required: ['privateHostId']
1215 |         }
1216 |       },
1217 |       {
1218 |         name: 'get_public_price',
1219 |         description: 'Get public pricing information for Sakura Cloud services',
1220 |         inputSchema: {
1221 |           type: 'object',
1222 |           properties: {}
1223 |         }
1224 |       },
1225 |       {
1226 |         name: 'get_apprun_list',
1227 |         description: 'Get list of all AppRun applications',
1228 |         inputSchema: {
1229 |           type: 'object',
1230 |           properties: {
1231 |             zone: {
1232 |               type: 'string',
1233 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1234 |             }
1235 |           }
1236 |         }
1237 |       },
1238 |       {
1239 |         name: 'get_apprun_info',
1240 |         description: 'Get detailed information about a specific AppRun application',
1241 |         inputSchema: {
1242 |           type: 'object',
1243 |           properties: {
1244 |             appId: {
1245 |               type: 'string',
1246 |               description: 'The ID of the AppRun application to retrieve'
1247 |             },
1248 |             zone: {
1249 |               type: 'string',
1250 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1251 |             }
1252 |           },
1253 |           required: ['appId']
1254 |         }
1255 |       },
1256 |       {
1257 |         name: 'create_apprun',
1258 |         description: 'Create a new AppRun application',
1259 |         inputSchema: {
1260 |           type: 'object',
1261 |           properties: {
1262 |             name: {
1263 |               type: 'string',
1264 |               description: 'Name of the AppRun application'
1265 |             },
1266 |             description: {
1267 |               type: 'string',
1268 |               description: 'Description of the AppRun application'
1269 |             },
1270 |             dockerImage: {
1271 |               type: 'string',
1272 |               description: 'Docker image to use for the AppRun application'
1273 |             },
1274 |             planId: {
1275 |               type: 'string',
1276 |               description: 'Plan ID for the AppRun application'
1277 |             },
1278 |             environment: {
1279 |               type: 'array',
1280 |               description: 'Environment variables for the AppRun application',
1281 |               items: {
1282 |                 type: 'object',
1283 |                 properties: {
1284 |                   key: { type: 'string' },
1285 |                   value: { type: 'string' }
1286 |                 }
1287 |               }
1288 |             },
1289 |             zone: {
1290 |               type: 'string',
1291 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1292 |             }
1293 |           },
1294 |           required: ['name', 'dockerImage', 'planId']
1295 |         }
1296 |       },
1297 |       {
1298 |         name: 'delete_apprun',
1299 |         description: 'Delete an AppRun application',
1300 |         inputSchema: {
1301 |           type: 'object',
1302 |           properties: {
1303 |             appId: {
1304 |               type: 'string',
1305 |               description: 'The ID of the AppRun application to delete'
1306 |             },
1307 |             zone: {
1308 |               type: 'string',
1309 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1310 |             }
1311 |           },
1312 |           required: ['appId']
1313 |         }
1314 |       },
1315 |       {
1316 |         name: 'start_apprun',
1317 |         description: 'Start an AppRun application',
1318 |         inputSchema: {
1319 |           type: 'object',
1320 |           properties: {
1321 |             appId: {
1322 |               type: 'string',
1323 |               description: 'The ID of the AppRun application to start'
1324 |             },
1325 |             zone: {
1326 |               type: 'string',
1327 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1328 |             }
1329 |           },
1330 |           required: ['appId']
1331 |         }
1332 |       },
1333 |       {
1334 |         name: 'stop_apprun',
1335 |         description: 'Stop an AppRun application',
1336 |         inputSchema: {
1337 |           type: 'object',
1338 |           properties: {
1339 |             appId: {
1340 |               type: 'string',
1341 |               description: 'The ID of the AppRun application to stop'
1342 |             },
1343 |             zone: {
1344 |               type: 'string',
1345 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1346 |             }
1347 |           },
1348 |           required: ['appId']
1349 |         }
1350 |       },
1351 |       {
1352 |         name: 'update_apprun',
1353 |         description: 'Update an existing AppRun application',
1354 |         inputSchema: {
1355 |           type: 'object',
1356 |           properties: {
1357 |             appId: {
1358 |               type: 'string',
1359 |               description: 'The ID of the AppRun application to update'
1360 |             },
1361 |             name: {
1362 |               type: 'string',
1363 |               description: 'New name of the AppRun application'
1364 |             },
1365 |             description: {
1366 |               type: 'string',
1367 |               description: 'New description of the AppRun application'
1368 |             },
1369 |             dockerImage: {
1370 |               type: 'string',
1371 |               description: 'New Docker image to use for the AppRun application'
1372 |             },
1373 |             planId: {
1374 |               type: 'string',
1375 |               description: 'New plan ID for the AppRun application'
1376 |             },
1377 |             environment: {
1378 |               type: 'array',
1379 |               description: 'New environment variables for the AppRun application',
1380 |               items: {
1381 |                 type: 'object',
1382 |                 properties: {
1383 |                   key: { type: 'string' },
1384 |                   value: { type: 'string' }
1385 |                 }
1386 |               }
1387 |             },
1388 |             zone: {
1389 |               type: 'string',
1390 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1391 |             }
1392 |           },
1393 |           required: ['appId']
1394 |         }
1395 |       },
1396 |       {
1397 |         name: 'get_apprun_logs',
1398 |         description: 'Get logs from an AppRun application',
1399 |         inputSchema: {
1400 |           type: 'object',
1401 |           properties: {
1402 |             appId: {
1403 |               type: 'string',
1404 |               description: 'The ID of the AppRun application to get logs from'
1405 |             },
1406 |             offset: {
1407 |               type: 'number',
1408 |               description: 'Offset to start fetching logs from (default: 0)'
1409 |             },
1410 |             limit: {
1411 |               type: 'number',
1412 |               description: 'Maximum number of log entries to fetch (default: 100)'
1413 |             },
1414 |             zone: {
1415 |               type: 'string',
1416 |               description: 'The zone to use (e.g., "tk1v", "is1a", "tk1a"). Defaults to "tk1v" if not specified.'
1417 |             }
1418 |           },
1419 |           required: ['appId']
1420 |         }
1421 |       }
1422 |     ]
1423 |   };
1424 | });
1425 | 
1426 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
1427 |   if (request.params.name === 'get_server_info') {
1428 |     try {
1429 |       validateCredentials();
1430 |       
1431 |       const serverId = request.params.arguments?.serverId as string;
1432 |       if (!serverId) {
1433 |         throw new Error('Server ID is required');
1434 |       }
1435 |       
1436 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1437 |       const serverInfo = await fetchFromSakuraCloud(`/server/${serverId}`, false, zone);
1438 |       
1439 |       return {
1440 |         content: [
1441 |           {
1442 |             type: 'text',
1443 |             text: JSON.stringify(serverInfo, null, 2)
1444 |           }
1445 |         ]
1446 |       };
1447 |     } catch (error) {
1448 |       console.error('Error calling tool:', error);
1449 |       throw error;
1450 |     }
1451 |   } else if (request.params.name === 'get_server_list') {
1452 |     try {
1453 |       validateCredentials();
1454 |       
1455 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1456 |       const serverList = await fetchFromSakuraCloud(`/server`, false, zone);
1457 |       
1458 |       return {
1459 |         content: [
1460 |           {
1461 |             type: 'text',
1462 |             text: JSON.stringify(serverList, null, 2)
1463 |           }
1464 |         ]
1465 |       };
1466 |     } catch (error) {
1467 |       console.error('Error calling tool:', error);
1468 |       throw error;
1469 |     }
1470 |   } else if (request.params.name === 'get_switch_list') {
1471 |     try {
1472 |       validateCredentials();
1473 |       
1474 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1475 |       const switchList = await fetchFromSakuraCloud(`/switch`, false, zone);
1476 |       
1477 |       return {
1478 |         content: [
1479 |           {
1480 |             type: 'text',
1481 |             text: JSON.stringify(switchList, null, 2)
1482 |           }
1483 |         ]
1484 |       };
1485 |     } catch (error) {
1486 |       console.error('Error calling tool:', error);
1487 |       throw error;
1488 |     }
1489 |   } else if (request.params.name === 'get_switch_info') {
1490 |     try {
1491 |       validateCredentials();
1492 |       
1493 |       const switchId = request.params.arguments?.switchId as string;
1494 |       if (!switchId) {
1495 |         throw new Error('Switch ID is required');
1496 |       }
1497 |       
1498 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1499 |       const switchInfo = await fetchFromSakuraCloud(`/switch/${switchId}`, false, zone);
1500 |       
1501 |       return {
1502 |         content: [
1503 |           {
1504 |             type: 'text',
1505 |             text: JSON.stringify(switchInfo, null, 2)
1506 |           }
1507 |         ]
1508 |       };
1509 |     } catch (error) {
1510 |       console.error('Error calling tool:', error);
1511 |       throw error;
1512 |     }
1513 |   } else if (request.params.name === 'get_appliance_list') {
1514 |     try {
1515 |       validateCredentials();
1516 |       
1517 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1518 |       const applianceList = await fetchFromSakuraCloud(`/appliance`, false, zone);
1519 |       
1520 |       return {
1521 |         content: [
1522 |           {
1523 |             type: 'text',
1524 |             text: JSON.stringify(applianceList, null, 2)
1525 |           }
1526 |         ]
1527 |       };
1528 |     } catch (error) {
1529 |       console.error('Error calling tool:', error);
1530 |       throw error;
1531 |     }
1532 |   } else if (request.params.name === 'get_appliance_info') {
1533 |     try {
1534 |       validateCredentials();
1535 |       
1536 |       const applianceId = request.params.arguments?.applianceId as string;
1537 |       if (!applianceId) {
1538 |         throw new Error('Appliance ID is required');
1539 |       }
1540 |       
1541 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1542 |       const applianceInfo = await fetchFromSakuraCloud(`/appliance/${applianceId}`, false, zone);
1543 |       
1544 |       return {
1545 |         content: [
1546 |           {
1547 |             type: 'text',
1548 |             text: JSON.stringify(applianceInfo, null, 2)
1549 |           }
1550 |         ]
1551 |       };
1552 |     } catch (error) {
1553 |       console.error('Error calling tool:', error);
1554 |       throw error;
1555 |     }
1556 |   } else if (request.params.name === 'get_disk_list') {
1557 |     try {
1558 |       validateCredentials();
1559 |       
1560 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1561 |       const diskList = await fetchFromSakuraCloud(`/disk`, false, zone);
1562 |       
1563 |       return {
1564 |         content: [
1565 |           {
1566 |             type: 'text',
1567 |             text: JSON.stringify(diskList, null, 2)
1568 |           }
1569 |         ]
1570 |       };
1571 |     } catch (error) {
1572 |       console.error('Error calling tool:', error);
1573 |       throw error;
1574 |     }
1575 |   } else if (request.params.name === 'get_disk_info') {
1576 |     try {
1577 |       validateCredentials();
1578 |       
1579 |       const diskId = request.params.arguments?.diskId as string;
1580 |       if (!diskId) {
1581 |         throw new Error('Disk ID is required');
1582 |       }
1583 |       
1584 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1585 |       const diskInfo = await fetchFromSakuraCloud(`/disk/${diskId}`, false, zone);
1586 |       
1587 |       return {
1588 |         content: [
1589 |           {
1590 |             type: 'text',
1591 |             text: JSON.stringify(diskInfo, null, 2)
1592 |           }
1593 |         ]
1594 |       };
1595 |     } catch (error) {
1596 |       console.error('Error calling tool:', error);
1597 |       throw error;
1598 |     }
1599 |   } else if (request.params.name === 'get_archive_list') {
1600 |     try {
1601 |       validateCredentials();
1602 |       
1603 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1604 |       const archiveList = await fetchFromSakuraCloud(`/archive`, false, zone);
1605 |       
1606 |       return {
1607 |         content: [
1608 |           {
1609 |             type: 'text',
1610 |             text: JSON.stringify(archiveList, null, 2)
1611 |           }
1612 |         ]
1613 |       };
1614 |     } catch (error) {
1615 |       console.error('Error calling tool:', error);
1616 |       throw error;
1617 |     }
1618 |   } else if (request.params.name === 'get_archive_info') {
1619 |     try {
1620 |       validateCredentials();
1621 |       
1622 |       const archiveId = request.params.arguments?.archiveId as string;
1623 |       if (!archiveId) {
1624 |         throw new Error('Archive ID is required');
1625 |       }
1626 |       
1627 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
1628 |       const archiveInfo = await fetchFromSakuraCloud(`/archive/${archiveId}`, false, zone);
1629 |       
1630 |       return {
1631 |         content: [
1632 |           {
1633 |             type: 'text',
1634 |             text: JSON.stringify(archiveInfo, null, 2)
1635 |           }
1636 |         ]
1637 |       };
1638 |     } catch (error) {
1639 |       console.error('Error calling tool:', error);
1640 |       throw error;
1641 |     }
1642 |   } else if (request.params.name === 'get_cdrom_list') {
1643 |     try {
1644 |       validateCredentials();
1645 |       
1646 |       const cdromList = await fetchFromSakuraCloud(`/cdrom`);
1647 |       
1648 |       return {
1649 |         content: [
1650 |           {
1651 |             type: 'text',
1652 |             text: JSON.stringify(cdromList, null, 2)
1653 |           }
1654 |         ]
1655 |       };
1656 |     } catch (error) {
1657 |       console.error('Error calling tool:', error);
1658 |       throw error;
1659 |     }
1660 |   } else if (request.params.name === 'get_cdrom_info') {
1661 |     try {
1662 |       validateCredentials();
1663 |       
1664 |       const cdromId = request.params.arguments?.cdromId as string;
1665 |       if (!cdromId) {
1666 |         throw new Error('ISO Image ID is required');
1667 |       }
1668 |       
1669 |       const cdromInfo = await fetchFromSakuraCloud(`/cdrom/${cdromId}`);
1670 |       
1671 |       return {
1672 |         content: [
1673 |           {
1674 |             type: 'text',
1675 |             text: JSON.stringify(cdromInfo, null, 2)
1676 |           }
1677 |         ]
1678 |       };
1679 |     } catch (error) {
1680 |       console.error('Error calling tool:', error);
1681 |       throw error;
1682 |     }
1683 |   } else if (request.params.name === 'get_bridge_list') {
1684 |     try {
1685 |       validateCredentials();
1686 |       
1687 |       const bridgeList = await fetchFromSakuraCloud(`/bridge`);
1688 |       
1689 |       return {
1690 |         content: [
1691 |           {
1692 |             type: 'text',
1693 |             text: JSON.stringify(bridgeList, null, 2)
1694 |           }
1695 |         ]
1696 |       };
1697 |     } catch (error) {
1698 |       console.error('Error calling tool:', error);
1699 |       throw error;
1700 |     }
1701 |   } else if (request.params.name === 'get_bridge_info') {
1702 |     try {
1703 |       validateCredentials();
1704 |       
1705 |       const bridgeId = request.params.arguments?.bridgeId as string;
1706 |       if (!bridgeId) {
1707 |         throw new Error('Bridge ID is required');
1708 |       }
1709 |       
1710 |       const bridgeInfo = await fetchFromSakuraCloud(`/bridge/${bridgeId}`);
1711 |       
1712 |       return {
1713 |         content: [
1714 |           {
1715 |             type: 'text',
1716 |             text: JSON.stringify(bridgeInfo, null, 2)
1717 |           }
1718 |         ]
1719 |       };
1720 |     } catch (error) {
1721 |       console.error('Error calling tool:', error);
1722 |       throw error;
1723 |     }
1724 |   } else if (request.params.name === 'get_router_list') {
1725 |     try {
1726 |       validateCredentials();
1727 |       
1728 |       const routerList = await fetchFromSakuraCloud(`/internet`);
1729 |       
1730 |       return {
1731 |         content: [
1732 |           {
1733 |             type: 'text',
1734 |             text: JSON.stringify(routerList, null, 2)
1735 |           }
1736 |         ]
1737 |       };
1738 |     } catch (error) {
1739 |       console.error('Error calling tool:', error);
1740 |       throw error;
1741 |     }
1742 |   } else if (request.params.name === 'get_router_info') {
1743 |     try {
1744 |       validateCredentials();
1745 |       
1746 |       const routerId = request.params.arguments?.routerId as string;
1747 |       if (!routerId) {
1748 |         throw new Error('Router ID is required');
1749 |       }
1750 |       
1751 |       const routerInfo = await fetchFromSakuraCloud(`/internet/${routerId}`);
1752 |       
1753 |       return {
1754 |         content: [
1755 |           {
1756 |             type: 'text',
1757 |             text: JSON.stringify(routerInfo, null, 2)
1758 |           }
1759 |         ]
1760 |       };
1761 |     } catch (error) {
1762 |       console.error('Error calling tool:', error);
1763 |       throw error;
1764 |     }
1765 |   } else if (request.params.name === 'get_interface_list') {
1766 |     try {
1767 |       validateCredentials();
1768 |       
1769 |       const interfaceList = await fetchFromSakuraCloud(`/interface`);
1770 |       
1771 |       return {
1772 |         content: [
1773 |           {
1774 |             type: 'text',
1775 |             text: JSON.stringify(interfaceList, null, 2)
1776 |           }
1777 |         ]
1778 |       };
1779 |     } catch (error) {
1780 |       console.error('Error calling tool:', error);
1781 |       throw error;
1782 |     }
1783 |   } else if (request.params.name === 'get_interface_info') {
1784 |     try {
1785 |       validateCredentials();
1786 |       
1787 |       const interfaceId = request.params.arguments?.interfaceId as string;
1788 |       if (!interfaceId) {
1789 |         throw new Error('Interface ID is required');
1790 |       }
1791 |       
1792 |       const interfaceInfo = await fetchFromSakuraCloud(`/interface/${interfaceId}`);
1793 |       
1794 |       return {
1795 |         content: [
1796 |           {
1797 |             type: 'text',
1798 |             text: JSON.stringify(interfaceInfo, null, 2)
1799 |           }
1800 |         ]
1801 |       };
1802 |     } catch (error) {
1803 |       console.error('Error calling tool:', error);
1804 |       throw error;
1805 |     }
1806 |   } else if (request.params.name === 'get_icon_list') {
1807 |     try {
1808 |       validateCredentials();
1809 |       
1810 |       const iconList = await fetchFromSakuraCloud(`/icon`);
1811 |       
1812 |       return {
1813 |         content: [
1814 |           {
1815 |             type: 'text',
1816 |             text: JSON.stringify(iconList, null, 2)
1817 |           }
1818 |         ]
1819 |       };
1820 |     } catch (error) {
1821 |       console.error('Error calling tool:', error);
1822 |       throw error;
1823 |     }
1824 |   } else if (request.params.name === 'get_icon_info') {
1825 |     try {
1826 |       validateCredentials();
1827 |       
1828 |       const iconId = request.params.arguments?.iconId as string;
1829 |       if (!iconId) {
1830 |         throw new Error('Icon ID is required');
1831 |       }
1832 |       
1833 |       const iconInfo = await fetchFromSakuraCloud(`/icon/${iconId}`);
1834 |       
1835 |       return {
1836 |         content: [
1837 |           {
1838 |             type: 'text',
1839 |             text: JSON.stringify(iconInfo, null, 2)
1840 |           }
1841 |         ]
1842 |       };
1843 |     } catch (error) {
1844 |       console.error('Error calling tool:', error);
1845 |       throw error;
1846 |     }
1847 |   } else if (request.params.name === 'get_note_list') {
1848 |     try {
1849 |       validateCredentials();
1850 |       
1851 |       const noteList = await fetchFromSakuraCloud(`/note`);
1852 |       
1853 |       return {
1854 |         content: [
1855 |           {
1856 |             type: 'text',
1857 |             text: JSON.stringify(noteList, null, 2)
1858 |           }
1859 |         ]
1860 |       };
1861 |     } catch (error) {
1862 |       console.error('Error calling tool:', error);
1863 |       throw error;
1864 |     }
1865 |   } else if (request.params.name === 'get_note_info') {
1866 |     try {
1867 |       validateCredentials();
1868 |       
1869 |       const noteId = request.params.arguments?.noteId as string;
1870 |       if (!noteId) {
1871 |         throw new Error('Note ID is required');
1872 |       }
1873 |       
1874 |       const noteInfo = await fetchFromSakuraCloud(`/note/${noteId}`);
1875 |       
1876 |       return {
1877 |         content: [
1878 |           {
1879 |             type: 'text',
1880 |             text: JSON.stringify(noteInfo, null, 2)
1881 |           }
1882 |         ]
1883 |       };
1884 |     } catch (error) {
1885 |       console.error('Error calling tool:', error);
1886 |       throw error;
1887 |     }
1888 |   } else if (request.params.name === 'get_sshkey_list') {
1889 |     try {
1890 |       validateCredentials();
1891 |       
1892 |       const sshkeyList = await fetchFromSakuraCloud(`/sshkey`);
1893 |       
1894 |       return {
1895 |         content: [
1896 |           {
1897 |             type: 'text',
1898 |             text: JSON.stringify(sshkeyList, null, 2)
1899 |           }
1900 |         ]
1901 |       };
1902 |     } catch (error) {
1903 |       console.error('Error calling tool:', error);
1904 |       throw error;
1905 |     }
1906 |   } else if (request.params.name === 'get_sshkey_info') {
1907 |     try {
1908 |       validateCredentials();
1909 |       
1910 |       const sshkeyId = request.params.arguments?.sshkeyId as string;
1911 |       if (!sshkeyId) {
1912 |         throw new Error('SSH Key ID is required');
1913 |       }
1914 |       
1915 |       const sshkeyInfo = await fetchFromSakuraCloud(`/sshkey/${sshkeyId}`);
1916 |       
1917 |       return {
1918 |         content: [
1919 |           {
1920 |             type: 'text',
1921 |             text: JSON.stringify(sshkeyInfo, null, 2)
1922 |           }
1923 |         ]
1924 |       };
1925 |     } catch (error) {
1926 |       console.error('Error calling tool:', error);
1927 |       throw error;
1928 |     }
1929 |   } else if (request.params.name === 'get_region_list') {
1930 |     try {
1931 |       validateCredentials();
1932 |       
1933 |       const regionList = await fetchFromSakuraCloud(`/region`);
1934 |       
1935 |       return {
1936 |         content: [
1937 |           {
1938 |             type: 'text',
1939 |             text: JSON.stringify(regionList, null, 2)
1940 |           }
1941 |         ]
1942 |       };
1943 |     } catch (error) {
1944 |       console.error('Error calling tool:', error);
1945 |       throw error;
1946 |     }
1947 |   } else if (request.params.name === 'get_region_info') {
1948 |     try {
1949 |       validateCredentials();
1950 |       
1951 |       const regionId = request.params.arguments?.regionId as string;
1952 |       if (!regionId) {
1953 |         throw new Error('Region ID is required');
1954 |       }
1955 |       
1956 |       const regionInfo = await fetchFromSakuraCloud(`/region/${regionId}`);
1957 |       
1958 |       return {
1959 |         content: [
1960 |           {
1961 |             type: 'text',
1962 |             text: JSON.stringify(regionInfo, null, 2)
1963 |           }
1964 |         ]
1965 |       };
1966 |     } catch (error) {
1967 |       console.error('Error calling tool:', error);
1968 |       throw error;
1969 |     }
1970 |   } else if (request.params.name === 'get_zone_list') {
1971 |     try {
1972 |       validateCredentials();
1973 |       
1974 |       const zoneList = await fetchFromSakuraCloud(`/zone`);
1975 |       
1976 |       return {
1977 |         content: [
1978 |           {
1979 |             type: 'text',
1980 |             text: JSON.stringify(zoneList, null, 2)
1981 |           }
1982 |         ]
1983 |       };
1984 |     } catch (error) {
1985 |       console.error('Error calling tool:', error);
1986 |       throw error;
1987 |     }
1988 |   } else if (request.params.name === 'get_zone_info') {
1989 |     try {
1990 |       validateCredentials();
1991 |       
1992 |       const zoneId = request.params.arguments?.zoneId as string;
1993 |       if (!zoneId) {
1994 |         throw new Error('Zone ID is required');
1995 |       }
1996 |       
1997 |       const zoneInfo = await fetchFromSakuraCloud(`/zone/${zoneId}`);
1998 |       
1999 |       return {
2000 |         content: [
2001 |           {
2002 |             type: 'text',
2003 |             text: JSON.stringify(zoneInfo, null, 2)
2004 |           }
2005 |         ]
2006 |       };
2007 |     } catch (error) {
2008 |       console.error('Error calling tool:', error);
2009 |       throw error;
2010 |     }
2011 |   } else if (request.params.name === 'get_product_info') {
2012 |     try {
2013 |       validateCredentials();
2014 |       
2015 |       const productType = request.params.arguments?.productType as string;
2016 |       if (!productType) {
2017 |         throw new Error('Product type is required');
2018 |       }
2019 |       
2020 |       // Validate product type
2021 |       if (!['server', 'disk', 'internet', 'license'].includes(productType)) {
2022 |         throw new Error('Invalid product type. Must be one of: server, disk, internet, license');
2023 |       }
2024 |       
2025 |       const productInfo = await fetchFromSakuraCloud(`/product/${productType}`);
2026 |       
2027 |       return {
2028 |         content: [
2029 |           {
2030 |             type: 'text',
2031 |             text: JSON.stringify(productInfo, null, 2)
2032 |           }
2033 |         ]
2034 |       };
2035 |     } catch (error) {
2036 |       console.error('Error calling tool:', error);
2037 |       throw error;
2038 |     }
2039 |   } else if (request.params.name === 'get_commonserviceitem_list') {
2040 |     try {
2041 |       validateCredentials();
2042 |       
2043 |       const commonServiceItemList = await fetchFromSakuraCloud(`/commonserviceitem`);
2044 |       
2045 |       return {
2046 |         content: [
2047 |           {
2048 |             type: 'text',
2049 |             text: JSON.stringify(commonServiceItemList, null, 2)
2050 |           }
2051 |         ]
2052 |       };
2053 |     } catch (error) {
2054 |       console.error('Error calling tool:', error);
2055 |       throw error;
2056 |     }
2057 |   } else if (request.params.name === 'get_commonserviceitem_info') {
2058 |     try {
2059 |       validateCredentials();
2060 |       
2061 |       const itemId = request.params.arguments?.itemId as string;
2062 |       if (!itemId) {
2063 |         throw new Error('Common Service Item ID is required');
2064 |       }
2065 |       
2066 |       const itemInfo = await fetchFromSakuraCloud(`/commonserviceitem/${itemId}`);
2067 |       
2068 |       return {
2069 |         content: [
2070 |           {
2071 |             type: 'text',
2072 |             text: JSON.stringify(itemInfo, null, 2)
2073 |           }
2074 |         ]
2075 |       };
2076 |     } catch (error) {
2077 |       console.error('Error calling tool:', error);
2078 |       throw error;
2079 |     }
2080 |   } else if (request.params.name === 'get_license_list') {
2081 |     try {
2082 |       validateCredentials();
2083 |       
2084 |       const licenseList = await fetchFromSakuraCloud(`/license`);
2085 |       
2086 |       return {
2087 |         content: [
2088 |           {
2089 |             type: 'text',
2090 |             text: JSON.stringify(licenseList, null, 2)
2091 |           }
2092 |         ]
2093 |       };
2094 |     } catch (error) {
2095 |       console.error('Error calling tool:', error);
2096 |       throw error;
2097 |     }
2098 |   } else if (request.params.name === 'get_license_info') {
2099 |     try {
2100 |       validateCredentials();
2101 |       
2102 |       const licenseId = request.params.arguments?.licenseId as string;
2103 |       if (!licenseId) {
2104 |         throw new Error('License ID is required');
2105 |       }
2106 |       
2107 |       const licenseInfo = await fetchFromSakuraCloud(`/license/${licenseId}`);
2108 |       
2109 |       return {
2110 |         content: [
2111 |           {
2112 |             type: 'text',
2113 |             text: JSON.stringify(licenseInfo, null, 2)
2114 |           }
2115 |         ]
2116 |       };
2117 |     } catch (error) {
2118 |       console.error('Error calling tool:', error);
2119 |       throw error;
2120 |     }
2121 |   } else if (request.params.name === 'get_bill_info') {
2122 |     try {
2123 |       validateCredentials();
2124 |       
2125 |       const year = request.params.arguments?.year as string;
2126 |       const month = request.params.arguments?.month as string;
2127 |       
2128 |       if (!year || !month) {
2129 |         throw new Error('Year and month are required for billing information');
2130 |       }
2131 |       
2132 |       const billInfo = await fetchFromSakuraCloud(`/bill/${year}${month}`);
2133 |       
2134 |       return {
2135 |         content: [
2136 |           {
2137 |             type: 'text',
2138 |             text: JSON.stringify(billInfo, null, 2)
2139 |           }
2140 |         ]
2141 |       };
2142 |     } catch (error) {
2143 |       console.error('Error calling tool:', error);
2144 |       throw error;
2145 |     }
2146 |   } else if (request.params.name === 'get_bill_detail') {
2147 |     try {
2148 |       validateCredentials();
2149 |       
2150 |       const year = request.params.arguments?.year as string;
2151 |       const month = request.params.arguments?.month as string;
2152 |       
2153 |       if (!year || !month) {
2154 |         throw new Error('Year and month are required for billing details');
2155 |       }
2156 |       
2157 |       const billDetailInfo = await fetchFromSakuraCloud(`/bill/${year}${month}/detail`);
2158 |       
2159 |       return {
2160 |         content: [
2161 |           {
2162 |             type: 'text',
2163 |             text: JSON.stringify(billDetailInfo, null, 2)
2164 |           }
2165 |         ]
2166 |       };
2167 |     } catch (error) {
2168 |       console.error('Error calling tool:', error);
2169 |       throw error;
2170 |     }
2171 |   } else if (request.params.name === 'get_coupon_info') {
2172 |     try {
2173 |       validateCredentials();
2174 |       
2175 |       const couponId = request.params.arguments?.couponId as string;
2176 |       if (!couponId) {
2177 |         throw new Error('Coupon ID is required');
2178 |       }
2179 |       
2180 |       const couponInfo = await fetchFromSakuraCloud(`/coupon/${couponId}`);
2181 |       
2182 |       return {
2183 |         content: [
2184 |           {
2185 |             type: 'text',
2186 |             text: JSON.stringify(couponInfo, null, 2)
2187 |           }
2188 |         ]
2189 |       };
2190 |     } catch (error) {
2191 |       console.error('Error calling tool:', error);
2192 |       throw error;
2193 |     }
2194 |   } else if (request.params.name === 'get_privatehost_info') {
2195 |     try {
2196 |       validateCredentials();
2197 |       
2198 |       const privateHostId = request.params.arguments?.privateHostId as string;
2199 |       if (!privateHostId) {
2200 |         throw new Error('Private Host ID is required');
2201 |       }
2202 |       
2203 |       const privateHostInfo = await fetchFromSakuraCloud(`/privatehost/${privateHostId}`);
2204 |       
2205 |       return {
2206 |         content: [
2207 |           {
2208 |             type: 'text',
2209 |             text: JSON.stringify(privateHostInfo, null, 2)
2210 |           }
2211 |         ]
2212 |       };
2213 |     } catch (error) {
2214 |       console.error('Error calling tool:', error);
2215 |       throw error;
2216 |     }
2217 |   } else if (request.params.name === 'get_public_price') {
2218 |     try {
2219 |       // No authentication needed for public price API
2220 |       const priceData = await fetchFromSakuraCloud('/public/price', true);
2221 |       
2222 |       return {
2223 |         content: [
2224 |           {
2225 |             type: 'text',
2226 |             text: JSON.stringify(priceData, null, 2)
2227 |           }
2228 |         ]
2229 |       };
2230 |     } catch (error) {
2231 |       console.error('Error calling tool:', error);
2232 |       throw error;
2233 |     }
2234 |   } else if (request.params.name === 'get_apprun_list') {
2235 |     try {
2236 |       validateCredentials();
2237 |       
2238 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2239 |       const appRunList = await fetchFromAppRunAPI('/applications');
2240 |       
2241 |       return {
2242 |         content: [
2243 |           {
2244 |             type: 'text',
2245 |             text: JSON.stringify(appRunList, null, 2)
2246 |           }
2247 |         ]
2248 |       };
2249 |     } catch (error) {
2250 |       console.error('Error calling tool:', error);
2251 |       throw error;
2252 |     }
2253 |   } else if (request.params.name === 'get_apprun_info') {
2254 |     try {
2255 |       validateCredentials();
2256 |       
2257 |       const appId = request.params.arguments?.appId as string;
2258 |       if (!appId) {
2259 |         throw new Error('AppRun application ID is required');
2260 |       }
2261 |       
2262 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2263 |       const appRunInfo = await fetchFromAppRunAPI(`/applications/${appId}`);
2264 |       
2265 |       return {
2266 |         content: [
2267 |           {
2268 |             type: 'text',
2269 |             text: JSON.stringify(appRunInfo, null, 2)
2270 |           }
2271 |         ]
2272 |       };
2273 |     } catch (error) {
2274 |       console.error('Error calling tool:', error);
2275 |       throw error;
2276 |     }
2277 |   } else if (request.params.name === 'create_apprun') {
2278 |     try {
2279 |       validateCredentials();
2280 |       
2281 |       const name = request.params.arguments?.name as string;
2282 |       const description = request.params.arguments?.description as string || '';
2283 |       const dockerImage = request.params.arguments?.dockerImage as string;
2284 |       const planId = request.params.arguments?.planId as string;
2285 |       const environment = request.params.arguments?.environment as Array<{key: string, value: string}> || [];
2286 |       
2287 |       if (!name || !dockerImage || !planId) {
2288 |         throw new Error('Name, Docker image, and plan ID are required');
2289 |       }
2290 |       
2291 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2292 |       
2293 |       const createBody = {
2294 |         name: name,
2295 |         description: description,
2296 |         planId: planId,
2297 |         image: dockerImage,
2298 |         environment: environment.map(env => ({ key: env.key, value: env.value })),
2299 |       };
2300 |       
2301 |       const createResult = await fetchFromAppRunAPI('/applications', 'POST', createBody);
2302 |       
2303 |       return {
2304 |         content: [
2305 |           {
2306 |             type: 'text',
2307 |             text: JSON.stringify(createResult, null, 2)
2308 |           }
2309 |         ]
2310 |       };
2311 |     } catch (error) {
2312 |       console.error('Error calling tool:', error);
2313 |       throw error;
2314 |     }
2315 |   } else if (request.params.name === 'delete_apprun') {
2316 |     try {
2317 |       validateCredentials();
2318 |       
2319 |       const appId = request.params.arguments?.appId as string;
2320 |       if (!appId) {
2321 |         throw new Error('AppRun application ID is required');
2322 |       }
2323 |       
2324 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2325 |       const deleteResult = await fetchFromAppRunAPI(`/applications/${appId}`, 'DELETE');
2326 |       
2327 |       return {
2328 |         content: [
2329 |           {
2330 |             type: 'text',
2331 |             text: JSON.stringify(deleteResult, null, 2)
2332 |           }
2333 |         ]
2334 |       };
2335 |     } catch (error) {
2336 |       console.error('Error calling tool:', error);
2337 |       throw error;
2338 |     }
2339 |   } else if (request.params.name === 'start_apprun') {
2340 |     try {
2341 |       validateCredentials();
2342 |       
2343 |       const appId = request.params.arguments?.appId as string;
2344 |       if (!appId) {
2345 |         throw new Error('AppRun application ID is required');
2346 |       }
2347 |       
2348 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2349 |       const startResult = await fetchFromAppRunAPI(`/applications/${appId}/start`, 'POST');
2350 |       
2351 |       return {
2352 |         content: [
2353 |           {
2354 |             type: 'text',
2355 |             text: JSON.stringify(startResult, null, 2)
2356 |           }
2357 |         ]
2358 |       };
2359 |     } catch (error) {
2360 |       console.error('Error calling tool:', error);
2361 |       throw error;
2362 |     }
2363 |   } else if (request.params.name === 'stop_apprun') {
2364 |     try {
2365 |       validateCredentials();
2366 |       
2367 |       const appId = request.params.arguments?.appId as string;
2368 |       if (!appId) {
2369 |         throw new Error('AppRun application ID is required');
2370 |       }
2371 |       
2372 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2373 |       const stopResult = await fetchFromAppRunAPI(`/applications/${appId}/stop`, 'POST');
2374 |       
2375 |       return {
2376 |         content: [
2377 |           {
2378 |             type: 'text',
2379 |             text: JSON.stringify(stopResult, null, 2)
2380 |           }
2381 |         ]
2382 |       };
2383 |     } catch (error) {
2384 |       console.error('Error calling tool:', error);
2385 |       throw error;
2386 |     }
2387 |   } else if (request.params.name === 'update_apprun') {
2388 |     try {
2389 |       validateCredentials();
2390 |       
2391 |       const appId = request.params.arguments?.appId as string;
2392 |       if (!appId) {
2393 |         throw new Error('AppRun application ID is required');
2394 |       }
2395 |       
2396 |       // Get current application data first
2397 |       const currentApp = await fetchFromAppRunAPI(`/applications/${appId}`);
2398 |       
2399 |       // Extract current values
2400 |       const currentName = currentApp.name || '';
2401 |       const currentDescription = currentApp.description || '';
2402 |       const currentPlanId = currentApp.planId || '';
2403 |       const currentDockerImage = currentApp.image || '';
2404 |       const currentEnvironment = currentApp.environment || [];
2405 |       
2406 |       // Get update values from request or use current values
2407 |       const name = request.params.arguments?.name as string || currentName;
2408 |       const description = request.params.arguments?.description as string || currentDescription;
2409 |       const planId = request.params.arguments?.planId as string || currentPlanId;
2410 |       const dockerImage = request.params.arguments?.dockerImage as string || currentDockerImage;
2411 |       const environment = request.params.arguments?.environment as Array<{key: string, value: string}> || 
2412 |         currentEnvironment.map((env: {Key: string, Value: string}) => ({ key: env.Key, value: env.Value }));
2413 |       
2414 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2415 |       
2416 |       const updateBody = {
2417 |         name: name,
2418 |         description: description,
2419 |         planId: planId,
2420 |         image: dockerImage,
2421 |         environment: environment.map(env => ({ key: env.key, value: env.value })),
2422 |       };
2423 |       
2424 |       const updateResult = await fetchFromAppRunAPI(`/applications/${appId}`, 'PUT', updateBody);
2425 |       
2426 |       return {
2427 |         content: [
2428 |           {
2429 |             type: 'text',
2430 |             text: JSON.stringify(updateResult, null, 2)
2431 |           }
2432 |         ]
2433 |       };
2434 |     } catch (error) {
2435 |       console.error('Error calling tool:', error);
2436 |       throw error;
2437 |     }
2438 |   } else if (request.params.name === 'get_apprun_logs') {
2439 |     try {
2440 |       validateCredentials();
2441 |       
2442 |       const appId = request.params.arguments?.appId as string;
2443 |       if (!appId) {
2444 |         throw new Error('AppRun application ID is required');
2445 |       }
2446 |       
2447 |       const offset = request.params.arguments?.offset as number || 0;
2448 |       const limit = request.params.arguments?.limit as number || 100;
2449 |       
2450 |       const zone = request.params.arguments?.zone as string || DEFAULT_ZONE;
2451 |       const logsQuery = `/applications/${appId}/logs?offset=${offset}&limit=${limit}`;
2452 |       const logs = await fetchFromAppRunAPI(logsQuery);
2453 |       
2454 |       return {
2455 |         content: [
2456 |           {
2457 |             type: 'text',
2458 |             text: JSON.stringify(logs, null, 2)
2459 |           }
2460 |         ]
2461 |       };
2462 |     } catch (error) {
2463 |       console.error('Error calling tool:', error);
2464 |       throw error;
2465 |     }
2466 |   }
2467 |   
2468 |   throw new Error(`Tool not found: ${request.params.name}`);
2469 | });
2470 | 
2471 | async function main() {
2472 |   const transport = new StdioServerTransport();
2473 |   await server.connect(transport);
2474 |   console.error("Sacloud MCP Server running on stdio");
2475 | }
2476 | 
2477 | main().catch((error) => {
2478 |   console.error("Fatal error in main():", error);
2479 |   process.exit(1);
2480 | });
```