419 lines
16 KiB
C++
419 lines
16 KiB
C++
|
/**
|
|||
|
* @file mcp.server.cpp
|
|||
|
* @brief Example MCP server implementation with custom resources and tools
|
|||
|
*/
|
|||
|
|
|||
|
#include "mcp_server.h"
|
|||
|
#include "mcp_resource.h"
|
|||
|
#include "mcp_tools.h"
|
|||
|
#include "mcp_workflow_resource.h"
|
|||
|
#include "custom_agent.h"
|
|||
|
|
|||
|
#include <iostream>
|
|||
|
#include <string>
|
|||
|
#include <chrono>
|
|||
|
#include <thread>
|
|||
|
#include <ctime>
|
|||
|
#include <iomanip>
|
|||
|
#include <sstream>
|
|||
|
|
|||
|
// 添加日志记录功能
|
|||
|
class Logger {
|
|||
|
public:
|
|||
|
enum class LogLevel {
|
|||
|
DEBUG,
|
|||
|
INFO,
|
|||
|
WARNING,
|
|||
|
ERROR
|
|||
|
};
|
|||
|
|
|||
|
static std::string getCurrentTimestamp() {
|
|||
|
auto now = std::chrono::system_clock::now();
|
|||
|
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
|
|||
|
auto time_t_now = std::chrono::system_clock::to_time_t(now);
|
|||
|
auto ms = now_ms.time_since_epoch().count() % 1000;
|
|||
|
|
|||
|
std::stringstream ss;
|
|||
|
ss << std::put_time(std::localtime(&time_t_now), "%Y-%m-%d %H:%M:%S");
|
|||
|
ss << "." << std::setfill('0') << std::setw(3) << ms;
|
|||
|
return ss.str();
|
|||
|
}
|
|||
|
|
|||
|
static std::string getLevelString(LogLevel level) {
|
|||
|
switch (level) {
|
|||
|
case LogLevel::DEBUG: return "DEBUG ";
|
|||
|
case LogLevel::INFO: return "INFO ";
|
|||
|
case LogLevel::WARNING: return "WARNING ";
|
|||
|
case LogLevel::ERROR: return "ERROR ";
|
|||
|
default: return "UNKNOWN ";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void log(LogLevel level, const std::string& module, const std::string& message) {
|
|||
|
std::cout << getCurrentTimestamp() << " | "
|
|||
|
<< getLevelString(level) << " | "
|
|||
|
<< module << ": "
|
|||
|
<< message << std::endl;
|
|||
|
}
|
|||
|
|
|||
|
static void debug(const std::string& module, const std::string& message) {
|
|||
|
log(LogLevel::DEBUG, module, message);
|
|||
|
}
|
|||
|
|
|||
|
static void info(const std::string& module, const std::string& message) {
|
|||
|
log(LogLevel::INFO, module, message);
|
|||
|
}
|
|||
|
|
|||
|
static void warning(const std::string& module, const std::string& message) {
|
|||
|
log(LogLevel::WARNING, module, message);
|
|||
|
}
|
|||
|
|
|||
|
static void error(const std::string& module, const std::string& message) {
|
|||
|
log(LogLevel::ERROR, module, message);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// 创建一个日志记录资源包装器
|
|||
|
class LoggingResourceWrapper : public mcp::resource {
|
|||
|
public:
|
|||
|
LoggingResourceWrapper(std::shared_ptr<mcp::resource> wrapped_resource, const std::string& resource_path)
|
|||
|
: wrapped_resource_(wrapped_resource), resource_path_(resource_path) {}
|
|||
|
|
|||
|
mcp::resource_type type() const override {
|
|||
|
return wrapped_resource_->type();
|
|||
|
}
|
|||
|
|
|||
|
mcp::json metadata() const override {
|
|||
|
return wrapped_resource_->metadata();
|
|||
|
}
|
|||
|
|
|||
|
mcp::response handle_request(const mcp::request& req) override {
|
|||
|
// 记录请求信息
|
|||
|
std::stringstream ss;
|
|||
|
ss << "Received " << mcp::http_method_to_string(req.method) << " request to "
|
|||
|
<< resource_path_ << req.path;
|
|||
|
|
|||
|
// 添加查询参数
|
|||
|
if (!req.query_params.empty()) {
|
|||
|
ss << " with params: {";
|
|||
|
bool first = true;
|
|||
|
for (const auto& [key, value] : req.query_params) {
|
|||
|
if (!first) ss << ", ";
|
|||
|
ss << key << ": " << value;
|
|||
|
first = false;
|
|||
|
}
|
|||
|
ss << "}";
|
|||
|
}
|
|||
|
|
|||
|
// 添加请求体信息(如果不是GET请求)
|
|||
|
if (req.method != mcp::http_method::get && !req.body.empty()) {
|
|||
|
if (req.body.length() > 1000) {
|
|||
|
ss << " with body: " << req.body.substr(0, 1000) << "... (truncated)";
|
|||
|
} else {
|
|||
|
ss << " with body: " << req.body;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Logger::info("mcp.server.handle_request:", ss.str());
|
|||
|
|
|||
|
// 调用包装的资源处理请求
|
|||
|
mcp::response res = wrapped_resource_->handle_request(req);
|
|||
|
|
|||
|
// 记录响应信息
|
|||
|
std::stringstream res_ss;
|
|||
|
res_ss << "Response status: " << res.status_code;
|
|||
|
if (!res.body.empty()) {
|
|||
|
if (res.body.length() > 1000) {
|
|||
|
res_ss << ", body: " << res.body.substr(0, 1000) << "... (truncated)";
|
|||
|
} else {
|
|||
|
res_ss << ", body: " << res.body;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Logger::info("mcp.server.handle_request:", res_ss.str());
|
|||
|
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
mcp::json schema() const override {
|
|||
|
return wrapped_resource_->schema();
|
|||
|
}
|
|||
|
|
|||
|
private:
|
|||
|
std::shared_ptr<mcp::resource> wrapped_resource_;
|
|||
|
std::string resource_path_;
|
|||
|
};
|
|||
|
|
|||
|
// Example tool handler for getting the current time
|
|||
|
mcp::json get_time_handler(const mcp::json& params) {
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Executing get_time tool");
|
|||
|
|
|||
|
auto now = std::chrono::system_clock::now();
|
|||
|
auto time_t_now = std::chrono::system_clock::to_time_t(now);
|
|||
|
|
|||
|
std::stringstream formatted_time;
|
|||
|
formatted_time << std::put_time(std::localtime(&time_t_now), "%Y-%m-%d %H:%M:%S");
|
|||
|
|
|||
|
mcp::json result = {
|
|||
|
{"timestamp", static_cast<int>(time_t_now)},
|
|||
|
{"formatted", formatted_time.str()}
|
|||
|
};
|
|||
|
|
|||
|
Logger::info("mcp.server.toolcall:result", "get_time result: " + result.dump());
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
// Example tool handler for mathematical calculations
|
|||
|
mcp::json calculator_handler(const mcp::json& params) {
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Executing calculator tool with params: " + params.dump());
|
|||
|
|
|||
|
if (!params.contains("operation") || !params["operation"].is_string()) {
|
|||
|
Logger::error("mcp.server.toolcall:execute", "Missing required parameter: operation (string)");
|
|||
|
throw std::runtime_error("Missing required parameter: operation (string)");
|
|||
|
}
|
|||
|
|
|||
|
std::string op = params["operation"];
|
|||
|
mcp::json result;
|
|||
|
|
|||
|
if (op == "add") {
|
|||
|
if (!params.contains("a") || !params["a"].is_number() ||
|
|||
|
!params.contains("b") || !params["b"].is_number()) {
|
|||
|
Logger::error("mcp.server.toolcall:execute", "Missing required parameters: a, b (numbers)");
|
|||
|
throw std::runtime_error("Missing required parameters: a, b (numbers)");
|
|||
|
}
|
|||
|
|
|||
|
double a = params["a"];
|
|||
|
double b = params["b"];
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Calculating " + std::to_string(a) + " + " + std::to_string(b));
|
|||
|
result = {{"result", a + b}};
|
|||
|
}
|
|||
|
else if (op == "subtract") {
|
|||
|
if (!params.contains("a") || !params["a"].is_number() ||
|
|||
|
!params.contains("b") || !params["b"].is_number()) {
|
|||
|
throw std::runtime_error("Missing required parameters: a, b (numbers)");
|
|||
|
}
|
|||
|
|
|||
|
double a = params["a"];
|
|||
|
double b = params["b"];
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Calculating " + std::to_string(a) + " - " + std::to_string(b));
|
|||
|
result = {{"result", a - b}};
|
|||
|
}
|
|||
|
else if (op == "multiply") {
|
|||
|
if (!params.contains("a") || !params["a"].is_number() ||
|
|||
|
!params.contains("b") || !params["b"].is_number()) {
|
|||
|
throw std::runtime_error("Missing required parameters: a, b (numbers)");
|
|||
|
}
|
|||
|
|
|||
|
double a = params["a"];
|
|||
|
double b = params["b"];
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Calculating " + std::to_string(a) + " * " + std::to_string(b));
|
|||
|
result = {{"result", a * b}};
|
|||
|
}
|
|||
|
else if (op == "divide") {
|
|||
|
if (!params.contains("a") || !params["a"].is_number() ||
|
|||
|
!params.contains("b") || !params["b"].is_number()) {
|
|||
|
throw std::runtime_error("Missing required parameters: a, b (numbers)");
|
|||
|
}
|
|||
|
|
|||
|
double a = params["a"];
|
|||
|
double b = params["b"];
|
|||
|
|
|||
|
if (b == 0) {
|
|||
|
Logger::error("mcp.server.toolcall:execute", "Division by zero");
|
|||
|
throw std::runtime_error("Division by zero");
|
|||
|
}
|
|||
|
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Calculating " + std::to_string(a) + " / " + std::to_string(b));
|
|||
|
result = {{"result", a / b}};
|
|||
|
}
|
|||
|
else {
|
|||
|
Logger::error("mcp.server.toolcall:execute", "Unknown operation: " + op);
|
|||
|
throw std::runtime_error("Unknown operation: " + op);
|
|||
|
}
|
|||
|
|
|||
|
Logger::info("mcp.server.toolcall:result", "calculator result: " + result.dump());
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
// Example tool handler for text processing
|
|||
|
mcp::json text_processor_handler(const mcp::json& params) {
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Executing text_processor tool with params: " + params.dump());
|
|||
|
|
|||
|
if (!params.contains("text") || !params["text"].is_string()) {
|
|||
|
Logger::error("mcp.server.toolcall:execute", "Missing required parameter: text (string)");
|
|||
|
throw std::runtime_error("Missing required parameter: text (string)");
|
|||
|
}
|
|||
|
|
|||
|
std::string text = params["text"];
|
|||
|
mcp::json result = {
|
|||
|
{"original_length", text.length()},
|
|||
|
{"original_text", text}
|
|||
|
};
|
|||
|
|
|||
|
// Process text based on parameters
|
|||
|
if (params.contains("to_uppercase") && params["to_uppercase"].is_boolean() && params["to_uppercase"]) {
|
|||
|
std::string uppercase_text = text;
|
|||
|
std::transform(uppercase_text.begin(), uppercase_text.end(), uppercase_text.begin(), ::toupper);
|
|||
|
result["uppercase"] = uppercase_text;
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Converted text to uppercase");
|
|||
|
}
|
|||
|
|
|||
|
if (params.contains("to_lowercase") && params["to_lowercase"].is_boolean() && params["to_lowercase"]) {
|
|||
|
std::string lowercase_text = text;
|
|||
|
std::transform(lowercase_text.begin(), lowercase_text.end(), lowercase_text.begin(), ::tolower);
|
|||
|
result["lowercase"] = lowercase_text;
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Converted text to lowercase");
|
|||
|
}
|
|||
|
|
|||
|
if (params.contains("reverse") && params["reverse"].is_boolean() && params["reverse"]) {
|
|||
|
std::string reversed_text = text;
|
|||
|
std::reverse(reversed_text.begin(), reversed_text.end());
|
|||
|
result["reversed"] = reversed_text;
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Reversed text");
|
|||
|
}
|
|||
|
|
|||
|
if (params.contains("word_count") && params["word_count"].is_boolean() && params["word_count"]) {
|
|||
|
// Simple word count by counting spaces
|
|||
|
int count = 1;
|
|||
|
for (char c : text) {
|
|||
|
if (c == ' ') {
|
|||
|
count++;
|
|||
|
}
|
|||
|
}
|
|||
|
result["word_count"] = count;
|
|||
|
Logger::info("mcp.server.toolcall:execute", "Counted words: " + std::to_string(count));
|
|||
|
}
|
|||
|
|
|||
|
Logger::info("mcp.server.toolcall:result", "text_processor result: " + result.dump());
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
int main() {
|
|||
|
// 创建和配置服务器
|
|||
|
mcp::server server("localhost", 8080);
|
|||
|
server.set_name("MCP Example Server");
|
|||
|
server.set_cors(true); // Enable CORS for browser clients
|
|||
|
|
|||
|
Logger::info("mcp.server", "Starting MCP Example Server");
|
|||
|
|
|||
|
// Create and register tools
|
|||
|
|
|||
|
// Time tool
|
|||
|
mcp::tool time_tool = mcp::tool_builder("get_time")
|
|||
|
.with_description("Get the current time")
|
|||
|
.build();
|
|||
|
|
|||
|
server.register_tool(time_tool, get_time_handler);
|
|||
|
Logger::info("mcp.server", "Registered get_time tool");
|
|||
|
|
|||
|
// Calculator tool
|
|||
|
mcp::tool calc_tool = mcp::tool_builder("calculator")
|
|||
|
.with_description("Perform mathematical calculations")
|
|||
|
.with_string_param("operation", "Operation to perform: add, subtract, multiply, divide")
|
|||
|
.with_number_param("a", "First operand")
|
|||
|
.with_number_param("b", "Second operand")
|
|||
|
.build();
|
|||
|
|
|||
|
server.register_tool(calc_tool, calculator_handler);
|
|||
|
Logger::info("mcp.server", "Registered calculator tool");
|
|||
|
|
|||
|
// Text processor tool
|
|||
|
mcp::tool text_tool = mcp::tool_builder("text_processor")
|
|||
|
.with_description("Process and analyze text")
|
|||
|
.with_string_param("text", "Text to process")
|
|||
|
.with_boolean_param("to_uppercase", "Convert to uppercase", false)
|
|||
|
.with_boolean_param("to_lowercase", "Convert to lowercase", false)
|
|||
|
.with_boolean_param("reverse", "Reverse the text", false)
|
|||
|
.with_boolean_param("word_count", "Count words", false)
|
|||
|
.build();
|
|||
|
|
|||
|
server.register_tool(text_tool, text_processor_handler);
|
|||
|
Logger::info("mcp.server", "Registered text_processor tool");
|
|||
|
|
|||
|
// Create and register file resource with logging wrapper
|
|||
|
auto file_resource = std::make_shared<mcp::file_resource>("./files");
|
|||
|
auto logging_file_resource = std::make_shared<LoggingResourceWrapper>(file_resource, "/files");
|
|||
|
server.register_resource("/files", logging_file_resource);
|
|||
|
Logger::info("mcp.server", "Registered file resource at /files");
|
|||
|
|
|||
|
// Create and register workflow resource with logging wrapper
|
|||
|
auto workflow_resource = std::make_shared<mcp::workflow_resource>();
|
|||
|
auto logging_workflow_resource = std::make_shared<LoggingResourceWrapper>(workflow_resource, "/workflows");
|
|||
|
server.register_resource("/workflows", logging_workflow_resource);
|
|||
|
Logger::info("mcp.server", "Registered workflow resource at /workflows");
|
|||
|
|
|||
|
// Create and register agent resource with logging wrapper
|
|||
|
auto agent_resource = std::make_shared<mcp::agent_resource>();
|
|||
|
auto logging_agent_resource = std::make_shared<LoggingResourceWrapper>(agent_resource, "/agents");
|
|||
|
server.register_resource("/agents", logging_agent_resource);
|
|||
|
Logger::info("mcp.server", "Registered agent resource at /agents");
|
|||
|
|
|||
|
// Create and register a custom echo agent
|
|||
|
auto echo_agent_ptr = std::make_shared<examples::echo_agent>("echo");
|
|||
|
echo_agent_ptr->initialize({
|
|||
|
{"uppercase", false},
|
|||
|
{"prefix", true},
|
|||
|
{"prefix_text", "Server Echo: "}
|
|||
|
});
|
|||
|
|
|||
|
agent_resource->register_agent(echo_agent_ptr);
|
|||
|
Logger::info("mcp.server", "Registered echo agent");
|
|||
|
|
|||
|
// Create a workflow and register it
|
|||
|
mcp::workflow time_text_workflow("time_with_text", "Get time and process text");
|
|||
|
mcp::tool_registry::instance().register_tool(time_tool, get_time_handler);
|
|||
|
mcp::tool_registry::instance().register_tool(text_tool, text_processor_handler);
|
|||
|
time_text_workflow.add_tool_call("get_time");
|
|||
|
time_text_workflow.add_tool_call("text_processor", {
|
|||
|
{"text", "Current time is important!"},
|
|||
|
{"to_uppercase", true},
|
|||
|
{"word_count", true}
|
|||
|
});
|
|||
|
|
|||
|
workflow_resource->register_workflow(time_text_workflow);
|
|||
|
Logger::info("mcp.server", "Registered time_with_text workflow");
|
|||
|
|
|||
|
// Create a workflow agent
|
|||
|
auto workflow_agent_ptr = std::make_shared<examples::workflow_agent>("workflow_runner");
|
|||
|
mcp::tool_registry::instance().register_tool(calc_tool, calculator_handler);
|
|||
|
workflow_agent_ptr->initialize({
|
|||
|
{"workflows", mcp::json::array({
|
|||
|
{
|
|||
|
{"name", "time_text_calc"},
|
|||
|
{"description", "Get time, process text and calculate"},
|
|||
|
{"steps", mcp::json::array({
|
|||
|
{{"tool_name", "get_time"}},
|
|||
|
{{"tool_name", "text_processor"}, {"parameters", {
|
|||
|
{"text", "Let's do some math!"},
|
|||
|
{"to_uppercase", true}
|
|||
|
}}},
|
|||
|
{{"tool_name", "calculator"}, {"parameters", {
|
|||
|
{"operation", "add"},
|
|||
|
{"a", 10},
|
|||
|
{"b", 20}
|
|||
|
}}}
|
|||
|
})}
|
|||
|
}
|
|||
|
})}
|
|||
|
});
|
|||
|
|
|||
|
agent_resource->register_agent(workflow_agent_ptr);
|
|||
|
Logger::info("mcp.server", "Registered workflow_runner agent");
|
|||
|
|
|||
|
// Start the server (blocking)
|
|||
|
std::cout << "Starting server at http://localhost:8080" << std::endl;
|
|||
|
std::cout << "Press Ctrl+C to stop" << std::endl;
|
|||
|
|
|||
|
try {
|
|||
|
Logger::info("mcp.server", "Server starting...");
|
|||
|
server.start(true);
|
|||
|
} catch (const std::exception& e) {
|
|||
|
Logger::error("mcp.server", std::string("Error: ") + e.what());
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|