73#include <sys/socket.h>
88 const std::filesystem::path& project_dir);
91namespace entropic::cli {
95constexpr size_t kRelayBufSize = 8192;
106std::string parse_flag(
int argc,
char* argv[],
const char* flag)
108 for (
int i = 1; i < argc - 1; ++i) {
109 if (std::strcmp(argv[i], flag) == 0) {
123int connect_unix_socket(
const std::string& path)
125 int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
126 bool ok = fd >= 0 && path.size() <
sizeof(sockaddr_un::sun_path);
127 int saved =
ok ? 0 : (fd < 0 ? errno : ENAMETOOLONG);
129 struct sockaddr_un addr {};
130 addr.sun_family = AF_UNIX;
131 std::strncpy(addr.sun_path, path.c_str(),
132 sizeof(addr.sun_path) - 1);
133 ok = ::connect(fd,
reinterpret_cast<sockaddr*
>(&addr),
135 if (!
ok) { saved = errno; }
138 if (fd >= 0) { ::close(fd); }
161int emit_no_engine_error(
162 const std::filesystem::path& requested,
163 const std::filesystem::path& canonical,
164 const std::filesystem::path& socket,
168 "entropic mcp-bridge: no running engine.\n"
169 " project_dir (requested): %s\n"
170 " project_dir (canonical): %s\n"
171 " expected socket: %s\n"
172 " connect failed: %s\n"
174 "An engine must be running for this project. Start one via the\n"
175 "engine host that owns the model (TUI, consumer app, or headless\n"
176 "server). mcp-bridge is a relay only — it does not load the\n"
178 requested.c_str(), canonical.c_str(),
179 socket.c_str(), why ? why :
"unknown");
191bool pump_once(
int src_fd,
int dst_fd)
193 char buf[kRelayBufSize];
194 ssize_t n = ::read(src_fd, buf,
sizeof(buf));
195 if (n <= 0) {
return false; }
198 ssize_t w = ::write(dst_fd, buf + off, n - off);
199 if (w <= 0) {
return false; }
210void service_revents(
struct pollfd* fds,
int sock_fd,
211 bool& stdin_open,
bool& sock_open)
213 if (stdin_open && (fds[0].revents & (POLLIN | POLLHUP))) {
214 if (!pump_once(STDIN_FILENO, sock_fd)) {
216 ::shutdown(sock_fd, SHUT_WR);
219 if (sock_open && (fds[1].revents & (POLLIN | POLLHUP))) {
220 if (!pump_once(sock_fd, STDOUT_FILENO)) {
241void relay_loop(
int sock_fd)
243 struct pollfd fds[2] = {};
244 fds[0].fd = STDIN_FILENO;
247 bool stdin_open =
true;
248 bool sock_open =
true;
249 while (stdin_open || sock_open) {
250 fds[0].events = stdin_open ? POLLIN : 0;
251 fds[1].events = sock_open ? POLLIN : 0;
252 int rc = ::poll(fds, 2, -1);
253 if (rc < 0 && errno != EINTR) {
break; }
255 service_revents(fds, sock_fd, stdin_open, sock_open);
282 std::string explicit_socket = parse_flag(argc, argv,
"--socket");
283 std::string requested = parse_flag(argc, argv,
"--project-dir");
284 std::filesystem::path requested_path = requested.empty()
285 ? std::filesystem::current_path()
286 : std::filesystem::path(requested);
289 auto canonical = std::filesystem::weakly_canonical(requested_path, ec);
290 if (ec) { canonical = requested_path; }
292 std::filesystem::path socket_path = explicit_socket.empty()
294 : std::filesystem::path(explicit_socket);
296 int fd = connect_unix_socket(socket_path.string());
298 return emit_no_engine_error(
299 requested_path, canonical, socket_path,
300 std::strerror(errno));
int run_mcp_bridge(int argc, char *argv[])
Entry point for the mcp-bridge subcommand.
Activate model on GPU (WARM → ACTIVE).
std::filesystem::path compute_socket_path(const std::filesystem::path &project_dir)
Compute project-unique Unix socket path for self-detection.
@ ok
Tool dispatched, returned non-empty content.