Implement comprehensive QoL improvements for MCP client
## Features Added - **Unique server name validation**: Prevent duplicate MCP server names to avoid tool routing conflicts - **Quick-add server examples**: Added example server buttons for "Everything" and "Test Server" configurations - **Conversation model selector**: Added dropdown to change AI models directly in chat interface - **Fixed nav bar height**: Conversation widget now properly accounts for navigation bar height ## User Experience Improvements - Server name uniqueness prevents tool naming conflicts and confusion - Quick-add buttons make it easier to get started with common MCP servers - Model selector allows switching between AI models without leaving conversation - Proper height calculation eliminates layout issues with conversation interface ## Technical Details - Server name validation happens before calling addMcpServer API - Model selector integrates with existing inference context - Height calculation uses CSS calc(100vh - 4rem) for nav bar compensation - All changes maintain backward compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
763742ed30
commit
8c46e57b49
3 changed files with 80 additions and 12 deletions
|
|
@ -16,7 +16,12 @@ export function ChatInterface() {
|
||||||
getAgentLoopState,
|
getAgentLoopState,
|
||||||
} = useConversation();
|
} = useConversation();
|
||||||
|
|
||||||
const { provider: currentProvider } = useInference();
|
const {
|
||||||
|
models,
|
||||||
|
selectedModel,
|
||||||
|
selectModel,
|
||||||
|
isAuthenticated
|
||||||
|
} = useInference();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
@ -29,7 +34,7 @@ export function ChatInterface() {
|
||||||
}, [activeConversation?.messages]);
|
}, [activeConversation?.messages]);
|
||||||
|
|
||||||
const handleSendMessage = async (content: string) => {
|
const handleSendMessage = async (content: string) => {
|
||||||
if (!activeConversationId || !currentProvider?.isAuthenticated) {
|
if (!activeConversationId || !isAuthenticated) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +70,7 @@ export function ChatInterface() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show auth prompt if not authenticated
|
// Show auth prompt if not authenticated
|
||||||
if (!currentProvider?.isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex items-center justify-center p-8">
|
<div className="flex-1 flex items-center justify-center p-8">
|
||||||
<InferenceLogin />
|
<InferenceLogin />
|
||||||
|
|
@ -80,13 +85,28 @@ export function ChatInterface() {
|
||||||
{/* Chat Header */}
|
{/* Chat Header */}
|
||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div className="flex-1">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
{activeConversation.title}
|
{activeConversation.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
<div className="flex items-center space-x-4 mt-1">
|
||||||
Model: {currentProvider.selectedModel?.name || 'None selected'}
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
</p>
|
Model:
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={selectedModel?.id || ''}
|
||||||
|
onChange={(e) => selectModel(e.target.value)}
|
||||||
|
disabled={isGenerating || !isAuthenticated}
|
||||||
|
className="text-sm bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-gray-900 dark:text-gray-100 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<option value="">Select a model</option>
|
||||||
|
{models.map((model) => (
|
||||||
|
<option key={model.id} value={model.id}>
|
||||||
|
{model.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isGenerating && (
|
{isGenerating && (
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function ConversationApp() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationProvider>
|
<ConversationProvider>
|
||||||
<div className="h-screen flex bg-gray-100 dark:bg-gray-900">
|
<div className="h-[calc(100vh-4rem)] flex bg-gray-100 dark:bg-gray-900">
|
||||||
{/* Left Sidebar - Conversations */}
|
{/* Left Sidebar - Conversations */}
|
||||||
<div className="w-80 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
<div className="w-80 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,17 @@ export function MCPTest() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const trimmedName = newServerName.trim();
|
||||||
|
|
||||||
|
// Check for duplicate server names
|
||||||
|
const existingServer = connections.find(conn => conn.name === trimmedName);
|
||||||
|
if (existingServer) {
|
||||||
|
alert(`A server with the name "${trimmedName}" already exists. Please choose a different name.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const config: MCPServerConfig = {
|
const config: MCPServerConfig = {
|
||||||
name: newServerName.trim(),
|
name: trimmedName,
|
||||||
url: newServerUrl.trim(),
|
url: newServerUrl.trim(),
|
||||||
transport: 'auto',
|
transport: 'auto',
|
||||||
authType: 'none',
|
authType: 'none',
|
||||||
|
|
@ -48,7 +57,7 @@ export function MCPTest() {
|
||||||
console.error('Failed to add server:', error);
|
console.error('Failed to add server:', error);
|
||||||
alert(`Failed to add server: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
alert(`Failed to add server: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
}
|
}
|
||||||
}, [newServerName, newServerUrl, addMcpServer]);
|
}, [newServerName, newServerUrl, addMcpServer, connections]);
|
||||||
|
|
||||||
const handleAddServerWithOAuth = useCallback(async () => {
|
const handleAddServerWithOAuth = useCallback(async () => {
|
||||||
if (!newServerName.trim() || !newServerUrl.trim()) {
|
if (!newServerName.trim() || !newServerUrl.trim()) {
|
||||||
|
|
@ -56,8 +65,17 @@ export function MCPTest() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const trimmedName = newServerName.trim();
|
||||||
|
|
||||||
|
// Check for duplicate server names
|
||||||
|
const existingServer = connections.find(conn => conn.name === trimmedName);
|
||||||
|
if (existingServer) {
|
||||||
|
alert(`A server with the name "${trimmedName}" already exists. Please choose a different name.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const config: MCPServerConfig = {
|
const config: MCPServerConfig = {
|
||||||
name: newServerName.trim(),
|
name: trimmedName,
|
||||||
url: newServerUrl.trim(),
|
url: newServerUrl.trim(),
|
||||||
transport: 'auto',
|
transport: 'auto',
|
||||||
authType: 'oauth',
|
authType: 'oauth',
|
||||||
|
|
@ -72,7 +90,7 @@ export function MCPTest() {
|
||||||
console.error('Failed to add server with OAuth:', error);
|
console.error('Failed to add server with OAuth:', error);
|
||||||
alert(`Failed to add server with OAuth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
alert(`Failed to add server with OAuth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
}
|
}
|
||||||
}, [newServerName, newServerUrl, addMcpServer]);
|
}, [newServerName, newServerUrl, addMcpServer, connections]);
|
||||||
|
|
||||||
const handleRemoveServer = useCallback((connectionId: string) => {
|
const handleRemoveServer = useCallback((connectionId: string) => {
|
||||||
if (confirm('Are you sure you want to remove this server?')) {
|
if (confirm('Are you sure you want to remove this server?')) {
|
||||||
|
|
@ -151,6 +169,36 @@ export function MCPTest() {
|
||||||
Add MCP Server
|
Add MCP Server
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
{/* Quick Add Examples */}
|
||||||
|
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4">
|
||||||
|
<h3 className="text-sm font-medium text-blue-900 dark:text-blue-100 mb-3">
|
||||||
|
Quick Add Examples
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setNewServerName('Everything');
|
||||||
|
setNewServerUrl('https://example-server.modelcontextprotocol.io/sse');
|
||||||
|
}}
|
||||||
|
className="px-3 py-1 text-xs bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Everything Server
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setNewServerName('Test Server');
|
||||||
|
setNewServerUrl('https://localhost:3000/mcp');
|
||||||
|
}}
|
||||||
|
className="px-3 py-1 text-xs bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Local Test Server
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-blue-700 dark:text-blue-300 mt-2">
|
||||||
|
Click to auto-fill common server configurations
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue