Skip to content

Commit 709a76c

Browse files
committed
feat: add NPM downloads card to OverviewPage and update README with downloads badge
1 parent 1d1ef16 commit 709a76c

4 files changed

Lines changed: 163 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ATV — DeFi Yield Vault MCP Server
22

33
[![npm version](https://img.shields.io/npm/v/@aarna-ai/mcp-server-atv)](https://www.npmjs.com/package/@aarna-ai/mcp-server-atv)
4+
[![npm downloads](https://img.shields.io/npm/dt/@aarna-ai/mcp-server-atv?label=downloads&logo=npm)](https://www.npmjs.com/package/@aarna-ai/mcp-server-atv)
45
[![smithery badge](https://smithery.ai/badge/aarna-ai/atv)](https://smithery.ai/servers/aarna-ai/atv)
56
[![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.aarna--ai%2Fatv-blue)](https://registry.modelcontextprotocol.io/servers/io.github.aarna-ai/atv)
67
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { useState, useEffect } from 'react';
2+
import {
3+
BarChart,
4+
Bar,
5+
XAxis,
6+
YAxis,
7+
CartesianGrid,
8+
Tooltip,
9+
ResponsiveContainer,
10+
} from 'recharts';
11+
12+
const PACKAGE = '@aarna-ai/mcp-server-atv';
13+
const TODAY = new Date().toISOString().slice(0, 10);
14+
const NPM_BASE = 'https://api.npmjs.org/downloads';
15+
16+
interface TrendPoint {
17+
day: string;
18+
downloads: number;
19+
}
20+
21+
interface State {
22+
total: number;
23+
monthly: number;
24+
weekly: number;
25+
trend: TrendPoint[];
26+
loading: boolean;
27+
error: string | null;
28+
}
29+
30+
export function NpmDownloadsCard() {
31+
const [state, setState] = useState<State>({
32+
total: 0,
33+
monthly: 0,
34+
weekly: 0,
35+
trend: [],
36+
loading: true,
37+
error: null,
38+
});
39+
40+
useEffect(() => {
41+
let cancelled = false;
42+
43+
async function load() {
44+
try {
45+
const [rangeRes, monthlyRes, weeklyRes] = await Promise.all([
46+
fetch(`${NPM_BASE}/range/2000-01-01:${TODAY}/${PACKAGE}`),
47+
fetch(`${NPM_BASE}/point/last-month/${PACKAGE}`),
48+
fetch(`${NPM_BASE}/point/last-week/${PACKAGE}`),
49+
]);
50+
51+
if (!rangeRes.ok || !monthlyRes.ok || !weeklyRes.ok) {
52+
throw new Error('npm registry request failed');
53+
}
54+
55+
const [rangeData, monthlyData, weeklyData] = await Promise.all([
56+
rangeRes.json(),
57+
monthlyRes.json(),
58+
weeklyRes.json(),
59+
]);
60+
61+
if (cancelled) return;
62+
63+
const allDownloads: TrendPoint[] = rangeData.downloads ?? [];
64+
const total = allDownloads.reduce((sum, d) => sum + d.downloads, 0);
65+
const trend = allDownloads.slice(-30).map((d) => ({
66+
day: new Date(d.day).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),
67+
downloads: d.downloads,
68+
}));
69+
70+
setState({
71+
total,
72+
monthly: monthlyData.downloads ?? 0,
73+
weekly: weeklyData.downloads ?? 0,
74+
trend,
75+
loading: false,
76+
error: null,
77+
});
78+
} catch (err) {
79+
if (!cancelled) {
80+
setState((s) => ({ ...s, loading: false, error: (err as Error).message }));
81+
}
82+
}
83+
}
84+
85+
load();
86+
return () => { cancelled = true; };
87+
}, []);
88+
89+
return (
90+
<div className="bg-white rounded-xl border border-gray-200 p-5">
91+
<div className="flex items-center justify-between mb-4">
92+
<div className="flex items-center gap-2">
93+
{/* npm logo */}
94+
<svg width="32" height="12" viewBox="0 0 780 304" xmlns="http://www.w3.org/2000/svg" aria-label="npm">
95+
<path fill="#CB3837" d="M0 0h780v240H0z"/>
96+
<path fill="#fff" d="M240 0h300v240H360V60h-60v180H240zM0 0h240v240H60V60H0zM60 60h60v120h60V60h60v180H60zM480 0h300v240H600V60h-60v180H480z"/>
97+
<path fill="#CB3837" d="M540 60h60v120h60V60h60v180H540z"/>
98+
<path fill="#fff" d="M0 242h780v62H0z"/>
99+
</svg>
100+
<h3 className="text-sm font-medium text-gray-700">Downloads</h3>
101+
</div>
102+
<span className="text-xs text-gray-400 font-mono">{PACKAGE}</span>
103+
</div>
104+
105+
{state.loading ? (
106+
<p className="text-sm text-gray-400">Loading...</p>
107+
) : state.error ? (
108+
<p className="text-sm text-red-500">{state.error}</p>
109+
) : (
110+
<>
111+
<div className="grid grid-cols-3 gap-4 mb-6">
112+
<div className="bg-indigo-50 rounded-lg p-4">
113+
<p className="text-xs text-indigo-500 mb-1">All Time</p>
114+
<p className="text-2xl font-bold text-indigo-700">
115+
{state.total.toLocaleString()}
116+
</p>
117+
</div>
118+
<div className="bg-gray-50 rounded-lg p-4">
119+
<p className="text-xs text-gray-500 mb-1">Last 30 Days</p>
120+
<p className="text-2xl font-bold text-gray-800">
121+
{state.monthly.toLocaleString()}
122+
</p>
123+
</div>
124+
<div className="bg-gray-50 rounded-lg p-4">
125+
<p className="text-xs text-gray-500 mb-1">Last 7 Days</p>
126+
<p className="text-2xl font-bold text-gray-800">
127+
{state.weekly.toLocaleString()}
128+
</p>
129+
</div>
130+
</div>
131+
132+
{state.trend.length > 0 && (
133+
<ResponsiveContainer width="100%" height={160}>
134+
<BarChart data={state.trend} barSize={8}>
135+
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" vertical={false} />
136+
<XAxis
137+
dataKey="day"
138+
tick={{ fontSize: 10 }}
139+
interval="preserveStartEnd"
140+
/>
141+
<YAxis tick={{ fontSize: 10 }} width={30} />
142+
<Tooltip
143+
formatter={(v: number) => [v.toLocaleString(), 'Downloads']}
144+
/>
145+
<Bar dataKey="downloads" fill="#6366f1" radius={[3, 3, 0, 0]} />
146+
</BarChart>
147+
</ResponsiveContainer>
148+
)}
149+
</>
150+
)}
151+
</div>
152+
);
153+
}

apps/dashboard/src/pages/OverviewPage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RequestVolumeChart } from '../components/charts/RequestVolumeChart';
55
import { StatusBreakdownChart } from '../components/charts/StatusBreakdownChart';
66
import { UserAgentChart } from '../components/charts/UserAgentChart';
77
import { RecentRequestsTable } from '../components/tables/RecentRequestsTable';
8+
import { NpmDownloadsCard } from '../components/cards/NpmDownloadsCard';
89
import { useAnalytics } from '../hooks/useAnalytics';
910
import {
1011
fetchOverview,
@@ -63,6 +64,11 @@ export function OverviewPage() {
6364
/>
6465
</div>
6566

67+
{/* NPM download stats */}
68+
<div className="mb-6">
69+
<NpmDownloadsCard />
70+
</div>
71+
6672
{/* Request volume chart */}
6773
{timeSeries.data && <RequestVolumeChart data={timeSeries.data} />}
6874

packages/mcp-server/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# @aarna-ai/mcp-server-atv
22

3+
[![npm version](https://img.shields.io/npm/v/@aarna-ai/mcp-server-atv)](https://www.npmjs.com/package/@aarna-ai/mcp-server-atv)
4+
[![npm downloads](https://img.shields.io/npm/dt/@aarna-ai/mcp-server-atv?label=downloads&logo=npm)](https://www.npmjs.com/package/@aarna-ai/mcp-server-atv)
5+
36
MCP server connector for **ATV** — AI-native access to Aarna's tokenized DeFi yield vaults on Ethereum and Base.
47

58
20 tools for vault discovery, performance metrics (NAV, TVL, APY), deposit/withdraw/stake transaction building, and portfolio tracking.

0 commit comments

Comments
 (0)