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
This commit is contained in:
yuanyuanxiang
2026-08-21 20:33:59 +02:00
parent 8de5c00a39
commit 8815c4f896
3 changed files with 126 additions and 0 deletions

View File

@@ -1796,6 +1796,19 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_CList_Message.SetItemText(0, 2, strMsg); m_CList_Message.SetItemText(0, 2, strMsg);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, 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; CString strStatusMsg;
EnterCriticalSection(&m_cs); 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, 1, strTime);
m_CList_Message.SetItemText(0, 2, strText); m_CList_Message.SetItemText(0, 2, strText);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, 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(title)delete title;
if(text)delete text; if(text)delete text;
@@ -9656,6 +9681,13 @@ void CMy2015RemoteDlg::OnMsglogDelete()
for (auto it = selected.rbegin(); it != selected.rend(); ++it) { for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
m_CList_Message.DeleteItem(*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() { void CMy2015RemoteDlg::OnMsglogCopy() {
@@ -9693,6 +9725,10 @@ void CMy2015RemoteDlg::OnMsglogCopy() {
void CMy2015RemoteDlg::OnMsglogClear() void CMy2015RemoteDlg::OnMsglogClear()
{ {
m_CList_Message.DeleteAllItems(); m_CList_Message.DeleteAllItems();
// 同步线程安全镜像,供 MCP get_audit_log 读取。
EnterCriticalSection(&m_cs);
m_MessageLog.clear();
LeaveCriticalSection(&m_cs);
} }
void CMy2015RemoteDlg::OnMsglogSearch() void CMy2015RemoteDlg::OnMsglogSearch()

View File

@@ -340,6 +340,15 @@ public:
std::vector<int> m_PendingOffline; // 存储端口号 std::vector<int> m_PendingOffline; // 存储端口号
CListCtrlEx m_CList_Online; CListCtrlEx m_CList_Online;
CListCtrl m_CList_Message; 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<LogMessage> m_MessageLog;
CSplitterBar m_SplitterBar; CSplitterBar m_SplitterBar;
int m_nSplitPos = 160; // 消息区高度(像素),可拖动调整 int m_nSplitPos = 160; // 消息区高度(像素),可拖动调整
std::vector<context*> m_HostList; // 虚拟列表数据源(全部客户端) std::vector<context*> m_HostList; // 虚拟列表数据源(全部客户端)

View File

@@ -639,6 +639,34 @@ Json::Value BuildClientLogOutputSchema() {
return schema; 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 的 outputSchemaimage 元数据base64 数据在 content 的 image 块中) // get_screenshot 的 outputSchemaimage 元数据base64 数据在 content 的 image 块中)
Json::Value BuildScreenshotOutputSchema() { Json::Value BuildScreenshotOutputSchema() {
Json::Value props(Json::objectValue); Json::Value props(Json::objectValue);
@@ -897,6 +925,23 @@ std::string BuildToolsListResult(const Json::Value& id) {
tools.append(tool); 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; result["tools"] = tools;
return BuildResult(id, result); return BuildResult(id, result);
} }
@@ -1418,6 +1463,41 @@ std::string BuildGetClientLog(const Json::Value& id, const Json::Value& args, CM
return BuildResult(id, result); return BuildResult(id, result);
} }
// tools/callget_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 分派 // tools/call 分派
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) { std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"]; 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_files") return BuildListFiles(id, args, parent);
if (toolName == "list_services") return BuildListServices(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_client_log") return BuildGetClientLog(id, args, parent);
if (toolName == "get_audit_log") return BuildGetAuditLog(id, parent);
return BuildError(id, -32602, return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName)); "Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));