char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
c3bbb3a
main
1import { 2assert , 3assertEquals , 4assertNotEquals , 5assertRejects , 6assertStringIncludes , 7} from "@std/assert" ; 8import { rootedEnvironment , testExtension } from "@berry/api/testing.ts" ; 9import type { Environment , JsonValue , Process } from "@berry/api" ; 10import { tempDir , until } from "@berry/util" ; 11import mcp from "./mod.ts" ; 12 13type Result = { 14content :{ type :string ; text :string }[]; 15isError :boolean ; 16structuredContent :{ 17pid :number ; 18count :number ; 19cwd :string ; 20token :string ; 21cancelled :number ; 22arguments :Record < string , JsonValue >; 23roots :{ uri :string }[]; 24}; 25}; 26 27async function within < T > ( promise :Promise < T >, ms = 5000 ) :Promise < T > { 28let timer :ReturnType < typeof setTimeout > | undefined ; 29try { 30return await Promise . race ([ 31promise , 32new Promise < never >(( _ , reject ) => 33timer = setTimeout (() => reject ( new Error ( "operation did not finish" )), ms ) 34), 35]); 36} finally { 37clearTimeout ( timer ); 38} 39} 40 41async function fixture () { 42const dir = await tempDir ({ prefix :"mcp-test-" }); 43await Deno . mkdir ( dir . path + "/project" ); 44await Deno . mkdir ( dir . path + "/other" ); 45await Deno . writeTextFile ( 46dir . path + "/server.ts" , 47await Deno . readTextFile ( new URL ( "./test-server.ts" , import . meta. url )), 48); 49const env = rootedEnvironment ( dir . path , { 50cwd :"/project" , 51variables :{ MCP_TOKEN :"remote-secret" }, 52}); 53const spawn = env . spawn ; 54const children :Process [] = []; 55env . spawn = async ( ...args ) => { 56const child = await spawn ( ...args ); 57children . push ( child ); 58return child ; 59}; 60const ext = testExtension ( mcp , { env}); 61await ext . configure ({ configFile :"/servers.json" }); 62const server = { 63command :Deno . execPath (), 64args :[ "run" , "-A" , "../server.ts" ], 65env :{ TOKEN :"${MCP_TOKEN}" }, 66}; 67const configure = ( servers :Record < string , unknown >) => 68Deno . writeTextFile ( dir . path + "/servers.json" , JSON . stringify ({ mcpServers :servers })); 69await configure ({ probe :server }); 70return { 71 ext, 72 env, 73 children, 74 configure, 75 server, 76root :dir . path , 77async [ Symbol . asyncDispose ]() { 78await ext . dispose (); 79await dir [ Symbol . asyncDispose ](); 80}, 81}; 82} 83 84Deno . test ( "MCP discovery is lazy, shares a connection, and calls preserve schemas, results, and progress" , async () => { 85await usingf = await fixture (); 86await f . ext . prompt (); 87assertEquals ( f . children . length , 0 ); 88const [ listed , described ] = await Promise . all ([ 89f . ext . call ( "mcp_list" , {}), 90f . ext . call ( "mcp_describe" , { server :"probe" , tool :"echo" }), 91]); 92assertEquals ( f . children . length , 1 ); 93assertEquals (( listed as { tools :{ name :string }[] }[])[ 0 ]. tools [ 0 ]. name , "echo" ); 94const description = described as { instructions :string ; tools :Record < string , JsonValue >[] }; 95assertStringIncludes ( description . instructions , "echo" ); 96assertEquals ( description . tools [ 0 ]. annotations , { readOnlyHint :true }); 97assert ( description . tools [ 0 ]. inputSchema ); 98assert ( description . tools [ 0 ]. outputSchema ); 99const progress :JsonValue [] = []; 100const result = await f . ext . call ( "mcp_call" , { 101server :"probe" , 102tool :"echo" , 103arguments :{ fail :true , query :"猫" }, 104}, { progress :( value ) => progress . push ( value ) }) as Result ; 105assertEquals ( result . content , [{ type :"text" , text :"café, 猫" }]); 106assertEquals ( result . isError , true ); 107assertEquals ( result . structuredContent . arguments , { fail :true , query :"猫" }); 108assertEquals ( result . structuredContent . cwd , f . root + "/project" ); 109assertEquals ( result . structuredContent . token , "remote-secret" ); 110assertStringIncludes ( result . structuredContent . roots [ 0 ]. uri , "/project" ); 111assertEquals (( progress [ 0 ] as { progress :number }). progress , 1 ); 112assertEquals ( f . ext . state , { configFile :"/servers.json" }); 113}); 114 115Deno . test ( "unavailable MCP servers say why, without leaking credentials or hiding the others" , async () => { 116await usingf = await fixture (); 117await f . configure ({ 118probe :f . server , 119broken :{ command :"berry-mcp-no-such-program" }, 120disabled :{ enabled :false , command :"${MISSING_TOKEN}" }, 121malformed :{ command :"deno" , args :"not-an-array" }, 122exits :{ 123command :Deno . execPath (), 124args :[ "eval" , "console.error('missing API_KEY'); Deno.exit(23)" ], 125}, 126unparsable :{ url :"http://[${MCP_TOKEN}]/mcp" }, 127userinfo :{ url :"http://user:${MCP_TOKEN}@localhost/mcp" }, 128}); 129const listed = await within ( f . ext . call ( "mcp_list" , {})) as { 130server :string ; 131status :string ; 132error ?:string ; 133}[]; 134assertEquals ( listed . map (({ server, status}) => [ server , status ]), [ 135[ "probe" , "connected" ], 136[ "broken" , "failed" ], 137[ "disabled" , "disabled" ], 138[ "malformed" , "failed" ], 139[ "exits" , "failed" ], 140[ "unparsable" , "failed" ], 141[ "userinfo" , "failed" ], 142]); 143assertEquals ( f . children . length , 2 ); 144const error = ( name :string ) => listed . find (({ server}) => server === name ) ! . error ! ; 145assertStringIncludes ( error ( "exits" ), "missing API_KEY" ); 146assertStringIncludes ( error ( "exits" ), "23" ); 147assertStringIncludes ( error ( "unparsable" ), "URL" ); 148assert ( ! JSON . stringify ( listed ). includes ( "remote-secret" )); 149await assertRejects ( 150() => f . ext . call ( "mcp_call" , { server :"disabled" , tool :"echo" }), 151Error , 152"disabled" , 153); 154await assertRejects ( 155() => f . ext . call ( "mcp_describe" , { server :"probe" , tool :"missing" }), 156Error , 157"No tool" , 158); 159await assertRejects ( 160() => f . ext . call ( "mcp_call" , { server :"missing" , tool :"echo" }), 161Error , 162"No MCP server" , 163); 164}); 165 166Deno . test ( "clients follow the machine, directory, and current server configuration" , async () => { 167await usingf = await fixture (); 168const call = ( env ?:Environment ) => 169f . ext . call ( "mcp_call" , { server :"probe" , tool :"echo" }, { env}) as Promise < Result >; 170const first = ( await call ()). structuredContent ; 171assertEquals (( await call ()). structuredContent . pid , first . pid ); 172f . env . cwd = "/other" ; 173const moved = ( await call ()). structuredContent ; 174assertNotEquals ( moved . pid , first . pid ); 175assertEquals ( moved . cwd , f . root + "/other" ); 176const another = { ...f . env , id :"another-machine" , variables :{ MCP_TOKEN :"another-secret" } }; 177const remote = ( await call ( another )). structuredContent ; 178assertNotEquals ( remote . pid , moved . pid ); 179assertEquals ( remote . token , "another-secret" ); 180await f . configure ({ probe :{ ...f . server , env :{ TOKEN :"changed" } } }); 181const changed = ( await call ()). structuredContent ; 182assertNotEquals ( changed . pid , moved . pid ); 183assertEquals ( changed . token , "changed" ); 184}); 185 186Deno . test ( "cancellation reaches the server without discarding the shared connection" , async () => { 187await usingf = await fixture (); 188const abort = new AbortController (); 189const waiting = f . ext . call ( "mcp_call" , { server :"probe" , tool :"wait" }, { 190signal :abort . signal , 191}); 192const rejected = assertRejects (() => waiting ); 193await until (() => Deno . stat ( f . root + "/project/waiting" ). then (() => true , () => false )); 194abort . abort (); 195await rejected ; 196const next = await f . ext . call ( "mcp_call" , { server :"probe" , tool :"echo" }) as Result ; 197assertEquals ( next . structuredContent . cancelled , 1 ); 198assertEquals ( f . children . length , 1 ); 199}); 200 201Deno . test ( "a crashed server reconnects for the next call, never by replaying the failed call" , async () => { 202await usingf = await fixture (); 203await assertRejects (() => f . ext . call ( "mcp_call" , { server :"probe" , tool :"crash" })); 204assertEquals ( f . children . length , 1 ); 205const next = await Promise . all ([ 206f . ext . call ( "mcp_call" , { server :"probe" , tool :"echo" }), 207f . ext . call ( "mcp_call" , { server :"probe" , tool :"echo" }), 208]) as Result []; 209assertEquals ( next . map (( result ) => result . structuredContent . count ). sort (), [ 1 , 2 ]); 210assertEquals ( f . children . length , 2 ); 211}); 212 213Deno . test ( "reload disposal force-stops a server that ignores stdin closure and SIGTERM" , async () => { 214await usingf = await fixture (); 215await f . configure ({ probe :{ ...f . server , args :[ ...f . server . args , "stubborn" ] } }); 216await f . ext . call ( "mcp_list" , {}); 217await f . ext . dispose (); 218assertEquals ( await f . children [ 0 ]. exited , null ); 219}); 220 221Deno . test ({ 222name :"reload disposal stops descendants even when their wrapper has already exited" , 223ignore :Deno . build . os === "windows" , 224async fn () { 225await usingf = await fixture (); 226await f . configure ({ probe :{ ...f . server , args :[ ...f . server . args , "descendant" ] } }); 227await f . ext . call ( "mcp_list" , {}); 228const pid = Number ( await Deno . readTextFile ( f . root + "/project/descendant" )); 229await f . ext . dispose (); 230await until (() => { 231try { 232Deno . kill ( pid , 0 ); 233return false ; 234} catch { 235return true ; 236} 237}); 238}, 239}); 240 241Deno . test ( "disposal during startup does not leave a late-spawned process running" , async () => { 242await usingf = await fixture (); 243const spawn = f . env . spawn ; 244const entered = Promise . withResolvers < void >(); 245const release = Promise . withResolvers < void >(); 246f . env . spawn = async ( ...args ) => { 247const child = await spawn ( ...args ); 248entered . resolve (); 249await release . promise ; 250return child ; 251}; 252const listing = f . ext . call ( "mcp_list" , {}); 253await entered . promise ; 254try { 255await within ( f . ext . dispose ()); 256await within ( listing ); 257} finally { 258release . resolve (); 259assertEquals ( await f . children [ 0 ]. exited , null ); 260} 261}); 262 263Deno . test ( "Streamable HTTP uses the environment's network and preserves MCP errors" , async () => { 264await usingf = await fixture (); 265const authorization :( string | null )[] = []; 266const methods :string [] = []; 267const server = Deno . serve ({ hostname :"127.0.0.1" , port :0 , onListen () {} }, async ( request ) => { 268authorization . push ( request . headers . get ( "authorization" )); 269methods . push ( request . method ); 270if ( request . method === "GET" ) return new Response ( null , { status :405 }); 271if ( request . method === "DELETE" ) return new Response ( null , { status :204 }); 272const message = await request . json (); 273if ( message . id === undefined ) return new Response ( null , { status :202 }); 274const result = message . method === "initialize" 275 ?{ 276protocolVersion :message . params . protocolVersion , 277capabilities :{ tools :{} }, 278serverInfo :{ name :"http" , version :"1" }, 279} 280 :message . method === "tools/list" 281 ?{ tools :[{ name :"echo" , inputSchema :{ type :"object" } }] } 282 :{ content :[], structuredContent :message . params . arguments . answer , isError :true }; 283return Response . json ({ jsonrpc :"2.0" , id :message . id , result}, { 284headers :{ "Mcp-Session-Id" :"test-session" }, 285}); 286}); 287try { 288let fetched = 0 ; 289const fetchHere = f . env . fetch ; 290f . env . fetch = ( input , init ) => { 291assertEquals ( String ( input ), "http://only-on-the-environment.invalid/mcp" ); 292fetched ++ ; 293return fetchHere ( `http://127.0.0.1: ${ server . addr . port } /mcp` , init ); 294}; 295await f . configure ({ 296remote :{ 297url :"http://only-on-the-environment.invalid/mcp" , 298headers :{ Authorization :"Bearer ${MCP_TOKEN}" }, 299}, 300}); 301const result = await f . ext . call ( "mcp_call" , { 302server :"remote" , 303tool :"echo" , 304arguments :{ answer :[ 1 , "two" , null ] }, 305}) as { structuredContent :JsonValue ; isError :boolean }; 306assertEquals ( result . structuredContent , [ 1 , "two" , null ]); 307assertEquals ( result . isError , true ); 308assert ( fetched > 0 ); 309assert ( authorization . every (( header ) => header === "Bearer remote-secret" )); 310await f . ext . dispose (); 311assert ( methods . includes ( "DELETE" )); 312} finally { 313await server . shutdown (); 314} 315}); 316 317Deno . test ( "an HTTP network failure retires the session without aborting other calls already using it" , async () => { 318await usingf = await fixture (); 319const entered = Promise . withResolvers < void >(); 320const release = Promise . withResolvers < void >(); 321let initialized = 0 ; 322const server = Deno . serve ({ hostname :"127.0.0.1" , port :0 , onListen () {} }, async ( request ) => { 323if ( request . method === "GET" ) return new Response ( null , { status :405 }); 324if ( request . method === "DELETE" ) return new Response ( null , { status :204 }); 325const message = await request . json (); 326if ( message . id === undefined ) return new Response ( null , { status :202 }); 327if ( message . params ?. name === "slow" ) { 328entered . resolve (); 329await release . promise ; 330} 331const result = message . method === "initialize" 332 ?{ 333protocolVersion :message . params . protocolVersion , 334capabilities :{ tools :{} }, 335serverInfo :{ name :"http" , version :String ( ++ initialized ) }, 336} 337 :{ content :[{ type :"text" , text :message . params . name }] }; 338return Response . json ({ jsonrpc :"2.0" , id :message . id , result}, { 339headers :{ "Mcp-Session-Id" :"session" }, 340}); 341}); 342const fetchHere = f . env . fetch ; 343f . env . fetch = ( input , init ) => { 344const message = typeof init ?. body === "string" ?JSON . parse ( init . body ) :undefined ; 345if ( message ?. params ?. name === "failed" ) return Promise . reject ( new TypeError ( "network lost" )); 346return fetchHere ( input , init ); 347}; 348const call = ( tool :string ) => f . ext . call ( "mcp_call" , { server :"http" , tool}); 349try { 350await f . configure ({ http :{ url :`http://127.0.0.1: ${ server . addr . port } /mcp` } }); 351const slow = call ( "slow" ). then (( value ) => ({ value}), ( error :unknown ) => ({ error})); 352await within ( entered . promise ); 353await assertRejects (() => call ( "failed" ), TypeError , "network lost" ); 354await call ( "fresh" ); 355assertEquals ( initialized , 2 ); 356release . resolve (); 357const result = await within ( slow ); 358assert ( "value" in result ); 359assertEquals (( result . value as { content :JsonValue }). content , [{ 360type :"text" , 361text :"slow" , 362}]); 363} finally { 364release . resolve (); 365await f . ext . dispose (); 366await server . shutdown (); 367} 368});