diff --git a/client/src/components/ResourceLinkView.tsx b/client/src/components/ResourceLinkView.tsx
new file mode 100644
index 00000000..94fce429
--- /dev/null
+++ b/client/src/components/ResourceLinkView.tsx
@@ -0,0 +1,104 @@
+import { useState, useCallback, useEffect } from "react";
+import { Copy, CheckCheck } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { useToast } from "@/lib/hooks/useToast";
+
+interface ResourceLinkViewProps {
+ uri: string;
+ name?: string;
+ description?: string;
+ mimeType?: string;
+}
+
+const ResourceLinkView = ({
+ uri,
+ name,
+ description,
+ mimeType,
+}: ResourceLinkViewProps) => {
+ const { toast } = useToast();
+ const [copied, setCopied] = useState(false);
+
+ useEffect(() => {
+ let timeoutId: NodeJS.Timeout;
+ if (copied) {
+ timeoutId = setTimeout(() => {
+ setCopied(false);
+ }, 500);
+ }
+ return () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ };
+ }, [copied]);
+
+ const handleCopyUri = useCallback(() => {
+ try {
+ navigator.clipboard.writeText(uri);
+ setCopied(true);
+ } catch (error) {
+ toast({
+ title: "Error",
+ description: `There was an error copying URI to clipboard: ${error instanceof Error ? error.message : String(error)}`,
+ variant: "destructive",
+ });
+ }
+ }, [uri, toast]);
+
+ const displayName = name || new URL(uri).pathname.split("/").pop() || uri;
+
+ return (
+
+
+
+
+
+
+ {uri}
+
+ {mimeType && (
+
+ {mimeType}
+
+ )}
+
+
+ {name && (
+
+ {name}
+
+ )}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ );
+};
+
+export default ResourceLinkView;
diff --git a/client/src/components/ToolResults.tsx b/client/src/components/ToolResults.tsx
index c6d90700..cf6bb90d 100644
--- a/client/src/components/ToolResults.tsx
+++ b/client/src/components/ToolResults.tsx
@@ -1,4 +1,5 @@
import JsonView from "./JsonView";
+import ResourceLinkView from "./ResourceLinkView";
import {
CallToolResultSchema,
CompatibilityCallToolResult,
@@ -200,6 +201,14 @@ const ToolResults = ({ toolResult, selectedTool }: ToolResultsProps) => {
) : (
))}
+ {item.type === "resource_link" && (
+
+ )}
))}
diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx
index c9f9b315..f954b6bd 100644
--- a/client/src/components/__tests__/ToolsTab.test.tsx
+++ b/client/src/components/__tests__/ToolsTab.test.tsx
@@ -360,4 +360,29 @@ describe("ToolsTab", () => {
).not.toBeInTheDocument();
});
});
+
+ describe("Resource Link Content Type", () => {
+ it("should render resource_link content type", () => {
+ const result = {
+ content: [
+ {
+ type: "resource_link",
+ uri: "https://example.com/resource",
+ name: "Test Resource",
+ description: "A test resource",
+ mimeType: "application/json",
+ },
+ ],
+ };
+
+ renderToolsTab({ selectedTool: mockTools[0], toolResult: result });
+
+ expect(
+ screen.getByText("https://example.com/resource"),
+ ).toBeInTheDocument();
+ expect(screen.getByText("Test Resource")).toBeInTheDocument();
+ expect(screen.getByText("A test resource")).toBeInTheDocument();
+ expect(screen.getByText("application/json")).toBeInTheDocument();
+ });
+ });
});