"""
Implement based on https://github.com/modelcontextprotocol/python-sdk
An MCP server that provides a tool to compile C++ code using godbolt.org,
and analyze the generated assembly.
Inspired from CPPCON: https://youtu.be/NWnbgwFU1Xg?si=FoIUKUchNUwCfRqc
"""
import asyncio
import logging
import requests
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.tools import Tool
from pydantic import Field # Import Field from Pydantic
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class CompilerServer:
"""
An MCP server that provides a tool to compile C++ code using godbolt.org.
"""
async def get_assembly(
self,
code: str = Field("The C++ code to compile"),
options: str = Field(
default="", description="Options to pass to the compiler, like /O2 or /Od"
),
) -> dict:
"""
Compiles C++ code using the godbolt.org API and returns the assembly.
"""
logger.info(f"get_assembly called with code and options: {options}")
full_options = options + " /c"
payload = {"source": code, "options": {"UserArguments": full_options}}
try:
# Using a synchronous request library in an async function.
# In a real-world scenario, an async HTTP library like httpx would be better.
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None,
lambda: requests.post(
"https://godbolt.org/api/compiler/vcpp_v19_latest_x64/compile",
json=payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
},
),
)
response.raise_for_status()
# The godbolt API returns a JSON object. The assembly is in the 'asm' property of the 'code' array.
# We will return the full response body for now.
api_response = response.json()
if "asm" in api_response and api_response["asm"]:
return {
"type": "text",
"text": "\n".join(item["text"] for item in api_response["asm"]),
}
else:
# If no assembly, return the raw stdout/stderr if available
return {
"type": "text",
"text": api_response.get("stdout", "")
or api_response.get("stderr", ""),
}
except requests.exceptions.RequestException as e:
logger.error(f"Error calling godbolt API: {e}")
return {"type": "error", "text": str(e)}
except Exception as e:
logger.error(f"An unexpected error occurred: {e}")
return {
"type": "error",
"text": f"An unexpected server error occurred: {str(e)}",
}
if __name__ == "__main__":
compiler_instance = CompilerServer()
# Manually create the Tool instance using Tool.from_function
get_assembly_tool = Tool.from_function(
fn=compiler_instance.get_assembly,
name="get_assembly",
description="Provide the assembly that MSVC would produce for a C++ function",
)
mcp_server = FastMCP(
name="CPPCon MCP Server",
tools=[get_assembly_tool],
)
# The FastMCP server's run_sse_async method handles its own uvicorn setup.
print("Starting FastMCP server with uvicorn via run_sse_async...")
asyncio.run(mcp_server.run_sse_async(mount_path="/"))