it-tools-mcp
Version:
MCP-compliant server access to over 100 IT tools and utilities commonly used by developers, system administrators, and IT professionals.
136 lines (135 loc) • 5.76 kB
JavaScript
import { z } from "zod";
export function registerValidateAnsiblePlaybook(server) {
server.registerTool("validate_ansible_playbook", {
inputSchema: {
playbook: z.string().describe("Ansible playbook YAML content"),
},
// VS Code compliance annotations
annotations: {
title: "Validate Ansible Playbook",
readOnlyHint: false
}
}, async ({ playbook }) => {
if (!playbook?.trim()) {
return {
content: [
{
type: "text",
text: "Please provide playbook content to validate",
},
],
};
}
try {
const yaml = await import('js-yaml');
let parsed;
try {
parsed = yaml.load(playbook);
}
catch (yamlError) {
return {
content: [
{
type: "text",
text: `YAML syntax error: ${yamlError.message}`,
},
],
};
}
const errors = [];
const warnings = [];
// Basic playbook structure validation
if (!Array.isArray(parsed)) {
errors.push('Playbook must be a YAML array of plays');
}
else {
// Validate each play
parsed.forEach((play, index) => {
const playNum = index + 1;
if (typeof play !== 'object' || play === null) {
errors.push(`Play ${playNum}: must be an object`);
return;
}
// Check required fields
if (!play.name && !play.hosts) {
warnings.push(`Play ${playNum}: should have either 'name' or 'hosts' field`);
}
if (!play.hosts) {
errors.push(`Play ${playNum}: missing required 'hosts' field`);
}
// Validate tasks
if (play.tasks) {
if (!Array.isArray(play.tasks)) {
errors.push(`Play ${playNum}: 'tasks' must be an array`);
}
else {
play.tasks.forEach((task, taskIndex) => {
const taskNum = taskIndex + 1;
if (typeof task !== 'object' || task === null) {
errors.push(`Play ${playNum}, Task ${taskNum}: must be an object`);
return;
}
// Check for task action
const taskActions = Object.keys(task).filter(key => !['name', 'when', 'tags', 'become', 'become_user', 'vars', 'register', 'failed_when', 'changed_when', 'ignore_errors', 'notify'].includes(key));
if (taskActions.length === 0) {
errors.push(`Play ${playNum}, Task ${taskNum}: no action module specified`);
}
else if (taskActions.length > 1) {
warnings.push(`Play ${playNum}, Task ${taskNum}: multiple action modules found: ${taskActions.join(', ')}`);
}
if (!task.name) {
warnings.push(`Play ${playNum}, Task ${taskNum}: should have a 'name' field for better readability`);
}
});
}
}
// Validate handlers
if (play.handlers) {
if (!Array.isArray(play.handlers)) {
errors.push(`Play ${playNum}: 'handlers' must be an array`);
}
}
// Check for common variables sections
if (play.vars && typeof play.vars !== 'object') {
errors.push(`Play ${playNum}: 'vars' must be an object`);
}
if (play.vars_files && !Array.isArray(play.vars_files)) {
errors.push(`Play ${playNum}: 'vars_files' must be an array`);
}
});
}
let result = '';
if (errors.length === 0 && warnings.length === 0) {
result = '✅ Playbook validation passed! No errors or warnings found.';
}
else {
if (errors.length > 0) {
result += `❌ Errors found:\n${errors.map(error => ` • ${error}`).join('\n')}`;
}
if (warnings.length > 0) {
if (result)
result += '\n\n';
result += `⚠️ Warnings:\n${warnings.map(warning => ` • ${warning}`).join('\n')}`;
}
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error validating playbook: ${error.message}`,
},
],
};
}
});
}