欢迎光临
我们一直在努力

实战Demo——Hello Mcp

文章目录

    • Exercise : Hello MCP
      • 基本信息
      • 学习目标
      • 前置准备
      • 需求规格
      • 步骤指引
      • 期望输出
      • 提示
      • 扩展挑战
      • My Answer

Exercise : Hello MCP

基本信息

项目内容
难度
预估时间 1-2 小时
核心技能 JSON-RPC 2.0, stdin/stdout 传输, nlohmann/json
产出物 一个可运行的最简 MCP 服务器 (约 100 行代码)

学习目标

  • 理解 MCP 协议中最基础的请求-响应循环
  • 掌握 stdin/stdout 行协议
  • 使用 nlohmann/json 解析和构造 JSON 消息
  • 理解 JSON-RPC 的 id 字段和 method 字段

前置准备

阅读以下文档的相关章节:

  • AI与MCP协议入门 – JSON-RPC 2.0规范和 MCP 协议基础
  • C++核心基础 – 熟悉 std::getline 和 std::cout

需求规格

实现一个最简单可能的 MCP 服务器,支持以下功能:

  • 通过 stdin 读取 JSON-RPC 请求(每行一个请求)
  • 支持 initialize 命令:返回服务器能力和版本
  • 支持 ping 命令:返回空对象 {}
  • 支持 tools/list 命令:返回一个硬编码的工具列表(包含一个 “echo” 工具)
  • 支持 tools/call 命令:如果调用 “echo”,返回用户传入的文本
  • 通过 stdout 返回 JSON-RPC 响应(每行一个响应)
  • 对未知命令返回 Method not found 错误
  • 步骤指引

    Step 1: 项目骨架 (15分钟)

    // hello_mcp.cpp
    #include <iostream>
    #include <string>
    #include "json.hpp"

    using json = nlohmann::json;

    int main() {
    std::string line;
    while (std::getline(std::cin, line)) {
    // 处理每一行请求
    if (line.empty()) continue;

    try {
    auto request = json::parse(line);
    auto response = handle_request(request);
    std::cout << response.dump() << std::endl;
    std::cout.flush(); // 重要: 立即刷新
    } catch (const json::parse_error& e) {
    // 返回 Parse error
    json error_response;
    error_response["jsonrpc"] = "2.0";
    error_response["id"] = nullptr;
    error_response["error"] = {
    {"code", 32700},
    {"message", "Parse error"}
    };
    std::cout << error_response.dump() << std::endl;
    }
    }
    return 0;
    }

    Step 2: 实现命令处理函数 (20分钟)

    json handle_request(const json& request) {
    std::string method = request.value("method", "");

    if (method == "initialize") {
    return handle_initialize(request);
    } else if (method == "ping") {
    return handle_ping(request);
    } else if (method == "tools/list") {
    return handle_tools_list(request);
    } else if (method == "tools/call") {
    return handle_tools_call(request);
    } else {
    // Method not found
    json error;
    error["jsonrpc"] = "2.0";
    error["id"] = request.value("id", nullptr);
    error["error"] = {
    {"code", 32601},
    {"message", "Method not found: " + method}
    };
    return error;
    }
    }

    Step 3: 实现各个命令 (20分钟)

    json handle_initialize(const json& request) {
    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];
    response["result"] = {
    {"protocolVersion", "2024-11-05"},
    {"serverInfo", {
    {"name", "hello-mcp"},
    {"version", "1.0.0"}
    }},
    {"capabilities", {
    {"tools", {{"listChanged", false}}}
    }}
    };
    return response;
    }

    json handle_ping(const json& request) {
    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];
    response["result"] = json::object();
    return response;
    }

    json handle_tools_list(const json& request) {
    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];

    json echo_tool;
    echo_tool["name"] = "echo";
    echo_tool["description"] = "Echoes back the input text";
    echo_tool["inputSchema"] = {
    {"type", "object"},
    {"properties", {
    {"text", {{"type", "string"}, {"description", "Text to echo"}}}
    }},
    {"required", json::array({"text"})}
    };

    response["result"]["tools"] = json::array({echo_tool});
    return response;
    }

    json handle_tools_call(const json& request) {
    std::string tool_name = request["params"]["name"];

    if (tool_name == "echo") {
    std::string text = request["params"]["arguments"]["text"];

    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];
    response["result"] = {
    {"content", json::array({
    {{"type", "text"}, {"text", "Echo: " + text}}
    })},
    {"isError", false}
    };
    return response;
    }

    // Tool not found
    json error;
    error["jsonrpc"] = "2.0";
    error["id"] = request["id"];
    error["error"] = {
    {"code", 32602},
    {"message", "Unknown tool: " + tool_name}
    };
    return error;
    }

    Step 4: 编译和测试 (15分钟)

    # 编译
    g++ -std=c++17 hello_mcp.cpp -I../libs_tier_01/json/include -o hello_mcp

    # 测试 initialize
    echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | ./hello_mcp

    # 测试 tools/list
    echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./hello_mcp

    # 测试 tools/call
    echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"text":"Hello MCP!"}}}' | ./hello_mcp

    # 测试未知命令
    echo '{"jsonrpc":"2.0","id":4,"method":"unknown"}' | ./hello_mcp

    期望输出

    initialize 请求的输出应类似:

    {"id":1,"jsonrpc":"2.0","result":{"capabilities":{"tools":{"listChanged":false}},"protocolVersion":"2024-11-05","serverInfo":{"name":"hello-mcp","version":"1.0.0"}}}

    提示

  • std::cout.flush() 很关键 – stdin/stdout 是行缓冲的,不 flush 客户端收不到响应
  • request.value("id", nullptr) 安全地获取 id 字段(JSON-RPC 通知没有 id)
  • 空行要跳过,否则 json::parse("") 会抛异常
  • 扩展挑战

    • 添加 prompts/list 命令,返回一个硬编码的提示词
    • 添加请求日志(输出到 stderr,避免干扰 stdout 协议)
    • 支持多个工具(添加 “time” 工具返回当前时间)
    • 让你的 Hello MCP 能被 Claude Desktop 直接加载使用

    My Answer

    #include <iostream>
    #include <nlohmann/json.hpp>
    #include <unordered_map>

    using json = nlohmann::json;

    /// @brief 构造一个空的响应消息
    /// @param request :请求消息
    /// @return 空的响应消息
    json Response(const json &request)
    {
    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];
    response["result"] = json::object();
    return response;
    }

    enum class ErrorCode
    {
    // JSON解析失败
    ParseError = 32700,
    // 请求对象无效
    InvalidRequest = 32600,
    // 请求的方法不存在
    MethodNotFound = 32601,
    // 方法参数无效
    InvalidParams = 32602,
    // 服务器内部错误
    InternalError = 32603
    };

    class Server
    {
    public:
    Server()
    {
    FunctionMap = {
    {"initialize", [this](const json &request)
    { return this->InitializeImpl(request); }},
    {"ping", [this](const json &request)
    { return this->PingImpl(request); }},
    {"tools/list", [this](const json &request)
    { return this->ToolsListImpl(request); }},
    {"tools/call", [this](const json &request)
    { return this->ToolsCallImpl(request); }}};
    }

    void start()
    {
    json response, request;
    std::string message;
    while (true)
    {
    if (!std::getline(std::cin, message) || message.empty())
    {
    break; // EOF or empty line, exit gracefully
    }
    try
    {
    request = json::parse(message);
    }
    catch (const json::parse_error &e)
    {
    response = Error(ErrorCode::ParseError, "null", e.what());
    std::cout << response.dump() << std::endl;
    continue;
    }
    std::string method;
    if (request.contains("method"))
    {
    method = request["method"].get<std::string>();
    auto it = FunctionMap.find(method);
    if (it != FunctionMap.end())
    {
    response = FunctionMap[method](request);
    std::cout << response.dump() << std::endl;
    }
    else
    {
    response = Error(ErrorCode::MethodNotFound, method, "Method not Found");
    std::cout << response.dump() << std::endl;
    }
    }
    else
    {
    response = Error(ErrorCode::InvalidRequest, request["id"].dump(), "Missing method");
    std::cout << response.dump() << std::endl;
    }
    }
    }

    private:
    json Error(ErrorCode code, const std::string &id, const std::string &message)
    {
    return {
    {"jsonrpc", "2.0"},
    {"error", {{"code", static_cast<int>(code)}, {"message", message}}},
    {"id", id}};
    }

    json InitializeImpl(const json &request)
    {
    json response = Response(request);
    if (!request.contains("params"))
    {
    return Error(ErrorCode::InvalidParams, request["id"].dump(), "Missing Params");
    }
    if(request["params"].empty()){
    response["result"]["protocolVersion"] = json::object();
    }else{
    response["result"]["protocolVersion"] = request["params"]["protocolVersion"];
    }

    response["result"]["capabilities"]["tools"] = json::object({{"listChanged", true}});
    response["result"]["capabilities"]["prompts"] = json::object({{"listChanged", true}});
    response["result"]["capabilities"]["resources"]["subscribe"] = true;
    response["result"]["capabilities"]["resources"]["listChanged"] = true;
    response["result"]["capabilities"]["logging"] = json::object();
    return response;
    }

    json PingImpl(const json &request)
    {
    return Response(request);
    }

    json ToolsListImpl(const json &request)
    {
    json response = Response(request);
    json tool;
    tool["name"] = "echo";
    tool["description"] = "server echo message from user";
    tool["inputSchema"] = json::object();
    response["result"]["tools"] = json::array();
    response["result"]["tools"].push_back(tool);
    return response;
    }

    json ToolsCallImpl(const json &request)
    {
    json response = Response(request);
    if (!request.contains("params"))
    {
    return Error(ErrorCode::InvalidParams, request["id"].dump(), "Missing Params");
    }
    if (request["params"]["name"] == "echo")
    {
    json context;
    context["type"] = "text";
    context["text"] = request["params"]["arguments"];
    response["result"]["content"] = json::array();
    response["result"]["content"].push_back(context);
    }
    else
    {
    response["result"]["content"] = json::object();
    }
    return response;
    }

    private:
    std::unordered_map<std::string, std::function<json(const json &)>> FunctionMap;
    };

    int main()
    {
    Server server;
    server.start();
    return 0;
    }


    赞(0)
    未经允许不得转载:171主机测评 » 实战Demo——Hello Mcp
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址