Skip to content

Commit 11e4e10

Browse files
authored
Merge pull request #778 from jesselumarie/jesselumarie/test-SEP-974
mcp: add icon + website for testing SEP-973
2 parents bb3a2a1 + 7b644f9 commit 11e4e10

File tree

8 files changed

+191
-29
lines changed

8 files changed

+191
-29
lines changed

client/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ const App = () => {
300300
const {
301301
connectionStatus,
302302
serverCapabilities,
303+
serverImplementation,
303304
mcpClient,
304305
requestHistory,
305306
clearRequestHistory,
@@ -982,6 +983,7 @@ const App = () => {
982983
loggingSupported={!!serverCapabilities?.logging || false}
983984
connectionType={connectionType}
984985
setConnectionType={setConnectionType}
986+
serverImplementation={serverImplementation}
985987
/>
986988
<div
987989
onMouseDown={handleSidebarDragStart}

client/src/__tests__/App.routing.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const disconnectedConnectionState = {
4949
completionsSupported: false,
5050
connect: jest.fn(),
5151
disconnect: jest.fn(),
52+
serverImplementation: null,
5253
};
5354

5455
// Connected state for tests that need an active connection
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Define Icon type locally since it might not be exported yet
2+
interface Icon {
3+
src: string;
4+
mimeType?: string;
5+
sizes?: string[];
6+
}
7+
8+
// Helper type for objects that may have icons
9+
export interface WithIcons {
10+
icons?: Icon[];
11+
}
12+
13+
interface IconDisplayProps {
14+
icons?: Icon[];
15+
className?: string;
16+
size?: "sm" | "md" | "lg";
17+
}
18+
19+
const IconDisplay = ({
20+
icons,
21+
className = "",
22+
size = "md",
23+
}: IconDisplayProps) => {
24+
if (!icons || icons.length === 0) {
25+
return null;
26+
}
27+
28+
const sizeClasses = {
29+
sm: "w-4 h-4",
30+
md: "w-6 h-6",
31+
lg: "w-8 h-8",
32+
};
33+
34+
const sizeClass = sizeClasses[size];
35+
36+
return (
37+
<div className={`flex gap-1 ${className}`}>
38+
{icons.map((icon, index) => (
39+
<img
40+
key={index}
41+
src={icon.src}
42+
alt=""
43+
className={`${sizeClass} object-contain flex-shrink-0`}
44+
style={{
45+
imageRendering: "auto",
46+
}}
47+
onError={(e) => {
48+
// Hide broken images
49+
e.currentTarget.style.display = "none";
50+
}}
51+
/>
52+
))}
53+
</div>
54+
);
55+
};
56+
57+
export default IconDisplay;

client/src/components/PromptsTab.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import {
99
PromptReference,
1010
ResourceReference,
1111
} from "@modelcontextprotocol/sdk/types.js";
12-
import { AlertCircle } from "lucide-react";
12+
import { AlertCircle, ChevronRight } from "lucide-react";
1313
import { useEffect, useState } from "react";
1414
import ListPane from "./ListPane";
1515
import { useCompletionState } from "@/lib/hooks/useCompletionState";
1616
import JsonView from "./JsonView";
17+
import IconDisplay, { WithIcons } from "./IconDisplay";
1718

1819
export type Prompt = {
1920
name: string;
@@ -23,6 +24,7 @@ export type Prompt = {
2324
description?: string;
2425
required?: boolean;
2526
}[];
27+
icons?: { src: string; mimeType?: string; sizes?: string[] }[];
2628
};
2729

2830
const PromptsTab = ({
@@ -108,11 +110,17 @@ const PromptsTab = ({
108110
setPromptArgs({});
109111
}}
110112
renderItem={(prompt) => (
111-
<div className="flex flex-col items-start">
112-
<span className="flex-1">{prompt.name}</span>
113-
<span className="text-sm text-gray-500 text-left">
114-
{prompt.description}
115-
</span>
113+
<div className="flex items-start w-full gap-2">
114+
<div className="flex-shrink-0 mt-1">
115+
<IconDisplay icons={prompt.icons} size="sm" />
116+
</div>
117+
<div className="flex flex-col flex-1 min-w-0">
118+
<span className="truncate">{prompt.name}</span>
119+
<span className="text-sm text-gray-500 text-left line-clamp-2">
120+
{prompt.description}
121+
</span>
122+
</div>
123+
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400 mt-1" />
116124
</div>
117125
)}
118126
title="Prompts"
@@ -122,9 +130,17 @@ const PromptsTab = ({
122130

123131
<div className="bg-card border border-border rounded-lg shadow">
124132
<div className="p-4 border-b border-gray-200 dark:border-border">
125-
<h3 className="font-semibold">
126-
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
127-
</h3>
133+
<div className="flex items-center gap-2">
134+
{selectedPrompt && (
135+
<IconDisplay
136+
icons={(selectedPrompt as WithIcons).icons}
137+
size="md"
138+
/>
139+
)}
140+
<h3 className="font-semibold">
141+
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
142+
</h3>
143+
</div>
128144
</div>
129145
<div className="p-4">
130146
{error ? (

client/src/components/ResourcesTab.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useEffect, useState } from "react";
1717
import { useCompletionState } from "@/lib/hooks/useCompletionState";
1818
import JsonView from "./JsonView";
1919
import { UriTemplate } from "@modelcontextprotocol/sdk/shared/uriTemplate.js";
20+
import IconDisplay, { WithIcons } from "./IconDisplay";
2021

2122
const ResourcesTab = ({
2223
resources,
@@ -129,7 +130,10 @@ const ResourcesTab = ({
129130
}}
130131
renderItem={(resource) => (
131132
<div className="flex items-center w-full">
132-
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
133+
<IconDisplay icons={(resource as WithIcons).icons} size="sm" />
134+
{!(resource as WithIcons).icons && (
135+
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
136+
)}
133137
<span className="flex-1 truncate" title={resource.uri.toString()}>
134138
{resource.name}
135139
</span>
@@ -159,7 +163,10 @@ const ResourcesTab = ({
159163
}}
160164
renderItem={(template) => (
161165
<div className="flex items-center w-full">
162-
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
166+
<IconDisplay icons={(template as WithIcons).icons} size="sm" />
167+
{!(template as WithIcons).icons && (
168+
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
169+
)}
163170
<span className="flex-1 truncate" title={template.uriTemplate}>
164171
{template.name}
165172
</span>
@@ -175,16 +182,26 @@ const ResourcesTab = ({
175182

176183
<div className="bg-card border border-border rounded-lg shadow">
177184
<div className="p-4 border-b border-gray-200 dark:border-border flex justify-between items-center">
178-
<h3
179-
className="font-semibold truncate"
180-
title={selectedResource?.name || selectedTemplate?.name}
181-
>
182-
{selectedResource
183-
? selectedResource.name
184-
: selectedTemplate
185-
? selectedTemplate.name
186-
: "Select a resource or template"}
187-
</h3>
185+
<div className="flex items-center gap-2 truncate">
186+
{(selectedResource || selectedTemplate) && (
187+
<IconDisplay
188+
icons={
189+
((selectedResource || selectedTemplate) as WithIcons).icons
190+
}
191+
size="md"
192+
/>
193+
)}
194+
<h3
195+
className="font-semibold truncate"
196+
title={selectedResource?.name || selectedTemplate?.name}
197+
>
198+
{selectedResource
199+
? selectedResource.name
200+
: selectedTemplate
201+
? selectedTemplate.name
202+
: "Select a resource or template"}
203+
</h3>
204+
</div>
188205
{selectedResource && (
189206
<div className="flex row-auto gap-1 justify-end w-2/5">
190207
{resourceSubscriptionsSupported &&

client/src/components/Sidebar.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
RefreshCwOff,
1515
Copy,
1616
CheckCheck,
17+
Server,
1718
} from "lucide-react";
1819
import { Button } from "@/components/ui/button";
1920
import { Input } from "@/components/ui/input";
@@ -40,6 +41,7 @@ import {
4041
import CustomHeaders from "./CustomHeaders";
4142
import { CustomHeaders as CustomHeadersType } from "@/lib/types/customHeaders";
4243
import { useToast } from "../lib/hooks/useToast";
44+
import IconDisplay, { WithIcons } from "./IconDisplay";
4345

4446
interface SidebarProps {
4547
connectionStatus: ConnectionStatus;
@@ -71,6 +73,9 @@ interface SidebarProps {
7173
setConfig: (config: InspectorConfig) => void;
7274
connectionType: "direct" | "proxy";
7375
setConnectionType: (type: "direct" | "proxy") => void;
76+
serverImplementation?:
77+
| (WithIcons & { name?: string; version?: string; websiteUrl?: string })
78+
| null;
7479
}
7580

7681
const Sidebar = ({
@@ -102,6 +107,7 @@ const Sidebar = ({
102107
setConfig,
103108
connectionType,
104109
setConnectionType,
110+
serverImplementation,
105111
}: SidebarProps) => {
106112
const [theme, setTheme] = useTheme();
107113
const [showEnvVars, setShowEnvVars] = useState(false);
@@ -776,6 +782,45 @@ const Sidebar = ({
776782
</span>
777783
</div>
778784

785+
{connectionStatus === "connected" && serverImplementation && (
786+
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg mb-4">
787+
<div className="flex items-center gap-2 mb-1">
788+
{(serverImplementation as WithIcons).icons &&
789+
(serverImplementation as WithIcons).icons!.length > 0 ? (
790+
<IconDisplay
791+
icons={(serverImplementation as WithIcons).icons}
792+
size="sm"
793+
/>
794+
) : (
795+
<Server className="w-4 h-4 text-gray-500" />
796+
)}
797+
{(serverImplementation as { websiteUrl?: string })
798+
.websiteUrl ? (
799+
<a
800+
href={
801+
(serverImplementation as { websiteUrl?: string })
802+
.websiteUrl
803+
}
804+
target="_blank"
805+
rel="noopener noreferrer"
806+
className="text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline transition-colors"
807+
>
808+
{serverImplementation.name || "MCP Server"}
809+
</a>
810+
) : (
811+
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
812+
{serverImplementation.name || "MCP Server"}
813+
</span>
814+
)}
815+
</div>
816+
{serverImplementation.version && (
817+
<div className="text-xs text-gray-500 dark:text-gray-400">
818+
Version: {serverImplementation.version}
819+
</div>
820+
)}
821+
</div>
822+
)}
823+
779824
{loggingSupported && connectionStatus === "connected" && (
780825
<div className="space-y-2">
781826
<label

client/src/components/ToolsTab.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
Send,
3131
ChevronDown,
3232
ChevronUp,
33+
ChevronRight,
3334
AlertCircle,
3435
Copy,
3536
CheckCheck,
@@ -40,6 +41,7 @@ import JsonView from "./JsonView";
4041
import ToolResults from "./ToolResults";
4142
import { useToast } from "@/lib/hooks/useToast";
4243
import useCopy from "@/lib/hooks/useCopy";
44+
import IconDisplay, { WithIcons } from "./IconDisplay";
4345
import { cn } from "@/lib/utils";
4446
import {
4547
META_NAME_RULES_MESSAGE,
@@ -158,11 +160,17 @@ const ToolsTab = ({
158160
}}
159161
setSelectedItem={setSelectedTool}
160162
renderItem={(tool) => (
161-
<div className="flex flex-col items-start">
162-
<span className="flex-1">{tool.name}</span>
163-
<span className="text-sm text-gray-500 text-left line-clamp-3">
164-
{tool.description}
165-
</span>
163+
<div className="flex items-start w-full gap-2">
164+
<div className="flex-shrink-0 mt-1">
165+
<IconDisplay icons={(tool as WithIcons).icons} size="sm" />
166+
</div>
167+
<div className="flex flex-col flex-1 min-w-0">
168+
<span className="truncate">{tool.name}</span>
169+
<span className="text-sm text-gray-500 text-left line-clamp-2">
170+
{tool.description}
171+
</span>
172+
</div>
173+
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400 mt-1" />
166174
</div>
167175
)}
168176
title="Tools"
@@ -172,9 +180,17 @@ const ToolsTab = ({
172180

173181
<div className="bg-card border border-border rounded-lg shadow">
174182
<div className="p-4 border-b border-gray-200 dark:border-border">
175-
<h3 className="font-semibold">
176-
{selectedTool ? selectedTool.name : "Select a tool"}
177-
</h3>
183+
<div className="flex items-center gap-2">
184+
{selectedTool && (
185+
<IconDisplay
186+
icons={(selectedTool as WithIcons).icons}
187+
size="md"
188+
/>
189+
)}
190+
<h3 className="font-semibold">
191+
{selectedTool ? selectedTool.name : "Select a tool"}
192+
</h3>
193+
</div>
178194
</div>
179195
<div className="p-4">
180196
{selectedTool ? (

0 commit comments

Comments
 (0)