// Step 246: Standalone MCP server entry point. // // Wires HeadlessEditorState (Step 245) + MCPBridge (Step 212) into a // standalone `whetstone_mcp` binary that any MCP client can launch // over stdio. No SDL, no ImGui, no OpenGL. // // Usage: whetstone_mcp [--workspace ] [--language ] [--verbose] #include "HeadlessEditorState.h" #include "MCPBridge.h" #include "ast/Serialization.h" #include #include #include #include // ----------------------------------------------------------------------- // CLI helpers (same pattern as pipeline_main.cpp) // ----------------------------------------------------------------------- static std::string getArg(int argc, char** argv, const std::string& key) { for (int i = 1; i < argc - 1; ++i) { if (argv[i] == key) return argv[i + 1]; } return ""; } static bool hasFlag(int argc, char** argv, const std::string& key) { for (int i = 1; i < argc; ++i) { if (argv[i] == key) return true; } return false; } // ----------------------------------------------------------------------- // Signal handling (same pattern as orchestrator_main.cpp) // ----------------------------------------------------------------------- static volatile sig_atomic_t g_running = 1; static void signalHandler(int /*sig*/) { g_running = 0; exit(0); } // ----------------------------------------------------------------------- // Resource reader — routes whetstone:// URIs to headless state // ----------------------------------------------------------------------- static json readResource(HeadlessEditorState& state, const std::string& uri) { if (uri == "whetstone://ast") { Module* ast = state.activeAST(); if (ast) return toJson(ast); return {{"error", "No active AST"}}; } if (uri == "whetstone://diagnostics") { return state.buildDiagnosticsJson(); } if (uri == "whetstone://libraries") { // Return available functions from primitives registry state.library.primitives.setRoot(state.activeAST()); state.library.primitives.setLanguage(state.defaultLanguage); auto funcs = state.library.primitives.getAvailableFunctions(); json libs = json::array(); for (const auto& p : funcs) { libs.push_back(json{{"name", p.name}, {"kind", p.kind}, {"source", p.source}}); } return libs; } if (uri == "whetstone://annotations") { Module* ast = state.activeAST(); if (!ast) return json::array(); json annotations = json::array(); for (auto* child : ast->allChildren()) { if (isAnnotationNode(child)) { annotations.push_back(toJson(child)); } } return annotations; } if (uri == "whetstone://settings") { return { {"workspace", state.workspaceRoot}, {"language", state.defaultLanguage}, {"verbose", state.verbose}, {"bufferCount", (int)state.bufferStates.size()}, {"mode", "headless-mcp"} }; } return {{"error", "Unknown resource: " + uri}}; } // ----------------------------------------------------------------------- // main // ----------------------------------------------------------------------- int main(int argc, char** argv) { // Parse CLI args std::string workspace = getArg(argc, argv, "--workspace"); std::string language = getArg(argc, argv, "--language"); bool verbose = hasFlag(argc, argv, "--verbose"); if (hasFlag(argc, argv, "--help") || hasFlag(argc, argv, "-h")) { std::cerr << "Usage: whetstone_mcp [--workspace ] " "[--language ] [--verbose]\n"; return 0; } // Signal handling signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); // Create headless state HeadlessEditorState state; state.verbose = verbose; if (!workspace.empty()) state.workspaceRoot = workspace; if (!language.empty()) state.defaultLanguage = language; // Create an empty default buffer so getAST etc. work immediately state.openBuffer("(scratch)", "", state.defaultLanguage); // Set default agent role to Refactor for full MCP access state.setAgentRole("mcp-session", AgentRole::Refactor); // Create bridge and wire handlers MCPBridge bridge; bridge.setRequestHandler([&state](const json& request) -> json { return state.processAgentRequest(request, "mcp-session"); }); bridge.setResourceReader([&state](const std::string& uri) -> json { return readResource(state, uri); }); // Log startup info to stderr (stdout is the MCP transport) std::cerr << "[whetstone-mcp] Starting MCP server v0.1.0\n"; if (!workspace.empty()) std::cerr << "[whetstone-mcp] Workspace: " << workspace << "\n"; std::cerr << "[whetstone-mcp] Language: " << state.defaultLanguage << "\n"; if (verbose) std::cerr << "[whetstone-mcp] Verbose mode enabled\n"; std::cerr << "[whetstone-mcp] Listening on stdio...\n"; // Run the MCP stdio transport loop bridge.runStdio(); return 0; }