Step 246: mcp_main.cpp — standalone MCP server entry point
Wires HeadlessEditorState + MCPBridge into a standalone whetstone_mcp binary that any MCP client (Claude Code, Cursor, etc.) can launch over stdio. No SDL, no ImGui, no OpenGL — headless only. 12/12 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
152
editor/src/mcp_main.cpp
Normal file
152
editor/src/mcp_main.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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 <dir>] [--language <lang>] [--verbose]
|
||||
|
||||
#include "HeadlessEditorState.h"
|
||||
#include "MCPBridge.h"
|
||||
#include "ast/Serialization.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 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 <dir>] "
|
||||
"[--language <lang>] [--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;
|
||||
}
|
||||
Reference in New Issue
Block a user