26#include <Poco/Dynamic/Var.h>
27#include <Poco/Exception.h>
28#include <Poco/JSON/Array.h>
29#include <Poco/JSON/Object.h>
30#include <Poco/JSON/Parser.h>
31#include <Poco/JSON/Stringifier.h>
32#include <Poco/Net/NetException.h>
33#include <Poco/Net/ServerSocket.h>
34#include <Poco/Net/SocketAddress.h>
35#include <Poco/Net/StreamSocket.h>
36#include <Poco/Timespan.h>
50std::string mcpStringify(
const Poco::Dynamic::Var&
v) {
51 std::ostringstream oss;
53 Poco::JSON::Stringifier::stringify(
v, oss, 0, 0);
57std::string mcpJsonEscape(
const std::string&
s) {
59 out.reserve(
s.size() + 8);
78 if (
static_cast<unsigned char>(
c) < 0x20) {
80 std::snprintf(buf,
sizeof(buf),
"\\u%04x",
static_cast<unsigned>(
c));
91std::string idToJson(
const Poco::Dynamic::Var&
id) {
92 if (
id.isEmpty())
return "null";
94 if (
id.isInteger() ||
id.isNumeric())
return std::to_string(
id.convert<Poco::Int64>());
98 return std::string(
"\"") + mcpJsonEscape(
id.convert<std::string>()) +
"\"";
104std::string makeResult(
const std::string& idJson,
const std::string& resultJson) {
105 return std::string(
"{\"jsonrpc\":\"2.0\",\"id\":") + idJson +
",\"result\":" + resultJson +
"}";
108std::string makeError(
const std::string& idJson,
int code,
const std::string& message) {
109 return std::string(
"{\"jsonrpc\":\"2.0\",\"id\":") + idJson +
110 ",\"error\":{\"code\":" + std::to_string(code) +
",\"message\":\"" + mcpJsonEscape(message) +
114std::string textContentResult(
const std::string& text,
bool isError =
false) {
115 Poco::JSON::Object::Ptr result = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
116 Poco::JSON::Array::Ptr content = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
117 Poco::JSON::Object::Ptr item = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
118 item->set(
"type",
"text");
119 item->set(
"text", text);
121 result->set(
"content", content);
122 if (isError) result->set(
"isError",
true);
123 return mcpStringify(Poco::Dynamic::Var(result));
143std::string engineStatusJson(
const McpServer& mcp) {
145 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
147 o->set(
"paused", dbg.isPaused());
148 o->set(
"pauseReason", pauseReasonName(dbg.lastPauseReason()));
149 const auto& loc = dbg.pauseLocation();
150 o->set(
"source", loc.source);
151 o->set(
"line", loc.line);
152 o->set(
"function", loc.function);
153 o->set(
"mcpPort", mcp.port());
154 o->set(
"mcpConnected", mcp.hasClient());
155 switch (mcp.transport()) {
157 o->set(
"transport",
"stdio");
160 o->set(
"transport",
"tcp");
163 o->set(
"transport",
"none");
167 o->set(
"gameRoot", mcp.gameRoot());
169 : std::string(
"unavailable"));
172 return mcpStringify(Poco::Dynamic::Var(o));
175Poco::Dynamic::Var argVar(Poco::JSON::Object::Ptr args,
const char* key) {
176 if (!args || !args->has(key))
return Poco::Dynamic::Var();
178 return args->get(key);
180 return Poco::Dynamic::Var();
184std::string argString(Poco::JSON::Object::Ptr args,
const char* key,
const std::string& def = {}) {
185 if (!args || !args->has(key))
return def;
187 return args->get(key).convert<std::string>();
193int argInt(Poco::JSON::Object::Ptr args,
const char* key,
int def = 0) {
194 if (!args || !args->has(key))
return def;
196 return args->get(key).convert<
int>();
202float argFloat(Poco::JSON::Object::Ptr args,
const char* key,
float def = 0.f) {
203 if (!args || !args->has(key))
return def;
205 return static_cast<float>(args->get(key).convert<
double>());
211glm::vec3 argVec3(Poco::JSON::Object::Ptr args,
const char* key,
const glm::vec3& def = {}) {
212 if (!args || !args->has(key))
return def;
214 auto arr = args->getArray(key);
215 if (arr && arr->size() >= 3) {
216 return glm::vec3(
static_cast<float>(arr->get(0).convert<
double>()),
217 static_cast<float>(arr->get(1).convert<
double>()),
218 static_cast<float>(arr->get(2).convert<
double>()));
225Poco::JSON::Array::Ptr vec3ToArray(
const glm::vec3&
v) {
226 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
233bool argBool(Poco::JSON::Object::Ptr args,
const char* key,
bool def =
false) {
234 if (!args || !args->has(key))
return def;
236 return args->get(key).convert<
bool>();
251std::string sqStringLiteralEscape(
const std::string&
s) {
return mcpJsonEscape(
s); }
254std::string sqLiteralValue(
const Poco::Dynamic::Var&
v) {
255 if (
v.isEmpty())
return "null";
256 if (
v.isBoolean())
return v.convert<
bool>() ?
"true" :
"false";
257 if (
v.isInteger())
return std::to_string(
v.convert<Poco::Int64>());
259 const double d =
v.convert<
double>();
261 std::snprintf(buf,
sizeof(buf),
"%g",
d);
265 return std::string(
"\"") + sqStringLiteralEscape(
v.convert<std::string>()) +
"\"";
267 std::string out =
"[";
269 auto arr =
v.extract<Poco::JSON::Array::Ptr>();
270 for (
size_t i = 0; i < arr->size(); ++i) {
272 out += sqLiteralValue(arr->get(i));
280 std::string out =
"{";
283 auto obj =
v.extract<Poco::JSON::Object::Ptr>();
284 for (
const auto& kv : *obj) {
285 if (!first) out +=
",";
287 out +=
"\"" + sqStringLiteralEscape(kv.first) +
"\"=" + sqLiteralValue(kv.second);
301 return compile ?
"compile failed" :
"runtime failed";
305bool runVmSnippet(
HSQUIRRELVM vm,
const std::string& source, std::string* err) {
306 const SQInteger top = sq_gettop(
vm);
307 if (SQ_FAILED(sq_compilebuffer(
vm, source.c_str(),
static_cast<SQInteger
>(source.size()),
308 _SC(
"mcp_snippet.nut"), SQTrue))) {
310 if (err) *err = snippetErrorText(
vm,
true);
313 sq_pushroottable(
vm);
314 if (SQ_FAILED(sq_call(
vm, 1, SQFalse, SQTrue))) {
316 if (err) *err = snippetErrorText(
vm,
false);
326 switch (sq_gettype(
vm,
idx)) {
332 return b ?
"true" :
"false";
336 sq_getinteger(
vm,
idx, &i);
337 return std::to_string(i);
343 std::snprintf(buf,
sizeof(buf),
"%g",
static_cast<double>(
f));
347 const SQChar*
s =
nullptr;
348 sq_getstring(
vm,
idx, &
s);
349 return std::string(
"\"") + mcpJsonEscape(
s ?
s :
"") +
"\"";
352 std::string out =
"[";
355 while (SQ_SUCCEEDED(sq_next(
vm,
idx))) {
356 if (!first) out +=
",";
358 out += sqValueToJson(
vm, -1);
366 std::string out =
"{";
369 while (SQ_SUCCEEDED(sq_next(
vm,
idx))) {
370 if (!first) out +=
",";
372 out += sqValueToJson(
vm, -2);
374 out += sqValueToJson(
vm, -1);
382 return "\"<unserializable>\"";
388std::string callSceneDirectorReturn(
HSQUIRRELVM vm,
const std::string& snippet,
390 const SQInteger top = sq_gettop(
vm);
391 if (SQ_FAILED(sq_compilebuffer(
vm, snippet.c_str(),
static_cast<SQInteger
>(snippet.size()),
392 _SC(
"mcp_scene_director.nut"), SQTrue))) {
394 if (err) *err = snippetErrorText(
vm,
true);
397 sq_pushroottable(
vm);
398 if (SQ_FAILED(sq_call(
vm, 1, SQTrue, SQTrue))) {
400 std::string
text = snippetErrorText(
vm,
false);
402 *err =
text ==
"runtime failed"
403 ?
"runtime failed (is the scene_director kit installed?)"
407 std::string json = sqValueToJson(
vm, -1);
413bool ensureSceneDirectorInstalled(
HSQUIRRELVM vm, std::string* err) {
414 const std::string check =
415 "return (\"scene_director\" in getroottable()) ? (scene_director != null) : false;";
416 const std::string out = callSceneDirectorReturn(
vm, check,
nullptr);
417 if (!out.empty() && out.find(
"true") != std::string::npos)
return true;
420 if (err) *err =
"scene_director.nut not embedded (rebuild EVScripts)";
423 return runVmSnippet(
vm, kit, err);
426std::string sceneDirectorToolError(
const std::string&
name,
const std::string& err) {
427 return "error: " +
name +
": " + (err.empty() ?
"unknown" : err);
431 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
433 o->set(
"error",
"Graphics module not available");
434 return mcpStringify(Poco::Dynamic::Var(o));
437 o->set(
"width",
s.width);
438 o->set(
"height",
s.height);
439 o->set(
"pixelWidth",
s.pixelWidth);
440 o->set(
"pixelHeight",
s.pixelHeight);
441 o->set(
"had3DThisFrame",
s.had3DThisFrame);
442 o->set(
"readbackEnabled",
s.readbackEnabled);
443 o->set(
"backend",
s.backend);
444 o->set(
"renderFlowEvents",
static_cast<int>(
DevTool::instance().renderFlow().eventCount()));
445 return mcpStringify(Poco::Dynamic::Var(o));
456std::string callTool(McpServer& mcp,
const std::string&
name, Poco::JSON::Object::Ptr args) {
460 if (
name ==
"eve_status")
return engineStatusJson(mcp);
463 if (
name ==
"eve_scene_status") {
464 auto* scene = mcpScene();
465 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
467 o->set(
"error",
"Scene module not available");
468 return mcpStringify(Poco::Dynamic::Var(o));
470 const std::string host = scene->activeHost();
472 o->set(
"activeHost", Poco::Dynamic::Var());
473 o->set(
"nodeCount", 0);
474 return mcpStringify(Poco::Dynamic::Var(o));
476 o->set(
"activeHost", host);
477 o->set(
"nodeCount", scene->nodeCount());
478 o->set(
"rootId", scene->rootId());
479 return mcpStringify(Poco::Dynamic::Var(o));
482 if (
name ==
"eve_scene_nodes") {
483 auto* scene = mcpScene();
484 if (!scene || scene->activeHost().empty())
return "error: no active scene host";
485 const int limit = argInt(args,
"limit", 500);
486 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
487 for (
const auto&
n : scene->nodes(limit)) {
488 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
490 o->set(
"name",
n.name);
491 o->set(
"path",
n.path);
492 o->set(
"visible",
n.visible);
495 return mcpStringify(Poco::Dynamic::Var(arr));
498 if (
name ==
"eve_scene_node_get") {
499 auto* scene = mcpScene();
500 if (!scene || scene->activeHost().empty())
return "error: no active scene host";
501 const std::string
id = argString(args,
"id");
503 if (
id.empty() || !scene->getNode(
id, &
n))
return "error: node not found: " +
id;
504 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
506 o->set(
"name",
n.name);
507 o->set(
"path",
n.path);
508 o->set(
"visible",
n.visible);
512 o->set(
"yaw",
n.yaw);
513 o->set(
"pitch",
n.pitch);
514 o->set(
"roll",
n.roll);
518 o->set(
"parent",
n.parent);
519 Poco::JSON::Array::Ptr kids = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
521 o->set(
"children", kids);
522 return mcpStringify(Poco::Dynamic::Var(o));
525 if (
name ==
"eve_scene_node_set") {
526 auto* scene = mcpScene();
527 if (!scene || scene->activeHost().empty())
return "error: no active scene host";
528 const std::string
id = argString(args,
"id");
529 if (
id.empty())
return "error: node not found: " +
id;
530 bool changed =
false;
531 if (args && args->has(
"x") && args->has(
"y") && args->has(
"z")) {
532 changed = scene->setNodeTransform(
id, argFloat(args,
"x"), argFloat(args,
"y"), argFloat(args,
"z"));
534 if (args && args->has(
"visible")) {
535 changed = scene->setNodeVisible(
id, argBool(args,
"visible")) || changed;
537 return changed ?
"ok" :
"error: node not found: " +
id;
541 if (
name ==
"eve_procgen_recipes") {
542 auto* pg = mcpProcgen();
543 if (!pg)
return "error: Procgen module not available";
544 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
545 auto addList = [&](
const char* key,
const std::vector<std::string>& items) {
546 Poco::JSON::Array::Ptr
a = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
547 for (
const auto& it : items)
a->add(it);
550 addList(
"algorithms", pg->algorithms());
551 addList(
"meshRecipes", pg->meshRecipes());
552 addList(
"textureRecipes", pg->textureRecipes());
553 addList(
"pbrRecipes", pg->pbrRecipes());
554 return mcpStringify(Poco::Dynamic::Var(o));
557 if (
name ==
"eve_procgen_map") {
558 auto* pg = mcpProcgen();
559 if (!pg)
return "error: Procgen module not available";
560 const std::string algorithm = argString(args,
"algorithm");
561 if (algorithm.empty())
return "error: missing algorithm";
562 std::vector<std::pair<std::string, std::string>> params;
563 for (
const auto& key : {
"roomCount",
"roomMin",
"roomMax",
"corridorWidth",
"autotile",
564 "scale",
"octaves"}) {
565 if (args && args->has(key)) params.emplace_back(key, std::to_string(argInt(args, key)));
567 if (args && args->has(
"corridorStyle")) params.emplace_back(
"corridorStyle", argString(args,
"corridorStyle"));
569 std::string json = pg->generateMap(algorithm, argInt(args,
"width", 32), argInt(args,
"height", 32),
570 static_cast<uint32_t
>(argInt(args,
"seed", 0)), params, &err);
571 if (json.empty())
return "error: " + (err.empty() ? std::string(
"empty grid") : err);
575 if (
name ==
"eve_procgen_mesh") {
576 auto* pg = mcpProcgen();
577 if (!pg)
return "error: Procgen module not available";
578 const std::string recipe = argString(args,
"recipe");
579 if (recipe.empty())
return "error: missing recipe";
582 pg->buildMesh(recipe,
static_cast<uint32_t
>(argInt(args,
"seed", 0)), argInt(args,
"width", -1),
583 argInt(args,
"height", -1), argInt(args,
"depth", -1), &err);
584 if (json.empty())
return "error: " + (err.empty() ? std::string(
"build failed") : err);
585 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
586 o->set(
"recipe", recipe);
588 Poco::JSON::Parser parser;
589 Poco::Dynamic::Var parsed = parser.parse(json);
590 Poco::JSON::Object::Ptr jo = parsed.extract<Poco::JSON::Object::Ptr>();
591 o->set(
"vertices", jo->get(
"vertices"));
592 o->set(
"triangles", jo->get(
"triangles"));
596 return mcpStringify(Poco::Dynamic::Var(o));
600 if (
name ==
"eve_physics_new_world") {
601 auto* ph = mcpPhysics();
602 if (!ph)
return "error: Physics module not available";
603 const int id = ph->newWorld(argFloat(args,
"gravityX", 0.f), argFloat(args,
"gravityY", 900.f));
604 if (
id < 0)
return "error: failed to create world";
605 float gx = 0.f, gy = 0.f;
606 ph->worldGravity(
id, &gx, &gy);
607 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
609 o->set(
"gravityX", gx);
610 o->set(
"gravityY", gy);
611 return mcpStringify(Poco::Dynamic::Var(o));
614 if (
name ==
"eve_physics_list_worlds") {
615 auto* ph = mcpPhysics();
616 if (!ph)
return "error: Physics module not available";
617 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
618 for (
int i = 0; i < ph->worldCount(); ++i) {
619 float gx = 0.f, gy = 0.f;
620 if (!ph->worldGravity(i, &gx, &gy))
continue;
621 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
623 o->set(
"gravityX", gx);
624 o->set(
"gravityY", gy);
627 return mcpStringify(Poco::Dynamic::Var(arr));
630 if (
name ==
"eve_physics_raycast") {
631 auto* ph = mcpPhysics();
632 if (!ph)
return "error: Physics module not available";
634 if (!ph->rayCast(argInt(args,
"world", 0), argFloat(args,
"x1"), argFloat(args,
"y1"), argFloat(args,
"x2"),
635 argFloat(args,
"y2"), &
h))
636 return "error: unknown physics world id";
637 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
638 o->set(
"hit",
h.hit);
640 o->set(
"bodyId",
h.bodyId);
643 o->set(
"normalX",
h.normalX);
644 o->set(
"normalY",
h.normalY);
645 o->set(
"fraction",
h.fraction);
647 return mcpStringify(Poco::Dynamic::Var(o));
650 if (
name ==
"eve_physics_remove_world") {
651 auto* ph = mcpPhysics();
652 if (!ph)
return "error: Physics module not available";
653 return ph->removeWorld(argInt(args,
"world", -1)) ?
"ok" :
"error: unknown physics world id";
657 if (
name ==
"eve_render_status") {
658 return renderStatusText(mcpCapture());
661 if (
name ==
"eve_render_describe") {
662 const bool fresh = argBool(args,
"fresh",
false);
663 const std::string reason = argString(args,
"reason");
667 if (
name ==
"eve_render_vision_config") {
669 if (args && args->has(
"baseUrl")) rv.setBaseUrl(argString(args,
"baseUrl"));
670 if (args && args->has(
"apiKey")) rv.setApiKey(argString(args,
"apiKey"));
671 if (args && args->has(
"model")) rv.setModel(argString(args,
"model"));
672 if (args && args->has(
"path")) rv.setPath(argString(args,
"path"));
673 if (args && args->has(
"timeoutMs")) rv.setTimeoutMs(argInt(args,
"timeoutMs", 20000));
674 return rv.configJson();
677 if (
name ==
"eve_screenshot") {
678 auto* cap = mcpCapture();
679 if (!cap)
return "error: Graphics module not available";
680 std::string path = argString(args,
"path");
681 if (path.empty()) path =
"mcp_screenshot.png";
684 if (!cap->
savePng(path, &
w, &
h, &err))
return "error: " + err;
685 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
686 o->set(
"path", path);
689 return mcpStringify(Poco::Dynamic::Var(o));
693 if (
name ==
"eve_particles_status") {
694 auto* part = mcpParticles();
695 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
697 o->set(
"error",
"Particles module not available");
698 return mcpStringify(Poco::Dynamic::Var(o));
700 o->set(
"emitterCount", part->emitterCount());
701 return mcpStringify(Poco::Dynamic::Var(o));
704 if (
name ==
"eve_particles_emit") {
705 auto* part = mcpParticles();
706 if (!part)
return "error: Particles module not available";
707 float ex = 0.f, ey = 0.f;
709 if (!part->createEmitter(argInt(args,
"buffer", 1000), argFloat(args,
"x"), argFloat(args,
"y"),
710 argString(args,
"preset"), argInt(args,
"count", 100), &ex, &ey, &cnt))
711 return "error: failed to create emitter";
712 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
715 o->set(
"count", cnt);
716 return mcpStringify(Poco::Dynamic::Var(o));
720 if (
name ==
"eve_audio_status") {
721 auto* audio = mcpAudio();
722 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
724 o->set(
"error",
"Audio module not available");
725 return mcpStringify(Poco::Dynamic::Var(o));
727 o->set(
"volume", audio->volume());
728 return mcpStringify(Poco::Dynamic::Var(o));
731 if (
name ==
"eve_audio_set_volume") {
732 auto* audio = mcpAudio();
733 if (!audio)
return "error: Audio module not available";
734 audio->setVolume(argFloat(args,
"volume", 1.f));
738 if (
name ==
"eve_audio_stop_all") {
739 auto* audio = mcpAudio();
740 if (!audio)
return "error: Audio module not available";
746 if (
name ==
"eve_host_status") {
747 return mcpHost() ? mcpHost()->status() :
"error: ui module not available";
749 if (
name ==
"eve_host_window_open") {
750 const std::string
title = argString(args,
"title",
"EVEngine AI Host");
751 return mcpHost() ? mcpHost()->openWindow(
title, argInt(args,
"width", 1280), argInt(args,
"height", 800))
752 :
"error: ui module not available";
754 if (
name ==
"eve_host_window_close") {
755 return mcpHost() ? mcpHost()->closeWindow() :
"error: ui module not available";
757 if (
name ==
"eve_host_window_state") {
758 return mcpHost() ? mcpHost()->windowState() :
"error: ui module not available";
760 if (
name ==
"eve_host_editor_apply") {
762 if (args && args->has(
"editor")) {
764 Poco::JSON::Object::Ptr o = args->getObject(
"editor");
766 json = mcpStringify(Poco::Dynamic::Var(o));
768 json = argString(args,
"editor");
770 json = argString(args,
"editor");
773 if (json.empty())
return "error: missing editor";
774 return mcpHost() ? mcpHost()->applyEditor(json) :
"error: ui module not available";
776 if (
name ==
"eve_host_editor_remove") {
777 return mcpHost() ? mcpHost()->removeEditor(argString(args,
"id")) :
"error: ui module not available";
779 if (
name ==
"eve_host_editor_list") {
780 return mcpHost() ? mcpHost()->listEditors() :
"error: ui module not available";
782 if (
name ==
"eve_host_editor_state") {
783 return mcpHost() ? mcpHost()->editorState(argString(args,
"id")) :
"error: ui module not available";
785 if (
name ==
"eve_host_editor_set_value") {
786 if (!args || !args->has(
"value"))
return "error: missing value";
787 return mcpHost() ? mcpHost()->setEditorValue(argString(args,
"editor"), argString(args,
"widget"),
788 mcpStringify(argVar(args,
"value")))
789 :
"error: ui module not available";
791 if (
name ==
"eve_host_editor_save") {
792 return mcpHost() ? mcpHost()->saveEditor(argString(args,
"id")) :
"error: ui module not available";
794 if (
name ==
"eve_host_editor_unload") {
795 return mcpHost() ? mcpHost()->unloadEditor(argString(args,
"id")) :
"error: ui module not available";
797 if (
name ==
"eve_host_vm_register") {
798 return mcpHost() ? mcpHost()->registerVM(argString(args,
"name"), argString(args,
"source"))
799 :
"error: ui module not available";
801 if (
name ==
"eve_host_vm_unregister") {
802 return mcpHost() ? mcpHost()->unregisterVM(argString(args,
"name")) :
"error: ui module not available";
804 if (
name ==
"eve_host_events") {
805 return mcpHost() ? mcpHost()->consumeEvents(argString(args,
"editor")) :
"error: ui module not available";
807 if (
name ==
"eve_host_widget_rect") {
808 return mcpHost() ? mcpHost()->widgetRect(argString(args,
"editor"), argString(args,
"widget"))
809 :
"error: ui module not available";
811 if (
name ==
"eve_host_capture") {
812 return mcpHost() ? mcpHost()->capture(argString(args,
"path")) :
"error: ui module not available";
814 if (
name ==
"eve_host_script") {
815 return mcpHost() ? mcpHost()->runScript(argString(args,
"source")) :
"error: ui module not available";
817 if (
name ==
"eve_host_shutdown") {
818 if (mcpHost()) mcpHost()->requestExit();
822 if (
name ==
"eve_eval") {
823 const std::string expr = argString(args,
"expression");
824 if (expr.empty())
return "error: missing expression";
825 auto info = dbg.evaluate(expr);
826 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
827 o->set(
"expression", expr);
828 o->set(
"name", info.name);
829 o->set(
"value", info.value);
830 o->set(
"type", info.type);
831 o->set(
"ok", !info.value.empty() || info.type ==
"null" || !info.name.empty());
832 return mcpStringify(Poco::Dynamic::Var(o));
835 if (
name ==
"eve_pause") {
840 if (
name ==
"eve_continue") {
842 dap.notifyContinued();
845 if (
name ==
"eve_step_over") {
847 dap.notifyContinued();
850 if (
name ==
"eve_step_into") {
852 dap.notifyContinued();
855 if (
name ==
"eve_step_out") {
857 dap.notifyContinued();
860 if (
name ==
"eve_step_frame") {
862 dap.notifyContinued();
866 if (
name ==
"eve_stack") {
867 auto frames = dbg.stackTrace();
868 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
869 for (
const auto&
f : frames) {
870 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
872 o->set(
"name",
f.name);
873 o->set(
"source",
f.loc.source);
874 o->set(
"line",
f.loc.line);
875 o->set(
"function",
f.loc.function);
878 return mcpStringify(Poco::Dynamic::Var(arr));
881 if (
name ==
"eve_locals") {
882 const int level = argInt(args,
"level", 1);
883 auto vars = dbg.locals(level);
884 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
885 for (
const auto&
v :
vars) {
886 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
887 o->set(
"name",
v.name);
888 o->set(
"value",
v.value);
889 o->set(
"type",
v.type);
892 return mcpStringify(Poco::Dynamic::Var(arr));
895 if (
name ==
"eve_set_breakpoint") {
896 const std::string source = argString(args,
"source");
897 const int line = argInt(args,
"line");
898 if (source.empty() ||
line <= 0)
return "error: need source and line";
899 const int id = dbg.setBreakpoint(source,
line,
true);
900 return "ok id=" + std::to_string(
id);
902 if (
name ==
"eve_clear_breakpoint") {
903 const std::string source = argString(args,
"source");
904 const int line = argInt(args,
"line");
905 if (source.empty() ||
line <= 0)
return "error: need source and line";
906 return dbg.clearBreakpoint(source,
line) ?
"cleared" :
"not_found";
908 if (
name ==
"eve_list_breakpoints") {
909 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
910 for (
const auto& bp : dbg.breakpoints()) {
911 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
913 o->set(
"source", bp.source);
914 o->set(
"line", bp.line);
915 o->set(
"enabled", bp.enabled);
918 return mcpStringify(Poco::Dynamic::Var(arr));
921 if (
name ==
"eve_watch_add") {
922 const std::string expr = argString(args,
"expression");
923 if (expr.empty())
return "error: missing expression";
925 dbg.refreshWatches();
928 if (
name ==
"eve_watch_list") {
929 dbg.refreshWatches();
930 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
931 for (
const auto&
w : dbg.watches()) {
932 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
933 o->set(
"expression",
w.expression);
934 o->set(
"value",
w.value);
938 return mcpStringify(Poco::Dynamic::Var(arr));
941 if (
name ==
"eve_snapshot_capture") {
943 if (!
vm)
return "error: no VM";
946 if (!err.empty() && json.empty())
return "error: " + err;
949 if (
name ==
"eve_snapshot_restore") {
951 if (!
vm)
return "error: no VM";
952 const std::string json = argString(args,
"json");
957 if (
name ==
"eve_snapshot_save") {
959 if (!
vm)
return "error: no VM";
960 const std::string path = argString(args,
"path");
965 if (
name ==
"eve_snapshot_load") {
967 if (!
vm)
return "error: no VM";
968 const std::string path = argString(args,
"path");
974 if (
name ==
"eve_error_slice") {
976 if (!last.empty())
return last;
977 return mcpFormatError(
"no prior error; callgraph slice at latest site");
980 if (
name ==
"eve_run_script") {
982 if (!
vm)
return "error: no VM";
983 const std::string source = argString(args,
"source");
984 if (source.empty())
return "error: missing source";
985 const SQInteger top = sq_gettop(
vm);
986 if (SQ_FAILED(sq_compilebuffer(
vm, source.c_str(),
987 static_cast<SQInteger
>(source.size()),
988 _SC(
"mcp_snippet.nut"), SQTrue))) {
990 return "error: " + snippetErrorText(
vm,
true);
992 sq_pushroottable(
vm);
993 if (SQ_FAILED(sq_call(
vm, 1, SQFalse, SQTrue))) {
995 return "error: " + snippetErrorText(
vm,
false);
1001 if (
name ==
"eve_ai_note") {
1002 const std::string
text = argString(args,
"text");
1003 if (
text.empty())
return "error: missing text";
1011 if (
name ==
"eve_scene_director_install") {
1013 if (!
vm)
return "error: no VM";
1015 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1019 if (
name ==
"eve_scene_director_status") {
1021 if (!
vm)
return "error: no VM";
1023 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1024 return callSceneDirectorReturn(
vm,
"return ::scene_director.status();", &err);
1027 if (
name ==
"eve_scene_reset") {
1029 if (!
vm)
return "error: no VM";
1031 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1032 const std::string out = callSceneDirectorReturn(
vm,
"return ::scene_director.reset();", &err);
1033 if (!err.empty())
return sceneDirectorToolError(
name, err);
1037 if (
name ==
"eve_scene_modify") {
1039 if (!
vm)
return "error: no VM";
1041 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1042 const std::string action = argString(args,
"action");
1043 const std::string target = argString(args,
"target");
1044 Poco::Dynamic::Var paramsVar;
1045 if (args && args->has(
"params")) {
1047 paramsVar = Poco::Dynamic::Var(args->getObject(
"params"));
1051 const std::string snippet =
1052 "return ::scene_director.modify(" + sqLiteralValue(Poco::Dynamic::Var(action)) +
"," +
1053 sqLiteralValue(Poco::Dynamic::Var(target)) +
"," + sqLiteralValue(paramsVar) +
");";
1054 const std::string out = callSceneDirectorReturn(
vm, snippet, &err);
1055 if (!err.empty())
return sceneDirectorToolError(
name, err);
1059 if (
name ==
"eve_camera_generate") {
1061 if (!
vm)
return "error: no VM";
1063 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1064 const int count = argInt(args,
"count", 6);
1065 const std::string snippet =
1066 "return ::scene_director.cameras(" + std::to_string(count) +
");";
1067 const std::string out = callSceneDirectorReturn(
vm, snippet, &err);
1068 if (!err.empty())
return sceneDirectorToolError(
name, err);
1072 if (
name ==
"eve_scene_info") {
1074 if (!
vm)
return "error: no VM";
1076 if (!ensureSceneDirectorInstalled(
vm, &err))
return sceneDirectorToolError(
name, err);
1077 const std::string out = callSceneDirectorReturn(
vm,
"return ::scene_director.info();", &err);
1078 if (!err.empty())
return sceneDirectorToolError(
name, err);
1083 if (
name ==
"inspect_generate_scene_camera_views") {
1084 const glm::vec3 center = argVec3(args,
"center");
1085 const float fov = argFloat(args,
"fov", 60.f);
1087 Poco::JSON::Object::Ptr root = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1088 Poco::JSON::Array::Ptr arr = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
1089 for (
const auto&
v : views) {
1090 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1091 o->set(
"name",
v.name);
1092 o->set(
"kind",
v.kind);
1093 o->set(
"eye", vec3ToArray(
v.eye));
1094 o->set(
"target", vec3ToArray(
v.target));
1095 o->set(
"fov",
static_cast<double>(
v.fovYDeg));
1098 root->set(
"views", arr);
1099 return mcpStringify(Poco::Dynamic::Var(root));
1102 if (
name ==
"set_camera_pose") {
1103 const glm::vec3
pos = argVec3(args,
"pos", glm::vec3(0.f, 1.8f, 0.f));
1104 const glm::vec3 rot = argVec3(args,
"rot", glm::vec3(0.f));
1105 const float fov = argFloat(args,
"fov", 0.f);
1107 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1110 o->set(
"error",
"failed to set camera pose (no Graphics/Camera3D)");
1113 Poco::JSON::Parser parser;
1118 return mcpStringify(Poco::Dynamic::Var(o));
1121 if (
name ==
"capture_render_frame") {
1122 const std::string
dir = argString(args,
"dir");
1123 const std::string tag = argString(args,
"tag",
"frame");
1124 std::vector<std::string> buffers;
1125 if (args && args->has(
"buffers")) {
1127 auto arr = args->getArray(
"buffers");
1129 for (
size_t i = 0; i < arr->size(); ++i)
1130 buffers.push_back(arr->get(i).convert<std::string>());
1136 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1137 o->set(
"ok", res.ok);
1139 o->set(
"png", res.pngPath);
1140 o->set(
"json", res.jsonPath);
1141 o->set(
"width", res.width);
1142 o->set(
"height", res.height);
1143 o->set(
"entityCount", res.entityCount);
1144 if (!res.depthPngPath.empty()) o->set(
"depthPng", res.depthPngPath);
1145 if (!res.normalPngPath.empty()) o->set(
"normalPng", res.normalPngPath);
1146 if (!res.idPngPath.empty()) o->set(
"idPng", res.idPngPath);
1147 if (!res.idJsonPath.empty()) o->set(
"idJson", res.idJsonPath);
1148 if (!res.unsupported.empty()) {
1149 Poco::JSON::Array::Ptr uns = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
1150 for (
const auto&
u : res.unsupported) uns->add(
u);
1151 o->set(
"unsupported", uns);
1154 o->set(
"error", res.error);
1156 return mcpStringify(Poco::Dynamic::Var(o));
1159 if (
name ==
"get_visible_entities_screen_bbox") {
1160 const bool hasPos = args && args->has(
"pos");
1161 const bool hasTarget = args && args->has(
"target");
1162 if (hasPos && hasTarget) {
1163 const glm::vec3
eye = argVec3(args,
"pos");
1164 const glm::vec3 tgt = argVec3(args,
"target");
1165 const float fov = argFloat(args,
"fov", 0.f);
1168 const float fov = argFloat(args,
"fov", 0.f);
1172 return "error: unknown tool " +
name;
1175std::string handleInitialize(McpServer& mcp,
const std::string& idJson,
1176 Poco::JSON::Object::Ptr params) {
1177 std::string clientName =
"mcp-client";
1178 std::string protocol =
"2025-06-18";
1181 if (params->has(
"clientInfo")) {
1182 auto info = params->getObject(
"clientInfo");
1183 if (info && info->has(
"name"))
1184 clientName = info->get(
"name").convert<std::string>();
1186 if (params->has(
"protocolVersion"))
1187 protocol = params->get(
"protocolVersion").convert<std::string>();
1196 const std::string resultJson =
1197 std::string(
"{\"protocolVersion\":\"") + mcpJsonEscape(protocol) +
1198 "\",\"capabilities\":{\"tools\":{},\"resources\":{},\"prompts\":{}},"
1199 "\"serverInfo\":{\"name\":\"evengine\",\"title\":\"EVEngine MCP\",\"version\":\"0.1.0\"},"
1200 "\"instructions\":\"EVEngine MCP for AI-assisted game development. eve_host_* tools create JSON-defined editor windows bound to Squirrel ViewModels (MVVM) for AI-crafted terrain/material/event editors.\"}";
1201 return makeResult(idJson, resultJson);
1204std::string handleToolsList(
const std::string& idJson) {
1205 static const char*
const kToolsParts[] = {
1207 "{\"name\":\"eve_status\",\"description\":\"Runtime + debugger + MCP/DAP status JSON.\","
1208 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1209 "{\"name\":\"eve_eval\",\"description\":\"Evaluate a Squirrel expression (local or roottable).\","
1210 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"expression\":{\"type\":\"string\"}},\"required\":[\"expression\"]}},"
1211 "{\"name\":\"eve_pause\",\"description\":\"Pause the game / script.\","
1212 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1213 "{\"name\":\"eve_continue\",\"description\":\"Continue from pause.\","
1214 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1215 "{\"name\":\"eve_step_over\",\"description\":\"Step over next statement.\","
1216 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1217 "{\"name\":\"eve_step_into\",\"description\":\"Step into next statement.\","
1218 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1219 "{\"name\":\"eve_step_out\",\"description\":\"Step out of current function.\","
1220 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1221 "{\"name\":\"eve_step_frame\",\"description\":\"Run one game frame then pause.\","
1222 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1223 "{\"name\":\"eve_stack\",\"description\":\"Return current script stack frames.\","
1224 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1225 "{\"name\":\"eve_locals\",\"description\":\"List locals at a stack level (default 1).\","
1226 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1227 "{\"name\":\"eve_set_breakpoint\",\"description\":\"Set a script breakpoint.\","
1228 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"string\"},\"line\":{\"type\":\"integer\"}},\"required\":[\"source\",\"line\"]}},"
1229 "{\"name\":\"eve_clear_breakpoint\",\"description\":\"Clear a script breakpoint.\","
1230 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"string\"},\"line\":{\"type\":\"integer\"}},\"required\":[\"source\",\"line\"]}},"
1231 "{\"name\":\"eve_list_breakpoints\",\"description\":\"List breakpoints.\","
1232 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1233 "{\"name\":\"eve_watch_add\",\"description\":\"Add a watch expression.\","
1234 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"expression\":{\"type\":\"string\"}},\"required\":[\"expression\"]}},"
1235 "{\"name\":\"eve_watch_list\",\"description\":\"List watches with last values.\","
1236 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1237 "{\"name\":\"eve_snapshot_capture\",\"description\":\"Capture script-state snapshot JSON.\","
1238 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1239 "{\"name\":\"eve_snapshot_restore\",\"description\":\"Restore script-state from snapshot JSON.\","
1240 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"json\":{\"type\":\"string\"}},\"required\":[\"json\"]}},"
1241 "{\"name\":\"eve_snapshot_save\",\"description\":\"Save snapshot to a file path.\","
1242 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"path\":{\"type\":\"string\"}},\"required\":[\"path\"]}},"
1243 "{\"name\":\"eve_snapshot_load\",\"description\":\"Load snapshot from a file path.\","
1244 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"path\":{\"type\":\"string\"}},\"required\":[\"path\"]}},"
1245 "{\"name\":\"eve_error_slice\",\"description\":\"Return last error report / backward slice (script + render).\","
1246 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1247 "{\"name\":\"eve_run_script\",\"description\":\"Compile and run a short Squirrel snippet in the live VM.\","
1248 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"string\"}},\"required\":[\"source\"]}},"
1249 "{\"name\":\"eve_ai_note\",\"description\":\"Append a note to the DevTools AI session log.\","
1250 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"required\":[\"text\"]}},"
1251 "{\"name\":\"eve_ai_log\",\"description\":\"Read the DevTools AI session log.\","
1252 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1253 "{\"name\":\"eve_scene_director_install\",\"description\":\"Install the scene-director authoring kit into the live VM (idempotent).\","
1254 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1255 "{\"name\":\"eve_scene_director_status\",\"description\":\"Scene-director kit status (installed / propCount / hasCamera).\","
1256 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1257 "{\"name\":\"eve_scene_reset\",\"description\":\"Clear all staged props, camera and reset lighting to defaults.\","
1258 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1259 "{\"name\":\"eve_scene_modify\",\"description\":\"Agent scene action: action in add_object|spawn|place|move_object|move|scale|rotate|rotation|remove_object|remove|visibility|material|lighting|set_lighting|camera|cameras|info|list|reset. spawn/move params: {id,kind,x,y,z,sx,sy,sz,yaw_deg,scale,pos,tint,seed,mesh_params,...}; lighting params: {timeOfDay,atmosphere,intensity,background}; camera params: {eye,target,fov}. Returns JSON.\","
1260 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"action\":{\"type\":\"string\"},\"target\":{\"type\":\"string\"},\"params\":{\"type\":\"object\"}},\"required\":[\"action\"]}},"
1261 "{\"name\":\"eve_camera_generate\",\"description\":\"Generate standardized QC camera rigs (eye/target/fov) orbiting the staged scene.\","
1262 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"integer\"}}}},"
1263 "{\"name\":\"eve_scene_info\",\"description\":\"Authoritative staged-scene truth JSON: props (id/kind/pos/scale/yaw_deg/tint) + count.\","
1264 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1265 "{\"name\":\"inspect_generate_scene_camera_views\",\"description\":\"Generate a standard set of inspection camera views (road-level, bird's-eye, corner close-up, vista) around a center point.\","
1266 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"center\":{\"type\":\"array\",\"items\":{\"type\":\"number\"},\"description\":\"[x,y,z] center point to orbit (default [0,0,0])\"},\"fov\":{\"type\":\"number\",\"description\":\"base vertical FOV in degrees (default 60)\"}}}}"
1268 "{\"name\":\"set_camera_pose\",\"description\":\"Set the active camera pose from position + Euler rotation + optional FOV.\","
1269 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"pos\":{\"type\":\"array\",\"items\":{\"type\":\"number\"},\"description\":\"[x,y,z] camera position\"},\"rot\":{\"type\":\"array\",\"items\":{\"type\":\"number\"},\"description\":\"[yawDeg,pitchDeg] facing\"},\"fov\":{\"type\":\"number\",\"description\":\"vertical FOV in degrees (0=keep current)\"}},\"required\":[\"pos\",\"rot\"]}}"
1271 "{\"name\":\"capture_render_frame\",\"description\":\"Atomically capture the current view and export matching buffers. PNG color frame + geometry JSON always; 'buffers' may add depth/normal (GBuffer), id (per-pixel render ID mask with JSON mapping) — shadow is unsupported on current backend.\","
1272 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"dir\":{\"type\":\"string\",\"description\":\"output directory (default: cache dir)\"},\"tag\":{\"type\":\"string\",\"description\":\"file name tag (default 'frame')\"},\"buffers\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"optional: 'color'|'depth'|'normal'|'id' (default ['color'])\"}}}}"
1274 "{\"name\":\"get_visible_entities_screen_bbox\",\"description\":\"Return visible scene entities in the frustum with their screen-space bbox, world AABB, id and asset label.\","
1275 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"pos\":{\"type\":\"array\",\"items\":{\"type\":\"number\"},\"description\":\"optional [x,y,z] camera eye override\"},\"target\":{\"type\":\"array\",\"items\":{\"type\":\"number\"},\"description\":\"optional [x,y,z] look target override\"},\"fov\":{\"type\":\"number\",\"description\":\"optional FOV override (degrees)\"}}}}"
1277 "{\"name\":\"eve_scene_status\",\"description\":\"Active scene host name, node count and root id.\","
1278 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1279 "{\"name\":\"eve_scene_nodes\",\"description\":\"List nodes of the active scene host (id/name/path/visible).\","
1280 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"limit\":{\"type\":\"integer\"}}}},"
1281 "{\"name\":\"eve_scene_node_get\",\"description\":\"Read transform/visibility/parent/children of a scene node by id.\","
1282 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}},\"required\":[\"id\"]}},"
1283 "{\"name\":\"eve_scene_node_set\",\"description\":\"Set position (x,y,z) or visibility of a scene node by id.\","
1284 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"},\"z\":{\"type\":\"number\"},\"visible\":{\"type\":\"boolean\"}},\"required\":[\"id\"]}},"
1285 "{\"name\":\"eve_procgen_recipes\",\"description\":\"List available procgen map algorithms and mesh/texture/PBR recipes.\","
1286 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1287 "{\"name\":\"eve_procgen_map\",\"description\":\"Generate a semantic tile grid with a procgen map algorithm.\","
1288 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"algorithm\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"seed\":{\"type\":\"integer\"}},\"required\":[\"algorithm\"]}},"
1289 "{\"name\":\"eve_procgen_mesh\",\"description\":\"Build a procedural CPU mesh (mesh.* recipe) and return stats.\","
1290 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"recipe\":{\"type\":\"string\"},\"seed\":{\"type\":\"integer\"}},\"required\":[\"recipe\"]}},"
1291 "{\"name\":\"eve_physics_new_world\",\"description\":\"Create a 2D physics world and return its id.\","
1292 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"gravityX\":{\"type\":\"number\"},\"gravityY\":{\"type\":\"number\"}}}},"
1293 "{\"name\":\"eve_physics_list_worlds\",\"description\":\"List live 2D physics worlds with gravity.\","
1294 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1295 "{\"name\":\"eve_physics_raycast\",\"description\":\"Raycast a segment in a physics world and report the closest hit.\","
1296 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"world\":{\"type\":\"integer\"},\"x1\":{\"type\":\"number\"},\"y1\":{\"type\":\"number\"},\"x2\":{\"type\":\"number\"},\"y2\":{\"type\":\"number\"}},\"required\":[\"world\",\"x1\",\"y1\",\"x2\",\"y2\"]}},"
1297 "{\"name\":\"eve_physics_remove_world\",\"description\":\"Destroy a 2D physics world by id.\","
1298 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"world\":{\"type\":\"integer\"}},\"required\":[\"world\"]}},"
1299 "{\"name\":\"eve_render_status\",\"description\":\"Render window size, 3D frame flag, readback state and RenderFlow event count.\","
1300 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1301 "{\"name\":\"eve_screenshot\",\"description\":\"Capture the current frame to a PNG file (enables readback).\","
1302 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"path\":{\"type\":\"string\"}}}},"
1303 "{\"name\":\"eve_render_describe\",\"description\":\"Capture the current frame and ask the configured vision model to describe it and relate it to render parameters. Cached unless fresh=true.\","
1304 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"fresh\":{\"type\":\"boolean\"},\"reason\":{\"type\":\"string\"}}}},"
1305 "{\"name\":\"eve_render_vision_config\",\"description\":\"Set/read the vision model config (baseUrl/apiKey/model/path/timeoutMs). No args returns current config (key masked).\","
1306 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"baseUrl\":{\"type\":\"string\"},\"apiKey\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"timeoutMs\":{\"type\":\"integer\"}}}},"
1307 "{\"name\":\"eve_particles_status\",\"description\":\"Report live particle emitter count.\","
1308 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1309 "{\"name\":\"eve_particles_emit\",\"description\":\"Spawn a particle emitter at a position (optionally a preset) and emit particles.\","
1310 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"},\"preset\":{\"type\":\"string\"},\"count\":{\"type\":\"integer\"}}}},"
1311 "{\"name\":\"eve_audio_status\",\"description\":\"Report master audio volume.\","
1312 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},"
1313 "{\"name\":\"eve_audio_set_volume\",\"description\":\"Set master audio volume (0..1).\","
1314 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"volume\":{\"type\":\"number\"}},\"required\":[\"volume\"]}},"
1315 "{\"name\":\"eve_audio_stop_all\",\"description\":\"Stop all playing audio sources.\","
1316 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},",
1317 "{\"name\":\"eve_host_status\",\"description\":\"Headless editor host status: window, editors, registered ViewModels, project root.\","
1318 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},",
1319 "{\"name\":\"eve_host_window_open\",\"description\":\"Create the host OS window (lazy; editors can also auto-open it).\","
1320 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"title\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"}}}},"
1321 "{\"name\":\"eve_host_window_close\",\"description\":\"Close the host OS window (MCP server stays alive).\","
1322 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},",
1323 "{\"name\":\"eve_host_window_state\",\"description\":\"Host window open state + size.\","
1324 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},",
1325 "{\"name\":\"eve_host_editor_apply\",\"description\":\"Apply an editor View (JSON widget tree). Auto-opens the host window on first use.\","
1326 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"editor\":{\"type\":\"object\"}},\"required\":[\"editor\"]}},",
1327 "{\"name\":\"eve_host_editor_remove\",\"description\":\"Remove an editor panel from the session.\","
1328 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}},\"required\":[\"id\"]}},",
1329 "{\"name\":\"eve_host_editor_list\",\"description\":\"List editors (id/title/vm).\","
1330 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}},",
1331 "{\"name\":\"eve_host_editor_state\",\"description\":\"Editor values + pending events (id omitted = all editors).\","
1332 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}}}},"
1333 "{\"name\":\"eve_host_editor_set_value\",\"description\":\"Set a widget value (JSON value). Writes the bound ViewModel and emits a change event.\","
1334 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"editor\":{\"type\":\"string\"},\"widget\":{\"type\":\"string\"},\"value\":{}},\"required\":[\"editor\",\"widget\",\"value\"]}},",
1335 "{\"name\":\"eve_host_editor_save\",\"description\":\"Persist editor as editors/<id>.editor.json + <id>.vm.nut in the project.\","
1336 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}},\"required\":[\"id\"]}},",
1337 "{\"name\":\"eve_host_editor_unload\",\"description\":\"Remove an editor from the session (files stay on disk).\","
1338 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}},\"required\":[\"id\"]}},",
1339 "{\"name\":\"eve_host_vm_register\",\"description\":\"Compile a Squirrel ViewModel and register it by table name.\","
1340 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"source\":{\"type\":\"string\"}},\"required\":[\"name\",\"source\"]}},",
1341 "{\"name\":\"eve_host_vm_unregister\",\"description\":\"Unregister a ViewModel table.\","
1342 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"}},\"required\":[\"name\"]}},",
1343 "{\"name\":\"eve_host_events\",\"description\":\"Read and clear interaction events (human clicks/sliders or AI set_value).\","
1344 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"editor\":{\"type\":\"string\"}}}},"
1345 "{\"name\":\"eve_host_widget_rect\",\"description\":\"Last-frame screen rect of a widget (for script drawing in viewports).\","
1346 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"editor\":{\"type\":\"string\"},\"widget\":{\"type\":\"string\"}},\"required\":[\"editor\",\"widget\"]}},",
1347 "{\"name\":\"eve_host_capture\",\"description\":\"Capture the host window to a PNG and return path/size.\","
1348 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"path\":{\"type\":\"string\"}}}},"
1349 "{\"name\":\"eve_host_script\",\"description\":\"Run a Squirrel snippet in the host VM (full engine API + eve.host).\","
1350 "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"source\":{\"type\":\"string\"}},\"required\":[\"source\"]}},",
1351 "{\"name\":\"eve_host_shutdown\",\"description\":\"Exit the headless MCP host process.\","
1352 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}}",
1355 static const std::string kToolsJson = [] {
1357 for (
const char*
p : kToolsParts) out +=
p;
1360 return makeResult(idJson, kToolsJson);
1363std::string handleToolsCall(McpServer& mcp,
const std::string& idJson,
1364 Poco::JSON::Object::Ptr params) {
1365 if (!params || !params->has(
"name"))
1366 return makeError(idJson, -32602,
"tools/call requires params.name");
1369 name = params->get(
"name").convert<std::string>();
1371 return makeError(idJson, -32602,
"tools/call params.name must be a string");
1373 Poco::JSON::Object::Ptr args = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1374 if (params->has(
"arguments")) {
1376 args = params->getObject(
"arguments");
1383 if (args) detail = mcpStringify(Poco::Dynamic::Var(args));
1389 const std::string out = callTool(mcp,
name, args);
1390 const bool isErr = out.rfind(
"error:", 0) == 0;
1391 return makeResult(idJson, textContentResult(out, isErr));
1392 }
catch (
const std::exception& e) {
1394 return makeResult(idJson, textContentResult(std::string(
"error: ") + e.what(),
true));
1398std::string handleResourcesList(
const std::string& idJson) {
1399 return makeResult(idJson,
1401 "{\"uri\":\"eve://status\",\"name\":\"status\",\"description\":\"Debugger / MCP / DAP status\",\"mimeType\":\"application/json\"},"
1402 "{\"uri\":\"eve://error-report\",\"name\":\"error-report\",\"description\":\"Last DevTools error slice report\",\"mimeType\":\"text/plain\"},"
1403 "{\"uri\":\"eve://ai-session\",\"name\":\"ai-session\",\"description\":\"AI / MCP session log\",\"mimeType\":\"text/plain\"},"
1404 "{\"uri\":\"eve://callgraph\",\"name\":\"callgraph\",\"description\":\"CallGraph event summary\",\"mimeType\":\"application/json\"}"
1408std::string handleResourcesRead(
const std::string& idJson, Poco::JSON::Object::Ptr params) {
1409 if (!params || !params->has(
"uri"))
1410 return makeError(idJson, -32602,
"resources/read requires params.uri");
1413 uri = params->get(
"uri").convert<std::string>();
1415 return makeError(idJson, -32602,
"resources/read params.uri must be a string");
1418 std::string mime =
"text/plain";
1419 if (uri ==
"eve://status") {
1421 mime =
"application/json";
1422 }
else if (uri ==
"eve://error-report") {
1424 if (
text.empty())
text =
"(no error report yet)\n";
1425 }
else if (uri ==
"eve://ai-session") {
1427 if (
text.empty())
text =
"(empty)\n";
1428 }
else if (uri ==
"eve://callgraph") {
1429 Poco::JSON::Object::Ptr o = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1432 text = mcpStringify(Poco::Dynamic::Var(o));
1433 mime =
"application/json";
1435 return makeError(idJson, -32002,
"Unknown resource: " + uri);
1438 Poco::JSON::Object::Ptr contentsItem = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1439 contentsItem->set(
"uri", uri);
1440 contentsItem->set(
"mimeType", mime);
1441 contentsItem->set(
"text", text);
1442 Poco::JSON::Array::Ptr contents = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
1443 contents->add(contentsItem);
1444 Poco::JSON::Object::Ptr result = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1445 result->set(
"contents", contents);
1446 return makeResult(idJson, mcpStringify(Poco::Dynamic::Var(result)));
1449std::string handlePromptsList(
const std::string& idJson) {
1450 return makeResult(idJson,
1452 "{\"name\":\"debug_failure\",\"description\":\"Investigate the latest script/render error using MCP tools and the error slice.\"},"
1453 "{\"name\":\"test_scenario\",\"description\":\"Drive a reproducible in-engine test: pause, snapshot, eval assertions, continue.\"},"
1454 "{\"name\":\"ai_game_review\",\"description\":\"Review live game state for AI-generated content issues.\"}"
1458std::string handlePromptsGet(
const std::string& idJson, Poco::JSON::Object::Ptr params) {
1459 if (!params || !params->has(
"name"))
1460 return makeError(idJson, -32602,
"prompts/get requires params.name");
1463 name = params->get(
"name").convert<std::string>();
1465 return makeError(idJson, -32602,
"prompts/get params.name must be a string");
1468 if (
name ==
"debug_failure") {
1470 "You are debugging an EVEngine game via MCP.\n"
1471 "1) Call eve_status and read resource eve://error-report.\n"
1472 "2) Use eve_stack / eve_locals / eve_eval to inspect state.\n"
1473 "3) Use eve_error_slice to narrow script/render causes.\n"
1474 "4) Propose a minimal fix; verify with eve_run_script or snapshot restore.";
1475 }
else if (
name ==
"test_scenario") {
1477 "Design a short automated test against the live EVEngine session:\n"
1478 "1) eve_pause then eve_snapshot_capture as baseline.\n"
1479 "2) Mutate or advance with eve_step_frame / eve_run_script.\n"
1480 "3) Assert with eve_eval / eve_watch_list.\n"
1481 "4) eve_snapshot_restore to reset; record notes via eve_ai_note.";
1482 }
else if (
name ==
"ai_game_review") {
1484 "Review this AI-generated or AI-assisted game build:\n"
1485 "1) eve_status + eve_ai_log for recent agent actions.\n"
1486 "2) Spot-check critical roots with eve_eval.\n"
1487 "3) If unstable, pause and capture a snapshot.\n"
1488 "4) Summarize risks and suggested script fixes.";
1490 return makeError(idJson, -32602,
"Unknown prompt: " +
name);
1493 Poco::JSON::Object::Ptr msg = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1494 msg->set(
"role",
"user");
1495 Poco::JSON::Array::Ptr content = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
1496 Poco::JSON::Object::Ptr part = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1497 part->set(
"type",
"text");
1498 part->set(
"text", text);
1500 msg->set(
"content", content);
1501 Poco::JSON::Array::Ptr messages = Poco::JSON::Array::Ptr(
new Poco::JSON::Array());
1503 Poco::JSON::Object::Ptr result = Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1504 result->set(
"description",
name);
1505 result->set(
"messages", messages);
1506 return makeResult(idJson, mcpStringify(Poco::Dynamic::Var(result)));
1518McpServer::McpServer() =
default;
1520McpServer::~McpServer() {
1523 std::lock_guard<std::mutex> lock(ioMu_);
1538 stdioQueue_.clear();
1539 listening_.store(
false);
1540 hasClient_.store(
false);
1546 for (
char&
c : root) {
1547 if (
c ==
'\\')
c =
'/';
1549 while (!root.empty() && (root.back() ==
'/' || root.back() ==
'\\')) root.pop_back();
1550 gameRoot_ = std::move(root);
1556 Poco::Net::SocketAddress addr(
"127.0.0.1",
port);
1557 server_ = std::make_unique<Poco::Net::ServerSocket>(addr);
1558 server_->setBlocking(
false);
1559 const int bound =
static_cast<int>(server_->address().port());
1561 listening_.store(
true);
1562 initialized_ =
false;
1564 if (gameRoot_.empty())
setGameRoot(std::filesystem::current_path().string());
1570 "listening on 127.0.0.1:" + std::to_string(bound));
1575 listening_.store(
false);
1586 std::lock_guard<std::mutex> lock(ioMu_);
1587 stdioQueue_.clear();
1590 listening_.store(
true);
1592 initialized_ =
false;
1593 stdinClosed_.store(
false);
1594 hasClient_.store(
true);
1597 joinReader_ = (&in != &std::cin);
1599 stdioReader_ = std::thread([
this, &in]() {
1601 while (listening_.load() && std::getline(in,
line)) {
1602 if (!line.empty() && line.back() ==
'\r') line.pop_back();
1603 if (line.empty()) continue;
1605 std::lock_guard<std::mutex> lock(ioMu_);
1606 stdioQueue_.push_back(std::move(line));
1609 stdinClosed_.store(
true);
1610 hasClient_.store(
false);
1613 transport_.store(Transport::None);
1614 listening_.store(
false);
1615 hasClient_.store(
false);
1621 "stdio transport ready (newline JSON-RPC)");
1625void McpServer::stop() {
1626 listening_.store(
false);
1627 hasClient_.store(
false);
1628 if (stdioReader_.joinable()) {
1630 stdioReader_.join();
1632 stdioReader_.detach();
1635 std::lock_guard<std::mutex> lock(ioMu_);
1650 stdioQueue_.clear();
1653 transport_.store(Transport::None);
1654 initialized_ =
false;
1656 stdioOut_ =
nullptr;
1657 AiPanel::instance().setMcpPort(0);
1658 AiPanel::instance().setMcpConnected(
false);
1659 AiPanel::instance().setClientName({});
1662void McpServer::poll() {
1663 if (!listening_.load())
return;
1664 if (transport_.load() == Transport::Stdio) {
1665 std::vector<std::string> batch;
1667 std::lock_guard<std::mutex> lock(ioMu_);
1668 batch.swap(stdioQueue_);
1670 for (
const auto&
line : batch) handleMessage(
line);
1673 acceptNonBlocking();
1677 if (RenderVision::instance().pending()) {
1678 auto* cap = mcpCapture();
1679 if (cap) RenderVision::instance().pollPending(cap, renderStatusText(cap));
1683void McpServer::acceptNonBlocking() {
1684 if (!server_ || client_)
return;
1686 Poco::Net::SocketAddress clientAddr;
1687 Poco::Net::StreamSocket ss = server_->acceptConnection(clientAddr);
1688 ss.setBlocking(
true);
1689 ss.setReceiveTimeout(Poco::Timespan(0, 1000));
1690 client_ = std::make_unique<Poco::Net::StreamSocket>(ss);
1691 hasClient_.store(
true);
1693 initialized_ =
false;
1694 AiPanel::instance().setMcpConnected(
true);
1695 AiPanel::instance().addLog(
"system",
"mcp.accept",
"client connected");
1696 }
catch (
const Poco::TimeoutException&) {
1697 }
catch (
const Poco::Net::NetException&) {
1702bool McpServer::sendLine(
const std::string& json) {
1703 if (transport_.load() == Transport::Stdio) {
1704 std::lock_guard<std::mutex> lock(ioMu_);
1705 if (!stdioOut_)
return false;
1707 const std::string frame = json +
"\n";
1708 (*stdioOut_) << frame;
1715 if (!client_)
return false;
1716 const std::string frame = json +
"\n";
1718 const int sent = client_->sendBytes(frame.data(),
static_cast<int>(frame.size()));
1719 return sent ==
static_cast<int>(frame.size());
1722 hasClient_.store(
false);
1723 AiPanel::instance().setMcpConnected(
false);
1728void McpServer::readAndDispatch() {
1729 if (!client_)
return;
1732 const int n = client_->receiveBytes(buf,
sizeof(buf));
1736 hasClient_.store(
false);
1737 AiPanel::instance().setMcpConnected(
false);
1738 AiPanel::instance().addLog(
"system",
"mcp.disconnect",
"client closed");
1742 recvBuf_.append(buf,
static_cast<size_t>(
n));
1743 }
catch (
const Poco::TimeoutException&) {
1745 }
catch (
const Poco::Net::NetException&) {
1749 hasClient_.store(
false);
1750 AiPanel::instance().setMcpConnected(
false);
1755 const auto nl = recvBuf_.find(
'\n');
1756 if (nl == std::string::npos)
break;
1757 std::string
line = recvBuf_.substr(0, nl);
1758 recvBuf_.erase(0, nl + 1);
1759 if (!
line.empty() &&
line.back() ==
'\r')
line.pop_back();
1760 if (
line.empty())
continue;
1761 handleMessage(
line);
1765void McpServer::handleMessage(
const std::string& json) {
1767 Poco::JSON::Parser parser;
1768 auto var = parser.parse(json);
1769 auto obj = var.extract<Poco::JSON::Object::Ptr>();
1773 if (obj->has(
"method")) {
1775 method = obj->get(
"method").convert<std::string>();
1776 }
catch (
const Poco::Exception& e) {
1777 sendLine(makeError(
"null", -32600, std::string(
"bad method: ") + e.displayText()));
1781 const bool hasId = obj->has(
"id");
1782 Poco::Dynamic::Var idVar;
1783 if (hasId) idVar = obj->get(
"id");
1784 std::string idJson =
"null";
1786 if (hasId) idJson = idToJson(idVar);
1787 }
catch (
const Poco::Exception& e) {
1788 sendLine(makeError(
"null", -32600, std::string(
"bad id: ") + e.displayText()));
1793 if (method ==
"notifications/initialized" || method ==
"initialized") {
1794 initialized_ =
true;
1795 AiPanel::instance().addLog(
"system",
"mcp.initialized",
"handshake complete");
1800 auto params = [&]() -> Poco::JSON::Object::Ptr {
1801 if (!obj->has(
"params"))
return Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1803 return obj->getObject(
"params");
1805 return Poco::JSON::Object::Ptr(
new Poco::JSON::Object());
1809 if (method ==
"initialize") {
1810 sendLine(handleInitialize(*
this, idJson, params()));
1813 if (method ==
"ping") {
1814 sendLine(makeResult(idJson,
"{}"));
1817 if (method ==
"tools/list") {
1818 sendLine(handleToolsList(idJson));
1821 if (method ==
"tools/call") {
1822 sendLine(handleToolsCall(*
this, idJson, params()));
1825 if (method ==
"resources/list") {
1826 sendLine(handleResourcesList(idJson));
1829 if (method ==
"resources/read") {
1830 sendLine(handleResourcesRead(idJson, params()));
1833 if (method ==
"prompts/list") {
1834 sendLine(handlePromptsList(idJson));
1837 if (method ==
"prompts/get") {
1838 sendLine(handlePromptsGet(idJson, params()));
1842 sendLine(makeError(idJson, -32601,
"Method not found: " + method));
1843 }
catch (
const Poco::Exception& e) {
1844 sendLine(makeError(
"null", -32700, std::string(
"Parse error: ") + e.displayText()));
1845 }
catch (
const std::exception& e) {
1846 sendLine(makeError(
"null", -32603, e.what()));
1848 sendLine(makeError(
"null", -32603,
"Internal error"));
struct SQVM * HSQUIRRELVM
std::vector< std::string > vars
Audio control surface (provided by the audio module).
Declarative editor-host control (provided by the ui module).
Particle system query surface (provided by the particles module).
2D physics world query surface (provided by the physics module).
Procedural generation query surface (provided by the procgen module).
Frame capture + camera + visible-entity inspection (graphics).
virtual bool savePng(const std::string &path, int *outWidth, int *outHeight, std::string *err)=0
Capture the last presented frame and write a PNG.
virtual RenderStatusInfo status() const =0
Scene graph query/mutation surface (provided by the scene module).
void addNote(std::string text)
std::string formatLog(size_t max=64) const
void setMcpPort(int port)
void addLog(std::string kind, std::string title, std::string detail={})
static AiPanel & instance()
void setMcpConnected(bool on)
void setClientName(std::string name)
static DebugAdapter & instance()
static Debugger & instance()
Embedded Model Context Protocol (MCP) server for AI-assisted game development.
void setGameRoot(std::string root)
Game / project directory hint for agents.
int listen(uint16_t port)
Bind TCP listen port (0 = ephemeral). Returns bound port or 0 on failure.
bool listenStdio(std::istream &in=std::cin, std::ostream &out=std::cout)
Switch to stdio transport (MCP stdio server). A reader thread pulls newline-delimited JSON from in in...
static McpServer & instance()
static RenderVision & instance()
std::string describe(eve::IRenderCapture *cap, const std::string &renderDataJson, bool fresh, const std::string &reason={})
Main-thread capture + vision describe. Returns the description text, or a string starting with "error...
std::string currentPoseJson()
当前激活相机位姿 JSON(eye/target/fov/viewport)。
std::string visibleEntitiesJson(const glm::vec3 *eye=nullptr, const glm::vec3 *target=nullptr, float fov=0.f)
生成当前视锥内可见实体的结构化 JSON: { camera, viewport, entities:[ { id, asset, world_aabb:{min,...
InspectCapture capture(const std::string &outDir={}, const std::string &tag="frame", const std::vector< std::string > &buffers={})
锁定当前相机 → 捕获渲染帧 PNG → 立刻导出配套可见实体几何 JSON。 两者共享同一相机位姿,杜绝图片与实体数据错位。 outDir 为空时使用缓存目录。返回文件路径与图像尺寸。 buffers...
static SceneInspect & instance()
bool setCameraPose(const glm::vec3 &eye, const glm::vec3 &rotYawPitch, float fov=0.f)
以 pos + 欧拉角 rot 设置相机位姿。 rot = [yawDeg, pitchDeg](与 firstperson 约定一致,Y-up)。 fov <= 0 时保持当前 FOV。
static std::vector< InspectView > generateViews(const glm::vec3 ¢er, float fov=60.f)
围绕 center 自动生成一组标准化巡检机位:
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
static Snapshot & instance()
std::size_t mcpCallgraphStackDepth()
std::size_t mcpCallgraphEvents()
std::string mcpFormatError(const std::string &message)
const std::string & mcpLastReport()
bool mcpDevAttached()
Thin hooks so McpServer.cpp need not include DevTool.hpp (avoids a cycle).
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
WidgetDesc text(std::string content, std::string id)
Static text label.
const char * scene_director_content
Ray-cast result (value type).
Render surface / frame state snapshot (value type).
Serializable snapshot of one scene node (value type, no scene types).
static T & get()
Returns the process-lifetime instance.
Structured snapshot of a script error: message, throw site and stack.
bool empty() const noexcept
True when no error payload was captured.