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.
- package/README.md +60 -0
- package/dist/FastMCP.d.ts +33 -6
- package/dist/FastMCP.js +111 -12
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/FastMCP.test.ts +236 -115
- package/src/FastMCP.ts +171 -18
package/src/FastMCP.test.ts
CHANGED
|
@@ -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:
|
|
19
|
-
|
|
19
|
+
client: createClient,
|
|
20
|
+
server: createServer,
|
|
20
21
|
}: {
|
|
21
|
-
|
|
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 =
|
|
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
|
-
|
|
47
|
-
new Client(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
669
|
-
{
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
687
|
-
|
|
696
|
+
capabilities: {
|
|
697
|
+
roots: {
|
|
698
|
+
listChanged: true,
|
|
699
|
+
},
|
|
700
|
+
},
|
|
688
701
|
},
|
|
689
|
-
|
|
690
|
-
};
|
|
691
|
-
});
|
|
702
|
+
);
|
|
692
703
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
|
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
|
-
|
|
715
|
-
{
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
733
|
-
|
|
736
|
+
capabilities: {
|
|
737
|
+
roots: {
|
|
738
|
+
listChanged: true,
|
|
739
|
+
},
|
|
740
|
+
},
|
|
734
741
|
},
|
|
735
|
-
|
|
736
|
-
};
|
|
737
|
-
});
|
|
742
|
+
);
|
|
738
743
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
|
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
|
+
});
|