myaidev-method 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +0 -1
- package/.env.example +5 -4
- package/CHANGELOG.md +2 -2
- package/CONTENT_CREATION_GUIDE.md +489 -3211
- package/DEVELOPER_USE_CASES.md +1 -1
- package/MODULAR_INSTALLATION.md +2 -2
- package/README.md +39 -33
- package/TECHNICAL_ARCHITECTURE.md +1 -1
- package/USER_GUIDE.md +242 -190
- package/agents/content-editor-agent.md +90 -0
- package/agents/content-planner-agent.md +97 -0
- package/agents/content-research-agent.md +62 -0
- package/agents/content-seo-agent.md +101 -0
- package/agents/content-writer-agent.md +69 -0
- package/agents/infographic-analyzer-agent.md +63 -0
- package/agents/infographic-designer-agent.md +72 -0
- package/bin/cli.js +776 -422
- package/{content-rules.example.md → content-rules-example.md} +2 -2
- package/dist/mcp/health-check.js +82 -68
- package/dist/mcp/mcp-config.json +8 -0
- package/dist/mcp/openstack-server.js +1746 -1262
- package/dist/server/.tsbuildinfo +1 -1
- package/extension.json +21 -4
- package/package.json +181 -184
- package/skills/company-config/SKILL.md +133 -0
- package/skills/configure/SKILL.md +1 -1
- package/skills/myai-configurator/SKILL.md +77 -0
- package/skills/myai-configurator/content-creation-configurator/SKILL.md +516 -0
- package/skills/myai-configurator/content-maintenance-configurator/SKILL.md +397 -0
- package/skills/myai-content-enrichment/SKILL.md +114 -0
- package/skills/myai-content-ideation/SKILL.md +288 -0
- package/skills/myai-content-ideation/evals/evals.json +182 -0
- package/skills/myai-content-production-coordinator/SKILL.md +946 -0
- package/skills/{content-rules-setup → myai-content-rules-setup}/SKILL.md +1 -1
- package/skills/{content-verifier → myai-content-verifier}/SKILL.md +1 -1
- package/skills/myai-content-writer/SKILL.md +333 -0
- package/skills/{infographic → myai-infographic}/SKILL.md +1 -1
- package/skills/myai-proprietary-content-verifier/SKILL.md +175 -0
- package/skills/myai-proprietary-content-verifier/evals/evals.json +36 -0
- package/skills/myai-skill-builder/SKILL.md +699 -0
- package/skills/myai-skill-builder/agents/analyzer-agent.md +137 -0
- package/skills/myai-skill-builder/agents/comparator-agent.md +77 -0
- package/skills/myai-skill-builder/agents/grader-agent.md +103 -0
- package/skills/myai-skill-builder/assets/eval_review.html +131 -0
- package/skills/myai-skill-builder/references/schemas.md +211 -0
- package/skills/myai-skill-builder/scripts/aggregate_benchmark.py +190 -0
- package/skills/myai-skill-builder/scripts/generate_review.py +381 -0
- package/skills/myai-skill-builder/scripts/package_skill.py +91 -0
- package/skills/myai-skill-builder/scripts/run_eval.py +105 -0
- package/skills/myai-skill-builder/scripts/run_loop.py +211 -0
- package/skills/myai-skill-builder/scripts/utils.py +123 -0
- package/skills/myai-visual-generator/SKILL.md +125 -0
- package/skills/myai-visual-generator/evals/evals.json +155 -0
- package/skills/myai-visual-generator/references/infographic-pipeline.md +73 -0
- package/skills/myai-visual-generator/references/research-visuals.md +57 -0
- package/skills/myai-visual-generator/references/services.md +89 -0
- package/skills/myai-visual-generator/scripts/visual-generation-utils.js +1272 -0
- package/skills/myaidev-figma/SKILL.md +212 -0
- package/skills/myaidev-figma/capture.js +133 -0
- package/skills/myaidev-figma/crawl.js +130 -0
- package/skills/myaidev-figma-configure/SKILL.md +130 -0
- package/skills/openstack-manager/SKILL.md +1 -1
- package/skills/payloadcms-publisher/SKILL.md +141 -77
- package/skills/payloadcms-publisher/references/field-mapping.md +142 -0
- package/skills/payloadcms-publisher/references/lexical-format.md +97 -0
- package/skills/security-auditor/SKILL.md +1 -1
- package/src/cli/commands/addon.js +105 -7
- package/src/config/workflows.js +172 -228
- package/src/lib/ascii-banner.js +197 -182
- package/src/lib/{content-coordinator.js → content-production-coordinator.js} +649 -459
- package/src/lib/installation-detector.js +93 -59
- package/src/lib/payloadcms-utils.js +285 -510
- package/src/lib/workflow-installer.js +55 -0
- package/src/mcp/health-check.js +82 -68
- package/src/mcp/openstack-server.js +1746 -1262
- package/src/scripts/configure-visual-apis.js +224 -173
- package/src/scripts/configure-wordpress-mcp.js +96 -66
- package/src/scripts/init/install.js +109 -85
- package/src/scripts/init-project.js +138 -67
- package/src/scripts/utils/write-content.js +67 -52
- package/src/scripts/wordpress/publish-to-wordpress.js +128 -128
- package/src/templates/claude/CLAUDE.md +19 -12
- package/hooks/hooks.json +0 -26
- package/skills/content-coordinator/SKILL.md +0 -130
- package/skills/content-enrichment/SKILL.md +0 -80
- package/skills/content-writer/SKILL.md +0 -285
- package/skills/skill-builder/SKILL.md +0 -417
- package/skills/visual-generator/SKILL.md +0 -140
- /package/skills/{content-writer → myai-content-writer}/agents/editor-agent.md +0 -0
- /package/skills/{content-writer → myai-content-writer}/agents/planner-agent.md +0 -0
- /package/skills/{content-writer → myai-content-writer}/agents/research-agent.md +0 -0
- /package/skills/{content-writer → myai-content-writer}/agents/seo-agent.md +0 -0
- /package/skills/{content-writer → myai-content-writer}/agents/visual-planner-agent.md +0 -0
- /package/skills/{content-writer → myai-content-writer}/agents/writer-agent.md +0 -0
|
@@ -29,7 +29,7 @@ const DEFAULT_CLOUD_INIT = process.env.CLOUD_INIT;
|
|
|
29
29
|
const server = new McpServer({
|
|
30
30
|
name: "openstack-mcp-server",
|
|
31
31
|
version: "1.0.0",
|
|
32
|
-
description: "OpenStack MCP Server for VM management and orchestration"
|
|
32
|
+
description: "OpenStack MCP Server for VM management and orchestration",
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// Session storage for tracking operations
|
|
@@ -56,7 +56,7 @@ function getOpenStackEnv() {
|
|
|
56
56
|
OS_AUTH_VERSION: OS_IDENTITY_API_VERSION,
|
|
57
57
|
OS_ENDPOINT_TYPE: "publicURL",
|
|
58
58
|
OS_INTERFACE: "publicURL",
|
|
59
|
-
OS_NO_CACHE: "1"
|
|
59
|
+
OS_NO_CACHE: "1",
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -67,13 +67,17 @@ async function runOpenStackCommand(args, timeout = 120000) {
|
|
|
67
67
|
|
|
68
68
|
// Check for required environment variables
|
|
69
69
|
if (!OS_AUTH_URL || !OS_USERNAME || !OS_PASSWORD || !OS_PROJECT_ID) {
|
|
70
|
-
reject(
|
|
70
|
+
reject(
|
|
71
|
+
new Error(
|
|
72
|
+
"Missing required OpenStack environment variables. Please configure using /myai-configurator openstack",
|
|
73
|
+
),
|
|
74
|
+
);
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
const proc = spawn("openstack", args, {
|
|
75
79
|
env,
|
|
76
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
80
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
let stdout = "";
|
|
@@ -95,7 +99,11 @@ async function runOpenStackCommand(args, timeout = 120000) {
|
|
|
95
99
|
proc.on("close", (code) => {
|
|
96
100
|
clearTimeout(timeoutId);
|
|
97
101
|
if (code === 0) {
|
|
98
|
-
resolve({
|
|
102
|
+
resolve({
|
|
103
|
+
success: true,
|
|
104
|
+
output: stdout.trim(),
|
|
105
|
+
stderr: stderr.trim(),
|
|
106
|
+
});
|
|
99
107
|
} else {
|
|
100
108
|
reject(new Error(stderr || `Command failed with exit code ${code}`));
|
|
101
109
|
}
|
|
@@ -110,15 +118,21 @@ async function runOpenStackCommand(args, timeout = 120000) {
|
|
|
110
118
|
|
|
111
119
|
// Parse OpenStack table output to JSON
|
|
112
120
|
function parseTableOutput(output) {
|
|
113
|
-
const lines = output
|
|
121
|
+
const lines = output
|
|
122
|
+
.split("\n")
|
|
123
|
+
.filter((line) => line.trim() && !line.startsWith("+"));
|
|
114
124
|
if (lines.length < 2) return [];
|
|
115
125
|
|
|
116
|
-
const headers = lines[0]
|
|
117
|
-
.
|
|
118
|
-
.
|
|
126
|
+
const headers = lines[0]
|
|
127
|
+
.split("|")
|
|
128
|
+
.map((h) => h.trim().toLowerCase().replace(/\s+/g, "_"))
|
|
129
|
+
.filter((h) => h);
|
|
119
130
|
|
|
120
|
-
return lines.slice(1).map(line => {
|
|
121
|
-
const values = line
|
|
131
|
+
return lines.slice(1).map((line) => {
|
|
132
|
+
const values = line
|
|
133
|
+
.split("|")
|
|
134
|
+
.map((v) => v.trim())
|
|
135
|
+
.filter((v) => v !== "");
|
|
122
136
|
const obj = {};
|
|
123
137
|
headers.forEach((header, i) => {
|
|
124
138
|
obj[header] = values[i] || "";
|
|
@@ -132,7 +146,7 @@ async function fetchFromUrl(url) {
|
|
|
132
146
|
return new Promise((resolve, reject) => {
|
|
133
147
|
// Convert GitHub Gist URL to raw URL if needed
|
|
134
148
|
let fetchUrl = url;
|
|
135
|
-
if (url.includes(
|
|
149
|
+
if (url.includes("gist.github.com") && !url.includes("/raw")) {
|
|
136
150
|
// Extract gist ID and convert to raw URL
|
|
137
151
|
const gistMatch = url.match(/gist\.github\.com\/([^\/]+)\/([a-f0-9]+)/);
|
|
138
152
|
if (gistMatch) {
|
|
@@ -140,33 +154,45 @@ async function fetchFromUrl(url) {
|
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
|
|
143
|
-
const protocol = fetchUrl.startsWith(
|
|
157
|
+
const protocol = fetchUrl.startsWith("https") ? https : http;
|
|
144
158
|
|
|
145
|
-
const request = protocol.get(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
const request = protocol.get(
|
|
160
|
+
fetchUrl,
|
|
161
|
+
{
|
|
162
|
+
headers: {
|
|
163
|
+
"User-Agent": "MyAIDev-Method-OpenStack-MCP/1.0",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
(response) => {
|
|
167
|
+
// Handle redirects
|
|
168
|
+
if (
|
|
169
|
+
response.statusCode >= 300 &&
|
|
170
|
+
response.statusCode < 400 &&
|
|
171
|
+
response.headers.location
|
|
172
|
+
) {
|
|
173
|
+
fetchFromUrl(response.headers.location).then(resolve).catch(reject);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
155
176
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
if (response.statusCode !== 200) {
|
|
178
|
+
reject(
|
|
179
|
+
new Error(
|
|
180
|
+
`HTTP ${response.statusCode}: Failed to fetch ${fetchUrl}`,
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
160
185
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
186
|
+
let data = "";
|
|
187
|
+
response.on("data", (chunk) => (data += chunk));
|
|
188
|
+
response.on("end", () => resolve(data));
|
|
189
|
+
},
|
|
190
|
+
);
|
|
165
191
|
|
|
166
|
-
request.on(
|
|
192
|
+
request.on("error", reject);
|
|
167
193
|
request.setTimeout(30000, () => {
|
|
168
194
|
request.destroy();
|
|
169
|
-
reject(new Error(
|
|
195
|
+
reject(new Error("Request timeout"));
|
|
170
196
|
});
|
|
171
197
|
});
|
|
172
198
|
}
|
|
@@ -177,7 +203,7 @@ async function resolveCloudInit(params) {
|
|
|
177
203
|
|
|
178
204
|
// 1. Explicit user_data content provided
|
|
179
205
|
if (params.user_data) {
|
|
180
|
-
return { content: params.user_data, source:
|
|
206
|
+
return { content: params.user_data, source: "inline" };
|
|
181
207
|
}
|
|
182
208
|
|
|
183
209
|
// 2. Cloud-init URL provided
|
|
@@ -193,7 +219,7 @@ async function resolveCloudInit(params) {
|
|
|
193
219
|
// 3. Cloud-init file path provided
|
|
194
220
|
if (params.cloud_init_file) {
|
|
195
221
|
try {
|
|
196
|
-
const content = fs.readFileSync(params.cloud_init_file,
|
|
222
|
+
const content = fs.readFileSync(params.cloud_init_file, "utf8");
|
|
197
223
|
return { content, source: `file:${params.cloud_init_file}` };
|
|
198
224
|
} catch (error) {
|
|
199
225
|
throw new Error(`Failed to read cloud-init file: ${error.message}`);
|
|
@@ -204,11 +230,14 @@ async function resolveCloudInit(params) {
|
|
|
204
230
|
if (params.use_default_cloud_init && DEFAULT_CLOUD_INIT) {
|
|
205
231
|
try {
|
|
206
232
|
// Check if it's a URL or file path
|
|
207
|
-
if (
|
|
233
|
+
if (
|
|
234
|
+
DEFAULT_CLOUD_INIT.startsWith("http://") ||
|
|
235
|
+
DEFAULT_CLOUD_INIT.startsWith("https://")
|
|
236
|
+
) {
|
|
208
237
|
const content = await fetchFromUrl(DEFAULT_CLOUD_INIT);
|
|
209
238
|
return { content, source: `default_url:${DEFAULT_CLOUD_INIT}` };
|
|
210
239
|
} else {
|
|
211
|
-
const content = fs.readFileSync(DEFAULT_CLOUD_INIT,
|
|
240
|
+
const content = fs.readFileSync(DEFAULT_CLOUD_INIT, "utf8");
|
|
212
241
|
return { content, source: `default_file:${DEFAULT_CLOUD_INIT}` };
|
|
213
242
|
}
|
|
214
243
|
} catch (error) {
|
|
@@ -221,1369 +250,1825 @@ async function resolveCloudInit(params) {
|
|
|
221
250
|
}
|
|
222
251
|
|
|
223
252
|
// Cloud-init fetch and preview tool
|
|
224
|
-
server.registerTool(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
253
|
+
server.registerTool(
|
|
254
|
+
"os_cloud_init_fetch",
|
|
255
|
+
{
|
|
256
|
+
title: "Fetch Cloud-Init Configuration",
|
|
257
|
+
description:
|
|
258
|
+
"Fetch and preview cloud-init configuration from URL, file, or default",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
url: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description:
|
|
265
|
+
"URL to fetch cloud-init from (supports GitHub Gist URLs)",
|
|
266
|
+
},
|
|
267
|
+
file: {
|
|
268
|
+
type: "string",
|
|
269
|
+
description: "Local file path to cloud-init script",
|
|
270
|
+
},
|
|
271
|
+
use_default: {
|
|
272
|
+
type: "boolean",
|
|
273
|
+
description: "Fetch from the default CLOUD_INIT environment variable",
|
|
274
|
+
default: false,
|
|
275
|
+
},
|
|
237
276
|
},
|
|
238
|
-
|
|
239
|
-
type: "boolean",
|
|
240
|
-
description: "Fetch from the default CLOUD_INIT environment variable",
|
|
241
|
-
default: false
|
|
242
|
-
}
|
|
277
|
+
additionalProperties: false,
|
|
243
278
|
},
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
279
|
+
},
|
|
280
|
+
async (params) => {
|
|
281
|
+
try {
|
|
282
|
+
let content = null;
|
|
283
|
+
let source = null;
|
|
284
|
+
|
|
285
|
+
if (params.url) {
|
|
286
|
+
content = await fetchFromUrl(params.url);
|
|
287
|
+
source = `url:${params.url}`;
|
|
288
|
+
} else if (params.file) {
|
|
289
|
+
content = fs.readFileSync(params.file, "utf8");
|
|
290
|
+
source = `file:${params.file}`;
|
|
291
|
+
} else if (params.use_default && DEFAULT_CLOUD_INIT) {
|
|
292
|
+
if (
|
|
293
|
+
DEFAULT_CLOUD_INIT.startsWith("http://") ||
|
|
294
|
+
DEFAULT_CLOUD_INIT.startsWith("https://")
|
|
295
|
+
) {
|
|
296
|
+
content = await fetchFromUrl(DEFAULT_CLOUD_INIT);
|
|
297
|
+
source = `default_url:${DEFAULT_CLOUD_INIT}`;
|
|
298
|
+
} else {
|
|
299
|
+
content = fs.readFileSync(DEFAULT_CLOUD_INIT, "utf8");
|
|
300
|
+
source = `default_file:${DEFAULT_CLOUD_INIT}`;
|
|
301
|
+
}
|
|
302
|
+
} else if (params.use_default && !DEFAULT_CLOUD_INIT) {
|
|
303
|
+
return {
|
|
304
|
+
content: [
|
|
305
|
+
{
|
|
306
|
+
type: "text",
|
|
307
|
+
text: JSON.stringify(
|
|
308
|
+
{
|
|
309
|
+
success: false,
|
|
310
|
+
error: "No default CLOUD_INIT configured in environment",
|
|
311
|
+
},
|
|
312
|
+
null,
|
|
313
|
+
2,
|
|
314
|
+
),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
};
|
|
261
318
|
} else {
|
|
262
|
-
|
|
263
|
-
|
|
319
|
+
return {
|
|
320
|
+
content: [
|
|
321
|
+
{
|
|
322
|
+
type: "text",
|
|
323
|
+
text: JSON.stringify(
|
|
324
|
+
{
|
|
325
|
+
success: false,
|
|
326
|
+
error: "Please provide url, file, or set use_default to true",
|
|
327
|
+
},
|
|
328
|
+
null,
|
|
329
|
+
2,
|
|
330
|
+
),
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
264
334
|
}
|
|
265
|
-
|
|
335
|
+
|
|
266
336
|
return {
|
|
267
|
-
content: [
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
content: [
|
|
338
|
+
{
|
|
339
|
+
type: "text",
|
|
340
|
+
text: JSON.stringify(
|
|
341
|
+
{
|
|
342
|
+
success: true,
|
|
343
|
+
source,
|
|
344
|
+
content_length: content.length,
|
|
345
|
+
preview:
|
|
346
|
+
content.substring(0, 2000) +
|
|
347
|
+
(content.length > 2000 ? "\n... (truncated)" : ""),
|
|
348
|
+
full_content: content,
|
|
349
|
+
},
|
|
350
|
+
null,
|
|
351
|
+
2,
|
|
352
|
+
),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
274
355
|
};
|
|
275
|
-
}
|
|
356
|
+
} catch (error) {
|
|
276
357
|
return {
|
|
277
|
-
content: [
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
358
|
+
content: [
|
|
359
|
+
{
|
|
360
|
+
type: "text",
|
|
361
|
+
text: JSON.stringify(
|
|
362
|
+
{
|
|
363
|
+
success: false,
|
|
364
|
+
error: error.message,
|
|
365
|
+
},
|
|
366
|
+
null,
|
|
367
|
+
2,
|
|
368
|
+
),
|
|
369
|
+
},
|
|
370
|
+
],
|
|
284
371
|
};
|
|
285
372
|
}
|
|
373
|
+
},
|
|
374
|
+
);
|
|
286
375
|
|
|
376
|
+
// Get default cloud-init info
|
|
377
|
+
server.registerTool(
|
|
378
|
+
"os_cloud_init_info",
|
|
379
|
+
{
|
|
380
|
+
title: "Cloud-Init Info",
|
|
381
|
+
description: "Get information about configured cloud-init defaults",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties: {},
|
|
385
|
+
additionalProperties: false,
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
async () => {
|
|
287
389
|
return {
|
|
288
|
-
content: [
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
390
|
+
content: [
|
|
391
|
+
{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: JSON.stringify(
|
|
394
|
+
{
|
|
395
|
+
success: true,
|
|
396
|
+
default_cloud_init: DEFAULT_CLOUD_INIT || null,
|
|
397
|
+
configured: !!DEFAULT_CLOUD_INIT,
|
|
398
|
+
type: DEFAULT_CLOUD_INIT
|
|
399
|
+
? DEFAULT_CLOUD_INIT.startsWith("http")
|
|
400
|
+
? "url"
|
|
401
|
+
: "file"
|
|
402
|
+
: null,
|
|
403
|
+
usage: {
|
|
404
|
+
use_default:
|
|
405
|
+
"Set use_default_cloud_init: true when creating a server",
|
|
406
|
+
custom_url: "Provide cloud_init_url parameter with any URL",
|
|
407
|
+
custom_file:
|
|
408
|
+
"Provide cloud_init_file parameter with local path",
|
|
409
|
+
inline: "Provide user_data parameter with YAML content",
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
null,
|
|
413
|
+
2,
|
|
414
|
+
),
|
|
415
|
+
},
|
|
416
|
+
],
|
|
308
417
|
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// Get default cloud-init info
|
|
313
|
-
server.registerTool("os_cloud_init_info", {
|
|
314
|
-
title: "Cloud-Init Info",
|
|
315
|
-
description: "Get information about configured cloud-init defaults",
|
|
316
|
-
inputSchema: {
|
|
317
|
-
type: "object",
|
|
318
|
-
properties: {},
|
|
319
|
-
additionalProperties: false
|
|
320
|
-
}
|
|
321
|
-
}, async () => {
|
|
322
|
-
return {
|
|
323
|
-
content: [{
|
|
324
|
-
type: "text",
|
|
325
|
-
text: JSON.stringify({
|
|
326
|
-
success: true,
|
|
327
|
-
default_cloud_init: DEFAULT_CLOUD_INIT || null,
|
|
328
|
-
configured: !!DEFAULT_CLOUD_INIT,
|
|
329
|
-
type: DEFAULT_CLOUD_INIT ?
|
|
330
|
-
(DEFAULT_CLOUD_INIT.startsWith('http') ? 'url' : 'file') :
|
|
331
|
-
null,
|
|
332
|
-
usage: {
|
|
333
|
-
use_default: "Set use_default_cloud_init: true when creating a server",
|
|
334
|
-
custom_url: "Provide cloud_init_url parameter with any URL",
|
|
335
|
-
custom_file: "Provide cloud_init_file parameter with local path",
|
|
336
|
-
inline: "Provide user_data parameter with YAML content"
|
|
337
|
-
}
|
|
338
|
-
}, null, 2)
|
|
339
|
-
}]
|
|
340
|
-
};
|
|
341
|
-
});
|
|
418
|
+
},
|
|
419
|
+
);
|
|
342
420
|
|
|
343
421
|
// Session management tool
|
|
344
|
-
server.registerTool(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
description:
|
|
353
|
-
|
|
422
|
+
server.registerTool(
|
|
423
|
+
"os_session_create",
|
|
424
|
+
{
|
|
425
|
+
title: "Create OpenStack Session",
|
|
426
|
+
description: "Create a new session for tracking OpenStack operations",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
description: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "Description of the session purpose",
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
additionalProperties: false,
|
|
354
436
|
},
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
});
|
|
437
|
+
},
|
|
438
|
+
async (params) => {
|
|
439
|
+
const sessionId = generateSessionId();
|
|
440
|
+
sessions.set(sessionId, {
|
|
441
|
+
id: sessionId,
|
|
442
|
+
description: params.description || "OpenStack operations session",
|
|
443
|
+
created: new Date().toISOString(),
|
|
444
|
+
operations: [],
|
|
445
|
+
});
|
|
365
446
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: JSON.stringify(
|
|
452
|
+
{
|
|
453
|
+
success: true,
|
|
454
|
+
session_id: sessionId,
|
|
455
|
+
message: "Session created successfully",
|
|
456
|
+
},
|
|
457
|
+
null,
|
|
458
|
+
2,
|
|
459
|
+
),
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
);
|
|
377
465
|
|
|
378
466
|
// Health check tool
|
|
379
|
-
server.registerTool(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
467
|
+
server.registerTool(
|
|
468
|
+
"os_health_check",
|
|
469
|
+
{
|
|
470
|
+
title: "OpenStack Health Check",
|
|
471
|
+
description: "Check OpenStack API connectivity and authentication",
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: "object",
|
|
474
|
+
properties: {},
|
|
475
|
+
additionalProperties: false,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
async () => {
|
|
479
|
+
try {
|
|
480
|
+
const result = await runOpenStackCommand([
|
|
481
|
+
"token",
|
|
482
|
+
"issue",
|
|
483
|
+
"-f",
|
|
484
|
+
"json",
|
|
485
|
+
]);
|
|
486
|
+
const tokenInfo = JSON.parse(result.output);
|
|
391
487
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
488
|
+
return {
|
|
489
|
+
content: [
|
|
490
|
+
{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: JSON.stringify(
|
|
493
|
+
{
|
|
494
|
+
success: true,
|
|
495
|
+
message: "OpenStack API is responding",
|
|
496
|
+
auth_url: OS_AUTH_URL,
|
|
497
|
+
project_id: OS_PROJECT_ID,
|
|
498
|
+
region: OS_REGION_NAME,
|
|
499
|
+
token_expires: tokenInfo.expires,
|
|
500
|
+
server_version: "1.0.0",
|
|
501
|
+
},
|
|
502
|
+
null,
|
|
503
|
+
2,
|
|
504
|
+
),
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
} catch (error) {
|
|
509
|
+
return {
|
|
510
|
+
content: [
|
|
511
|
+
{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: JSON.stringify(
|
|
514
|
+
{
|
|
515
|
+
success: false,
|
|
516
|
+
error: error.message,
|
|
517
|
+
suggestion:
|
|
518
|
+
"Check your OpenStack credentials. Run /myai-configure openstack to reconfigure.",
|
|
519
|
+
},
|
|
520
|
+
null,
|
|
521
|
+
2,
|
|
522
|
+
),
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
);
|
|
419
529
|
|
|
420
530
|
// List available images
|
|
421
|
-
server.registerTool(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
531
|
+
server.registerTool(
|
|
532
|
+
"os_image_list",
|
|
533
|
+
{
|
|
534
|
+
title: "List OpenStack Images",
|
|
535
|
+
description: "List available VM images in OpenStack",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
limit: {
|
|
540
|
+
type: "number",
|
|
541
|
+
description: "Maximum number of images to return",
|
|
542
|
+
default: 20,
|
|
543
|
+
},
|
|
544
|
+
status: {
|
|
545
|
+
type: "string",
|
|
546
|
+
enum: ["active", "queued", "saving", "deleted"],
|
|
547
|
+
description: "Filter by image status",
|
|
548
|
+
},
|
|
431
549
|
},
|
|
432
|
-
|
|
433
|
-
type: "string",
|
|
434
|
-
enum: ["active", "queued", "saving", "deleted"],
|
|
435
|
-
description: "Filter by image status"
|
|
436
|
-
}
|
|
550
|
+
additionalProperties: false,
|
|
437
551
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (params.status) args.push("--status", params.status);
|
|
552
|
+
},
|
|
553
|
+
async (params) => {
|
|
554
|
+
try {
|
|
555
|
+
const args = ["image", "list", "-f", "json"];
|
|
556
|
+
if (params.limit) args.push("--limit", params.limit.toString());
|
|
557
|
+
if (params.status) args.push("--status", params.status);
|
|
445
558
|
|
|
446
|
-
|
|
447
|
-
|
|
559
|
+
const result = await runOpenStackCommand(args);
|
|
560
|
+
const images = JSON.parse(result.output);
|
|
448
561
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
562
|
+
return {
|
|
563
|
+
content: [
|
|
564
|
+
{
|
|
565
|
+
type: "text",
|
|
566
|
+
text: JSON.stringify(
|
|
567
|
+
{
|
|
568
|
+
success: true,
|
|
569
|
+
count: images.length,
|
|
570
|
+
images: images.map((img) => ({
|
|
571
|
+
id: img.ID,
|
|
572
|
+
name: img.Name,
|
|
573
|
+
status: img.Status,
|
|
574
|
+
})),
|
|
575
|
+
},
|
|
576
|
+
null,
|
|
577
|
+
2,
|
|
578
|
+
),
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
};
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
content: [
|
|
585
|
+
{
|
|
586
|
+
type: "text",
|
|
587
|
+
text: JSON.stringify(
|
|
588
|
+
{
|
|
589
|
+
success: false,
|
|
590
|
+
error: error.message,
|
|
591
|
+
},
|
|
592
|
+
null,
|
|
593
|
+
2,
|
|
594
|
+
),
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
);
|
|
475
601
|
|
|
476
602
|
// List available flavors
|
|
477
|
-
server.registerTool(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
603
|
+
server.registerTool(
|
|
604
|
+
"os_flavor_list",
|
|
605
|
+
{
|
|
606
|
+
title: "List OpenStack Flavors",
|
|
607
|
+
description: "List available VM flavors (instance sizes) in OpenStack",
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: {},
|
|
611
|
+
additionalProperties: false,
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
async () => {
|
|
615
|
+
try {
|
|
616
|
+
const result = await runOpenStackCommand([
|
|
617
|
+
"flavor",
|
|
618
|
+
"list",
|
|
619
|
+
"-f",
|
|
620
|
+
"json",
|
|
621
|
+
]);
|
|
622
|
+
const flavors = JSON.parse(result.output);
|
|
489
623
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: JSON.stringify(
|
|
629
|
+
{
|
|
630
|
+
success: true,
|
|
631
|
+
count: flavors.length,
|
|
632
|
+
flavors: flavors.map((f) => ({
|
|
633
|
+
id: f.ID,
|
|
634
|
+
name: f.Name,
|
|
635
|
+
vcpus: f.VCPUs,
|
|
636
|
+
ram_mb: f.RAM,
|
|
637
|
+
disk_gb: f.Disk,
|
|
638
|
+
})),
|
|
639
|
+
},
|
|
640
|
+
null,
|
|
641
|
+
2,
|
|
642
|
+
),
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
return {
|
|
648
|
+
content: [
|
|
649
|
+
{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: JSON.stringify(
|
|
652
|
+
{
|
|
653
|
+
success: false,
|
|
654
|
+
error: error.message,
|
|
655
|
+
},
|
|
656
|
+
null,
|
|
657
|
+
2,
|
|
658
|
+
),
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
);
|
|
518
665
|
|
|
519
666
|
// List available networks
|
|
520
|
-
server.registerTool(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
667
|
+
server.registerTool(
|
|
668
|
+
"os_network_list",
|
|
669
|
+
{
|
|
670
|
+
title: "List OpenStack Networks",
|
|
671
|
+
description: "List available networks in OpenStack",
|
|
672
|
+
inputSchema: {
|
|
673
|
+
type: "object",
|
|
674
|
+
properties: {},
|
|
675
|
+
additionalProperties: false,
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
async () => {
|
|
679
|
+
try {
|
|
680
|
+
const result = await runOpenStackCommand([
|
|
681
|
+
"network",
|
|
682
|
+
"list",
|
|
683
|
+
"-f",
|
|
684
|
+
"json",
|
|
685
|
+
]);
|
|
686
|
+
const networks = JSON.parse(result.output);
|
|
532
687
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
688
|
+
return {
|
|
689
|
+
content: [
|
|
690
|
+
{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: JSON.stringify(
|
|
693
|
+
{
|
|
694
|
+
success: true,
|
|
695
|
+
count: networks.length,
|
|
696
|
+
networks: networks.map((n) => ({
|
|
697
|
+
id: n.ID,
|
|
698
|
+
name: n.Name,
|
|
699
|
+
subnets: n.Subnets,
|
|
700
|
+
})),
|
|
701
|
+
},
|
|
702
|
+
null,
|
|
703
|
+
2,
|
|
704
|
+
),
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
};
|
|
708
|
+
} catch (error) {
|
|
709
|
+
return {
|
|
710
|
+
content: [
|
|
711
|
+
{
|
|
712
|
+
type: "text",
|
|
713
|
+
text: JSON.stringify(
|
|
714
|
+
{
|
|
715
|
+
success: false,
|
|
716
|
+
error: error.message,
|
|
717
|
+
},
|
|
718
|
+
null,
|
|
719
|
+
2,
|
|
720
|
+
),
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
);
|
|
559
727
|
|
|
560
728
|
// List security groups
|
|
561
|
-
server.registerTool(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
729
|
+
server.registerTool(
|
|
730
|
+
"os_security_group_list",
|
|
731
|
+
{
|
|
732
|
+
title: "List Security Groups",
|
|
733
|
+
description: "List available security groups in OpenStack",
|
|
734
|
+
inputSchema: {
|
|
735
|
+
type: "object",
|
|
736
|
+
properties: {},
|
|
737
|
+
additionalProperties: false,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
async () => {
|
|
741
|
+
try {
|
|
742
|
+
const result = await runOpenStackCommand([
|
|
743
|
+
"security",
|
|
744
|
+
"group",
|
|
745
|
+
"list",
|
|
746
|
+
"-f",
|
|
747
|
+
"json",
|
|
748
|
+
]);
|
|
749
|
+
const groups = JSON.parse(result.output);
|
|
573
750
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
751
|
+
return {
|
|
752
|
+
content: [
|
|
753
|
+
{
|
|
754
|
+
type: "text",
|
|
755
|
+
text: JSON.stringify(
|
|
756
|
+
{
|
|
757
|
+
success: true,
|
|
758
|
+
count: groups.length,
|
|
759
|
+
security_groups: groups.map((g) => ({
|
|
760
|
+
id: g.ID,
|
|
761
|
+
name: g.Name,
|
|
762
|
+
description: g.Description,
|
|
763
|
+
})),
|
|
764
|
+
},
|
|
765
|
+
null,
|
|
766
|
+
2,
|
|
767
|
+
),
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
};
|
|
771
|
+
} catch (error) {
|
|
772
|
+
return {
|
|
773
|
+
content: [
|
|
774
|
+
{
|
|
775
|
+
type: "text",
|
|
776
|
+
text: JSON.stringify(
|
|
777
|
+
{
|
|
778
|
+
success: false,
|
|
779
|
+
error: error.message,
|
|
780
|
+
},
|
|
781
|
+
null,
|
|
782
|
+
2,
|
|
783
|
+
),
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
);
|
|
600
790
|
|
|
601
791
|
// List keypairs
|
|
602
|
-
server.registerTool(
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
792
|
+
server.registerTool(
|
|
793
|
+
"os_keypair_list",
|
|
794
|
+
{
|
|
795
|
+
title: "List SSH Keypairs",
|
|
796
|
+
description: "List available SSH keypairs in OpenStack",
|
|
797
|
+
inputSchema: {
|
|
798
|
+
type: "object",
|
|
799
|
+
properties: {},
|
|
800
|
+
additionalProperties: false,
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
async () => {
|
|
804
|
+
try {
|
|
805
|
+
const result = await runOpenStackCommand([
|
|
806
|
+
"keypair",
|
|
807
|
+
"list",
|
|
808
|
+
"-f",
|
|
809
|
+
"json",
|
|
810
|
+
]);
|
|
811
|
+
const keypairs = JSON.parse(result.output);
|
|
614
812
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
813
|
+
return {
|
|
814
|
+
content: [
|
|
815
|
+
{
|
|
816
|
+
type: "text",
|
|
817
|
+
text: JSON.stringify(
|
|
818
|
+
{
|
|
819
|
+
success: true,
|
|
820
|
+
count: keypairs.length,
|
|
821
|
+
keypairs: keypairs.map((k) => ({
|
|
822
|
+
name: k.Name,
|
|
823
|
+
fingerprint: k.Fingerprint,
|
|
824
|
+
type: k.Type,
|
|
825
|
+
})),
|
|
826
|
+
},
|
|
827
|
+
null,
|
|
828
|
+
2,
|
|
829
|
+
),
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
};
|
|
833
|
+
} catch (error) {
|
|
834
|
+
return {
|
|
835
|
+
content: [
|
|
836
|
+
{
|
|
837
|
+
type: "text",
|
|
838
|
+
text: JSON.stringify(
|
|
839
|
+
{
|
|
840
|
+
success: false,
|
|
841
|
+
error: error.message,
|
|
842
|
+
},
|
|
843
|
+
null,
|
|
844
|
+
2,
|
|
845
|
+
),
|
|
846
|
+
},
|
|
847
|
+
],
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
);
|
|
641
852
|
|
|
642
853
|
// Create keypair
|
|
643
|
-
server.registerTool(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
854
|
+
server.registerTool(
|
|
855
|
+
"os_keypair_create",
|
|
856
|
+
{
|
|
857
|
+
title: "Create SSH Keypair",
|
|
858
|
+
description: "Create a new SSH keypair for VM access",
|
|
859
|
+
inputSchema: {
|
|
860
|
+
type: "object",
|
|
861
|
+
properties: {
|
|
862
|
+
name: {
|
|
863
|
+
type: "string",
|
|
864
|
+
description: "Name for the keypair",
|
|
865
|
+
},
|
|
866
|
+
public_key_file: {
|
|
867
|
+
type: "string",
|
|
868
|
+
description:
|
|
869
|
+
"Path to existing public key file (optional, will generate if not provided)",
|
|
870
|
+
},
|
|
652
871
|
},
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
description: "Path to existing public key file (optional, will generate if not provided)"
|
|
656
|
-
}
|
|
872
|
+
required: ["name"],
|
|
873
|
+
additionalProperties: false,
|
|
657
874
|
},
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
try {
|
|
663
|
-
const args = ["keypair", "create", params.name];
|
|
875
|
+
},
|
|
876
|
+
async (params) => {
|
|
877
|
+
try {
|
|
878
|
+
const args = ["keypair", "create", params.name];
|
|
664
879
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
880
|
+
if (params.public_key_file) {
|
|
881
|
+
args.push("--public-key", params.public_key_file);
|
|
882
|
+
}
|
|
668
883
|
|
|
669
|
-
|
|
884
|
+
args.push("-f", "json");
|
|
670
885
|
|
|
671
|
-
|
|
672
|
-
|
|
886
|
+
const result = await runOpenStackCommand(args);
|
|
887
|
+
const keypair = JSON.parse(result.output);
|
|
673
888
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
889
|
+
const response = {
|
|
890
|
+
success: true,
|
|
891
|
+
keypair: {
|
|
892
|
+
name: keypair.name || params.name,
|
|
893
|
+
fingerprint: keypair.fingerprint,
|
|
894
|
+
},
|
|
895
|
+
message: "Keypair created successfully",
|
|
896
|
+
};
|
|
682
897
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
898
|
+
// If a new keypair was generated (no public key provided), include the private key
|
|
899
|
+
if (!params.public_key_file && keypair.private_key) {
|
|
900
|
+
response.private_key = keypair.private_key;
|
|
901
|
+
response.warning =
|
|
902
|
+
"Save this private key securely. It will not be shown again!";
|
|
903
|
+
}
|
|
688
904
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
}
|
|
905
|
+
return {
|
|
906
|
+
content: [
|
|
907
|
+
{
|
|
908
|
+
type: "text",
|
|
909
|
+
text: JSON.stringify(response, null, 2),
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
};
|
|
913
|
+
} catch (error) {
|
|
914
|
+
return {
|
|
915
|
+
content: [
|
|
916
|
+
{
|
|
917
|
+
type: "text",
|
|
918
|
+
text: JSON.stringify(
|
|
919
|
+
{
|
|
920
|
+
success: false,
|
|
921
|
+
error: error.message,
|
|
922
|
+
},
|
|
923
|
+
null,
|
|
924
|
+
2,
|
|
925
|
+
),
|
|
926
|
+
},
|
|
927
|
+
],
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
);
|
|
707
932
|
|
|
708
933
|
// List servers (VMs)
|
|
709
|
-
server.registerTool(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
934
|
+
server.registerTool(
|
|
935
|
+
"os_server_list",
|
|
936
|
+
{
|
|
937
|
+
title: "List Servers",
|
|
938
|
+
description: "List all VMs/servers in the project",
|
|
939
|
+
inputSchema: {
|
|
940
|
+
type: "object",
|
|
941
|
+
properties: {
|
|
942
|
+
status: {
|
|
943
|
+
type: "string",
|
|
944
|
+
enum: ["ACTIVE", "BUILD", "ERROR", "SHUTOFF", "SUSPENDED", "PAUSED"],
|
|
945
|
+
description: "Filter by server status",
|
|
946
|
+
},
|
|
947
|
+
name: {
|
|
948
|
+
type: "string",
|
|
949
|
+
description: "Filter by server name (partial match)",
|
|
950
|
+
},
|
|
719
951
|
},
|
|
720
|
-
|
|
721
|
-
type: "string",
|
|
722
|
-
description: "Filter by server name (partial match)"
|
|
723
|
-
}
|
|
952
|
+
additionalProperties: false,
|
|
724
953
|
},
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (params.name) args.push("--name", params.name);
|
|
954
|
+
},
|
|
955
|
+
async (params) => {
|
|
956
|
+
try {
|
|
957
|
+
const args = ["server", "list", "-f", "json"];
|
|
958
|
+
if (params.status) args.push("--status", params.status);
|
|
959
|
+
if (params.name) args.push("--name", params.name);
|
|
732
960
|
|
|
733
|
-
|
|
734
|
-
|
|
961
|
+
const result = await runOpenStackCommand(args);
|
|
962
|
+
const servers = JSON.parse(result.output);
|
|
735
963
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
964
|
+
return {
|
|
965
|
+
content: [
|
|
966
|
+
{
|
|
967
|
+
type: "text",
|
|
968
|
+
text: JSON.stringify(
|
|
969
|
+
{
|
|
970
|
+
success: true,
|
|
971
|
+
count: servers.length,
|
|
972
|
+
servers: servers.map((s) => ({
|
|
973
|
+
id: s.ID,
|
|
974
|
+
name: s.Name,
|
|
975
|
+
status: s.Status,
|
|
976
|
+
networks: s.Networks,
|
|
977
|
+
image: s.Image,
|
|
978
|
+
flavor: s.Flavor,
|
|
979
|
+
})),
|
|
980
|
+
},
|
|
981
|
+
null,
|
|
982
|
+
2,
|
|
983
|
+
),
|
|
984
|
+
},
|
|
985
|
+
],
|
|
986
|
+
};
|
|
987
|
+
} catch (error) {
|
|
988
|
+
return {
|
|
989
|
+
content: [
|
|
990
|
+
{
|
|
991
|
+
type: "text",
|
|
992
|
+
text: JSON.stringify(
|
|
993
|
+
{
|
|
994
|
+
success: false,
|
|
995
|
+
error: error.message,
|
|
996
|
+
},
|
|
997
|
+
null,
|
|
998
|
+
2,
|
|
999
|
+
),
|
|
1000
|
+
},
|
|
1001
|
+
],
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
);
|
|
765
1006
|
|
|
766
1007
|
// Create server (VM)
|
|
767
|
-
server.registerTool(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1008
|
+
server.registerTool(
|
|
1009
|
+
"os_server_create",
|
|
1010
|
+
{
|
|
1011
|
+
title: "Create Server",
|
|
1012
|
+
description:
|
|
1013
|
+
"Create a new VM/server in OpenStack with optional cloud-init configuration",
|
|
1014
|
+
inputSchema: {
|
|
1015
|
+
type: "object",
|
|
1016
|
+
properties: {
|
|
1017
|
+
name: {
|
|
1018
|
+
type: "string",
|
|
1019
|
+
description: "Name for the new server",
|
|
1020
|
+
},
|
|
1021
|
+
image: {
|
|
1022
|
+
type: "string",
|
|
1023
|
+
description: "Image ID or name to use",
|
|
1024
|
+
},
|
|
1025
|
+
flavor: {
|
|
1026
|
+
type: "string",
|
|
1027
|
+
description: "Flavor ID or name (instance size)",
|
|
1028
|
+
},
|
|
1029
|
+
network: {
|
|
1030
|
+
type: "string",
|
|
1031
|
+
description: "Network ID or name to attach",
|
|
1032
|
+
},
|
|
1033
|
+
keypair: {
|
|
1034
|
+
type: "string",
|
|
1035
|
+
description: "SSH keypair name for access",
|
|
1036
|
+
},
|
|
1037
|
+
security_groups: {
|
|
1038
|
+
type: "array",
|
|
1039
|
+
items: { type: "string" },
|
|
1040
|
+
description: "Security group names to apply",
|
|
1041
|
+
},
|
|
1042
|
+
user_data: {
|
|
1043
|
+
type: "string",
|
|
1044
|
+
description: "Inline cloud-init user data script (plain text YAML)",
|
|
1045
|
+
},
|
|
1046
|
+
cloud_init_url: {
|
|
1047
|
+
type: "string",
|
|
1048
|
+
description:
|
|
1049
|
+
"URL to fetch cloud-init script from (supports GitHub Gist URLs)",
|
|
1050
|
+
},
|
|
1051
|
+
cloud_init_file: {
|
|
1052
|
+
type: "string",
|
|
1053
|
+
description: "Local file path to cloud-init script",
|
|
1054
|
+
},
|
|
1055
|
+
use_default_cloud_init: {
|
|
1056
|
+
type: "boolean",
|
|
1057
|
+
description:
|
|
1058
|
+
"Use the default cloud-init from CLOUD_INIT environment variable",
|
|
1059
|
+
default: false,
|
|
1060
|
+
},
|
|
1061
|
+
availability_zone: {
|
|
1062
|
+
type: "string",
|
|
1063
|
+
description: "Availability zone for the server",
|
|
1064
|
+
},
|
|
1065
|
+
wait: {
|
|
1066
|
+
type: "boolean",
|
|
1067
|
+
description: "Wait for server to become active",
|
|
1068
|
+
default: true,
|
|
1069
|
+
},
|
|
805
1070
|
},
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
description: "Local file path to cloud-init script"
|
|
809
|
-
},
|
|
810
|
-
use_default_cloud_init: {
|
|
811
|
-
type: "boolean",
|
|
812
|
-
description: "Use the default cloud-init from CLOUD_INIT environment variable",
|
|
813
|
-
default: false
|
|
814
|
-
},
|
|
815
|
-
availability_zone: {
|
|
816
|
-
type: "string",
|
|
817
|
-
description: "Availability zone for the server"
|
|
818
|
-
},
|
|
819
|
-
wait: {
|
|
820
|
-
type: "boolean",
|
|
821
|
-
description: "Wait for server to become active",
|
|
822
|
-
default: true
|
|
823
|
-
}
|
|
1071
|
+
required: ["name", "image", "flavor"],
|
|
1072
|
+
additionalProperties: false,
|
|
824
1073
|
},
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
let tmpFile = null;
|
|
830
|
-
let cloudInitInfo = null;
|
|
831
|
-
|
|
832
|
-
try {
|
|
833
|
-
const args = ["server", "create"];
|
|
834
|
-
args.push("--image", params.image);
|
|
835
|
-
args.push("--flavor", params.flavor);
|
|
1074
|
+
},
|
|
1075
|
+
async (params) => {
|
|
1076
|
+
let tmpFile = null;
|
|
1077
|
+
let cloudInitInfo = null;
|
|
836
1078
|
|
|
837
|
-
|
|
838
|
-
args
|
|
839
|
-
|
|
1079
|
+
try {
|
|
1080
|
+
const args = ["server", "create"];
|
|
1081
|
+
args.push("--image", params.image);
|
|
1082
|
+
args.push("--flavor", params.flavor);
|
|
840
1083
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1084
|
+
if (params.network) {
|
|
1085
|
+
args.push("--network", params.network);
|
|
1086
|
+
}
|
|
844
1087
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
});
|
|
849
|
-
}
|
|
1088
|
+
if (params.keypair) {
|
|
1089
|
+
args.push("--key-name", params.keypair);
|
|
1090
|
+
}
|
|
850
1091
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
args.push("--user-data", tmpFile);
|
|
857
|
-
}
|
|
1092
|
+
if (params.security_groups && params.security_groups.length > 0) {
|
|
1093
|
+
params.security_groups.forEach((sg) => {
|
|
1094
|
+
args.push("--security-group", sg);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
858
1097
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1098
|
+
// Resolve cloud-init from various sources
|
|
1099
|
+
cloudInitInfo = await resolveCloudInit(params);
|
|
1100
|
+
if (cloudInitInfo) {
|
|
1101
|
+
tmpFile = `/tmp/userdata-${Date.now()}.txt`;
|
|
1102
|
+
fs.writeFileSync(tmpFile, cloudInitInfo.content);
|
|
1103
|
+
args.push("--user-data", tmpFile);
|
|
1104
|
+
}
|
|
862
1105
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1106
|
+
if (params.availability_zone) {
|
|
1107
|
+
args.push("--availability-zone", params.availability_zone);
|
|
1108
|
+
}
|
|
866
1109
|
|
|
867
|
-
|
|
868
|
-
|
|
1110
|
+
if (params.wait !== false) {
|
|
1111
|
+
args.push("--wait");
|
|
1112
|
+
}
|
|
869
1113
|
|
|
870
|
-
|
|
871
|
-
|
|
1114
|
+
args.push("-f", "json");
|
|
1115
|
+
args.push(params.name);
|
|
872
1116
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
type: "server_create",
|
|
876
|
-
server_id: serverResult.id,
|
|
877
|
-
server_name: params.name,
|
|
878
|
-
cloud_init_source: cloudInitInfo ? cloudInitInfo.source : null,
|
|
879
|
-
timestamp: new Date().toISOString()
|
|
880
|
-
});
|
|
1117
|
+
const result = await runOpenStackCommand(args, 300000); // 5 minute timeout for server creation
|
|
1118
|
+
const serverResult = JSON.parse(result.output);
|
|
881
1119
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
flavor: serverResult.flavor,
|
|
891
|
-
key_name: serverResult.key_name,
|
|
892
|
-
security_groups: serverResult.security_groups
|
|
893
|
-
},
|
|
894
|
-
message: "Server created successfully"
|
|
895
|
-
};
|
|
1120
|
+
// Log operation
|
|
1121
|
+
operationHistory.push({
|
|
1122
|
+
type: "server_create",
|
|
1123
|
+
server_id: serverResult.id,
|
|
1124
|
+
server_name: params.name,
|
|
1125
|
+
cloud_init_source: cloudInitInfo ? cloudInitInfo.source : null,
|
|
1126
|
+
timestamp: new Date().toISOString(),
|
|
1127
|
+
});
|
|
896
1128
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1129
|
+
const response = {
|
|
1130
|
+
success: true,
|
|
1131
|
+
server: {
|
|
1132
|
+
id: serverResult.id,
|
|
1133
|
+
name: serverResult.name,
|
|
1134
|
+
status: serverResult.status,
|
|
1135
|
+
addresses: serverResult.addresses,
|
|
1136
|
+
image: serverResult.image,
|
|
1137
|
+
flavor: serverResult.flavor,
|
|
1138
|
+
key_name: serverResult.key_name,
|
|
1139
|
+
security_groups: serverResult.security_groups,
|
|
1140
|
+
},
|
|
1141
|
+
message: "Server created successfully",
|
|
902
1142
|
};
|
|
903
|
-
}
|
|
904
1143
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1144
|
+
// Include cloud-init info in response
|
|
1145
|
+
if (cloudInitInfo) {
|
|
1146
|
+
response.cloud_init = {
|
|
1147
|
+
source: cloudInitInfo.source,
|
|
1148
|
+
applied: true,
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return {
|
|
1153
|
+
content: [
|
|
1154
|
+
{
|
|
1155
|
+
type: "text",
|
|
1156
|
+
text: JSON.stringify(response, null, 2),
|
|
1157
|
+
},
|
|
1158
|
+
],
|
|
1159
|
+
};
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
return {
|
|
1162
|
+
content: [
|
|
1163
|
+
{
|
|
1164
|
+
type: "text",
|
|
1165
|
+
text: JSON.stringify(
|
|
1166
|
+
{
|
|
1167
|
+
success: false,
|
|
1168
|
+
error: error.message,
|
|
1169
|
+
},
|
|
1170
|
+
null,
|
|
1171
|
+
2,
|
|
1172
|
+
),
|
|
1173
|
+
},
|
|
1174
|
+
],
|
|
1175
|
+
};
|
|
1176
|
+
} finally {
|
|
1177
|
+
// Clean up temp file
|
|
1178
|
+
if (tmpFile) {
|
|
1179
|
+
try {
|
|
1180
|
+
fs.unlinkSync(tmpFile);
|
|
1181
|
+
} catch (e) {}
|
|
1182
|
+
}
|
|
927
1183
|
}
|
|
928
|
-
}
|
|
929
|
-
|
|
1184
|
+
},
|
|
1185
|
+
);
|
|
930
1186
|
|
|
931
1187
|
// Get server details
|
|
932
|
-
server.registerTool(
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1188
|
+
server.registerTool(
|
|
1189
|
+
"os_server_show",
|
|
1190
|
+
{
|
|
1191
|
+
title: "Show Server Details",
|
|
1192
|
+
description: "Get detailed information about a specific server",
|
|
1193
|
+
inputSchema: {
|
|
1194
|
+
type: "object",
|
|
1195
|
+
properties: {
|
|
1196
|
+
server: {
|
|
1197
|
+
type: "string",
|
|
1198
|
+
description: "Server ID or name",
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
required: ["server"],
|
|
1202
|
+
additionalProperties: false,
|
|
942
1203
|
},
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1204
|
+
},
|
|
1205
|
+
async (params) => {
|
|
1206
|
+
try {
|
|
1207
|
+
const result = await runOpenStackCommand([
|
|
1208
|
+
"server",
|
|
1209
|
+
"show",
|
|
1210
|
+
params.server,
|
|
1211
|
+
"-f",
|
|
1212
|
+
"json",
|
|
1213
|
+
]);
|
|
1214
|
+
const server = JSON.parse(result.output);
|
|
950
1215
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1216
|
+
return {
|
|
1217
|
+
content: [
|
|
1218
|
+
{
|
|
1219
|
+
type: "text",
|
|
1220
|
+
text: JSON.stringify(
|
|
1221
|
+
{
|
|
1222
|
+
success: true,
|
|
1223
|
+
server: {
|
|
1224
|
+
id: server.id,
|
|
1225
|
+
name: server.name,
|
|
1226
|
+
status: server.status,
|
|
1227
|
+
addresses: server.addresses,
|
|
1228
|
+
image: server.image,
|
|
1229
|
+
flavor: server.flavor,
|
|
1230
|
+
key_name: server.key_name,
|
|
1231
|
+
security_groups: server.security_groups,
|
|
1232
|
+
created: server.created,
|
|
1233
|
+
updated: server.updated,
|
|
1234
|
+
availability_zone: server.availability_zone,
|
|
1235
|
+
host: server["OS-EXT-SRV-ATTR:host"],
|
|
1236
|
+
power_state: server["OS-EXT-STS:power_state"],
|
|
1237
|
+
task_state: server["OS-EXT-STS:task_state"],
|
|
1238
|
+
},
|
|
1239
|
+
},
|
|
1240
|
+
null,
|
|
1241
|
+
2,
|
|
1242
|
+
),
|
|
1243
|
+
},
|
|
1244
|
+
],
|
|
1245
|
+
};
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
return {
|
|
1248
|
+
content: [
|
|
1249
|
+
{
|
|
1250
|
+
type: "text",
|
|
1251
|
+
text: JSON.stringify(
|
|
1252
|
+
{
|
|
1253
|
+
success: false,
|
|
1254
|
+
error: error.message,
|
|
1255
|
+
},
|
|
1256
|
+
null,
|
|
1257
|
+
2,
|
|
1258
|
+
),
|
|
1259
|
+
},
|
|
1260
|
+
],
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
},
|
|
1264
|
+
);
|
|
987
1265
|
|
|
988
1266
|
// Delete server
|
|
989
|
-
server.registerTool(
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1267
|
+
server.registerTool(
|
|
1268
|
+
"os_server_delete",
|
|
1269
|
+
{
|
|
1270
|
+
title: "Delete Server",
|
|
1271
|
+
description: "Delete a VM/server from OpenStack",
|
|
1272
|
+
inputSchema: {
|
|
1273
|
+
type: "object",
|
|
1274
|
+
properties: {
|
|
1275
|
+
server: {
|
|
1276
|
+
type: "string",
|
|
1277
|
+
description: "Server ID or name to delete",
|
|
1278
|
+
},
|
|
1279
|
+
wait: {
|
|
1280
|
+
type: "boolean",
|
|
1281
|
+
description: "Wait for deletion to complete",
|
|
1282
|
+
default: true,
|
|
1283
|
+
},
|
|
998
1284
|
},
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
description: "Wait for deletion to complete",
|
|
1002
|
-
default: true
|
|
1003
|
-
}
|
|
1285
|
+
required: ["server"],
|
|
1286
|
+
additionalProperties: false,
|
|
1004
1287
|
},
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
args.push("--wait");
|
|
1013
|
-
}
|
|
1288
|
+
},
|
|
1289
|
+
async (params) => {
|
|
1290
|
+
try {
|
|
1291
|
+
const args = ["server", "delete", params.server];
|
|
1292
|
+
if (params.wait !== false) {
|
|
1293
|
+
args.push("--wait");
|
|
1294
|
+
}
|
|
1014
1295
|
|
|
1015
|
-
|
|
1296
|
+
await runOpenStackCommand(args, 180000); // 3 minute timeout
|
|
1016
1297
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1298
|
+
operationHistory.push({
|
|
1299
|
+
type: "server_delete",
|
|
1300
|
+
server: params.server,
|
|
1301
|
+
timestamp: new Date().toISOString(),
|
|
1302
|
+
});
|
|
1022
1303
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1304
|
+
return {
|
|
1305
|
+
content: [
|
|
1306
|
+
{
|
|
1307
|
+
type: "text",
|
|
1308
|
+
text: JSON.stringify(
|
|
1309
|
+
{
|
|
1310
|
+
success: true,
|
|
1311
|
+
message: `Server '${params.server}' deleted successfully`,
|
|
1312
|
+
},
|
|
1313
|
+
null,
|
|
1314
|
+
2,
|
|
1315
|
+
),
|
|
1316
|
+
},
|
|
1317
|
+
],
|
|
1318
|
+
};
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
return {
|
|
1321
|
+
content: [
|
|
1322
|
+
{
|
|
1323
|
+
type: "text",
|
|
1324
|
+
text: JSON.stringify(
|
|
1325
|
+
{
|
|
1326
|
+
success: false,
|
|
1327
|
+
error: error.message,
|
|
1328
|
+
},
|
|
1329
|
+
null,
|
|
1330
|
+
2,
|
|
1331
|
+
),
|
|
1332
|
+
},
|
|
1333
|
+
],
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
);
|
|
1044
1338
|
|
|
1045
1339
|
// Start server
|
|
1046
|
-
server.registerTool(
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1340
|
+
server.registerTool(
|
|
1341
|
+
"os_server_start",
|
|
1342
|
+
{
|
|
1343
|
+
title: "Start Server",
|
|
1344
|
+
description: "Start a stopped server",
|
|
1345
|
+
inputSchema: {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
properties: {
|
|
1348
|
+
server: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
description: "Server ID or name to start",
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
required: ["server"],
|
|
1354
|
+
additionalProperties: false,
|
|
1056
1355
|
},
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
try {
|
|
1062
|
-
await runOpenStackCommand(["server", "start", params.server]);
|
|
1356
|
+
},
|
|
1357
|
+
async (params) => {
|
|
1358
|
+
try {
|
|
1359
|
+
await runOpenStackCommand(["server", "start", params.server]);
|
|
1063
1360
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1361
|
+
return {
|
|
1362
|
+
content: [
|
|
1363
|
+
{
|
|
1364
|
+
type: "text",
|
|
1365
|
+
text: JSON.stringify(
|
|
1366
|
+
{
|
|
1367
|
+
success: true,
|
|
1368
|
+
message: `Server '${params.server}' start initiated`,
|
|
1369
|
+
},
|
|
1370
|
+
null,
|
|
1371
|
+
2,
|
|
1372
|
+
),
|
|
1373
|
+
},
|
|
1374
|
+
],
|
|
1375
|
+
};
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
return {
|
|
1378
|
+
content: [
|
|
1379
|
+
{
|
|
1380
|
+
type: "text",
|
|
1381
|
+
text: JSON.stringify(
|
|
1382
|
+
{
|
|
1383
|
+
success: false,
|
|
1384
|
+
error: error.message,
|
|
1385
|
+
},
|
|
1386
|
+
null,
|
|
1387
|
+
2,
|
|
1388
|
+
),
|
|
1389
|
+
},
|
|
1390
|
+
],
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
},
|
|
1394
|
+
);
|
|
1085
1395
|
|
|
1086
1396
|
// Stop server
|
|
1087
|
-
server.registerTool(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1397
|
+
server.registerTool(
|
|
1398
|
+
"os_server_stop",
|
|
1399
|
+
{
|
|
1400
|
+
title: "Stop Server",
|
|
1401
|
+
description: "Stop a running server",
|
|
1402
|
+
inputSchema: {
|
|
1403
|
+
type: "object",
|
|
1404
|
+
properties: {
|
|
1405
|
+
server: {
|
|
1406
|
+
type: "string",
|
|
1407
|
+
description: "Server ID or name to stop",
|
|
1408
|
+
},
|
|
1409
|
+
},
|
|
1410
|
+
required: ["server"],
|
|
1411
|
+
additionalProperties: false,
|
|
1097
1412
|
},
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
try {
|
|
1103
|
-
await runOpenStackCommand(["server", "stop", params.server]);
|
|
1413
|
+
},
|
|
1414
|
+
async (params) => {
|
|
1415
|
+
try {
|
|
1416
|
+
await runOpenStackCommand(["server", "stop", params.server]);
|
|
1104
1417
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1418
|
+
return {
|
|
1419
|
+
content: [
|
|
1420
|
+
{
|
|
1421
|
+
type: "text",
|
|
1422
|
+
text: JSON.stringify(
|
|
1423
|
+
{
|
|
1424
|
+
success: true,
|
|
1425
|
+
message: `Server '${params.server}' stop initiated`,
|
|
1426
|
+
},
|
|
1427
|
+
null,
|
|
1428
|
+
2,
|
|
1429
|
+
),
|
|
1430
|
+
},
|
|
1431
|
+
],
|
|
1432
|
+
};
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
return {
|
|
1435
|
+
content: [
|
|
1436
|
+
{
|
|
1437
|
+
type: "text",
|
|
1438
|
+
text: JSON.stringify(
|
|
1439
|
+
{
|
|
1440
|
+
success: false,
|
|
1441
|
+
error: error.message,
|
|
1442
|
+
},
|
|
1443
|
+
null,
|
|
1444
|
+
2,
|
|
1445
|
+
),
|
|
1446
|
+
},
|
|
1447
|
+
],
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
);
|
|
1126
1452
|
|
|
1127
1453
|
// Reboot server
|
|
1128
|
-
server.registerTool(
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1454
|
+
server.registerTool(
|
|
1455
|
+
"os_server_reboot",
|
|
1456
|
+
{
|
|
1457
|
+
title: "Reboot Server",
|
|
1458
|
+
description: "Reboot a server (soft or hard reboot)",
|
|
1459
|
+
inputSchema: {
|
|
1460
|
+
type: "object",
|
|
1461
|
+
properties: {
|
|
1462
|
+
server: {
|
|
1463
|
+
type: "string",
|
|
1464
|
+
description: "Server ID or name to reboot",
|
|
1465
|
+
},
|
|
1466
|
+
hard: {
|
|
1467
|
+
type: "boolean",
|
|
1468
|
+
description: "Perform hard reboot instead of soft reboot",
|
|
1469
|
+
default: false,
|
|
1470
|
+
},
|
|
1137
1471
|
},
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
description: "Perform hard reboot instead of soft reboot",
|
|
1141
|
-
default: false
|
|
1142
|
-
}
|
|
1472
|
+
required: ["server"],
|
|
1473
|
+
additionalProperties: false,
|
|
1143
1474
|
},
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
args.push(
|
|
1154
|
-
}
|
|
1155
|
-
args.push(params.server);
|
|
1475
|
+
},
|
|
1476
|
+
async (params) => {
|
|
1477
|
+
try {
|
|
1478
|
+
const args = ["server", "reboot"];
|
|
1479
|
+
if (params.hard) {
|
|
1480
|
+
args.push("--hard");
|
|
1481
|
+
} else {
|
|
1482
|
+
args.push("--soft");
|
|
1483
|
+
}
|
|
1484
|
+
args.push(params.server);
|
|
1156
1485
|
|
|
1157
|
-
|
|
1486
|
+
await runOpenStackCommand(args);
|
|
1158
1487
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1488
|
+
return {
|
|
1489
|
+
content: [
|
|
1490
|
+
{
|
|
1491
|
+
type: "text",
|
|
1492
|
+
text: JSON.stringify(
|
|
1493
|
+
{
|
|
1494
|
+
success: true,
|
|
1495
|
+
message: `Server '${params.server}' ${params.hard ? "hard" : "soft"} reboot initiated`,
|
|
1496
|
+
},
|
|
1497
|
+
null,
|
|
1498
|
+
2,
|
|
1499
|
+
),
|
|
1500
|
+
},
|
|
1501
|
+
],
|
|
1502
|
+
};
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
return {
|
|
1505
|
+
content: [
|
|
1506
|
+
{
|
|
1507
|
+
type: "text",
|
|
1508
|
+
text: JSON.stringify(
|
|
1509
|
+
{
|
|
1510
|
+
success: false,
|
|
1511
|
+
error: error.message,
|
|
1512
|
+
},
|
|
1513
|
+
null,
|
|
1514
|
+
2,
|
|
1515
|
+
),
|
|
1516
|
+
},
|
|
1517
|
+
],
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
);
|
|
1180
1522
|
|
|
1181
1523
|
// Get server console URL
|
|
1182
|
-
server.registerTool(
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1524
|
+
server.registerTool(
|
|
1525
|
+
"os_server_console",
|
|
1526
|
+
{
|
|
1527
|
+
title: "Get Server Console",
|
|
1528
|
+
description: "Get console URL for accessing server",
|
|
1529
|
+
inputSchema: {
|
|
1530
|
+
type: "object",
|
|
1531
|
+
properties: {
|
|
1532
|
+
server: {
|
|
1533
|
+
type: "string",
|
|
1534
|
+
description: "Server ID or name",
|
|
1535
|
+
},
|
|
1536
|
+
type: {
|
|
1537
|
+
type: "string",
|
|
1538
|
+
enum: ["novnc", "xvpvnc", "spice", "rdp", "serial"],
|
|
1539
|
+
description: "Console type",
|
|
1540
|
+
default: "novnc",
|
|
1541
|
+
},
|
|
1191
1542
|
},
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
enum: ["novnc", "xvpvnc", "spice", "rdp", "serial"],
|
|
1195
|
-
description: "Console type",
|
|
1196
|
-
default: "novnc"
|
|
1197
|
-
}
|
|
1543
|
+
required: ["server"],
|
|
1544
|
+
additionalProperties: false,
|
|
1198
1545
|
},
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1546
|
+
},
|
|
1547
|
+
async (params) => {
|
|
1548
|
+
try {
|
|
1549
|
+
const result = await runOpenStackCommand([
|
|
1550
|
+
"console",
|
|
1551
|
+
"url",
|
|
1552
|
+
"show",
|
|
1553
|
+
"--" + (params.type || "novnc"),
|
|
1554
|
+
params.server,
|
|
1555
|
+
"-f",
|
|
1556
|
+
"json",
|
|
1557
|
+
]);
|
|
1558
|
+
const console_info = JSON.parse(result.output);
|
|
1211
1559
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1560
|
+
return {
|
|
1561
|
+
content: [
|
|
1562
|
+
{
|
|
1563
|
+
type: "text",
|
|
1564
|
+
text: JSON.stringify(
|
|
1565
|
+
{
|
|
1566
|
+
success: true,
|
|
1567
|
+
console: {
|
|
1568
|
+
type: console_info.type,
|
|
1569
|
+
url: console_info.url,
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
null,
|
|
1573
|
+
2,
|
|
1574
|
+
),
|
|
1575
|
+
},
|
|
1576
|
+
],
|
|
1577
|
+
};
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
return {
|
|
1580
|
+
content: [
|
|
1581
|
+
{
|
|
1582
|
+
type: "text",
|
|
1583
|
+
text: JSON.stringify(
|
|
1584
|
+
{
|
|
1585
|
+
success: false,
|
|
1586
|
+
error: error.message,
|
|
1587
|
+
},
|
|
1588
|
+
null,
|
|
1589
|
+
2,
|
|
1590
|
+
),
|
|
1591
|
+
},
|
|
1592
|
+
],
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
);
|
|
1236
1597
|
|
|
1237
1598
|
// Attach floating IP
|
|
1238
|
-
server.registerTool(
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1599
|
+
server.registerTool(
|
|
1600
|
+
"os_floating_ip_create",
|
|
1601
|
+
{
|
|
1602
|
+
title: "Create Floating IP",
|
|
1603
|
+
description: "Create a new floating IP from an external network",
|
|
1604
|
+
inputSchema: {
|
|
1605
|
+
type: "object",
|
|
1606
|
+
properties: {
|
|
1607
|
+
network: {
|
|
1608
|
+
type: "string",
|
|
1609
|
+
description: "External network name or ID",
|
|
1610
|
+
},
|
|
1611
|
+
},
|
|
1612
|
+
required: ["network"],
|
|
1613
|
+
additionalProperties: false,
|
|
1248
1614
|
},
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1615
|
+
},
|
|
1616
|
+
async (params) => {
|
|
1617
|
+
try {
|
|
1618
|
+
const result = await runOpenStackCommand([
|
|
1619
|
+
"floating",
|
|
1620
|
+
"ip",
|
|
1621
|
+
"create",
|
|
1622
|
+
params.network,
|
|
1623
|
+
"-f",
|
|
1624
|
+
"json",
|
|
1625
|
+
]);
|
|
1626
|
+
const floating_ip = JSON.parse(result.output);
|
|
1260
1627
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1628
|
+
return {
|
|
1629
|
+
content: [
|
|
1630
|
+
{
|
|
1631
|
+
type: "text",
|
|
1632
|
+
text: JSON.stringify(
|
|
1633
|
+
{
|
|
1634
|
+
success: true,
|
|
1635
|
+
floating_ip: {
|
|
1636
|
+
id: floating_ip.id,
|
|
1637
|
+
ip: floating_ip.floating_ip_address,
|
|
1638
|
+
network: floating_ip.floating_network_id,
|
|
1639
|
+
status: floating_ip.status,
|
|
1640
|
+
},
|
|
1641
|
+
message: "Floating IP created successfully",
|
|
1642
|
+
},
|
|
1643
|
+
null,
|
|
1644
|
+
2,
|
|
1645
|
+
),
|
|
1271
1646
|
},
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1647
|
+
],
|
|
1648
|
+
};
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
return {
|
|
1651
|
+
content: [
|
|
1652
|
+
{
|
|
1653
|
+
type: "text",
|
|
1654
|
+
text: JSON.stringify(
|
|
1655
|
+
{
|
|
1656
|
+
success: false,
|
|
1657
|
+
error: error.message,
|
|
1658
|
+
},
|
|
1659
|
+
null,
|
|
1660
|
+
2,
|
|
1661
|
+
),
|
|
1662
|
+
},
|
|
1663
|
+
],
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
},
|
|
1667
|
+
);
|
|
1288
1668
|
|
|
1289
1669
|
// List floating IPs
|
|
1290
|
-
server.registerTool(
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1670
|
+
server.registerTool(
|
|
1671
|
+
"os_floating_ip_list",
|
|
1672
|
+
{
|
|
1673
|
+
title: "List Floating IPs",
|
|
1674
|
+
description: "List all floating IPs in the project",
|
|
1675
|
+
inputSchema: {
|
|
1676
|
+
type: "object",
|
|
1677
|
+
properties: {},
|
|
1678
|
+
additionalProperties: false,
|
|
1679
|
+
},
|
|
1680
|
+
},
|
|
1681
|
+
async () => {
|
|
1682
|
+
try {
|
|
1683
|
+
const result = await runOpenStackCommand([
|
|
1684
|
+
"floating",
|
|
1685
|
+
"ip",
|
|
1686
|
+
"list",
|
|
1687
|
+
"-f",
|
|
1688
|
+
"json",
|
|
1689
|
+
]);
|
|
1690
|
+
const floating_ips = JSON.parse(result.output);
|
|
1302
1691
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1692
|
+
return {
|
|
1693
|
+
content: [
|
|
1694
|
+
{
|
|
1695
|
+
type: "text",
|
|
1696
|
+
text: JSON.stringify(
|
|
1697
|
+
{
|
|
1698
|
+
success: true,
|
|
1699
|
+
count: floating_ips.length,
|
|
1700
|
+
floating_ips: floating_ips.map((f) => ({
|
|
1701
|
+
id: f.ID,
|
|
1702
|
+
ip: f["Floating IP Address"],
|
|
1703
|
+
fixed_ip: f["Fixed IP Address"],
|
|
1704
|
+
port: f.Port,
|
|
1705
|
+
status: f.Status,
|
|
1706
|
+
})),
|
|
1707
|
+
},
|
|
1708
|
+
null,
|
|
1709
|
+
2,
|
|
1710
|
+
),
|
|
1711
|
+
},
|
|
1712
|
+
],
|
|
1713
|
+
};
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
return {
|
|
1716
|
+
content: [
|
|
1717
|
+
{
|
|
1718
|
+
type: "text",
|
|
1719
|
+
text: JSON.stringify(
|
|
1720
|
+
{
|
|
1721
|
+
success: false,
|
|
1722
|
+
error: error.message,
|
|
1723
|
+
},
|
|
1724
|
+
null,
|
|
1725
|
+
2,
|
|
1726
|
+
),
|
|
1727
|
+
},
|
|
1728
|
+
],
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
},
|
|
1732
|
+
);
|
|
1331
1733
|
|
|
1332
1734
|
// Associate floating IP with server
|
|
1333
|
-
server.registerTool(
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1735
|
+
server.registerTool(
|
|
1736
|
+
"os_server_add_floating_ip",
|
|
1737
|
+
{
|
|
1738
|
+
title: "Add Floating IP to Server",
|
|
1739
|
+
description: "Associate a floating IP with a server",
|
|
1740
|
+
inputSchema: {
|
|
1741
|
+
type: "object",
|
|
1742
|
+
properties: {
|
|
1743
|
+
server: {
|
|
1744
|
+
type: "string",
|
|
1745
|
+
description: "Server ID or name",
|
|
1746
|
+
},
|
|
1747
|
+
floating_ip: {
|
|
1748
|
+
type: "string",
|
|
1749
|
+
description: "Floating IP address to associate",
|
|
1750
|
+
},
|
|
1342
1751
|
},
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
description: "Floating IP address to associate"
|
|
1346
|
-
}
|
|
1752
|
+
required: ["server", "floating_ip"],
|
|
1753
|
+
additionalProperties: false,
|
|
1347
1754
|
},
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1755
|
+
},
|
|
1756
|
+
async (params) => {
|
|
1757
|
+
try {
|
|
1758
|
+
await runOpenStackCommand([
|
|
1759
|
+
"server",
|
|
1760
|
+
"add",
|
|
1761
|
+
"floating",
|
|
1762
|
+
"ip",
|
|
1763
|
+
params.server,
|
|
1764
|
+
params.floating_ip,
|
|
1765
|
+
]);
|
|
1358
1766
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1767
|
+
return {
|
|
1768
|
+
content: [
|
|
1769
|
+
{
|
|
1770
|
+
type: "text",
|
|
1771
|
+
text: JSON.stringify(
|
|
1772
|
+
{
|
|
1773
|
+
success: true,
|
|
1774
|
+
message: `Floating IP '${params.floating_ip}' associated with server '${params.server}'`,
|
|
1775
|
+
},
|
|
1776
|
+
null,
|
|
1777
|
+
2,
|
|
1778
|
+
),
|
|
1779
|
+
},
|
|
1780
|
+
],
|
|
1781
|
+
};
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
return {
|
|
1784
|
+
content: [
|
|
1785
|
+
{
|
|
1786
|
+
type: "text",
|
|
1787
|
+
text: JSON.stringify(
|
|
1788
|
+
{
|
|
1789
|
+
success: false,
|
|
1790
|
+
error: error.message,
|
|
1791
|
+
},
|
|
1792
|
+
null,
|
|
1793
|
+
2,
|
|
1794
|
+
),
|
|
1795
|
+
},
|
|
1796
|
+
],
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
);
|
|
1380
1801
|
|
|
1381
1802
|
// Volume operations
|
|
1382
|
-
server.registerTool(
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1803
|
+
server.registerTool(
|
|
1804
|
+
"os_volume_list",
|
|
1805
|
+
{
|
|
1806
|
+
title: "List Volumes",
|
|
1807
|
+
description: "List all volumes in the project",
|
|
1808
|
+
inputSchema: {
|
|
1809
|
+
type: "object",
|
|
1810
|
+
properties: {},
|
|
1811
|
+
additionalProperties: false,
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
async () => {
|
|
1815
|
+
try {
|
|
1816
|
+
const result = await runOpenStackCommand([
|
|
1817
|
+
"volume",
|
|
1818
|
+
"list",
|
|
1819
|
+
"-f",
|
|
1820
|
+
"json",
|
|
1821
|
+
]);
|
|
1822
|
+
const volumes = JSON.parse(result.output);
|
|
1394
1823
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1824
|
+
return {
|
|
1825
|
+
content: [
|
|
1826
|
+
{
|
|
1827
|
+
type: "text",
|
|
1828
|
+
text: JSON.stringify(
|
|
1829
|
+
{
|
|
1830
|
+
success: true,
|
|
1831
|
+
count: volumes.length,
|
|
1832
|
+
volumes: volumes.map((v) => ({
|
|
1833
|
+
id: v.ID,
|
|
1834
|
+
name: v.Name,
|
|
1835
|
+
status: v.Status,
|
|
1836
|
+
size_gb: v.Size,
|
|
1837
|
+
attached_to: v["Attached to"],
|
|
1838
|
+
})),
|
|
1839
|
+
},
|
|
1840
|
+
null,
|
|
1841
|
+
2,
|
|
1842
|
+
),
|
|
1843
|
+
},
|
|
1844
|
+
],
|
|
1845
|
+
};
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
return {
|
|
1848
|
+
content: [
|
|
1849
|
+
{
|
|
1850
|
+
type: "text",
|
|
1851
|
+
text: JSON.stringify(
|
|
1852
|
+
{
|
|
1853
|
+
success: false,
|
|
1854
|
+
error: error.message,
|
|
1855
|
+
},
|
|
1856
|
+
null,
|
|
1857
|
+
2,
|
|
1858
|
+
),
|
|
1859
|
+
},
|
|
1860
|
+
],
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
);
|
|
1423
1865
|
|
|
1424
1866
|
// Create volume
|
|
1425
|
-
server.registerTool(
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1867
|
+
server.registerTool(
|
|
1868
|
+
"os_volume_create",
|
|
1869
|
+
{
|
|
1870
|
+
title: "Create Volume",
|
|
1871
|
+
description: "Create a new block storage volume",
|
|
1872
|
+
inputSchema: {
|
|
1873
|
+
type: "object",
|
|
1874
|
+
properties: {
|
|
1875
|
+
name: {
|
|
1876
|
+
type: "string",
|
|
1877
|
+
description: "Volume name",
|
|
1878
|
+
},
|
|
1879
|
+
size: {
|
|
1880
|
+
type: "number",
|
|
1881
|
+
description: "Volume size in GB",
|
|
1882
|
+
},
|
|
1883
|
+
type: {
|
|
1884
|
+
type: "string",
|
|
1885
|
+
description: "Volume type (optional)",
|
|
1886
|
+
},
|
|
1887
|
+
image: {
|
|
1888
|
+
type: "string",
|
|
1889
|
+
description: "Image ID to create volume from (optional)",
|
|
1890
|
+
},
|
|
1442
1891
|
},
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
description: "Image ID to create volume from (optional)"
|
|
1446
|
-
}
|
|
1892
|
+
required: ["name", "size"],
|
|
1893
|
+
additionalProperties: false,
|
|
1447
1894
|
},
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
const args = ["volume", "create"];
|
|
1454
|
-
args.push("--size", params.size.toString());
|
|
1895
|
+
},
|
|
1896
|
+
async (params) => {
|
|
1897
|
+
try {
|
|
1898
|
+
const args = ["volume", "create"];
|
|
1899
|
+
args.push("--size", params.size.toString());
|
|
1455
1900
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1901
|
+
if (params.type) {
|
|
1902
|
+
args.push("--type", params.type);
|
|
1903
|
+
}
|
|
1459
1904
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1905
|
+
if (params.image) {
|
|
1906
|
+
args.push("--image", params.image);
|
|
1907
|
+
}
|
|
1463
1908
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1909
|
+
args.push("-f", "json");
|
|
1910
|
+
args.push(params.name);
|
|
1466
1911
|
|
|
1467
|
-
|
|
1468
|
-
|
|
1912
|
+
const result = await runOpenStackCommand(args, 180000);
|
|
1913
|
+
const volume = JSON.parse(result.output);
|
|
1469
1914
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1915
|
+
return {
|
|
1916
|
+
content: [
|
|
1917
|
+
{
|
|
1918
|
+
type: "text",
|
|
1919
|
+
text: JSON.stringify(
|
|
1920
|
+
{
|
|
1921
|
+
success: true,
|
|
1922
|
+
volume: {
|
|
1923
|
+
id: volume.id,
|
|
1924
|
+
name: volume.name,
|
|
1925
|
+
size: volume.size,
|
|
1926
|
+
status: volume.status,
|
|
1927
|
+
type: volume.type,
|
|
1928
|
+
},
|
|
1929
|
+
message: "Volume created successfully",
|
|
1930
|
+
},
|
|
1931
|
+
null,
|
|
1932
|
+
2,
|
|
1933
|
+
),
|
|
1481
1934
|
},
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1935
|
+
],
|
|
1936
|
+
};
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
return {
|
|
1939
|
+
content: [
|
|
1940
|
+
{
|
|
1941
|
+
type: "text",
|
|
1942
|
+
text: JSON.stringify(
|
|
1943
|
+
{
|
|
1944
|
+
success: false,
|
|
1945
|
+
error: error.message,
|
|
1946
|
+
},
|
|
1947
|
+
null,
|
|
1948
|
+
2,
|
|
1949
|
+
),
|
|
1950
|
+
},
|
|
1951
|
+
],
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
},
|
|
1955
|
+
);
|
|
1498
1956
|
|
|
1499
1957
|
// Attach volume to server
|
|
1500
|
-
server.registerTool(
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1958
|
+
server.registerTool(
|
|
1959
|
+
"os_server_add_volume",
|
|
1960
|
+
{
|
|
1961
|
+
title: "Attach Volume to Server",
|
|
1962
|
+
description: "Attach a volume to a server",
|
|
1963
|
+
inputSchema: {
|
|
1964
|
+
type: "object",
|
|
1965
|
+
properties: {
|
|
1966
|
+
server: {
|
|
1967
|
+
type: "string",
|
|
1968
|
+
description: "Server ID or name",
|
|
1969
|
+
},
|
|
1970
|
+
volume: {
|
|
1971
|
+
type: "string",
|
|
1972
|
+
description: "Volume ID or name",
|
|
1973
|
+
},
|
|
1974
|
+
device: {
|
|
1975
|
+
type: "string",
|
|
1976
|
+
description:
|
|
1977
|
+
"Device path (e.g., /dev/vdb), auto-assigned if not specified",
|
|
1978
|
+
},
|
|
1513
1979
|
},
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
description: "Device path (e.g., /dev/vdb), auto-assigned if not specified"
|
|
1517
|
-
}
|
|
1980
|
+
required: ["server", "volume"],
|
|
1981
|
+
additionalProperties: false,
|
|
1518
1982
|
},
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
try {
|
|
1524
|
-
const args = ["server", "add", "volume"];
|
|
1983
|
+
},
|
|
1984
|
+
async (params) => {
|
|
1985
|
+
try {
|
|
1986
|
+
const args = ["server", "add", "volume"];
|
|
1525
1987
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1988
|
+
if (params.device) {
|
|
1989
|
+
args.push("--device", params.device);
|
|
1990
|
+
}
|
|
1529
1991
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1992
|
+
args.push(params.server);
|
|
1993
|
+
args.push(params.volume);
|
|
1532
1994
|
|
|
1533
|
-
|
|
1995
|
+
await runOpenStackCommand(args);
|
|
1534
1996
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1997
|
+
return {
|
|
1998
|
+
content: [
|
|
1999
|
+
{
|
|
2000
|
+
type: "text",
|
|
2001
|
+
text: JSON.stringify(
|
|
2002
|
+
{
|
|
2003
|
+
success: true,
|
|
2004
|
+
message: `Volume '${params.volume}' attached to server '${params.server}'`,
|
|
2005
|
+
},
|
|
2006
|
+
null,
|
|
2007
|
+
2,
|
|
2008
|
+
),
|
|
2009
|
+
},
|
|
2010
|
+
],
|
|
2011
|
+
};
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
return {
|
|
2014
|
+
content: [
|
|
2015
|
+
{
|
|
2016
|
+
type: "text",
|
|
2017
|
+
text: JSON.stringify(
|
|
2018
|
+
{
|
|
2019
|
+
success: false,
|
|
2020
|
+
error: error.message,
|
|
2021
|
+
},
|
|
2022
|
+
null,
|
|
2023
|
+
2,
|
|
2024
|
+
),
|
|
2025
|
+
},
|
|
2026
|
+
],
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
);
|
|
1556
2031
|
|
|
1557
2032
|
// Get operation history
|
|
1558
|
-
server.registerTool(
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2033
|
+
server.registerTool(
|
|
2034
|
+
"os_operation_history",
|
|
2035
|
+
{
|
|
2036
|
+
title: "Get Operation History",
|
|
2037
|
+
description: "Get history of OpenStack operations performed",
|
|
2038
|
+
inputSchema: {
|
|
2039
|
+
type: "object",
|
|
2040
|
+
properties: {
|
|
2041
|
+
limit: {
|
|
2042
|
+
type: "number",
|
|
2043
|
+
description: "Maximum number of operations to return",
|
|
2044
|
+
default: 20,
|
|
2045
|
+
},
|
|
2046
|
+
},
|
|
2047
|
+
additionalProperties: false,
|
|
1569
2048
|
},
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const history = operationHistory.slice(-limit);
|
|
2049
|
+
},
|
|
2050
|
+
async (params) => {
|
|
2051
|
+
const limit = params.limit || 20;
|
|
2052
|
+
const history = operationHistory.slice(-limit);
|
|
1575
2053
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2054
|
+
return {
|
|
2055
|
+
content: [
|
|
2056
|
+
{
|
|
2057
|
+
type: "text",
|
|
2058
|
+
text: JSON.stringify(
|
|
2059
|
+
{
|
|
2060
|
+
success: true,
|
|
2061
|
+
count: history.length,
|
|
2062
|
+
operations: history,
|
|
2063
|
+
},
|
|
2064
|
+
null,
|
|
2065
|
+
2,
|
|
2066
|
+
),
|
|
2067
|
+
},
|
|
2068
|
+
],
|
|
2069
|
+
};
|
|
2070
|
+
},
|
|
2071
|
+
);
|
|
1587
2072
|
|
|
1588
2073
|
// Start the MCP server
|
|
1589
2074
|
async function main() {
|
|
@@ -1594,7 +2079,6 @@ async function main() {
|
|
|
1594
2079
|
console.error("OpenStack MCP Server v1.0.0 running...");
|
|
1595
2080
|
console.error(`Auth URL: ${OS_AUTH_URL || "Not configured"}`);
|
|
1596
2081
|
console.error(`Region: ${OS_REGION_NAME || "Not configured"}`);
|
|
1597
|
-
|
|
1598
2082
|
} catch (error) {
|
|
1599
2083
|
console.error("Failed to start server:", error.message);
|
|
1600
2084
|
process.exit(1);
|