char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
be80d8a
main
1import { assert , assertEquals , assertRejects } from "@std/assert" ; 2import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context" ; 3import { testExtension } from "@berry/api/testing.ts" ; 4import { tempDir , until } from "@berry/util" ; 5import mcp from "../../extensions/mcp/mod.ts" ; 6import { BOOTSTRAP , Connection , RemoteEnvironment } from "./connection.ts" ; 7 8Deno . test ( "MCP servers use the worker's environment and remote force-stop leaves the worker usable" , async () => { 9await usingdir = await tempDir ({ prefix :"remote-mcp-" }); 10await Deno . writeTextFile ( 11dir . path + "/server.ts" , 12await Deno . readTextFile ( new URL ( "../../extensions/mcp/test-server.ts" , import . meta. url )), 13); 14await Deno . writeTextFile ( 15dir . path + "/mcp.json" , 16JSON . stringify ({ 17mcpServers :{ 18probe :{ 19command :Deno . execPath (), 20args :[ "run" , "-A" , "server.ts" , "stubborn" ], 21env :{ TOKEN :"${BERRY_MACHINE}" }, 22}, 23}, 24}), 25); 26const connection = await Connection . open ( "mcp-test" , [ 27"env" , 28"BERRY_MACHINE=mcp-test" , 29Deno . execPath (), 30"eval" , 31"--quiet" , 32BOOTSTRAP , 33]); 34const env = new RemoteEnvironment ( "mcp-test" , connection , dir . path ); 35const ext = testExtension ( mcp , { env}); 36try { 37await ext . configure ({ configFile :"mcp.json" }); 38const result = await ext . call ( "mcp_call" , { server :"probe" , tool :"echo" }) as { 39structuredContent :{ token :string ; cwd :string }; 40}; 41assertEquals ( result . structuredContent . token , "mcp-test" ); 42assertEquals ( result . structuredContent . cwd , dir . path ); 43await ext . dispose (); 44assertEquals (( await env . readTextFile ( "mcp.json" , context )). ok , true ); 45 46const child = await env . spawn ([ "cat" ], {}, context ); 47await child . stdout . cancel (); 48const writer = child . stdin . getWriter (); 49await writer . write ( new TextEncoder (). encode ( "discarded output" )); 50await writer . close (); 51assertEquals ( await child . exited , 0 ); 52assertEquals (( await env . fileInfo ( "server.ts" , context )). ok , true ); 53 54const abandoned = await env . spawn ([ "cat" ], {}, context ); 55const pending = assertRejects (() => new Response ( abandoned . stdout ). text (), Error , "lost" ); 56await connection . close (); 57await pending ; 58} finally { 59await ext . dispose (); 60await connection . close (); 61} 62}); 63 64Deno . test ( "HTTP MCP calls reuse a live worker but the first call after worker replacement opens a fresh session" , async () => { 65await usingdir = await tempDir ({ prefix :"remote-http-mcp-" }); 66let initialized = 0 ; 67let called = 0 ; 68let session = "" ; 69const server = Deno . serve ({ hostname :"127.0.0.1" , port :0 , onListen () {} }, async ( request ) => { 70if ( request . method === "GET" ) return new Response ( null , { status :405 }); 71if ( request . method === "DELETE" ) return new Response ( null , { status :204 }); 72const message = await request . json (); 73if ( message . id === undefined ) return new Response ( null , { status :202 }); 74if ( message . method !== "initialize" && request . headers . get ( "Mcp-Session-Id" ) !== session ) { 75return new Response ( null , { status :404 }); 76} 77const result = message . method === "initialize" 78 ?{ 79protocolVersion :message . params . protocolVersion , 80capabilities :{ tools :{} }, 81serverInfo :{ name :"http" , version :"1" }, 82} 83 :{ content :[], structuredContent :{ session, called :++ called } }; 84if ( message . method === "initialize" ) session = String ( ++ initialized ); 85return Response . json ({ jsonrpc :"2.0" , id :message . id , result}, { 86headers :{ "Mcp-Session-Id" :session }, 87}); 88}); 89await Deno . writeTextFile ( 90dir . path + "/mcp.json" , 91JSON . stringify ({ 92mcpServers :{ http :{ url :`http://127.0.0.1: ${ server . addr . port } /mcp` } }, 93}), 94); 95const open = () => Connection . open ( "http" , [ Deno . execPath (), "eval" , "--quiet" , BOOTSTRAP ]); 96const first = await open (); 97let second :Connection | undefined ; 98const ext = testExtension ( mcp , { env :new RemoteEnvironment ( "http" , first , dir . path ) }); 99const call = ( env ?:RemoteEnvironment ) => 100ext . call ( "mcp_call" , { server :"http" , tool :"echo" }, { env}); 101try { 102await ext . configure ({ configFile :"mcp.json" }); 103await call (); 104await call ( new RemoteEnvironment ( "http" , first , dir . path )); 105assertEquals ( initialized , 1 ); 106await first . close (); 107assert ( first . lifetime . aborted ); 108second = await open (); 109const result = await call ( new RemoteEnvironment ( "http" , second , dir . path )) as { 110structuredContent :{ session :string ; called :number }; 111}; 112assertEquals ( result . structuredContent , { session :"2" , called :3 }); 113assertEquals ( initialized , 2 ); 114assertEquals ( called , 3 ); 115} finally { 116await ext . dispose (); 117await first . close (); 118await second ?. close (); 119await server . shutdown (); 120} 121}); 122 123Deno . test ({ 124name :"a stalled remote worker cannot prevent fetch cancellation or HTTP MCP disposal" , 125ignore :Deno . build . os !== "linux" , 126async fn () { 127await usingdir = await tempDir ({ prefix :"stalled-mcp-" }); 128const server = Deno . serve ( 129{ hostname :"127.0.0.1" , port :0 , onListen () {} }, 130async ( request ) => { 131if ( request . method === "GET" ) return new Response ( null , { status :405 }); 132if ( request . method === "DELETE" ) return new Response ( null , { status :204 }); 133const message = await request . json (); 134if ( message . id === undefined ) return new Response ( null , { status :202 }); 135const result = message . method === "initialize" 136 ?{ 137protocolVersion :message . params . protocolVersion , 138capabilities :{ tools :{} }, 139serverInfo :{ name :"http" , version :"1" }, 140} 141 :{ content :[] }; 142return Response . json ({ jsonrpc :"2.0" , id :message . id , result}, { 143headers :{ "Mcp-Session-Id" :"session" }, 144}); 145}, 146); 147const url = `http://127.0.0.1: ${ server . addr . port } /mcp` ; 148await Deno . writeTextFile ( 149dir . path + "/mcp.json" , 150JSON . stringify ({ mcpServers :{ http :{ url} } }), 151); 152const connection = await Connection . open ( "stalled" , [ 153"sh" , 154"-c" , 155'echo $$ > "$1"; shift; exec "$@"' , 156"sh" , 157dir . path + "/pid" , 158Deno . execPath (), 159"eval" , 160"--quiet" , 161BOOTSTRAP , 162]); 163const pid = Number ( await Deno . readTextFile ( dir . path + "/pid" )); 164const env = new RemoteEnvironment ( "stalled" , connection , dir . path ); 165const ext = testExtension ( mcp , { env}); 166try { 167await ext . configure ({ configFile :"mcp.json" }); 168await ext . call ( "mcp_call" , { server :"http" , tool :"echo" }); 169Deno . kill ( pid , "SIGSTOP" ); 170const abort = new AbortController (); 171const reason = new Error ( "request canceled" ); 172let rejected :unknown ; 173const fetching = env . fetch ( url , { signal :abort . signal }). catch (( error :unknown ) => 174rejected = error 175); 176abort . abort ( reason ); 177await until (() => rejected !== undefined , { 178timeout :1000 , 179message :"fetch abort waited for a worker reply" , 180}); 181await fetching ; 182assertEquals ( rejected , reason ); 183let disposed = false ; 184const disposing = ext . dispose (). then (() => disposed = true ); 185await until (() => disposed , { 186timeout :3000 , 187message :"HTTP MCP disposal waited for a stalled worker" , 188}); 189await disposing ; 190assert ( ! connection . lifetime . aborted ); 191} finally { 192Deno . kill ( pid , "SIGCONT" ); 193await ext . dispose (); 194await connection . close (); 195await server . shutdown (); 196} 197}, 198});