2025-03-16 17:17:01 +08:00
|
|
|
#ifndef HUMANUS_TOOL_BASE_H
|
|
|
|
#define HUMANUS_TOOL_BASE_H
|
|
|
|
|
2025-03-16 22:56:03 +08:00
|
|
|
#include "toml.hpp"
|
2025-03-19 18:44:54 +08:00
|
|
|
#include "schema.h"
|
|
|
|
#include "agent/base.h"
|
|
|
|
#include "mcp/include/mcp_client.h"
|
|
|
|
#include "mcp/include/mcp_stdio_client.h"
|
|
|
|
#include "mcp/include/mcp_sse_client.h"
|
2025-03-16 17:17:01 +08:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
namespace humanus {
|
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read tool configuration from config_mcp.toml
|
2025-03-16 17:17:01 +08:00
|
|
|
struct MCPToolConfig {
|
2025-03-16 22:56:03 +08:00
|
|
|
std::string type;
|
|
|
|
std::string host;
|
2025-03-17 16:35:11 +08:00
|
|
|
int port;
|
2025-03-16 22:56:03 +08:00
|
|
|
std::string url;
|
2025-03-16 17:17:01 +08:00
|
|
|
std::string command;
|
|
|
|
std::vector<std::string> args;
|
|
|
|
json env_vars = json::object();
|
|
|
|
|
|
|
|
static MCPToolConfig load_from_toml(const std::string& tool_name) {
|
|
|
|
MCPToolConfig config;
|
|
|
|
|
|
|
|
try {
|
2025-03-17 16:35:11 +08:00
|
|
|
// Get config file path
|
2025-03-16 17:17:01 +08:00
|
|
|
auto config_path = PROJECT_ROOT / "config" / "config_mcp.toml";
|
|
|
|
if (!std::filesystem::exists(config_path)) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("MCP config file not found: " + config_path.string());
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Parse TOML file
|
2025-03-16 22:56:03 +08:00
|
|
|
const auto& data = toml::parse_file(config_path.string());
|
2025-03-16 17:17:01 +08:00
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Check if tool config exists
|
2025-03-16 17:17:01 +08:00
|
|
|
if (!data.contains(tool_name) || !data[tool_name].is_table()) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("Tool configuration not found in MCP config file: " + tool_name);
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
|
2025-03-16 22:56:03 +08:00
|
|
|
const auto& tool_table = *data[tool_name].as_table();
|
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read type
|
2025-03-16 22:56:03 +08:00
|
|
|
if (!tool_table.contains("type") || !tool_table["type"].is_string()) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("Tool configuration missing type field: " + tool_name);
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
2025-03-16 22:56:03 +08:00
|
|
|
config.type = tool_table["type"].as_string()->get();
|
|
|
|
|
|
|
|
if (config.type == "stdio") {
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read command
|
2025-03-16 22:56:03 +08:00
|
|
|
if (!tool_table.contains("command") || !tool_table["command"].is_string()) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("stdio type tool configuration missing command field: " + tool_name);
|
2025-03-16 22:56:03 +08:00
|
|
|
}
|
|
|
|
config.command = tool_table["command"].as_string()->get();
|
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read arguments (if any)
|
2025-03-16 22:56:03 +08:00
|
|
|
if (tool_table.contains("args") && tool_table["args"].is_array()) {
|
|
|
|
const auto& args_array = *tool_table["args"].as_array();
|
|
|
|
for (const auto& arg : args_array) {
|
|
|
|
if (arg.is_string()) {
|
|
|
|
config.args.push_back(arg.as_string()->get());
|
|
|
|
}
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
}
|
2025-03-16 22:56:03 +08:00
|
|
|
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read environment variables
|
2025-03-24 04:25:22 +08:00
|
|
|
if (tool_table.contains("env") && tool_table["env"].is_table()) {
|
|
|
|
const auto& env_table = *tool_table["env"].as_table();
|
2025-03-16 22:56:03 +08:00
|
|
|
for (const auto& [key, value] : env_table) {
|
|
|
|
if (value.is_string()) {
|
|
|
|
config.env_vars[key] = value.as_string()->get();
|
|
|
|
} else if (value.is_integer()) {
|
|
|
|
config.env_vars[key] = value.as_integer()->get();
|
|
|
|
} else if (value.is_floating_point()) {
|
|
|
|
config.env_vars[key] = value.as_floating_point()->get();
|
|
|
|
} else if (value.is_boolean()) {
|
|
|
|
config.env_vars[key] = value.as_boolean()->get();
|
|
|
|
}
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
}
|
2025-03-16 22:56:03 +08:00
|
|
|
} else if (config.type == "sse") {
|
2025-03-17 16:35:11 +08:00
|
|
|
// Read host and port or url
|
2025-03-16 22:56:03 +08:00
|
|
|
if (tool_table.contains("url") && tool_table["url"].is_string()) {
|
|
|
|
config.url = tool_table["url"].as_string()->get();
|
|
|
|
} else {
|
|
|
|
if (!tool_table.contains("host") || !tool_table["host"].is_string()) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("sse type tool configuration missing host field: " + tool_name);
|
2025-03-16 22:56:03 +08:00
|
|
|
}
|
|
|
|
config.host = tool_table["host"].as_string()->get();
|
|
|
|
|
2025-03-17 01:58:37 +08:00
|
|
|
if (!tool_table.contains("port") || !tool_table["port"].is_integer()) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("sse type tool configuration missing port field: " + tool_name);
|
2025-03-16 22:56:03 +08:00
|
|
|
}
|
2025-03-17 01:58:37 +08:00
|
|
|
config.port = tool_table["port"].as_integer()->get();
|
2025-03-16 22:56:03 +08:00
|
|
|
}
|
|
|
|
} else {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("Unsupported tool type: " + config.type);
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
2025-03-17 16:35:11 +08:00
|
|
|
std::cerr << "Failed to load MCP tool configuration: " << e.what() << std::endl;
|
2025-03-16 22:56:03 +08:00
|
|
|
throw;
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-17 01:58:37 +08:00
|
|
|
// Represents the result of a tool execution.
|
|
|
|
struct ToolResult {
|
|
|
|
json output;
|
|
|
|
json error;
|
|
|
|
json system;
|
|
|
|
|
|
|
|
ToolResult(const json& output, const json& error = {}, const json& system = {})
|
|
|
|
: output(output), error(error), system(system) {}
|
|
|
|
|
|
|
|
bool empty() const {
|
|
|
|
return output.empty() && error.empty() && system.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
ToolResult operator+(const ToolResult& other) const {
|
|
|
|
auto combined_field = [](const json& field, const json& other_field) {
|
|
|
|
if (field.empty()) {
|
|
|
|
return other_field;
|
|
|
|
}
|
|
|
|
if (other_field.empty()) {
|
|
|
|
return field;
|
|
|
|
}
|
|
|
|
json result = json::array();
|
|
|
|
if (field.is_array()) {
|
|
|
|
result.insert(result.end(), field.begin(), field.end());
|
|
|
|
} else {
|
|
|
|
result.push_back(field);
|
|
|
|
}
|
|
|
|
if (other_field.is_array()) {
|
|
|
|
result.insert(result.end(), other_field.begin(), other_field.end());
|
|
|
|
} else {
|
|
|
|
result.push_back(other_field);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
combined_field(output, other.output),
|
|
|
|
combined_field(error, other.error),
|
|
|
|
combined_field(system, other.system)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-03-17 14:07:41 +08:00
|
|
|
static std::string parse_json_content(const json& content) {
|
|
|
|
if (content.is_string()) {
|
|
|
|
return content.get<std::string>();
|
|
|
|
} else {
|
|
|
|
return content.dump(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-17 01:58:37 +08:00
|
|
|
std::string to_string() const {
|
2025-03-17 14:07:41 +08:00
|
|
|
return !error.empty() ? "Error: " + parse_json_content(error) : parse_json_content(output);
|
2025-03-17 01:58:37 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// A ToolResult that represents a failure.
|
|
|
|
struct ToolError : ToolResult {
|
2025-03-19 18:44:54 +08:00
|
|
|
ToolError(const json& error) : ToolResult({}, error) {}
|
2025-03-17 01:58:37 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Execute the tool with given parameters.
|
|
|
|
struct BaseTool {
|
2025-03-17 14:07:41 +08:00
|
|
|
inline static std::set<std::string> special_tool_name = {"terminate", "planning"};
|
2025-03-17 01:58:37 +08:00
|
|
|
|
|
|
|
std::string name;
|
|
|
|
std::string description;
|
|
|
|
json parameters;
|
|
|
|
|
|
|
|
std::unique_ptr<mcp::client> _client;
|
|
|
|
|
|
|
|
BaseTool(const std::string& name, const std::string& description, const json& parameters) :
|
|
|
|
name(name), description(description), parameters(parameters) {
|
|
|
|
if (special_tool_name.find(name) != special_tool_name.end()) {
|
|
|
|
return;
|
|
|
|
}
|
2025-03-17 16:35:11 +08:00
|
|
|
// Load tool configuration from config file
|
2025-03-17 01:58:37 +08:00
|
|
|
auto _config = MCPToolConfig::load_from_toml(name);
|
|
|
|
|
|
|
|
if (_config.type == "stdio") {
|
|
|
|
std::string command = _config.command;
|
|
|
|
if (!_config.args.empty()) {
|
|
|
|
for (const auto& arg : _config.args) {
|
|
|
|
command += " " + arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_client = std::make_unique<mcp::stdio_client>(command, _config.env_vars);
|
|
|
|
} else if (_config.type == "sse") {
|
|
|
|
if (!_config.host.empty() && _config.port > 0) {
|
|
|
|
_client = std::make_unique<mcp::sse_client>(_config.host, _config.port);
|
|
|
|
} else if (!_config.url.empty()) {
|
|
|
|
_client = std::make_unique<mcp::sse_client>(_config.url, "/sse");
|
|
|
|
} else {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("MCP SSE configuration missing host or port or url");
|
2025-03-17 01:58:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_client->initialize(name + "_client", "0.0.1");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the tool with given parameters.
|
|
|
|
ToolResult operator()(const json& arguments) {
|
|
|
|
return execute(arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the tool with given parameters.
|
|
|
|
virtual ToolResult execute(const json& arguments) {
|
|
|
|
try {
|
|
|
|
if (!_client) {
|
2025-03-17 16:35:11 +08:00
|
|
|
throw std::runtime_error("MCP client not initialized");
|
2025-03-17 01:58:37 +08:00
|
|
|
}
|
|
|
|
json result = _client->call_tool(name, arguments);
|
|
|
|
bool is_error = result.value("isError", false);
|
2025-03-17 16:35:11 +08:00
|
|
|
// Return different ToolResult based on whether there is an error
|
2025-03-17 01:58:37 +08:00
|
|
|
if (is_error) {
|
|
|
|
return ToolError(result.value("content", json::array()));
|
|
|
|
} else {
|
|
|
|
return ToolResult(result.value("content", json::array()));
|
|
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
return ToolError(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json to_param() const {
|
|
|
|
return {
|
|
|
|
{"type", "function"},
|
|
|
|
{"function", {
|
|
|
|
{"name", name},
|
|
|
|
{"description", description},
|
|
|
|
{"parameters", parameters}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct AgentAware : ToolResult {
|
|
|
|
std::shared_ptr<BaseAgent> agent = nullptr;
|
|
|
|
};
|
|
|
|
|
2025-03-16 17:17:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif // HUMANUS_TOOL_BASE_H
|