#ifndef ESP_PLATFORM #include #include #include #include #include #include #include #include #include #include namespace tt::service::webserver { namespace { constexpr size_t JSON_BODY_LIMIT = 64 * 1024; cJSON* makeError(const cJSON* id, int code, const char* message) { cJSON* response = cJSON_CreateObject(); cJSON_AddStringToObject(response, "jsonrpc", "2.0"); cJSON* error = cJSON_AddObjectToObject(response, "error"); cJSON_AddNumberToObject(error, "code", code); cJSON_AddStringToObject(error, "message", message); if (id != nullptr) cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, true)); else cJSON_AddNullToObject(response, "id"); return response; } cJSON* makeTextResult(const cJSON* id, const char* text, bool isError = false) { cJSON* response = cJSON_CreateObject(); cJSON_AddStringToObject(response, "jsonrpc", "2.0"); cJSON* result = cJSON_AddObjectToObject(response, "result"); cJSON* content = cJSON_AddArrayToObject(result, "content"); cJSON* item = cJSON_CreateObject(); cJSON_AddStringToObject(item, "type", "text"); cJSON_AddStringToObject(item, "text", text); cJSON_AddItemToArray(content, item); if (isError) cJSON_AddBoolToObject(result, "isError", true); if (id != nullptr) cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, true)); else cJSON_AddNullToObject(response, "id"); return response; } cJSON* makeToolList(const cJSON* id) { cJSON* response = cJSON_CreateObject(); cJSON_AddStringToObject(response, "jsonrpc", "2.0"); cJSON* result = cJSON_AddObjectToObject(response, "result"); cJSON* tools = cJSON_AddArrayToObject(result, "tools"); cJSON* capabilities = cJSON_CreateObject(); cJSON_AddStringToObject(capabilities, "name", "get_capabilities"); cJSON_AddStringToObject(capabilities, "description", "Get the simulator platform and display capability information."); cJSON_AddItemToObject(capabilities, "inputSchema", cJSON_Parse("{\"type\":\"object\",\"properties\":{}}")); cJSON_AddItemToArray(tools, capabilities); cJSON* listApps = cJSON_CreateObject(); cJSON_AddStringToObject(listApps, "name", "list_apps"); cJSON_AddStringToObject(listApps, "description", "List apps available in the simulator."); cJSON_AddItemToObject(listApps, "inputSchema", cJSON_Parse("{\"type\":\"object\",\"properties\":{}}")); cJSON_AddItemToArray(tools, listApps); cJSON* runApp = cJSON_CreateObject(); cJSON_AddStringToObject(runApp, "name", "run_app"); cJSON_AddStringToObject(runApp, "description", "Launch an installed simulator app by its app ID."); cJSON_AddItemToObject(runApp, "inputSchema", cJSON_Parse("{\"type\":\"object\",\"properties\":{\"app_id\":{\"type\":\"string\"}},\"required\":[\"app_id\"]}")); cJSON_AddItemToArray(tools, runApp); if (id != nullptr) cJSON_AddItemToObject(response, "id", cJSON_Duplicate(id, true)); else cJSON_AddNullToObject(response, "id"); return response; } std::string listAppsJson() { std::vector manifests; app_manager_for_each_manifest([](const AppManifest* manifest, void* context) { static_cast*>(context)->push_back(manifest); }, &manifests); cJSON* apps = cJSON_CreateArray(); for (const AppManifest* manifest : manifests) { cJSON* app = cJSON_CreateObject(); cJSON_AddStringToObject(app, "id", manifest->id); cJSON_AddStringToObject(app, "name", manifest->name); cJSON_AddItemToArray(apps, app); } char* text = cJSON_PrintUnformatted(apps); std::string output = text == nullptr ? "[]" : text; cJSON_free(text); cJSON_Delete(apps); return output; } cJSON* handleCall(const cJSON* id, const cJSON* params) { const cJSON* name = cJSON_GetObjectItemCaseSensitive(params, "name"); const cJSON* args = cJSON_GetObjectItemCaseSensitive(params, "arguments"); if (!cJSON_IsString(name) || (args != nullptr && !cJSON_IsObject(args))) { return makeError(id, -32602, "Invalid params"); } if (strcmp(name->valuestring, "get_capabilities") == 0) { return makeTextResult(id, "{\"platform\":\"tactility-simulator\",\"mcp\":true}"); } if (strcmp(name->valuestring, "list_apps") == 0) { const std::string apps = listAppsJson(); return makeTextResult(id, apps.c_str()); } if (strcmp(name->valuestring, "run_app") == 0) { const cJSON* appId = cJSON_GetObjectItemCaseSensitive(args, "app_id"); if (!cJSON_IsString(appId) || appId->valuestring == nullptr || appId->valuestring[0] == '\0') { return makeError(id, -32602, "app_id is required"); } AppManifest manifest{}; if (app_manager_find_manifest(appId->valuestring, &manifest) != ERROR_NONE) { return makeTextResult(id, "app not found", true); } AppInstanceId instanceId = 0; const error_t result = app_start(appId->valuestring, 0, nullptr, &instanceId); if (result != ERROR_NONE) return makeTextResult(id, "app launch failed", true); return makeTextResult(id, "ok"); } return makeError(id, -32602, "Unknown simulator MCP tool"); } cJSON* handleRpc(const char* body) { cJSON* request = cJSON_Parse(body); if (request == nullptr) return makeError(nullptr, -32700, "Parse error"); const cJSON* id = cJSON_GetObjectItemCaseSensitive(request, "id"); const cJSON* version = cJSON_GetObjectItemCaseSensitive(request, "jsonrpc"); const cJSON* method = cJSON_GetObjectItemCaseSensitive(request, "method"); const cJSON* params = cJSON_GetObjectItemCaseSensitive(request, "params"); cJSON* response = nullptr; if (!cJSON_IsObject(request) || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0 || !cJSON_IsString(method)) { response = makeError(id, -32600, "Invalid Request"); } else if (strcmp(method->valuestring, "tools/list") == 0) { response = makeToolList(id); } else if (strcmp(method->valuestring, "tools/call") == 0) { response = handleCall(id, params); } else { response = makeError(id, -32601, "Method not found"); } cJSON_Delete(request); return response; } } error_t WebServerService::handleApiMcp(HttpServerRequest* request, void*) { const uint64_t length = http_server_request_get_content_length(request); if (length == 0 || length > JSON_BODY_LIMIT) { http_server_request_send_error(request, 400, "Invalid Content-Length"); return ERROR_INVALID_ARGUMENT; } std::string body(static_cast(length), '\0'); size_t received = 0; while (received < body.size()) { const int count = http_server_request_receive(request, body.data() + received, body.size() - received); if (count <= 0) { http_server_request_send_error(request, 400, "Incomplete request body"); return ERROR_UNDEFINED; } received += static_cast(count); } cJSON* response = handleRpc(body.c_str()); char* text = cJSON_PrintUnformatted(response); cJSON_Delete(response); if (text == nullptr) { http_server_request_send_error(request, 500, "Serialization failed"); return ERROR_RESOURCE; } http_server_request_set_content_type(request, "application/json"); http_server_request_send_string(request, text); cJSON_free(text); return ERROR_NONE; } } #endif // ESP_PLATFORM