#!/usr/bin/env python3 """ MCP server for Hadolint Dockerfile linter. """ import asyncio import json import logging import subprocess import sys from typing import Any, Dict, List import mcp.server.stdio import mcp.types as types from mcp.server import NotificationOptions, Server from mcp.server.models import InitializationOptions # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Create server instance server = Server("hadolint-mcp") @server.list_tools() async def handle_list_tools() -> List[types.Tool]: """ List available tools. """ return [ types.Tool( name="lint_dockerfile", description="Lint a Dockerfile using Hadolint", inputSchema={ "type": "object", "properties": { "dockerfile_content": { "type": "string", "description": "The content of the Dockerfile to lint" } }, "required": ["dockerfile_content"] } ) ] @server.call_tool() async def handle_call_tool( name: str, arguments: Dict[str, Any] | None ) -> List[types.TextContent | types.ImageContent | types.EmbeddedResource]: """ Handle tool calls. """ if name != "lint_dockerfile": raise ValueError(f"Unknown tool: {name}") if not arguments: raise ValueError("Missing arguments") dockerfile_content = arguments.get("dockerfile_content") if not dockerfile_content: raise ValueError("Missing dockerfile_content argument") try: # Run hadolint on the Dockerfile content process = await asyncio.create_subprocess_exec( "hadolint", "-", # Read from stdin stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, stderr = await process.communicate(input=dockerfile_content.encode()) if process.returncode != 0: # Hadolint returns non-zero for linting errors, which is expected # We still return the output as the result result = stdout.decode() + stderr.decode() else: result = stdout.decode() # If no output, hadolint passed with no issues if not result.strip(): result = "Hadolint: No issues found." return [ types.TextContent( type="text", text=result ) ] except FileNotFoundError: logger.error("Hadolint command not found. Please ensure hadolint is installed and in PATH.") return [ types.TextContent( type="text", text="Error: Hadolint command not found. Please install hadolint." ) ] except Exception as e: logger.exception("Error running hadolint") return [ types.TextContent( type="text", text=f"Error running hadolint: {str(e)}" ) ] async def main(): """ Run the MCP server. """ # Run the server using stdio async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, InitializationOptions( server_name="hadolint-mcp", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), ) if __name__ == "__main__": asyncio.run(main())