@@ -15,6 +15,8 @@ import {
15
15
ListToolsRequestSchema ,
16
16
SetLevelRequestSchema ,
17
17
ErrorCode ,
18
+ ListToolsResultSchema ,
19
+ CallToolResultSchema ,
18
20
} from "../types.js" ;
19
21
import { Transport } from "../shared/transport.js" ;
20
22
import { InMemoryTransport } from "../inMemory.js" ;
@@ -545,3 +547,329 @@ test("should handle request timeout", async () => {
545
547
code : ErrorCode . RequestTimeout ,
546
548
} ) ;
547
549
} ) ;
550
+
551
+ describe ( "Server.tool" , ( ) => {
552
+ test ( "should register zero-argument tool" , async ( ) => {
553
+ const server = new Server ( {
554
+ name : "test server" ,
555
+ version : "1.0" ,
556
+ } ) ;
557
+ const client = new Client ( {
558
+ name : "test client" ,
559
+ version : "1.0" ,
560
+ } ) ;
561
+
562
+ server . tool ( "test" , async ( ) => ( {
563
+ content : [
564
+ {
565
+ type : "text" ,
566
+ text : "Test response" ,
567
+ } ,
568
+ ] ,
569
+ } ) ) ;
570
+
571
+ const [ clientTransport , serverTransport ] =
572
+ InMemoryTransport . createLinkedPair ( ) ;
573
+
574
+ await Promise . all ( [
575
+ client . connect ( clientTransport ) ,
576
+ server . connect ( serverTransport ) ,
577
+ ] ) ;
578
+
579
+ const result = await client . request (
580
+ {
581
+ method : "tools/list" ,
582
+ } ,
583
+ ListToolsResultSchema ,
584
+ ) ;
585
+
586
+ expect ( result . tools ) . toHaveLength ( 1 ) ;
587
+ expect ( result . tools [ 0 ] . name ) . toBe ( "test" ) ;
588
+ expect ( result . tools [ 0 ] . inputSchema ) . toEqual ( {
589
+ type : "object" ,
590
+ } ) ;
591
+ } ) ;
592
+
593
+ test ( "should register tool with args schema" , async ( ) => {
594
+ const server = new Server ( {
595
+ name : "test server" ,
596
+ version : "1.0" ,
597
+ } ) ;
598
+ const client = new Client ( {
599
+ name : "test client" ,
600
+ version : "1.0" ,
601
+ } ) ;
602
+
603
+ server . tool (
604
+ "test" ,
605
+ {
606
+ name : z . string ( ) ,
607
+ value : z . number ( ) ,
608
+ } ,
609
+ async ( args ) => ( {
610
+ content : [
611
+ {
612
+ type : "text" ,
613
+ text : `${ args . name } : ${ args . value } ` ,
614
+ } ,
615
+ ] ,
616
+ } ) ,
617
+ ) ;
618
+
619
+ const [ clientTransport , serverTransport ] =
620
+ InMemoryTransport . createLinkedPair ( ) ;
621
+
622
+ await Promise . all ( [
623
+ client . connect ( clientTransport ) ,
624
+ server . connect ( serverTransport ) ,
625
+ ] ) ;
626
+
627
+ const result = await client . request (
628
+ {
629
+ method : "tools/list" ,
630
+ } ,
631
+ ListToolsResultSchema ,
632
+ ) ;
633
+
634
+ expect ( result . tools ) . toHaveLength ( 1 ) ;
635
+ expect ( result . tools [ 0 ] . name ) . toBe ( "test" ) ;
636
+ expect ( result . tools [ 0 ] . inputSchema ) . toMatchObject ( {
637
+ type : "object" ,
638
+ properties : {
639
+ name : { type : "string" } ,
640
+ value : { type : "number" } ,
641
+ } ,
642
+ } ) ;
643
+ } ) ;
644
+
645
+ test ( "should register tool with description" , async ( ) => {
646
+ const server = new Server ( {
647
+ name : "test server" ,
648
+ version : "1.0" ,
649
+ } ) ;
650
+ const client = new Client ( {
651
+ name : "test client" ,
652
+ version : "1.0" ,
653
+ } ) ;
654
+
655
+ server . tool ( "test" , "Test description" , async ( ) => ( {
656
+ content : [
657
+ {
658
+ type : "text" ,
659
+ text : "Test response" ,
660
+ } ,
661
+ ] ,
662
+ } ) ) ;
663
+
664
+ const [ clientTransport , serverTransport ] =
665
+ InMemoryTransport . createLinkedPair ( ) ;
666
+
667
+ await Promise . all ( [
668
+ client . connect ( clientTransport ) ,
669
+ server . connect ( serverTransport ) ,
670
+ ] ) ;
671
+
672
+ const result = await client . request (
673
+ {
674
+ method : "tools/list" ,
675
+ } ,
676
+ ListToolsResultSchema ,
677
+ ) ;
678
+
679
+ expect ( result . tools ) . toHaveLength ( 1 ) ;
680
+ expect ( result . tools [ 0 ] . name ) . toBe ( "test" ) ;
681
+ expect ( result . tools [ 0 ] . description ) . toBe ( "Test description" ) ;
682
+ } ) ;
683
+
684
+ test ( "should validate tool args" , async ( ) => {
685
+ const server = new Server ( {
686
+ name : "test server" ,
687
+ version : "1.0" ,
688
+ } ) ;
689
+
690
+ const client = new Client (
691
+ {
692
+ name : "test client" ,
693
+ version : "1.0" ,
694
+ } ,
695
+ {
696
+ capabilities : {
697
+ tools : { } ,
698
+ } ,
699
+ } ,
700
+ ) ;
701
+
702
+ server . tool (
703
+ "test" ,
704
+ {
705
+ name : z . string ( ) ,
706
+ value : z . number ( ) ,
707
+ } ,
708
+ async ( args ) => ( {
709
+ content : [
710
+ {
711
+ type : "text" ,
712
+ text : `${ args . name } : ${ args . value } ` ,
713
+ } ,
714
+ ] ,
715
+ } ) ,
716
+ ) ;
717
+
718
+ const [ clientTransport , serverTransport ] =
719
+ InMemoryTransport . createLinkedPair ( ) ;
720
+
721
+ await Promise . all ( [
722
+ client . connect ( clientTransport ) ,
723
+ server . connect ( serverTransport ) ,
724
+ ] ) ;
725
+
726
+ await expect (
727
+ client . request (
728
+ {
729
+ method : "tools/call" ,
730
+ params : {
731
+ name : "test" ,
732
+ arguments : {
733
+ name : "test" ,
734
+ value : "not a number" ,
735
+ } ,
736
+ } ,
737
+ } ,
738
+ CallToolResultSchema ,
739
+ ) ,
740
+ ) . rejects . toThrow ( / I n v a l i d a r g u m e n t s / ) ;
741
+ } ) ;
742
+
743
+ test ( "should prevent duplicate tool registration" , ( ) => {
744
+ const server = new Server ( {
745
+ name : "test server" ,
746
+ version : "1.0" ,
747
+ } ) ;
748
+
749
+ server . tool ( "test" , async ( ) => ( {
750
+ content : [
751
+ {
752
+ type : "text" ,
753
+ text : "Test response" ,
754
+ } ,
755
+ ] ,
756
+ } ) ) ;
757
+
758
+ expect ( ( ) => {
759
+ server . tool ( "test" , async ( ) => ( {
760
+ content : [
761
+ {
762
+ type : "text" ,
763
+ text : "Test response 2" ,
764
+ } ,
765
+ ] ,
766
+ } ) ) ;
767
+ } ) . toThrow ( / a l r e a d y r e g i s t e r e d / ) ;
768
+ } ) ;
769
+
770
+ test ( "should allow client to call server tools" , async ( ) => {
771
+ const server = new Server ( {
772
+ name : "test server" ,
773
+ version : "1.0" ,
774
+ } ) ;
775
+
776
+ const client = new Client (
777
+ {
778
+ name : "test client" ,
779
+ version : "1.0" ,
780
+ } ,
781
+ {
782
+ capabilities : {
783
+ tools : { } ,
784
+ } ,
785
+ } ,
786
+ ) ;
787
+
788
+ server . tool (
789
+ "test" ,
790
+ "Test tool" ,
791
+ {
792
+ input : z . string ( ) ,
793
+ } ,
794
+ async ( args ) => ( {
795
+ content : [
796
+ {
797
+ type : "text" ,
798
+ text : `Processed: ${ args . input } ` ,
799
+ } ,
800
+ ] ,
801
+ } ) ,
802
+ ) ;
803
+
804
+ const [ clientTransport , serverTransport ] =
805
+ InMemoryTransport . createLinkedPair ( ) ;
806
+
807
+ await Promise . all ( [
808
+ client . connect ( clientTransport ) ,
809
+ server . connect ( serverTransport ) ,
810
+ ] ) ;
811
+
812
+ const result = await client . request (
813
+ {
814
+ method : "tools/call" ,
815
+ params : {
816
+ name : "test" ,
817
+ arguments : {
818
+ input : "hello" ,
819
+ } ,
820
+ } ,
821
+ } ,
822
+ CallToolResultSchema ,
823
+ ) ;
824
+
825
+ expect ( result . content ) . toEqual ( [
826
+ {
827
+ type : "text" ,
828
+ text : "Processed: hello" ,
829
+ } ,
830
+ ] ) ;
831
+ } ) ;
832
+
833
+ test ( "should handle server tool errors gracefully" , async ( ) => {
834
+ const server = new Server ( {
835
+ name : "test server" ,
836
+ version : "1.0" ,
837
+ } ) ;
838
+
839
+ const client = new Client (
840
+ {
841
+ name : "test client" ,
842
+ version : "1.0" ,
843
+ } ,
844
+ {
845
+ capabilities : {
846
+ tools : { } ,
847
+ } ,
848
+ } ,
849
+ ) ;
850
+
851
+ server . tool ( "error-test" , async ( ) => {
852
+ throw new Error ( "Tool execution failed" ) ;
853
+ } ) ;
854
+
855
+ const [ clientTransport , serverTransport ] =
856
+ InMemoryTransport . createLinkedPair ( ) ;
857
+
858
+ await Promise . all ( [
859
+ client . connect ( clientTransport ) ,
860
+ server . connect ( serverTransport ) ,
861
+ ] ) ;
862
+
863
+ await expect (
864
+ client . request (
865
+ {
866
+ method : "tools/call" ,
867
+ params : {
868
+ name : "error-test" ,
869
+ } ,
870
+ } ,
871
+ CallToolResultSchema ,
872
+ ) ,
873
+ ) . rejects . toThrow ( "Tool execution failed" ) ;
874
+ } ) ;
875
+ } ) ;
0 commit comments