Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 77 additions & 22 deletions examples/event_loop_example.cc
Original file line number Diff line number Diff line change
@@ -1,21 +1,54 @@
#include <atomic>
#include <chrono>
#include <csignal>
#include <fcntl.h>
#include <iostream>
#include <thread>

// Platform-specific includes - must come before mcp headers
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#ifndef _SOCKLEN_T_DEFINED
#define _SOCKLEN_T_DEFINED
typedef int socklen_t;
#endif
#else
#include <fcntl.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif

#include "mcp/event/event_loop.h"
#include "mcp/event/libevent_dispatcher.h"

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
*/
Expand All @@ -25,31 +58,41 @@ 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<const char*>(&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;
addr.sin_family = AF_INET;
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;
}
Expand All @@ -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;
};
Expand All @@ -95,18 +141,18 @@ 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";
break;
}

// 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";
Expand All @@ -130,24 +176,28 @@ class EchoServer {
if (events & static_cast<uint32_t>(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);
} else if (n == 0) {
// 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;
}
}
Expand All @@ -156,17 +206,22 @@ class EchoServer {
if (events & static_cast<uint32_t>(FileReadyType::Write) &&
!conn->buffer.empty()) {
while (!conn->buffer.empty()) {
#ifdef _WIN32
int n = send(conn->fd, conn->buffer.data(),
static_cast<int>(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;
}
}
Expand All @@ -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<int, std::shared_ptr<Connection>> connections_;
std::unordered_map<os_fd_t, std::shared_ptr<Connection>> connections_;
};

int main(int argc, char* argv[]) {
Expand Down
15 changes: 6 additions & 9 deletions examples/mcp/mcp_config_example_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

// Core MCP includes
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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_) {
Expand All @@ -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;
};

Expand Down
Loading