This document describes the implementation of the mcpGraph MCP server with graph execution capabilities. The visual editor/UX is deferred to a later phase.
The implementation creates a working MCP server that can:
- Parse YAML configuration files
- Expose MCP tools defined in the configuration
- Execute directed graphs of nodes when tools are called
- Handle data transformation and routing between nodes
Implemented:
- TypeScript project initialized
- Logger (stderr only) implemented in
src/logger.ts - All dependencies added:
@modelcontextprotocol/sdk- MCP SDKjsonata- Data transformationjson-logic-js- Conditional routingjs-yaml- YAML parsingzod- Schema validation
Note: A custom execution loop was implemented to provide full control over execution flow and enable future introspection/debugging capabilities.
File: src/types/config.ts
All TypeScript interfaces defined:
McpGraphConfig- Root configuration structureServerMetadata- MCP server metadataExecutionLimits- Optional execution limits configuration (maxNodeExecutions, maxExecutionTimeMs)ToolDefinition- Tool definition with input/output schemas (entry/exit nodes are defined in nodes withtoolfield)NodeDefinition- Base node interfaceEntryNode- Entry point node that receives tool argumentsExitNode- Exit point node that returns final resultMcpNode- MCP tool call nodeTransformNode- JSONata transformation nodeSwitchNode- JSON Logic routing node
File: src/config/schema.ts
Zod schemas implemented to validate YAML structure on load with clear error messages for invalid configurations.
File: src/config/parser.ts
YAML file parsing into structured configuration with validation against schema. Handles file I/O errors gracefully.
File: src/config/loader.ts
Loads configuration from file path and returns parsed and validated configuration object.
File: src/graph/graph.ts
Directed graph implementation from node definitions:
- Node adjacency (edges) storage
- Graph traversal utilities
- Support for cycles
Note: Not implemented as separate file - functionality integrated into Graph class and validator.ts:
- Node ID to node definition mapping
- Node reference validation (tool references in entry/exit nodes, next, switch targets)
- Orphaned node detection
- Graph connectivity validation
File: src/graph/validator.ts
Graph structure validation:
- All referenced nodes exist
- All tools have exactly one entry and one exit node
- Entry nodes are only referenced as tool entry points
- Exit nodes are only referenced as tool exit points
- Exit nodes are reachable from entry nodes
- Detailed validation error messages
Note: Not implemented as separate file - functionality integrated into src/main.ts:
- MCP server initialization using
@modelcontextprotocol/sdk - Stdio transport setup
- Server lifecycle management
Note: Not implemented as separate file - functionality integrated into src/main.ts:
- Tool definitions converted to MCP tool schemas
- Tools registered with MCP server
- Tool names mapped to execution handlers
Note: Not implemented as separate file - functionality integrated into src/main.ts:
- Tool invocation request handling
- Tool argument extraction
- Graph execution initiation at tool's entry node
- Result return to MCP client
File: src/expressions/jsonata.ts
JSONata library wrapper:
- Expression evaluation with context data
- Error handling
- Support for JSONata references (e.g.,
$.entry_count_files.directoryfor tool input,$.list_directory_nodefor node outputs)
File: src/expressions/json-logic.ts
JSON Logic library wrapper:
- Rule evaluation with context data
- Boolean results for routing decisions
- Error handling
- Note:
varoperations in JSON Logic rules are pre-processed and evaluated using JSONata, providing full JSONata expression support (including$previousNode()function) within JSON Logic rules
File: src/execution/context.ts
Expression evaluation context building:
- Tool input arguments
- Previous node outputs
- Execution state
- Data access for expressions
Note: src/expressions/context.ts exists but functionality is primarily in src/execution/context.ts.
Note: Not implemented as separate base class - each executor is standalone with consistent execution pattern:
- Pre-execution validation
- Execute node logic
- Post-execution processing
- Determine next node(s)
File: src/execution/nodes/mcp-tool-executor.ts
MCP tool node execution:
- Pre-transform: Apply JSONata to format tool arguments
- Call MCP tool using MCP client
- Handle MCP call errors
- Return output and next node
File: src/execution/nodes/transform-executor.ts
Transform node execution:
- Apply JSONata transformation expression
- Pass through data with transformation
- Return transformed output and next node
File: src/execution/nodes/switch-executor.ts
Switch node execution:
- Evaluate JSON Logic conditions in order
- Select target node based on first matching condition
- Handle default/fallback case (conditions without rules)
- Return next node based on condition result
File: src/execution/nodes/entry-executor.ts
Entry node execution:
- Receive tool arguments from MCP client
- Initialize execution context with tool arguments
- Make tool arguments available to subsequent nodes via entry node output (e.g.,
$.entry_count_files.directory) - Pass execution to next node
File: src/execution/nodes/exit-executor.ts
Exit node execution:
- Extract final result from execution context
- Signal execution completion
- Return final result to MCP tool caller
File: src/execution/context.ts
Execution state management:
- Execution History: Single source of truth - ordered array of
NodeExecutionRecordobjects- Each record includes:
executionIndex(unique sequential ID),nodeId,nodeType,startTime,endTime,duration,output,error - No
inputfield - input context is derived from history when needed
- Each record includes:
- Context Building: Context for JSONata/JSON Logic is built from history once per node execution
- Walks backwards through history, most recent execution of each node wins
- Flat context structure:
{ "node_id": output, ... }- backward compatible with$.node_idnotation - Supports time-travel debugging via
getContextForExecution(executionIndex)- builds context up to a specific point
- Data Access: All data referenced by node ID (e.g.,
$.entry_node_id.*,$.mcp_node_id.*) - Loop Support: When same node executes multiple times, context contains latest execution; history functions provide access to all executions
File: src/execution/executor.ts
Main graph execution orchestrator:
- Custom sequential execution loop
- Start at tool's entry node
- Execute current node based on type (entry, mcp, transform, switch, exit)
- Move to next node based on node's
nextfield or switch routing - Continue until exit node is reached
- Track execution history (node inputs/outputs)
- Handle errors with context
Note: The execution loop supports cycles (directed graphs with cycles). To prevent infinite loops, execution limits are enforced:
maxNodeExecutions: Maximum total node executions across the entire graph (default: 1000)maxExecutionTimeMs: Maximum wall-clock time for graph execution in milliseconds (default: 300000 = 5 minutes)- Both limits are checked before each node execution. If either limit is exceeded, execution stops immediately with a clear error message.
- Limits are configurable in the YAML configuration via the optional
executionLimitssection.
Note: Not implemented as separate file - functionality integrated into executor.ts:
- Node execution sequence coordination
- Data flow between nodes
- Branching (switch nodes)
- Parallel execution support deferred
File: src/mcp/client-manager.ts
MCP client connection management:
- Connections to external MCP servers
- Client connection caching
- Connection lifecycle handling
- Support for multiple concurrent servers
Note: Not implemented as separate file - client creation functionality is in client-manager.ts:
- MCP client creation for different server types
- Stdio transport support
- Client configuration
Note: Not implemented as separate file - tool calling functionality is in mcp-tool-executor.ts:
callToolrequest execution to external MCP servers- Tool argument handling
- Tool response processing
- Error and timeout handling
Note: Not implemented - using standard Error types:
- Basic error handling works with standard Error
- Custom error types (ConfigError, GraphError, ExecutionError, NodeError, McpError) would improve developer experience but are not required
Note: Basic error handling implemented throughout - centralized handler not implemented:
- Error logging with context (via logger)
- Error propagation through execution
- Basic user-friendly error messages
- Centralized error handler would be a nice-to-have enhancement
Note: Basic logging implemented - structured execution logger not implemented:
- Node execution events logged via basic logger
- Structured logging would be valuable for debugging but basic logger works
File: src/main.ts
Main entry point implementation:
- Command-line argument parsing (config file path)
- Configuration loading
- Graph validation
- MCP server startup
- Tool registration
- Tool execution handling
- Graceful shutdown handling
Files: tests/files.test.ts, tests/mcp-server.test.ts, tests/switch.test.ts
Integration tests implemented:
- Full execution flow with sample configs
- Different node types tested
- MCP client integration tested
- Switch node conditional routing tested
File: examples/file_utils.yaml
Sample configuration implemented demonstrating:
- Tool definition
- Entry/exit nodes
- MCP tool node
- Transform node with JSONata
Basic JSDoc comments present on key functions. Comprehensive documentation would be a nice-to-have enhancement.
Basic error messages implemented. More helpful suggestions and context would improve developer experience.
File: README.md
README updated with:
- Usage instructions
- Installation from npm
- MCP server configuration examples
- Examples and configuration format documentation
-
Custom Execution Engine: A custom execution loop was implemented to provide full control over execution flow, enable observability (execution history), and support future debugging/introspection features.
-
Expression Evaluation: All expressions (JSONata, JSON Logic) are evaluated with a consistent context where all data is referenced by node ID. Tool input is stored as the entry node's output (e.g.,
$.entry_count_files.directory), and each node's output is stored by its node ID (e.g.,$.list_directory_node). The$previousNode()function is available in all JSONata expressions (including those used in JSON Logicvaroperations) to access the output of the node that executed immediately before the current node. -
Data Flow: Data flows through nodes as a JSON object where each node's output is stored by its node ID. Each node can read from previous nodes (by their node IDs) and write its own output (stored by its node ID).
-
Error Handling: Errors at any node are caught, logged, and propagated. Basic error handling works; custom error types would be an enhancement.
-
MCP Client Management: External MCP servers are managed as separate clients. The system maintains a registry of available MCP servers and their tools.
-
Code Organization: Some planned separate files were integrated into existing files (e.g., server setup in main.ts, tool registration in main.ts). This works well and keeps the codebase simpler.
See docs/future-introspection-debugging.md for planned introspection and debugging features.
Other future enhancements:
- Visual editor/UX
- Hot-reload of configuration
- Loop node types (for, while, foreach)
- Parallel node execution
- Retry logic for failed nodes
- Execution history persistence
- Performance monitoring/metrics
- OpenTelemetry integration
- Custom error types
- Structured execution logging
- Centralized error handler