draracle头像

日志:自己手搓MCP服务器

return function(procedure, request, body)

    print( "start mcp" )
    local payload = body.body
    if type(payload) ~= "table" then
        return script_failed()
    end

    local method = payload.method
    local id = payload.id

    -- 1. 握手
    if method == "initialize" then
        local resp = {
            jsonrpc = "2.0",
            id = id,
            result = {
                protocolVersion = payload.params and payload.params.protocolVersion or "2024-11-05",
                capabilities = { tools = { listChanged = true } }, -- 原生布尔!放心的用!
                serverInfo = { name = "lua-mcp-server", version = "1.0.0" }
            }
        }
        -- 纯正的 Table 转字符串,没有任何心里负担!
        return script_succeed(), http_respond(200, "application/json"), "application/json", json_encode(resp)

    -- 2. 客户端就绪通知
    elseif method == "notifications/initialized" then
        return script_no_resp()

    -- 3. 工具列表
    elseif method == "tools/list" then
        local resp = {
            jsonrpc = "2.0",
            id = id,
            result = {
                tools = {
                    {
                        name = "hello",
                        description = "Say hello to someone",
                        inputSchema = {
                            type = "object",
                            properties = {
                                name = { type = "string", description = "The name to greet" }
                            },
                            required = { "name" }
                        }
                    }
                }
            }
        }
        return script_succeed(), http_respond(200, "application/json"), "application/json", json_encode(resp)

    -- 4. 调用工具
    elseif method == "tools/call" then
        local tool_name = payload.params and payload.params.name
        local args = payload.params and payload.params.arguments or {}

        if tool_name == "hello" then
            local name = args.name or "World"
            local resp = {
                jsonrpc = "2.0",
                id = id,
                result = {
                    content = {
                        { type = "text", text = "Hello, " .. name .. "! This is generated by Lua." }
                    }
                }
            }
            return script_succeed(), http_respond(200, "application/json"), "application/json", json_encode(resp)
        end
    end

    -- 兜底
    local err_resp = {
        jsonrpc = "2.0",
        id = id,
        error = { code = -32601, message = "Method not found" }
    }

    print( "end mcp, succeed" )

    return script_succeed(), http_respond(200, "application/json"), "application/json", json_encode(err_resp)
end
#include "CCoroutinableExecute.h"
#include "../../enumeration/OBJECT_ID.h"
#include "../../enumeration/PROCEDURE_RESULT_ID.h"
#include "../../enumeration/SCRIPT_EXECUTE_RESULT.h"
//Begin section for file CCoroutinableExecute.cpp
//TODO: Add definitions that you want preserved
//End section for file CCoroutinableExecute.cpp



namespace 
{



    static bool StripPort(a_string & host)
    {
        bool result = false;
        if( host.empty() == false )
        {
            do 
            {
                // [ipv6] 或 [ipv6]:port
                a_string::size_type 
                    close  = host.rfind( ']' );
                if( close != a_string::npos )
                {
                    a_string::size_type 
                        colon  = host.find( ':', close + 1 );
                    if( colon != a_string::npos ) 
                    {
                        host.resize( colon );
                        result = true;
                    }
                    break;
                }

                // 没有方括号
                // hostname,无端口
                a_string::size_type first = host.find( ':' );
                if( first == a_string::npos ) {
                    break;
                }

                // 裸 IPv6,无端口(且格式非法,交给后面的字符校验拒掉)
                a_string::size_type last = host.rfind( ':' );
                if( first != last ) {
                    break;
                }

                host.resize( first );
                result = true;

            } while( false );
        }
        return result;
    }



    //@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
    static a_string BuildRoute(CHttpRequest & request)
    {
        //TODO Auto-generated method stub
        // host:从 Host 头拿(origin-form 的 URI 里没有 host)
        a_string host;
        if( !request.Header().GetHeader("Host", host) ) {
            host = request.Line().URI().GetHost();   // 兜底:absolute-form
        }
        ToLower( host );

        // 剥掉端口("localhost:80" → "localhost")
        StripPort( host );

        // path
        a_string path = request.Line().URI().GetPath();
        ToLower( path );
        Trim( path, '/' );


        // 路径安全校验 
        // 1. 拒绝 .. 段(目录穿越)
        //    匹配 "/../"、"/.."、"../"、".." 结尾 等
        // 2. 拒绝空段连续("//")、"." 段
        // 3. 拒绝控制字符和 \0
        bool checked = true;
        do
        {
            // 拆成段判断
            CStringArray segs;
            Splite( path, '/', segs );

            for( size_t i = 0, n = segs.size(); i < n; ++ i )
            {
                if( segs[i] == ".." || segs[i] == "." || segs[i] == "" ) {
                    checked = false; break;
                }
            }

            if( !checked ) break;

            // 控制字符 / \0,和一些非法字符
            for( size_t i = 0, n = path.size(); i < n; ++ i )
            {
                U08 c = static_cast<U08>( path[i] );
                if( c < 0x20 || c == 0x7F ) {
                    checked = false; break;
                }

                if( c == ':' || c == '[' || c == ']' ) {
                    checked = false; break;
                }
            }

            if( !checked ) break;

            if( host.empty() ) 
            {
                checked = false;
                break;
            }

            // host 校验:只允许 a-z 0-9 - . : []
            for( size_t i = 0, n = host.size(); i < n; ++ i )
            {
                U08 c = static_cast<U08>( host[i] );
                bool ok = 
                    ( c >= 'a' && c <= 'z' ) || 
                    ( c >= '0' && c <= '9' ) || 
                    c == '-' || c == '.' || 
                    c == ':' || c == '[' || c == ']';

                if( !ok ) {
                    checked = false; break;
                }
            }

        } while( false );

        a_string result;
        if( checked )
        {
            // 拼成 host/path
            if( path.empty() ) {
                result = host;
            } else {
                result = host + "/" + path;
            }
        }
        return result;
    }



    static void ErrorRespond(CHttpRespond & respond, U32 status)
    {
        respond.Line().SetVersion( 1, 1 );
        respond.Line().SetStatus ( status );
        respond.Line().SetReason ( CHttpRespondStatus::ToString(status) );

        respond.Header()["Connection"]     = "close";
        respond.Header()["Content-Type"]   = "text/plain";
        respond.Header()["Content-Length"] = CVariant( static_cast<U64>(0) );
    }



    static bool OnRespondJson(CJson & respond, const a_string & body_type, const a_string & body, CHttpRespond & output, CHttpBody & output_body)
    {
        if( respond.GetType() != JVT_OBJECT ) {
            return false;
        }

        U32      status = HSC_OK;
        a_string reason = CHttpRespondStatus::ToString( HSC_OK );

        if( respond.IsMemberExist("status") ) {
            status = respond["status"];
        }
        if( respond.IsMemberExist("reason") ) {
            reason = respond["reason"];
        }

        output.Line().SetVersion( 1, 1 );
        output.Line().SetStatus ( status );
        output.Line().SetReason ( reason.c_str() );

        // headers: { {"k","v"}, ... }
        if( respond.IsMemberExist("headers") )
        {
            atom::CJson node = respond["headers"];

            for( size_t i = 0, n = node.Length(); i < n; ++ i )
            {
                // 注意:Lua 数组 1 起,CJson::operator[] 0 起
                atom::CJson item = node[i];

                a_string k = item[0];   // Lua 的 [1]
                a_string v = item[1];   // Lua 的 [2]

                if( k.empty() == false ) {
                    output.Header()[ k.c_str() ] = v.c_str();
                }
            }
        }

        bool result = output.Line().IsValid();

        I64 length = 0;
        if( body.empty() == false )
        {
            output_body.Type() = body_type;
            output_body.Body() = body;
            output_body.Size() = length = body.length();
        }

        a_string value;
        if( output.Header().GetHeader("Content-Length", value) == false ) {
            output.Header()["Content-Length"] = CVariant( length );
        }
        return result;
    }



}



//@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
flux::CCoroutinableExecute::CCoroutinableExecute():
connector(0)
{
    //TODO Auto-generated method stub
}

//@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
flux::CCoroutinableExecute::~CCoroutinableExecute() 
{
    //TODO Auto-generated method stub
}

//@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
U64 flux::CCoroutinableExecute::Run() 
{
    //TODO Auto-generated method stub
    U64 link(0); 
    a_string route; 
    CJson input, input_body;

    a_string host, origin;
    bool has_origin = false;
    bool origin_ok  = false;

    {
        CCriticalSectionScope scope( section );
        link = connector;

        // CORS:提取 host 和 origin
        request.Header().GetHeader( "Host", host );
        ToLower( host );

        // 注意:这个位置可能对IPV6作为Host有兼容性问题,[:]不被路径允许
        // 去掉端口
        StripPort( host );

        has_origin = request.Header().GetHeader( "Origin", origin );

        CJsonlizer json;
        json << request;
        input = json.Obtain();

        //printf( "%s\n", input.Stringify().c_str() );

        a_string type;
        if( request.Header().GetHeader("Content-Type", type) ) {
            request_body.Type() = type;
        }

        json.Clear();
        json << request_body;
        input_body = json.Obtain();
    
        route = BuildRoute( request );
    }

    // 攻击/畸形路径——不发响应,立即关闭连接
    if( route.empty() ) 
    {        
        if( link ) {
            CHttpService::GetInstance() -> Close( link );
        }

        SetResult( PID_FAILED_TO_EXECUTE );
        return U64_MAX;
    }

    // 查询跨域白名单
    if( has_origin ) {
        origin_ok = CHttpService::GetInstance() -> IsValid( host, origin );
    }

    bool need_respond = true;

    // 获取自己的名字,用于协程方法
    U64 self   = CInstanceUtility::GetInstanceName( nest );
    U32 result = PID_FAILED_TO_EXECUTE;
    CHttpRespond respond;
    CHttpBody    respond_body;


    do
    {
        // 404:路由不存在 
        if( false == CScriptScheduler::
            GetInstance() -> IsRouteExist( route.c_str() ) )
        {
            CLog log;
            log << "Route not found: " << route.c_str()
                << ", when run coroutinable execute procedure: " << self
                << end_error;

            ErrorRespond( respond, HSC_NOT_FOUND );
            break;
        }

        // 预检请求:不进 Lua,直接回
        a_string acrm;
        bool is_preflight =  
            has_origin && (
            request.Line  ().GetMethod() == RM_OPTIONS ) && 
            request.Header().GetHeader( "Access-Control-Request-Method", acrm );

        if( is_preflight )
        {
            respond.Line().SetVersion( 1, 1 );

            if( origin_ok )
            {
                respond.Line().SetStatus( HSC_NO_CONTENT );
                respond.Header()["Access-Control-Allow-Origin" ] = origin.c_str();
                respond.Header()["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS";
                respond.Header()["Access-Control-Allow-Headers"] = "Content-Type, Authorization";
                respond.Header()["Access-Control-Max-Age"      ] = "86400";
                respond.Header()["Vary"]                         = "Origin";
                respond.Header()["Content-Length"]               = CVariant( static_cast<U64>(0) );

                result = PID_SUCCEED;
            }
            else
            {
                respond.Line().SetStatus( HSC_FORBIDDEN );
                respond.Line().SetReason( CHttpRespondStatus::ToString(HSC_FORBIDDEN) );
                respond.Header()["Content-Length"] = CVariant( static_cast<U64>(0) );
                respond.Header()["Vary"]           = "Origin";
            }
            break;
        }

        // CSRF 防御:破坏性方法要求 Content-Type: application/json
        {
            U08 method = request.Line().GetMethod();

            bool dangerous = ( 
                method == RM_POST || 
                method == RM_PUT || 
                method == RM_DELETE );

            if( dangerous )
            {
                a_string type;
                if( !request.Header().GetHeader("Content-Type", type) ) 
                {
                    ErrorRespond( respond, HSC_UNSUPPORTED_MEDIA_TYPE );
                    break;
                }

                // 取分号前的部分(MIME type),丢弃 charset 等参数
                a_string::size_type semi = type.find( ';' );
                if( semi != a_string::npos ) {
                    type.resize( semi );
                }

                // 去首尾空白("application/json " → "application/json")
                Trim( type, ' ' );
                Trim( type, '\t' );

                // 转为小写
                ToLower( type );

                // 精确匹配
                if( type != "application/json" ) 
                {
                    ErrorRespond( respond, HSC_UNSUPPORTED_MEDIA_TYPE );
                    break;
                }
            }
        }

        // 准备参数
        CScriptArguments args;
        args << self << input << input_body;
    
        // 注意:405 错误由脚本处理
        // 如:
        // -- 只允许 POST
        // if method ~= "POST" then
        //     return true, {
        //         status  = 405,
        //         reason  = "Method Not Allowed",
        //         headers = {
        //             { "Allow", "POST" }
        //         }
        //     }, "text/plain", ""
        // end

        a_string error; 
        if( false == 
            CScriptScheduler::GetInstance() -> 
            RunRoute( route.c_str(), args, error ) )
        {
            // 如果 Lua 异常时还处于流式中,先关闭流,再回 500
            CObjectPtr session = 
                CHttpService::GetInstance()->GetSession( link );
            if( session ) 
            {
                CInterface<IHttpSessionEndPoint> segment;
                if( segment.Mount(session, IID_HTTP_SESSION_END_POINT) ) 
                {
                    if( segment -> IsStreamMode() ) 
                    {
                        segment -> CloseStream();   // 状态机会直接推进到CLOSING状态
                        need_respond = false;
                    }

                    // 此时流已关闭,不要再走 ErrorRespond,
                    // 或者走 ErrorRespond 但用 Connection: close 让对端知道异常

                }
            }

            CLog log;
            log << "Failed to execute script route: " << route.c_str()
                << ", when run coroutinable execute procedure: " << self
                << end_error;

            if( need_respond )
            {
                ErrorRespond( respond, HSC_INTERNAL_SERVER_ERROR );

                if( error.empty() ) {
                    error = "Internal Server Error";
                }

                //#ifdef _DEBUG

                respond.Header()["Content-Length"] = CVariant( static_cast<U64>( error.length() ) );
                respond_body.Type() = "text/plain";
                respond_body.Size() = static_cast<I64>( error.length() );
                respond_body.Body() = error;
            }

            //#endif // DEBUG
            break;
        }

        U08 retval = 0;
        args >> retval; 

        if( retval == SER_FAILED )
        {
            ErrorRespond( respond, HSC_INTERNAL_SERVER_ERROR );
            break;
        }
        else
        if( retval == SER_SUCCEED )
        {
            CJson output;
            a_string output_body_type, output_body;
            args >> output >> output_body_type >> output_body;

            if( false == OnRespondJson(
                output, output_body_type, output_body, respond, respond_body) ) 
            {
                ErrorRespond( respond, HSC_INTERNAL_SERVER_ERROR );
                break;
            }
        }
        else
        if( retval == SER_NO_RESPOND ) 
        {
            respond.Line().SetVersion( 1, 1 );
            respond.Line().SetStatus( HSC_NO_CONTENT );
            respond.Line().SetReason( "No Content" );
            respond.Header()["Content-Length"] = CVariant( static_cast<U64>(0) );
            
            // 保持 need_respond = true,让底部去调用 OnRespond
            // 如果这是流式操作,流式相关接口会提前处理关闭,不会用
            // 到这里,这个主要是为了通知HTTP服务推进状态机
            //need_respond = false;
        }

        result = PID_SUCCEED;

    } while( false );


    if( need_respond )
    {
        // 补 CORS 头(无论成功还是失败响应)
        if( has_origin && origin_ok )
        {
            respond.Header()["Access-Control-Allow-Origin"] = origin.c_str();
            respond.Header()["Vary"]                        = "Origin";
        }

        // 通过 connector 找到 session,回填响应
        if( link )
        {
            CObjectPtr stub = CHttpService::GetInstance() -> GetSession( link );
            if( stub )
            {
                CInterface<IHttpSessionEndPoint> segment;
                if( segment.Mount(stub, IID_HTTP_SESSION_END_POINT) ) {
                    segment -> OnRespond( respond, respond_body );
                }
            }
        }
    }

    SetResult( result );
    return U64_MAX;
}

//@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
bool flux::CCoroutinableExecute::Prepare(CArchive & in) 
{
    //TODO Auto-generated method stub
    CInterface<IConfigable<CJson> > segment;
    if( segment.Mount(this, IID_CONFIGABLE) )
    {
        CJson value;
        {
            CCriticalSectionScope scope( section );
            in >> connector >> request >> request_body;
            value = connector;
        }

        segment -> SetConfig( value );
    }
    return true;
}

//@generated "UML to C++ (com.ibm.xtools.transform.uml2.cpp.CPPTransformation)"
U32 flux::CCoroutinableExecute::GetResult(CArchive & result) 
{
    //TODO Auto-generated method stub
    UNREFERENCED_PARAMETER( result );
    CCriticalSectionScope scope( section );    
    return code;
}

开发日志
日期:2026-10-21
主题:HTTP/WebSocket 底层闭环与 MCP 协议全链路打通

一、 核心成果

  1. HTTP 与 SSE 流式引擎完成

    • 完善 HTTP 状态机,解决 Keep-Alive 复用、CORS 跨域与请求走私防御。

    • 实现基于协程的 SSE 流式输出,支持 http_stream_begin/write/close 非阻塞推流。

  2. WebSocket 核心彻底解耦

    • 将原 FrameSpliter 拆分为 CoreController(纯协议引擎)、Wrapper(业务胶水层)与 Entry(门面),成功复用于 HTTP 与 Game 双端。

    • 修复 TCP 优雅关闭问题(shutdown(SD_SEND) + closesocket),彻底消除 SSE/WSS 结束时的 RST 报错。

    • 增加分片重组 OOM 防御与连接超时熔断机制。

  3. JSON 与变体层(CVariant/CJson)深度重构

    • 增加原生 VT_BOOL 支持,修复布尔值序列化及运算逻辑。

    • 修复历史遗留 Bug:数字字符串(如 "2.0")被误判为浮点数、空对象 {} 解析丢失、嵌套节点 Key 丢失。

  4. MCP Server 正式跑通

    • 基于 HTTP/SSE 底座与 Lua 脚本,成功实现 MCP 的 initialize 握手、notifications/initialized 通知及 tools/list、tools/call 接口。

    • 已通过官方 MCP Inspector 验证,工具调用响应完全符合 JSON-RPC 2.0 与 Zod 校验规范。

二、 性能与稳定性

  • 全链路无阻塞(C++ 协程 + Lua 挂起),单机可支撑高并发长连接。

  • 内存生命周期闭环完整(连接断开后对象计数完美归零)。

三、 下阶段计划

  • 引入 Ollama / vLLM 本地大模型,在 Lua 侧封装 chat 工具。

  • 利用现有 SSE 流式底座,实现大模型 Token 级流式打字机输出。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/draracle/article/details/168661613

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--