Skip to content

Commit 47986f1

Browse files
committed
fix: simplify output schemas for text-only tools and add structuredContent
For text-only tool responses, simplify outputSchemas from complex nested arrays to simple { content: z.string() } format. All tool responses now include structuredContent matching their outputSchema, fixing MCP protocol violations when tools had output schemas but no structured content. This applies to both filesystem and everything servers.
1 parent 55c3a31 commit 47986f1

2 files changed

Lines changed: 136 additions & 167 deletions

File tree

src/everything/everything.ts

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -547,21 +547,25 @@ export const createServer = () => {
547547

548548
if (name === ToolName.ECHO) {
549549
const validatedArgs = EchoSchema.parse(args);
550+
const text = `Echo: ${validatedArgs.message}`;
550551
return {
551-
content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }],
552+
content: [{ type: "text", text }],
553+
structuredContent: { content: text }
552554
};
553555
}
554556

555557
if (name === ToolName.ADD) {
556558
const validatedArgs = AddSchema.parse(args);
557559
const sum = validatedArgs.a + validatedArgs.b;
560+
const text = `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`;
558561
return {
559562
content: [
560563
{
561564
type: "text",
562-
text: `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`,
565+
text,
563566
},
564567
],
568+
structuredContent: { content: text }
565569
};
566570
}
567571

@@ -588,24 +592,28 @@ export const createServer = () => {
588592
}
589593
}
590594

595+
const text = `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`;
591596
return {
592597
content: [
593598
{
594599
type: "text",
595-
text: `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`,
600+
text,
596601
},
597602
],
603+
structuredContent: { content: text }
598604
};
599605
}
600606

601607
if (name === ToolName.PRINT_ENV) {
608+
const text = JSON.stringify(process.env, null, 2);
602609
return {
603610
content: [
604611
{
605612
type: "text",
606-
text: JSON.stringify(process.env, null, 2),
613+
text,
607614
},
608615
],
616+
structuredContent: { content: text }
609617
};
610618
}
611619

@@ -619,31 +627,35 @@ export const createServer = () => {
619627
maxTokens,
620628
extra.sendRequest
621629
);
630+
const text = `LLM sampling result: ${result.content.text}`;
622631
return {
623632
content: [
624-
{ type: "text", text: `LLM sampling result: ${result.content.text}` },
633+
{ type: "text", text },
625634
],
635+
structuredContent: { content: text }
626636
};
627637
}
628638

629639
if (name === ToolName.GET_TINY_IMAGE) {
630640
GetTinyImageSchema.parse(args);
641+
const content = [
642+
{
643+
type: "text",
644+
text: "This is a tiny image:",
645+
},
646+
{
647+
type: "image",
648+
data: MCP_TINY_IMAGE,
649+
mimeType: "image/png",
650+
},
651+
{
652+
type: "text",
653+
text: "The image above is the MCP tiny image.",
654+
},
655+
];
631656
return {
632-
content: [
633-
{
634-
type: "text",
635-
text: "This is a tiny image:",
636-
},
637-
{
638-
type: "image",
639-
data: MCP_TINY_IMAGE,
640-
mimeType: "image/png",
641-
},
642-
{
643-
type: "text",
644-
text: "The image above is the MCP tiny image.",
645-
},
646-
],
657+
content,
658+
structuredContent: { content }
647659
};
648660
}
649661

@@ -695,7 +707,7 @@ export const createServer = () => {
695707
});
696708
}
697709

698-
return { content };
710+
return { content, structuredContent: { content } };
699711
}
700712

701713
if (name === ToolName.GET_RESOURCE_REFERENCE) {
@@ -709,21 +721,23 @@ export const createServer = () => {
709721

710722
const resource = ALL_RESOURCES[resourceIndex];
711723

724+
const content = [
725+
{
726+
type: "text",
727+
text: `Returning resource reference for Resource ${resourceId}:`,
728+
},
729+
{
730+
type: "resource",
731+
resource: resource,
732+
},
733+
{
734+
type: "text",
735+
text: `You can access this resource using the URI: ${resource.uri}`,
736+
},
737+
];
712738
return {
713-
content: [
714-
{
715-
type: "text",
716-
text: `Returning resource reference for Resource ${resourceId}:`,
717-
},
718-
{
719-
type: "resource",
720-
resource: resource,
721-
},
722-
{
723-
type: "text",
724-
text: `You can access this resource using the URI: ${resource.uri}`,
725-
},
726-
],
739+
content,
740+
structuredContent: { content }
727741
};
728742
}
729743

@@ -844,7 +858,7 @@ export const createServer = () => {
844858
text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`,
845859
});
846860

847-
return { content };
861+
return { content, structuredContent: { content } };
848862
}
849863

850864
if (name === ToolName.GET_RESOURCE_LINKS) {
@@ -873,7 +887,7 @@ export const createServer = () => {
873887
});
874888
}
875889

876-
return { content };
890+
return { content, structuredContent: { content } };
877891
}
878892

879893
if (name === ToolName.STRUCTURED_CONTENT) {
@@ -917,60 +931,68 @@ export const createServer = () => {
917931

918932
const uri = `data:application/zip;base64,${await zip.generateAsync({ type: "base64" })}`;
919933

934+
const content = [
935+
{
936+
type: "resource_link",
937+
mimeType: "application/zip",
938+
uri,
939+
},
940+
];
920941
return {
921-
content: [
922-
{
923-
type: "resource_link",
924-
mimeType: "application/zip",
925-
uri,
926-
},
927-
],
942+
content,
943+
structuredContent: { content }
928944
};
929945
}
930946

931947
if (name === ToolName.LIST_ROOTS) {
932948
ListRootsSchema.parse(args);
933949

934950
if (!clientSupportsRoots) {
951+
const text = "The MCP client does not support the roots protocol.\n\n" +
952+
"This means the server cannot access information about the client's workspace directories or file system roots.";
935953
return {
936954
content: [
937955
{
938956
type: "text",
939-
text: "The MCP client does not support the roots protocol.\n\n" +
940-
"This means the server cannot access information about the client's workspace directories or file system roots."
957+
text
941958
}
942-
]
959+
],
960+
structuredContent: { content: text }
943961
};
944962
}
945963

946964
if (currentRoots.length === 0) {
965+
const text = "The client supports roots but no roots are currently configured.\n\n" +
966+
"This could mean:\n" +
967+
"1. The client hasn't provided any roots yet\n" +
968+
"2. The client provided an empty roots list\n" +
969+
"3. The roots configuration is still being loaded";
947970
return {
948971
content: [
949972
{
950973
type: "text",
951-
text: "The client supports roots but no roots are currently configured.\n\n" +
952-
"This could mean:\n" +
953-
"1. The client hasn't provided any roots yet\n" +
954-
"2. The client provided an empty roots list\n" +
955-
"3. The roots configuration is still being loaded"
974+
text
956975
}
957-
]
976+
],
977+
structuredContent: { content: text }
958978
};
959979
}
960980

961981
const rootsList = currentRoots.map((root, index) => {
962982
return `${index + 1}. ${root.name || 'Unnamed Root'}\n URI: ${root.uri}`;
963983
}).join('\n\n');
964984

985+
const text = `Current MCP Roots (${currentRoots.length} total):\n\n${rootsList}\n\n` +
986+
"Note: This server demonstrates the roots protocol capability but doesn't actually access files. " +
987+
"The roots are provided by the MCP client and can be used by servers that need file system access.";
965988
return {
966989
content: [
967990
{
968991
type: "text",
969-
text: `Current MCP Roots (${currentRoots.length} total):\n\n${rootsList}\n\n` +
970-
"Note: This server demonstrates the roots protocol capability but doesn't actually access files. " +
971-
"The roots are provided by the MCP client and can be used by servers that need file system access."
992+
text
972993
}
973-
]
994+
],
995+
structuredContent: { content: text }
974996
};
975997
}
976998

0 commit comments

Comments
 (0)