fastmcp 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,16 +10,17 @@ import {
10
10
  ListRootsRequestSchema,
11
11
  LoggingMessageNotificationSchema,
12
12
  McpError,
13
+ PingRequestSchema,
13
14
  Root,
14
15
  } from "@modelcontextprotocol/sdk/types.js";
15
16
 
16
17
  const runWithTestServer = async ({
17
18
  run,
18
- client: clientOverride,
19
- start,
19
+ client: createClient,
20
+ server: createServer,
20
21
  }: {
21
- start: () => Promise<FastMCP>;
22
- client?: Client;
22
+ server?: () => Promise<FastMCP>;
23
+ client?: () => Promise<Client>;
23
24
  run: ({
24
25
  client,
25
26
  server,
@@ -31,7 +32,12 @@ const runWithTestServer = async ({
31
32
  }) => {
32
33
  const port = await getRandomPort();
33
34
 
34
- const server = await start();
35
+ const server = createServer
36
+ ? await createServer()
37
+ : new FastMCP({
38
+ name: "Test",
39
+ version: "1.0.0",
40
+ });
35
41
 
36
42
  await server.start({
37
43
  transportType: "sse",
@@ -42,17 +48,17 @@ const runWithTestServer = async ({
42
48
  });
43
49
 
44
50
  try {
45
- const client =
46
- clientOverride ??
47
- new Client(
48
- {
49
- name: "example-client",
50
- version: "1.0.0",
51
- },
52
- {
53
- capabilities: {},
54
- },
55
- );
51
+ const client = createClient
52
+ ? await createClient()
53
+ : new Client(
54
+ {
55
+ name: "example-client",
56
+ version: "1.0.0",
57
+ },
58
+ {
59
+ capabilities: {},
60
+ },
61
+ );
56
62
 
57
63
  const transport = new SSEClientTransport(
58
64
  new URL(`http://localhost:${port}/sse`),
@@ -76,7 +82,7 @@ const runWithTestServer = async ({
76
82
 
77
83
  test("adds tools", async () => {
78
84
  await runWithTestServer({
79
- start: async () => {
85
+ server: async () => {
80
86
  const server = new FastMCP({
81
87
  name: "Test",
82
88
  version: "1.0.0",
@@ -121,7 +127,7 @@ test("adds tools", async () => {
121
127
 
122
128
  test("calls a tool", async () => {
123
129
  await runWithTestServer({
124
- start: async () => {
130
+ server: async () => {
125
131
  const server = new FastMCP({
126
132
  name: "Test",
127
133
  version: "1.0.0",
@@ -159,7 +165,7 @@ test("calls a tool", async () => {
159
165
 
160
166
  test("returns a list", async () => {
161
167
  await runWithTestServer({
162
- start: async () => {
168
+ server: async () => {
163
169
  const server = new FastMCP({
164
170
  name: "Test",
165
171
  version: "1.0.0",
@@ -205,7 +211,7 @@ test("returns a list", async () => {
205
211
 
206
212
  test("returns an image", async () => {
207
213
  await runWithTestServer({
208
- start: async () => {
214
+ server: async () => {
209
215
  const server = new FastMCP({
210
216
  name: "Test",
211
217
  version: "1.0.0",
@@ -254,7 +260,7 @@ test("returns an image", async () => {
254
260
 
255
261
  test("handles UserError errors", async () => {
256
262
  await runWithTestServer({
257
- start: async () => {
263
+ server: async () => {
258
264
  const server = new FastMCP({
259
265
  name: "Test",
260
266
  version: "1.0.0",
@@ -293,7 +299,7 @@ test("handles UserError errors", async () => {
293
299
 
294
300
  test("calling an unknown tool throws McpError with MethodNotFound code", async () => {
295
301
  await runWithTestServer({
296
- start: async () => {
302
+ server: async () => {
297
303
  const server = new FastMCP({
298
304
  name: "Test",
299
305
  version: "1.0.0",
@@ -322,7 +328,7 @@ test("calling an unknown tool throws McpError with MethodNotFound code", async (
322
328
 
323
329
  test("tracks tool progress", async () => {
324
330
  await runWithTestServer({
325
- start: async () => {
331
+ server: async () => {
326
332
  const server = new FastMCP({
327
333
  name: "Test",
328
334
  version: "1.0.0",
@@ -377,12 +383,6 @@ test("tracks tool progress", async () => {
377
383
 
378
384
  test("sets logging levels", async () => {
379
385
  await runWithTestServer({
380
- start: async () => {
381
- return new FastMCP({
382
- name: "Test",
383
- version: "1.0.0",
384
- });
385
- },
386
386
  run: async ({ client, session }) => {
387
387
  await client.setLoggingLevel("debug");
388
388
 
@@ -397,7 +397,7 @@ test("sets logging levels", async () => {
397
397
 
398
398
  test("sends logging messages to the client", async () => {
399
399
  await runWithTestServer({
400
- start: async () => {
400
+ server: async () => {
401
401
  const server = new FastMCP({
402
402
  name: "Test",
403
403
  version: "1.0.0",
@@ -473,7 +473,7 @@ test("sends logging messages to the client", async () => {
473
473
 
474
474
  test("adds resources", async () => {
475
475
  await runWithTestServer({
476
- start: async () => {
476
+ server: async () => {
477
477
  const server = new FastMCP({
478
478
  name: "Test",
479
479
  version: "1.0.0",
@@ -508,7 +508,7 @@ test("adds resources", async () => {
508
508
 
509
509
  test("adds prompts", async () => {
510
510
  await runWithTestServer({
511
- start: async () => {
511
+ server: async () => {
512
512
  const server = new FastMCP({
513
513
  name: "Test",
514
514
  version: "1.0.0",
@@ -532,6 +532,26 @@ test("adds prompts", async () => {
532
532
  return server;
533
533
  },
534
534
  run: async ({ client }) => {
535
+ expect(
536
+ await client.getPrompt({
537
+ name: "git-commit",
538
+ arguments: {
539
+ changes: "foo",
540
+ },
541
+ }),
542
+ ).toEqual({
543
+ description: "Generate a Git commit message",
544
+ messages: [
545
+ {
546
+ role: "user",
547
+ content: {
548
+ type: "text",
549
+ text: "Generate a concise but descriptive commit message for these changes:\n\nfoo",
550
+ },
551
+ },
552
+ ],
553
+ });
554
+
535
555
  expect(await client.listPrompts()).toEqual({
536
556
  prompts: [
537
557
  {
@@ -665,40 +685,34 @@ test("handles multiple clients", async () => {
665
685
  });
666
686
 
667
687
  test("session knows about client capabilities", async () => {
668
- const client = new Client(
669
- {
670
- name: "example-client",
671
- version: "1.0.0",
672
- },
673
- {
674
- capabilities: {
675
- roots: {
676
- listChanged: true,
688
+ await runWithTestServer({
689
+ client: async () => {
690
+ const client = new Client(
691
+ {
692
+ name: "example-client",
693
+ version: "1.0.0",
677
694
  },
678
- },
679
- },
680
- );
681
-
682
- client.setRequestHandler(ListRootsRequestSchema, () => {
683
- return {
684
- roots: [
685
695
  {
686
- uri: "file:///home/user/projects/frontend",
687
- name: "Frontend Repository",
696
+ capabilities: {
697
+ roots: {
698
+ listChanged: true,
699
+ },
700
+ },
688
701
  },
689
- ],
690
- };
691
- });
702
+ );
692
703
 
693
- await runWithTestServer({
694
- client,
695
- start: async () => {
696
- const server = new FastMCP({
697
- name: "Test",
698
- version: "1.0.0",
704
+ client.setRequestHandler(ListRootsRequestSchema, () => {
705
+ return {
706
+ roots: [
707
+ {
708
+ uri: "file:///home/user/projects/frontend",
709
+ name: "Frontend Repository",
710
+ },
711
+ ],
712
+ };
699
713
  });
700
714
 
701
- return server;
715
+ return client;
702
716
  },
703
717
  run: async ({ session }) => {
704
718
  expect(session.clientCapabilities).toEqual({
@@ -711,40 +725,34 @@ test("session knows about client capabilities", async () => {
711
725
  });
712
726
 
713
727
  test("session knows about roots", async () => {
714
- const client = new Client(
715
- {
716
- name: "example-client",
717
- version: "1.0.0",
718
- },
719
- {
720
- capabilities: {
721
- roots: {
722
- listChanged: true,
728
+ await runWithTestServer({
729
+ client: async () => {
730
+ const client = new Client(
731
+ {
732
+ name: "example-client",
733
+ version: "1.0.0",
723
734
  },
724
- },
725
- },
726
- );
727
-
728
- client.setRequestHandler(ListRootsRequestSchema, () => {
729
- return {
730
- roots: [
731
735
  {
732
- uri: "file:///home/user/projects/frontend",
733
- name: "Frontend Repository",
736
+ capabilities: {
737
+ roots: {
738
+ listChanged: true,
739
+ },
740
+ },
734
741
  },
735
- ],
736
- };
737
- });
742
+ );
738
743
 
739
- await runWithTestServer({
740
- client,
741
- start: async () => {
742
- const server = new FastMCP({
743
- name: "Test",
744
- version: "1.0.0",
744
+ client.setRequestHandler(ListRootsRequestSchema, () => {
745
+ return {
746
+ roots: [
747
+ {
748
+ uri: "file:///home/user/projects/frontend",
749
+ name: "Frontend Repository",
750
+ },
751
+ ],
752
+ };
745
753
  });
746
754
 
747
- return server;
755
+ return client;
748
756
  },
749
757
  run: async ({ session }) => {
750
758
  expect(session.roots).toEqual([
@@ -758,20 +766,6 @@ test("session knows about roots", async () => {
758
766
  });
759
767
 
760
768
  test("session listens to roots changes", async () => {
761
- const client = new Client(
762
- {
763
- name: "example-client",
764
- version: "1.0.0",
765
- },
766
- {
767
- capabilities: {
768
- roots: {
769
- listChanged: true,
770
- },
771
- },
772
- },
773
- );
774
-
775
769
  let clientRoots: Root[] = [
776
770
  {
777
771
  uri: "file:///home/user/projects/frontend",
@@ -779,23 +773,31 @@ test("session listens to roots changes", async () => {
779
773
  },
780
774
  ];
781
775
 
782
- client.setRequestHandler(ListRootsRequestSchema, () => {
783
- return {
784
- roots: clientRoots,
785
- };
786
- });
787
-
788
776
  await runWithTestServer({
789
- client,
790
- start: async () => {
791
- const server = new FastMCP({
792
- name: "Test",
793
- version: "1.0.0",
777
+ client: async () => {
778
+ const client = new Client(
779
+ {
780
+ name: "example-client",
781
+ version: "1.0.0",
782
+ },
783
+ {
784
+ capabilities: {
785
+ roots: {
786
+ listChanged: true,
787
+ },
788
+ },
789
+ },
790
+ );
791
+
792
+ client.setRequestHandler(ListRootsRequestSchema, () => {
793
+ return {
794
+ roots: clientRoots,
795
+ };
794
796
  });
795
797
 
796
- return server;
798
+ return client;
797
799
  },
798
- run: async ({ session }) => {
800
+ run: async ({ session, client }) => {
799
801
  expect(session.roots).toEqual([
800
802
  {
801
803
  uri: "file:///home/user/projects/frontend",
@@ -843,3 +845,122 @@ test("session listens to roots changes", async () => {
843
845
  },
844
846
  });
845
847
  });
848
+
849
+ test("session sends pings to the client", async () => {
850
+ await runWithTestServer({
851
+ run: async ({ client }) => {
852
+ const onPing = vi.fn().mockReturnValue({});
853
+
854
+ client.setRequestHandler(PingRequestSchema, onPing);
855
+
856
+ await delay(2000);
857
+
858
+ expect(onPing).toHaveBeenCalledTimes(1);
859
+ },
860
+ });
861
+ });
862
+
863
+ test("prompt argument autocompletion", async () => {
864
+ await runWithTestServer({
865
+ server: async () => {
866
+ const server = new FastMCP({
867
+ name: "Test",
868
+ version: "1.0.0",
869
+ });
870
+
871
+ server.addPrompt({
872
+ name: "countryPoem",
873
+ description: "Writes a poem about a country",
874
+ load: async ({ name }) => {
875
+ return `Hello, ${name}!`;
876
+ },
877
+ arguments: [
878
+ {
879
+ name: "name",
880
+ description: "Name of the country",
881
+ required: true,
882
+ complete: async (value) => {
883
+ if (value === "Germ") {
884
+ return {
885
+ values: ["Germany"],
886
+ };
887
+ }
888
+
889
+ return {
890
+ values: [],
891
+ };
892
+ },
893
+ },
894
+ ],
895
+ });
896
+
897
+ return server;
898
+ },
899
+ run: async ({ client }) => {
900
+ const response = await client.complete({
901
+ ref: {
902
+ type: "ref/prompt",
903
+ name: "countryPoem",
904
+ },
905
+ argument: {
906
+ name: "name",
907
+ value: "Germ",
908
+ },
909
+ });
910
+
911
+ expect(response).toEqual({
912
+ completion: {
913
+ values: ["Germany"],
914
+ },
915
+ });
916
+ },
917
+ });
918
+ });
919
+
920
+ test("adds automatic prompt argument completion when enum is provided", async () => {
921
+ await runWithTestServer({
922
+ server: async () => {
923
+ const server = new FastMCP({
924
+ name: "Test",
925
+ version: "1.0.0",
926
+ });
927
+
928
+ server.addPrompt({
929
+ name: "countryPoem",
930
+ description: "Writes a poem about a country",
931
+ load: async ({ name }) => {
932
+ return `Hello, ${name}!`;
933
+ },
934
+ arguments: [
935
+ {
936
+ name: "name",
937
+ description: "Name of the country",
938
+ required: true,
939
+ enum: ["Germany", "France", "Italy"],
940
+ },
941
+ ],
942
+ });
943
+
944
+ return server;
945
+ },
946
+ run: async ({ client }) => {
947
+ const response = await client.complete({
948
+ ref: {
949
+ type: "ref/prompt",
950
+ name: "countryPoem",
951
+ },
952
+ argument: {
953
+ name: "name",
954
+ value: "Germ",
955
+ },
956
+ });
957
+
958
+ expect(response).toEqual({
959
+ completion: {
960
+ values: ["Germany"],
961
+ total: 1,
962
+ },
963
+ });
964
+ },
965
+ });
966
+ });