Instant-SWOT-Agent / frontend /src /components /MCPDataPanel.tsx
vn6295337's picture
Fix Company Profile path: look at sec_edgar.company
f18a6fd
import React from "react"
import type { MetricEntry } from "@/lib/api"
import type { MCPRawData } from "@/lib/types"
import {
ExternalLink,
Building2,
MapPin,
Briefcase
} from "lucide-react"
interface MCPDataPanelProps {
metrics: MetricEntry[]
rawData?: MCPRawData
companyName?: string
ticker?: string
exchange?: string
cik?: string
}
// Metric name mapping: snake_case → Human Readable
const METRIC_LABELS: Record<string, string> = {
// Fundamentals
revenue: 'Revenue',
net_income: 'Net Income',
gross_profit: 'Gross Profit',
operating_income: 'Operating Income',
gross_margin_pct: 'Gross Margin %',
operating_margin_pct: 'Operating Margin %',
net_margin_pct: 'Net Margin %',
free_cash_flow: 'Free Cash Flow',
operating_cash_flow: 'Operating Cash Flow',
total_assets: 'Total Assets',
total_liabilities: 'Total Liabilities',
stockholders_equity: "Stockholders' Equity",
cash: 'Cash',
long_term_debt: 'Long-term Debt',
net_debt: 'Net Debt',
debt_to_equity: 'Debt to Equity',
rd_expense: 'R&D Expense',
eps: 'EPS',
// Valuation
market_cap: 'Market Cap',
enterprise_value: 'Enterprise Value',
trailing_pe: 'Trailing P/E',
forward_pe: 'Forward P/E',
pb_ratio: 'P/B Ratio',
ps_ratio: 'P/S Ratio',
trailing_peg: 'PEG Ratio',
price_to_fcf: 'Price/FCF',
ev_ebitda: 'EV/EBITDA',
ev_revenue: 'EV/Revenue',
revenue_growth: 'Revenue Growth',
earnings_growth: 'Earnings Growth',
// Volatility
vix: 'VIX',
vxn: 'VXN',
beta: 'Beta',
historical_volatility: 'Historical Volatility',
hist_vol: 'Historical Volatility',
implied_volatility: 'Implied Volatility',
// Macro
gdp_growth: 'GDP Growth',
gdp: 'GDP',
interest_rate: 'Interest Rate',
cpi_inflation: 'CPI Inflation',
inflation: 'Inflation',
unemployment: 'Unemployment',
// Common variations with / or shorthand
'p/e': 'P/E',
'p/b': 'P/B',
'p/s': 'P/S',
'ev/ebitda': 'EV/EBITDA',
'ev/revenue': 'EV/Revenue',
pe: 'P/E',
pb: 'P/B',
ps: 'P/S',
net_margin: 'Net Margin',
}
// Acronyms that should stay uppercase
const ACRONYMS = new Set(['gdp', 'cpi', 'vix', 'vxn', 'pe', 'pb', 'ps', 'ev', 'eps', 'fcf', 'rd', 'ebitda', 'cik', 'ttm', 'fy'])
// Convert snake_case metric name to human-readable label
function formatMetricName(metric: string): string {
// Check lowercase version for case-insensitive matching
const lowerMetric = metric.toLowerCase()
if (METRIC_LABELS[lowerMetric]) {
return METRIC_LABELS[lowerMetric]
}
if (METRIC_LABELS[metric]) {
return METRIC_LABELS[metric]
}
// Fallback: convert snake_case to Title Case with acronym handling
return metric
.split(/[_\s]+/)
.map(word => {
const lower = word.toLowerCase()
// Keep acronyms uppercase
if (ACRONYMS.has(lower)) {
return lower.toUpperCase()
}
// Handle P/B, P/E style (already has /)
if (word.includes('/')) {
return word.toUpperCase()
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
})
.join(' ')
}
// Metrics that should display as percentages
const PERCENTAGE_METRICS = new Set([
'net_margin_pct', 'gross_margin_pct', 'operating_margin_pct',
'net_margin', 'gross_margin', 'operating_margin',
'revenue_growth', 'earnings_growth',
'gdp_growth', 'cpi_inflation', 'inflation', 'unemployment', 'interest_rate',
'historical_volatility', 'implied_volatility', 'hist_vol'
])
// Metrics that should display as ratios (x suffix)
const RATIO_METRICS = new Set([
'trailing_pe', 'forward_pe', 'pb_ratio', 'ps_ratio', 'trailing_peg',
'price_to_fcf', 'ev_ebitda', 'ev_revenue', 'debt_to_equity', 'beta',
'p/e', 'p/b', 'p/s', 'peg'
])
// Metrics that are currency values (large numbers get $B/$M formatting)
const CURRENCY_METRICS = new Set([
'revenue', 'net_income', 'gross_profit', 'operating_income',
'free_cash_flow', 'operating_cash_flow', 'total_assets', 'total_liabilities',
'stockholders_equity', 'cash', 'long_term_debt', 'net_debt', 'rd_expense',
'market_cap', 'enterprise_value'
])
// Format numbers for display with appropriate units
function formatValue(value: string | number, metric?: string): string {
if (value === null || value === undefined) return '—'
if (typeof value === 'string') return value
const num = value
const lowerMetric = (metric || '').toLowerCase()
// Check if this is a percentage metric
if (PERCENTAGE_METRICS.has(lowerMetric)) {
return `${num.toFixed(2)}%`
}
// Check if this is a ratio metric
if (RATIO_METRICS.has(lowerMetric)) {
return `${num.toFixed(2)}x`
}
// Check if this is a currency metric (large numbers)
if (CURRENCY_METRICS.has(lowerMetric)) {
if (Math.abs(num) >= 1e12) return `$${(num / 1e12).toFixed(1)}T`
if (Math.abs(num) >= 1e9) return `$${(num / 1e9).toFixed(1)}B`
if (Math.abs(num) >= 1e6) return `$${(num / 1e6).toFixed(1)}M`
if (Math.abs(num) >= 1e3) return `$${(num / 1e3).toFixed(1)}K`
return `$${num.toFixed(2)}`
}
// Default formatting for other metrics
if (Math.abs(num) >= 1e12) return `$${(num / 1e12).toFixed(1)}T`
if (Math.abs(num) >= 1e9) return `$${(num / 1e9).toFixed(1)}B`
if (Math.abs(num) >= 1e6) return `$${(num / 1e6).toFixed(1)}M`
if (Math.abs(num) < 0.01 && num !== 0) return num.toExponential(2)
if (Number.isInteger(num)) return num.toLocaleString()
return num.toFixed(2)
}
// Infer data source from category and metric
function inferDataSource(category: string, metric: string, form?: string, dataSource?: string): string {
const lowerMetric = metric.toLowerCase()
if (category === 'fundamentals') {
// Use explicit data_source if provided, otherwise fall back to form-based inference
if (dataSource === 'sec_edgar') return 'SEC EDGAR'
if (dataSource === 'yahoo_finance') return 'Yahoo Finance'
// Legacy fallback: infer from form field
return form ? 'SEC EDGAR' : 'Yahoo Finance'
}
if (category === 'valuation') return 'Yahoo Finance'
if (category === 'volatility') {
if (['vix', 'vxn'].includes(lowerMetric)) return 'FRED'
if (['beta', 'historical_volatility'].includes(lowerMetric)) return 'Calculated (Yahoo Finance)'
return 'Market Average'
}
if (category === 'macro') {
if (lowerMetric === 'gdp_growth') return 'BEA'
if (lowerMetric === 'interest_rate') return 'FRED'
return 'BLS'
}
return category
}
// Infer data type from form and metric
function inferDataType(form?: string, metric?: string, source?: string): string {
if (form === '10-K') return 'FY'
if (form === '10-Q') return 'Q'
const lowerMetric = (metric || '').toLowerCase()
// Valuation metrics are spot/current prices (not TTM)
const spotMetrics = [
'current_price', 'market_cap', 'enterprise_value',
'trailing_pe', 'forward_pe', 'pb_ratio', 'ps_ratio',
'trailing_peg', 'forward_peg', 'ev_ebitda', 'ev_revenue',
'price_to_fcf', 'dividend_yield'
]
if (spotMetrics.includes(lowerMetric)) return 'Spot'
// Growth metrics are year-over-year
const yoyMetrics = ['revenue_growth', 'earnings_growth']
if (yoyMetrics.includes(lowerMetric)) return 'YoY'
// Volatility/macro metrics
if (['vix', 'vxn'].includes(lowerMetric)) return 'Daily'
if (['gdp_growth'].includes(lowerMetric)) return 'Quarterly'
if (['interest_rate', 'cpi_inflation', 'unemployment'].includes(lowerMetric)) return 'Monthly'
if (lowerMetric === 'beta') return '1Y'
if (lowerMetric === 'historical_volatility') return '30D'
if (lowerMetric === 'implied_volatility') return 'Forward'
return 'TTM'
}
// Extract date from multiple possible field names
function extractDate(item: Record<string, unknown>): string | undefined {
// Check multiple possible date field names
const dateFields = ['datetime', 'published_date', 'date', 'publishedAt', 'timestamp', 'created_at']
for (const field of dateFields) {
if (item[field]) {
return String(item[field])
}
}
return undefined
}
// Normalize various date formats to YYYY-MM-DD
function normalizeDate(dateStr: string | undefined | null): string {
if (!dateStr) return '-'
const str = String(dateStr).trim()
// Already a dash or empty
if (str === '-' || str === '') return '-'
// Quarter format: 2025Q3 -> 2025-09-30 (BEA quarters: Q1=Mar, Q2=Jun, Q3=Sep, Q4=Dec)
const quarterMatch = str.match(/^(\d{4})Q(\d)$/)
if (quarterMatch) {
const year = quarterMatch[1]
const quarter = parseInt(quarterMatch[2], 10)
// BEA quarter end dates: Q1=03-31, Q2=06-30, Q3=09-30, Q4=12-31
const quarterEndDates: Record<number, string> = {
1: '03-31',
2: '06-30',
3: '09-30',
4: '12-31'
}
return `${year}-${quarterEndDates[quarter] || '12-31'}`
}
// Month-year format: 2025-November -> 2025-11-30 (last day of month)
const monthYearMatch = str.match(/^(\d{4})-(\w+)$/)
if (monthYearMatch) {
const year = parseInt(monthYearMatch[1], 10)
const monthName = monthYearMatch[2].toLowerCase()
const monthMap: Record<string, number> = {
january: 1, february: 2, march: 3, april: 4, may: 5, june: 6,
july: 7, august: 8, september: 9, october: 10, november: 11, december: 12
}
const month = monthMap[monthName]
if (month) {
// Get last day of month
const lastDay = new Date(year, month, 0).getDate()
return `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`
}
}
// Compact format: 20260108 -> 2026-01-08
const compactMatch = str.match(/^(\d{4})(\d{2})(\d{2})$/)
if (compactMatch) {
return `${compactMatch[1]}-${compactMatch[2]}-${compactMatch[3]}`
}
// ISO format already: YYYY-MM-DD - return as is
if (/^\d{4}-\d{2}-\d{2}$/.test(str)) {
return str
}
// ISO datetime: YYYY-MM-DDTHH:MM:SS -> YYYY-MM-DD
const isoMatch = str.match(/^(\d{4}-\d{2}-\d{2})T/)
if (isoMatch) {
return isoMatch[1]
}
// Return original if no pattern matches
return str
}
// Format fiscal period label (e.g., "FY 2023" or "Q3 2024")
function formatFiscalPeriod(form?: string, fiscalYear?: number, endDate?: string): string | null {
if (!fiscalYear) return null
if (form === '10-K') {
return `FY ${fiscalYear}`
} else if (form === '10-Q' && endDate) {
try {
// Parse quarter from end date (YYYY-MM-DD)
const month = parseInt(endDate.split('-')[1], 10)
const quarter = Math.ceil(month / 3)
return `Q${quarter} ${fiscalYear}`
} catch {
return `FY ${fiscalYear}`
}
}
return `FY ${fiscalYear}`
}
export function MCPDataPanel({ metrics, rawData, companyName, ticker, exchange, cik }: MCPDataPanelProps) {
// Group metrics by source, including temporal data
const groupedMetrics = React.useMemo(() => {
const groups: Record<string, Array<{
metric: string
value: string | number
fiscalPeriod?: string | null
endDate?: string
form?: string
dataSource?: string
}>> = {
fundamentals: [],
valuation: [],
volatility: [],
macro: [],
news: [],
sentiment: []
}
for (const m of metrics) {
const source = m.source.toLowerCase()
if (source in groups) {
// Format fiscal period if temporal data is available
const fiscalPeriod = formatFiscalPeriod(m.form, m.fiscal_year, m.end_date)
groups[source].push({
metric: m.metric,
value: m.value,
fiscalPeriod,
endDate: m.end_date,
form: m.form,
dataSource: m.data_source
})
}
}
return groups
}, [metrics])
// Build quantitative rows for table display
const quantitativeRows = React.useMemo(() => {
const categories = ['fundamentals', 'valuation', 'volatility', 'macro']
const rows: Array<{
metric: string
value: string
dataType: string
asOf: string
source: string
category: string
}> = []
for (const cat of categories) {
for (const m of groupedMetrics[cat] || []) {
rows.push({
metric: m.metric,
value: formatValue(m.value, m.metric),
dataType: inferDataType(m.form, m.metric),
asOf: normalizeDate(m.endDate),
source: inferDataSource(cat, m.metric, m.form, m.dataSource),
category: cat.charAt(0).toUpperCase() + cat.slice(1)
})
}
}
return rows
}, [groupedMetrics])
// Extract news articles from raw_data if available
// Actual structure: rawData.metrics.news.items[]
const newsArticles = React.useMemo(() => {
if (!rawData) return []
const articles: Array<{
title: string
url: string
date?: string
source?: string
}> = []
// Navigate to metrics.news.items - the actual structure from Research Service
const metricsObj = rawData.metrics as Record<string, unknown> | undefined
const newsData = metricsObj?.news as Record<string, unknown> | undefined
if (newsData) {
// Get items array (flat list with source field)
const items = newsData.items as Array<Record<string, unknown>> | undefined
if (items && Array.isArray(items) && items.length > 0) {
for (const a of items) {
articles.push({
title: String(a.title || a.content || 'News article'),
url: String(a.url || '#'),
date: extractDate(a),
source: a.source ? String(a.source) : 'Tavily'
})
}
}
}
// Fallback: check rawData.news directly (legacy format)
if (articles.length === 0 && rawData.news && Array.isArray(rawData.news)) {
for (const a of rawData.news.slice(0, 10)) {
articles.push({
title: a.title || 'News article',
url: a.url || '#',
date: extractDate(a as Record<string, unknown>),
source: a.source || 'Tavily'
})
}
}
return articles
}, [rawData])
// Extract sentiment items (individual news/posts from Finnhub and Reddit)
// Actual structure: rawData.metrics.sentiment.items[] with source field for filtering
const sentimentItems = React.useMemo(() => {
if (!rawData) return []
const results: Array<{
title: string
url: string
date?: string
source: string
subreddit?: string
}> = []
// Navigate to metrics.sentiment.items - flat array with source field
const metricsObj = rawData.metrics as Record<string, unknown> | undefined
const sentimentData = metricsObj?.sentiment as Record<string, unknown> | undefined
if (!sentimentData) return []
const items = sentimentData.items as Array<Record<string, unknown>> | undefined
if (!items || !Array.isArray(items)) return []
for (const item of items) {
const source = String(item.source || 'Unknown')
results.push({
title: String(item.title || item.content || `${source} item`),
url: String(item.url || '#'),
date: extractDate(item),
source,
subreddit: item.subreddit ? String(item.subreddit) : undefined
})
}
return results
}, [rawData])
// Build qualitative rows for table display (news + sentiment)
const qualitativeRows = React.useMemo(() => {
const rows: Array<{
title: string
date: string
source: string
subreddit: string
url: string
category: string
}> = []
// News articles
for (const article of newsArticles) {
rows.push({
title: article.title,
date: normalizeDate(article.date),
source: article.source || 'Tavily',
subreddit: '-',
url: article.url,
category: 'News'
})
}
// Sentiment items
for (const item of sentimentItems) {
rows.push({
title: item.title,
date: normalizeDate(item.date),
source: item.source,
subreddit: item.subreddit ? `r/${item.subreddit}` : '-',
url: item.url,
category: 'Sentiment'
})
}
return rows
}, [newsArticles, sentimentItems])
// Extract company profile info from raw_data if available
const companyProfile = React.useMemo(() => {
if (!rawData) return null
// Path 1: metrics.fundamentals.sec_edgar.company (from FinancialsBasket)
const fundamentals = rawData.metrics?.fundamentals as Record<string, unknown> | undefined
const secEdgar = fundamentals?.sec_edgar as Record<string, unknown> | undefined
const secCompany = secEdgar?.company as Record<string, unknown> | undefined
// Path 2: multi_source.fundamentals_all.sec_edgar.company (alternative path)
const multiSource = rawData.multi_source as Record<string, unknown> | undefined
const fundsAll = multiSource?.fundamentals_all as Record<string, unknown> | undefined
const fundsSecEdgar = fundsAll?.sec_edgar as Record<string, unknown> | undefined
const fundsCompany = fundsSecEdgar?.company as Record<string, unknown> | undefined
// Use whichever company object is available
const company = secCompany || fundsCompany
// Get sector from multiple possible locations
const secSector = secEdgar?.sector as string || fundsSecEdgar?.sector as string
const companySector = company?.sector as string
// Extract business_address from SEC EDGAR company info
const businessAddr = company?.business_address as Record<string, unknown> | undefined
// Build HQ location from business_address
let hqLocation = null
if (businessAddr) {
const city = businessAddr.city as string
const state = businessAddr.state_or_country as string || businessAddr.stateOrCountry as string || businessAddr.state as string
if (city && state) {
hqLocation = `${city}, ${state}`
}
}
// Legacy fallback for older data structures
const legacyProfile = rawData.company_info as Record<string, unknown> | undefined
if (!hqLocation && legacyProfile) {
const city = legacyProfile.city as string
const state = legacyProfile.state as string || legacyProfile.stateOrCountry as string
if (city && state) {
hqLocation = `${city}, ${state}`
}
}
return {
sector: secSector || companySector || legacyProfile?.sector as string || null,
industry: company?.sic_description as string || legacyProfile?.industry as string || null,
hqLocation,
employees: legacyProfile?.fullTimeEmployees as number || legacyProfile?.employees as number || null,
website: legacyProfile?.website as string || null,
sicDescription: company?.sic_description as string || null,
}
}, [rawData])
// Check if we have any data at all
const hasAnyData = metrics.length > 0 || newsArticles.length > 0
if (!hasAnyData) {
return null
}
return (
<div className="space-y-4">
{/* Company Profile */}
{(companyName || ticker) && (
<div className="bg-card rounded-lg border border-border overflow-hidden">
<div className="px-3 py-2 bg-muted/50 border-b border-border">
<h3 className="text-sm font-medium text-foreground">Company Profile</h3>
</div>
<div className="p-3 flex flex-wrap gap-x-6 gap-y-2 text-sm">
{companyName && (
<div className="flex items-center gap-2">
<Building2 className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{companyName}</span>
{ticker && <span className="text-muted-foreground">({ticker})</span>}
</div>
)}
{(exchange || cik) && (
<div className="flex items-center gap-2 text-muted-foreground">
{exchange && <span>{exchange}</span>}
{exchange && cik && <span></span>}
{cik && <span>CIK: {cik}</span>}
</div>
)}
{companyProfile?.sector && (
<div className="flex items-center gap-2">
<Briefcase className="h-4 w-4 text-muted-foreground" />
<span>{companyProfile.sector}</span>
{companyProfile?.industry && companyProfile.industry !== companyProfile.sector && (
<span className="text-muted-foreground">/ {companyProfile.industry}</span>
)}
</div>
)}
{companyProfile?.hqLocation && (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-muted-foreground" />
<span>{companyProfile.hqLocation}</span>
</div>
)}
{companyProfile?.employees && (
<div className="flex items-center gap-2">
<span className="text-muted-foreground">Employees:</span>
<span>{Number(companyProfile.employees).toLocaleString()}</span>
</div>
)}
{companyProfile?.website && (
<div className="flex items-center gap-2">
<ExternalLink className="h-4 w-4 text-muted-foreground" />
<a
href={companyProfile.website}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 hover:underline"
>
{companyProfile.website.replace(/^https?:\/\//, '').replace(/\/$/, '')}
</a>
</div>
)}
{companyProfile?.sicDescription && (
<div className="flex items-center gap-2 text-muted-foreground">
<span>SIC: {companyProfile.sicDescription}</span>
</div>
)}
</div>
</div>
)}
{/* Quantitative Data Table */}
{quantitativeRows.length > 0 && (
<div className="bg-card rounded-lg border border-border overflow-hidden w-fit">
<div className="px-4 py-2 bg-muted/50 border-b border-border">
<h3 className="text-sm font-medium text-foreground">Quantitative Data</h3>
</div>
<div className="overflow-x-auto p-2">
<table className="text-xs">
<thead className="bg-muted/30">
<tr>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Ref</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Metric</th>
<th className="px-3 py-1.5 text-right font-medium text-muted-foreground">Value</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Data Type</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">As Of</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Source</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Category</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{quantitativeRows.map((row, idx) => (
<tr key={idx} className="hover:bg-muted/20">
<td className="px-3 py-1.5 text-muted-foreground">M{String(idx + 1).padStart(2, '0')}</td>
<td className="px-3 py-1.5">{formatMetricName(row.metric)}</td>
<td className="px-3 py-1.5 text-right font-medium">{row.value}</td>
<td className="px-3 py-1.5 text-muted-foreground">{row.dataType}</td>
<td className="px-3 py-1.5 text-muted-foreground">{row.asOf}</td>
<td className="px-3 py-1.5 text-muted-foreground">{row.source}</td>
<td className="px-3 py-1.5">{row.category}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Qualitative Data Table */}
{qualitativeRows.length > 0 && (
<div className="bg-card rounded-lg border border-border overflow-hidden w-fit">
<div className="px-4 py-2 bg-muted/50 border-b border-border">
<h3 className="text-sm font-medium text-foreground">Qualitative Data</h3>
</div>
<div className="overflow-x-auto p-2">
<table className="text-xs">
<thead className="bg-muted/30">
<tr>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">S/N</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Title</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Date</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Source</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Subreddit</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">URL</th>
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">Category</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{qualitativeRows.map((row, idx) => (
<tr key={idx} className="hover:bg-muted/20">
<td className="px-3 py-1.5 text-muted-foreground">{idx + 1}</td>
<td className="px-3 py-1.5 max-w-[250px] truncate" title={row.title}>{row.title}</td>
<td className="px-3 py-1.5 text-muted-foreground">{row.date}</td>
<td className="px-3 py-1.5">{row.source}</td>
<td className="px-3 py-1.5 text-muted-foreground">{row.subreddit}</td>
<td className="px-3 py-1.5">
<a
href={row.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 hover:underline inline-flex items-center gap-1"
>
Link
<ExternalLink className="h-3 w-3" />
</a>
</td>
<td className="px-3 py-1.5">{row.category}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
)
}
export default MCPDataPanel