MCPcopy Create free account
hub / github.com/agentforce314/clawcodex / DirectConnectServer

Class DirectConnectServer

src/server/server.py:161–490  ·  view source on GitHub ↗

Direct Connect server — HTTP ``/sessions`` + WS ``/ws/ ``. Lifecycle: srv = DirectConnectServer(config, manager, spawn_agent) await srv.start() await srv.serve_forever() # or use srv.stop() to shut down await srv.stop() Two listeners: HTTP on ``config.p

Source from the content-addressed store, hash-verified

159
160@dataclass
161class DirectConnectServer:
162 """Direct Connect server — HTTP ``/sessions`` + WS ``/ws/<sid>``.
163
164 Lifecycle:
165 srv = DirectConnectServer(config, manager, spawn_agent)
166 await srv.start()
167 await srv.serve_forever() # or use srv.stop() to shut down
168 await srv.stop()
169
170 Two listeners: HTTP on ``config.port`` (or ephemeral) and WS on a
171 separate ephemeral port. ``ws_url`` in the create-session response
172 points at the WS port. Auth is per-session: ``POST /sessions``
173 returns ``auth_token``; the WS upgrade requires it via
174 ``Authorization: Bearer <token>`` or ``?token=<token>``.
175 """
176
177 config: ServerConfig
178 manager: SessionManager
179 spawn_agent: SpawnAgent
180 _http_server: asyncio.AbstractServer | None = None
181 _ws_server: asyncio.Server | None = None
182 _session_tokens: dict[str, str] = field(default_factory=dict)
183 _ws_port: int | None = None
184
185 # ─── Public lifecycle ────────────────────────────────────────────
186
187 async def start(self) -> None:
188 """Bind both listeners; ``serve_forever`` to actually run."""
189 # WS listener first so we know its port for the ``ws_url`` we
190 # hand out from the HTTP route.
191 self._ws_server = await ws_serve(
192 self._handle_ws_connection,
193 host=self.config.host or '127.0.0.1',
194 port=0, # ephemeral
195 )
196 ws_sockets = list(self._ws_server.sockets or [])
197 if not ws_sockets:
198 raise RuntimeError('DirectConnectServer: WS listener has no socket')
199 self._ws_port = ws_sockets[0].getsockname()[1]
200
201 # HTTP listener.
202 if self.config.unix:
203 self._http_server = await asyncio.start_unix_server(
204 self._handle_http_connection,
205 path=self.config.unix,
206 )
207 else:
208 self._http_server = await asyncio.start_server(
209 self._handle_http_connection,
210 host=self.config.host or '127.0.0.1',
211 port=self.config.port,
212 )
213
214 async def serve_forever(self) -> None:
215 if self._http_server is None or self._ws_server is None:
216 raise RuntimeError('start() must be called before serve_forever()')
217 # Both servers run concurrently; cancellation of either tears
218 # down both atomically via the gather.

Calls

no outgoing calls