// ReclassMcpBridge: Bridges stdin/stdout to QLocalSocket for MCP transport. // Claude Desktop spawns this process; it connects to the ReclassMcpBridge named pipe // inside the running Reclass application. // // stdin (from Claude) → QLocalSocket → McpBridge (in Reclass) // stdout (to Claude) ← QLocalSocket ← McpBridge (in Reclass) #include #include #include #include #include #ifdef _WIN32 #include #include #include #endif int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); #ifdef _WIN32 // Ensure stdin/stdout are in binary mode on Windows _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); #endif auto* socket = new QLocalSocket(&app); QByteArray readBuf; // Socket → stdout: forward lines from Reclass to Claude Desktop QObject::connect(socket, &QLocalSocket::readyRead, [&]() { readBuf.append(socket->readAll()); while (true) { int idx = readBuf.indexOf('\n'); if (idx < 0) break; QByteArray line = readBuf.left(idx + 1); // include newline readBuf.remove(0, idx + 1); fwrite(line.constData(), 1, line.size(), stdout); fflush(stdout); } }); QObject::connect(socket, &QLocalSocket::disconnected, [&]() { fprintf(stderr, "[ReclassMcpBridge] Disconnected from server\n"); app.quit(); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QObject::connect(socket, &QLocalSocket::errorOccurred, [&](QLocalSocket::LocalSocketError err) { #else QObject::connect(socket, QOverload::of(&QLocalSocket::error), [&](QLocalSocket::LocalSocketError err) { #endif fprintf(stderr, "[ReclassMcpBridge] Socket error %d: %s\n", (int)err, socket->errorString().toUtf8().constData()); app.quit(); }); // Connect to the named pipe socket->connectToServer("ReclassMcpBridge"); if (!socket->waitForConnected(5000)) { fprintf(stderr, "[ReclassMcpBridge] Failed to connect to ReclassMcpBridge pipe: %s\n", socket->errorString().toUtf8().constData()); return 1; } fprintf(stderr, "[ReclassMcpBridge] Connected to ReclassMcpBridge\n"); // Stdin → socket: poll stdin with a timer (stdin isn't a socket on Windows) QByteArray stdinBuf; auto* stdinTimer = new QTimer(&app); stdinTimer->setInterval(10); QObject::connect(stdinTimer, &QTimer::timeout, [&]() { #ifdef _WIN32 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); DWORD avail = 0; if (!PeekNamedPipe(hStdin, nullptr, 0, nullptr, &avail, nullptr)) { // stdin closed (pipe broken) app.quit(); return; } if (avail == 0) return; char buf[4096]; DWORD bytesRead = 0; DWORD toRead = qMin(avail, (DWORD)sizeof(buf)); if (!ReadFile(hStdin, buf, toRead, &bytesRead, nullptr) || bytesRead == 0) { app.quit(); return; } stdinBuf.append(buf, (int)bytesRead); #else // On Unix, we could use QSocketNotifier, but timer works fine too char buf[4096]; fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); struct timeval tv = {0, 0}; if (select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv) <= 0) return; ssize_t n = ::read(STDIN_FILENO, buf, sizeof(buf)); if (n <= 0) { app.quit(); return; } stdinBuf.append(buf, (int)n); #endif // Forward complete lines to socket while (true) { int idx = stdinBuf.indexOf('\n'); if (idx < 0) break; QByteArray line = stdinBuf.left(idx + 1); stdinBuf.remove(0, idx + 1); socket->write(line); socket->flush(); } }); stdinTimer->start(); return app.exec(); }