@@ -13,50 +13,78 @@ import os from "node:os";
1313import path from "node:path" ;
1414import si from "systeminformation" ;
1515import { z } from "zod" ;
16- // Schemas - types are derived from these using z.infer
17- const CpuCoreSchema = z . object ( {
18- idle : z . number ( ) ,
19- total : z . number ( ) ,
20- } ) ;
2116
22- const CpuStatsSchema = z . object ( {
23- cores : z . array ( CpuCoreSchema ) ,
24- model : z . string ( ) ,
25- count : z . number ( ) ,
26- } ) ;
17+ // Works both from source (server.ts) and compiled (dist/server.js)
18+ const DIST_DIR = import . meta. filename . endsWith ( ".ts" )
19+ ? path . join ( import . meta. dirname , "dist" )
20+ : import . meta. dirname ;
2721
28- const MemoryStatsSchema = z . object ( {
29- usedBytes : z . number ( ) ,
30- totalBytes : z . number ( ) ,
31- usedPercent : z . number ( ) ,
32- freeBytes : z . number ( ) ,
33- usedFormatted : z . string ( ) ,
34- totalFormatted : z . string ( ) ,
35- } ) ;
22+ // =============================================================================
23+ // Types and schemas
24+ // =============================================================================
3625
3726const SystemInfoSchema = z . object ( {
3827 hostname : z . string ( ) ,
3928 platform : z . string ( ) ,
4029 arch : z . string ( ) ,
41- uptime : z . number ( ) ,
42- uptimeFormatted : z . string ( ) ,
30+ cpu : z . object ( {
31+ model : z . string ( ) ,
32+ count : z . number ( ) ,
33+ } ) ,
34+ memory : z . object ( {
35+ totalBytes : z . number ( ) ,
36+ } ) ,
4337} ) ;
4438
45- const SystemStatsSchema = z . object ( {
46- cpu : CpuStatsSchema ,
47- memory : MemoryStatsSchema ,
48- system : SystemInfoSchema ,
49- timestamp : z . string ( ) ,
39+ type SystemInfo = z . infer < typeof SystemInfoSchema > ;
40+
41+ const CpuCoreSchema = z . object ( {
42+ idle : z . number ( ) ,
43+ total : z . number ( ) ,
5044} ) ;
5145
52- // Types derived from schemas
5346type CpuCore = z . infer < typeof CpuCoreSchema > ;
54- type MemoryStats = z . infer < typeof MemoryStatsSchema > ;
55- type SystemStats = z . infer < typeof SystemStatsSchema > ;
56- // Works both from source (server.ts) and compiled (dist/server.js)
57- const DIST_DIR = import . meta. filename . endsWith ( ".ts" )
58- ? path . join ( import . meta. dirname , "dist" )
59- : import . meta. dirname ;
47+
48+ const PollStatsSchema = z . object ( {
49+ cpu : z . object ( {
50+ cores : z . array ( CpuCoreSchema ) ,
51+ } ) ,
52+ memory : z . object ( {
53+ usedBytes : z . number ( ) ,
54+ usedPercent : z . number ( ) ,
55+ freeBytes : z . number ( ) ,
56+ } ) ,
57+ uptime : z . object ( {
58+ seconds : z . number ( ) ,
59+ } ) ,
60+ timestamp : z . string ( ) ,
61+ } ) ;
62+
63+ type PollStats = z . infer < typeof PollStatsSchema > ;
64+
65+ // =============================================================================
66+ // Static system info (called once by Model-facing tool)
67+ // =============================================================================
68+
69+ function getSystemInfo ( ) : SystemInfo {
70+ const cpuInfo = os . cpus ( ) [ 0 ] ;
71+ return {
72+ hostname : os . hostname ( ) ,
73+ platform : `${ os . platform ( ) } ${ os . arch ( ) } ` ,
74+ arch : os . arch ( ) ,
75+ cpu : {
76+ model : cpuInfo ?. model ?? "Unknown" ,
77+ count : os . cpus ( ) . length ,
78+ } ,
79+ memory : {
80+ totalBytes : os . totalmem ( ) ,
81+ } ,
82+ } ;
83+ }
84+
85+ // =============================================================================
86+ // Dynamic polling stats (called repeatedly by app-only tool)
87+ // =============================================================================
6088
6189// Returns raw CPU timing data per core (client calculates usage from deltas)
6290function getCpuSnapshots ( ) : CpuCore [ ] {
@@ -67,111 +95,73 @@ function getCpuSnapshots(): CpuCore[] {
6795 return { idle, total } ;
6896 } ) ;
6997}
70-
71- function formatUptime ( seconds : number ) : string {
72- const days = Math . floor ( seconds / 86400 ) ;
73- const hours = Math . floor ( ( seconds % 86400 ) / 3600 ) ;
74- const minutes = Math . floor ( ( seconds % 3600 ) / 60 ) ;
75-
76- const parts : string [ ] = [ ] ;
77- if ( days > 0 ) parts . push ( `${ days } d` ) ;
78- if ( hours > 0 ) parts . push ( `${ hours } h` ) ;
79- if ( minutes > 0 ) parts . push ( `${ minutes } m` ) ;
80-
81- return parts . length > 0 ? parts . join ( " " ) : "< 1m" ;
82- }
83-
84- function formatBytes ( bytes : number ) : string {
85- const units = [ "B" , "KB" , "MB" , "GB" , "TB" ] ;
86- let value = bytes ;
87- let unitIndex = 0 ;
88-
89- while ( value >= 1024 && unitIndex < units . length - 1 ) {
90- value /= 1024 ;
91- unitIndex ++ ;
92- }
93-
94- return `${ value . toFixed ( 1 ) } ${ units [ unitIndex ] } ` ;
95- }
96-
97- async function getMemoryStats ( ) : Promise < MemoryStats > {
98+ async function getPollStats ( ) : Promise < PollStats > {
9899 const mem = await si . mem ( ) ;
99- return {
100- usedBytes : mem . active ,
101- totalBytes : mem . total ,
102- usedPercent : Math . round ( ( mem . active / mem . total ) * 100 ) ,
103- freeBytes : mem . available ,
104- usedFormatted : formatBytes ( mem . active ) ,
105- totalFormatted : formatBytes ( mem . total ) ,
106- } ;
107- }
108-
109- async function getStats ( ) : Promise < SystemStats > {
110- const cpuSnapshots = getCpuSnapshots ( ) ;
111- const cpuInfo = os . cpus ( ) [ 0 ] ;
112- const memory = await getMemoryStats ( ) ;
113100 const uptimeSeconds = os . uptime ( ) ;
114101
115102 return {
116103 cpu : {
117- cores : cpuSnapshots ,
118- model : cpuInfo ?. model ?? "Unknown" ,
119- count : os . cpus ( ) . length ,
104+ cores : getCpuSnapshots ( ) ,
105+ } ,
106+ memory : {
107+ usedBytes : mem . active ,
108+ usedPercent : Math . round ( ( mem . active / mem . total ) * 100 ) ,
109+ freeBytes : mem . available ,
120110 } ,
121- memory,
122- system : {
123- hostname : os . hostname ( ) ,
124- platform : `${ os . platform ( ) } ${ os . arch ( ) } ` ,
125- arch : os . arch ( ) ,
126- uptime : uptimeSeconds ,
127- uptimeFormatted : formatUptime ( uptimeSeconds ) ,
111+ uptime : {
112+ seconds : uptimeSeconds ,
128113 } ,
129114 timestamp : new Date ( ) . toISOString ( ) ,
130115 } ;
131116}
132117
118+ // =============================================================================
119+ // MCP server
120+ // =============================================================================
121+
133122export function createServer ( ) : McpServer {
134123 const server = new McpServer ( {
135124 name : "System Monitor Server" ,
136125 version : "1.0.0" ,
137126 } ) ;
138127
139- // Register the get-system-stats tool and its associated UI resource
140128 const resourceUri = "ui://system-monitor/mcp-app.html" ;
141129
130+ // Model-facing tool: returns static system configuration
142131 registerAppTool (
143132 server ,
144- "get-system-stats " ,
133+ "get-system-info " ,
145134 {
146- title : "Get System Stats " ,
135+ title : "Get System Info " ,
147136 description :
148- "Returns current system statistics including per-core CPU usage, memory, and system info ." ,
137+ "Returns system information, including hostname, platform, CPU info, and memory ." ,
149138 inputSchema : { } ,
150- outputSchema : SystemStatsSchema . shape ,
139+ outputSchema : SystemInfoSchema . shape ,
151140 _meta : { ui : { resourceUri } } ,
152141 } ,
153- async ( ) : Promise < CallToolResult > => {
154- const stats = await getStats ( ) ;
142+ ( ) : CallToolResult => {
143+ const info = getSystemInfo ( ) ;
155144 return {
156- content : [ { type : "text" , text : JSON . stringify ( stats ) } ] ,
157- structuredContent : stats ,
145+ content : [ { type : "text" , text : JSON . stringify ( info ) } ] ,
146+ structuredContent : info ,
158147 } ;
159148 } ,
160149 ) ;
161150
162- // App-only tool for polling - used by the UI for periodic refresh
151+ // App-only tool: returns dynamic metrics for polling
163152 registerAppTool (
164153 server ,
165- "refresh -stats" ,
154+ "poll-system -stats" ,
166155 {
167- title : "Refresh Stats" ,
168- description : "Refresh system statistics (app-only, for polling)" ,
156+ title : "Poll System Stats" ,
157+ description :
158+ "Returns dynamic system metrics for polling: per-core CPU timing, memory usage, and uptime. App-only." ,
169159 inputSchema : { } ,
170- outputSchema : SystemStatsSchema . shape ,
160+ outputSchema : PollStatsSchema . shape ,
171161 _meta : { ui : { visibility : [ "app" ] } } ,
172162 } ,
173163 async ( ) : Promise < CallToolResult > => {
174- const stats = await getStats ( ) ;
164+ const stats = await getPollStats ( ) ;
175165 return {
176166 content : [ { type : "text" , text : JSON . stringify ( stats ) } ] ,
177167 structuredContent : stats ,
0 commit comments