feat: add mcp tab

This commit is contained in:
d-kimsuon
2025-09-03 20:03:47 +09:00
parent 7800037455
commit 155afeaf70
4 changed files with 168 additions and 3 deletions

View File

@@ -0,0 +1,96 @@
"use client";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { RefreshCwIcon } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { honoClient } from "@/lib/api/client";
export const McpTab: FC = () => {
const queryClient = useQueryClient();
const {
data: mcpData,
isLoading,
error,
} = useQuery({
queryKey: ["mcp", "list"],
queryFn: async () => {
const response = await honoClient.api.mcp.list.$get();
if (!response.ok) {
throw new Error("Failed to fetch MCP servers");
}
return response.json();
},
});
const handleReload = () => {
queryClient.invalidateQueries({ queryKey: ["mcp", "list"] });
};
return (
<div className="flex flex-col h-full">
<div className="p-3 border-b border-sidebar-border">
<div className="flex items-center justify-between">
<h2 className="text-sm font-semibold text-sidebar-foreground">
MCP Servers
</h2>
<Button
onClick={handleReload}
variant="ghost"
size="sm"
className="h-7 w-7 p-0"
disabled={isLoading}
title="Reload MCP servers"
>
<RefreshCwIcon
className={`w-3 h-3 ${isLoading ? "animate-spin" : ""}`}
/>
</Button>
</div>
</div>
<div className="flex-1 overflow-auto p-3">
{isLoading && (
<div className="flex items-center justify-center h-32">
<div className="text-sm text-muted-foreground">Loading...</div>
</div>
)}
{error && (
<div className="text-sm text-red-500">
Failed to load MCP servers: {(error as Error).message}
</div>
)}
{mcpData && mcpData.servers.length === 0 && (
<div className="text-sm text-muted-foreground text-center py-8">
No MCP servers found
</div>
)}
{mcpData && mcpData.servers.length > 0 && (
<div className="space-y-3">
{mcpData.servers.map((server) => (
<div
key={server.name}
className="p-3 bg-sidebar-accent/50 rounded-md border border-sidebar-border"
>
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<h3 className="text-sm font-medium text-sidebar-foreground truncate">
{server.name}
</h3>
<p className="text-xs text-muted-foreground mt-1 font-mono break-all">
{server.command}
</p>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
};

View File

@@ -1,10 +1,11 @@
"use client";
import { MessageSquareIcon, SettingsIcon } from "lucide-react";
import { MessageSquareIcon, PlugIcon, SettingsIcon } from "lucide-react";
import { type FC, useState } from "react";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { useProject } from "../../../../hooks/useProject";
import { McpTab } from "./McpTab";
import { SessionsTab } from "./SessionsTab";
import { SettingsTab } from "./SettingsTab";
@@ -24,12 +25,12 @@ export const SessionSidebar: FC<{
const {
data: { sessions },
} = useProject(projectId);
const [activeTab, setActiveTab] = useState<"sessions" | "settings">(
const [activeTab, setActiveTab] = useState<"sessions" | "mcp" | "settings">(
"sessions",
);
const [isExpanded, setIsExpanded] = useState(true);
const handleTabClick = (tab: "sessions" | "settings") => {
const handleTabClick = (tab: "sessions" | "mcp" | "settings") => {
if (activeTab === tab && isExpanded) {
// If clicking the active tab while expanded, collapse
setIsExpanded(false);
@@ -50,6 +51,8 @@ export const SessionSidebar: FC<{
projectId={projectId}
/>
);
case "mcp":
return <McpTab />;
case "settings":
return <SettingsTab />;
default:
@@ -82,6 +85,21 @@ export const SessionSidebar: FC<{
<MessageSquareIcon className="w-4 h-4" />
</button>
<button
type="button"
onClick={() => handleTabClick("mcp")}
className={cn(
"w-8 h-8 flex items-center justify-center rounded-md transition-colors",
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
activeTab === "mcp" && isExpanded
? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
: "text-sidebar-foreground/70",
)}
title="MCP Servers"
>
<PlugIcon className="w-4 h-4" />
</button>
<button
type="button"
onClick={() => handleTabClick("settings")}