Buckets:
evalstate/sandbox-testing-2 / hf-mcp-server /packages /app /src /web /components /McpMethodsCard.tsx
| import useSWR from 'swr'; | |
| import { useState } from 'react'; | |
| import type { ColumnDef } from '@tanstack/react-table'; | |
| import { Card, CardHeader, CardTitle, CardDescription, CardContent } from './ui/card'; | |
| import { Checkbox } from './ui/checkbox'; | |
| import { DataTable } from './data-table'; | |
| import { createSortableHeader } from './data-table-utils'; | |
| import type { TransportMetricsResponse } from '../../shared/transport-metrics.js'; | |
| // SWR fetcher function | |
| const fetcher = (url: string) => | |
| fetch(url).then((res) => { | |
| if (!res.ok) { | |
| throw new Error(`Failed to fetch: ${res.status}`); | |
| } | |
| return res.json(); | |
| }); | |
| type MethodData = { | |
| method: string; | |
| count: number; | |
| errors: number; | |
| errorRate: number; | |
| averageResponseTime?: number; | |
| lastCalled: string; | |
| // Gradio-specific metrics if available | |
| gradioSuccess?: number; | |
| gradioFailure?: number; | |
| }; | |
| export function McpMethodsCard() { | |
| // State for filtering only resource, tool, and prompt calls | |
| const [showOnlyPrimaryMcpCalls, setShowOnlyPrimaryMcpCalls] = useState(false); | |
| // Use SWR for transport metrics with auto-refresh | |
| const { data: metrics, error } = useSWR<TransportMetricsResponse>('/api/transport-metrics', fetcher, { | |
| refreshInterval: 3000, // Refresh every 3 seconds | |
| revalidateOnFocus: true, | |
| revalidateOnReconnect: true, | |
| }); | |
| const isLoading = !metrics && !error; | |
| const isStdioMode = metrics?.transport === 'stdio'; | |
| // Process methods and enrich with Gradio metrics | |
| const processedMethods: MethodData[] = (metrics?.methods || []).map((method) => { | |
| let totalErrors = method.errors; | |
| let gradioSuccess: number | undefined; | |
| let gradioFailure: number | undefined; | |
| // Check if this is a tool call and we have Gradio metrics | |
| if (method.method.startsWith('tools/call:') && metrics?.gradioMetrics) { | |
| // Extract tool name from method (tools/call:toolName -> toolName) | |
| const toolName = method.method.replace('tools/call:', ''); | |
| const gradioByTool = metrics.gradioMetrics.byTool; | |
| // Look for exact match in Gradio metrics | |
| if (gradioByTool[toolName]) { | |
| gradioSuccess = gradioByTool[toolName].success; | |
| gradioFailure = gradioByTool[toolName].failure; | |
| // Add Gradio failures to total error count | |
| totalErrors += gradioFailure; | |
| } | |
| } | |
| // Recalculate error rate with the updated error count | |
| const errorRate = method.count > 0 ? (totalErrors / method.count) * 100 : 0; | |
| const methodData: MethodData = { | |
| method: method.method, | |
| count: method.count, | |
| errors: totalErrors, | |
| errorRate: errorRate, | |
| averageResponseTime: method.averageResponseTime, | |
| lastCalled: method.lastCalled, | |
| gradioSuccess, | |
| gradioFailure, | |
| }; | |
| return methodData; | |
| }); | |
| // Filter methods if checkbox is checked | |
| const filteredMethods = showOnlyPrimaryMcpCalls | |
| ? processedMethods.filter( | |
| (m) => | |
| m.method.startsWith('tools/call') || | |
| m.method.startsWith('prompts/get') || | |
| m.method.startsWith('resources/read'), | |
| ) | |
| : processedMethods; | |
| // Calculate total calls, tool calls, prompt calls, and resource reads | |
| const totalMcpCalls = processedMethods.reduce((sum, method) => sum + method.count, 0); | |
| const toolCalls = processedMethods | |
| .filter((m) => m.method.startsWith('tools/call')) | |
| .reduce((sum, method) => sum + method.count, 0); | |
| const promptCalls = processedMethods | |
| .filter((m) => m.method.startsWith('prompts/get')) | |
| .reduce((sum, method) => sum + method.count, 0); | |
| const resourceReads = processedMethods | |
| .filter((m) => m.method.startsWith('resources/read')) | |
| .reduce((sum, method) => sum + method.count, 0); | |
| // Define columns for the data table | |
| const columns: ColumnDef<MethodData>[] = [ | |
| { | |
| accessorKey: 'method', | |
| header: createSortableHeader('Method'), | |
| cell: ({ row }) => { | |
| const method = row.getValue('method') as string; | |
| return ( | |
| <div className="font-mono text-sm"> | |
| {method === 'tools/call' ? ( | |
| <span className="text-blue-600 dark:text-blue-400">tools/call</span> | |
| ) : method.startsWith('tools/call:') ? ( | |
| <> | |
| <span className="text-blue-600 dark:text-blue-400">tools/call:</span> | |
| <span className="text-green-600 dark:text-green-400">{method.replace('tools/call:', '')}</span> | |
| </> | |
| ) : method === 'prompts/get' ? ( | |
| <span className="text-purple-600 dark:text-purple-400">prompts/get</span> | |
| ) : method.startsWith('prompts/get:') ? ( | |
| <> | |
| <span className="text-purple-600 dark:text-purple-400">prompts/get:</span> | |
| <span className="text-orange-600 dark:text-orange-400">{method.replace('prompts/get:', '')}</span> | |
| </> | |
| ) : method === 'resources/read' ? ( | |
| <span className="text-cyan-600 dark:text-cyan-400">resources/read</span> | |
| ) : method.startsWith('resources/read:') ? ( | |
| <> | |
| <span className="text-cyan-600 dark:text-cyan-400">resources/read:</span> | |
| <span className="text-teal-600 dark:text-teal-400 break-all"> | |
| {method.replace('resources/read:', '')} | |
| </span> | |
| </> | |
| ) : ( | |
| method | |
| )} | |
| </div> | |
| ); | |
| }, | |
| }, | |
| { | |
| accessorKey: 'count', | |
| header: createSortableHeader('Calls', 'right'), | |
| cell: ({ row }) => <div className="text-right font-mono">{row.getValue<number>('count').toLocaleString()}</div>, | |
| }, | |
| { | |
| accessorKey: 'errors', | |
| header: createSortableHeader('Errors', 'right'), | |
| cell: ({ row }) => { | |
| const errors = row.getValue<number>('errors'); | |
| return ( | |
| <div className="text-right font-mono"> | |
| {errors > 0 ? <span className="text-red-600 dark:text-red-400">{errors}</span> : '0'} | |
| </div> | |
| ); | |
| }, | |
| }, | |
| { | |
| accessorKey: 'gradioMetrics', | |
| header: createSortableHeader('Success/Failure', 'right'), | |
| cell: ({ row }) => { | |
| const data = row.original; | |
| if (data.gradioSuccess !== undefined && data.gradioFailure !== undefined) { | |
| return ( | |
| <div className="text-right font-mono text-sm"> | |
| <span className="text-green-600 dark:text-green-400">{data.gradioSuccess}</span> | |
| <span className="text-muted-foreground">/</span> | |
| <span className="text-red-600 dark:text-red-400">{data.gradioFailure}</span> | |
| </div> | |
| ); | |
| } | |
| return <div className="text-right text-muted-foreground">—</div>; | |
| }, | |
| }, | |
| { | |
| accessorKey: 'errorRate', | |
| header: createSortableHeader('Error Rate', 'right'), | |
| cell: ({ row }) => { | |
| const errorRate = row.getValue<number>('errorRate'); | |
| return ( | |
| <div className="text-right font-mono"> | |
| {errorRate > 0 ? <span className="text-red-600 dark:text-red-400">{errorRate.toFixed(1)}%</span> : '0%'} | |
| </div> | |
| ); | |
| }, | |
| }, | |
| { | |
| accessorKey: 'averageResponseTime', | |
| header: createSortableHeader('Avg Response', 'right'), | |
| cell: ({ row }) => { | |
| const avgTime = row.getValue<number | undefined>('averageResponseTime'); | |
| return <div className="text-right font-mono">{avgTime ? `${avgTime.toFixed(0)}ms` : '—'}</div>; | |
| }, | |
| }, | |
| { | |
| accessorKey: 'lastCalled', | |
| header: createSortableHeader('Last Called', 'right'), | |
| cell: ({ row }) => ( | |
| <div className="text-right text-sm text-muted-foreground"> | |
| {new Date(row.getValue<string>('lastCalled')).toLocaleTimeString()} | |
| </div> | |
| ), | |
| }, | |
| ]; | |
| return ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>HTTP JSON Transport Statistics</CardTitle> | |
| <CardDescription> | |
| MCP method call statistics and performance metrics | |
| {isStdioMode | |
| ? ' (Empty in STDIO mode)' | |
| : metrics?.isStateless | |
| ? '' | |
| : ' (Response times not available in stateful modes)'} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| {isLoading && ( | |
| <div className="flex items-center justify-center py-8"> | |
| <div className="text-sm text-muted-foreground">Loading metrics...</div> | |
| </div> | |
| )} | |
| {error && ( | |
| <div className="flex items-center justify-center py-8"> | |
| <div className="text-sm text-destructive">Error loading metrics: {error.message}</div> | |
| </div> | |
| )} | |
| {isStdioMode && ( | |
| <div className="flex items-center justify-center py-8"> | |
| <div className="text-sm text-muted-foreground"> | |
| Method tracking is not available in STDIO mode. This data is only collected for HTTP-based transports. | |
| </div> | |
| </div> | |
| )} | |
| {metrics && !isStdioMode && ( | |
| <div className="space-y-4"> | |
| {processedMethods.length > 0 && ( | |
| <div className="flex items-center justify-between"> | |
| <div className="text-sm text-muted-foreground"> | |
| Showing {filteredMethods.length} method{filteredMethods.length !== 1 ? 's' : ''} tracked since{' '} | |
| {new Date(metrics.startupTime).toLocaleString()} | |
| </div> | |
| <div className="flex items-center gap-4"> | |
| <div className="text-sm font-medium"> | |
| Total MCP Calls: <span className="font-mono">{totalMcpCalls.toLocaleString()}</span> | |
| </div> | |
| <div className="text-sm font-medium"> | |
| Tool Calls: <span className="font-mono">{toolCalls.toLocaleString()}</span> | |
| </div> | |
| <div className="text-sm font-medium"> | |
| Prompt Calls: <span className="font-mono">{promptCalls.toLocaleString()}</span> | |
| </div> | |
| <div className="text-sm font-medium"> | |
| Resource Reads: <span className="font-mono">{resourceReads.toLocaleString()}</span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {processedMethods.length > 0 && ( | |
| <div className="flex items-center space-x-2"> | |
| <Checkbox | |
| id="tool-prompt-calls-filter" | |
| checked={showOnlyPrimaryMcpCalls} | |
| onCheckedChange={(checked) => setShowOnlyPrimaryMcpCalls(!!checked)} | |
| /> | |
| <label | |
| htmlFor="tool-prompt-calls-filter" | |
| className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer" | |
| > | |
| Show only Tools/Prompts/Resources | |
| </label> | |
| </div> | |
| )} | |
| {filteredMethods.length === 0 ? ( | |
| <div className="flex items-center justify-center py-8"> | |
| <div className="text-sm text-muted-foreground"> | |
| {showOnlyPrimaryMcpCalls | |
| ? 'No tool, prompt, or resource calls recorded yet.' | |
| : 'No method calls recorded yet.'} | |
| </div> | |
| </div> | |
| ) : ( | |
| <DataTable | |
| columns={columns} | |
| data={filteredMethods} | |
| searchColumn="method" | |
| searchPlaceholder="Filter methods..." | |
| pageSize={50} | |
| defaultColumnVisibility={{ | |
| method: true, | |
| count: true, | |
| errors: false, | |
| gradioMetrics: false, | |
| errorRate: true, | |
| averageResponseTime: metrics.transport === 'streamableHttpJson', | |
| lastCalled: true, | |
| }} | |
| defaultSorting={[{ id: 'count', desc: true }]} | |
| /> | |
| )} | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 10.8 kB
- Xet hash:
- 6d2247c55c177ceb7f9c00d877ef8dc1221baff134caa86fb6404d4d37f63354
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.