264 lines
11 KiB
C++
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;
|
|
}
|