From 8815c4f8969031643f13f9d7aa4aa1b805ecc655 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Fri, 21 Aug 2026 20:33:59 +0200 Subject: [PATCH] Feature: Add get_audit_log MCP tool Expose the server main-window audit log (host online/offline, operation results, alerts) as a read-only, no-arg, in-memory MCP tool. The log lives in m_CList_Message, a CListCtrl that is only safe to touch on the UI thread, so add a thread-safe mirror m_MessageLog (guarded by m_cs) and keep it in sync at the four item-mutation sites: ShowMessage, OnShowErrMessage, OnMsglogDelete and OnMsglogClear. The tool reads the mirror directly under m_cs and returns up to MAX_MESSAGE_COUNT (1000) entries, newest first. Co-Authored-By: deepseek-v4-pro --- server/2015Remote/2015RemoteDlg.cpp | 36 +++++++++++++ server/2015Remote/2015RemoteDlg.h | 9 ++++ server/2015Remote/McpServer.cpp | 81 +++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 4eddc9e..a282323 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -1796,6 +1796,19 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg) m_CList_Message.SetItemText(0, 2, strMsg); m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg)); + // 同步线程安全镜像,供 MCP get_audit_log 读取(CListCtrl 仅 UI 线程可见)。 + { + EnterCriticalSection(&m_cs); + LogMessage entry; + entry.type = strType.GetString(); + entry.time = strTime.GetString(); + entry.msg = strMsg.GetString(); + m_MessageLog.insert(m_MessageLog.begin(), entry); + if ((int)m_MessageLog.size() > MAX_MESSAGE_COUNT) + m_MessageLog.pop_back(); + LeaveCriticalSection(&m_cs); + } + CString strStatusMsg; EnterCriticalSection(&m_cs); @@ -1875,6 +1888,18 @@ LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam) m_CList_Message.SetItemText(0, 1, strTime); m_CList_Message.SetItemText(0, 2, strText); m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, strText)); + // 同步线程安全镜像,供 MCP get_audit_log 读取(CListCtrl 仅 UI 线程可见)。 + { + EnterCriticalSection(&m_cs); + LogMessage entry; + entry.type = strTitle.GetString(); + entry.time = strTime.GetString(); + entry.msg = strText.GetString(); + m_MessageLog.insert(m_MessageLog.begin(), entry); + if ((int)m_MessageLog.size() > MAX_MESSAGE_COUNT) + m_MessageLog.pop_back(); + LeaveCriticalSection(&m_cs); + } if(title)delete title; if(text)delete text; @@ -9656,6 +9681,13 @@ void CMy2015RemoteDlg::OnMsglogDelete() for (auto it = selected.rbegin(); it != selected.rend(); ++it) { m_CList_Message.DeleteItem(*it); } + // 同步线程安全镜像(新在前,索引与 CListCtrl 一一对应;同样倒序避免索引变化) + EnterCriticalSection(&m_cs); + for (auto it = selected.rbegin(); it != selected.rend(); ++it) { + if (*it >= 0 && (size_t)*it < m_MessageLog.size()) + m_MessageLog.erase(m_MessageLog.begin() + *it); + } + LeaveCriticalSection(&m_cs); } void CMy2015RemoteDlg::OnMsglogCopy() { @@ -9693,6 +9725,10 @@ void CMy2015RemoteDlg::OnMsglogCopy() { void CMy2015RemoteDlg::OnMsglogClear() { m_CList_Message.DeleteAllItems(); + // 同步线程安全镜像,供 MCP get_audit_log 读取。 + EnterCriticalSection(&m_cs); + m_MessageLog.clear(); + LeaveCriticalSection(&m_cs); } void CMy2015RemoteDlg::OnMsglogSearch() diff --git a/server/2015Remote/2015RemoteDlg.h b/server/2015Remote/2015RemoteDlg.h index dbfe94a..89edb16 100644 --- a/server/2015Remote/2015RemoteDlg.h +++ b/server/2015Remote/2015RemoteDlg.h @@ -340,6 +340,15 @@ public: std::vector m_PendingOffline; // 存储端口号 CListCtrlEx m_CList_Online; CListCtrl m_CList_Message; + // 消息/审计日志的线程安全镜像(供 MCP get_audit_log 工具读取)。m_CList_Message 是 + // CListCtrl、只能在 UI 线程访问,MCP 线程据此镜像读取。受 m_cs 保护,新在前、旧在后, + // 与 CListCtrl 显示顺序一致(索引一一对应,便于删除同步)。 + struct LogMessage { + std::string type; // 类型(操作成功/主机上线/主机下线…) + std::string time; // 时间戳 "YYYY-MM-DD HH:MM:SS" + std::string msg; // 内容 + }; + std::vector m_MessageLog; CSplitterBar m_SplitterBar; int m_nSplitPos = 160; // 消息区高度(像素),可拖动调整 std::vector m_HostList; // 虚拟列表数据源(全部客户端) diff --git a/server/2015Remote/McpServer.cpp b/server/2015Remote/McpServer.cpp index 71c195c..0144d5a 100644 --- a/server/2015Remote/McpServer.cpp +++ b/server/2015Remote/McpServer.cpp @@ -639,6 +639,34 @@ Json::Value BuildClientLogOutputSchema() { return schema; } +// get_audit_log 的 outputSchema(服务端消息/审计日志条目数组) +Json::Value BuildAuditLogOutputSchema() { + Json::Value props(Json::objectValue); + + Json::Value entriesProp(Json::objectValue); + entriesProp["type"] = "array"; + Json::Value items(Json::objectValue); + items["type"] = "object"; + Json::Value itemProps(Json::objectValue); + const char* strFields[] = { "type", "time", "msg" }; + for (const char* f : strFields) { + Json::Value s(Json::objectValue); + s["type"] = "string"; + itemProps[f] = s; + } + items["properties"] = itemProps; + entriesProp["items"] = items; + props["entries"] = entriesProp; + + Json::Value schema(Json::objectValue); + schema["type"] = "object"; + schema["properties"] = props; + Json::Value required(Json::arrayValue); + required.append("entries"); + schema["required"] = required; + return schema; +} + // get_screenshot 的 outputSchema(image 元数据;base64 数据在 content 的 image 块中) Json::Value BuildScreenshotOutputSchema() { Json::Value props(Json::objectValue); @@ -897,6 +925,23 @@ std::string BuildToolsListResult(const Json::Value& id) { tools.append(tool); } + // 11) get_audit_log(服务端本地:主界面消息/审计日志,纯内存,无子链接) + { + Json::Value tool(Json::objectValue); + tool["name"] = "get_audit_log"; + tool["description"] = u8"获取 YAMA 服务端主界面的消息/审计日志(主机上线/下线、操作结果、告警等)。返回列表内当前全部条目(最多 1000 条,新在前)。只读、纯内存。"; + + Json::Value inputSchema(Json::objectValue); + inputSchema["type"] = "object"; + inputSchema["properties"] = Json::Value(Json::objectValue); + inputSchema["required"] = Json::Value(Json::arrayValue); + tool["inputSchema"] = inputSchema; + + tool["outputSchema"] = BuildAuditLogOutputSchema(); + + tools.append(tool); + } + result["tools"] = tools; return BuildResult(id, result); } @@ -1418,6 +1463,41 @@ std::string BuildGetClientLog(const Json::Value& id, const Json::Value& args, CM return BuildResult(id, result); } +// tools/call:get_audit_log(服务端本地消息日志,读取 UI 日志列表的线程安全镜像) +std::string BuildGetAuditLog(const Json::Value& id, CMy2015RemoteDlg* parent) { + Json::Value entries(Json::arrayValue); + if (parent) { + EnterCriticalSection(&parent->m_cs); + // m_MessageLog 新在前、旧在后(与界面一致),直接顺序输出。 + for (const auto& e : parent->m_MessageLog) { + Json::Value item(Json::objectValue); + item["type"] = ToUtf8(e.type.c_str(), 936); + item["time"] = ToUtf8(e.time.c_str(), 936); + item["msg"] = ToUtf8(e.msg.c_str(), 936); + entries.append(item); + } + LeaveCriticalSection(&parent->m_cs); + } + + int count = (int)entries.size(); + + Json::Value result(Json::objectValue); + Json::Value structuredContent(Json::objectValue); + structuredContent["entries"] = entries; + result["structuredContent"] = structuredContent; + + Json::Value content(Json::arrayValue); + Json::Value item(Json::objectValue); + item["type"] = "text"; + item["text"] = count > 0 ? std::string(u8"共 ") + std::to_string(count) + std::string(u8" 条日志。") + : std::string(u8"当前无日志。"); + content.append(item); + result["content"] = content; + result["isError"] = false; + + return BuildResult(id, result); +} + // tools/call 分派 std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) { const Json::Value& id = root["id"]; @@ -1440,6 +1520,7 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) { if (toolName == "list_files") return BuildListFiles(id, args, parent); if (toolName == "list_services") return BuildListServices(id, args, parent); if (toolName == "get_client_log") return BuildGetClientLog(id, args, parent); + if (toolName == "get_audit_log") return BuildGetAuditLog(id, parent); return BuildError(id, -32602, "Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));