84#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS) || defined(EVENGINE_WEBGPU)
87 std::cerr <<
"eve mcp: headless MCP host is desktop-only" << std::endl;
91 std::string gameDir =
path;
95 gameDir = std::filesystem::current_path(ec).string();
97 gameDir = std::filesystem::absolute(
path, ec).string();
98 if (ec) gameDir =
path;
101 if (!gameDir.empty() && gameDir !=
".") {
103 std::filesystem::current_path(gameDir, ec);
105 std::cerr <<
"Cannot chdir to project path '" << gameDir <<
"': " << ec.message()
111 Runtime runtime(2048, ssq::Libs::ALL);
112 runtime.initialize();
113 runtime.vm().setPrintFunc(&hostPrintFunc, &hostPrintFunc);
121 projectFs = eve::filesystem::Filesystem::create();
124 auto cwd = std::filesystem::current_path(ec);
133 }
catch (
const std::exception& e) {
134 std::cerr <<
"eve mcp: filesystem mount skipped (" << e.what() <<
")" << std::endl;
140 dt.attach(runtime,
false);
141 dt.exposeScriptApi(runtime.vm());
150 auto*
host = eve::cap::query<eve::IEditorHost>();
152 host->start(runtime.vm(), gameDir,
true);
153 host->exposeScriptApi(runtime.vm());
155 std::cerr <<
"eve mcp: editor host unavailable (ui module missing)"
163 auto* hot = eve::ModuleManager::getInstance<eve::filesystem::HotReload>(
"HotReload");
164 const int watchCount = projectFs && hot ? hot->watchTree(
".") : 0;
166 if (std::filesystem::is_regular_file(std::filesystem::path(gameDir) /
"mcp.nut")) {
167 const std::string initial =
host->reloadResource(
"mcp.nut");
168 if (initial.rfind(
"error:", 0) == 0)
169 std::cerr <<
"MCP host script load failed: " << initial << std::endl;
171 std::cerr <<
"MCP hot-reload: watching " <<
watchCount <<
" path(s)" << std::endl;
173 auto& mcp = dt.mcp();
174 mcp.setGameRoot(gameDir);
177 const int bound = dt.startMcp(
static_cast<uint16_t
>(port));
179 std::cerr <<
"Failed to start MCP server on port " << port << std::endl;
182 std::cerr <<
"MCP listening on 127.0.0.1:" << bound
183 <<
" (newline JSON-RPC; use tools/eve-mcp for Cursor stdio)" << std::endl;
185 if (!mcp.listenStdio()) {
186 std::cerr <<
"Failed to start stdio MCP transport" << std::endl;
189 std::cerr <<
"MCP stdio transport ready (headless editor host; project=" << gameDir
193 std::map<std::string, std::chrono::steady_clock::time_point> pendingReloads;
194 constexpr auto reloadDebounce = std::chrono::milliseconds(100);
195 while (!
host->exitRequested()) {
200 if (
kind.empty())
break;
201 if (
kind !=
"modified" &&
kind !=
"added" &&
kind !=
"movedTo")
continue;
204 if (!changed.empty()) pendingReloads[changed] = std::chrono::steady_clock::now();
207 const auto now = std::chrono::steady_clock::now();
208 for (
auto it = pendingReloads.begin(); it != pendingReloads.end();) {
209 if (now - it->second < reloadDebounce) {
213 const std::string changed = it->first;
214 if (isEditorHostResource(changed)) {
215 const std::string result =
host->reloadResource(changed);
216 std::cerr <<
"MCP hot-reload: " << changed <<
": " << result << std::endl;
217 }
else if (hot && hot->tryReload(changed)) {
218 std::cerr <<
"MCP hot-reload asset: " << changed << std::endl;
220 it = pendingReloads.erase(it);
225 std::cerr <<
"MCP stdio closed; host exiting" << std::endl;
228 std::this_thread::sleep_for(std::chrono::milliseconds(2));
234 }
catch (
const std::exception& e) {
235 std::cerr <<
"eve mcp failed: " << e.what() << std::endl;