Skip to content

Commit 9dc3b74

Browse files
committed
mcp: add icons
1 parent f216d42 commit 9dc3b74

File tree

7 files changed

+129
-0
lines changed

7 files changed

+129
-0
lines changed

client/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ const App = () => {
249249
const {
250250
connectionStatus,
251251
serverCapabilities,
252+
serverImplementation,
252253
mcpClient,
253254
requestHistory,
254255
clearRequestHistory,
@@ -920,6 +921,7 @@ const App = () => {
920921
loggingSupported={!!serverCapabilities?.logging || false}
921922
connectionType={connectionType}
922923
setConnectionType={setConnectionType}
924+
serverImplementation={serverImplementation}
923925
/>
924926
<div
925927
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ 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 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 = ({
@@ -109,6 +111,7 @@ const PromptsTab = ({
109111
}}
110112
renderItem={(prompt) => (
111113
<div className="flex flex-col items-start">
114+
<IconDisplay icons={prompt.icons} size="sm" />
112115
<span className="flex-1">{prompt.name}</span>
113116
<span className="text-sm text-gray-500 text-left">
114117
{prompt.description}

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import JsonView from "./JsonView";
3939
import ToolResults from "./ToolResults";
4040
import { useToast } from "@/lib/hooks/useToast";
4141
import useCopy from "@/lib/hooks/useCopy";
42+
import IconDisplay, { WithIcons } from "./IconDisplay";
4243

4344
// Type guard to safely detect the optional _meta field without using `any`
4445
const hasMeta = (tool: Tool): tool is Tool & { _meta: unknown } =>
@@ -120,6 +121,7 @@ const ToolsTab = ({
120121
setSelectedItem={setSelectedTool}
121122
renderItem={(tool) => (
122123
<div className="flex flex-col items-start">
124+
<IconDisplay icons={(tool as WithIcons).icons} size="sm" />
123125
<span className="flex-1">{tool.name}</span>
124126
<span className="text-sm text-gray-500 text-left line-clamp-3">
125127
{tool.description}
@@ -136,6 +138,17 @@ const ToolsTab = ({
136138
<h3 className="font-semibold">
137139
{selectedTool ? selectedTool.name : "Select a tool"}
138140
</h3>
141+
<div className="flex items-center gap-2">
142+
{selectedTool && (
143+
<IconDisplay
144+
icons={(selectedTool as WithIcons).icons}
145+
size="md"
146+
/>
147+
)}
148+
<h3 className="font-semibold">
149+
{selectedTool ? selectedTool.name : "Select a tool"}
150+
</h3>
151+
</div>
139152
</div>
140153
<div className="p-4">
141154
{selectedTool ? (

client/src/lib/hooks/useConnection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
Progress,
3131
LoggingLevel,
3232
ElicitRequestSchema,
33+
Implementation,
3334
} from "@modelcontextprotocol/sdk/types.js";
3435
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
3536
import { useEffect, useState } from "react";
@@ -80,6 +81,7 @@ interface UseConnectionOptions {
8081
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8182
getRoots?: () => any[];
8283
defaultLoggingLevel?: LoggingLevel;
84+
serverImplementation?: Implementation;
8385
}
8486

8587
export function useConnection({
@@ -117,6 +119,8 @@ export function useConnection({
117119
const [mcpProtocolVersion, setMcpProtocolVersion] = useState<string | null>(
118120
null,
119121
);
122+
const [serverImplementation, setServerImplementation] =
123+
useState<Implementation | null>(null);
120124

121125
useEffect(() => {
122126
if (!oauthClientId) {
@@ -684,6 +688,8 @@ export function useConnection({
684688
setClientTransport(transport);
685689

686690
capabilities = client.getServerCapabilities();
691+
const serverInfo = client.getServerVersion();
692+
setServerImplementation(serverInfo || null);
687693
const initializeRequest = {
688694
method: "initialize",
689695
};
@@ -801,11 +807,13 @@ export function useConnection({
801807

802808
const clearRequestHistory = () => {
803809
setRequestHistory([]);
810+
setServerImplementation(null);
804811
};
805812

806813
return {
807814
connectionStatus,
808815
serverCapabilities,
816+
serverImplementation,
809817
mcpClient,
810818
requestHistory,
811819
clearRequestHistory,

0 commit comments

Comments
 (0)