Skip to content

Commit 6309d1d

Browse files
committed
test: add image validation tests for unsupported and malformed MIME types
**Changes:** - Added tests to ensure that the MCP tool correctly ignores images with unsupported MIME types and malformed content. - Implemented console warnings for unsupported MIME types and missing properties in image content. **Files Modified:** - `src/core/tools/__tests__/useMcpToolTool.spec.ts`
1 parent 6451bc5 commit 6309d1d

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

src/core/tools/__tests__/useMcpToolTool.spec.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,210 @@ describe("useMcpToolTool", () => {
755755
consoleSpy.mockRestore()
756756
})
757757

758+
it("should ignore images with unsupported MIME types", async () => {
759+
const block: ToolUse = {
760+
type: "tool_use",
761+
name: "use_mcp_tool",
762+
params: {
763+
server_name: "test_server",
764+
tool_name: "test_tool",
765+
arguments: '{"param": "value"}',
766+
},
767+
partial: false,
768+
}
769+
770+
mockAskApproval.mockResolvedValue(true)
771+
772+
const mockToolResult = {
773+
content: [
774+
{ type: "text", text: "Generated content with different image types:" },
775+
{
776+
type: "image",
777+
data: "PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+PGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiLz48L3N2Zz4=",
778+
mimeType: "image/svg+xml", // Unsupported MIME type
779+
},
780+
{
781+
type: "image",
782+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jU",
783+
mimeType: "image/png", // Supported MIME type
784+
},
785+
],
786+
isError: false,
787+
}
788+
789+
mockProviderRef.deref.mockReturnValue({
790+
getMcpHub: () => ({
791+
callTool: vi.fn().mockResolvedValue(mockToolResult),
792+
}),
793+
postMessageToWebview: vi.fn(),
794+
getState: vi.fn().mockResolvedValue({
795+
mcpMaxImagesPerResponse: 20,
796+
mcpMaxImageSizeMB: 10,
797+
}),
798+
})
799+
800+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
801+
802+
await useMcpToolTool(
803+
mockTask as Task,
804+
block,
805+
mockAskApproval,
806+
mockHandleError,
807+
mockPushToolResult,
808+
mockRemoveClosingTag,
809+
)
810+
811+
// Should only include the supported PNG image, not the SVG
812+
const sayCall = (mockTask.say as any).mock.calls.find((call: any) => call[0] === "mcp_server_response")
813+
expect(sayCall[2]).toHaveLength(1)
814+
expect(sayCall[2][0]).toContain("data:image/png;base64,")
815+
816+
// Should log warning about unsupported MIME type
817+
expect(consoleSpy).toHaveBeenCalledWith("Unsupported image MIME type: image/svg+xml")
818+
819+
expect(mockPushToolResult).toHaveBeenCalledWith(
820+
"Tool result: Generated content with different image types: (with 1 images)",
821+
)
822+
823+
consoleSpy.mockRestore()
824+
})
825+
826+
it("should ignore malformed image content missing data property", async () => {
827+
const block: ToolUse = {
828+
type: "tool_use",
829+
name: "use_mcp_tool",
830+
params: {
831+
server_name: "test_server",
832+
tool_name: "test_tool",
833+
arguments: '{"param": "value"}',
834+
},
835+
partial: false,
836+
}
837+
838+
mockAskApproval.mockResolvedValue(true)
839+
840+
const mockToolResult = {
841+
content: [
842+
{ type: "text", text: "Generated content with malformed image:" },
843+
{
844+
type: "image",
845+
// Missing data property
846+
mimeType: "image/png",
847+
},
848+
{
849+
type: "image",
850+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jU",
851+
mimeType: "image/png", // Valid image
852+
},
853+
],
854+
isError: false,
855+
}
856+
857+
mockProviderRef.deref.mockReturnValue({
858+
getMcpHub: () => ({
859+
callTool: vi.fn().mockResolvedValue(mockToolResult),
860+
}),
861+
postMessageToWebview: vi.fn(),
862+
getState: vi.fn().mockResolvedValue({
863+
mcpMaxImagesPerResponse: 20,
864+
mcpMaxImageSizeMB: 10,
865+
}),
866+
})
867+
868+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
869+
870+
await useMcpToolTool(
871+
mockTask as Task,
872+
block,
873+
mockAskApproval,
874+
mockHandleError,
875+
mockPushToolResult,
876+
mockRemoveClosingTag,
877+
)
878+
879+
// Should only include the valid image, not the malformed one
880+
const sayCall = (mockTask.say as any).mock.calls.find((call: any) => call[0] === "mcp_server_response")
881+
expect(sayCall[2]).toHaveLength(1)
882+
expect(sayCall[2][0]).toContain("data:image/png;base64,")
883+
884+
// Should log warning about missing data property
885+
expect(consoleSpy).toHaveBeenCalledWith("Invalid MCP ImageContent: missing data or mimeType")
886+
887+
expect(mockPushToolResult).toHaveBeenCalledWith(
888+
"Tool result: Generated content with malformed image: (with 1 images)",
889+
)
890+
891+
consoleSpy.mockRestore()
892+
})
893+
894+
it("should ignore malformed image content missing mimeType property", async () => {
895+
const block: ToolUse = {
896+
type: "tool_use",
897+
name: "use_mcp_tool",
898+
params: {
899+
server_name: "test_server",
900+
tool_name: "test_tool",
901+
arguments: '{"param": "value"}',
902+
},
903+
partial: false,
904+
}
905+
906+
mockAskApproval.mockResolvedValue(true)
907+
908+
const mockToolResult = {
909+
content: [
910+
{ type: "text", text: "Generated content with malformed image:" },
911+
{
912+
type: "image",
913+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jU",
914+
// Missing mimeType property
915+
},
916+
{
917+
type: "image",
918+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jU",
919+
mimeType: "image/png", // Valid image
920+
},
921+
],
922+
isError: false,
923+
}
924+
925+
mockProviderRef.deref.mockReturnValue({
926+
getMcpHub: () => ({
927+
callTool: vi.fn().mockResolvedValue(mockToolResult),
928+
}),
929+
postMessageToWebview: vi.fn(),
930+
getState: vi.fn().mockResolvedValue({
931+
mcpMaxImagesPerResponse: 20,
932+
mcpMaxImageSizeMB: 10,
933+
}),
934+
})
935+
936+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
937+
938+
await useMcpToolTool(
939+
mockTask as Task,
940+
block,
941+
mockAskApproval,
942+
mockHandleError,
943+
mockPushToolResult,
944+
mockRemoveClosingTag,
945+
)
946+
947+
// Should only include the valid image, not the malformed one
948+
const sayCall = (mockTask.say as any).mock.calls.find((call: any) => call[0] === "mcp_server_response")
949+
expect(sayCall[2]).toHaveLength(1)
950+
expect(sayCall[2][0]).toContain("data:image/png;base64,")
951+
952+
// Should log warning about missing mimeType property
953+
expect(consoleSpy).toHaveBeenCalledWith("Invalid MCP ImageContent: missing data or mimeType")
954+
955+
expect(mockPushToolResult).toHaveBeenCalledWith(
956+
"Tool result: Generated content with malformed image: (with 1 images)",
957+
)
958+
959+
consoleSpy.mockRestore()
960+
})
961+
758962
it("should handle user rejection", async () => {
759963
const block: ToolUse = {
760964
type: "tool_use",

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ describe("ClineProvider", () => {
554554
diagnosticsEnabled: true,
555555
openRouterImageApiKey: undefined,
556556
openRouterImageGenerationSelectedModel: undefined,
557+
mcpMaxImagesPerResponse: 10,
558+
mcpMaxImageSizeMB: 10,
557559
}
558560

559561
const message: ExtensionMessage = {

0 commit comments

Comments
 (0)