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:
@@ -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()
|
||||||
|
|||||||
@@ -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; // 虚拟列表数据源(全部客户端)
|
||||||
|
|||||||
@@ -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 的 outputSchema(image 元数据;base64 数据在 content 的 image 块中)
|
// get_screenshot 的 outputSchema(image 元数据;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/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 分派
|
// 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));
|
||||||
|
|||||||
Reference in New Issue
Block a user