Skip to main content
Glama

MCP SysOperator

by tarnover
aws.ts32.9 kB
import { AnsibleExecutionError } from '../common/errors.js'; import { execAsync, createTempDirectory, writeTempFile, cleanupTempDirectory } from '../common/utils.js'; import { EC2InstanceOptions, S3Options, VPCOptions, CloudFormationOptions, IAMOptions, RDSOptions, Route53Options, ELBOptions, LambdaOptions, DynamicInventoryOptions, // Export schema definitions for use in index.ts EC2InstanceSchema, S3Schema, VPCSchema, CloudFormationSchema, IAMSchema, RDSSchema, Route53Schema, ELBSchema, LambdaSchema, DynamicInventorySchema } from '../common/types.js'; // Re-export schemas for use in index.ts export { EC2InstanceSchema, S3Schema, VPCSchema, CloudFormationSchema, IAMSchema, RDSSchema, Route53Schema, ELBSchema, LambdaSchema, DynamicInventorySchema }; import { AnsibleError } from '../common/errors.js'; import { verifyAwsCredentials } from '../common/utils.js'; /** * Convert object to YAML-formatted string for Ansible playbook */ const formatYamlParams = (params: Record<string, any>, indentation: number = 6): string => { // Filter out undefined/null values and format each key-value pair return Object.entries(params) .filter(([_, value]) => value !== undefined && value !== null) .map(([key, value]) => { const indent = ' '.repeat(indentation); let formattedValue; // Format based on value type if (typeof value === 'string') { // Basic YAML string escaping (double quotes, escape backslashes and double quotes) formattedValue = `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; } else if (Array.isArray(value) || typeof value === 'object') { // Use JSON.stringify for arrays and objects, assuming it's valid YAML subset formattedValue = JSON.stringify(value); } else { formattedValue = value; // Numbers, booleans } return `${indent}${key}: ${formattedValue}`; }) .join('\n'); }; /** * Helper function to execute a dynamically generated AWS playbook */ async function executeAwsPlaybook( operationName: string, playbookContent: string, extraParams: string = '', tempFiles: { filename: string, content: string }[] = [] // For additional files like templates, policies ): Promise<string> { let tempDir: string | undefined; try { // Create a unique temporary directory tempDir = await createTempDirectory(`ansible-aws-${operationName}`); // Write the main playbook file const playbookPath = await writeTempFile(tempDir, 'playbook.yml', playbookContent); // Write any additional temporary files for (const file of tempFiles) { await writeTempFile(tempDir, file.filename, file.content); } // Build the command const command = `ansible-playbook ${playbookPath} ${extraParams}`; console.error(`Executing: ${command}`); // Execute the playbook asynchronously const { stdout, stderr } = await execAsync(command); // Return stdout, or a success message if stdout is empty return stdout || `${operationName} completed successfully (no output).`; } catch (error: any) { // Handle execution errors const errorMessage = error.stderr || error.message || 'Unknown error'; throw new AnsibleExecutionError(`Ansible execution failed for ${operationName}: ${errorMessage}`, error.stderr); } finally { // Ensure cleanup happens even if errors occur if (tempDir) { await cleanupTempDirectory(tempDir); } } } /** * EC2 Instance Operations */ export async function ec2InstanceOperations(args: EC2InstanceOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, instanceIds, filters, instanceType, imageId, keyName, securityGroups, userData, count, tags, waitForCompletion, terminationProtection, ...restParams } = args; let playbookContent = `--- - name: AWS EC2 ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List EC2 instances amazon.aws.ec2_instance_info: region: "${region}" ${filters ? formatYamlParams({ filters }) : ''} register: ec2_info - name: Display instances debug: var: ec2_info.instances`; break; case 'create': playbookContent += ` - name: Create EC2 instance amazon.aws.ec2_instance: region: "${region}" state: present instance_type: "${instanceType}" image_id: "${imageId}" ${formatYamlParams({ key_name: keyName, security_groups: securityGroups, user_data: userData, exact_count: count, tags: tags, wait: waitForCompletion, termination_protection: terminationProtection, ...restParams })} register: ec2_create - name: Display created instance details debug: var: ec2_create`; break; case 'terminate': playbookContent += ` - name: Terminate EC2 instances amazon.aws.ec2_instance: region: "${region}" instance_ids: ${JSON.stringify(instanceIds)} state: absent wait: ${waitForCompletion ? 'yes' : 'no'} register: ec2_terminate - name: Display termination result debug: var: ec2_terminate`; break; case 'start': playbookContent += ` - name: Start EC2 instances amazon.aws.ec2_instance: region: "${region}" instance_ids: ${JSON.stringify(instanceIds)} state: running wait: ${waitForCompletion ? 'yes' : 'no'} register: ec2_start - name: Display start result debug: var: ec2_start`; break; case 'stop': playbookContent += ` - name: Stop EC2 instances amazon.aws.ec2_instance: region: "${region}" instance_ids: ${JSON.stringify(instanceIds)} state: stopped wait: ${waitForCompletion ? 'yes' : 'no'} register: ec2_stop - name: Display stop result debug: var: ec2_stop`; break; default: // Should be caught by Zod validation, but good to have a fallback throw new AnsibleError(`Unsupported EC2 action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`ec2-${action}`, playbookContent); } /** * S3 Operations */ export async function s3Operations(args: S3Options): Promise<string> { await verifyAwsCredentials(); const { action, region, bucket, objectKey, localPath, acl, tags, metadata, contentType } = args; let playbookContent = `--- - name: AWS S3 ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list_buckets': playbookContent += ` - name: List S3 buckets amazon.aws.s3_bucket_info: region: "${region}" register: s3_buckets - name: Display buckets debug: var: s3_buckets.buckets`; break; case 'create_bucket': playbookContent += ` - name: Create S3 bucket amazon.aws.s3_bucket: region: "${region}" name: "${bucket}" state: present ${formatYamlParams({ tags, acl })} register: s3_create - name: Display creation result debug: var: s3_create`; break; case 'delete_bucket': playbookContent += ` - name: Delete S3 bucket amazon.aws.s3_bucket: region: "${region}" name: "${bucket}" state: absent force: true register: s3_delete - name: Display deletion result debug: var: s3_delete`; break; case 'list_objects': playbookContent += ` - name: List S3 objects amazon.aws.s3_object: region: "${region}" bucket: "${bucket}" mode: list register: s3_objects - name: Display objects debug: var: s3_objects.keys`; break; case 'upload': playbookContent += ` - name: Upload file to S3 amazon.aws.s3_object: region: "${region}" bucket: "${bucket}" object: "${objectKey}" src: "${localPath}" mode: put ${formatYamlParams({ acl, tags, metadata, content_type: contentType })} register: s3_upload - name: Display upload result debug: var: s3_upload`; break; case 'download': playbookContent += ` - name: Download file from S3 amazon.aws.s3_object: region: "${region}" bucket: "${bucket}" object: "${objectKey}" dest: "${localPath}" mode: get register: s3_download - name: Display download result debug: var: s3_download`; break; default: throw new AnsibleError(`Unsupported S3 action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`s3-${action}`, playbookContent); } /** * VPC Operations */ export async function vpcOperations(args: VPCOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, vpcId, cidrBlock, name, dnsSupport, dnsHostnames, tags, subnets } = args; let playbookContent = `--- - name: AWS VPC ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List VPCs amazon.aws.ec2_vpc_net_info: region: "${region}" register: vpc_info - name: Display VPCs debug: var: vpc_info.vpcs`; break; case 'create': playbookContent += ` - name: Create VPC amazon.aws.ec2_vpc_net: region: "${region}" cidr_block: "${cidrBlock}" state: present ${formatYamlParams({ name, dns_support: dnsSupport, dns_hostnames: dnsHostnames, tags })} register: vpc_create - name: Display VPC details debug: var: vpc_create.vpc`; // If subnets are specified, add subnet creation task if (subnets && subnets.length > 0) { playbookContent += ` - name: Create subnets amazon.aws.ec2_vpc_subnet: region: "${region}" vpc_id: "{{ vpc_create.vpc.id }}" cidr: "{{ item.cidr }}" az: "{{ item.az | default(omit) }}" tags: "{{ item.tags | default(omit) }}" state: present loop: ${subnets.map((subnet) => ` - ${JSON.stringify(subnet)}`).join('\n')} register: subnet_create - name: Display subnet details debug: var: subnet_create`; } break; case 'delete': playbookContent += ` - name: Delete VPC amazon.aws.ec2_vpc_net: region: "${region}" vpc_id: "${vpcId}" state: absent register: vpc_delete - name: Display deletion result debug: var: vpc_delete`; break; default: throw new AnsibleError(`Unsupported VPC action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`vpc-${action}`, playbookContent); } /** * CloudFormation Operations */ export async function cloudFormationOperations(args: CloudFormationOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, stackName, templateBody, templateUrl, parameters, capabilities, tags } = args; const tempFiles: { filename: string, content: string }[] = []; let templateParam = ''; if (templateBody) { // Prepare template body to be written to a temp file tempFiles.push({ filename: 'template.cfn', content: templateBody }); templateParam = 'template: "template.cfn"'; // Reference the temp file name } else if (templateUrl) { templateParam = `template_url: "${templateUrl}"`; } else if (action === 'create' || action === 'update') { // Template is required for create/update throw new AnsibleError('Either templateBody or templateUrl must be provided for CloudFormation create/update actions.'); } let playbookContent = `--- - name: AWS CloudFormation ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List CloudFormation stacks amazon.aws.cloudformation_info: region: "${region}" register: cfn_info - name: Display stacks debug: var: cfn_info.stacks`; break; case 'create': case 'update': playbookContent += ` - name: ${action === 'create' ? 'Create' : 'Update'} CloudFormation stack amazon.aws.cloudformation: region: "${region}" stack_name: "${stackName}" state: present ${templateParam} # Use the determined template parameter ${formatYamlParams({ template_parameters: parameters, capabilities, tags })} register: cfn_result - name: Display stack outputs/result debug: var: cfn_result`; break; case 'delete': playbookContent += ` - name: Delete CloudFormation stack amazon.aws.cloudformation: region: "${region}" stack_name: "${stackName}" state: absent register: cfn_delete - name: Display deletion result debug: var: cfn_delete`; break; default: throw new AnsibleError(`Unsupported CloudFormation action: ${action}`); } // Execute the generated playbook, passing template body if needed return executeAwsPlaybook(`cloudformation-${action}`, playbookContent, '', tempFiles); } /** * IAM Operations */ export async function iamOperations(args: IAMOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, policyName, policyDocument, path, roleName, assumeRolePolicyDocument, managedPolicies } = args; const tempFiles: { filename: string, content: string }[] = []; let policyDocParam = ''; let assumeRoleDocParam = ''; if (policyDocument) { const policyFilename = 'policy.json'; tempFiles.push({ filename: policyFilename, content: JSON.stringify(policyDocument, null, 2) }); policyDocParam = `policy_document: "{{ lookup('file', '${policyFilename}') }}"`; } if (assumeRolePolicyDocument) { const assumeRoleFilename = 'assume_role_policy.json'; tempFiles.push({ filename: assumeRoleFilename, content: JSON.stringify(assumeRolePolicyDocument, null, 2) }); assumeRoleDocParam = `assume_role_policy_document: "{{ lookup('file', '${assumeRoleFilename}') }}"`; } let playbookContent = `--- - name: AWS IAM ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list_roles': playbookContent += ` - name: List IAM roles amazon.aws.iam_role_info: region: "${region}" register: iam_roles - name: Display roles debug: var: iam_roles.iam_roles`; break; case 'list_policies': playbookContent += ` - name: List IAM policies amazon.aws.iam_policy_info: region: "${region}" register: iam_policies - name: Display policies debug: var: iam_policies.policies`; break; case 'create_role': playbookContent += ` - name: Create IAM role amazon.aws.iam_role: region: "${region}" name: "${roleName}" ${assumeRoleDocParam} state: present ${formatYamlParams({ path, managed_policies: managedPolicies })} register: iam_result - name: Display role details debug: var: iam_result`; break; case 'create_policy': playbookContent += ` - name: Create IAM policy amazon.aws.iam_policy: region: "${region}" policy_name: "${policyName}" ${policyDocParam} state: present ${formatYamlParams({ path })} register: iam_result - name: Display policy details debug: var: iam_result`; break; case 'delete_role': playbookContent += ` - name: Delete IAM role amazon.aws.iam_role: region: "${region}" name: "${roleName}" state: absent register: iam_role_delete - name: Display deletion result debug: var: iam_role_delete`; break; case 'delete_policy': playbookContent += ` - name: Delete IAM policy amazon.aws.iam_policy: region: "${region}" policy_name: "${policyName}" state: absent register: iam_policy_delete - name: Display deletion result debug: var: iam_policy_delete`; break; default: throw new AnsibleError(`Unsupported IAM action: ${action}`); } // Execute the generated playbook, passing policy docs if needed return executeAwsPlaybook(`iam-${action}`, playbookContent, '', tempFiles); } /** * RDS Operations */ export async function rdsOperations(args: RDSOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, dbInstanceIdentifier, dbEngine, dbInstanceClass, allocatedStorage, masterUsername, masterPassword, vpcSecurityGroupIds, dbSubnetGroupName, tags, multiAZ, backupRetentionPeriod, skipFinalSnapshot } = args; let playbookContent = `--- - name: AWS RDS ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List RDS instances amazon.aws.rds_instance_info: region: "${region}" register: rds_info - name: Display RDS instances debug: var: rds_info.instances`; break; case 'create': playbookContent += ` - name: Create RDS instance amazon.aws.rds_instance: region: "${region}" db_instance_identifier: "${dbInstanceIdentifier}" engine: "${dbEngine}" db_instance_class: "${dbInstanceClass}" allocated_storage: ${allocatedStorage} master_username: "${masterUsername}" master_user_password: "${masterPassword}" state: present ${formatYamlParams({ vpc_security_group_ids: vpcSecurityGroupIds, db_subnet_group_name: dbSubnetGroupName, tags, multi_az: multiAZ, backup_retention_period: backupRetentionPeriod, // Add other relevant RDS params here if needed })} register: rds_result - name: Display RDS instance details debug: var: rds_result`; break; case 'delete': playbookContent += ` - name: Delete RDS instance amazon.aws.rds_instance: region: "${region}" db_instance_identifier: "${dbInstanceIdentifier}" state: absent skip_final_snapshot: ${skipFinalSnapshot ? 'yes' : 'no'} register: rds_delete - name: Display deletion result debug: var: rds_delete`; break; case 'start': playbookContent += ` - name: Start RDS instance amazon.aws.rds_instance: region: "${region}" db_instance_identifier: "${dbInstanceIdentifier}" state: started register: rds_start - name: Display start result debug: var: rds_start`; break; case 'stop': playbookContent += ` - name: Stop RDS instance amazon.aws.rds_instance: region: "${region}" db_instance_identifier: "${dbInstanceIdentifier}" state: stopped register: rds_stop - name: Display stop result debug: var: rds_stop`; break; default: throw new AnsibleError(`Unsupported RDS action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`rds-${action}`, playbookContent); } /** * Route53 Operations */ export async function route53Operations(args: Route53Options): Promise<string> { await verifyAwsCredentials(); const { action, region, zoneId, zoneName, recordName, recordType, recordTtl, recordValue, recordState, comment } = args; let playbookContent = `--- - name: AWS Route53 ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list_zones': playbookContent += ` - name: List Route53 hosted zones amazon.aws.route53_info: region: "${region}" query: hosted_zone register: route53_zones - name: Display hosted zones debug: var: route53_zones.HostedZones`; break; case 'list_records': playbookContent += ` - name: List Route53 records amazon.aws.route53_info: region: "${region}" query: record_sets hosted_zone_id: "${zoneId}" register: route53_records - name: Display records debug: var: route53_records.ResourceRecordSets`; break; case 'create_zone': playbookContent += ` - name: Create Route53 hosted zone amazon.aws.route53_zone: region: "${region}" zone: "${zoneName}" state: present ${formatYamlParams({ comment })} register: route53_result - name: Display zone details debug: var: route53_result`; break; case 'create_record': playbookContent += ` - name: Create Route53 record amazon.aws.route53: region: "${region}" zone: "${zoneName}" record: "${recordName}" type: "${recordType}" ttl: ${recordTtl ?? 300} value: ${JSON.stringify(Array.isArray(recordValue) ? recordValue : [recordValue])} state: ${recordState ?? 'present'} ${formatYamlParams({ comment })} register: route53_result - name: Display record details debug: var: route53_result`; break; case 'delete_record': playbookContent += ` - name: Delete Route53 record amazon.aws.route53: region: "${region}" zone: "${zoneName}" record: "${recordName}" type: "${recordType}" # Value might be needed for deletion depending on the record type/setup value: ${JSON.stringify(Array.isArray(recordValue) ? recordValue : [recordValue])} state: absent register: route53_delete - name: Display deletion result debug: var: route53_delete`; break; case 'delete_zone': playbookContent += ` - name: Delete Route53 hosted zone amazon.aws.route53_zone: region: "${region}" zone: "${zoneName}" state: absent register: route53_zone_delete - name: Display deletion result debug: var: route53_zone_delete`; break; default: throw new AnsibleError(`Unsupported Route53 action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`route53-${action}`, playbookContent); } /** * ELB Operations */ export async function elbOperations(args: ELBOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, name, lbType = 'application', scheme, subnets, securityGroups, listeners, healthCheck, tags, targetGroups } = args; // Determine module based on lbType let moduleName: string; let infoModuleName: string; switch (lbType) { case 'application': moduleName = 'amazon.aws.elb_application_lb'; infoModuleName = 'amazon.aws.elb_application_lb_info'; break; case 'network': moduleName = 'amazon.aws.elb_network_lb'; infoModuleName = 'amazon.aws.elb_network_lb_info'; break; case 'classic': moduleName = 'amazon.aws.elb_classic_lb'; infoModuleName = 'amazon.aws.elb_classic_lb_info'; break; default: throw new AnsibleError(`Unsupported ELB type: ${lbType}`); } let playbookContent = `--- - name: AWS ELB ${action} operation (${lbType}) hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List ${lbType} load balancers ${infoModuleName}: region: "${region}" register: elb_info - name: Display load balancers debug: var: elb_info`; // Adjust var based on actual module output if needed break; case 'create': playbookContent += ` - name: Create ${lbType} load balancer ${moduleName}: region: "${region}" name: "${name}" state: present ${formatYamlParams({ scheme, subnets, security_groups: securityGroups, listeners, // May need adjustment for different LB types health_check: healthCheck, // May need adjustment tags, target_groups: targetGroups // For Application/Network LBs })} register: elb_result - name: Display load balancer details debug: var: elb_result`; break; case 'delete': playbookContent += ` - name: Delete ${lbType} load balancer ${moduleName}: region: "${region}" name: "${name}" state: absent register: elb_delete - name: Display deletion result debug: var: elb_delete`; break; default: throw new AnsibleError(`Unsupported ELB action: ${action}`); } // Execute the generated playbook return executeAwsPlaybook(`elb-${action}`, playbookContent); } /** * Lambda Operations */ export async function lambdaOperations(args: LambdaOptions): Promise<string> { await verifyAwsCredentials(); const { action, region, name, zipFile, s3Bucket, s3Key, functionCode, runtime, handler, role, description, timeout, memorySize, environment, tags, payload } = args; const tempFiles: { filename: string, content: string }[] = []; let codeParams = ''; let zipPath: string | undefined; // To track zip file for cleanup if (zipFile) { // Assuming zipFile is a path accessible to the server running this code codeParams = `zip_file: "${zipFile}"`; } else if (s3Bucket && s3Key) { codeParams = `s3_bucket: "${s3Bucket}"\n s3_key: "${s3Key}"`; } else if (functionCode) { // If code is provided directly, write it to a temp file and prepare to zip it const codeFilename = 'lambda_function.py'; // Assuming Python, adjust if needed tempFiles.push({ filename: codeFilename, content: functionCode }); // We'll need to zip this file before executing Ansible // This requires the 'zip' utility on the server zipPath = 'lambda_function.zip'; // Relative path within temp dir codeParams = `zip_file: "${zipPath}"`; } else if (action === 'create' || action === 'update') { throw new AnsibleError('Lambda code source (zipFile, S3, or functionCode) must be provided for create/update actions.'); } let playbookContent = `--- - name: AWS Lambda ${action} operation hosts: localhost connection: local gather_facts: no tasks:`; switch (action) { case 'list': playbookContent += ` - name: List Lambda functions amazon.aws.lambda_info: region: "${region}" register: lambda_info - name: Display Lambda functions debug: var: lambda_info.functions`; break; case 'create': case 'update': playbookContent += ` - name: ${action === 'create' ? 'Create' : 'Update'} Lambda function amazon.aws.lambda: region: "${region}" name: "${name}" state: present ${codeParams} # Use determined code source params ${formatYamlParams({ runtime, handler, role, description, timeout, memory_size: memorySize, environment_variables: environment, tags })} register: lambda_result - name: Display function details debug: var: lambda_result`; break; case 'delete': playbookContent += ` - name: Delete Lambda function amazon.aws.lambda: region: "${region}" name: "${name}" state: absent register: lambda_delete - name: Display deletion result debug: var: lambda_delete`; break; case 'invoke': playbookContent += ` - name: Invoke Lambda function amazon.aws.lambda_invoke: region: "${region}" function_name: "${name}" invocation_type: RequestResponse # Or Event, DryRun ${formatYamlParams({ payload })} register: lambda_invoke - name: Display invocation result debug: var: lambda_invoke`; break; default: throw new AnsibleError(`Unsupported Lambda action: ${action}`); } // Special handling for zipping code before executing Ansible let tempDir: string | undefined; try { tempDir = await createTempDirectory(`ansible-aws-lambda-${action}`); // Write the main playbook file const playbookPath = await writeTempFile(tempDir, 'playbook.yml', playbookContent); // Write any additional temporary files (like the function code) for (const file of tempFiles) { await writeTempFile(tempDir, file.filename, file.content); } // If we need to zip the code, do it now using execAsync if (zipPath && tempFiles.some(f => f.filename === 'lambda_function.py')) { const codeFilePath = `${tempDir}/lambda_function.py`; const zipFilePath = `${tempDir}/${zipPath}`; const zipCommand = `zip -j "${zipFilePath}" "${codeFilePath}"`; console.error(`Executing: ${zipCommand}`); await execAsync(zipCommand, { cwd: tempDir }); // Run zip in the temp directory } // Build the final Ansible command const command = `ansible-playbook ${playbookPath}`; console.error(`Executing: ${command}`); // Execute the playbook asynchronously const { stdout, stderr } = await execAsync(command); return stdout || `Lambda ${action} completed successfully (no output).`; } catch (error: any) { const errorMessage = error.stderr || error.message || 'Unknown error'; throw new AnsibleExecutionError(`Ansible execution failed for lambda-${action}: ${errorMessage}`, error.stderr); } finally { if (tempDir) { await cleanupTempDirectory(tempDir); } } } /** * Dynamic Inventory Operations */ export async function dynamicInventoryOperations(args: DynamicInventoryOptions): Promise<string> { await verifyAwsCredentials(); const { region, filters, hostnames, keyed_groups, compose } = args; let inventoryContent = `--- plugin: amazon.aws.aws_ec2 regions: - ${region}`; if (filters) { inventoryContent += ` filters: ${formatYamlParams(filters, 2)}`; // Indent level 2 for filters } if (hostnames && hostnames.length > 0) { inventoryContent += ` hostnames: ${hostnames.map(h => ` - ${JSON.stringify(h)}`).join('\n')}`; } if (keyed_groups && keyed_groups.length > 0) { inventoryContent += ` keyed_groups: ${keyed_groups.map(group => ` - prefix: ${group.prefix}\n key: ${group.key}\n separator: ${group.separator ?? ''}`).join('\n')}`; } if (compose) { inventoryContent += ` compose: ${formatYamlParams(compose, 2)}`; // Indent level 2 for compose } // This operation doesn't run a playbook, it *generates* an inventory file // and then tests it. We'll adapt the helper pattern slightly. let tempDir: string | undefined; try { tempDir = await createTempDirectory('ansible-aws-dyninv'); const inventoryPath = await writeTempFile(tempDir, 'inventory.aws_ec2.yml', inventoryContent); // Create a simple test playbook const testPlaybookContent = `--- - name: Test AWS Dynamic Inventory hosts: all # Target hosts defined by the dynamic inventory gather_facts: no tasks: - name: Ping hosts found by dynamic inventory ansible.builtin.ping:`; const testPlaybookPath = await writeTempFile(tempDir, 'test_playbook.yml', testPlaybookContent); // Execute ansible-inventory --list first to show the structure const listCommand = `ansible-inventory -i ${inventoryPath} --list`; console.error(`Executing: ${listCommand}`); const listResult = await execAsync(listCommand); // Execute the test playbook using the dynamic inventory const runCommand = `ansible-playbook -i ${inventoryPath} ${testPlaybookPath}`; console.error(`Executing: ${runCommand}`); const runResult = await execAsync(runCommand); return `Dynamic Inventory (${inventoryPath}) Content:\n${inventoryContent}\n\nInventory List Output:\n${listResult.stdout}\n\nPlaybook Test Output:\n${runResult.stdout}`; } catch (error: any) { const errorMessage = error.stderr || error.message || 'Unknown error'; throw new AnsibleExecutionError(`Failed dynamic inventory operation: ${errorMessage}`, error.stderr); } finally { if (tempDir) { await cleanupTempDirectory(tempDir); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tarnover/mcp-sysoperator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server