Skip to content

Commit ff22f25

Browse files
committed
Tests for Server.tool
1 parent b538731 commit ff22f25

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed

src/server/index.test.ts

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
ListToolsRequestSchema,
1616
SetLevelRequestSchema,
1717
ErrorCode,
18+
ListToolsResultSchema,
19+
CallToolResultSchema,
1820
} from "../types.js";
1921
import { Transport } from "../shared/transport.js";
2022
import { InMemoryTransport } from "../inMemory.js";
@@ -545,3 +547,329 @@ test("should handle request timeout", async () => {
545547
code: ErrorCode.RequestTimeout,
546548
});
547549
});
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(/Invalid arguments/);
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(/already registered/);
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

Comments
 (0)