66
77AGI_SOCK=/tmp/agi.sock go run agi_sshd.go
88
9- export INPUT_SOCK="$(mktemp -d)/input.sock"; export OUTPUT_SOCK="$(mktemp -d)/text-output.sock"; export NDJSON_OUTPUT_SOCK="$(mktemp -d)/ndjson-output.sock"; ssh -NnT -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -R /tmux.sock:$(echo $TMUX | sed -e 's/,.*//g') -R "${OUTPUT_SOCK}:${OUTPUT_SOCK}" -R "${NDJSON_OUTPUT_SOCK}:${NDJSON_OUTPUT_SOCK}" -R "${INPUT_SOCK}:${INPUT_SOCK}" user@localhost
9+ export INPUT_SOCK="$(mktemp -d)/input.sock"; export OUTPUT_SOCK="$(mktemp -d)/text-output.sock"; export NDJSON_OUTPUT_SOCK="$(mktemp -d)/ndjson-output.sock"; export MCP_REVERSE_PROXY_SOCK="$(mktemp -d)/mcp-reverse-proxy.sock"; ssh -NnT -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -R /tmux.sock:$(echo $TMUX | sed -e 's/,.*//g') -R "${OUTPUT_SOCK}:${OUTPUT_SOCK}" -R "${NDJSON_OUTPUT_SOCK}:${NDJSON_OUTPUT_SOCK}" -R "${MCP_REVERSE_PROXY_SOCK}:${MCP_REVERSE_PROXY_SOCK }" -R "${INPUT_SOCK}:${INPUT_SOCK}" user@localhost
1010
1111
1212gh auth refresh -h github.com -s admin:public_key
@@ -3323,6 +3323,18 @@ def make_argparse_parser(argv=None):
33233323 default = None ,
33243324 type = str ,
33253325 )
3326+ parser .add_argument (
3327+ "--mcp-reverse-proxy-socket-path" ,
3328+ dest = "mcp_reverse_proxy_socket_path" ,
3329+ default = None ,
3330+ type = str ,
3331+ )
3332+ parser .add_argument (
3333+ "--client-side-mcp-reverse-proxy-socket-path" ,
3334+ dest = "client_side_mcp_reverse_proxy_socket_path" ,
3335+ default = None ,
3336+ type = str ,
3337+ )
33263338 parser .add_argument (
33273339 "--agi-name" ,
33283340 dest = "agi_name" ,
@@ -3650,6 +3662,68 @@ class AGIThreadNotFoundError(Exception):
36503662 pass
36513663
36523664
3665+ import http
3666+ import httpx
3667+
3668+
3669+ class CaddyConfigLoadError (Exception ):
3670+ pass
3671+
3672+
3673+ async def caddy_config_update (mcp_reverse_proxy_socket_path , slug ):
3674+ transport = httpx .AsyncHTTPTransport (uds = mcp_reverse_proxy_socket_path )
3675+ async with httpx .AsyncClient (transport = transport ) as client :
3676+ # TODO Booooooooo timeout booooooo
3677+ time = 0
3678+ success = False
3679+ while not success and time < 5 :
3680+ try :
3681+ await client .get ("http://127.0.0.1/config/" )
3682+ success = True
3683+ except httpx .RemoteProtocolError :
3684+ await asyncio .sleep (0.1 )
3685+ time += 0.1
3686+ response = await client .get (
3687+ "http://127.0.0.1/config/" ,
3688+ )
3689+ caddy_config = response .json ()
3690+ exists = False
3691+ proxy_route_caddy_admin = None
3692+ for route in caddy_config ['apps' ]['http' ]['servers' ]['srv0' ]['routes' ]:
3693+ for match in route ['match' ]:
3694+ for host in match ['host' ]:
3695+ if host == '127.0.0.1' :
3696+ proxy_route_caddy_admin = route
3697+ elif host == slug :
3698+ exists = True
3699+ if exists :
3700+ return
3701+ proxy_route_new = json .loads (
3702+ json .dumps (
3703+ proxy_route_caddy_admin ,
3704+ ).replace (
3705+ "caddy-admin" , slug ,
3706+ ).replace (
3707+ "127.0.0.1" , slug ,
3708+ ),
3709+ )
3710+ caddy_config ['apps' ]['http' ]['servers' ]['srv0' ]['routes' ].append (
3711+ proxy_route_new ,
3712+ )
3713+ response = await client .post (
3714+ "http://127.0.0.1/load" ,
3715+ headers = {"Content-Type" : "application/json" },
3716+ content = json .dumps (caddy_config ),
3717+ )
3718+ if response .status_code != http .HTTPStatus .OK .value :
3719+ raise CaddyConfigLoadError (f"{ response .status_code } : { response .text } " )
3720+ response = await client .get (
3721+ "http://127.0.0.1/config/" ,
3722+ )
3723+ caddy_config = response .json ()
3724+ snoop .pp (caddy_config )
3725+
3726+
36533727async def agent_openai (
36543728 tg : asyncio .TaskGroup ,
36553729 async_exit_stack : contextlib .AsyncExitStack ,
@@ -3660,6 +3734,7 @@ async def agent_openai(
36603734 action_stream_insert : Callable [[Any ], Awaitable [Any ]],
36613735 waiting_event_stream_insert : Callable [[Any ], Awaitable [Any ]],
36623736 openai_api_key : str ,
3737+ mcp_reverse_proxy_socket_path : str ,
36633738 * ,
36643739 openai_base_url : Optional [str ] = None ,
36653740):
@@ -3678,12 +3753,38 @@ async def agent_openai(
36783753 # params={"command": "uvx", "args": ["mcp-server-git"]},
36793754 # )
36803755 # )
3681- # mcp_server_workflow = await async_exit_stack.enter_async_context(
3682- # openai_agents_mcp.MCPServerStdio(
3683- # # cache_tools_list=True, # Cache the tools list, for demonstration
3684- # params={"command": "python", "args": ["-c", "import mcp from agi; mcp.run(transport='stdio')"]},
3685- # )
3686- # )
3756+ # TODO Actions for adding more MCP servers, for N-1 in stack actions for
3757+ # starting them on the client, for N-1 in stack writing new ones and
3758+ # analysis and threat model trust boundry stuff.
3759+ # TODO These should be a class that's part of the action data
3760+ mcp_servers = [
3761+ {
3762+ "name" : "File Resource Server" ,
3763+ "slug" : "files" ,
3764+ },
3765+ # {
3766+ # "name": "/usr/bin/ss network utility",
3767+ # "slug": "bin-ss",
3768+ # },
3769+ ]
3770+ mcp_servers_workflow = []
3771+
3772+ transport = httpx .AsyncHTTPTransport (uds = mcp_reverse_proxy_socket_path )
3773+ for mcp_server in mcp_servers :
3774+ await caddy_config_update (mcp_reverse_proxy_socket_path , mcp_server ['slug' ])
3775+ mcp_servers_workflow .append (
3776+ await async_exit_stack .enter_async_context (
3777+ openai_agents_mcp .MCPServerSse (
3778+ name = mcp_server ["name" ],
3779+ params = {
3780+ "url" : f"http://{ mcp_server ['slug' ]} /sse" ,
3781+ "transport" : transport ,
3782+ },
3783+ ),
3784+ ),
3785+ )
3786+
3787+ snoop .pp ("MCP servers have been setup" )
36873788
36883789 agents = {}
36893790 threads = {}
@@ -3744,11 +3845,19 @@ async def agent_openai(
37443845
37453846 shell: interpreter {0}
37463847
3848+ By default you should use `shell: bash -xe {0}`.
3849+
37473850 Assume {0} is a temporary file containing the
37483851 contents the code you place in `run`.
3852+
3853+ You should not include use of actions/checkout
3854+ unless specifically requested to checkout the current
3855+ repo.
37493856 """ .strip (),
37503857 ),
3751- # mcp_servers=[mcp_server_workflow],
3858+ # TODO Dynamically auto discover applicable MCPs and add
3859+ # them to agents
3860+ # mcp_servers=mcp_servers_workflow,
37523861 output_type = PolicyEngineWorkflow ,
37533862 )
37543863
@@ -4311,9 +4420,11 @@ async def main(
43114420 input_socket_path : Optional [str ] = None ,
43124421 text_output_socket_path : Optional [str ] = None ,
43134422 ndjson_output_socket_path : Optional [str ] = None ,
4423+ mcp_reverse_proxy_socket_path : Optional [str ] = None ,
43144424 client_side_input_socket_path : Optional [str ] = None ,
43154425 client_side_text_output_socket_path : Optional [str ] = None ,
43164426 client_side_ndjson_output_socket_path : Optional [str ] = None ,
4427+ client_side_mcp_reverse_proxy_socket_path : Optional [str ] = None ,
43174428):
43184429 if log is not None :
43194430 # logging.basicConfig(level=log)
@@ -4351,7 +4462,16 @@ async def main(
43514462 write_ndjson_output = write_unix_socket (ndjson_output_socket_path )
43524463 await write_ndjson_output .asend (None )
43534464
4465+ async def error_handler_send_error_to_client (exc_type , exc_value , traceback ):
4466+ nonlocal write_ndjson_output
4467+ output_message = OutputMessage (
4468+ work_name = f"main.error.halted" ,
4469+ result = f"{ exc_type } { exc_value } { traceback } " ,
4470+ )
4471+ await write_ndjson_output .asend (f"{ output_message .model_dump_json ()} \n " .encode ())
4472+
43544473 async with kvstore , asyncio .TaskGroup () as tg , contextlib .AsyncExitStack () as async_exit_stack :
4474+ async_exit_stack .push_async_exit (error_handler_send_error_to_client )
43554475 # Raw Input Action Stream
43564476 unvalidated_user_input_action_stream = pdb_action_stream (
43574477 tg ,
@@ -4401,6 +4521,7 @@ async def user_input_action_stream_queue_iterator(queue):
44014521 action_stream_insert ,
44024522 waiting_event_stream_insert ,
44034523 openai_api_key ,
4524+ mcp_reverse_proxy_socket_path ,
44044525 openai_base_url = openai_base_url ,
44054526 )
44064527 else :
@@ -4777,9 +4898,11 @@ async def tmux_test(
47774898 input_socket_path : Optional [str ] = None ,
47784899 text_output_socket_path : Optional [str ] = None ,
47794900 ndjson_output_socket_path : Optional [str ] = None ,
4901+ mcp_reverse_proxy_socket_path : Optional [str ] = None ,
47804902 client_side_input_socket_path : Optional [str ] = None ,
47814903 client_side_text_output_socket_path : Optional [str ] = None ,
47824904 client_side_ndjson_output_socket_path : Optional [str ] = None ,
4905+ client_side_mcp_reverse_proxy_socket_path : Optional [str ] = None ,
47834906 ** kwargs
47844907):
47854908 pane = None
@@ -5046,6 +5169,49 @@ async def tmux_test(
50465169 pane .send_keys (f'socat UNIX-LISTEN:${ agi_name .upper ()} _INPUT_SOCK,fork EXEC:"/usr/bin/tail -F ${ agi_name .upper ()} _INPUT" &' , enter = True )
50475170 pane .send_keys (f'ls -lAF ${ agi_name .upper ()} _INPUT' , enter = True )
50485171
5172+ pane .send_keys (
5173+ 'cat > "${CALLER_PATH}/mcp_server_files.py" <<\' WRITE_OUT_SH_EOF\' '
5174+ + "\n "
5175+ + pathlib .Path (__file__ ).parent .joinpath ("mcp_server_files.py" ).read_text (),
5176+ enter = True ,
5177+ )
5178+ pane .send_keys ('' , enter = True )
5179+ pane .send_keys ('WRITE_OUT_SH_EOF' , enter = True )
5180+
5181+ pane .send_keys (
5182+ textwrap .dedent (
5183+ '''
5184+ if [ ! -f "${CALLER_PATH}/mcp_server_files.logs.txt" ]; then
5185+ python -u ${CALLER_PATH}/mcp_server_files.py --transport sse --uds ${CALLER_PATH}/files.sock 1>"${CALLER_PATH}/mcp_server_files.logs.txt" 2>&1 &
5186+ tail -F "${CALLER_PATH}/mcp_server_files.logs.txt" &
5187+ MCP_SERVER_FILES_PID=$!
5188+ fi
5189+ ''' .lstrip (),
5190+ ),
5191+ enter = True ,
5192+ )
5193+
5194+ pane .send_keys (
5195+ 'cat > "${CALLER_PATH}/Caddyfile" <<\' WRITE_OUT_SH_EOF\' '
5196+ + "\n "
5197+ + pathlib .Path (__file__ ).parent .joinpath ("Caddyfile" ).read_text ().replace ("{{CALLER_PATH}}" , tempdir ).replace ("{{CLIENT_SIDE_MCP_REVERSE_PROXY_SOCKET_PATH}}" , client_side_mcp_reverse_proxy_socket_path ),
5198+ enter = True ,
5199+ )
5200+ pane .send_keys ('' , enter = True )
5201+ pane .send_keys ('WRITE_OUT_SH_EOF' , enter = True )
5202+
5203+ # if [ ! -f "${CALLER_PATH}/caddy.logs.txt" ]; then
5204+ pane .send_keys (
5205+ textwrap .dedent (
5206+ '''
5207+ HOME=${CALLER_PATH} caddy run --config ${CALLER_PATH}/Caddyfile 1>"${CALLER_PATH}/caddy.logs.txt" 2>&1 &
5208+ tail -F "${CALLER_PATH}/caddy.logs.txt" &
5209+ CADDY_PID=$!
5210+ ''' .lstrip (),
5211+ ),
5212+ enter = True ,
5213+ )
5214+
50495215 pane .send_keys (f'set +x' , enter = True )
50505216
50515217 await main (
@@ -5055,8 +5221,10 @@ async def tmux_test(
50555221 client_side_input_socket_path = client_side_input_socket_path ,
50565222 text_output_socket_path = text_output_socket_path ,
50575223 ndjson_output_socket_path = ndjson_output_socket_path ,
5224+ mcp_reverse_proxy_socket_path = mcp_reverse_proxy_socket_path ,
50585225 client_side_text_output_socket_path = client_side_text_output_socket_path ,
50595226 client_side_ndjson_output_socket_path = client_side_ndjson_output_socket_path ,
5227+ client_side_mcp_reverse_proxy_socket_path = client_side_mcp_reverse_proxy_socket_path ,
50605228 ** kwargs
50615229 )
50625230 finally :
@@ -5091,7 +5259,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
50915259 )
50925260
50935261
5094- def run_tmux_attach (socket_path , input_socket_path , client_side_input_socket_path , text_output_socket_path , client_side_text_output_socket_path , ndjson_output_socket_path , client_side_ndjson_output_socket_path ):
5262+ def run_tmux_attach (socket_path , input_socket_path , client_side_input_socket_path , text_output_socket_path , client_side_text_output_socket_path , ndjson_output_socket_path , client_side_ndjson_output_socket_path , mcp_reverse_proxy_socket_path , client_side_mcp_reverse_proxy_socket_path ):
50955263 cmd = [
50965264 sys .executable ,
50975265 "-u" ,
@@ -5110,6 +5278,10 @@ def run_tmux_attach(socket_path, input_socket_path, client_side_input_socket_pat
51105278 ndjson_output_socket_path ,
51115279 "--client-side-ndjson-output-socket-path" ,
51125280 client_side_ndjson_output_socket_path ,
5281+ "--mcp-reverse-proxy-socket-path" ,
5282+ mcp_reverse_proxy_socket_path ,
5283+ "--client-side-mcp-reverse-proxy-socket-path" ,
5284+ client_side_mcp_reverse_proxy_socket_path ,
51135285 "--agi-name" ,
51145286 # TODO Something secure here, scitt URN and lookup for PS1?
51155287 f"alice{ str (uuid .uuid4 ()).split ('-' )[4 ]} " ,
@@ -5142,9 +5314,11 @@ class RequestConnectTMUX(BaseModel):
51425314 socket_input_path : str = Field (alias = "input.sock" )
51435315 socket_text_output_path : str = Field (alias = "text-output.sock" )
51445316 socket_ndjson_output_path : str = Field (alias = "ndjson-output.sock" )
5317+ socket_mcp_reverse_proxy_path : str = Field (alias = "mcp-reverse-proxy.sock" )
51455318 socket_client_side_input_path : str = Field (alias = "client-side-input.sock" )
51465319 socket_client_side_text_output_path : str = Field (alias = "client-side-text-output.sock" )
51475320 socket_client_side_ndjson_output_path : str = Field (alias = "client-side-ndjson-output.sock" )
5321+ socket_client_side_mcp_reverse_proxy_path : str = Field (alias = "client-side-mcp-reverse-proxy.sock" )
51485322
51495323
51505324@app .post ("/connect/tmux" )
@@ -5158,6 +5332,8 @@ async def connect(request_connect_tmux: RequestConnectTMUX, background_tasks: Ba
51585332 request_connect_tmux .socket_client_side_text_output_path ,
51595333 request_connect_tmux .socket_ndjson_output_path ,
51605334 request_connect_tmux .socket_client_side_ndjson_output_path ,
5335+ request_connect_tmux .socket_mcp_reverse_proxy_path ,
5336+ request_connect_tmux .socket_client_side_mcp_reverse_proxy_path ,
51615337 )
51625338 return {
51635339 "connected" : True ,
0 commit comments