6
6
7
7
AGI_SOCK=/tmp/agi.sock go run agi_sshd.go
8
8
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
10
10
11
11
12
12
gh auth refresh -h github.com -s admin:public_key
@@ -3323,6 +3323,18 @@ def make_argparse_parser(argv=None):
3323
3323
default = None ,
3324
3324
type = str ,
3325
3325
)
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
+ )
3326
3338
parser .add_argument (
3327
3339
"--agi-name" ,
3328
3340
dest = "agi_name" ,
@@ -3650,6 +3662,68 @@ class AGIThreadNotFoundError(Exception):
3650
3662
pass
3651
3663
3652
3664
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
+
3653
3727
async def agent_openai (
3654
3728
tg : asyncio .TaskGroup ,
3655
3729
async_exit_stack : contextlib .AsyncExitStack ,
@@ -3660,6 +3734,7 @@ async def agent_openai(
3660
3734
action_stream_insert : Callable [[Any ], Awaitable [Any ]],
3661
3735
waiting_event_stream_insert : Callable [[Any ], Awaitable [Any ]],
3662
3736
openai_api_key : str ,
3737
+ mcp_reverse_proxy_socket_path : str ,
3663
3738
* ,
3664
3739
openai_base_url : Optional [str ] = None ,
3665
3740
):
@@ -3678,12 +3753,38 @@ async def agent_openai(
3678
3753
# params={"command": "uvx", "args": ["mcp-server-git"]},
3679
3754
# )
3680
3755
# )
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" )
3687
3788
3688
3789
agents = {}
3689
3790
threads = {}
@@ -3744,11 +3845,19 @@ async def agent_openai(
3744
3845
3745
3846
shell: interpreter {0}
3746
3847
3848
+ By default you should use `shell: bash -xe {0}`.
3849
+
3747
3850
Assume {0} is a temporary file containing the
3748
3851
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.
3749
3856
""" .strip (),
3750
3857
),
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,
3752
3861
output_type = PolicyEngineWorkflow ,
3753
3862
)
3754
3863
@@ -4311,9 +4420,11 @@ async def main(
4311
4420
input_socket_path : Optional [str ] = None ,
4312
4421
text_output_socket_path : Optional [str ] = None ,
4313
4422
ndjson_output_socket_path : Optional [str ] = None ,
4423
+ mcp_reverse_proxy_socket_path : Optional [str ] = None ,
4314
4424
client_side_input_socket_path : Optional [str ] = None ,
4315
4425
client_side_text_output_socket_path : Optional [str ] = None ,
4316
4426
client_side_ndjson_output_socket_path : Optional [str ] = None ,
4427
+ client_side_mcp_reverse_proxy_socket_path : Optional [str ] = None ,
4317
4428
):
4318
4429
if log is not None :
4319
4430
# logging.basicConfig(level=log)
@@ -4351,7 +4462,16 @@ async def main(
4351
4462
write_ndjson_output = write_unix_socket (ndjson_output_socket_path )
4352
4463
await write_ndjson_output .asend (None )
4353
4464
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
+
4354
4473
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 )
4355
4475
# Raw Input Action Stream
4356
4476
unvalidated_user_input_action_stream = pdb_action_stream (
4357
4477
tg ,
@@ -4401,6 +4521,7 @@ async def user_input_action_stream_queue_iterator(queue):
4401
4521
action_stream_insert ,
4402
4522
waiting_event_stream_insert ,
4403
4523
openai_api_key ,
4524
+ mcp_reverse_proxy_socket_path ,
4404
4525
openai_base_url = openai_base_url ,
4405
4526
)
4406
4527
else :
@@ -4777,9 +4898,11 @@ async def tmux_test(
4777
4898
input_socket_path : Optional [str ] = None ,
4778
4899
text_output_socket_path : Optional [str ] = None ,
4779
4900
ndjson_output_socket_path : Optional [str ] = None ,
4901
+ mcp_reverse_proxy_socket_path : Optional [str ] = None ,
4780
4902
client_side_input_socket_path : Optional [str ] = None ,
4781
4903
client_side_text_output_socket_path : Optional [str ] = None ,
4782
4904
client_side_ndjson_output_socket_path : Optional [str ] = None ,
4905
+ client_side_mcp_reverse_proxy_socket_path : Optional [str ] = None ,
4783
4906
** kwargs
4784
4907
):
4785
4908
pane = None
@@ -5046,6 +5169,49 @@ async def tmux_test(
5046
5169
pane .send_keys (f'socat UNIX-LISTEN:${ agi_name .upper ()} _INPUT_SOCK,fork EXEC:"/usr/bin/tail -F ${ agi_name .upper ()} _INPUT" &' , enter = True )
5047
5170
pane .send_keys (f'ls -lAF ${ agi_name .upper ()} _INPUT' , enter = True )
5048
5171
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
+
5049
5215
pane .send_keys (f'set +x' , enter = True )
5050
5216
5051
5217
await main (
@@ -5055,8 +5221,10 @@ async def tmux_test(
5055
5221
client_side_input_socket_path = client_side_input_socket_path ,
5056
5222
text_output_socket_path = text_output_socket_path ,
5057
5223
ndjson_output_socket_path = ndjson_output_socket_path ,
5224
+ mcp_reverse_proxy_socket_path = mcp_reverse_proxy_socket_path ,
5058
5225
client_side_text_output_socket_path = client_side_text_output_socket_path ,
5059
5226
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 ,
5060
5228
** kwargs
5061
5229
)
5062
5230
finally :
@@ -5091,7 +5259,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
5091
5259
)
5092
5260
5093
5261
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 ):
5095
5263
cmd = [
5096
5264
sys .executable ,
5097
5265
"-u" ,
@@ -5110,6 +5278,10 @@ def run_tmux_attach(socket_path, input_socket_path, client_side_input_socket_pat
5110
5278
ndjson_output_socket_path ,
5111
5279
"--client-side-ndjson-output-socket-path" ,
5112
5280
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 ,
5113
5285
"--agi-name" ,
5114
5286
# TODO Something secure here, scitt URN and lookup for PS1?
5115
5287
f"alice{ str (uuid .uuid4 ()).split ('-' )[4 ]} " ,
@@ -5142,9 +5314,11 @@ class RequestConnectTMUX(BaseModel):
5142
5314
socket_input_path : str = Field (alias = "input.sock" )
5143
5315
socket_text_output_path : str = Field (alias = "text-output.sock" )
5144
5316
socket_ndjson_output_path : str = Field (alias = "ndjson-output.sock" )
5317
+ socket_mcp_reverse_proxy_path : str = Field (alias = "mcp-reverse-proxy.sock" )
5145
5318
socket_client_side_input_path : str = Field (alias = "client-side-input.sock" )
5146
5319
socket_client_side_text_output_path : str = Field (alias = "client-side-text-output.sock" )
5147
5320
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" )
5148
5322
5149
5323
5150
5324
@app .post ("/connect/tmux" )
@@ -5158,6 +5332,8 @@ async def connect(request_connect_tmux: RequestConnectTMUX, background_tasks: Ba
5158
5332
request_connect_tmux .socket_client_side_text_output_path ,
5159
5333
request_connect_tmux .socket_ndjson_output_path ,
5160
5334
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 ,
5161
5337
)
5162
5338
return {
5163
5339
"connected" : True ,
0 commit comments