Entropic
2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
mcp_bridge.cpp
Go to the documentation of this file.
1
// SPDX-License-Identifier: Apache-2.0
72
#include <poll.h>
73
#include <sys/socket.h>
74
#include <sys/un.h>
75
#include <unistd.h>
76
77
#include <cerrno>
78
#include <cstdio>
79
#include <cstring>
80
#include <filesystem>
81
#include <string>
82
83
namespace
entropic
{
84
// Forward-declared from include/entropic/mcp/mcp_json_discovery.h; pulled in
85
// via libentropic-mcp at link time so the CLI does not have to include the
86
// mcp internal header (which transitively pulls nlohmann/json_fwd).
87
std::filesystem::path
compute_socket_path
(
88
const
std::filesystem::path& project_dir);
89
}
// namespace entropic
90
91
namespace
entropic::cli {
92
93
namespace
{
94
95
constexpr
size_t
kRelayBufSize
= 8192;
96
106
std::string
parse_flag
(
int
argc
,
char
*
argv
[],
const
char
*
flag
)
107
{
108
for
(
int
i
= 1;
i
<
argc
- 1; ++
i
) {
109
if
(std::strcmp(
argv
[
i
],
flag
) == 0) {
110
return
argv
[
i
+ 1];
111
}
112
}
113
return
{};
114
}
115
123
int
connect_unix_socket
(
const
std::string& path)
124
{
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
);
128
if
(
ok
) {
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
),
134
sizeof
(
addr
)) == 0;
135
if
(!
ok
) {
saved
=
errno
; }
136
}
137
if
(!
ok
) {
138
if
(fd >= 0) { ::close(fd); }
139
errno
=
saved
;
140
fd = -1;
141
}
142
return
fd;
143
}
144
161
int
emit_no_engine_error
(
162
const
std::filesystem::path&
requested
,
163
const
std::filesystem::path&
canonical
,
164
const
std::filesystem::path&
socket
,
165
const
char
*
why
)
166
{
167
std::fprintf(
stderr
,
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"
173
"\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"
177
"model itself.\n"
,
178
requested
.c_str(),
canonical
.c_str(),
179
socket
.c_str(),
why
?
why
:
"unknown"
);
180
return
1;
181
}
182
191
bool
pump_once
(
int
src_fd
,
int
dst_fd
)
192
{
193
char
buf
[
kRelayBufSize
];
194
ssize_t
n
=
::read
(
src_fd
,
buf
,
sizeof
(
buf
));
195
if
(
n
<= 0) {
return
false
; }
196
ssize_t
off
= 0;
197
while
(
off
<
n
) {
198
ssize_t
w
=
::write
(
dst_fd
,
buf
+
off
,
n
-
off
);
199
if
(
w
<= 0) {
return
false
; }
200
off
+=
w
;
201
}
202
return
true
;
203
}
204
210
void
service_revents
(
struct
pollfd
*
fds
,
int
sock_fd
,
211
bool
&
stdin_open
,
bool
&
sock_open
)
212
{
213
if
(
stdin_open
&& (
fds
[0].
revents
& (
POLLIN
|
POLLHUP
))) {
214
if
(!
pump_once
(
STDIN_FILENO
,
sock_fd
)) {
215
stdin_open
=
false
;
216
::shutdown(
sock_fd
,
SHUT_WR
);
217
}
218
}
219
if
(
sock_open
&& (
fds
[1].
revents
& (
POLLIN
|
POLLHUP
))) {
220
if
(!
pump_once
(
sock_fd
,
STDOUT_FILENO
)) {
221
sock_open
=
false
;
222
}
223
}
224
}
225
241
void
relay_loop
(
int
sock_fd
)
242
{
243
struct
pollfd
fds
[2] = {};
244
fds
[0].fd =
STDIN_FILENO
;
245
fds
[1].fd =
sock_fd
;
246
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
; }
254
if
(
rc
> 0) {
255
service_revents
(
fds
,
sock_fd
,
stdin_open
,
sock_open
);
256
}
257
}
258
}
259
260
}
// namespace
261
275
int
run_mcp_bridge
(
int
argc
,
char
*
argv
[])
276
{
277
// --socket PATH overrides discovery. Primary uses: deterministic
278
// tests, and users whose engine is configured with a non-default
279
// socket path. Absent the override, the bridge derives the path
280
// from the canonicalized project_dir hash — same scheme as the
281
// engine's ExternalBridge.
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
);
287
288
std::error_code
ec
;
289
auto
canonical
= std::filesystem::weakly_canonical(
requested_path
,
ec
);
290
if
(
ec
) {
canonical
=
requested_path
; }
291
292
std::filesystem::path socket_path =
explicit_socket
.empty()
293
?
entropic::compute_socket_path
(
canonical
)
294
: std::filesystem::path(
explicit_socket
);
295
296
int
fd =
connect_unix_socket
(socket_path.string());
297
if
(fd < 0) {
298
return
emit_no_engine_error
(
299
requested_path
,
canonical
, socket_path,
300
std::strerror(
errno
));
301
}
302
303
relay_loop
(fd);
304
::close(fd);
305
return
0;
306
}
307
308
}
// namespace entropic::cli
w
const ReadWindow & w
Requested window.
Definition
filesystem.cpp:605
entropic::cli::run_mcp_bridge
int run_mcp_bridge(int argc, char *argv[])
Entry point for the mcp-bridge subcommand.
Definition
mcp_bridge.cpp:275
entropic
Activate model on GPU (WARM → ACTIVE).
Definition
bundled_models.h:20
entropic::compute_socket_path
std::filesystem::path compute_socket_path(const std::filesystem::path &project_dir)
Compute project-unique Unix socket path for self-detection.
Definition
mcp_json_discovery.cpp:288
entropic::BudgetMode::off
@ off
Disabled (default) — no thinking-budget gating.
entropic::ToolResultKind::ok
@ ok
Tool dispatched, returned non-empty content.
entropic::OutsideRootAccess::optional
@ optional
optional: every escape asks the host's path approver
entropic::PathAccess::read
@ read
read_file, list_directory
entropic::PathAccess::write
@ write
write_file, edit_file
cli
mcp_bridge.cpp
Generated by
1.9.8