Feature: Add search_hosts and get_host_detail MCP tools

Add two pure in-memory MCP tools over the online host list. search_hosts filters by name/remark, IP, group and OS (all optional, AND-combined, ASCII case-insensitive substring match); get_host_detail returns one online host by id and answers -32602 for a missing/non-numeric id and -32002 for an unknown or offline id. Sync Mcp_Phase2_Design.md with the second-review corrections and the P2a implementation.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-18 10:44:10 +02:00
parent 13052b7cae
commit 86c4f14cf4
2 changed files with 627 additions and 46 deletions

View File

@@ -70,14 +70,62 @@ std::string BuildPingResult(const Json::Value& id) {
return BuildResult(id, Json::Value(Json::objectValue));
}
// list_online_hosts 的 outputSchema见 docs/Mcp_Design.md §3.3.2
Json::Value BuildHostOutputSchema() {
Json::Value props(Json::objectValue);
// ========== P2a 通用辅助 ==========
Json::Value hostsProp(Json::objectValue);
hostsProp["type"] = "array";
Json::Value items(Json::objectValue);
items["type"] = "object";
// 小写化(仅 ASCIIUTF-8 多字节原样保留):用于不区分大小写的子串匹配
std::string ToLowerAscii(const std::string& s) {
std::string r = s;
for (char& c : r) if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
return r;
}
// 不区分大小写的子串匹配
bool ContainsCI(const std::string& haystack, const std::string& needle) {
if (needle.empty()) return true;
return ToLowerAscii(haystack).find(ToLowerAscii(needle)) != std::string::npos;
}
// 取对象的字符串字段,缺失/非字符串返回 ""
std::string JsonStrField(const Json::Value& v, const char* key) {
if (v.isObject() && v.isMember(key) && v[key].isString())
return v[key].asString();
return "";
}
// 是否纯数字host id 为 uint64 十进制字符串)
bool IsDigits(const std::string& s) {
if (s.empty()) return false;
for (char c : s) if (c < '0' || c > '9') return false;
return true;
}
// 读取 tools/call 的入参MCP 规范params.arguments 为工具入参对象)
Json::Value GetCallArguments(const Json::Value& params) {
if (params.isObject() && params.isMember("arguments") && params["arguments"].isObject())
return params["arguments"];
return Json::Value(Json::objectValue);
}
// 读取可选字符串入参,缺失返回 ""
std::string GetStringArg(const Json::Value& args, const char* key) {
return JsonStrField(args, key);
}
// 收集所有在线主机 JSON 数组m_cs 锁内遍历,复用 BuildHostJson 序列化,方案 C
void CollectOnlineHosts(CMy2015RemoteDlg* parent, Json::Value& hosts) {
if (!parent) return;
EnterCriticalSection(&parent->m_cs);
for (context* ctx : parent->m_HostList) {
if (!ctx || !ctx->IsLogin()) continue;
hosts.append(BuildHostJson(ctx, parent->m_ClientMap));
}
LeaveCriticalSection(&parent->m_cs);
}
// ========== 工具 schema ==========
// 单台主机字段 schemahosts 数组元素 / 单机详情共用的形状)
Json::Value BuildHostItemSchema() {
Json::Value itemProps(Json::objectValue);
const char* strFields[] = {
"id", "name", "remark", "ip", "os", "location", "rtt",
@@ -91,7 +139,18 @@ Json::Value BuildHostOutputSchema() {
Json::Value onlineProp(Json::objectValue);
onlineProp["type"] = "boolean";
itemProps["online"] = onlineProp;
items["properties"] = itemProps;
return itemProps;
}
// list_online_hosts / search_hosts 的 outputSchemahosts 数组)
Json::Value BuildHostOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value hostsProp(Json::objectValue);
hostsProp["type"] = "array";
Json::Value items(Json::objectValue);
items["type"] = "object";
items["properties"] = BuildHostItemSchema();
hostsProp["items"] = items;
props["hosts"] = hostsProp;
@@ -104,57 +163,118 @@ Json::Value BuildHostOutputSchema() {
return schema;
}
// get_host_detail 的 outputSchema单台主机
Json::Value BuildHostDetailOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value hostProp(Json::objectValue);
hostProp["type"] = "object";
hostProp["properties"] = BuildHostItemSchema();
props["host"] = hostProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
Json::Value required(Json::arrayValue);
required.append("host");
schema["required"] = required;
return schema;
}
// search_hosts 的 inputSchema全部可选
Json::Value BuildSearchHostsInputSchema() {
Json::Value props(Json::objectValue);
const char* strParams[] = { "name", "ip", "group", "os" };
for (const char* p : strParams) {
Json::Value s(Json::objectValue);
s["type"] = "string";
props[p] = s;
}
Json::Value onlineProp(Json::objectValue);
onlineProp["type"] = "boolean";
props["online"] = onlineProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
return schema;
}
// get_host_detail 的 inputSchemaid 必填)
Json::Value BuildGetHostDetailInputSchema() {
Json::Value props(Json::objectValue);
Json::Value idProp(Json::objectValue);
idProp["type"] = "string";
idProp["description"] = u8"主机 id取 list_online_hosts / search_hosts 返回的 id 字段";
props["id"] = idProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
Json::Value required(Json::arrayValue);
required.append("id");
schema["required"] = required;
return schema;
}
// tools/list
std::string BuildToolsListResult(const Json::Value& id) {
Json::Value result(Json::objectValue);
Json::Value tools(Json::arrayValue);
Json::Value tool(Json::objectValue);
tool["name"] = "list_online_hosts";
// 说明文字为 UTF-8项目 /execution-charset:.936 会把普通窄字面量编译成 GBK
// 故用 u8 前缀确保输出到 JSON 的字节是 UTF-8。
tool["description"] = u8"获取当前所有在线主机的列表包含计算机名、IP、操作系统、版本、备注、分组、活动窗口、延迟等实时信息。";
// 1) list_online_hosts
{
Json::Value tool(Json::objectValue);
tool["name"] = "list_online_hosts";
// 说明文字为 UTF-8项目 /execution-charset:.936 会把普通窄字面量编译成 GBK
// 故用 u8 前缀确保输出到 JSON 的字节是 UTF-8。
tool["description"] = u8"获取当前所有在线主机的列表包含计算机名、IP、操作系统、版本、备注、分组、活动窗口、延迟等实时信息。";
Json::Value inputSchema(Json::objectValue);
inputSchema["type"] = "object";
inputSchema["properties"] = Json::Value(Json::objectValue);
inputSchema["required"] = Json::Value(Json::arrayValue);
tool["inputSchema"] = inputSchema;
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"] = BuildHostOutputSchema();
tool["outputSchema"] = BuildHostOutputSchema();
tools.append(tool);
}
// 2) search_hostsP2a纯内存过滤无子链接
{
Json::Value tool(Json::objectValue);
tool["name"] = "search_hosts";
tool["description"] = u8"按计算机名/备注、IP、分组、操作系统过滤在线主机。所有条件均可选、按 AND 组合子串匹配ASCII 不区分大小写)。只返回在线主机。";
tool["inputSchema"] = BuildSearchHostsInputSchema();
tool["outputSchema"] = BuildHostOutputSchema();
tools.append(tool);
}
// 3) get_host_detailP2a单机详情纯内存
{
Json::Value tool(Json::objectValue);
tool["name"] = "get_host_detail";
tool["description"] = u8"获取单台在线主机的详细信息id、计算机名、IP、操作系统、备注、分组、活动窗口、屏幕分辨率、客户端类型等";
tool["inputSchema"] = BuildGetHostDetailInputSchema();
tool["outputSchema"] = BuildHostDetailOutputSchema();
tools.append(tool);
}
tools.append(tool);
result["tools"] = tools;
return BuildResult(id, result);
}
// tools/calllist_online_hosts
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"];
const Json::Value& params = root.isMember("params") ? root["params"]
: Json::Value(Json::objectValue);
std::string toolName;
if (params.isObject() && params.isMember("name") && params["name"].isString()) {
toolName = params["name"].asString();
}
if (toolName != "list_online_hosts") {
return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
}
// tools/calllist_online_hosts
std::string BuildListOnlineHosts(const Json::Value& id, CMy2015RemoteDlg* parent) {
Json::Value hosts(Json::arrayValue);
int count = 0;
if (parent) {
// 与 WebService 侧一致的锁内遍历,复用 BuildHostJson 序列化(方案 C
EnterCriticalSection(&parent->m_cs);
for (context* ctx : parent->m_HostList) {
if (!ctx || !ctx->IsLogin()) continue;
hosts.append(BuildHostJson(ctx, parent->m_ClientMap));
++count;
}
LeaveCriticalSection(&parent->m_cs);
}
CollectOnlineHosts(parent, hosts);
int count = (int)hosts.size();
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
@@ -172,6 +292,109 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
return BuildResult(id, result);
}
// tools/callsearch_hosts
std::string BuildSearchHosts(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
Json::Value all(Json::arrayValue);
CollectOnlineHosts(parent, all);
std::string fName = GetStringArg(args, "name");
std::string fIp = GetStringArg(args, "ip");
std::string fGroup = GetStringArg(args, "group");
std::string fOs = GetStringArg(args, "os");
bool hasOnline = args.isObject() && args.isMember("online") && args["online"].isBool();
bool wantOnline = hasOnline ? args["online"].asBool() : true;
Json::Value hosts(Json::arrayValue);
// 列表只含在线主机:显式 online=false 时直接空结果
if (!hasOnline || wantOnline) {
for (unsigned int i = 0; i < all.size(); ++i) {
const Json::Value& h = all[i];
if (!fName.empty()) {
std::string name = JsonStrField(h, "name");
std::string remark = JsonStrField(h, "remark");
if (!ContainsCI(name, fName) && !ContainsCI(remark, fName)) continue;
}
if (!fIp.empty() && !ContainsCI(JsonStrField(h, "ip"), fIp)) continue;
if (!fGroup.empty() && !ContainsCI(JsonStrField(h, "group"), fGroup)) continue;
if (!fOs.empty() && !ContainsCI(JsonStrField(h, "os"), fOs)) continue;
hosts.append(h);
}
}
int count = (int)hosts.size();
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["hosts"] = hosts;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = std::string(u8"") + std::to_string(count) + std::string(u8" 台主机匹配。");
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// tools/callget_host_detail
std::string BuildGetHostDetail(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
std::string sid = GetStringArg(args, "id");
if (sid.empty()) {
return BuildError(id, -32602, "Missing required parameter: id");
}
if (!IsDigits(sid)) {
return BuildError(id, -32602, "Invalid id: expected a decimal host id string");
}
Json::Value all(Json::arrayValue);
CollectOnlineHosts(parent, all);
for (unsigned int i = 0; i < all.size(); ++i) {
const Json::Value& h = all[i];
if (JsonStrField(h, "id") == sid) {
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["host"] = h;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
std::string name = JsonStrField(h, "name");
std::string ip = JsonStrField(h, "ip");
item["text"] = std::string(u8"主机 ") + name + " (" + ip + ")" + u8" 的详情。";
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
}
return BuildError(id, -32002, "Host not found or offline: " + sid);
}
// tools/call 分派
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"];
Json::Value params = root.isMember("params") ? root["params"] : Json::Value(Json::objectValue);
std::string toolName;
if (params.isObject() && params.isMember("name") && params["name"].isString()) {
toolName = params["name"].asString();
}
const Json::Value args = GetCallArguments(params);
if (toolName == "list_online_hosts") return BuildListOnlineHosts(id, parent);
if (toolName == "search_hosts") return BuildSearchHosts(id, args, parent);
if (toolName == "get_host_detail") return BuildGetHostDetail(id, args, parent);
return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
}
} // namespace
//////////////////////////////////////////////////////////////////////////