onmcp/cpp/mock_mcp_server/main.cpp

264 lines
11 KiB
C++

#include "common/http.h"
#include "common/json.h"
#include "common/mcp.h"
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
using namespace ohmcp;
namespace {
Json StringProp(const std::string &description)
{
return Json::Object({{"type", "string"}, {"description", description}});
}
Json IntegerProp(const std::string &description)
{
return Json::Object({{"type", "integer"}, {"description", description}});
}
Json NumberProp(const std::string &description)
{
return Json::Object({{"type", "number"}, {"description", description}});
}
Json ObjectSchema(std::map<std::string, Json> properties, std::vector<Json> required = {})
{
return Json::Object({
{"type", "object"},
{"properties", Json::Object(std::move(properties))},
{"required", Json::Array(std::move(required))},
});
}
Json ReadOnlyAnnotations(bool openWorld = true)
{
return Json::Object({
{"readOnlyHint", true},
{"destructiveHint", false},
{"idempotentHint", true},
{"openWorldHint", openWorld},
});
}
Json OhMcpMeta(bool cacheable, int ttlMs, const std::string &permission, const std::string &securityLevel = "normal")
{
return Json::Object({
{"cacheable", cacheable},
{"cacheTtlMs", ttlMs},
{"requiredPermission", permission},
{"securityLevel", securityLevel},
});
}
Json Tool(const std::string &name, const std::string &title, const std::string &description,
const std::string &category, const Json &inputSchema, const Json &outputSchema,
const Json &xOhmcp)
{
return Json::Object({
{"name", name},
{"title", title},
{"description", description},
{"version", "1.0.0"},
{"category", category},
{"inputSchema", inputSchema},
{"outputSchema", outputSchema},
{"annotations", ReadOnlyAnnotations(true)},
{"x-ohmcp", xOhmcp},
});
}
std::vector<Json> NamedItems(const std::string &city, const std::string &keyword, int limit, const std::string &type)
{
std::vector<Json> items;
for (int i = 1; i <= limit; ++i) {
items.push_back(Json::Object({
{"name", city + keyword + "推荐" + std::to_string(i)},
{"type", type},
{"address", city + "市中心示例路" + std::to_string(100 + i) + ""},
{"score", 4.5 + (i % 3) * 0.1},
}));
}
return items;
}
} // namespace
Json BuildTools()
{
Json weatherInput = ObjectSchema({{"city", StringProp("城市名称,例如:苏州")}}, {"city"});
Json weatherOutput = ObjectSchema({
{"city", StringProp("城市名称")},
{"weather", StringProp("天气现象")},
{"temperature_c", NumberProp("摄氏温度")},
{"wind", StringProp("风向风力")},
{"source", StringProp("数据来源")},
}, {"city", "weather"});
Json textSearchInput = ObjectSchema({
{"city", StringProp("城市名称,例如:苏州")},
{"keywords", StringProp("搜索关键词,例如:园林、博物馆")},
{"limit", IntegerProp("返回数量,默认 3")},
}, {"city", "keywords"});
Json textSearchOutput = ObjectSchema({
{"city", StringProp("城市名称")},
{"keywords", StringProp("搜索关键词")},
{"pois", Json::Object({{"type", "array"}, {"description", "POI 搜索结果"}})},
{"source", StringProp("数据来源")},
}, {"city", "keywords", "pois"});
Json routeInput = ObjectSchema({
{"origin", StringProp("出发地,例如:上海")},
{"destination", StringProp("目的地,例如:苏州")},
{"city", StringProp("目的地城市,可选")},
}, {"origin", "destination"});
Json routeOutput = ObjectSchema({
{"origin", StringProp("出发地")},
{"destination", StringProp("目的地")},
{"strategy", StringProp("路线策略")},
{"duration_min", NumberProp("预计耗时,分钟")},
{"cost_yuan", NumberProp("预计费用,元")},
{"steps", Json::Object({{"type", "array"}, {"description", "路线步骤"}})},
{"source", StringProp("数据来源")},
}, {"origin", "destination", "duration_min"});
Json hotelInput = ObjectSchema({
{"city", StringProp("城市名称,例如:苏州")},
{"keyword", StringProp("搜索关键词,例如:酒店、民宿")},
{"limit", IntegerProp("返回数量,默认 2")},
}, {"city"});
Json hotelOutput = ObjectSchema({
{"city", StringProp("城市名称")},
{"hotels", Json::Object({{"type", "array"}, {"description", "酒店列表"}})},
{"source", StringProp("数据来源")},
}, {"city", "hotels"});
Json restaurantInput = ObjectSchema({
{"city", StringProp("城市名称,例如:苏州")},
{"keyword", StringProp("搜索关键词,例如:苏帮菜")},
{"limit", IntegerProp("返回数量,默认 2")},
}, {"city"});
Json restaurantOutput = ObjectSchema({
{"city", StringProp("城市名称")},
{"restaurants", Json::Object({{"type", "array"}, {"description", "餐厅列表"}})},
{"source", StringProp("数据来源")},
}, {"city", "restaurants"});
return Json::Array({
Tool("amap.maps_weather", "高德天气查询", "查询指定城市天气信息。", "amap.weather",
weatherInput, weatherOutput,
OhMcpMeta(true, 60000, "ohos.permission.MCP_AMAP_WEATHER")),
Tool("amap.maps_text_search", "高德文本搜索", "按城市和关键词搜索景点、商圈、博物馆等 POI。", "amap.search",
textSearchInput, textSearchOutput,
OhMcpMeta(true, 300000, "ohos.permission.MCP_AMAP_SEARCH")),
Tool("amap.maps_direction_transit_integrated_by_address", "高德公交路线规划", "根据出发地和目的地规划公共交通路线。", "amap.route",
routeInput, routeOutput,
OhMcpMeta(true, 60000, "ohos.permission.MCP_AMAP_ROUTE")),
Tool("amap.hotel_search", "酒店搜索", "搜索目的地城市的酒店候选。", "amap.hotel",
hotelInput, hotelOutput,
OhMcpMeta(true, 300000, "ohos.permission.MCP_AMAP_HOTEL")),
Tool("amap.restaurant_search", "餐厅搜索", "搜索目的地城市的餐厅候选。", "amap.restaurant",
restaurantInput, restaurantOutput,
OhMcpMeta(true, 300000, "ohos.permission.MCP_AMAP_RESTAURANT")),
});
}
Json CallTool(const std::string &name, const Json &args)
{
// Simulate external API/tool latency; result cache in mcpd bypasses this cost.
std::this_thread::sleep_for(std::chrono::milliseconds(2));
if (name == "amap.maps_weather") {
std::string city = args["city"].StringOr();
if (city.empty()) throw std::runtime_error("missing arg: city");
return Json::Object({
{"city", city}, {"weather", ""}, {"temperature_c", 25}, {"wind", "东南风 2 级"}, {"source", "cpp-mock-amap"}
});
}
if (name == "amap.maps_text_search") {
std::string city = args["city"].StringOr();
std::string keywords = args["keywords"].StringOr();
int limit = args["limit"].IntOr(3);
if (city.empty() || keywords.empty()) throw std::runtime_error("missing args: city, keywords");
return Json::Object({
{"city", city}, {"keywords", keywords}, {"pois", Json::Array(NamedItems(city, keywords, limit, "poi"))}, {"source", "cpp-mock-amap"}
});
}
if (name == "amap.maps_direction_transit_integrated_by_address") {
std::string origin = args["origin"].StringOr();
std::string destination = args["destination"].StringOr();
if (origin.empty() || destination.empty()) throw std::runtime_error("missing args: origin, destination");
return Json::Object({
{"origin", origin}, {"destination", destination}, {"strategy", "train+metro"},
{"duration_min", 68}, {"cost_yuan", 39.5},
{"steps", Json::Array({"高铁到达苏州站", "地铁前往酒店", "步行到达目的地"})},
{"source", "cpp-mock-amap"}
});
}
if (name == "amap.hotel_search") {
std::string city = args["city"].StringOr();
int limit = args["limit"].IntOr(2);
if (city.empty()) throw std::runtime_error("missing arg: city");
std::vector<Json> hotels;
for (int i = 1; i <= limit; ++i) {
hotels.push_back(Json::Object({
{"name", city + "园林酒店" + std::to_string(i)}, {"price_yuan", 320 + i * 80},
{"score", 4.6 + (i % 2) * 0.1}, {"area", "古城核心区"}
}));
}
return Json::Object({{"city", city}, {"hotels", Json::Array(hotels)}, {"source", "cpp-mock-amap"}});
}
if (name == "amap.restaurant_search") {
std::string city = args["city"].StringOr();
std::string keyword = args["keyword"].StringOr("苏帮菜");
int limit = args["limit"].IntOr(2);
if (city.empty()) throw std::runtime_error("missing arg: city");
std::vector<Json> restaurants;
for (int i = 1; i <= limit; ++i) {
restaurants.push_back(Json::Object({
{"name", city + keyword + "餐厅" + std::to_string(i)}, {"avg_price_yuan", 90 + i * 30},
{"score", 4.5 + (i % 2) * 0.1}, {"specialty", keyword}
}));
}
return Json::Object({{"city", city}, {"restaurants", Json::Array(restaurants)}, {"source", "cpp-mock-amap"}});
}
throw std::runtime_error("unknown tool: " + name);
}
int main(int argc, char **argv)
{
int port = 18081;
for (int i = 1; i + 1 < argc; ++i) {
if (std::string(argv[i]) == "--port") port = std::stoi(argv[++i]);
}
HttpServer server("127.0.0.1", port, [](const HttpRequest &req) -> HttpResponse {
if (req.path != "/rpc" || req.method != "POST") {
return {404, "application/json", "{\"error\":\"not found\"}"};
}
try {
Json rpc = ParseJson(req.body);
Json id = rpc["id"];
std::string method = rpc["method"].StringOr();
if (method == "tools/list") {
return {200, "application/json", DumpJson(RpcResult(id, Json::Object({{"tools", BuildTools()}})))};
}
if (method == "tools/call") {
Json params = rpc["params"];
Json result = CallTool(params["name"].StringOr(), params["arguments"]);
return {200, "application/json", DumpJson(RpcResult(id, Json::Object({{"content", Json::Array({Json::Object({{"type", "json"}, {"json", result}})})}})))};
}
return {200, "application/json", DumpJson(RpcError(id, -32601, "method not found"))};
} catch (const std::exception &e) {
return {200, "application/json", DumpJson(RpcError(Json(nullptr), -32000, e.what()))};
}
});
std::cout << "mock_mcp_server ";
server.ServeForever();
return 0;
}