载入中...
搜索中...
未找到
McpDomainTools.cpp
浏览该文件的文档.
2
5
7#include "common/Capability.h"
8#include "common/DecalQuery.h"
11#include "common/SensingQuery.h"
12
13#include <Poco/Dynamic/Var.h>
14#include <Poco/JSON/Object.h>
15#include <Poco/JSON/Parser.h>
16
17#include <algorithm>
18#include <array>
19#include <cstdint>
20#include <string>
21#include <string_view>
22
23namespace eve::dev {
24namespace {
25
26std::string textPayload(Poco::JSON::Object::Ptr object, bool isError = false) {
27 return textContentResult(mcpStringify(Poco::Dynamic::Var(object)), isError);
28}
29
30std::string errorPayload(const std::string& message) {
31 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
32 out->set("ok", false);
33 out->set("error", message);
34 return textPayload(out, true);
35}
36
38std::string unavailablePayload(const char* capability) {
39 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
40 out->set("ok", false);
41 out->set("unavailable", capability);
42 out->set("error", std::string(capability) + " is not provided by the loaded modules");
43 return textPayload(out);
44}
45
46// ============================== Decals =====================================
47
49std::array<float, 3> getArgVec3(Poco::JSON::Object::Ptr args, const char* key, const std::array<float, 3>& def) {
50 if (!args || !args->has(key)) return def;
51 try {
52 auto array = args->getArray(key);
53 if (array && array->size() >= 3) {
54 return {static_cast<float>(array->get(0).convert<double>()),
55 static_cast<float>(array->get(1).convert<double>()),
56 static_cast<float>(array->get(2).convert<double>())};
57 }
58 } catch (...) {
59 }
60 return def;
61}
62
64constexpr std::uint32_t kAllCategoryBits = 0xFFFFFFFFu;
65
66std::string decalStatus() {
67 auto* decals = eve::cap::query<eve::IDecalQuery>();
68 if (!decals) return unavailablePayload(eve::IDecalQuery::capabilityName);
69 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
70 out->set("ok", true);
71 out->set("count", decals->count());
72 return textPayload(out);
73}
74
75std::string decalProject(Poco::JSON::Object::Ptr args) {
76 auto* decals = eve::cap::query<eve::IDecalQuery>();
77 if (!decals) return unavailablePayload(eve::IDecalQuery::capabilityName);
78
79 // A decal needs a surface normal; up is the least surprising default, and a
80 // caller that omits it almost always means "on the ground".
81 const float nx = getArgFloat(args, "nx", 0.f);
82 const float ny = getArgFloat(args, "ny", 1.f);
83 const float nz = getArgFloat(args, "nz", 0.f);
84
85 // Zero means "engine default" for every size/time field: the decal module
86 // owns those defaults, so the tool must not invent a second set.
87 const int id =
88 decals->project(getArgFloat(args, "x"), getArgFloat(args, "y"), getArgFloat(args, "z"), nx, ny, nz,
89 /*albedoTexture=*/nullptr, getArgString(args, "kind"), getArgFloat(args, "size"),
90 getArgFloat(args, "depth"), getArgBool(args, "randomYaw"), getArgInt(args, "seed"),
91 getArgFloat(args, "fadeIn"), getArgFloat(args, "lifetime"), getArgFloat(args, "fadeOut"));
92 if (id <= 0) return errorPayload("decal projection was rejected");
93
94 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
95 out->set("ok", true);
96 out->set("id", id);
97 out->set("count", decals->count());
98 return textPayload(out);
99}
100
101std::string decalRemove(Poco::JSON::Object::Ptr args) {
102 auto* decals = eve::cap::query<eve::IDecalQuery>();
103 if (!decals) return unavailablePayload(eve::IDecalQuery::capabilityName);
104 const int id = getArgInt(args, "id", -1);
105 if (id <= 0) return errorPayload("missing decal id");
106 if (!decals->remove(id)) return errorPayload("no decal with id " + std::to_string(id));
107
108 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
109 out->set("ok", true);
110 out->set("count", decals->count());
111 return textPayload(out);
112}
113
114std::string decalClear() {
115 auto* decals = eve::cap::query<eve::IDecalQuery>();
116 if (!decals) return unavailablePayload(eve::IDecalQuery::capabilityName);
117 const int before = decals->count();
118 decals->clearAll();
119
120 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
121 out->set("ok", true);
122 out->set("removed", before);
123 out->set("count", decals->count());
124 return textPayload(out);
125}
126
127std::string decalSetLimit(Poco::JSON::Object::Ptr args) {
128 auto* decals = eve::cap::query<eve::IDecalQuery>();
129 if (!decals) return unavailablePayload(eve::IDecalQuery::capabilityName);
130 const std::string kind = getArgString(args, "kind");
131 if (kind.empty()) return errorPayload("missing kind");
132 const int limit = getArgInt(args, "limit", -1);
133 if (limit < 0) return errorPayload("limit must be >= 0");
134 decals->setLimit(kind, limit);
135
136 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
137 out->set("ok", true);
138 out->set("kind", kind);
139 out->set("limit", limit);
140 return textPayload(out);
141}
142
143// ====================== 3D spatial queries ==================================
144// The 2D tools (eve_physics_raycast) cannot answer "what is between the camera
145// and the player" or "how high is the ground here" in a 3D scene; these two
146// capabilities already existed and only lacked an agent surface.
147
148std::string physicsSphereCast(Poco::JSON::Object::Ptr args) {
149 auto* cast = eve::cap::query<eve::ICameraObstructionQuery>();
150 if (!cast) return unavailablePayload(eve::ICameraObstructionQuery::capabilityName);
151 if (!args || !args->has("from") || !args->has("to")) return errorPayload("from and to are required");
152
153 const std::array<float, 3> from = getArgVec3(args, "from", {0.f, 0.f, 0.f});
154 const std::array<float, 3> to = getArgVec3(args, "to", {0.f, 0.f, 0.f});
155 const float radius = getArgFloat(args, "radius", 0.f);
156 const long long mask = getArgInt64(args, "maskBits", static_cast<long long>(kAllCategoryBits));
157
159 const bool swept = cast->sphereCast(from[0], from[1], from[2], to[0], to[1], to[2], radius,
160 static_cast<std::uint64_t>(mask), getArgInt(args, "ignoredBodyId", -1), &hit);
161 if (!swept) return errorPayload("sphere cast is unavailable (no live 3D physics world)");
162
163 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
164 out->set("ok", true);
165 out->set("hit", hit.hit);
166 out->set("radius", radius);
167 if (hit.hit) {
168 out->set("bodyId", hit.bodyId);
169 out->set("fraction", hit.fraction);
170 Poco::JSON::Array::Ptr point = Poco::JSON::Array::Ptr(new Poco::JSON::Array());
171 point->add(hit.x);
172 point->add(hit.y);
173 point->add(hit.z);
174 out->set("point", point);
175 Poco::JSON::Array::Ptr normal = Poco::JSON::Array::Ptr(new Poco::JSON::Array());
176 normal->add(hit.normalX);
177 normal->add(hit.normalY);
178 normal->add(hit.normalZ);
179 out->set("normal", normal);
180 }
181 return textPayload(out);
182}
183
184std::string worldProjectDown(Poco::JSON::Object::Ptr args) {
185 auto* world = eve::cap::query<eve::IProcgenWorldQuery>();
186 if (!world) return unavailablePayload(eve::IProcgenWorldQuery::capabilityName);
187
188 const float x = getArgFloat(args, "x");
189 const float z = getArgFloat(args, "z");
190 const float maxY = getArgFloat(args, "maxY", 1000.f);
191 const float minY = getArgFloat(args, "minY", -1000.f);
192 const long long mask = getArgInt64(args, "maskBits", static_cast<long long>(kAllCategoryBits));
193
194 auto projected = world->projectDown(x, z, maxY, minY, static_cast<std::uint64_t>(mask));
195 if (!projected) return errorPayload("world projection failed: " + projected.status().describe());
196
197 const eve::ProcgenSurfaceHit& surface = projected.value();
198 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
199 out->set("ok", true);
200 out->set("hit", surface.hit);
201 out->set("x", x);
202 out->set("z", z);
203 if (surface.hit) {
204 out->set("objectId", static_cast<Poco::Int64>(surface.objectId));
205 out->set("y", surface.y);
206 Poco::JSON::Array::Ptr normal = Poco::JSON::Array::Ptr(new Poco::JSON::Array());
207 normal->add(surface.normalX);
208 normal->add(surface.normalY);
209 normal->add(surface.normalZ);
210 out->set("normal", normal);
211 }
212 return textPayload(out);
213}
214
215// ========================== Profiler ========================================
216
218std::string profilerFrame() {
219 auto* profiler = eve::cap::query<eve::IProfilerQuery>();
220 if (!profiler) return unavailablePayload(eve::IProfilerQuery::capabilityName);
221 return textContentResult(profiler->frameJson());
222}
223
224std::string profilerReport() {
225 auto* profiler = eve::cap::query<eve::IProfilerQuery>();
226 if (!profiler) return unavailablePayload(eve::IProfilerQuery::capabilityName);
227 const std::string report = profiler->textReport();
228 return textContentResult(report.empty() ? "(no completed frame yet)\n" : report);
229}
230
231// ========================== Sensing =========================================
232
233std::string sensingLastQuery(Poco::JSON::Object::Ptr args) {
234 auto* sensing = eve::cap::query<eve::ISensingQuery>();
235 if (!sensing) return unavailablePayload(eve::ISensingQuery::capabilityName);
236
237 const bool hasIndex = args && args->has("world");
238 const int index = getArgInt(args, "world", -1);
239
240 Poco::JSON::Object::Ptr out = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
241 out->set("ok", true);
242 out->set("worldCount", sensing->worldCount());
243
244 Poco::JSON::Array::Ptr worlds = Poco::JSON::Array::Ptr(new Poco::JSON::Array());
245 bool found = false;
246 for (const auto& entry : sensing->lastQueries()) {
247 if (hasIndex && entry.index != index) continue;
248 found = true;
249 Poco::JSON::Object::Ptr item = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
250 item->set("index", entry.index);
251 // The world owns this document's schema (eve.sensing.lastQuery v1); nest
252 // it as an object so the agent does not have to parse JSON-in-JSON.
253 try {
254 Poco::JSON::Parser parser;
255 Poco::Dynamic::Var parsed = parser.parse(entry.lastQueryJson);
256 item->set("query", parsed);
257 } catch (...) {
258 item->set("query", entry.lastQueryJson);
259 }
260 worlds->add(item);
261 }
262 if (hasIndex && !found) {
263 Poco::JSON::Object::Ptr missing = Poco::JSON::Object::Ptr(new Poco::JSON::Object());
264 missing->set("ok", false);
265 missing->set("error", "no live sensing world with index " + std::to_string(index));
266 return textPayload(missing, true);
267 }
268 out->set("worlds", worlds);
269 return textPayload(out);
270}
271
272} // namespace
273
274bool isMcpDomainTool(std::string_view name) {
275 return name == "eve_decal_status" || name == "eve_decal_project" || name == "eve_decal_remove" ||
276 name == "eve_decal_clear" || name == "eve_decal_set_limit" || name == "eve_physics_sphere_cast" ||
277 name == "eve_world_project_down" || name == "eve_profiler_frame" || name == "eve_profiler_report" ||
278 name == "eve_sensing_last_query";
279}
280
281std::string callMcpDomainTool(std::string_view name, Poco::JSON::Object::Ptr args) {
282 if (name == "eve_decal_status") return decalStatus();
283 if (name == "eve_decal_project") return decalProject(args);
284 if (name == "eve_decal_remove") return decalRemove(args);
285 if (name == "eve_decal_clear") return decalClear();
286 if (name == "eve_decal_set_limit") return decalSetLimit(args);
287 if (name == "eve_physics_sphere_cast") return physicsSphereCast(args);
288 if (name == "eve_world_project_down") return worldProjectDown(args);
289 if (name == "eve_profiler_frame") return profilerFrame();
290 if (name == "eve_profiler_report") return profilerReport();
291 if (name == "eve_sensing_last_query") return sensingLastQuery(args);
292 return errorPayload("unknown domain tool '" + std::string(name) + "'");
293}
294
295std::string_view mcpDomainToolSchemas() {
296 return R"json({"name":"eve_decal_status","description":"Live decal count. Decals are runtime surface projections (scorch marks, blood, footprints) owned by the decal module.","inputSchema":{"type":"object","properties":{}}},{"name":"eve_decal_project","description":"Project a decal at a world position facing a normal. Omitted size/depth/lifetime/fade fields use the decal module defaults; no albedo texture is bound through this path, so decals take the module's default look.","inputSchema":{"type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"},"z":{"type":"number"},"nx":{"type":"number"},"ny":{"type":"number"},"nz":{"type":"number"},"kind":{"type":"string","description":"decal kind; a kind with a configured limit evicts its oldest instance first"},"size":{"type":"number"},"depth":{"type":"number"},"randomYaw":{"type":"boolean"},"seed":{"type":"integer","description":"deterministic yaw seed"},"fadeIn":{"type":"number"},"lifetime":{"type":"number"},"fadeOut":{"type":"number"}},"required":["x","y","z"]}},{"name":"eve_decal_remove","description":"Remove one decal by id and report the remaining count.","inputSchema":{"type":"object","properties":{"id":{"type":"integer","minimum":1}},"required":["id"]}},{"name":"eve_decal_clear","description":"Remove every decal and report how many were dropped.","inputSchema":{"type":"object","properties":{}}},{"name":"eve_decal_set_limit","description":"Set the per-kind instance limit used for eviction; 0 disables eviction for that kind.","inputSchema":{"type":"object","properties":{"kind":{"type":"string"},"limit":{"type":"integer","minimum":0}},"required":["kind","limit"]}},{"name":"eve_physics_sphere_cast","description":"Sweep a sphere (radius 0 = ray) through every registered 3D physics world and return the closest hit: bodyId, fraction along the segment, world point and surface normal. This is the 3D counterpart of eve_physics_raycast, which only queries 2D worlds.","inputSchema":{"type":"object","properties":{"from":{"type":"array","items":{"type":"number"},"description":"[x,y,z] segment start"},"to":{"type":"array","items":{"type":"number"},"description":"[x,y,z] segment end"},"radius":{"type":"number","minimum":0,"description":"sphere radius (default 0 = ray)"},"maskBits":{"type":"integer","description":"collision category mask; default matches every category, 0 matches none"},"ignoredBodyId":{"type":"integer","description":"body to skip, e.g. the camera rig"}},"required":["from","to"]}},{"name":"eve_world_project_down","description":"Project a vertical ray through the procedural/3D world surface at (x,z) and return the closest hit height and normal. Use it to place objects on terrain or to check ground level under a moving entity.","inputSchema":{"type":"object","properties":{"x":{"type":"number"},"z":{"type":"number"},"maxY":{"type":"number","description":"projection start height (default 1000)"},"minY":{"type":"number","description":"projection end height (default -1000)"},"maskBits":{"type":"integer","description":"collision category mask; default matches every category"}},"required":["x","z"]}},{"name":"eve_profiler_frame","description":"Last completed frame's profile as JSON (schema eve.profiler.frame): enabled/hasFrame, cpuFrameMs (sum of top-level zone self times), gpuMs when a GPU timer is available, and the per-module/per-zone self and total milliseconds sorted by self time. Use it for unattended performance work instead of guessing from wall-clock timings.","inputSchema":{"type":"object","properties":{}}},{"name":"eve_profiler_report","description":"Human-readable per-module/per-zone hotspot report of the last completed frame (the same text the profiler panel shows).","inputSchema":{"type":"object","properties":{}}},{"name":"eve_sensing_last_query","description":"Last completed sensing query of every live sensing world, with the world's own document (schema eve.sensing.lastQuery) nested as `query`: origin, shape kind, spatial scan counters, and the ranked candidates with their scores and score reasons. Answers why an NPC selected a particular target.","inputSchema":{"type":"object","properties":{"world":{"type":"integer","minimum":0,"description":"optional world index; omit to read every live world"}}}})json";
297}
298
299} // namespace eve::dev
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string from
int mask
Vec3 projected
Definition CaveMesh.cpp:122
float nx
float nz
float ny
std::string message
std::uint32_t key
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
TokenKind kind
Capability-backed domain tool families for the embedded MCP server.
std::string name
Texture * normal
World3D * world
float radius
bool hit
bool found
int limit
Definition TreeMesh.cpp:164
uint32_t index
glm::vec3 point
std::unordered_map< PhysicsWorldHandle::index_type, World3D * > worlds
Definition World3D.cpp:164
static constexpr const char * capabilityName
static constexpr const char * capabilityName
Definition DecalQuery.h:19
static constexpr const char * capabilityName
static constexpr const char * capabilityName
static constexpr const char * capabilityName
bool getArgBool(Poco::JSON::Object::Ptr args, const char *key, bool def=false)
Boolean argument, or def when absent or not convertible.
Definition McpArgs.hpp:65
bool isMcpDomainTool(std::string_view name)
Whether name belongs to a capability-backed domain tool family.
float getArgFloat(Poco::JSON::Object::Ptr args, const char *key, float def=0.f)
Floating-point argument, or def when absent or not convertible.
Definition McpArgs.hpp:55
std::string_view mcpDomainToolSchemas()
Comma-free MCP tool schema fragments for the domain families.
std::string mcpStringify(const Poco::Dynamic::Var &value)
Compact single-line JSON text for a newline-framed MCP payload.
Definition McpJson.hpp:23
std::string textContentResult(const std::string &text, bool isError=false)
MCP tools/call result object carrying one text content item.
Definition McpJson.hpp:59
long long getArgInt64(Poco::JSON::Object::Ptr args, const char *key, long long def=0)
64-bit integer argument (sequence numbers, handles), or def.
Definition McpArgs.hpp:45
int getArgInt(Poco::JSON::Object::Ptr args, const char *key, int def=0)
Integer argument, or def when absent or not convertible.
Definition McpArgs.hpp:35
std::string callMcpDomainTool(std::string_view name, Poco::JSON::Object::Ptr args)
Execute one domain tool.
std::string getArgString(Poco::JSON::Object::Ptr args, const char *key, const std::string &def={})
String argument, or def when absent or not convertible.
Definition McpArgs.hpp:25
Closest hit returned by the optional camera-obstruction provider.
Value result for a procedural world-surface projection query.