diff --git a/README.md b/README.md index 58fb47ab..93fdc679 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,30 @@ make release sudo make install ``` +### Windows Build (Cygwin + MinGW) + +Building on Windows requires Cygwin with MinGW-w64 toolchain: + +#### Prerequisites +Install [Cygwin](https://www.cygwin.com/) with these packages: +- `make`, `cmake` - Build tools +- `mingw64-x86_64-gcc-g++` - MinGW C++ compiler +- `mingw64-x86_64-libevent` - Event library +- `mingw64-x86_64-openssl` - SSL/TLS library + +#### Build Commands +```bash +# From Cygwin bash shell +./build-mingw.sh # Release build (default) +./build-mingw.sh debug # Debug build +./build-mingw.sh release # Release build +./build-mingw.sh clean # Clean build directory +``` + +#### Output +- Build directory: `build-mingw/` +- Executable: `build-mingw/examples/mcp/mcp_example_server.exe` + ### Running Tests ```bash make test # Run tests with minimal output diff --git a/examples/event_loop_example.cc b/examples/event_loop_example.cc index 0dd35831..015a8bbc 100644 --- a/examples/event_loop_example.cc +++ b/examples/event_loop_example.cc @@ -1,14 +1,25 @@ #include #include #include -#include #include #include + +// Platform-specific includes - must come before mcp headers +#ifdef _WIN32 +#include +#include +#ifndef _SOCKLEN_T_DEFINED +#define _SOCKLEN_T_DEFINED +typedef int socklen_t; +#endif +#else +#include #include #include #include #include +#endif #include "mcp/event/event_loop.h" #include "mcp/event/libevent_dispatcher.h" @@ -16,6 +27,28 @@ using namespace mcp::event; using namespace std::chrono_literals; +// Platform-specific socket helpers (os_fd_t is defined in event_loop.h) +namespace { +#ifdef _WIN32 +constexpr os_fd_t INVALID_SOCKET_FD = INVALID_SOCKET; +inline void close_socket(os_fd_t s) { closesocket(s); } +inline int get_socket_error() { return WSAGetLastError(); } +inline bool is_would_block(int err) { return err == WSAEWOULDBLOCK; } +inline void set_nonblocking(os_fd_t fd) { + u_long mode = 1; + ioctlsocket(fd, FIONBIO, &mode); +} +#else +constexpr os_fd_t INVALID_SOCKET_FD = -1; +inline void close_socket(os_fd_t s) { close(s); } +inline int get_socket_error() { return errno; } +inline bool is_would_block(int err) { + return err == EAGAIN || err == EWOULDBLOCK; +} +inline void set_nonblocking(os_fd_t fd) { fcntl(fd, F_SETFL, O_NONBLOCK); } +#endif +} // namespace + /** * Simple echo server example using the MCP event loop */ @@ -25,23 +58,32 @@ class EchoServer { : dispatcher_(dispatcher), port_(port) {} bool start() { +#ifdef _WIN32 + // Initialize Winsock + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + std::cerr << "WSAStartup failed\n"; + return false; + } +#endif + // Create server socket server_fd_ = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd_ < 0) { + if (server_fd_ == INVALID_SOCKET_FD) { std::cerr << "Failed to create socket\n"; return false; } // Set socket options int opt = 1; - if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < - 0) { + if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)) < 0) { std::cerr << "Failed to set socket options\n"; return false; } // Make non-blocking - fcntl(server_fd_, F_SETFL, O_NONBLOCK); + set_nonblocking(server_fd_); // Bind struct sockaddr_in addr; @@ -49,7 +91,8 @@ class EchoServer { addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port_); - if (bind(server_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (bind(server_fd_, reinterpret_cast<::sockaddr*>(&addr), sizeof(addr)) < + 0) { std::cerr << "Failed to bind to port " << port_ << "\n"; return false; } @@ -73,15 +116,18 @@ class EchoServer { void stop() { accept_event_.reset(); connections_.clear(); - if (server_fd_ >= 0) { - close(server_fd_); - server_fd_ = -1; + if (server_fd_ != INVALID_SOCKET_FD) { + close_socket(server_fd_); + server_fd_ = INVALID_SOCKET_FD; } +#ifdef _WIN32 + WSACleanup(); +#endif } private: struct Connection { - int fd; + os_fd_t fd; FileEventPtr event; std::string buffer; }; @@ -95,10 +141,10 @@ class EchoServer { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); - int client_fd = - accept(server_fd_, (struct sockaddr*)&client_addr, &client_len); - if (client_fd < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { + os_fd_t client_fd = accept( + server_fd_, reinterpret_cast<::sockaddr*>(&client_addr), &client_len); + if (client_fd == INVALID_SOCKET_FD) { + if (is_would_block(get_socket_error())) { break; // No more connections } std::cerr << "Accept failed\n"; @@ -106,7 +152,7 @@ class EchoServer { } // Make non-blocking - fcntl(client_fd, F_SETFL, O_NONBLOCK); + set_nonblocking(client_fd); std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << "\n"; @@ -130,7 +176,11 @@ class EchoServer { if (events & static_cast(FileReadyType::Read)) { char buffer[1024]; while (true) { +#ifdef _WIN32 + int n = recv(conn->fd, buffer, sizeof(buffer), 0); +#else ssize_t n = read(conn->fd, buffer, sizeof(buffer)); +#endif if (n > 0) { // Echo back conn->buffer.append(buffer, n); @@ -138,16 +188,16 @@ class EchoServer { // Connection closed std::cout << "Connection closed\n"; connections_.erase(conn->fd); - close(conn->fd); + close_socket(conn->fd); return; } else { - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (is_would_block(get_socket_error())) { break; // No more data } // Error std::cerr << "Read error\n"; connections_.erase(conn->fd); - close(conn->fd); + close_socket(conn->fd); return; } } @@ -156,17 +206,22 @@ class EchoServer { if (events & static_cast(FileReadyType::Write) && !conn->buffer.empty()) { while (!conn->buffer.empty()) { +#ifdef _WIN32 + int n = send(conn->fd, conn->buffer.data(), + static_cast(conn->buffer.size()), 0); +#else ssize_t n = write(conn->fd, conn->buffer.data(), conn->buffer.size()); +#endif if (n > 0) { conn->buffer.erase(0, n); } else { - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (is_would_block(get_socket_error())) { break; // Can't write more now } // Error std::cerr << "Write error\n"; connections_.erase(conn->fd); - close(conn->fd); + close_socket(conn->fd); return; } } @@ -180,9 +235,9 @@ class EchoServer { Dispatcher& dispatcher_; int port_; - int server_fd_ = -1; + os_fd_t server_fd_ = INVALID_SOCKET_FD; FileEventPtr accept_event_; - std::unordered_map> connections_; + std::unordered_map> connections_; }; int main(int argc, char* argv[]) { diff --git a/examples/mcp/mcp_config_example_server.cc b/examples/mcp/mcp_config_example_server.cc index edd2a0c8..7cde3ce5 100644 --- a/examples/mcp/mcp_config_example_server.cc +++ b/examples/mcp/mcp_config_example_server.cc @@ -30,11 +30,9 @@ #include #include #include -#include #include #include #include -#include #include // Core MCP includes @@ -350,11 +348,11 @@ class ConfigDrivenExampleServer : public network::ListenerCallbacks { << std::endl; } - auto trimView = [](std::string_view value) -> std::string_view { + auto trimString = [](const std::string& value) -> std::string { const char* whitespace = " \t\n\r"; const auto first = value.find_first_not_of(whitespace); - if (first == std::string_view::npos) { - return std::string_view{}; + if (first == std::string::npos) { + return std::string{}; } const auto last = value.find_last_not_of(whitespace); return value.substr(first, last - first + 1); @@ -403,14 +401,13 @@ class ConfigDrivenExampleServer : public network::ListenerCallbacks { auto tryDecodeArgumentsString = [&](const std::string& raw, const char* source) -> bool { - std::string_view trimmed = trimView(raw); + std::string trimmed = trimString(raw); if (trimmed.empty()) { return false; } if (trimmed.front() == '{' || trimmed.front() == '[') { try { - json::JsonValue parsed = - json::JsonValue::parse(std::string(trimmed)); + json::JsonValue parsed = json::JsonValue::parse(trimmed); return tryParseArgumentsObject(parsed, source); } catch (const std::exception& e) { if (server_.verbose_) { @@ -424,7 +421,7 @@ class ConfigDrivenExampleServer : public network::ListenerCallbacks { std::cerr << "[Tool] Treating arguments from " << source << " as plain text" << std::endl; } - respondWithText(std::string(trimmed)); + respondWithText(trimmed); return true; }; diff --git a/examples/mcp/mcp_example_client.cc b/examples/mcp/mcp_example_client.cc index e8c5d23b..95baa317 100644 --- a/examples/mcp/mcp_example_client.cc +++ b/examples/mcp/mcp_example_client.cc @@ -957,74 +957,45 @@ int main(int argc, char* argv[]) { return 1; } - // For HTTP+SSE, connection is established immediately (stateless protocol) - // For stdio/websocket, wait for actual connection - if (options.transport != "http") { - std::cerr << "[INFO] Waiting for connection to be established..." - << std::endl; - - int wait_count = 0; - bool connected = false; - while (!connected && wait_count < 100 && - !g_shutdown) { // 10 seconds timeout - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - wait_count++; - if (wait_count % 10 == 0) { - std::cerr << "[INFO] Still connecting..." << std::endl; - } + // Wait for connection to be fully established + // The connection happens asynchronously, so we need to wait for it + std::cerr << "[INFO] Waiting for connection to be established..." + << std::endl; - // Check connection status safely - { - std::lock_guard lock(g_client_mutex); - if (g_client) { - connected = g_client->isConnected(); - } - } + int wait_count = 0; + bool connected = false; + while (!connected && wait_count < 100 && !g_shutdown) { // 10 seconds timeout + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + wait_count++; + if (wait_count % 10 == 0) { + std::cerr << "[INFO] Still connecting... (" << wait_count / 10 << "s)" + << std::endl; } - if (!connected) { - if (g_shutdown) { - std::cerr << "[INFO] Shutdown requested during connection" << std::endl; - return 0; + // Check connection status safely + { + std::lock_guard lock(g_client_mutex); + if (g_client) { + connected = g_client->isConnected(); } - std::cerr << "[ERROR] Connection timeout - failed to establish connection" - << std::endl; - std::cerr << "[HINT] Make sure the server is running on " << options.host - << ":" << options.port << std::endl; - return 1; } } if (g_shutdown) { - std::cerr << "[INFO] Shutdown requested, exiting..." << std::endl; + std::cerr << "[INFO] Shutdown requested during connection" << std::endl; return 0; } - std::cerr << "[INFO] Connected successfully!" << std::endl; - - // The event loop is already running from connect() - // No need to start it separately - - // Wait for connection to be fully established - // The connection happens asynchronously, so we need to wait for it - std::cerr << "[INFO] Waiting for connection to be established..." - << std::endl; - { - std::lock_guard lock(g_client_mutex); - if (g_client) { - // Wait up to 5 seconds for connection to be established - auto start = std::chrono::steady_clock::now(); - while (!g_client->isConnected()) { - if (std::chrono::steady_clock::now() - start > - std::chrono::seconds(5)) { - std::cerr << "[ERROR] Timeout waiting for connection" << std::endl; - return 1; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } + if (!connected) { + std::cerr << "[ERROR] Connection timeout - failed to establish connection" + << std::endl; + std::cerr << "[HINT] Make sure the server is running on " << options.host + << ":" << options.port << std::endl; + return 1; } + std::cerr << "[INFO] Connected successfully!" << std::endl; + // Initialize MCP protocol - REQUIRED before any requests std::cerr << "[INFO] Initializing MCP protocol..." << std::endl; std::future init_future; diff --git a/examples/stdio_echo/stdio_echo_client_basic.cc b/examples/stdio_echo/stdio_echo_client_basic.cc index 1b56e2a8..29f65800 100644 --- a/examples/stdio_echo/stdio_echo_client_basic.cc +++ b/examples/stdio_echo/stdio_echo_client_basic.cc @@ -279,7 +279,9 @@ int main(int argc, char* argv[]) { // Setup signal handlers signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); - signal(SIGPIPE, SIG_IGN); +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); // SIGPIPE doesn't exist on Windows +#endif try { std::cerr << "MCP Stdio Echo Client (Basic)\n" diff --git a/examples/stdio_echo/stdio_echo_server_basic.cc b/examples/stdio_echo/stdio_echo_server_basic.cc index dd545147..897b2ff5 100644 --- a/examples/stdio_echo/stdio_echo_server_basic.cc +++ b/examples/stdio_echo/stdio_echo_server_basic.cc @@ -164,7 +164,9 @@ int main(int argc, char* argv[]) { // Setup signal handlers signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); - signal(SIGPIPE, SIG_IGN); +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); // SIGPIPE doesn't exist on Windows +#endif try { std::cerr << "MCP Stdio Echo Server (Basic)\n" diff --git a/examples/transport/tcp_ssl_http_example.cc b/examples/transport/tcp_ssl_http_example.cc index 6b08712b..5fa88107 100644 --- a/examples/transport/tcp_ssl_http_example.cc +++ b/examples/transport/tcp_ssl_http_example.cc @@ -9,6 +9,12 @@ * 4. Use the transport for MCP communication */ +// Platform-specific includes must come first +#ifdef _WIN32 +#include +#include +#endif + #include #include #include diff --git a/include/mcp/c_api/mcp_c_bridge.h b/include/mcp/c_api/mcp_c_bridge.h index d4db71ee..6bfe44f9 100644 --- a/include/mcp/c_api/mcp_c_bridge.h +++ b/include/mcp/c_api/mcp_c_bridge.h @@ -21,6 +21,12 @@ #ifndef MCP_C_BRIDGE_H #define MCP_C_BRIDGE_H +/* Windows socket headers must come first to avoid conflicts */ +#ifdef _WIN32 +#include +#include +#endif + /* Include C API headers */ #include "mcp_c_api.h" #include "mcp_c_collections.h" @@ -690,8 +696,13 @@ inline mcp::network::Address::InstanceConstSharedPtr to_cpp_address_safe( std::string(addr->addr.inet.host), addr->addr.inet.port); case mcp_address::MCP_AF_UNIX: +#ifndef _WIN32 return std::make_shared( std::string(addr->addr.unix.path)); +#else + // Unix domain sockets not supported on Windows + return nullptr; +#endif default: return nullptr; diff --git a/include/mcp/c_api/mcp_c_raii.h b/include/mcp/c_api/mcp_c_raii.h index b6d34791..b9c46f8e 100644 --- a/include/mcp/c_api/mcp_c_raii.h +++ b/include/mcp/c_api/mcp_c_raii.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mcp/echo/stdio_transport.h b/include/mcp/echo/stdio_transport.h index 58138292..53705838 100644 --- a/include/mcp/echo/stdio_transport.h +++ b/include/mcp/echo/stdio_transport.h @@ -16,11 +16,18 @@ #define MCP_ECHO_STDIO_TRANSPORT_H #include -#include #include -#include #include + +// Platform-specific includes +#ifdef _WIN32 +#include +#include +#else +#include +#include #include +#endif #include "mcp/echo/echo_basic.h" #include "mcp/event/libevent_dispatcher.h" @@ -76,6 +83,13 @@ class StdioTransport : public EchoTransportBase { return true; } +#ifdef _WIN32 + // On Windows, get stdin handle for later use + stdin_handle_ = GetStdHandle(STD_INPUT_HANDLE); + if (stdin_handle_ == INVALID_HANDLE_VALUE) { + return false; + } +#else // Set stdin to non-blocking mode // This prevents read() from blocking indefinitely // and allows clean shutdown via poll timeout @@ -84,6 +98,7 @@ class StdioTransport : public EchoTransportBase { return false; } fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); +#endif running_ = true; connected_ = true; @@ -129,6 +144,34 @@ class StdioTransport : public EchoTransportBase { // Uses poll() for efficient waiting with timeout // Allows responsive shutdown without blocking char buffer[8192]; + +#ifdef _WIN32 + // Windows implementation using WaitForSingleObject and ReadFile + while (running_) { + // Wait with 100ms timeout for responsive shutdown + DWORD waitResult = WaitForSingleObject(stdin_handle_, 100); + + if (waitResult == WAIT_OBJECT_0) { + DWORD bytesRead = 0; + if (ReadFile(stdin_handle_, buffer, sizeof(buffer), &bytesRead, NULL)) { + if (bytesRead > 0) { + // Data received + if (data_callback_) { + data_callback_(std::string(buffer, bytesRead)); + } + } else { + // EOF - stdin closed + if (connection_callback_) { + connection_callback_(false); + } + connected_ = false; + break; + } + } + } + // WAIT_TIMEOUT is normal, just continue loop + } +#else struct pollfd pfd; pfd.fd = STDIN_FILENO; pfd.events = POLLIN; @@ -158,6 +201,7 @@ class StdioTransport : public EchoTransportBase { // This is normal when no data available } } +#endif } std::atomic running_; @@ -165,6 +209,9 @@ class StdioTransport : public EchoTransportBase { std::thread read_thread_; DataCallback data_callback_; ConnectionCallback connection_callback_; +#ifdef _WIN32 + HANDLE stdin_handle_ = INVALID_HANDLE_VALUE; +#endif }; /** diff --git a/include/mcp/network/address.h b/include/mcp/network/address.h index 2aeabbff..d7745441 100644 --- a/include/mcp/network/address.h +++ b/include/mcp/network/address.h @@ -12,6 +12,11 @@ #ifdef _WIN32 #include #include +// Windows doesn't define socklen_t - use int as per Winsock convention +#ifndef _SOCKLEN_T_DEFINED +#define _SOCKLEN_T_DEFINED +typedef int socklen_t; +#endif #else #include #include diff --git a/include/mcp/network/address_impl.h b/include/mcp/network/address_impl.h index 97150739..1d674eab 100644 --- a/include/mcp/network/address_impl.h +++ b/include/mcp/network/address_impl.h @@ -1,6 +1,13 @@ #ifndef MCP_NETWORK_ADDRESS_IMPL_H #define MCP_NETWORK_ADDRESS_IMPL_H +// Platform-specific includes for socket structures +// Must come before other includes to avoid conflicts +#ifdef _WIN32 +#include +#include +#endif + #include #include "mcp/network/address.h" diff --git a/include/mcp/network/io_handle.h b/include/mcp/network/io_handle.h index 4167502e..6c5365ab 100644 --- a/include/mcp/network/io_handle.h +++ b/include/mcp/network/io_handle.h @@ -30,6 +30,26 @@ using os_fd_t = int; constexpr os_fd_t INVALID_SOCKET_FD = -1; #endif +// Platform-specific socket error codes +// These constants normalize error handling across Windows and Unix +#ifdef _WIN32 +constexpr int SOCKET_ERROR_AGAIN = WSAEWOULDBLOCK; +constexpr int SOCKET_ERROR_INPROGRESS = WSAEINPROGRESS; +constexpr int SOCKET_ERROR_WOULDBLOCK = WSAEWOULDBLOCK; +constexpr int SOCKET_ERROR_CONNREFUSED = WSAECONNREFUSED; +constexpr int SOCKET_ERROR_CONNRESET = WSAECONNRESET; +constexpr int SOCKET_ERROR_NOTCONN = WSAENOTCONN; +inline int getLastSocketError() { return WSAGetLastError(); } +#else +constexpr int SOCKET_ERROR_AGAIN = EAGAIN; +constexpr int SOCKET_ERROR_INPROGRESS = EINPROGRESS; +constexpr int SOCKET_ERROR_WOULDBLOCK = EWOULDBLOCK; +constexpr int SOCKET_ERROR_CONNREFUSED = ECONNREFUSED; +constexpr int SOCKET_ERROR_CONNRESET = ECONNRESET; +constexpr int SOCKET_ERROR_NOTCONN = ENOTCONN; +inline int getLastSocketError() { return errno; } +#endif + // Bring IoResult types into this namespace using ::mcp::IoCallResult; using ::mcp::IoResult; diff --git a/include/mcp/types.h b/include/mcp/types.h index 22d9441e..b760aa44 100644 --- a/include/mcp/types.h +++ b/include/mcp/types.h @@ -472,6 +472,11 @@ struct ListResourcesResult : PaginatedResultBase { ListResourcesResult() = default; }; +struct ListToolsResult { + std::vector tools; + ListToolsResult() = default; +}; + // JSON-RPC message types namespace jsonrpc { @@ -492,8 +497,8 @@ struct Request { }; // Generic result type for responses -// Note: ListResourcesResult is defined outside jsonrpc namespace but used here -// For tools and prompts, we use the vector types directly since they're simpler +// Note: ListResourcesResult and ListToolsResult are defined outside jsonrpc +// namespace but used here using ResponseResult = variant, std::vector, std::vector, - ListResourcesResult>; + ListResourcesResult, + ListToolsResult>; struct Response { std::string jsonrpc = "2.0"; @@ -1017,11 +1023,8 @@ struct ListToolsRequest : PaginatedRequest { ListToolsRequest() : PaginatedRequest() { method = "tools/list"; } }; -struct ListToolsResult { - std::vector tools; - - ListToolsResult() = default; -}; +// Note: ListToolsResult is defined earlier (before ResponseResult) to allow +// it to be used in the ResponseResult variant type. struct CallToolRequest : jsonrpc::Request { std::string name; diff --git a/src/c_api/mcp_c_memory_impl.cc b/src/c_api/mcp_c_memory_impl.cc index d02002bd..9eb7b014 100644 --- a/src/c_api/mcp_c_memory_impl.cc +++ b/src/c_api/mcp_c_memory_impl.cc @@ -8,6 +8,7 @@ */ #include +#include #include #include #include diff --git a/src/client/mcp_client.cc b/src/client/mcp_client.cc index 90cd5849..207ce43f 100644 --- a/src/client/mcp_client.cc +++ b/src/client/mcp_client.cc @@ -7,6 +7,7 @@ #include #include "mcp/event/libevent_dispatcher.h" +#include "mcp/json/json_serialization.h" #include "mcp/mcp_application_base.h" #include "mcp/mcp_connection_manager.h" #include "mcp/network/socket_interface_impl.h" diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc index bb037ad2..f3ff11a3 100644 --- a/src/config/config_manager.cc +++ b/src/config/config_manager.cc @@ -16,6 +16,9 @@ #include #include +#if defined(__linux__) || defined(__unix__) +#include // for environ +#endif #include "mcp/config/json_conversion.h" #include "mcp/config/parse_error.h" @@ -58,8 +61,9 @@ bool EnvironmentConfigSource::hasConfiguration() const { char** envp = *_NSGetEnviron(); for (char** env = envp; env && *env; ++env) { #elif defined(__linux__) || defined(__unix__) - extern char** environ; // provided by C runtime - for (char** env = environ; env && *env; ++env) { + // environ is a global C variable, must reference with :: to avoid namespace + // issues + for (char** env = ::environ; env && *env; ++env) { #else // Fallback: no portable way; report none for (char** env = nullptr; env && *env; ++env) { diff --git a/src/json/json_serialization.cc b/src/json/json_serialization.cc index a0b9dc74..7bf81d22 100644 --- a/src/json/json_serialization.cc +++ b/src/json/json_serialization.cc @@ -126,6 +126,10 @@ JsonValue serialize_ResponseResult(const jsonrpc::ResponseResult& result) { [&json_result](const ListResourcesResult& list_result) { // ListResourcesResult is a full result object with resources array json_result = to_json(list_result); + }, + [&json_result](const ListToolsResult& list_result) { + // ListToolsResult is a full result object with tools array + json_result = to_json(list_result); }); return json_result; @@ -528,6 +532,10 @@ jsonrpc::ResponseResult deserialize_ResponseResult(const JsonValue& json) { if (json.contains("resources") && json["resources"].isArray()) { return jsonrpc::ResponseResult(from_json(json)); } + // Check if it's a ListToolsResult (has "tools" array) + if (json.contains("tools") && json["tools"].isArray()) { + return jsonrpc::ResponseResult(from_json(json)); + } // Otherwise treat as Metadata return jsonrpc::ResponseResult(jsonToMetadata(json)); } else if (json.isArray() && json.size() > 0) { diff --git a/src/network/connection_impl.cc b/src/network/connection_impl.cc index 98a486e8..acdfeaee 100644 --- a/src/network/connection_impl.cc +++ b/src/network/connection_impl.cc @@ -857,7 +857,30 @@ void ConnectionImpl::onWriteReady() { } if (connecting_) { - // Connection completed + // Write event fired while connecting - check if connection actually + // succeeded For non-blocking connect, we MUST check SO_ERROR to determine + // actual result + int socket_error = 0; + socklen_t error_len = sizeof(socket_error); + auto getsockopt_result = socket_->ioHandle().getSocketOption( + SOL_SOCKET, SO_ERROR, &socket_error, &error_len); + + std::cerr << "[CONN] onWriteReady(): fd=" << socket_->ioHandle().fd() + << " connecting=true, SO_ERROR=" << socket_error << std::endl; + + if (!getsockopt_result.ok() || socket_error != 0) { + // Connection failed + std::cerr << "[CONN] onWriteReady(): connection FAILED, error=" + << socket_error << std::endl; + connecting_ = false; + connected_ = false; + immediate_error_event_ = ConnectionEvent::RemoteClose; + closeSocket(ConnectionEvent::RemoteClose); + return; + } + + // Connection succeeded + std::cerr << "[CONN] onWriteReady(): connection SUCCEEDED" << std::endl; connecting_ = false; connected_ = true; state_ = ConnectionState::Open; @@ -1022,11 +1045,24 @@ void ConnectionImpl::doConnect() { auto result = socket_->connect(socket_->connectionInfoProvider().remoteAddress()); + std::cerr << "[CONN] doConnect(): fd=" << socket_->ioHandle().fd() + << " result.ok()=" << result.ok(); + if (result.ok()) { + std::cerr << " value=" << *result; + } else { + std::cerr << " error=" << result.error_code() + << " (INPROGRESS=" << SOCKET_ERROR_INPROGRESS + << " WOULDBLOCK=" << SOCKET_ERROR_WOULDBLOCK << ")"; + } + std::cerr << std::endl; + if (result.ok() && *result == 0) { // Immediate connection success (rare for TCP but can happen with local // connections) Schedule the Connected event to be handled in the next // dispatcher iteration This ensures all callbacks are invoked in proper // dispatcher thread context + std::cerr << "[CONN] doConnect(): immediate connection success" + << std::endl; connecting_ = false; connected_ = true; state_ = ConnectionState::Open; @@ -1047,12 +1083,18 @@ void ConnectionImpl::doConnect() { // Write events should only be enabled when there's data to send. // Enabling both causes busy loop on macOS/kqueue. enableFileEvents(static_cast(event::FileReadyType::Read)); - } else if (!result.ok() && result.error_code() == EINPROGRESS) { + } else if (!result.ok() && (result.error_code() == SOCKET_ERROR_INPROGRESS || + result.error_code() == SOCKET_ERROR_WOULDBLOCK)) { // Connection in progress, wait for write ready // Note: Only Write needed here since connection isn't established yet + std::cerr + << "[CONN] doConnect(): connection in progress, waiting for Write event" + << std::endl; enableFileEvents(static_cast(event::FileReadyType::Write)); } else { - // Connection failed + // Connection failed immediately + std::cerr << "[CONN] doConnect(): connection failed immediately" + << std::endl; immediate_error_event_ = ConnectionEvent::RemoteClose; connecting_ = false; // Activate write event to trigger error handling on next loop diff --git a/src/network/io_socket_handle_impl.cc b/src/network/io_socket_handle_impl.cc index e7921f8c..31337711 100644 --- a/src/network/io_socket_handle_impl.cc +++ b/src/network/io_socket_handle_impl.cc @@ -28,23 +28,21 @@ namespace network { namespace { -// Platform-specific error codes +// Additional platform-specific error codes not in io_handle.h #ifdef _WIN32 -constexpr int SOCKET_ERROR_AGAIN = WSAEWOULDBLOCK; constexpr int SOCKET_ERROR_INVAL = WSAEINVAL; -constexpr int SOCKET_ERROR_INPROGRESS = WSAEINPROGRESS; constexpr int SOCKET_ERROR_MSGSIZE = WSAEMSGSIZE; - -int getLastSocketError() { return WSAGetLastError(); } #else -constexpr int SOCKET_ERROR_AGAIN = EAGAIN; constexpr int SOCKET_ERROR_INVAL = EINVAL; -constexpr int SOCKET_ERROR_INPROGRESS = EINPROGRESS; constexpr int SOCKET_ERROR_MSGSIZE = EMSGSIZE; - -int getLastSocketError() { return errno; } #endif +// Use constants from io_handle.h for common error codes: +// - SOCKET_ERROR_AGAIN +// - SOCKET_ERROR_INPROGRESS +// - SOCKET_ERROR_WOULDBLOCK +// - getLastSocketError() + // Maximum number of iovecs for vectored I/O #ifdef IOV_MAX constexpr size_t MAX_IOV = IOV_MAX; @@ -643,18 +641,39 @@ IoResult IoSocketHandleImpl::accept() { IoResult IoSocketHandleImpl::connect( const Address::InstanceConstSharedPtr& address) { if (!isOpen()) { + std::cerr << "[DEBUG SOCKET] connect(): fd not open" << std::endl; return IoResult::error(EBADF); } + std::cerr << "[DEBUG SOCKET] connect(): fd=" << fd_ + << " addr=" << address->asString() << std::endl; + int result = ::connect(fd_, address->sockAddr(), address->sockAddrLen()); if (result == 0) { + // Immediate connection success (rare but can happen for local connections) + std::cerr << "[DEBUG SOCKET] connect(): fd=" << fd_ << " immediate success" + << std::endl; return IoResult::success(0); } else { int error = getLastSocketError(); - // EINPROGRESS is expected for non-blocking connect - if (error == SOCKET_ERROR_INPROGRESS) { - return IoResult::success(0); + std::cerr << "[DEBUG SOCKET] connect(): fd=" << fd_ << " result=" << result + << " error=" << error + << " (INPROGRESS=" << SOCKET_ERROR_INPROGRESS + << " AGAIN=" << SOCKET_ERROR_AGAIN << ")" << std::endl; + + // For non-blocking connect: + // - EINPROGRESS (Unix) or WSAEINPROGRESS (Windows): connection in progress + // - EWOULDBLOCK/WSAEWOULDBLOCK: also means connection in progress on some + // platforms Return error with the appropriate code so caller can wait for + // completion + if (error == SOCKET_ERROR_INPROGRESS || error == SOCKET_ERROR_AGAIN) { + // Return EINPROGRESS (normalized) so caller knows to wait for write event + std::cerr << "[DEBUG SOCKET] connect(): fd=" << fd_ + << " connection in progress, returning INPROGRESS" << std::endl; + return IoResult::error(SOCKET_ERROR_INPROGRESS); } + std::cerr << "[DEBUG SOCKET] connect(): fd=" << fd_ + << " connect failed with error=" << error << std::endl; return IoResult::error(error); } } diff --git a/src/network/socket_interface_impl.cc b/src/network/socket_interface_impl.cc index 476d19be..8ee2dffb 100644 --- a/src/network/socket_interface_impl.cc +++ b/src/network/socket_interface_impl.cc @@ -38,14 +38,12 @@ namespace network { namespace { -// Platform-specific error codes +// Additional platform-specific error codes not in io_handle.h +// Common error codes (SOCKET_ERROR_AGAIN, SOCKET_ERROR_INPROGRESS, +// SOCKET_ERROR_WOULDBLOCK, getLastSocketError) are defined in io_handle.h #ifdef _WIN32 -constexpr int SOCKET_ERROR_AGAIN = WSAEWOULDBLOCK; constexpr int SOCKET_ERROR_INVAL = WSAEINVAL; constexpr int SOCKET_ERROR_AFNOSUPPORT = WSAEAFNOSUPPORT; -constexpr int SOCKET_ERROR_INPROGRESS = WSAEINPROGRESS; - -int getLastSocketError() { return WSAGetLastError(); } class WinsockInitializer { public: @@ -64,12 +62,8 @@ class WinsockInitializer { static WinsockInitializer winsock_init; #else -constexpr int SOCKET_ERROR_AGAIN = EAGAIN; constexpr int SOCKET_ERROR_INVAL = EINVAL; constexpr int SOCKET_ERROR_AFNOSUPPORT = EAFNOSUPPORT; -constexpr int SOCKET_ERROR_INPROGRESS = EINPROGRESS; - -int getLastSocketError() { return errno; } #endif // Convert address type and IP version to socket domain diff --git a/src/network/tcp_server_listener_impl.cc b/src/network/tcp_server_listener_impl.cc index 2a88cbdd..ae80b5e8 100644 --- a/src/network/tcp_server_listener_impl.cc +++ b/src/network/tcp_server_listener_impl.cc @@ -38,19 +38,15 @@ std::atomic TcpActiveListener::next_listener_tag_{1}; namespace { -// Platform-specific socket error codes and helper +// Additional platform-specific socket error codes not in io_handle.h +// Common error codes (SOCKET_ERROR_AGAIN, getLastSocketError) are in +// io_handle.h #ifdef _WIN32 -constexpr int SOCKET_ERROR_AGAIN = WSAEWOULDBLOCK; constexpr int SOCKET_ERROR_MFILE = WSAEMFILE; constexpr int SOCKET_ERROR_NOFILE = WSAENOBUFS; // Closest to ENFILE on Windows - -int getLastSocketError() { return WSAGetLastError(); } #else -constexpr int SOCKET_ERROR_AGAIN = EAGAIN; constexpr int SOCKET_ERROR_MFILE = EMFILE; constexpr int SOCKET_ERROR_NOFILE = ENFILE; - -int getLastSocketError() { return errno; } #endif class NullProtocolCallbacks : public McpProtocolCallbacks { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 35da83fc..21dad713 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -836,7 +836,7 @@ if(BUILD_C_API) ) if(NGHTTP2_FOUND) - target_link_libraries(test_chain_from_json nghttp2) + target_link_libraries(test_chain_from_json ${NGHTTP2_LIBRARIES}) endif() if(LLHTTP_FOUND) target_link_libraries(test_chain_from_json llhttp) @@ -853,7 +853,7 @@ if(BUILD_C_API) ) if(NGHTTP2_FOUND) - target_link_libraries(test_unified_chain_handles nghttp2) + target_link_libraries(test_unified_chain_handles ${NGHTTP2_LIBRARIES}) endif() if(LLHTTP_FOUND) target_link_libraries(test_unified_chain_handles llhttp) @@ -870,7 +870,7 @@ if(BUILD_C_API) ) if(NGHTTP2_FOUND) - target_link_libraries(test_filter_only_api nghttp2) + target_link_libraries(test_filter_only_api ${NGHTTP2_LIBRARIES}) endif() if(LLHTTP_FOUND) target_link_libraries(test_filter_only_api llhttp) @@ -888,7 +888,7 @@ if(BUILD_C_API) ) if(NGHTTP2_FOUND) - target_link_libraries(test_filter_only_api_simple nghttp2) + target_link_libraries(test_filter_only_api_simple ${NGHTTP2_LIBRARIES}) endif() if(LLHTTP_FOUND) target_link_libraries(test_filter_only_api_simple llhttp) @@ -906,7 +906,7 @@ if(BUILD_C_API) ) if(NGHTTP2_FOUND) - target_link_libraries(test_filter_registration_simple nghttp2) + target_link_libraries(test_filter_registration_simple ${NGHTTP2_LIBRARIES}) endif() if(LLHTTP_FOUND) target_link_libraries(test_filter_registration_simple llhttp)