Skip to main content
Glama
validator.ts4.57 kB
/** * Graph structure validator */ import type { McpGraphConfig } from "../types/config.js"; import { Graph } from "./graph.js"; import { logger } from "../logger.js"; export interface ValidationError { message: string; nodeId?: string; toolName?: string; } export function validateGraph(config: McpGraphConfig): ValidationError[] { const errors: ValidationError[] = []; const graph = new Graph(config.nodes); // Validate all tools have exactly one entry and one exit node for (const tool of config.tools) { const entryNodes = config.nodes.filter( (n) => n.type === "entry" && (n as { tool: string }).tool === tool.name ); const exitNodes = config.nodes.filter( (n) => n.type === "exit" && (n as { tool: string }).tool === tool.name ); if (entryNodes.length === 0) { errors.push({ message: `Tool "${tool.name}" has no entry node`, toolName: tool.name, }); } else if (entryNodes.length > 1) { errors.push({ message: `Tool "${tool.name}" has multiple entry nodes: ${entryNodes.map((n) => n.id).join(", ")}`, toolName: tool.name, }); } if (exitNodes.length === 0) { errors.push({ message: `Tool "${tool.name}" has no exit node`, toolName: tool.name, }); } else if (exitNodes.length > 1) { errors.push({ message: `Tool "${tool.name}" has multiple exit nodes: ${exitNodes.map((n) => n.id).join(", ")}`, toolName: tool.name, }); } } // Validate entry and exit nodes reference valid tools for (const node of config.nodes) { if (node.type === "entry") { const toolName = (node as { tool: string }).tool; const tool = config.tools.find((t) => t.name === toolName); if (!tool) { errors.push({ message: `Entry node "${node.id}" references non-existent tool "${toolName}"`, nodeId: node.id, }); } } if (node.type === "exit") { const toolName = (node as { tool: string }).tool; const tool = config.tools.find((t) => t.name === toolName); if (!tool) { errors.push({ message: `Exit node "${node.id}" references non-existent tool "${toolName}"`, nodeId: node.id, }); } } } // Validate all node references exist for (const node of config.nodes) { if ("next" in node && node.next) { if (!graph.hasNode(node.next)) { errors.push({ message: `Node "${node.id}" references non-existent next node "${node.next}"`, nodeId: node.id, }); } } if (node.type === "switch") { for (const condition of node.conditions) { if (!graph.hasNode(condition.target)) { errors.push({ message: `Switch node "${node.id}" references non-existent target "${condition.target}"`, nodeId: node.id, }); } } } } // Validate mcp nodes reference valid servers for (const node of config.nodes) { if (node.type === "mcp") { const serverName = node.server; if (!config.servers || !config.servers[serverName]) { errors.push({ message: `MCP node "${node.id}" references non-existent server "${serverName}"`, nodeId: node.id, }); } } } // Validate exit nodes are reachable (basic check - can be enhanced) for (const tool of config.tools) { const entryNodes = config.nodes.filter( (n) => n.type === "entry" && (n as { tool: string }).tool === tool.name ); const exitNodes = config.nodes.filter( (n) => n.type === "exit" && (n as { tool: string }).tool === tool.name ); if (entryNodes.length === 1 && exitNodes.length === 1) { const entryNode = entryNodes[0]; const exitNode = exitNodes[0]; if (!isReachable(graph, entryNode.id, exitNode.id)) { errors.push({ message: `Tool "${tool.name}" exit node "${exitNode.id}" is not reachable from entry node "${entryNode.id}"`, toolName: tool.name, }); } } } return errors; } function isReachable(graph: Graph, startId: string, targetId: string): boolean { const visited = new Set<string>(); const queue: string[] = [startId]; while (queue.length > 0) { const current = queue.shift()!; if (current === targetId) { return true; } if (visited.has(current)) { continue; } visited.add(current); const nextNodes = graph.getNextNodes(current); queue.push(...nextNodes); } return false; }

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/TeamSparkAI/mcpGraph'

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