Files
nexus-mcp/src/components/MCPGuideTab.tsx

1196 lines
55 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef } from 'react';
import {
Cpu,
Copy,
Check,
Terminal,
Play,
Send,
Radio,
Eye,
FileText,
Zap,
Activity,
Layers,
Sparkles,
Server,
ArrowRight,
RefreshCw,
Sliders,
CheckCircle2,
AlertCircle,
HelpCircle,
Code,
FileCode,
HardDrive
} from 'lucide-react';
import { MCPRequest, MCPResponse } from '../types';
export const MCPGuideTab: React.FC = () => {
const [activeMainSection, setActiveMainSection] = useState<'sse-hub' | 'ollama-gpu' | 'inspector' | 'client-configs'>('sse-hub');
const [activeGuide, setActiveGuide] = useState<'claude' | 'cursor' | 'openwebui' | 'python' | 'ts' | 'curl'>('claude');
const [copiedKey, setCopiedKey] = useState<string | null>(null);
// SSE Live Stream Inspector State
const [sseConnected, setSseConnected] = useState<boolean>(false);
const [sseLogs, setSseLogs] = useState<Array<{ id: string; time: string; event: string; data: string }>>([]);
const [ssePingCount, setSsePingCount] = useState<number>(0);
const sseEventSourceRef = useRef<EventSource | null>(null);
// Local Ollama / GPU Probe State
const [ollamaEndpoint, setOllamaEndpoint] = useState<string>('http://localhost:11434');
const [probingOllama, setProbingOllama] = useState<boolean>(false);
const [probeResult, setProbeResult] = useState<any>(null);
// Local Model Interactive Playground State
const [activeModelTool, setActiveModelTool] = useState<'prompt' | 'vision' | 'embed' | 'manager' | 'nvidia'>('prompt');
const [promptInput, setPromptInput] = useState<string>('Explain how Model Context Protocol routes tool calls to local sub-models.');
const [selectedSubModel, setSelectedSubModel] = useState<string>('llama3.2:1b');
const [visionImageInput, setVisionImageInput] = useState<string>('https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=600');
const [visionPrompt, setVisionPrompt] = useState<string>('Examine this image and detect key visual elements, text, and scene composition.');
const [embedTextInput, setEmbedTextInput] = useState<string>('Model Context Protocol enables universal tool calling and structured knowledge sharing.');
const [embedCompareInput, setEmbedCompareInput] = useState<string>('MCP standardizes agent tool invocation and context synchronization across AI clients.');
const [selectedEmbedModel, setSelectedEmbedModel] = useState<string>('nomic-embed-text');
const [managerAction, setManagerAction] = useState<string>('list_models');
const [toolExecuting, setToolExecuting] = useState<boolean>(false);
const [toolExecutionOutput, setToolExecutionOutput] = useState<any>(null);
// JSON-RPC Inspector State
const [inspectorMethod, setInspectorMethod] = useState<string>('tools/list');
const [inspectorParams, setInspectorParams] = useState<string>('{}');
const [inspectorResponse, setInspectorResponse] = useState<MCPResponse | null>(null);
const [inspectorLoading, setInspectorLoading] = useState<boolean>(false);
const baseUrl = window.location.origin;
const localSseUrl = `http://localhost:3000/mcp/sse`;
const publicSseUrl = `${baseUrl}/mcp/sse`;
const directRpcUrl = `${baseUrl}/mcp`;
const messagesPostUrl = `${baseUrl}/mcp/messages`;
const copyCode = (code: string, key: string) => {
navigator.clipboard.writeText(code);
setCopiedKey(key);
setTimeout(() => setCopiedKey(null), 2000);
};
// Connect / Disconnect to live SSE stream
const toggleSSEStream = () => {
if (sseConnected) {
if (sseEventSourceRef.current) {
sseEventSourceRef.current.close();
sseEventSourceRef.current = null;
}
setSseConnected(false);
setSseLogs(prev => [{
id: Math.random().toString(36),
time: new Date().toLocaleTimeString(),
event: 'client_disconnected',
data: 'Disconnected from SSE stream endpoint.',
}, ...prev]);
} else {
try {
const es = new EventSource('/mcp/sse');
sseEventSourceRef.current = es;
es.onopen = () => {
setSseConnected(true);
setSseLogs(prev => [{
id: Math.random().toString(36),
time: new Date().toLocaleTimeString(),
event: 'open',
data: 'Connected to MCP SSE stream channel.',
}, ...prev]);
};
es.addEventListener('endpoint', (e: MessageEvent) => {
setSseLogs(prev => [{
id: Math.random().toString(36),
time: new Date().toLocaleTimeString(),
event: 'endpoint',
data: e.data,
}, ...prev]);
});
es.addEventListener('message', (e: MessageEvent) => {
setSseLogs(prev => [{
id: Math.random().toString(36),
time: new Date().toLocaleTimeString(),
event: 'message',
data: e.data,
}, ...prev]);
});
es.onerror = () => {
// Keep-alive or reconnection
setSsePingCount(p => p + 1);
};
} catch (err: any) {
console.error('Failed to connect to SSE stream:', err);
}
}
};
useEffect(() => {
return () => {
if (sseEventSourceRef.current) {
sseEventSourceRef.current.close();
}
};
}, []);
// Probe local Ollama instance
const handleProbeOllama = async () => {
setProbingOllama(true);
try {
const res = await fetch('/api/v1/ollama/probe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ endpoint: ollamaEndpoint }),
});
const data = await res.json();
setProbeResult(data);
} catch (err: any) {
setProbeResult({
connected: false,
error: err.message,
message: 'Could not connect to Ollama probe endpoint.',
});
} finally {
setProbingOllama(false);
}
};
// Run local model tool
const handleExecuteModelTool = async () => {
setToolExecuting(true);
try {
let toolName = 'ollama_delegate_prompt';
let payload: any = {};
if (activeModelTool === 'prompt') {
toolName = 'ollama_delegate_prompt';
payload = {
prompt: promptInput,
model: selectedSubModel,
endpoint: ollamaEndpoint,
};
} else if (activeModelTool === 'vision') {
toolName = 'ollama_vision_inspect';
payload = {
imageUrlOrBase64: visionImageInput,
prompt: visionPrompt,
model: 'llama3.2-vision',
endpoint: ollamaEndpoint,
};
} else if (activeModelTool === 'embed') {
toolName = 'ollama_embed_document';
payload = {
text: embedTextInput,
compareText: embedCompareInput,
model: selectedEmbedModel,
endpoint: ollamaEndpoint,
};
} else if (activeModelTool === 'manager') {
toolName = 'ollama_gpu_model_manager';
payload = {
action: managerAction,
endpoint: ollamaEndpoint,
};
} else if (activeModelTool === 'nvidia') {
toolName = 'nvidia_nim_delegate';
payload = {
prompt: promptInput,
model: 'meta/llama-3.1-8b-instruct',
endpoint: 'http://localhost:8888/v1',
};
}
const res = await fetch(`/api/v1/tools/${toolName}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await res.json();
setToolExecutionOutput(data);
} catch (err: any) {
setToolExecutionOutput({ error: err.message || 'Execution failed' });
} finally {
setToolExecuting(false);
}
};
// Run JSON-RPC Inspector
const runInspectorRPC = async () => {
setInspectorLoading(true);
try {
let parsedParams = {};
try {
parsedParams = JSON.parse(inspectorParams);
} catch {
// use empty object if invalid
}
const reqPayload: MCPRequest = {
jsonrpc: '2.0',
id: Math.floor(Math.random() * 1000),
method: inspectorMethod,
params: parsedParams,
};
const res = await fetch('/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(reqPayload),
});
const data = await res.json();
setInspectorResponse(data);
} catch (err: any) {
setInspectorResponse({
jsonrpc: '2.0',
error: { code: -32603, message: err.message || 'Failed to send request' },
});
} finally {
setInspectorLoading(false);
}
};
const presetInspector = (method: string, paramsObj: any) => {
setInspectorMethod(method);
setInspectorParams(JSON.stringify(paramsObj, null, 2));
};
// Client Configs
const claudeConfig = JSON.stringify(
{
mcpServers: {
"do-everything-mcp-hub": {
url: `${baseUrl}/mcp/sse`
}
}
},
null,
2
);
const cursorConfig = JSON.stringify(
{
mcpServers: {
"local-mcp-hub": {
url: `${baseUrl}/mcp/sse`,
transport: "sse"
}
}
},
null,
2
);
const openwebuiConfig = `// OpenWebUI / AnythingLLM / LibreChat MCP SSE Connection
Endpoint URL: ${baseUrl}/mcp/sse
Messages POST: ${baseUrl}/mcp/messages
Protocol: MCP JSON-RPC 2.0 (2024-11-05)
System Prompt Injection:
"You are connected to the universal Do Everything MCP Hub. You have access to local GPU sub-models via Ollama (ollama_delegate_prompt, ollama_vision_inspect, ollama_embed_document), web scraping, code execution, and persistent memory. Call tools whenever user requests tasks matching registered schemas."
`;
const pythonCode = `import asyncio
import httpx
async def call_mcp_sse_or_rpc(tool_name: str, arguments: dict):
mcp_url = "${baseUrl}/mcp"
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments
}
}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(mcp_url, json=payload)
return response.json()
# Example: Delegate sub-task to local Ollama model (llama3.2:1b)
result = asyncio.run(call_mcp_sse_or_rpc("ollama_delegate_prompt", {
"prompt": "Summarize this log text in 2 concise bullets",
"model": "llama3.2:1b"
}))
print(result)
`;
const tsCode = `// Connect to Do Everything MCP Server via SSE or HTTP JSON-RPC
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
async function main() {
const transport = new SSEClientTransport(new URL("${baseUrl}/mcp/sse"));
const client = new Client({ name: "my-model-agent", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);
// List all available tools including local GPU delegates
const tools = await client.listTools();
console.log("Connected tools:", tools);
// Call Ollama document embedder
const embedRes = await client.callTool({
name: "ollama_embed_document",
arguments: { text: "Self-hosted Model Context Protocol server" }
});
console.log("Embedding vector:", embedRes);
}
main();
`;
const curlCmd = `curl -X POST ${baseUrl}/mcp \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "ollama_delegate_prompt",
"arguments": {
"prompt": "Analyze code architecture",
"model": "llama3.2:1b"
}
}
}'`;
return (
<div className="space-y-8">
{/* Top Banner */}
<div className="bg-gradient-to-r from-slate-900 via-indigo-950 to-slate-900 rounded-3xl p-6 sm:p-8 text-white shadow-xl border border-slate-800 relative overflow-hidden">
<div className="relative z-10 max-w-4xl space-y-3">
<div className="inline-flex items-center space-x-2 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
<Radio className="h-3.5 w-3.5 text-indigo-400 animate-pulse" />
<span>Universal MCP SSE Endpoint & Local GPU Power-Up Hub</span>
</div>
<h2 className="text-2xl sm:text-3xl font-black tracking-tight">
Direct SSE Model Pointer & Local GPU Delegation Hub
</h2>
<p className="text-slate-300 text-xs sm:text-sm leading-relaxed">
Point <strong>any</strong> external or local host model (Claude Desktop, Cursor, OpenWebUI, LlamaIndex, Ollama) directly at this servers SSE address. Empower your main host model to dispatch vision inspection, document vector embeddings, fast sub-model reasoning, and GPU VRAM management to your local hardware.
</p>
<div className="pt-2 flex flex-wrap gap-2">
<button
onClick={() => setActiveMainSection('sse-hub')}
className={`px-4 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-2 ${
activeMainSection === 'sse-hub'
? 'bg-indigo-600 text-white shadow-lg shadow-indigo-600/30'
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
}`}
>
<Radio className="w-3.5 h-3.5" />
<span>1-Click SSE Endpoint</span>
</button>
<button
onClick={() => setActiveMainSection('ollama-gpu')}
className={`px-4 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-2 ${
activeMainSection === 'ollama-gpu'
? 'bg-emerald-600 text-white shadow-lg shadow-emerald-600/30'
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
}`}
>
<Cpu className="w-3.5 h-3.5" />
<span>NVIDIA & Ollama GPU Delegation</span>
</button>
<button
onClick={() => setActiveMainSection('client-configs')}
className={`px-4 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-2 ${
activeMainSection === 'client-configs'
? 'bg-indigo-600 text-white'
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
}`}
>
<FileCode className="w-3.5 h-3.5" />
<span>Client Setup Configs</span>
</button>
<button
onClick={() => setActiveMainSection('inspector')}
className={`px-4 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-2 ${
activeMainSection === 'inspector'
? 'bg-indigo-600 text-white'
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
}`}
>
<Terminal className="w-3.5 h-3.5" />
<span>JSON-RPC Inspector</span>
</button>
</div>
</div>
</div>
{/* SECTION 1: SSE ENDPOINT HUB */}
{activeMainSection === 'sse-hub' && (
<div className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left 2 Cols: Endpoint Addresses */}
<div className="lg:col-span-2 space-y-4">
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm space-y-5">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Radio className="h-5 w-5 text-indigo-600" />
<h3 className="text-base font-bold text-slate-900">Local & Public SSE Stream Addresses</h3>
</div>
<span className="px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-800 flex items-center space-x-1 font-mono">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
<span>MCP 2024-11-05 Compliant</span>
</span>
</div>
<div className="space-y-4">
{/* Local Address */}
<div className="p-4 bg-slate-50 rounded-2xl border border-slate-200 space-y-2">
<div className="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Local SSE Endpoint (For local clients & containers):</span>
<span className="text-indigo-600 font-mono">GET /mcp/sse</span>
</div>
<div className="flex items-center space-x-2">
<input
readOnly
value={localSseUrl}
className="flex-1 px-3 py-2 bg-white border border-slate-300 rounded-xl text-xs font-mono text-slate-900 focus:outline-none select-all"
/>
<button
onClick={() => copyCode(localSseUrl, 'local-sse')}
className="px-3.5 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shrink-0"
>
{copiedKey === 'local-sse' ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedKey === 'local-sse' ? 'Copied' : 'Copy'}</span>
</button>
</div>
</div>
{/* Public App URL Address */}
<div className="p-4 bg-indigo-50/50 rounded-2xl border border-indigo-100 space-y-2">
<div className="flex items-center justify-between text-xs font-semibold text-indigo-900">
<span>Public / Deployed Web Endpoint:</span>
<span className="text-indigo-600 font-mono">GET /mcp/sse</span>
</div>
<div className="flex items-center space-x-2">
<input
readOnly
value={publicSseUrl}
className="flex-1 px-3 py-2 bg-white border border-indigo-200 rounded-xl text-xs font-mono text-indigo-950 focus:outline-none select-all"
/>
<button
onClick={() => copyCode(publicSseUrl, 'public-sse')}
className="px-3.5 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shrink-0"
>
{copiedKey === 'public-sse' ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedKey === 'public-sse' ? 'Copied' : 'Copy'}</span>
</button>
</div>
</div>
{/* POST Messages Address */}
<div className="p-4 bg-slate-50 rounded-2xl border border-slate-200 space-y-2">
<div className="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Direct JSON-RPC / Messages POST Endpoint:</span>
<span className="text-slate-500 font-mono">POST /mcp</span>
</div>
<div className="flex items-center space-x-2">
<input
readOnly
value={directRpcUrl}
className="flex-1 px-3 py-2 bg-white border border-slate-300 rounded-xl text-xs font-mono text-slate-900 focus:outline-none select-all"
/>
<button
onClick={() => copyCode(directRpcUrl, 'direct-rpc')}
className="px-3.5 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shrink-0"
>
{copiedKey === 'direct-rpc' ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedKey === 'direct-rpc' ? 'Copied' : 'Copy'}</span>
</button>
</div>
</div>
</div>
</div>
</div>
{/* Right Col: Live SSE Inspector */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm space-y-4 flex flex-col justify-between">
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-bold uppercase tracking-wider text-slate-700 flex items-center space-x-1.5">
<Activity className="w-4 h-4 text-emerald-600" />
<span>Live SSE Stream Monitor</span>
</span>
<button
onClick={toggleSSEStream}
className={`px-3 py-1 rounded-full text-xs font-bold transition flex items-center space-x-1.5 ${
sseConnected
? 'bg-emerald-100 text-emerald-800 hover:bg-emerald-200'
: 'bg-indigo-600 text-white hover:bg-indigo-500'
}`}
>
<span className={`w-2 h-2 rounded-full ${sseConnected ? 'bg-emerald-500 animate-pulse' : 'bg-white'}`}></span>
<span>{sseConnected ? 'Connected (Listening)' : 'Connect Test'}</span>
</button>
</div>
<p className="text-xs text-slate-500">
Subscribes to <code className="text-indigo-600 font-mono">/mcp/sse</code> in real time to capture protocol handshakes, endpoint discovery events, and keep-alive frames.
</p>
<div className="h-56 bg-slate-950 rounded-2xl p-3 text-[11px] font-mono text-emerald-400 overflow-y-auto space-y-1.5 border border-slate-800">
{sseLogs.length === 0 ? (
<div className="text-slate-500 py-6 text-center">
Click "Connect Test" above to stream live SSE packets.
</div>
) : (
sseLogs.map((log) => (
<div key={log.id} className="border-b border-slate-900/60 pb-1">
<div className="flex items-center justify-between text-[10px] text-slate-400">
<span className="text-indigo-400 font-bold">event: {log.event}</span>
<span>{log.time}</span>
</div>
<div className="text-emerald-300 break-all">{log.data}</div>
</div>
))
)}
</div>
</div>
<div className="text-[11px] text-slate-500 pt-2 border-t border-slate-100 flex items-center justify-between">
<span>Heartbeat: <strong>Active (10s)</strong></span>
<span>Active Listeners: <strong>{sseConnected ? '1 Connected' : '0'}</strong></span>
</div>
</div>
</div>
</div>
)}
{/* SECTION 2: NVIDIA & OLLAMA LOCAL GPU DELEGATION HUB */}
{activeMainSection === 'ollama-gpu' && (
<div className="space-y-6">
{/* Header & Probe Bar */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm space-y-5">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="space-y-1">
<div className="flex items-center space-x-2">
<div className="w-8 h-8 rounded-xl bg-emerald-100 text-emerald-700 flex items-center justify-center font-bold">
<Cpu className="w-4 h-4" />
</div>
<h3 className="text-lg font-black text-slate-900">Local GPU Model Delegation & Worker Pool</h3>
</div>
<p className="text-xs text-slate-600">
Your host main model can call on small, specialized local models running on your GPU (Ollama, vLLM, llama.cpp, NVIDIA NIM) for sub-tasks, saving cloud API tokens and enabling zero-latency local vision & embeddings.
</p>
</div>
{/* Endpoint probe */}
<div className="flex items-center space-x-2">
<input
type="text"
value={ollamaEndpoint}
onChange={(e) => setOllamaEndpoint(e.target.value)}
placeholder="http://localhost:11434"
className="px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono text-slate-800 w-48 sm:w-60 focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
<button
onClick={handleProbeOllama}
disabled={probingOllama}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shrink-0 disabled:opacity-50"
>
<RefreshCw className={`w-3.5 h-3.5 ${probingOllama ? 'animate-spin' : ''}`} />
<span>Probe GPU Models</span>
</button>
</div>
</div>
{/* Probe Results Banner */}
{probeResult && (
<div className={`p-4 rounded-2xl border text-xs ${
probeResult.connected
? 'bg-emerald-50 border-emerald-200 text-emerald-900'
: 'bg-amber-50 border-amber-200 text-amber-900'
}`}>
<div className="flex items-center space-x-2 font-bold mb-1">
{probeResult.connected ? <CheckCircle2 className="w-4 h-4 text-emerald-600" /> : <AlertCircle className="w-4 h-4 text-amber-600" />}
<span>{probeResult.connected ? 'Local Ollama Connected & Ready' : 'Local Ollama Offline (Using Smart Simulation Fallback)'}</span>
</div>
<p className="text-[11px] leading-relaxed">{probeResult.message}</p>
{probeResult.models && probeResult.models.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1.5">
{probeResult.models.map((m: any, i: number) => (
<span key={i} className="px-2 py-0.5 rounded-md bg-emerald-200 text-emerald-900 font-mono text-[10px] font-bold">
{m.name || m}
</span>
))}
</div>
)}
</div>
)}
{/* 4 Specialized Delegation Roles */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 pt-2">
<button
onClick={() => setActiveModelTool('prompt')}
className={`p-4 rounded-2xl border text-left transition flex flex-col justify-between space-y-2 ${
activeModelTool === 'prompt'
? 'border-emerald-600 bg-emerald-50/50 shadow-sm'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<div className="w-7 h-7 rounded-lg bg-emerald-100 text-emerald-700 flex items-center justify-center font-bold">
<Zap className="w-3.5 h-3.5" />
</div>
<div>
<div className="text-xs font-bold text-slate-900">Sub-Model Delegation</div>
<div className="text-[10px] text-slate-500">llama3.2:1b, deepseek-r1:1.5b</div>
</div>
</button>
<button
onClick={() => setActiveModelTool('vision')}
className={`p-4 rounded-2xl border text-left transition flex flex-col justify-between space-y-2 ${
activeModelTool === 'vision'
? 'border-emerald-600 bg-emerald-50/50 shadow-sm'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<div className="w-7 h-7 rounded-lg bg-blue-100 text-blue-700 flex items-center justify-center font-bold">
<Eye className="w-3.5 h-3.5" />
</div>
<div>
<div className="text-xs font-bold text-slate-900">Vision Image Inspector</div>
<div className="text-[10px] text-slate-500">llava, llama3.2-vision</div>
</div>
</button>
<button
onClick={() => setActiveModelTool('embed')}
className={`p-4 rounded-2xl border text-left transition flex flex-col justify-between space-y-2 ${
activeModelTool === 'embed'
? 'border-emerald-600 bg-emerald-50/50 shadow-sm'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<div className="w-7 h-7 rounded-lg bg-purple-100 text-purple-700 flex items-center justify-center font-bold">
<FileText className="w-3.5 h-3.5" />
</div>
<div>
<div className="text-xs font-bold text-slate-900">Document Embeddings</div>
<div className="text-[10px] text-slate-500">nomic-embed, bge-m3</div>
</div>
</button>
<button
onClick={() => setActiveModelTool('manager')}
className={`p-4 rounded-2xl border text-left transition flex flex-col justify-between space-y-2 ${
activeModelTool === 'manager'
? 'border-emerald-600 bg-emerald-50/50 shadow-sm'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<div className="w-7 h-7 rounded-lg bg-amber-100 text-amber-700 flex items-center justify-center font-bold">
<HardDrive className="w-3.5 h-3.5" />
</div>
<div>
<div className="text-xs font-bold text-slate-900">VRAM & GPU Telemetry</div>
<div className="text-[10px] text-slate-500">Model status & tags</div>
</div>
</button>
</div>
</div>
{/* Interactive Test Console */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Input Form */}
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm space-y-4">
<div className="flex items-center justify-between">
<span className="text-xs font-bold uppercase tracking-wider text-slate-700">
{activeModelTool === 'prompt' && '1. ollama_delegate_prompt'}
{activeModelTool === 'vision' && '2. ollama_vision_inspect'}
{activeModelTool === 'embed' && '3. ollama_embed_document'}
{activeModelTool === 'manager' && '4. ollama_gpu_model_manager'}
{activeModelTool === 'nvidia' && '5. nvidia_nim_delegate'}
</span>
<span className="text-[10px] text-emerald-600 font-bold bg-emerald-50 px-2 py-0.5 rounded-full">
MCP Tool Execution
</span>
</div>
{/* Sub-tool inputs */}
{activeModelTool === 'prompt' && (
<div className="space-y-3">
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Target Local Model</label>
<select
value={selectedSubModel}
onChange={(e) => setSelectedSubModel(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-emerald-500"
>
<option value="llama3.2:1b">llama3.2:1b (Fastest, 1.3GB VRAM)</option>
<option value="llama3.2:3b">llama3.2:3b (Balanced, 2.4GB VRAM)</option>
<option value="deepseek-r1:1.5b">deepseek-r1:1.5b (Fast Reasoning & Math)</option>
<option value="qwen2.5-coder:1.5b">qwen2.5-coder:1.5b (Code & Refactoring)</option>
<option value="phi3.5">phi3.5 (Logic & Common Sense)</option>
<option value="mistral:7b">mistral:7b (High Capacity 7B)</option>
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Prompt / Sub-Task to Delegate</label>
<textarea
rows={4}
value={promptInput}
onChange={(e) => setPromptInput(e.target.value)}
placeholder="Enter task to delegate to local model..."
className="w-full p-3 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>
</div>
)}
{activeModelTool === 'vision' && (
<div className="space-y-3">
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Image URL (or Base64 data)</label>
<input
type="text"
value={visionImageInput}
onChange={(e) => setVisionImageInput(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Inspection Prompt</label>
<textarea
rows={3}
value={visionPrompt}
onChange={(e) => setVisionPrompt(e.target.value)}
className="w-full p-3 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{visionImageInput && visionImageInput.startsWith('http') && (
<div className="h-28 rounded-xl overflow-hidden border border-slate-200 relative">
<img src={visionImageInput} alt="Preview" className="w-full h-full object-cover" />
</div>
)}
</div>
)}
{activeModelTool === 'embed' && (
<div className="space-y-3">
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Local Embedding Model</label>
<select
value={selectedEmbedModel}
onChange={(e) => setSelectedEmbedModel(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="nomic-embed-text">nomic-embed-text (384-dim, 274MB)</option>
<option value="bge-m3">bge-m3 (1024-dim, Multilingual)</option>
<option value="all-minilm">all-minilm (384-dim, Fast)</option>
<option value="snowflake-arctic-embed">snowflake-arctic-embed (Dense Search)</option>
</select>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Document / Passage Text</label>
<textarea
rows={3}
value={embedTextInput}
onChange={(e) => setEmbedTextInput(e.target.value)}
className="w-full p-3 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Compare Against (Optional Cosine Similarity)</label>
<input
type="text"
value={embedCompareInput}
onChange={(e) => setEmbedCompareInput(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
</div>
)}
{activeModelTool === 'manager' && (
<div className="space-y-3">
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Manager Telemetry Action</label>
<select
value={managerAction}
onChange={(e) => setManagerAction(e.target.value)}
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="list_models">list_models (Installed Local Checkpoints)</option>
<option value="running_vram_models">running_vram_models (Currently Loaded in VRAM)</option>
<option value="ping_endpoint">ping_endpoint (Check Ollama Version & API Health)</option>
</select>
</div>
<p className="text-[11px] text-slate-500">
Returns live telemetry for GPU accelerators, VRAM footprint, and loaded local worker models.
</p>
</div>
)}
<button
onClick={handleExecuteModelTool}
disabled={toolExecuting}
className="w-full py-3 rounded-2xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition flex items-center justify-center space-x-2 shadow-md shadow-emerald-600/20 disabled:opacity-50"
>
{toolExecuting ? (
<>
<RefreshCw className="w-4 h-4 animate-spin" />
<span>Executing on GPU / Local Model...</span>
</>
) : (
<>
<Play className="w-4 h-4 fill-white" />
<span>Execute Local Model Tool via MCP</span>
</>
)}
</button>
</div>
{/* Output Display */}
<div className="bg-slate-950 rounded-3xl p-6 border border-slate-800 shadow-sm flex flex-col justify-between space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between text-xs">
<span className="font-bold text-slate-300 uppercase tracking-wider flex items-center space-x-2">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
<span>Execution Result Output</span>
</span>
{toolExecutionOutput?.durationMs && (
<span className="text-emerald-400 font-mono">{toolExecutionOutput.durationMs}ms</span>
)}
</div>
<div className="h-80 bg-slate-900 rounded-2xl p-4 text-xs font-mono text-emerald-400 overflow-y-auto border border-slate-800">
{toolExecutionOutput ? (
<pre className="whitespace-pre-wrap">{JSON.stringify(toolExecutionOutput, null, 2)}</pre>
) : (
<div className="text-slate-500 py-16 text-center space-y-2">
<Cpu className="w-8 h-8 mx-auto text-slate-700" />
<p>Click "Execute Local Model Tool" to dispatch to your local GPU worker.</p>
</div>
)}
</div>
</div>
<div className="text-[11px] text-slate-400 flex items-center justify-between pt-2 border-t border-slate-900">
<span>Hardware: <strong>Local GPU / Ollama</strong></span>
<span>Format: <strong>JSON-RPC 2.0</strong></span>
</div>
</div>
</div>
</div>
)}
{/* SECTION 3: CLIENT SETUP CONFIGS */}
{activeMainSection === 'client-configs' && (
<div className="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden space-y-6">
<div className="flex border-b border-slate-200 overflow-x-auto bg-slate-50 p-2 gap-1">
<button
onClick={() => setActiveGuide('claude')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'claude' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<FileCode className="h-4 w-4" />
<span>Claude Desktop</span>
</button>
<button
onClick={() => setActiveGuide('cursor')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'cursor' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<Terminal className="h-4 w-4" />
<span>Cursor & Windsurf</span>
</button>
<button
onClick={() => setActiveGuide('openwebui')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'openwebui' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<Cpu className="h-4 w-4" />
<span>Ollama & OpenWebUI</span>
</button>
<button
onClick={() => setActiveGuide('python')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'python' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<Code className="h-4 w-4" />
<span>Python MCP Client</span>
</button>
<button
onClick={() => setActiveGuide('ts')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'ts' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<Code className="h-4 w-4" />
<span>TypeScript SDK</span>
</button>
<button
onClick={() => setActiveGuide('curl')}
className={`px-4 py-2.5 text-xs font-bold rounded-xl flex items-center space-x-2 transition ${
activeGuide === 'curl' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-600 hover:bg-slate-200'
}`}
>
<Terminal className="h-4 w-4" />
<span>cURL Command</span>
</button>
</div>
<div className="p-6 pt-0 space-y-4">
{activeGuide === 'claude' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Add this snippet to your <code className="bg-slate-100 px-2 py-0.5 rounded text-indigo-600 font-mono text-xs">claude_desktop_config.json</code> file to connect Claude Desktop to this server via SSE:
</p>
<div className="relative">
<button
onClick={() => copyCode(claudeConfig, 'claude')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'claude' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'claude' ? 'Copied!' : 'Copy Config'}</span>
</button>
<pre className="p-4 bg-slate-900 text-indigo-300 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{claudeConfig}
</pre>
</div>
</div>
)}
{activeGuide === 'cursor' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Add this MCP SSE configuration in Cursor or Windsurf under <code className="bg-slate-100 px-2 py-0.5 rounded text-indigo-600 font-mono text-xs">.cursor/mcp.json</code> or Settings:
</p>
<div className="relative">
<button
onClick={() => copyCode(cursorConfig, 'cursor')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'cursor' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'cursor' ? 'Copied!' : 'Copy Config'}</span>
</button>
<pre className="p-4 bg-slate-900 text-indigo-300 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{cursorConfig}
</pre>
</div>
</div>
)}
{activeGuide === 'openwebui' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Connect OpenWebUI, AnythingLLM, or Ollama frontends to this server for automated tool routing:
</p>
<div className="relative">
<button
onClick={() => copyCode(openwebuiConfig, 'openwebui')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'openwebui' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'openwebui' ? 'Copied!' : 'Copy Info'}</span>
</button>
<pre className="p-4 bg-slate-900 text-amber-300 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{openwebuiConfig}
</pre>
</div>
</div>
)}
{activeGuide === 'python' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Execute MCP tools directly in Python using standard HTTP requests:
</p>
<div className="relative">
<button
onClick={() => copyCode(pythonCode, 'python')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'python' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'python' ? 'Copied!' : 'Copy Code'}</span>
</button>
<pre className="p-4 bg-slate-900 text-emerald-300 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{pythonCode}
</pre>
</div>
</div>
)}
{activeGuide === 'ts' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Connect using the official Model Context Protocol TypeScript SDK:
</p>
<div className="relative">
<button
onClick={() => copyCode(tsCode, 'ts')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'ts' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'ts' ? 'Copied!' : 'Copy Code'}</span>
</button>
<pre className="p-4 bg-slate-900 text-sky-300 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{tsCode}
</pre>
</div>
</div>
)}
{activeGuide === 'curl' && (
<div className="space-y-4">
<p className="text-xs sm:text-sm text-slate-700">
Direct curl test command:
</p>
<div className="relative">
<button
onClick={() => copyCode(curlCmd, 'curl')}
className="absolute top-3 right-3 px-3 py-1.5 rounded-xl text-xs font-bold bg-slate-800 hover:bg-slate-700 text-slate-200 transition flex items-center space-x-1"
>
{copiedKey === 'curl' ? <Check className="h-3.5 w-3.5 text-emerald-400" /> : <Copy className="h-3.5 w-3.5" />}
<span>{copiedKey === 'curl' ? 'Copied!' : 'Copy Command'}</span>
</button>
<pre className="p-4 bg-slate-900 text-emerald-400 rounded-2xl text-xs font-mono overflow-x-auto leading-relaxed border border-slate-800">
{curlCmd}
</pre>
</div>
</div>
)}
</div>
</div>
)}
{/* SECTION 4: JSON-RPC INSPECTOR */}
{activeMainSection === 'inspector' && (
<div className="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm space-y-6">
<div className="flex items-center justify-between">
<h3 className="text-base font-bold text-slate-900 flex items-center space-x-2">
<Terminal className="h-5 w-5 text-indigo-600" />
<span>Interactive MCP JSON-RPC Inspector</span>
</h3>
<span className="text-xs text-slate-500 font-mono">POST {baseUrl}/mcp</span>
</div>
{/* Quick Presets */}
<div className="space-y-2">
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">Quick Presets:</label>
<div className="flex flex-wrap gap-2">
<button
onClick={() => presetInspector('initialize', { protocolVersion: '2024-11-05', capabilities: {} })}
className="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-xs font-medium text-slate-700"
>
initialize
</button>
<button
onClick={() => presetInspector('tools/list', {})}
className="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-xs font-medium text-slate-700"
>
tools/list
</button>
<button
onClick={() => presetInspector('tools/call', { name: 'ollama_delegate_prompt', arguments: { prompt: 'What are 3 benefits of running local AI models?', model: 'llama3.2:1b' } })}
className="px-3 py-1.5 rounded-xl bg-emerald-50 hover:bg-emerald-100 text-xs font-medium text-emerald-800"
>
tools/call (ollama_delegate)
</button>
<button
onClick={() => presetInspector('tools/call', { name: 'ollama_embed_document', arguments: { text: 'Model Context Protocol vector embeddings test' } })}
className="px-3 py-1.5 rounded-xl bg-purple-50 hover:bg-purple-100 text-xs font-medium text-purple-800"
>
tools/call (ollama_embed)
</button>
<button
onClick={() => presetInspector('tools/call', { name: 'math_evaluator', arguments: { operation: 'eval', expression: 'sqrt(144) * 12 + 100' } })}
className="px-3 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-xs font-medium text-indigo-700"
>
tools/call (math)
</button>
<button
onClick={() => presetInspector('resources/list', {})}
className="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-xs font-medium text-slate-700"
>
resources/list
</button>
</div>
</div>
{/* Form */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Method Name</label>
<input
type="text"
value={inspectorMethod}
onChange={(e) => setInspectorMethod(e.target.value)}
className="w-full px-3 py-2 border border-slate-300 rounded-xl text-sm font-mono focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="e.g. tools/list or tools/call"
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-700 mb-1">Params (JSON format)</label>
<textarea
rows={6}
value={inspectorParams}
onChange={(e) => setInspectorParams(e.target.value)}
className="w-full p-3 border border-slate-300 rounded-xl text-xs font-mono focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-slate-50"
/>
</div>
<button
onClick={runInspectorRPC}
disabled={inspectorLoading}
className="w-full py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-medium text-sm transition-all flex items-center justify-center space-x-2 disabled:opacity-50"
>
{inspectorLoading ? (
<span>Executing JSON-RPC...</span>
) : (
<>
<Send className="h-4 w-4" />
<span>Send MCP JSON-RPC Request</span>
</>
)}
</button>
</div>
{/* Response Output */}
<div className="space-y-2">
<label className="block text-xs font-semibold text-slate-700">JSON-RPC 2.0 Response Output</label>
<div className="h-72 p-4 bg-slate-900 text-emerald-400 rounded-xl text-xs font-mono overflow-y-auto border border-slate-800">
{inspectorResponse ? (
<pre className="whitespace-pre-wrap">{JSON.stringify(inspectorResponse, null, 2)}</pre>
) : (
<span className="text-slate-500">// Response payload will appear here after clicking Send.</span>
)}
</div>
</div>
</div>
</div>
)}
</div>
);
};