Http Sse source deep dive
一个原生 x64dbg 插件,把调试器能力通过 MCP Streamable HTTP 与 SSE 暴露给 AI 客户端。真正值得拆的是:它不是脚本壳,而是把 Win32 socket、JSON-RPC、工具 schema、x64dbg bridge 和菜单配置全部编进同一个 Zig 插件。
HTTP/SSE 层的最小实现
Transport 层的核心是把 Win32 socket 直接包成 MCP 服务:src/core/mcp_server.zig:179(证据 src/evidence.md:9) 启动线程,src/core/mcp_server.zig:264(证据 src/evidence.md:10) 初始化 socket,src/core/mcp_server.zig:312(证据 src/evidence.md:11) 接收连接,src/core/mcp_server.zig:358(证据 src/evidence.md:12) 解析请求。证据索引见 src/core/mcp_server.zig:439(证据 src/evidence.md:33),它说明 JSON-RPC 分发没有外部框架参与。
▸Transport 角色
它同时支持 Streamable HTTP 与 legacy SSE:src/core/mcp_server.zig:393(证据 src/evidence.md:24) 管 GET,src/core/mcp_server.zig:409(证据 src/evidence.md:25) 管 POST,src/core/mcp_server.zig:499(证据 src/evidence.md:45) 建 SSE,src/core/mcp_server.zig:530(证据 src/evidence.md:46) 处理 SSE message。实现集中,调试方便,但也把协议细节暴露给手写 parser。
Socket 生命周期
▸启动与停止
179pub fn start() void { 180 if (server_running) { 181 bridge.logPuts("[x64dbg-MCP Server] Server already running.\x00"); 182 return; 183 } 184 185 server_running = true; 186 187 server_handle = CreateThread(null, 0, serverThreadProc, null, 0, null); 188 if (server_handle == null) { 189 server_running = false; 190 bridge.logPuts("[x64dbg-MCP Server] Failed to create server thread.\x00"); 191 return; 192 } 193 194 { 195 var buf: [192]u8 = undefined; 196 const prefix = "[x64dbg-MCP Server] Starting MCP server on "; 197 @memcpy(buf[0..prefix.len], prefix); 198 var pos: usize = prefix.len; 199 @memcpy(buf[pos .. pos + server_ip_len], server_ip[0..server_ip_len]); 200 pos += server_ip_len; 201 buf[pos] = ':'; 202 pos += 1; 203 pos += fmtPort(server_port, buf[pos..]); 204 const suffix = "..."; 205 @memcpy(buf[pos .. pos + suffix.len], suffix); 206 pos += suffix.len; 207 buf[pos] = 0; 208 bridge.logPuts(@ptrCast(&buf)); 209 } 210} 211 212pub fn stop() void { 213 if (!server_running) return; 214 server_running = false; 215 216 // Close all SSE client sockets to unblock their keepalive loops 217 for (&sse_clients) |*slot| { 218 if (slot.* != INVALID_SOCKET) { 219 _ = ws2.closesocket(slot.*); 220 slot.* = INVALID_SOCKET; 221 } 222 } 223 224 // Close listen socket to unblock accept() 225 if (listen_sock != INVALID_SOCKET) { 226 _ = ws2.closesocket(listen_sock); 227 listen_sock = INVALID_SOCKET; 228 } 229 230 // Wait for server thread to exit (up to 5s) 231 if (server_handle) |h| { 232 _ = WaitForSingleObject(h, 5000); 233 _ = CloseHandle(h); 234 server_handle = null; 235 } 236 { 237 var buf: [192]u8 = undefined; 238 const prefix = "[x64dbg-MCP Server] MCP server stopped (was on "; 239 @memcpy(buf[0..prefix.len], prefix); 240 var pos: usize = prefix.len; 241 @memcpy(buf[pos .. pos + server_ip_len], server_ip[0..server_ip_len]); 242 pos += server_ip_len; 243 buf[pos] = ':'; 244 pos += 1; 245 pos += fmtPort(server_port, buf[pos..]); 246 buf[pos] = ')'; 247 pos += 1; 248 buf[pos] = 0; 249 bridge.logPuts(@ptrCast(&buf)); 250 }
src/core/mcp_server.zig:179(证据 src/evidence.md:9) 和 src/core/mcp_server.zig:212(证据 src/evidence.md:19) 是成对设计:启动只允许一次,停止时关闭 SSE clients 和 listen socket。由于 src/core/mcp_server.zig:190(证据 src/evidence.md:20) 用 Win32 thread,serverLoop 中的资源释放必须完整,否则会影响宿主调试器。
请求分流
▸handleClient
358fn handleClient(sock: SOCKET) void { 359 const recv_result = wsRecv(sock); 360 if (recv_result.len == 0) { 361 _ = ws2.closesocket(sock); 362 return; 363 } 364 365 const request = recv_result.buf[0..recv_result.len]; 366 const method_line = getFirstLine(request); 367 368 if (std.mem.startsWith(u8, method_line, "OPTIONS ")) { 369 wsSend(sock, "HTTP/1.1 204 No Content\r\n" ++ CORS_HEADERS ++ "\r\n"); 370 _ = ws2.closesocket(sock); 371 return; 372 } 373 374 { 375 const headers_slice = if (findHeaderEnd(request)) |he| request[0..he] else request; 376 const tok = parseHeaderValue(headers_slice, "Authorization"); 377 var authorized = false; 378 if (tok) |t| { 379 if (t.len > 7 and std.mem.eql(u8, t[0..7], "Bearer ")) { 380 const bearer = t[7..]; 381 if (auth_token_len > 0 and bearer.len == auth_token_len and std.mem.eql(u8, bearer, auth_token[0..auth_token_len])) { 382 authorized = true; 383 } 384 } 385 } 386 if (!authorized) { 387 wsSend(sock, "HTTP/1.1 401 Unauthorized\r\n" ++ CORS_HEADERS ++ "Content-Length: 24\r\n\r\n{\"error\":\"Unauthorized\"}"); 388 _ = ws2.closesocket(sock); 389 return; 390 } 391 } 392 393 if (std.mem.startsWith(u8, method_line, "GET ")) { 394 const path = getPath(method_line); 395 if (std.mem.indexOf(u8, path, ".well-known") != null) { 396 wsSend(sock, "HTTP/1.1 404 Not Found\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 397 _ = ws2.closesocket(sock); 398 return; 399 } 400 if (std.mem.eql(u8, path, "/sse")) { 401 handleSseConnect(sock); 402 return; 403 } 404 sendServerInfo(sock); 405 _ = ws2.closesocket(sock); 406 return; 407 } 408 409 if (std.mem.startsWith(u8, method_line, "POST ")) { 410 const path = getPath(method_line); 411 if (std.mem.indexOf(u8, path, ".well-known") != null) { 412 wsSend(sock, "HTTP/1.1 404 Not Found\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 413 _ = ws2.closesocket(sock); 414 return; 415 } 416 417 if (findHeaderEnd(request)) |header_end| { 418 const body = request[header_end + 4 ..]; 419 const headers = request[0..header_end]; 420 421 if (std.mem.eql(u8, path, "/messages") or std.mem.startsWith(u8, path, "/messages?")) { 422 const session_id = parseQueryParam(path, "sessionId") orelse 423 parseHeaderValue(headers, "Mcp-Session-Id"); 424 handleSseMessage(sock, body, session_id); 425 } else { 426 handleJsonRpc(sock, body); 427 } 428 } else { 429 wsSend(sock, "HTTP/1.1 400 Bad Request\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 430 } 431 _ = ws2.closesocket(sock);
认证检查在 src/core/mcp_server.zig:374(证据 src/evidence.md:22) 到 src/core/mcp_server.zig:388(证据 src/evidence.md:23) 完成;随后 src/core/mcp_server.zig:393(证据 src/evidence.md:24) 选择 GET 路径,src/core/mcp_server.zig:409(证据 src/evidence.md:25) 选择 POST 路径。这里没有路径级权限概念,token 一旦通过就能到达工具分发层。
MCP 方法分派
▸方法白名单
439fn handleJsonRpc(sock: SOCKET, body: []const u8) void { 440 const parsed = std.json.parseFromSlice(std.json.Value, std.heap.page_allocator, body, .{}) catch { 441 sendJsonRpcError(sock, null, -32700, "Parse error"); 442 return; 443 }; 444 defer parsed.deinit(); 445 446 const root = parsed.value; 447 const method_val = json_mod.getStringField(root, "method") orelse { 448 sendJsonRpcError(sock, null, -32600, "Invalid request: missing method"); 449 return; 450 }; 451 const id = json_mod.getObjectField(root, "id"); 452 453 if (id == null or std.mem.startsWith(u8, method_val, "notifications/")) { 454 wsSend(sock, "HTTP/1.1 202 Accepted\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 455 return; 456 } 457 458 if (std.mem.eql(u8, method_val, "initialize")) { 459 handleInitialize(sock, id); 460 } else if (std.mem.eql(u8, method_val, "tools/list")) { 461 handleToolsList(sock, id); 462 } else if (std.mem.eql(u8, method_val, "tools/call")) { 463 handleToolsCall(sock, id, root); 464 } else if (std.mem.eql(u8, method_val, "ping")) { 465 sendJsonRpcResult(sock, id, "{}"); 466 } else { 467 sendJsonRpcError(sock, id, -32601, "Method not found"); 468 } 469}
src/core/mcp_server.zig:439(证据 src/evidence.md:33) 只认 initialize、tools/list、tools/call、ping;src/core/mcp_server.zig:588(证据 src/evidence.md:57) 生成初始化结果,src/core/mcp_server.zig:621(证据 src/evidence.md:15) 生成工具清单,src/core/mcp_server.zig:650(证据 src/evidence.md:16) 处理调用结果。这是 MCP 兼容的最小闭环。
Legacy 客户端桥
▸SSE client 管理
499fn handleSseConnect(sock: SOCKET) void { 500 const idx = sseAddClient(sock) orelse { 501 wsSend(sock, "HTTP/1.1 503 Service Unavailable\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 502 _ = ws2.closesocket(sock); 503 return; 504 }; 505 506 const session_id = sse_session_ids[idx][0..sse_session_lens[idx]]; 507 508 wsSend(sock, "HTTP/1.1 200 OK\r\n" ++ CORS_HEADERS ++ 509 "Content-Type: text/event-stream\r\nCache-Control: no-cache\r\nConnection: keep-alive\r\n\r\n"); 510 511 var endpoint_buf: [128]u8 = undefined; 512 const endpoint = std.fmt.bufPrint(&endpoint_buf, "/messages?sessionId={s}", .{session_id}) catch "/messages"; 513 sseSendEvent(sock, "endpoint", endpoint); 514 515 bridge.logPuts("[x64dbg-MCP Server] SSE client connected.\x00"); 516 517 // Keep connection alive until client disconnects or server stops 518 while (server_running) { 519 Sleep(5000); 520 // Send keep-alive comment 521 const n = ws2.send(sock, ": keepalive\n\n", 13, 0); 522 if (n <= 0) break; 523 } 524 525 sseRemoveClient(idx); 526 _ = ws2.closesocket(sock); 527 bridge.logPuts("[x64dbg-MCP Server] SSE client disconnected.\x00"); 528} 529 530fn handleSseMessage(sock: SOCKET, body: []const u8, session_id: ?[]const u8) void { 531 const parsed = std.json.parseFromSlice(std.json.Value, std.heap.page_allocator, body, .{}) catch { 532 sendJsonRpcError(sock, null, -32700, "Parse error"); 533 return; 534 }; 535 defer parsed.deinit(); 536 537 const root = parsed.value; 538 const method_val = json_mod.getStringField(root, "method") orelse { 539 sendJsonRpcError(sock, null, -32600, "Invalid request: missing method"); 540 return; 541 }; 542 const id = json_mod.getObjectField(root, "id"); 543 544 // Notifications get 202 545 if (id == null or std.mem.startsWith(u8, method_val, "notifications/")) { 546 wsSend(sock, "HTTP/1.1 202 Accepted\r\n" ++ CORS_HEADERS ++ "Content-Length: 0\r\n\r\n"); 547 return; 548 } 549 550 // Process the RPC call and build the result 551 var result_buf: [131072]u8 = undefined; 552 var rw = JsonWriter.init(&result_buf); 553 554 if (std.mem.eql(u8, method_val, "initialize")) { 555 buildInitializeResult(&rw); 556 } else if (std.mem.eql(u8, method_val, "tools/list")) { 557 buildToolsListResult(&rw); 558 } else if (std.mem.eql(u8, method_val, "tools/call")) { 559 buildToolsCallResult(&rw, root); 560 } else if (std.mem.eql(u8, method_val, "ping")) { 561 rw.raw("{}"); 562 } else { 563 // Method not found — respond directly on POST socket 564 sendJsonRpcError(sock, id, -32601, "Method not found"); 565 return; 566 } 567 568 // Build full JSON-RPC response 569 var resp_buf: [131072]u8 = undefined; 570 var w = JsonWriter.init(&resp_buf); 571 w.raw("{\"jsonrpc\":\"2.0\",\"id\":"); 572 writeJsonValue(&w, id); 573 w.raw(",\"result\":"); 574 w.raw(rw.slice()); 575 w.raw("}"); 576 const resp_json = w.slice(); 577 578 // Try to push response via SSE channel 579 const sse_idx = if (session_id) |sid| sseFindBySessionId(sid) else null; 580 if (sse_idx) |idx| { 581 sseSendEvent(sse_clients[idx], "message", resp_json); 582 } 583 584 // Also respond on the POST connection 585 wsSend(sock, "HTTP/1.1 200 OK\r\n" ++ CORS_HEADERS ++ "Content-Type: application/json\r\nContent-Length: 0\r\n\r\n"); 586}
SSE 链路在 src/core/mcp_server.zig:499(证据 src/evidence.md:45) 分配 client slot,在 src/core/mcp_server.zig:530(证据 src/evidence.md:46) 收到消息后把 JSON-RPC 结果推回 stream。这个兼容路径有价值,但最好配合短 token、loopback bind 和外层防火墙。
Transport 风险
▸默认开放地址
src/core/mcp_server.zig:37(证据 src/evidence.md:52) 的默认 IP 是 0.0.0.0,src/core/config.zig:203(证据 src/evidence.md:38) 也会把它作为初始配置。虽然 src/core/config.zig:406(证据 src/evidence.md:58) 的 UI 文案解释了 127.0.0.1 与 0.0.0.0,默认开放仍值得在生产使用前改成 loopback。