Add system-monitor-server example

Demo MCP App showcasing real-time OS metrics visualization with:
- Per-core CPU usage via stacked area chart (Chart.js)
- Memory usage bar gauge with color-coded thresholds
- Auto-polling at 2-second intervals
- Light/dark theme support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2025-12-03 14:27:16 -06:00
commit fa2d413f1d
10 changed files with 1075 additions and 0 deletions

View file

@ -0,0 +1,49 @@
# Example: System Monitor App
A demo MCP App that displays real-time OS metrics with a stacked area chart for per-core CPU usage and a bar gauge for memory.
## Features
- **Per-Core CPU Monitoring**: Stacked area chart showing individual CPU core utilization over a 1-minute sliding window
- **Memory Usage**: Horizontal bar gauge with color-coded thresholds (green/yellow/red)
- **System Info**: Hostname, platform, and uptime display
- **Auto-Polling**: Automatically starts monitoring on load with 2-second refresh interval
- **Theme Support**: Adapts to light/dark mode preferences
## Running
1. Install dependencies:
```bash
npm install
```
2. Build and start the server:
```bash
npm start
```
The server will listen on `http://localhost:3001/mcp`.
3. View using the [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) example or another MCP Apps-compatible host.
## Architecture
### Server (`server.ts`)
Exposes a single `get-system-stats` tool that returns:
- Raw per-core CPU timing data (idle/total counters)
- Memory usage (used/total/percentage)
- System info (hostname, platform, uptime)
The tool is linked to a UI resource via `_meta[RESOURCE_URI_META_KEY]`.
### App (`src/mcp-app.ts`)
- Uses Chart.js for the stacked area chart visualization
- Polls the server tool every 2 seconds
- Computes CPU usage percentages client-side from timing deltas
- Maintains a 30-point history (1 minute at 2s intervals)
- Updates all UI elements on each poll

View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Monitor</title>
</head>
<body>
<main class="main">
<header class="header">
<h1 class="title">System Monitor</h1>
<div class="header-controls">
<button id="poll-toggle-btn" class="btn">Start</button>
<span class="status">
<span id="status-indicator" class="status-indicator"></span>
<span id="status-text" class="status-text">Ready</span>
</span>
</div>
</header>
<section class="chart-section">
<h2 class="section-title">CPU Usage</h2>
<div class="chart-container">
<canvas id="cpu-chart"></canvas>
</div>
</section>
<section class="memory-section">
<h2 class="section-title">Memory</h2>
<div class="memory-bar-container">
<div class="memory-bar">
<div id="memory-bar-fill" class="memory-bar-fill"></div>
</div>
<span id="memory-percent" class="memory-percent">--%</span>
</div>
<div id="memory-detail" class="memory-detail">-- / --</div>
</section>
<section class="info-section">
<h2 class="section-title">System Info</h2>
<dl class="info-list">
<div class="info-item">
<dt>Hostname</dt>
<dd id="info-hostname">--</dd>
</div>
<div class="info-item">
<dt>Platform</dt>
<dd id="info-platform">--</dd>
</div>
<div class="info-item">
<dt>Uptime</dt>
<dd id="info-uptime">--</dd>
</div>
</dl>
</section>
</main>
<script type="module" src="./src/mcp-app.ts"></script>
</body>
</html>

View file

@ -0,0 +1,31 @@
{
"name": "system-monitor-server",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "INPUT=mcp-app.html vite build",
"watch": "INPUT=mcp-app.html vite build --watch",
"serve": "bun server.ts",
"start": "NODE_ENV=development npm run build && npm run serve",
"dev": "NODE_ENV=development concurrently 'npm run watch' 'npm run serve'"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "../..",
"@modelcontextprotocol/sdk": "^1.22.0",
"chart.js": "^4.4.0",
"systeminformation": "^5.27.11",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"concurrently": "^9.2.1",
"cors": "^2.8.5",
"express": "^5.1.0",
"typescript": "^5.9.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0"
}
}

View file

@ -0,0 +1,218 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import si from "systeminformation";
import { z } from "zod";
import { RESOURCE_URI_META_KEY } from "../../dist/src/app";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
// Schemas - types are derived from these using z.infer
const CpuCoreSchema = z.object({
idle: z.number(),
total: z.number(),
});
const CpuStatsSchema = z.object({
cores: z.array(CpuCoreSchema),
model: z.string(),
count: z.number(),
});
const MemoryStatsSchema = z.object({
usedBytes: z.number(),
totalBytes: z.number(),
usedPercent: z.number(),
freeBytes: z.number(),
usedFormatted: z.string(),
totalFormatted: z.string(),
});
const SystemInfoSchema = z.object({
hostname: z.string(),
platform: z.string(),
arch: z.string(),
uptime: z.number(),
uptimeFormatted: z.string(),
});
const SystemStatsSchema = z.object({
cpu: CpuStatsSchema,
memory: MemoryStatsSchema,
system: SystemInfoSchema,
timestamp: z.string(),
});
// Types derived from schemas
type CpuCore = z.infer<typeof CpuCoreSchema>;
type MemoryStats = z.infer<typeof MemoryStatsSchema>;
type SystemStats = z.infer<typeof SystemStatsSchema>;
const DIST_DIR = path.join(import.meta.dirname, "dist");
// Returns raw CPU timing data per core (client calculates usage from deltas)
function getCpuSnapshots(): CpuCore[] {
return os.cpus().map((cpu) => {
const times = cpu.times;
const idle = times.idle;
const total = times.user + times.nice + times.sys + times.idle + times.irq;
return { idle, total };
});
}
function formatUptime(seconds: number): string {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const parts: string[] = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
return parts.length > 0 ? parts.join(" ") : "< 1m";
}
function formatBytes(bytes: number): string {
const units = ["B", "KB", "MB", "GB", "TB"];
let value = bytes;
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
return `${value.toFixed(1)} ${units[unitIndex]}`;
}
async function getMemoryStats(): Promise<MemoryStats> {
const mem = await si.mem();
return {
usedBytes: mem.active,
totalBytes: mem.total,
usedPercent: Math.round((mem.active / mem.total) * 100),
freeBytes: mem.available,
usedFormatted: formatBytes(mem.active),
totalFormatted: formatBytes(mem.total),
};
}
const server = new McpServer({
name: "System Monitor Server",
version: "1.0.0",
});
// Register the get-system-stats tool and its associated UI resource
{
const resourceUri = "ui://system-monitor/mcp-app.html";
server.registerTool(
"get-system-stats",
{
title: "Get System Stats",
description:
"Returns current system statistics including per-core CPU usage, memory, and system info.",
inputSchema: {},
outputSchema: SystemStatsSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
const cpuSnapshots = getCpuSnapshots();
const cpuInfo = os.cpus()[0];
const memory = await getMemoryStats();
const uptimeSeconds = os.uptime();
const stats: SystemStats = {
cpu: {
cores: cpuSnapshots,
model: cpuInfo?.model ?? "Unknown",
count: os.cpus().length,
},
memory,
system: {
hostname: os.hostname(),
platform: `${os.platform()} ${os.arch()}`,
arch: os.arch(),
uptime: uptimeSeconds,
uptimeFormatted: formatUptime(uptimeSeconds),
},
timestamp: new Date().toISOString(),
};
return {
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
structuredContent: stats,
};
},
);
server.registerResource(
resourceUri,
resourceUri,
{ description: "System Monitor UI" },
async (): Promise<ReadResourceResult> => {
const html = await fs.readFile(
path.join(DIST_DIR, "mcp-app.html"),
"utf-8",
);
return {
contents: [{ uri: resourceUri, mimeType: "text/html+mcp", text: html }],
};
},
);
}
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req: Request, res: Response) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(PORT, () => {
console.log(
`System Monitor Server listening on http://localhost:${PORT}/mcp`,
);
});
function shutdown() {
console.log("\nShutting down...");
httpServer.close(() => {
console.log("Server closed");
process.exit(0);
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

View file

@ -0,0 +1,12 @@
* {
box-sizing: border-box;
}
html, body {
font-family: system-ui, -apple-system, sans-serif;
font-size: 1rem;
}
code {
font-size: 1em;
}

View file

@ -0,0 +1,233 @@
:root {
--color-bg: #ffffff;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-danger: #ef4444;
--color-bar-bg: #e5e7eb;
--color-card-bg: #f9fafb;
--color-border: #e5e7eb;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111827;
--color-text: #f9fafb;
--color-text-muted: #9ca3af;
--color-primary: #3b82f6;
--color-primary-hover: #60a5fa;
--color-success: #34d399;
--color-warning: #fbbf24;
--color-danger: #f87171;
--color-bar-bg: #374151;
--color-card-bg: #1f2937;
--color-border: #374151;
}
}
html, body {
margin: 0;
padding: 0;
background: var(--color-bg);
color: var(--color-text);
}
.main {
width: 600px;
margin: 0 auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
flex-shrink: 0;
}
.header-controls {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.btn {
padding: 6px 16px;
min-width: 60px;
font-size: 0.875rem;
font-weight: 500;
border: none;
border-radius: 6px;
background: var(--color-primary);
color: white;
cursor: pointer;
transition: background 0.15s ease;
}
.btn:hover {
background: var(--color-primary-hover);
}
.btn.active {
background: var(--color-danger);
}
.btn.active:hover {
background: #dc2626;
}
.status {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.75rem;
color: var(--color-text-muted);
}
.status-text {
min-width: 8ch;
font-family: ui-monospace, monospace;
text-align: right;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--color-text-muted);
}
.status-indicator.polling {
background: var(--color-success);
animation: pulse 1.5s infinite;
}
.status-indicator.error {
background: var(--color-danger);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Section titles */
.section-title {
margin: 0 0 8px 0;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
}
/* Chart section */
.chart-section {
background: var(--color-card-bg);
border-radius: 8px;
padding: 12px;
border: 1px solid var(--color-border);
}
.chart-container {
position: relative;
height: 200px;
}
/* Memory section */
.memory-section {
background: var(--color-card-bg);
border-radius: 8px;
padding: 12px;
border: 1px solid var(--color-border);
}
.memory-bar-container {
display: flex;
align-items: center;
gap: 12px;
}
.memory-bar {
flex: 1;
height: 20px;
background: var(--color-bar-bg);
border-radius: 10px;
overflow: hidden;
}
.memory-bar-fill {
height: 100%;
width: 0%;
background: var(--color-success);
border-radius: 10px;
transition: width 0.3s ease, background 0.3s ease;
}
.memory-bar-fill.warning {
background: var(--color-warning);
}
.memory-bar-fill.danger {
background: var(--color-danger);
}
.memory-percent {
font-size: 1.25rem;
font-weight: 600;
min-width: 50px;
text-align: right;
}
.memory-detail {
margin-top: 4px;
font-size: 0.875rem;
color: var(--color-text-muted);
}
/* Info section */
.info-section {
background: var(--color-card-bg);
border-radius: 8px;
padding: 12px;
border: 1px solid var(--color-border);
}
.info-list {
margin: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.info-item dt {
font-size: 0.875rem;
color: var(--color-text-muted);
}
.info-item dd {
margin: 0;
font-size: 0.875rem;
font-weight: 500;
}

View file

@ -0,0 +1,360 @@
/**
* @file System Monitor App - displays real-time OS metrics with Chart.js
*/
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
import { Chart, registerables } from "chart.js";
import "./global.css";
import "./mcp-app.css";
// Register Chart.js components
Chart.register(...registerables);
const log = {
info: console.log.bind(console, "[APP]"),
error: console.error.bind(console, "[APP]"),
};
// Types for system stats response
interface SystemStats {
cpu: {
cores: Array<{ idle: number; total: number }>;
model: string;
count: number;
};
memory: {
usedBytes: number;
totalBytes: number;
usedPercent: number;
freeBytes: number;
usedFormatted: string;
totalFormatted: string;
};
system: {
hostname: string;
platform: string;
arch: string;
uptime: number;
uptimeFormatted: string;
};
timestamp: string;
}
// DOM element references
const pollToggleBtn = document.getElementById("poll-toggle-btn")!;
const statusIndicator = document.getElementById("status-indicator")!;
const statusText = document.getElementById("status-text")!;
const cpuChartCanvas = document.getElementById(
"cpu-chart",
) as HTMLCanvasElement;
const memoryBarFill = document.getElementById("memory-bar-fill")!;
const memoryPercent = document.getElementById("memory-percent")!;
const memoryDetail = document.getElementById("memory-detail")!;
const infoHostname = document.getElementById("info-hostname")!;
const infoPlatform = document.getElementById("info-platform")!;
const infoUptime = document.getElementById("info-uptime")!;
// Polling state
const HISTORY_LENGTH = 30;
const POLL_INTERVAL = 2000;
interface PollingState {
isPolling: boolean;
intervalId: number | null;
cpuHistory: number[][]; // [timestamp][coreIndex] = usage%
labels: string[];
coreCount: number;
chart: Chart | null;
previousCpuSnapshots: Array<{ idle: number; total: number }> | null;
}
const state: PollingState = {
isPolling: false,
intervalId: null,
cpuHistory: [],
labels: [],
coreCount: 0,
chart: null,
previousCpuSnapshots: null,
};
// Color palette for CPU cores (distinct colors)
const CORE_COLORS = [
"rgba(59, 130, 246, 0.7)", // blue
"rgba(16, 185, 129, 0.7)", // green
"rgba(245, 158, 11, 0.7)", // amber
"rgba(239, 68, 68, 0.7)", // red
"rgba(139, 92, 246, 0.7)", // purple
"rgba(236, 72, 153, 0.7)", // pink
"rgba(20, 184, 166, 0.7)", // teal
"rgba(249, 115, 22, 0.7)", // orange
"rgba(34, 197, 94, 0.7)", // emerald
"rgba(168, 85, 247, 0.7)", // violet
"rgba(251, 146, 60, 0.7)", // orange-light
"rgba(74, 222, 128, 0.7)", // green-light
"rgba(96, 165, 250, 0.7)", // blue-light
"rgba(248, 113, 113, 0.7)", // red-light
"rgba(167, 139, 250, 0.7)", // purple-light
"rgba(244, 114, 182, 0.7)", // pink-light
];
// Calculate CPU usage percentages from raw timing data
function calculateCpuUsage(
current: Array<{ idle: number; total: number }>,
previous: Array<{ idle: number; total: number }> | null,
): number[] {
if (!previous || previous.length !== current.length) {
return current.map(() => 0);
}
return current.map((cur, i) => {
const prev = previous[i];
const idleDiff = cur.idle - prev.idle;
const totalDiff = cur.total - prev.total;
if (totalDiff === 0) return 0;
return Math.round((1 - idleDiff / totalDiff) * 100);
});
}
function initChart(coreCount: number): Chart {
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const textColor = isDarkMode ? "#9ca3af" : "#6b7280";
const gridColor = isDarkMode ? "#374151" : "#e5e7eb";
const datasets = Array.from({ length: coreCount }, (_, i) => ({
label: `P${i}`,
data: [] as number[],
fill: true,
backgroundColor: CORE_COLORS[i % CORE_COLORS.length],
borderColor: CORE_COLORS[i % CORE_COLORS.length].replace("0.7", "1"),
borderWidth: 1,
pointRadius: 0,
tension: 0.3,
}));
return new Chart(cpuChartCanvas, {
type: "line",
data: {
labels: [],
datasets,
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 300,
},
interaction: {
intersect: false,
mode: "index",
},
plugins: {
legend: {
display: true,
position: "bottom",
labels: {
boxWidth: 12,
padding: 8,
font: { size: 10 },
color: textColor,
},
},
tooltip: {
enabled: true,
callbacks: {
label: (context) =>
`${context.dataset.label}: ${context.parsed.y}%`,
},
},
},
scales: {
x: {
display: false,
},
y: {
stacked: true,
min: 0,
max: coreCount * 100,
ticks: {
callback: (value) => `${value}%`,
color: textColor,
font: { size: 10 },
},
grid: {
color: gridColor,
},
},
},
},
});
}
function updateChart(cpuHistory: number[][], labels: string[]): void {
if (!state.chart) return;
state.chart.data.labels = labels;
// Transpose: cpuHistory[time][core] -> datasets[core].data[time]
for (let coreIdx = 0; coreIdx < state.coreCount; coreIdx++) {
state.chart.data.datasets[coreIdx].data = cpuHistory.map(
(snapshot) => snapshot[coreIdx] ?? 0,
);
}
// Dynamic y-axis scaling
// Calculate max stacked value (sum of all cores at each time point)
const stackedTotals = cpuHistory.map((snapshot) =>
snapshot.reduce((sum, val) => sum + val, 0),
);
const currentMax = Math.max(...stackedTotals, 0);
// Add 20% headroom, clamp to reasonable bounds
const headroom = 1.2;
const minVisible = state.coreCount * 15; // At least 15% per core visible
const absoluteMax = state.coreCount * 100;
const dynamicMax = Math.min(
Math.max(currentMax * headroom, minVisible),
absoluteMax,
);
state.chart.options.scales!.y!.max = dynamicMax;
state.chart.update("none");
}
function updateMemoryBar(memory: SystemStats["memory"]): void {
const percent = memory.usedPercent;
memoryBarFill.style.width = `${percent}%`;
memoryBarFill.classList.remove("warning", "danger");
if (percent >= 80) {
memoryBarFill.classList.add("danger");
} else if (percent >= 60) {
memoryBarFill.classList.add("warning");
}
memoryPercent.textContent = `${percent}%`;
memoryDetail.textContent = `${memory.usedFormatted} / ${memory.totalFormatted}`;
}
function updateSystemInfo(system: SystemStats["system"]): void {
infoHostname.textContent = system.hostname;
infoPlatform.textContent = system.platform;
infoUptime.textContent = system.uptimeFormatted;
}
function updateStatus(text: string, isPolling = false, isError = false): void {
statusText.textContent = text;
statusIndicator.classList.remove("polling", "error");
if (isError) {
statusIndicator.classList.add("error");
} else if (isPolling) {
statusIndicator.classList.add("polling");
}
}
// Create app instance
const app = new App({ name: "System Monitor", version: "1.0.0" });
async function fetchStats(): Promise<void> {
try {
const result = await app.callServerTool({
name: "get-system-stats",
arguments: {},
});
const stats = result.structuredContent as unknown as SystemStats;
// Initialize chart on first data if needed
if (!state.chart && stats.cpu.count > 0) {
state.coreCount = stats.cpu.count;
state.chart = initChart(state.coreCount);
}
// Calculate CPU usage from raw timing data (client-side)
const coreUsages = calculateCpuUsage(
stats.cpu.cores,
state.previousCpuSnapshots,
);
state.previousCpuSnapshots = stats.cpu.cores;
state.cpuHistory.push(coreUsages);
state.labels.push(new Date().toLocaleTimeString());
// Trim to window size
if (state.cpuHistory.length > HISTORY_LENGTH) {
state.cpuHistory.shift();
state.labels.shift();
}
// Update UI
updateChart(state.cpuHistory, state.labels);
updateMemoryBar(stats.memory);
updateSystemInfo(stats.system);
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
updateStatus(time, true);
} catch (error) {
log.error("Failed to fetch stats:", error);
updateStatus("Error", false, true);
}
}
function startPolling(): void {
if (state.isPolling) return;
state.isPolling = true;
pollToggleBtn.textContent = "Stop";
pollToggleBtn.classList.add("active");
updateStatus("Starting...", true);
// Immediate first fetch
fetchStats();
// Start interval
state.intervalId = window.setInterval(fetchStats, POLL_INTERVAL);
}
function stopPolling(): void {
if (!state.isPolling) return;
state.isPolling = false;
if (state.intervalId) {
clearInterval(state.intervalId);
state.intervalId = null;
}
pollToggleBtn.textContent = "Start";
pollToggleBtn.classList.remove("active");
updateStatus("Stopped");
}
function togglePolling(): void {
if (state.isPolling) {
stopPolling();
} else {
startPolling();
}
}
// Event listeners
pollToggleBtn.addEventListener("click", togglePolling);
// Handle theme changes
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
if (state.chart) {
state.chart.destroy();
state.chart = initChart(state.coreCount);
updateChart(state.cpuHistory, state.labels);
}
});
// Register handlers and connect
app.onerror = log.error;
app.connect(new PostMessageTransport(window.parent));
// Auto-start polling after a short delay
setTimeout(startPolling, 500);

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "server.ts"]
}

View file

@ -0,0 +1,24 @@
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
const INPUT = process.env.INPUT;
if (!INPUT) {
throw new Error("INPUT environment variable is not set");
}
const isDevelopment = process.env.NODE_ENV === "development";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
sourcemap: isDevelopment ? "inline" : undefined,
cssMinify: !isDevelopment,
minify: !isDevelopment,
rollupOptions: {
input: INPUT,
},
outDir: "dist",
emptyOutDir: false,
},
});

69
package-lock.json generated
View file

@ -160,6 +160,27 @@
"vite-plugin-singlefile": "^2.3.0"
}
},
"examples/system-monitor-server": {
"version": "1.0.0",
"dependencies": {
"@modelcontextprotocol/ext-apps": "../..",
"@modelcontextprotocol/sdk": "^1.22.0",
"chart.js": "^4.4.0",
"systeminformation": "^5.27.11",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"concurrently": "^9.2.1",
"cors": "^2.8.5",
"express": "^5.1.0",
"typescript": "^5.9.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"dev": true,
@ -471,6 +492,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@modelcontextprotocol/ext-apps": {
"resolved": "",
"link": true
@ -1344,6 +1371,18 @@
"node": ">=8"
}
},
"node_modules/chart.js": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-error": {
"version": "2.1.1",
"dev": true,
@ -2944,6 +2983,36 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/system-monitor-server": {
"resolved": "examples/system-monitor-server",
"link": true
},
"node_modules/systeminformation": {
"version": "5.27.11",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.11.tgz",
"integrity": "sha512-K3Lto/2m3K2twmKHdgx5B+0in9qhXK4YnoT9rIlgwN/4v7OV5c8IjbeAUkuky/6VzCQC7iKCAqi8rZathCdjHg==",
"license": "MIT",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"dev": true,