@@ -214,9 +214,12 @@ func TestE2EMultipleSkills(t *testing.T) {
214214 })
215215
216216 t .Run ("execute_code_call_tool" , func (t * testing.T ) {
217+ code := dedent (`
218+ execute_sql(sql="SELECT 1")
219+ ` )
217220 result , err := session .CallTool (ctx , & mcp.CallToolParams {
218221 Name : "execute_code" ,
219- Arguments : map [string ]any {"code" : `execute_sql(sql="SELECT 1")` },
222+ Arguments : map [string ]any {"code" : code },
220223 })
221224 if err != nil {
222225 t .Fatal (err )
@@ -240,11 +243,11 @@ func TestE2EMultipleSkills(t *testing.T) {
240243 })
241244
242245 t .Run ("execute_code_multi_tool" , func (t * testing.T ) {
243- code := `
244- a = execute_sql(sql="SELECT 1")
245- b = read_file(path="/tmp/test.txt")
246- a + " | " + b
247- `
246+ code := dedent ( `
247+ a = execute_sql(sql="SELECT 1")
248+ b = read_file(path="/tmp/test.txt")
249+ a + " | " + b
250+ ` )
248251 result , err := session .CallTool (ctx , & mcp.CallToolParams {
249252 Name : "execute_code" ,
250253 Arguments : map [string ]any {"code" : code },
@@ -329,9 +332,12 @@ func TestE2EPositionalArgs(t *testing.T) {
329332 session := connectTestClient (t , ctx , mgr )
330333
331334 t .Run ("positional_arg" , func (t * testing.T ) {
335+ code := dedent (`
336+ execute_sql("SELECT 1")
337+ ` )
332338 result , err := session .CallTool (ctx , & mcp.CallToolParams {
333339 Name : "execute_code" ,
334- Arguments : map [string ]any {"code" : `execute_sql("SELECT 1")` },
340+ Arguments : map [string ]any {"code" : code },
335341 })
336342 if err != nil {
337343 t .Fatal (err )
@@ -351,9 +357,12 @@ func TestE2EPositionalArgs(t *testing.T) {
351357 })
352358
353359 t .Run ("keyword_arg" , func (t * testing.T ) {
360+ code := dedent (`
361+ execute_sql(sql="SELECT 2")
362+ ` )
354363 result , err := session .CallTool (ctx , & mcp.CallToolParams {
355364 Name : "execute_code" ,
356- Arguments : map [string ]any {"code" : `execute_sql(sql="SELECT 2")` },
365+ Arguments : map [string ]any {"code" : code },
357366 })
358367 if err != nil {
359368 t .Fatal (err )
@@ -427,9 +436,12 @@ func TestE2EToolNameConflict(t *testing.T) {
427436 })
428437
429438 t .Run ("execute_code_prefixed_name" , func (t * testing.T ) {
439+ code := dedent (`
440+ alpha_search(q="test")
441+ ` )
430442 result , err := session .CallTool (ctx , & mcp.CallToolParams {
431443 Name : "execute_code" ,
432- Arguments : map [string ]any {"code" : `alpha_search(q="test")` },
444+ Arguments : map [string ]any {"code" : code },
433445 })
434446 if err != nil {
435447 t .Fatal (err )
@@ -449,9 +461,12 @@ func TestE2EToolNameConflict(t *testing.T) {
449461 })
450462
451463 t .Run ("execute_code_unique_name" , func (t * testing.T ) {
464+ code := dedent (`
465+ unique_tool()
466+ ` )
452467 result , err := session .CallTool (ctx , & mcp.CallToolParams {
453468 Name : "execute_code" ,
454- Arguments : map [string ]any {"code" : `unique_tool()` },
469+ Arguments : map [string ]any {"code" : code },
455470 })
456471 if err != nil {
457472 t .Fatal (err )
@@ -471,6 +486,123 @@ func TestE2EToolNameConflict(t *testing.T) {
471486 })
472487}
473488
489+ func TestE2EStructuredOutput (t * testing.T ) {
490+ t .Parallel ()
491+ ctx := t .Context ()
492+
493+ type WeatherInput struct {
494+ Location string `json:"location" jsonschema:"city name"`
495+ }
496+ type WeatherOutput struct {
497+ Temperature float64 `json:"temperature"`
498+ Conditions string `json:"conditions"`
499+ }
500+
501+ ds := mcp .NewServer (& mcp.Implementation {Name : "weather-server" }, nil )
502+ mcp .AddTool (
503+ ds ,
504+ & mcp.Tool {Name : "get_weather" , Description : "Get weather data" },
505+ func (ctx context.Context , req * mcp.CallToolRequest , input WeatherInput ) (* mcp.CallToolResult , WeatherOutput , error ) {
506+ return & mcp.CallToolResult {}, WeatherOutput {
507+ Temperature : 22.5 ,
508+ Conditions : "Partly cloudy" ,
509+ }, nil
510+ },
511+ )
512+
513+ dsServerT , dsClientT := mcp .NewInMemoryTransports ()
514+ go func () { _ = ds .Run (ctx , dsServerT ) }()
515+ dsClient := mcp .NewClient (& mcp.Implementation {Name : "test" }, nil )
516+ dsSession , err := dsClient .Connect (ctx , dsClientT , nil )
517+ if err != nil {
518+ t .Fatal (err )
519+ }
520+
521+ srv , err := mcpserver .NewServerFromSession (ctx , dsSession )
522+ if err != nil {
523+ t .Fatal (err )
524+ }
525+ mgr , err := mcpserver .NewManagerFromServers (map [string ]* mcpserver.Server {"weather" : srv })
526+ if err != nil {
527+ t .Fatal (err )
528+ }
529+ defer mgr .Close ()
530+
531+ session := connectTestClient (t , ctx , mgr )
532+
533+ t .Run ("access_string_field" , func (t * testing.T ) {
534+ code := dedent (`
535+ w = get_weather(location="NYC")
536+ w["conditions"]
537+ ` )
538+ result , err := session .CallTool (ctx , & mcp.CallToolParams {
539+ Name : "execute_code" ,
540+ Arguments : map [string ]any {"code" : code },
541+ })
542+ if err != nil {
543+ t .Fatal (err )
544+ }
545+ if result .IsError {
546+ tc := result .Content [0 ].(* mcp.TextContent )
547+ t .Fatalf ("error: %s" , tc .Text )
548+ }
549+ tc := result .Content [0 ].(* mcp.TextContent )
550+ if tc .Text != "Partly cloudy" {
551+ t .Errorf ("expected 'Partly cloudy', got %q" , tc .Text )
552+ }
553+ })
554+
555+ t .Run ("access_numeric_field" , func (t * testing.T ) {
556+ code := dedent (`
557+ w = get_weather(location="NYC")
558+ w["temperature"]
559+ ` )
560+ result , err := session .CallTool (ctx , & mcp.CallToolParams {
561+ Name : "execute_code" ,
562+ Arguments : map [string ]any {"code" : code },
563+ })
564+ if err != nil {
565+ t .Fatal (err )
566+ }
567+ if result .IsError {
568+ tc := result .Content [0 ].(* mcp.TextContent )
569+ t .Fatalf ("error: %s" , tc .Text )
570+ }
571+ tc := result .Content [0 ].(* mcp.TextContent )
572+ if tc .Text != "22.5" {
573+ t .Errorf ("expected '22.5', got %q" , tc .Text )
574+ }
575+ })
576+ }
577+
578+ // dedent strips the common leading whitespace from all non-empty lines.
579+ func dedent (s string ) string {
580+ lines := strings .Split (strings .TrimRight (s , "\n " ), "\n " )
581+
582+ // Find minimum indentation across non-empty lines.
583+ minIndent := - 1
584+ for _ , line := range lines {
585+ trimmed := strings .TrimLeft (line , " \t " )
586+ if trimmed == "" {
587+ continue
588+ }
589+ indent := len (line ) - len (trimmed )
590+ if minIndent < 0 || indent < minIndent {
591+ minIndent = indent
592+ }
593+ }
594+ if minIndent <= 0 {
595+ return strings .TrimSpace (s )
596+ }
597+
598+ for i , line := range lines {
599+ if len (line ) >= minIndent {
600+ lines [i ] = line [minIndent :]
601+ }
602+ }
603+ return strings .TrimSpace (strings .Join (lines , "\n " ))
604+ }
605+
474606func splitOnce (s , sep string ) []string {
475607 i := strings .Index (s , sep )
476608 if i < 0 {
0 commit comments