mcp: add icons

This commit is contained in:
Jesse Lumarie 2025-10-18 20:42:46 -06:00
commit 9dc3b748a8
7 changed files with 129 additions and 0 deletions

View file

@ -249,6 +249,7 @@ const App = () => {
const {
connectionStatus,
serverCapabilities,
serverImplementation,
mcpClient,
requestHistory,
clearRequestHistory,
@ -920,6 +921,7 @@ const App = () => {
loggingSupported={!!serverCapabilities?.logging || false}
connectionType={connectionType}
setConnectionType={setConnectionType}
serverImplementation={serverImplementation}
/>
<div
onMouseDown={handleSidebarDragStart}

View file

@ -49,6 +49,7 @@ const disconnectedConnectionState = {
completionsSupported: false,
connect: jest.fn(),
disconnect: jest.fn(),
serverImplementation: null,
};
// Connected state for tests that need an active connection

View file

@ -0,0 +1,57 @@
// Define Icon type locally since it might not be exported yet
interface Icon {
src: string;
mimeType?: string;
sizes?: string[];
}
// Helper type for objects that may have icons
export interface WithIcons {
icons?: Icon[];
}
interface IconDisplayProps {
icons?: Icon[];
className?: string;
size?: "sm" | "md" | "lg";
}
const IconDisplay = ({
icons,
className = "",
size = "md",
}: IconDisplayProps) => {
if (!icons || icons.length === 0) {
return null;
}
const sizeClasses = {
sm: "w-4 h-4",
md: "w-6 h-6",
lg: "w-8 h-8",
};
const sizeClass = sizeClasses[size];
return (
<div className={`flex gap-1 ${className}`}>
{icons.map((icon, index) => (
<img
key={index}
src={icon.src}
alt=""
className={`${sizeClass} object-contain flex-shrink-0`}
style={{
imageRendering: "auto",
}}
onError={(e) => {
// Hide broken images
e.currentTarget.style.display = "none";
}}
/>
))}
</div>
);
};
export default IconDisplay;

View file

@ -14,6 +14,7 @@ import { useEffect, useState } from "react";
import ListPane from "./ListPane";
import { useCompletionState } from "@/lib/hooks/useCompletionState";
import JsonView from "./JsonView";
import IconDisplay from "./IconDisplay";
export type Prompt = {
name: string;
@ -23,6 +24,7 @@ export type Prompt = {
description?: string;
required?: boolean;
}[];
icons?: { src: string; mimeType?: string; sizes?: string[] }[];
};
const PromptsTab = ({
@ -109,6 +111,7 @@ const PromptsTab = ({
}}
renderItem={(prompt) => (
<div className="flex flex-col items-start">
<IconDisplay icons={prompt.icons} size="sm" />
<span className="flex-1">{prompt.name}</span>
<span className="text-sm text-gray-500 text-left">
{prompt.description}

View file

@ -14,6 +14,7 @@ import {
RefreshCwOff,
Copy,
CheckCheck,
Server,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -40,6 +41,7 @@ import {
import CustomHeaders from "./CustomHeaders";
import { CustomHeaders as CustomHeadersType } from "@/lib/types/customHeaders";
import { useToast } from "../lib/hooks/useToast";
import IconDisplay, { WithIcons } from "./IconDisplay";
interface SidebarProps {
connectionStatus: ConnectionStatus;
@ -71,6 +73,9 @@ interface SidebarProps {
setConfig: (config: InspectorConfig) => void;
connectionType: "direct" | "proxy";
setConnectionType: (type: "direct" | "proxy") => void;
serverImplementation?:
| (WithIcons & { name?: string; version?: string; websiteUrl?: string })
| null;
}
const Sidebar = ({
@ -102,6 +107,7 @@ const Sidebar = ({
setConfig,
connectionType,
setConnectionType,
serverImplementation,
}: SidebarProps) => {
const [theme, setTheme] = useTheme();
const [showEnvVars, setShowEnvVars] = useState(false);
@ -776,6 +782,45 @@ const Sidebar = ({
</span>
</div>
{connectionStatus === "connected" && serverImplementation && (
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg mb-4">
<div className="flex items-center gap-2 mb-1">
{(serverImplementation as WithIcons).icons &&
(serverImplementation as WithIcons).icons!.length > 0 ? (
<IconDisplay
icons={(serverImplementation as WithIcons).icons}
size="sm"
/>
) : (
<Server className="w-4 h-4 text-gray-500" />
)}
{(serverImplementation as { websiteUrl?: string })
.websiteUrl ? (
<a
href={
(serverImplementation as { websiteUrl?: string })
.websiteUrl
}
target="_blank"
rel="noopener noreferrer"
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"
>
{serverImplementation.name || "MCP Server"}
</a>
) : (
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
{serverImplementation.name || "MCP Server"}
</span>
)}
</div>
{serverImplementation.version && (
<div className="text-xs text-gray-500 dark:text-gray-400">
Version: {serverImplementation.version}
</div>
)}
</div>
)}
{loggingSupported && connectionStatus === "connected" && (
<div className="space-y-2">
<label

View file

@ -39,6 +39,7 @@ import JsonView from "./JsonView";
import ToolResults from "./ToolResults";
import { useToast } from "@/lib/hooks/useToast";
import useCopy from "@/lib/hooks/useCopy";
import IconDisplay, { WithIcons } from "./IconDisplay";
// Type guard to safely detect the optional _meta field without using `any`
const hasMeta = (tool: Tool): tool is Tool & { _meta: unknown } =>
@ -120,6 +121,7 @@ const ToolsTab = ({
setSelectedItem={setSelectedTool}
renderItem={(tool) => (
<div className="flex flex-col items-start">
<IconDisplay icons={(tool as WithIcons).icons} size="sm" />
<span className="flex-1">{tool.name}</span>
<span className="text-sm text-gray-500 text-left line-clamp-3">
{tool.description}
@ -136,6 +138,17 @@ const ToolsTab = ({
<h3 className="font-semibold">
{selectedTool ? selectedTool.name : "Select a tool"}
</h3>
<div className="flex items-center gap-2">
{selectedTool && (
<IconDisplay
icons={(selectedTool as WithIcons).icons}
size="md"
/>
)}
<h3 className="font-semibold">
{selectedTool ? selectedTool.name : "Select a tool"}
</h3>
</div>
</div>
<div className="p-4">
{selectedTool ? (

View file

@ -30,6 +30,7 @@ import {
Progress,
LoggingLevel,
ElicitRequestSchema,
Implementation,
} from "@modelcontextprotocol/sdk/types.js";
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { useEffect, useState } from "react";
@ -80,6 +81,7 @@ interface UseConnectionOptions {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getRoots?: () => any[];
defaultLoggingLevel?: LoggingLevel;
serverImplementation?: Implementation;
}
export function useConnection({
@ -117,6 +119,8 @@ export function useConnection({
const [mcpProtocolVersion, setMcpProtocolVersion] = useState<string | null>(
null,
);
const [serverImplementation, setServerImplementation] =
useState<Implementation | null>(null);
useEffect(() => {
if (!oauthClientId) {
@ -684,6 +688,8 @@ export function useConnection({
setClientTransport(transport);
capabilities = client.getServerCapabilities();
const serverInfo = client.getServerVersion();
setServerImplementation(serverInfo || null);
const initializeRequest = {
method: "initialize",
};
@ -801,11 +807,13 @@ export function useConnection({
const clearRequestHistory = () => {
setRequestHistory([]);
setServerImplementation(null);
};
return {
connectionStatus,
serverCapabilities,
serverImplementation,
mcpClient,
requestHistory,
clearRequestHistory,