99 getAllEndpoints ,
1010 getAllKeywords ,
1111 validateChainData ,
12+ traverseRelations ,
1213} from './dataService.js' ;
1314import { getMonitoringResults , getMonitoringStatus } from './rpcMonitor.js' ;
1415
@@ -131,6 +132,32 @@ export function getToolDefinitions() {
131132 properties : { } ,
132133 } ,
133134 } ,
135+ {
136+ name : 'get_stats' ,
137+ description : 'Get aggregate statistics: total chains, mainnets, testnets, L2s, beacons, and RPC health percentage' ,
138+ inputSchema : {
139+ type : 'object' ,
140+ properties : { } ,
141+ } ,
142+ } ,
143+ {
144+ name : 'traverse_relations' ,
145+ description : 'BFS graph traversal of chain relations from a starting chain. Returns all reachable chains (nodes) and their relationship edges up to a given depth.' ,
146+ inputSchema : {
147+ type : 'object' ,
148+ properties : {
149+ chainId : {
150+ type : 'number' ,
151+ description : 'The chain ID to start traversal from (e.g., 1 for Ethereum)' ,
152+ } ,
153+ depth : {
154+ type : 'number' ,
155+ description : 'Maximum traversal depth (1-5, default: 2)' ,
156+ } ,
157+ } ,
158+ required : [ 'chainId' ] ,
159+ } ,
160+ } ,
134161 {
135162 name : 'get_rpc_monitor_by_id' ,
136163 description : 'Get RPC endpoint monitoring results for a specific chain by its chain ID' ,
@@ -287,6 +314,56 @@ function handleValidateChains() {
287314 return textResponse ( validationResults ) ;
288315}
289316
317+ function handleGetStats ( ) {
318+ const chains = getAllChains ( ) ;
319+ const monitorResults = getMonitoringResults ( ) ;
320+
321+ const totalChains = chains . length ;
322+ const totalMainnets = chains . filter ( c => ! c . tags ?. includes ( 'Testnet' ) && ! c . tags ?. includes ( 'L2' ) && ! c . tags ?. includes ( 'Beacon' ) ) . length ;
323+ const totalTestnets = chains . filter ( c => c . tags ?. includes ( 'Testnet' ) ) . length ;
324+ const totalL2s = chains . filter ( c => c . tags ?. includes ( 'L2' ) ) . length ;
325+ const totalBeacons = chains . filter ( c => c . tags ?. includes ( 'Beacon' ) ) . length ;
326+
327+ const rpcTested = monitorResults . testedEndpoints ;
328+ const rpcWorking = monitorResults . workingEndpoints ;
329+ const rpcFailed = monitorResults . failedEndpoints || 0 ;
330+ const rpcHealthPercent = rpcTested > 0 ? Math . round ( ( rpcWorking / rpcTested ) * 10000 ) / 100 : null ;
331+
332+ return textResponse ( {
333+ totalChains,
334+ totalMainnets,
335+ totalTestnets,
336+ totalL2s,
337+ totalBeacons,
338+ rpc : {
339+ totalEndpoints : monitorResults . totalEndpoints ,
340+ tested : rpcTested ,
341+ working : rpcWorking ,
342+ failed : rpcFailed ,
343+ healthPercent : rpcHealthPercent ,
344+ } ,
345+ lastUpdated : monitorResults . lastUpdated ,
346+ } ) ;
347+ }
348+
349+ function handleTraverseRelations ( args ) {
350+ const { chainId, depth } = args ;
351+ if ( ! isValidChainId ( chainId ) ) {
352+ return errorResponse ( 'Invalid chain ID' ) ;
353+ }
354+
355+ const maxDepth = depth !== undefined ? depth : 2 ;
356+ if ( typeof maxDepth !== 'number' || maxDepth < 1 || maxDepth > 5 ) {
357+ return errorResponse ( 'Invalid depth. Must be between 1 and 5' ) ;
358+ }
359+
360+ const result = traverseRelations ( chainId , maxDepth ) ;
361+ if ( ! result ) {
362+ return errorResponse ( 'Chain not found' ) ;
363+ }
364+ return textResponse ( result ) ;
365+ }
366+
290367function getStatusLabel ( status , results ) {
291368 if ( status . isMonitoring ) return 'Running' ;
292369 if ( results . testedEndpoints > 0 ) return 'Completed' ;
@@ -304,6 +381,7 @@ function formatRpcMonitorStatus(status, results) {
304381 `- Total endpoints discovered: ${ results . totalEndpoints } ` ,
305382 `- Endpoints tested: ${ results . testedEndpoints } ` ,
306383 `- Working endpoints: ${ results . workingEndpoints } ` ,
384+ `- Failed endpoints: ${ results . failedEndpoints ?? 0 } ` ,
307385 '- Use `get_rpc_monitor_by_id` for per-chain endpoint details.' ,
308386 ] ;
309387
@@ -355,9 +433,10 @@ function handleGetRpcMonitorById(args) {
355433 ] ;
356434 for ( const ep of chainResults ) {
357435 const block = ep . blockNumber == null ? '' : ` — block #${ ep . blockNumber } ` ;
436+ const latency = ep . latencyMs != null ? ` [${ ep . latencyMs } ms]` : '' ;
358437 const client = ep . clientVersion && ep . clientVersion !== 'unavailable' ? ` (${ ep . clientVersion } )` : '' ;
359438 lines . push (
360- `- **${ ep . status } ** ${ ep . url } ${ block } ${ client } ` ,
439+ `- **${ ep . status } ** ${ ep . url } ${ block } ${ latency } ${ client } ` ,
361440 ...( ep . error ? [ ` - Error: ${ ep . error } ` ] : [ ] )
362441 ) ;
363442 }
@@ -376,6 +455,8 @@ const toolHandlers = {
376455 get_sources : handleGetSources ,
377456 get_keywords : handleGetKeywords ,
378457 validate_chains : handleValidateChains ,
458+ get_stats : handleGetStats ,
459+ traverse_relations : handleTraverseRelations ,
379460 get_rpc_monitor : handleGetRpcMonitor ,
380461 get_rpc_monitor_by_id : handleGetRpcMonitorById ,
381462} ;
0 commit comments