Skip to main content
Glama

analyzeGraph

Analyze network graphs within Heptabase by computing metrics like centrality, clustering, and density, and export results to a specified path. Ideal for data-driven insights from whiteboard structures.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
exportPathNo
metricsNo
whiteboardIdNo

Implementation Reference

  • Main handler function that analyzes the knowledge graph or specific whiteboard, computing metrics like node/edge counts, centrality, clustering coefficient, and density. Formats results as text and optionally exports to JSON.
    export async function analyzeGraph(
      dataService: HeptabaseDataService,
      params: z.infer<typeof analyzeGraphSchema>
    ) {
      let analysis: any = {
        timestamp: new Date().toISOString(),
        metrics: {}
      };
    
      if (params.whiteboardId) {
        // Analyze specific whiteboard
        const data = await dataService.getWhiteboard(params.whiteboardId, {
          includeCards: true,
          includeConnections: true
        });
    
        analysis.whiteboard = data.whiteboard.name;
        analysis.nodes = data.cards?.length || 0;
        analysis.edges = data.connections?.length || 0;
      } else {
        // Analyze entire knowledge graph
        const whiteboards = await dataService.getWhiteboards();
        const cards = await dataService.getCards();
        const connections = await dataService.getConnections();
    
        analysis.whiteboards = whiteboards.length;
        analysis.nodes = cards.length;
        analysis.edges = connections.length;
      }
    
      // Calculate metrics if requested
      if (params.metrics?.includes('centrality')) {
        analysis.metrics.centrality = await calculateCentrality(dataService, params.whiteboardId);
      }
    
      if (params.metrics?.includes('clustering')) {
        analysis.metrics.clustering = await calculateClustering(dataService, params.whiteboardId);
      }
    
      if (params.metrics?.includes('density')) {
        analysis.metrics.density = analysis.edges / (analysis.nodes * (analysis.nodes - 1));
      }
    
      // Format response
      let text = 'Knowledge Graph Analysis\n\n';
      
      if (params.whiteboardId) {
        text += `Whiteboard: ${analysis.whiteboard}\n`;
      } else {
        text += `Total whiteboards: ${analysis.whiteboards}\n`;
      }
      
      text += `Total nodes: ${analysis.nodes}\n`;
      text += `Total edges: ${analysis.edges}\n`;
    
      if (analysis.metrics.centrality) {
        text += '\nCentrality:\n';
        text += 'Most central nodes:\n';
        const sorted = Object.entries(analysis.metrics.centrality)
          .sort(([, a]: any, [, b]: any) => b - a)
          .slice(0, 5);
        
        for (const [nodeId, score] of sorted) {
          text += `- ${nodeId}: ${score}\n`;
        }
      }
    
      if (analysis.metrics.clustering) {
        text += `\nClustering coefficient: ${analysis.metrics.clustering}\n`;
      }
    
      if (analysis.metrics.density) {
        text += `\nGraph density: ${analysis.metrics.density.toFixed(3)}\n`;
      }
    
      // Export if requested
      if (params.exportPath) {
        await fs.mkdir(path.dirname(params.exportPath), { recursive: true });
        await fs.writeFile(params.exportPath, JSON.stringify(analysis, null, 2), 'utf8');
        text += `\nAnalysis saved to ${params.exportPath}`;
      }
    
      return {
        content: [{
          type: 'text',
          text
        }]
      };
    }
  • Zod input schema defining optional parameters: whiteboardId, metrics array (centrality/clustering/density), and exportPath.
    export const analyzeGraphSchema = z.object({
      whiteboardId: z.string().optional(),
      metrics: z.array(z.enum(['centrality', 'clustering', 'density'])).optional(),
      exportPath: z.string().optional()
    });
  • src/server.ts:702-712 (registration)
    Registers the analyzeGraph tool both internally (this.tools.analyzeGraph) and with the MCP server (this.server.tool), wrapping the handler with data service initialization.
    this.tools.analyzeGraph = {
      inputSchema: analyzeGraphSchema,
      handler: async (params) => {
        await this.ensureDataServiceInitialized();
        return analyzeGraph(this.dataService, params);
      }
    };
    
    this.server.tool('analyzeGraph', analyzeGraphSchema.shape, async (params: z.infer<typeof analyzeGraphSchema>) => {
      return this.tools.analyzeGraph.handler(params);
    });
  • Helper function to calculate degree centrality for nodes in the graph.
    async function calculateCentrality(dataService: HeptabaseDataService, whiteboardId?: string): Promise<Record<string, number>> {
      const connections = whiteboardId 
        ? (await dataService.getWhiteboard(whiteboardId, { includeConnections: true })).connections || []
        : await dataService.getConnections();
    
      const centrality: Record<string, number> = {};
    
      // Calculate degree centrality
      for (const connection of connections) {
        centrality[connection.beginId] = (centrality[connection.beginId] || 0) + 1;
        centrality[connection.endId] = (centrality[connection.endId] || 0) + 1;
      }
    
      return centrality;
    }
  • Helper function to calculate the average clustering coefficient for the graph.
    async function calculateClustering(dataService: HeptabaseDataService, whiteboardId?: string): Promise<number> {
      const connections = whiteboardId 
        ? (await dataService.getWhiteboard(whiteboardId, { includeConnections: true })).connections || []
        : await dataService.getConnections();
    
      // Simple clustering coefficient calculation
      // In a real implementation, this would be more sophisticated
      if (connections.length === 0) return 0;
    
      // Build adjacency list
      const adjacency: Record<string, Set<string>> = {};
      
      for (const connection of connections) {
        if (!adjacency[connection.beginId]) adjacency[connection.beginId] = new Set();
        if (!adjacency[connection.endId]) adjacency[connection.endId] = new Set();
        
        adjacency[connection.beginId].add(connection.endId);
        adjacency[connection.endId].add(connection.beginId);
      }
    
      // Calculate local clustering coefficient for each node
      let totalClustering = 0;
      let nodeCount = 0;
    
      for (const [node, neighbors] of Object.entries(adjacency)) {
        if (neighbors.size < 2) continue;
    
        let triangles = 0;
        const neighborArray = Array.from(neighbors);
        
        for (let i = 0; i < neighborArray.length; i++) {
          for (let j = i + 1; j < neighborArray.length; j++) {
            if (adjacency[neighborArray[i]]?.has(neighborArray[j])) {
              triangles++;
            }
          }
        }
    
        const possibleTriangles = (neighbors.size * (neighbors.size - 1)) / 2;
        totalClustering += triangles / possibleTriangles;
        nodeCount++;
      }
    
      return nodeCount > 0 ? totalClustering / nodeCount : 0;
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LarryStanley/heptabase-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server