myaidev-method 0.3.4 → 0.3.6

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.
Files changed (94) hide show
  1. package/.claude-plugin/plugin.json +0 -1
  2. package/.env.example +5 -4
  3. package/CHANGELOG.md +2 -2
  4. package/CONTENT_CREATION_GUIDE.md +489 -3211
  5. package/DEVELOPER_USE_CASES.md +1 -1
  6. package/MODULAR_INSTALLATION.md +2 -2
  7. package/README.md +39 -33
  8. package/TECHNICAL_ARCHITECTURE.md +1 -1
  9. package/USER_GUIDE.md +242 -190
  10. package/agents/content-editor-agent.md +90 -0
  11. package/agents/content-planner-agent.md +97 -0
  12. package/agents/content-research-agent.md +62 -0
  13. package/agents/content-seo-agent.md +101 -0
  14. package/agents/content-writer-agent.md +69 -0
  15. package/agents/infographic-analyzer-agent.md +63 -0
  16. package/agents/infographic-designer-agent.md +72 -0
  17. package/bin/cli.js +846 -427
  18. package/{content-rules.example.md → content-rules-example.md} +2 -2
  19. package/dist/mcp/health-check.js +82 -68
  20. package/dist/mcp/mcp-config.json +8 -0
  21. package/dist/mcp/openstack-server.js +1746 -1262
  22. package/dist/server/.tsbuildinfo +1 -1
  23. package/extension.json +21 -4
  24. package/package.json +181 -184
  25. package/skills/company-config/SKILL.md +133 -0
  26. package/skills/configure/SKILL.md +1 -1
  27. package/skills/myai-configurator/SKILL.md +77 -0
  28. package/skills/myai-configurator/content-creation-configurator/SKILL.md +516 -0
  29. package/skills/myai-configurator/content-maintenance-configurator/SKILL.md +397 -0
  30. package/skills/myai-content-enrichment/SKILL.md +114 -0
  31. package/skills/myai-content-ideation/SKILL.md +288 -0
  32. package/skills/myai-content-ideation/evals/evals.json +182 -0
  33. package/skills/myai-content-production-coordinator/SKILL.md +946 -0
  34. package/skills/{content-rules-setup → myai-content-rules-setup}/SKILL.md +1 -1
  35. package/skills/{content-verifier → myai-content-verifier}/SKILL.md +1 -1
  36. package/skills/myai-content-writer/SKILL.md +333 -0
  37. package/skills/{infographic → myai-infographic}/SKILL.md +1 -1
  38. package/skills/myai-proprietary-content-verifier/SKILL.md +175 -0
  39. package/skills/myai-proprietary-content-verifier/evals/evals.json +36 -0
  40. package/skills/myai-skill-builder/SKILL.md +699 -0
  41. package/skills/myai-skill-builder/agents/analyzer-agent.md +137 -0
  42. package/skills/myai-skill-builder/agents/comparator-agent.md +77 -0
  43. package/skills/myai-skill-builder/agents/grader-agent.md +103 -0
  44. package/skills/myai-skill-builder/assets/eval_review.html +131 -0
  45. package/skills/myai-skill-builder/references/schemas.md +211 -0
  46. package/skills/myai-skill-builder/scripts/aggregate_benchmark.py +190 -0
  47. package/skills/myai-skill-builder/scripts/generate_review.py +381 -0
  48. package/skills/myai-skill-builder/scripts/package_skill.py +91 -0
  49. package/skills/myai-skill-builder/scripts/run_eval.py +105 -0
  50. package/skills/myai-skill-builder/scripts/run_loop.py +211 -0
  51. package/skills/myai-skill-builder/scripts/utils.py +123 -0
  52. package/skills/myai-visual-generator/SKILL.md +125 -0
  53. package/skills/myai-visual-generator/evals/evals.json +155 -0
  54. package/skills/myai-visual-generator/references/infographic-pipeline.md +73 -0
  55. package/skills/myai-visual-generator/references/research-visuals.md +57 -0
  56. package/skills/myai-visual-generator/references/services.md +89 -0
  57. package/skills/myai-visual-generator/scripts/visual-generation-utils.js +1272 -0
  58. package/skills/myaidev-figma/SKILL.md +212 -0
  59. package/skills/myaidev-figma/capture.js +133 -0
  60. package/skills/myaidev-figma/crawl.js +130 -0
  61. package/skills/myaidev-figma-configure/SKILL.md +130 -0
  62. package/skills/openstack-manager/SKILL.md +1 -1
  63. package/skills/payloadcms-publisher/SKILL.md +141 -77
  64. package/skills/payloadcms-publisher/references/field-mapping.md +142 -0
  65. package/skills/payloadcms-publisher/references/lexical-format.md +97 -0
  66. package/skills/security-auditor/SKILL.md +1 -1
  67. package/src/cli/commands/addon.js +105 -7
  68. package/src/config/workflows.js +172 -228
  69. package/src/lib/ascii-banner.js +197 -182
  70. package/src/lib/{content-coordinator.js → content-production-coordinator.js} +649 -459
  71. package/src/lib/installation-detector.js +93 -59
  72. package/src/lib/payloadcms-utils.js +285 -510
  73. package/src/lib/workflow-installer.js +55 -0
  74. package/src/mcp/health-check.js +82 -68
  75. package/src/mcp/openstack-server.js +1746 -1262
  76. package/src/scripts/configure-visual-apis.js +224 -173
  77. package/src/scripts/configure-wordpress-mcp.js +96 -66
  78. package/src/scripts/init/install.js +109 -85
  79. package/src/scripts/init-project.js +138 -67
  80. package/src/scripts/utils/write-content.js +67 -52
  81. package/src/scripts/wordpress/publish-to-wordpress.js +128 -128
  82. package/src/templates/claude/CLAUDE.md +19 -12
  83. package/hooks/hooks.json +0 -26
  84. package/skills/content-coordinator/SKILL.md +0 -130
  85. package/skills/content-enrichment/SKILL.md +0 -80
  86. package/skills/content-writer/SKILL.md +0 -285
  87. package/skills/skill-builder/SKILL.md +0 -417
  88. package/skills/visual-generator/SKILL.md +0 -140
  89. /package/skills/{content-writer → myai-content-writer}/agents/editor-agent.md +0 -0
  90. /package/skills/{content-writer → myai-content-writer}/agents/planner-agent.md +0 -0
  91. /package/skills/{content-writer → myai-content-writer}/agents/research-agent.md +0 -0
  92. /package/skills/{content-writer → myai-content-writer}/agents/seo-agent.md +0 -0
  93. /package/skills/{content-writer → myai-content-writer}/agents/visual-planner-agent.md +0 -0
  94. /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(new Error("Missing required OpenStack environment variables. Please configure using /myai-configure openstack"));
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({ success: true, output: stdout.trim(), stderr: stderr.trim() });
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.split("\n").filter(line => line.trim() && !line.startsWith("+"));
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].split("|")
117
- .map(h => h.trim().toLowerCase().replace(/\s+/g, "_"))
118
- .filter(h => h);
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.split("|").map(v => v.trim()).filter(v => v !== "");
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('gist.github.com') && !url.includes('/raw')) {
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('https') ? https : http;
157
+ const protocol = fetchUrl.startsWith("https") ? https : http;
144
158
 
145
- const request = protocol.get(fetchUrl, {
146
- headers: {
147
- 'User-Agent': 'MyAIDev-Method-OpenStack-MCP/1.0'
148
- }
149
- }, (response) => {
150
- // Handle redirects
151
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
152
- fetchFromUrl(response.headers.location).then(resolve).catch(reject);
153
- return;
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
- if (response.statusCode !== 200) {
157
- reject(new Error(`HTTP ${response.statusCode}: Failed to fetch ${fetchUrl}`));
158
- return;
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
- let data = '';
162
- response.on('data', chunk => data += chunk);
163
- response.on('end', () => resolve(data));
164
- });
186
+ let data = "";
187
+ response.on("data", (chunk) => (data += chunk));
188
+ response.on("end", () => resolve(data));
189
+ },
190
+ );
165
191
 
166
- request.on('error', reject);
192
+ request.on("error", reject);
167
193
  request.setTimeout(30000, () => {
168
194
  request.destroy();
169
- reject(new Error('Request timeout'));
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: 'inline' };
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, 'utf8');
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 (DEFAULT_CLOUD_INIT.startsWith('http://') || DEFAULT_CLOUD_INIT.startsWith('https://')) {
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, 'utf8');
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("os_cloud_init_fetch", {
225
- title: "Fetch Cloud-Init Configuration",
226
- description: "Fetch and preview cloud-init configuration from URL, file, or default",
227
- inputSchema: {
228
- type: "object",
229
- properties: {
230
- url: {
231
- type: "string",
232
- description: "URL to fetch cloud-init from (supports GitHub Gist URLs)"
233
- },
234
- file: {
235
- type: "string",
236
- description: "Local file path to cloud-init script"
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
- use_default: {
239
- type: "boolean",
240
- description: "Fetch from the default CLOUD_INIT environment variable",
241
- default: false
242
- }
277
+ additionalProperties: false,
243
278
  },
244
- additionalProperties: false
245
- }
246
- }, async (params) => {
247
- try {
248
- let content = null;
249
- let source = null;
250
-
251
- if (params.url) {
252
- content = await fetchFromUrl(params.url);
253
- source = `url:${params.url}`;
254
- } else if (params.file) {
255
- content = fs.readFileSync(params.file, 'utf8');
256
- source = `file:${params.file}`;
257
- } else if (params.use_default && DEFAULT_CLOUD_INIT) {
258
- if (DEFAULT_CLOUD_INIT.startsWith('http://') || DEFAULT_CLOUD_INIT.startsWith('https://')) {
259
- content = await fetchFromUrl(DEFAULT_CLOUD_INIT);
260
- source = `default_url:${DEFAULT_CLOUD_INIT}`;
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
- content = fs.readFileSync(DEFAULT_CLOUD_INIT, 'utf8');
263
- source = `default_file:${DEFAULT_CLOUD_INIT}`;
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
- } else if (params.use_default && !DEFAULT_CLOUD_INIT) {
335
+
266
336
  return {
267
- content: [{
268
- type: "text",
269
- text: JSON.stringify({
270
- success: false,
271
- error: "No default CLOUD_INIT configured in environment"
272
- }, null, 2)
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
- } else {
356
+ } catch (error) {
276
357
  return {
277
- content: [{
278
- type: "text",
279
- text: JSON.stringify({
280
- success: false,
281
- error: "Please provide url, file, or set use_default to true"
282
- }, null, 2)
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
- type: "text",
290
- text: JSON.stringify({
291
- success: true,
292
- source,
293
- content_length: content.length,
294
- preview: content.substring(0, 2000) + (content.length > 2000 ? '\n... (truncated)' : ''),
295
- full_content: content
296
- }, null, 2)
297
- }]
298
- };
299
- } catch (error) {
300
- return {
301
- content: [{
302
- type: "text",
303
- text: JSON.stringify({
304
- success: false,
305
- error: error.message
306
- }, null, 2)
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("os_session_create", {
345
- title: "Create OpenStack Session",
346
- description: "Create a new session for tracking OpenStack operations",
347
- inputSchema: {
348
- type: "object",
349
- properties: {
350
- description: {
351
- type: "string",
352
- description: "Description of the session purpose"
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
- additionalProperties: false
356
- }
357
- }, async (params) => {
358
- const sessionId = generateSessionId();
359
- sessions.set(sessionId, {
360
- id: sessionId,
361
- description: params.description || "OpenStack operations session",
362
- created: new Date().toISOString(),
363
- operations: []
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
- return {
367
- content: [{
368
- type: "text",
369
- text: JSON.stringify({
370
- success: true,
371
- session_id: sessionId,
372
- message: "Session created successfully"
373
- }, null, 2)
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("os_health_check", {
380
- title: "OpenStack Health Check",
381
- description: "Check OpenStack API connectivity and authentication",
382
- inputSchema: {
383
- type: "object",
384
- properties: {},
385
- additionalProperties: false
386
- }
387
- }, async () => {
388
- try {
389
- const result = await runOpenStackCommand(["token", "issue", "-f", "json"]);
390
- const tokenInfo = JSON.parse(result.output);
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
- return {
393
- content: [{
394
- type: "text",
395
- text: JSON.stringify({
396
- success: true,
397
- message: "OpenStack API is responding",
398
- auth_url: OS_AUTH_URL,
399
- project_id: OS_PROJECT_ID,
400
- region: OS_REGION_NAME,
401
- token_expires: tokenInfo.expires,
402
- server_version: "1.0.0"
403
- }, null, 2)
404
- }]
405
- };
406
- } catch (error) {
407
- return {
408
- content: [{
409
- type: "text",
410
- text: JSON.stringify({
411
- success: false,
412
- error: error.message,
413
- suggestion: "Check your OpenStack credentials. Run /myai-configure openstack to reconfigure."
414
- }, null, 2)
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("os_image_list", {
422
- title: "List OpenStack Images",
423
- description: "List available VM images in OpenStack",
424
- inputSchema: {
425
- type: "object",
426
- properties: {
427
- limit: {
428
- type: "number",
429
- description: "Maximum number of images to return",
430
- default: 20
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
- status: {
433
- type: "string",
434
- enum: ["active", "queued", "saving", "deleted"],
435
- description: "Filter by image status"
436
- }
550
+ additionalProperties: false,
437
551
  },
438
- additionalProperties: false
439
- }
440
- }, async (params) => {
441
- try {
442
- const args = ["image", "list", "-f", "json"];
443
- if (params.limit) args.push("--limit", params.limit.toString());
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
- const result = await runOpenStackCommand(args);
447
- const images = JSON.parse(result.output);
559
+ const result = await runOpenStackCommand(args);
560
+ const images = JSON.parse(result.output);
448
561
 
449
- return {
450
- content: [{
451
- type: "text",
452
- text: JSON.stringify({
453
- success: true,
454
- count: images.length,
455
- images: images.map(img => ({
456
- id: img.ID,
457
- name: img.Name,
458
- status: img.Status
459
- }))
460
- }, null, 2)
461
- }]
462
- };
463
- } catch (error) {
464
- return {
465
- content: [{
466
- type: "text",
467
- text: JSON.stringify({
468
- success: false,
469
- error: error.message
470
- }, null, 2)
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("os_flavor_list", {
478
- title: "List OpenStack Flavors",
479
- description: "List available VM flavors (instance sizes) in OpenStack",
480
- inputSchema: {
481
- type: "object",
482
- properties: {},
483
- additionalProperties: false
484
- }
485
- }, async () => {
486
- try {
487
- const result = await runOpenStackCommand(["flavor", "list", "-f", "json"]);
488
- const flavors = JSON.parse(result.output);
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
- return {
491
- content: [{
492
- type: "text",
493
- text: JSON.stringify({
494
- success: true,
495
- count: flavors.length,
496
- flavors: flavors.map(f => ({
497
- id: f.ID,
498
- name: f.Name,
499
- vcpus: f.VCPUs,
500
- ram_mb: f.RAM,
501
- disk_gb: f.Disk
502
- }))
503
- }, null, 2)
504
- }]
505
- };
506
- } catch (error) {
507
- return {
508
- content: [{
509
- type: "text",
510
- text: JSON.stringify({
511
- success: false,
512
- error: error.message
513
- }, null, 2)
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("os_network_list", {
521
- title: "List OpenStack Networks",
522
- description: "List available networks in OpenStack",
523
- inputSchema: {
524
- type: "object",
525
- properties: {},
526
- additionalProperties: false
527
- }
528
- }, async () => {
529
- try {
530
- const result = await runOpenStackCommand(["network", "list", "-f", "json"]);
531
- const networks = JSON.parse(result.output);
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
- return {
534
- content: [{
535
- type: "text",
536
- text: JSON.stringify({
537
- success: true,
538
- count: networks.length,
539
- networks: networks.map(n => ({
540
- id: n.ID,
541
- name: n.Name,
542
- subnets: n.Subnets
543
- }))
544
- }, null, 2)
545
- }]
546
- };
547
- } catch (error) {
548
- return {
549
- content: [{
550
- type: "text",
551
- text: JSON.stringify({
552
- success: false,
553
- error: error.message
554
- }, null, 2)
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("os_security_group_list", {
562
- title: "List Security Groups",
563
- description: "List available security groups in OpenStack",
564
- inputSchema: {
565
- type: "object",
566
- properties: {},
567
- additionalProperties: false
568
- }
569
- }, async () => {
570
- try {
571
- const result = await runOpenStackCommand(["security", "group", "list", "-f", "json"]);
572
- const groups = JSON.parse(result.output);
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
- return {
575
- content: [{
576
- type: "text",
577
- text: JSON.stringify({
578
- success: true,
579
- count: groups.length,
580
- security_groups: groups.map(g => ({
581
- id: g.ID,
582
- name: g.Name,
583
- description: g.Description
584
- }))
585
- }, null, 2)
586
- }]
587
- };
588
- } catch (error) {
589
- return {
590
- content: [{
591
- type: "text",
592
- text: JSON.stringify({
593
- success: false,
594
- error: error.message
595
- }, null, 2)
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("os_keypair_list", {
603
- title: "List SSH Keypairs",
604
- description: "List available SSH keypairs in OpenStack",
605
- inputSchema: {
606
- type: "object",
607
- properties: {},
608
- additionalProperties: false
609
- }
610
- }, async () => {
611
- try {
612
- const result = await runOpenStackCommand(["keypair", "list", "-f", "json"]);
613
- const keypairs = JSON.parse(result.output);
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
- return {
616
- content: [{
617
- type: "text",
618
- text: JSON.stringify({
619
- success: true,
620
- count: keypairs.length,
621
- keypairs: keypairs.map(k => ({
622
- name: k.Name,
623
- fingerprint: k.Fingerprint,
624
- type: k.Type
625
- }))
626
- }, null, 2)
627
- }]
628
- };
629
- } catch (error) {
630
- return {
631
- content: [{
632
- type: "text",
633
- text: JSON.stringify({
634
- success: false,
635
- error: error.message
636
- }, null, 2)
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("os_keypair_create", {
644
- title: "Create SSH Keypair",
645
- description: "Create a new SSH keypair for VM access",
646
- inputSchema: {
647
- type: "object",
648
- properties: {
649
- name: {
650
- type: "string",
651
- description: "Name for the keypair"
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
- public_key_file: {
654
- type: "string",
655
- description: "Path to existing public key file (optional, will generate if not provided)"
656
- }
872
+ required: ["name"],
873
+ additionalProperties: false,
657
874
  },
658
- required: ["name"],
659
- additionalProperties: false
660
- }
661
- }, async (params) => {
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
- if (params.public_key_file) {
666
- args.push("--public-key", params.public_key_file);
667
- }
880
+ if (params.public_key_file) {
881
+ args.push("--public-key", params.public_key_file);
882
+ }
668
883
 
669
- args.push("-f", "json");
884
+ args.push("-f", "json");
670
885
 
671
- const result = await runOpenStackCommand(args);
672
- const keypair = JSON.parse(result.output);
886
+ const result = await runOpenStackCommand(args);
887
+ const keypair = JSON.parse(result.output);
673
888
 
674
- const response = {
675
- success: true,
676
- keypair: {
677
- name: keypair.name || params.name,
678
- fingerprint: keypair.fingerprint
679
- },
680
- message: "Keypair created successfully"
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
- // If a new keypair was generated (no public key provided), include the private key
684
- if (!params.public_key_file && keypair.private_key) {
685
- response.private_key = keypair.private_key;
686
- response.warning = "Save this private key securely. It will not be shown again!";
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
- return {
690
- content: [{
691
- type: "text",
692
- text: JSON.stringify(response, null, 2)
693
- }]
694
- };
695
- } catch (error) {
696
- return {
697
- content: [{
698
- type: "text",
699
- text: JSON.stringify({
700
- success: false,
701
- error: error.message
702
- }, null, 2)
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("os_server_list", {
710
- title: "List Servers",
711
- description: "List all VMs/servers in the project",
712
- inputSchema: {
713
- type: "object",
714
- properties: {
715
- status: {
716
- type: "string",
717
- enum: ["ACTIVE", "BUILD", "ERROR", "SHUTOFF", "SUSPENDED", "PAUSED"],
718
- description: "Filter by server status"
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
- name: {
721
- type: "string",
722
- description: "Filter by server name (partial match)"
723
- }
952
+ additionalProperties: false,
724
953
  },
725
- additionalProperties: false
726
- }
727
- }, async (params) => {
728
- try {
729
- const args = ["server", "list", "-f", "json"];
730
- if (params.status) args.push("--status", params.status);
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
- const result = await runOpenStackCommand(args);
734
- const servers = JSON.parse(result.output);
961
+ const result = await runOpenStackCommand(args);
962
+ const servers = JSON.parse(result.output);
735
963
 
736
- return {
737
- content: [{
738
- type: "text",
739
- text: JSON.stringify({
740
- success: true,
741
- count: servers.length,
742
- servers: servers.map(s => ({
743
- id: s.ID,
744
- name: s.Name,
745
- status: s.Status,
746
- networks: s.Networks,
747
- image: s.Image,
748
- flavor: s.Flavor
749
- }))
750
- }, null, 2)
751
- }]
752
- };
753
- } catch (error) {
754
- return {
755
- content: [{
756
- type: "text",
757
- text: JSON.stringify({
758
- success: false,
759
- error: error.message
760
- }, null, 2)
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("os_server_create", {
768
- title: "Create Server",
769
- description: "Create a new VM/server in OpenStack with optional cloud-init configuration",
770
- inputSchema: {
771
- type: "object",
772
- properties: {
773
- name: {
774
- type: "string",
775
- description: "Name for the new server"
776
- },
777
- image: {
778
- type: "string",
779
- description: "Image ID or name to use"
780
- },
781
- flavor: {
782
- type: "string",
783
- description: "Flavor ID or name (instance size)"
784
- },
785
- network: {
786
- type: "string",
787
- description: "Network ID or name to attach"
788
- },
789
- keypair: {
790
- type: "string",
791
- description: "SSH keypair name for access"
792
- },
793
- security_groups: {
794
- type: "array",
795
- items: { type: "string" },
796
- description: "Security group names to apply"
797
- },
798
- user_data: {
799
- type: "string",
800
- description: "Inline cloud-init user data script (plain text YAML)"
801
- },
802
- cloud_init_url: {
803
- type: "string",
804
- description: "URL to fetch cloud-init script from (supports GitHub Gist URLs)"
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
- cloud_init_file: {
807
- type: "string",
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
- required: ["name", "image", "flavor"],
826
- additionalProperties: false
827
- }
828
- }, async (params) => {
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
- if (params.network) {
838
- args.push("--network", params.network);
839
- }
1079
+ try {
1080
+ const args = ["server", "create"];
1081
+ args.push("--image", params.image);
1082
+ args.push("--flavor", params.flavor);
840
1083
 
841
- if (params.keypair) {
842
- args.push("--key-name", params.keypair);
843
- }
1084
+ if (params.network) {
1085
+ args.push("--network", params.network);
1086
+ }
844
1087
 
845
- if (params.security_groups && params.security_groups.length > 0) {
846
- params.security_groups.forEach(sg => {
847
- args.push("--security-group", sg);
848
- });
849
- }
1088
+ if (params.keypair) {
1089
+ args.push("--key-name", params.keypair);
1090
+ }
850
1091
 
851
- // Resolve cloud-init from various sources
852
- cloudInitInfo = await resolveCloudInit(params);
853
- if (cloudInitInfo) {
854
- tmpFile = `/tmp/userdata-${Date.now()}.txt`;
855
- fs.writeFileSync(tmpFile, cloudInitInfo.content);
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
- if (params.availability_zone) {
860
- args.push("--availability-zone", params.availability_zone);
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
- if (params.wait !== false) {
864
- args.push("--wait");
865
- }
1106
+ if (params.availability_zone) {
1107
+ args.push("--availability-zone", params.availability_zone);
1108
+ }
866
1109
 
867
- args.push("-f", "json");
868
- args.push(params.name);
1110
+ if (params.wait !== false) {
1111
+ args.push("--wait");
1112
+ }
869
1113
 
870
- const result = await runOpenStackCommand(args, 300000); // 5 minute timeout for server creation
871
- const serverResult = JSON.parse(result.output);
1114
+ args.push("-f", "json");
1115
+ args.push(params.name);
872
1116
 
873
- // Log operation
874
- operationHistory.push({
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
- const response = {
883
- success: true,
884
- server: {
885
- id: serverResult.id,
886
- name: serverResult.name,
887
- status: serverResult.status,
888
- addresses: serverResult.addresses,
889
- image: serverResult.image,
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
- // Include cloud-init info in response
898
- if (cloudInitInfo) {
899
- response.cloud_init = {
900
- source: cloudInitInfo.source,
901
- applied: true
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
- return {
906
- content: [{
907
- type: "text",
908
- text: JSON.stringify(response, null, 2)
909
- }]
910
- };
911
- } catch (error) {
912
- return {
913
- content: [{
914
- type: "text",
915
- text: JSON.stringify({
916
- success: false,
917
- error: error.message
918
- }, null, 2)
919
- }]
920
- };
921
- } finally {
922
- // Clean up temp file
923
- if (tmpFile) {
924
- try {
925
- fs.unlinkSync(tmpFile);
926
- } catch (e) {}
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("os_server_show", {
933
- title: "Show Server Details",
934
- description: "Get detailed information about a specific server",
935
- inputSchema: {
936
- type: "object",
937
- properties: {
938
- server: {
939
- type: "string",
940
- description: "Server ID or name"
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
- required: ["server"],
944
- additionalProperties: false
945
- }
946
- }, async (params) => {
947
- try {
948
- const result = await runOpenStackCommand(["server", "show", params.server, "-f", "json"]);
949
- const server = JSON.parse(result.output);
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
- return {
952
- content: [{
953
- type: "text",
954
- text: JSON.stringify({
955
- success: true,
956
- server: {
957
- id: server.id,
958
- name: server.name,
959
- status: server.status,
960
- addresses: server.addresses,
961
- image: server.image,
962
- flavor: server.flavor,
963
- key_name: server.key_name,
964
- security_groups: server.security_groups,
965
- created: server.created,
966
- updated: server.updated,
967
- availability_zone: server.availability_zone,
968
- host: server["OS-EXT-SRV-ATTR:host"],
969
- power_state: server["OS-EXT-STS:power_state"],
970
- task_state: server["OS-EXT-STS:task_state"]
971
- }
972
- }, null, 2)
973
- }]
974
- };
975
- } catch (error) {
976
- return {
977
- content: [{
978
- type: "text",
979
- text: JSON.stringify({
980
- success: false,
981
- error: error.message
982
- }, null, 2)
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("os_server_delete", {
990
- title: "Delete Server",
991
- description: "Delete a VM/server from OpenStack",
992
- inputSchema: {
993
- type: "object",
994
- properties: {
995
- server: {
996
- type: "string",
997
- description: "Server ID or name to delete"
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
- wait: {
1000
- type: "boolean",
1001
- description: "Wait for deletion to complete",
1002
- default: true
1003
- }
1285
+ required: ["server"],
1286
+ additionalProperties: false,
1004
1287
  },
1005
- required: ["server"],
1006
- additionalProperties: false
1007
- }
1008
- }, async (params) => {
1009
- try {
1010
- const args = ["server", "delete", params.server];
1011
- if (params.wait !== false) {
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
- await runOpenStackCommand(args, 180000); // 3 minute timeout
1296
+ await runOpenStackCommand(args, 180000); // 3 minute timeout
1016
1297
 
1017
- operationHistory.push({
1018
- type: "server_delete",
1019
- server: params.server,
1020
- timestamp: new Date().toISOString()
1021
- });
1298
+ operationHistory.push({
1299
+ type: "server_delete",
1300
+ server: params.server,
1301
+ timestamp: new Date().toISOString(),
1302
+ });
1022
1303
 
1023
- return {
1024
- content: [{
1025
- type: "text",
1026
- text: JSON.stringify({
1027
- success: true,
1028
- message: `Server '${params.server}' deleted successfully`
1029
- }, null, 2)
1030
- }]
1031
- };
1032
- } catch (error) {
1033
- return {
1034
- content: [{
1035
- type: "text",
1036
- text: JSON.stringify({
1037
- success: false,
1038
- error: error.message
1039
- }, null, 2)
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("os_server_start", {
1047
- title: "Start Server",
1048
- description: "Start a stopped server",
1049
- inputSchema: {
1050
- type: "object",
1051
- properties: {
1052
- server: {
1053
- type: "string",
1054
- description: "Server ID or name to start"
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
- required: ["server"],
1058
- additionalProperties: false
1059
- }
1060
- }, async (params) => {
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
- return {
1065
- content: [{
1066
- type: "text",
1067
- text: JSON.stringify({
1068
- success: true,
1069
- message: `Server '${params.server}' start initiated`
1070
- }, null, 2)
1071
- }]
1072
- };
1073
- } catch (error) {
1074
- return {
1075
- content: [{
1076
- type: "text",
1077
- text: JSON.stringify({
1078
- success: false,
1079
- error: error.message
1080
- }, null, 2)
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("os_server_stop", {
1088
- title: "Stop Server",
1089
- description: "Stop a running server",
1090
- inputSchema: {
1091
- type: "object",
1092
- properties: {
1093
- server: {
1094
- type: "string",
1095
- description: "Server ID or name to stop"
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
- required: ["server"],
1099
- additionalProperties: false
1100
- }
1101
- }, async (params) => {
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
- return {
1106
- content: [{
1107
- type: "text",
1108
- text: JSON.stringify({
1109
- success: true,
1110
- message: `Server '${params.server}' stop initiated`
1111
- }, null, 2)
1112
- }]
1113
- };
1114
- } catch (error) {
1115
- return {
1116
- content: [{
1117
- type: "text",
1118
- text: JSON.stringify({
1119
- success: false,
1120
- error: error.message
1121
- }, null, 2)
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("os_server_reboot", {
1129
- title: "Reboot Server",
1130
- description: "Reboot a server (soft or hard reboot)",
1131
- inputSchema: {
1132
- type: "object",
1133
- properties: {
1134
- server: {
1135
- type: "string",
1136
- description: "Server ID or name to reboot"
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
- hard: {
1139
- type: "boolean",
1140
- description: "Perform hard reboot instead of soft reboot",
1141
- default: false
1142
- }
1472
+ required: ["server"],
1473
+ additionalProperties: false,
1143
1474
  },
1144
- required: ["server"],
1145
- additionalProperties: false
1146
- }
1147
- }, async (params) => {
1148
- try {
1149
- const args = ["server", "reboot"];
1150
- if (params.hard) {
1151
- args.push("--hard");
1152
- } else {
1153
- args.push("--soft");
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
- await runOpenStackCommand(args);
1486
+ await runOpenStackCommand(args);
1158
1487
 
1159
- return {
1160
- content: [{
1161
- type: "text",
1162
- text: JSON.stringify({
1163
- success: true,
1164
- message: `Server '${params.server}' ${params.hard ? 'hard' : 'soft'} reboot initiated`
1165
- }, null, 2)
1166
- }]
1167
- };
1168
- } catch (error) {
1169
- return {
1170
- content: [{
1171
- type: "text",
1172
- text: JSON.stringify({
1173
- success: false,
1174
- error: error.message
1175
- }, null, 2)
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("os_server_console", {
1183
- title: "Get Server Console",
1184
- description: "Get console URL for accessing server",
1185
- inputSchema: {
1186
- type: "object",
1187
- properties: {
1188
- server: {
1189
- type: "string",
1190
- description: "Server ID or name"
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
- type: {
1193
- type: "string",
1194
- enum: ["novnc", "xvpvnc", "spice", "rdp", "serial"],
1195
- description: "Console type",
1196
- default: "novnc"
1197
- }
1543
+ required: ["server"],
1544
+ additionalProperties: false,
1198
1545
  },
1199
- required: ["server"],
1200
- additionalProperties: false
1201
- }
1202
- }, async (params) => {
1203
- try {
1204
- const result = await runOpenStackCommand([
1205
- "console", "url", "show",
1206
- "--" + (params.type || "novnc"),
1207
- params.server,
1208
- "-f", "json"
1209
- ]);
1210
- const console_info = JSON.parse(result.output);
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
- return {
1213
- content: [{
1214
- type: "text",
1215
- text: JSON.stringify({
1216
- success: true,
1217
- console: {
1218
- type: console_info.type,
1219
- url: console_info.url
1220
- }
1221
- }, null, 2)
1222
- }]
1223
- };
1224
- } catch (error) {
1225
- return {
1226
- content: [{
1227
- type: "text",
1228
- text: JSON.stringify({
1229
- success: false,
1230
- error: error.message
1231
- }, null, 2)
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("os_floating_ip_create", {
1239
- title: "Create Floating IP",
1240
- description: "Create a new floating IP from an external network",
1241
- inputSchema: {
1242
- type: "object",
1243
- properties: {
1244
- network: {
1245
- type: "string",
1246
- description: "External network name or ID"
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
- required: ["network"],
1250
- additionalProperties: false
1251
- }
1252
- }, async (params) => {
1253
- try {
1254
- const result = await runOpenStackCommand([
1255
- "floating", "ip", "create",
1256
- params.network,
1257
- "-f", "json"
1258
- ]);
1259
- const floating_ip = JSON.parse(result.output);
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
- return {
1262
- content: [{
1263
- type: "text",
1264
- text: JSON.stringify({
1265
- success: true,
1266
- floating_ip: {
1267
- id: floating_ip.id,
1268
- ip: floating_ip.floating_ip_address,
1269
- network: floating_ip.floating_network_id,
1270
- status: floating_ip.status
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
- message: "Floating IP created successfully"
1273
- }, null, 2)
1274
- }]
1275
- };
1276
- } catch (error) {
1277
- return {
1278
- content: [{
1279
- type: "text",
1280
- text: JSON.stringify({
1281
- success: false,
1282
- error: error.message
1283
- }, null, 2)
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("os_floating_ip_list", {
1291
- title: "List Floating IPs",
1292
- description: "List all floating IPs in the project",
1293
- inputSchema: {
1294
- type: "object",
1295
- properties: {},
1296
- additionalProperties: false
1297
- }
1298
- }, async () => {
1299
- try {
1300
- const result = await runOpenStackCommand(["floating", "ip", "list", "-f", "json"]);
1301
- const floating_ips = JSON.parse(result.output);
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
- return {
1304
- content: [{
1305
- type: "text",
1306
- text: JSON.stringify({
1307
- success: true,
1308
- count: floating_ips.length,
1309
- floating_ips: floating_ips.map(f => ({
1310
- id: f.ID,
1311
- ip: f["Floating IP Address"],
1312
- fixed_ip: f["Fixed IP Address"],
1313
- port: f.Port,
1314
- status: f.Status
1315
- }))
1316
- }, null, 2)
1317
- }]
1318
- };
1319
- } catch (error) {
1320
- return {
1321
- content: [{
1322
- type: "text",
1323
- text: JSON.stringify({
1324
- success: false,
1325
- error: error.message
1326
- }, null, 2)
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("os_server_add_floating_ip", {
1334
- title: "Add Floating IP to Server",
1335
- description: "Associate a floating IP with a server",
1336
- inputSchema: {
1337
- type: "object",
1338
- properties: {
1339
- server: {
1340
- type: "string",
1341
- description: "Server ID or name"
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
- floating_ip: {
1344
- type: "string",
1345
- description: "Floating IP address to associate"
1346
- }
1752
+ required: ["server", "floating_ip"],
1753
+ additionalProperties: false,
1347
1754
  },
1348
- required: ["server", "floating_ip"],
1349
- additionalProperties: false
1350
- }
1351
- }, async (params) => {
1352
- try {
1353
- await runOpenStackCommand([
1354
- "server", "add", "floating", "ip",
1355
- params.server,
1356
- params.floating_ip
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
- return {
1360
- content: [{
1361
- type: "text",
1362
- text: JSON.stringify({
1363
- success: true,
1364
- message: `Floating IP '${params.floating_ip}' associated with server '${params.server}'`
1365
- }, null, 2)
1366
- }]
1367
- };
1368
- } catch (error) {
1369
- return {
1370
- content: [{
1371
- type: "text",
1372
- text: JSON.stringify({
1373
- success: false,
1374
- error: error.message
1375
- }, null, 2)
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("os_volume_list", {
1383
- title: "List Volumes",
1384
- description: "List all volumes in the project",
1385
- inputSchema: {
1386
- type: "object",
1387
- properties: {},
1388
- additionalProperties: false
1389
- }
1390
- }, async () => {
1391
- try {
1392
- const result = await runOpenStackCommand(["volume", "list", "-f", "json"]);
1393
- const volumes = JSON.parse(result.output);
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
- return {
1396
- content: [{
1397
- type: "text",
1398
- text: JSON.stringify({
1399
- success: true,
1400
- count: volumes.length,
1401
- volumes: volumes.map(v => ({
1402
- id: v.ID,
1403
- name: v.Name,
1404
- status: v.Status,
1405
- size_gb: v.Size,
1406
- attached_to: v["Attached to"]
1407
- }))
1408
- }, null, 2)
1409
- }]
1410
- };
1411
- } catch (error) {
1412
- return {
1413
- content: [{
1414
- type: "text",
1415
- text: JSON.stringify({
1416
- success: false,
1417
- error: error.message
1418
- }, null, 2)
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("os_volume_create", {
1426
- title: "Create Volume",
1427
- description: "Create a new block storage volume",
1428
- inputSchema: {
1429
- type: "object",
1430
- properties: {
1431
- name: {
1432
- type: "string",
1433
- description: "Volume name"
1434
- },
1435
- size: {
1436
- type: "number",
1437
- description: "Volume size in GB"
1438
- },
1439
- type: {
1440
- type: "string",
1441
- description: "Volume type (optional)"
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
- image: {
1444
- type: "string",
1445
- description: "Image ID to create volume from (optional)"
1446
- }
1892
+ required: ["name", "size"],
1893
+ additionalProperties: false,
1447
1894
  },
1448
- required: ["name", "size"],
1449
- additionalProperties: false
1450
- }
1451
- }, async (params) => {
1452
- try {
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
- if (params.type) {
1457
- args.push("--type", params.type);
1458
- }
1901
+ if (params.type) {
1902
+ args.push("--type", params.type);
1903
+ }
1459
1904
 
1460
- if (params.image) {
1461
- args.push("--image", params.image);
1462
- }
1905
+ if (params.image) {
1906
+ args.push("--image", params.image);
1907
+ }
1463
1908
 
1464
- args.push("-f", "json");
1465
- args.push(params.name);
1909
+ args.push("-f", "json");
1910
+ args.push(params.name);
1466
1911
 
1467
- const result = await runOpenStackCommand(args, 180000);
1468
- const volume = JSON.parse(result.output);
1912
+ const result = await runOpenStackCommand(args, 180000);
1913
+ const volume = JSON.parse(result.output);
1469
1914
 
1470
- return {
1471
- content: [{
1472
- type: "text",
1473
- text: JSON.stringify({
1474
- success: true,
1475
- volume: {
1476
- id: volume.id,
1477
- name: volume.name,
1478
- size: volume.size,
1479
- status: volume.status,
1480
- type: volume.type
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
- message: "Volume created successfully"
1483
- }, null, 2)
1484
- }]
1485
- };
1486
- } catch (error) {
1487
- return {
1488
- content: [{
1489
- type: "text",
1490
- text: JSON.stringify({
1491
- success: false,
1492
- error: error.message
1493
- }, null, 2)
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("os_server_add_volume", {
1501
- title: "Attach Volume to Server",
1502
- description: "Attach a volume to a server",
1503
- inputSchema: {
1504
- type: "object",
1505
- properties: {
1506
- server: {
1507
- type: "string",
1508
- description: "Server ID or name"
1509
- },
1510
- volume: {
1511
- type: "string",
1512
- description: "Volume ID or name"
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
- device: {
1515
- type: "string",
1516
- description: "Device path (e.g., /dev/vdb), auto-assigned if not specified"
1517
- }
1980
+ required: ["server", "volume"],
1981
+ additionalProperties: false,
1518
1982
  },
1519
- required: ["server", "volume"],
1520
- additionalProperties: false
1521
- }
1522
- }, async (params) => {
1523
- try {
1524
- const args = ["server", "add", "volume"];
1983
+ },
1984
+ async (params) => {
1985
+ try {
1986
+ const args = ["server", "add", "volume"];
1525
1987
 
1526
- if (params.device) {
1527
- args.push("--device", params.device);
1528
- }
1988
+ if (params.device) {
1989
+ args.push("--device", params.device);
1990
+ }
1529
1991
 
1530
- args.push(params.server);
1531
- args.push(params.volume);
1992
+ args.push(params.server);
1993
+ args.push(params.volume);
1532
1994
 
1533
- await runOpenStackCommand(args);
1995
+ await runOpenStackCommand(args);
1534
1996
 
1535
- return {
1536
- content: [{
1537
- type: "text",
1538
- text: JSON.stringify({
1539
- success: true,
1540
- message: `Volume '${params.volume}' attached to server '${params.server}'`
1541
- }, null, 2)
1542
- }]
1543
- };
1544
- } catch (error) {
1545
- return {
1546
- content: [{
1547
- type: "text",
1548
- text: JSON.stringify({
1549
- success: false,
1550
- error: error.message
1551
- }, null, 2)
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("os_operation_history", {
1559
- title: "Get Operation History",
1560
- description: "Get history of OpenStack operations performed",
1561
- inputSchema: {
1562
- type: "object",
1563
- properties: {
1564
- limit: {
1565
- type: "number",
1566
- description: "Maximum number of operations to return",
1567
- default: 20
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
- additionalProperties: false
1571
- }
1572
- }, async (params) => {
1573
- const limit = params.limit || 20;
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
- return {
1577
- content: [{
1578
- type: "text",
1579
- text: JSON.stringify({
1580
- success: true,
1581
- count: history.length,
1582
- operations: history
1583
- }, null, 2)
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);