# Mcpgraph

> Build no-code MCP servers with tools that compose and orchestrate other MCP tools, with data transformation and conditional logic.

- Skill: `majiayu000/mcpgraph-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/mcpgraph-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/mcpgraph-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/mcpgraph-2

---


# Building mcpGraphs

This skill teaches you how to build mcpGraph configurations - declarative YAML files that define MCP tools as directed graphs of nodes.

## What is an mcpGraph?

An **mcpGraph** is a declarative YAML configuration file that defines MCP (Model Context Protocol) tools as directed graphs. Each tool executes a sequence of nodes that can:
- Call other MCP tools (on internal or external MCP servers)
- Transform data using JSONata expressions
- Make routing decisions using JSON Logic

**When to use mcpGraph:**
- You need to orchestrate multiple MCP tool calls in sequence
- You want to transform data between tool calls
- You need conditional routing based on data
- You want declarative, observable configurations (no embedded code)
- You need to compose complex workflows from simpler MCP tools

**Why use mcpGraph:**
- **Declarative**: All logic expressed in YAML using standard expression languages
- **Observable**: Every transformation and decision is traceable
- **No Embedded Code**: Uses JSONata and JSON Logic instead of full programming languages
- **Standard-Based**: Built on MCP, JSONata, and JSON Logic standards
- **Composable**: Build complex tools from simpler MCP tools

## How to Use mcpGraph

This skill assumes mcpGraph is installed in your local environment and available as the `mcpgraph` command.

To use mcpGraph as an MCP server (e.g., in Claude Desktop), add it to your MCP client configuration:

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "mcpgraph": {
      "command": "mcpgraph",
      "args": ["-g", "/path/to/your/config.yaml"]
    }
  }
}
```

The `-g` (or `--graph`) flag specifies the path to your YAML configuration file.

## File Structure

An mcpGraph configuration file is a YAML file with the following structure:

```yaml
version: "1.0"

# MCP Server Metadata
server:
  name: "serverName"          # Required: unique identifier
  version: "1.0.0"            # Required: server version
  title: "Display Name"       # Optional: display name (defaults to name)
  instructions: "..."         # Optional: server usage instructions

# MCP Servers used by the graph
mcpServers:
  serverName:
    command: "command"         # For stdio servers
    args: ["arg1", "arg2"]    # Command arguments
  # OR for HTTP servers:
  # httpServer:
  #   type: "streamableHttp"
  #   url: "https://api.example.com/mcp"
  #   headers:
  #     Authorization: "Bearer token"

# Tool Definitions for tools implemented by this graph and available as part of this MCP server
tools:
  - name: "toolName"
    description: "Tool description"
    inputSchema:
      type: "object"
      properties:
        paramName:
          type: "string"
          description: "Parameter description"
      required:
        - paramName
    outputSchema:
      type: "object"
      properties:
        result:
          type: "string"
          description: "Result description"
    nodes:
      # Node definitions here
```

### MCP Server Metadata

The `server` section defines metadata for the MCP server that will expose these tools:
- **`name`** (required): Unique identifier for the server
- **`version`** (required): Server version (semantic versioning)
- **`title`** (optional): Display name (defaults to `name` if not provided)
- **`instructions`** (optional): Instructions for using this server

### MCP Servers

The `mcpServers` section defines the MCP servers that the graph will call. Each server can be:
- **stdio server**: Uses `command` and `args` to run a command-line program
- **streamableHttp server**: Uses `type: "streamableHttp"`, `url`, and optional `headers`

### Tools

The `tools` array defines the MCP tools exposed by this server. Each tool has:
- **`name`**: Tool identifier
- **`description`**: Tool description
- **`inputSchema`**: JSON Schema defining input parameters (standard MCP tool schema)
- **`outputSchema`**: JSON Schema defining output structure
- **`nodes`**: Array of node definitions that form the execution graph

## Graph Structure and Flow

A graph is a directed sequence of nodes that execute in order. Execution flow:
1. Starts at the **entry** node (receives tool arguments)
2. Executes nodes sequentially based on `next` fields
3. **switch** nodes can conditionally route to different nodes
4. Continues until the **exit** node is reached
5. Exit node returns the final result

### Node Connections

Nodes are connected using the `next` field, which specifies the ID of the next node to execute:
```yaml
- id: "node1"
  type: "transform"
  next: "node2"  # Executes node2 after node1
```

**Switch nodes** use `conditions` with `next` fields (each condition specifies its own `next` node), plus a top-level `next` field as the default:
```yaml
- id: "switch_node"
  type: "switch"
  conditions:
    - rule: { ">": [{ var: "entry.value" }, 10] }
      next: "high_path"
  next: "default_path"  # Default case if no conditions match
```

### Execution Context

During execution, each node's output is stored in the execution context. You can access node outputs using JSONata expressions:
- `$.node_id` - Accesses the latest output of a node with ID `node_id`
- `$.entry.paramName` - Accesses a parameter from the entry node

The context is a flat structure: `{ "node_id": output, ... }`

## Node Types

### Entry Node

The entry point for a tool's graph execution. Receives tool arguments.

**Properties:**
- `id`: Node identifier (typically "entry")
- `type`: `"entry"`
- `next`: ID of the next node to execute

**Output:** The tool input arguments (passed through as-is)

**Example:**
```yaml
- id: "entry"
  type: "entry"
  next: "process_node"
```

### MCP Node

Calls an MCP tool on an internal or external MCP server.

**Properties:**
- `id`: Node identifier
- `type`: `"mcp"`
- `server`: Name of the MCP server (from `mcpServers` section)
- `tool`: Name of the tool to call on that server
- `args`: Arguments to pass to the tool (can use JSONata expressions)
- `next`: ID of the next node to execute

**Output:** The MCP tool's response (parsed from the tool's content)

**Example:**
```yaml
- id: "list_directory_node"
  type: "mcp"
  server: "filesystem"
  tool: "list_directory"
  args:
    path:
      expr: "$.entry.directory"  # JSONata expression accessing entry node output
  next: "count_files_node"
```

### Transform Node

Applies JSONata expressions to transform data between nodes.

**Properties:**
- `id`: Node identifier
- `type`: `"transform"`
- `transform.expr`: JSONata expression (string)
- `next`: ID of the next node to execute

**Output:** The result of evaluating the JSONata expression

**Expression Format:**
- Use **single-quoted strings** for simple expressions: `expr: '{ "result": "value" }'`
- Use **block scalars** (`|`) for complex multi-line expressions to improve readability

**Example (simple):**
```yaml
- id: "count_files_node"
  type: "transform"
  transform:
    expr: '{ "count": $count($split($.list_directory_node.content, "\n")) }'
  next: "exit"
```

**Example (complex):**
```yaml
- id: "increment_node"
  type: "transform"
  transform:
    expr: |
      $executionCount("increment_node") = 0
        ? { "counter": 1, "sum": 1, "target": $.entry_sum.n }
        : { "counter": $nodeExecution("increment_node", -1).counter + 1, ... }
  next: "check_condition"
```

### Switch Node

Uses JSON Logic to conditionally route to different nodes based on data.

**Properties:**
- `id`: Node identifier
- `type`: `"switch"`
- `conditions`: Array of condition rules
  - `rule`: JSON Logic expression (required - all conditions must have rules)
  - `next`: ID of the node to route to if this condition matches
- `next`: ID of the default next node (used if no conditions match)

**Output:** The node ID of the next node that was routed to (string)

**Important:** `var` operations in JSON Logic rules are evaluated using JSONata, allowing full JSONata expression support (including history functions).

**Example:**
```yaml
- id: "switch_node"
  type: "switch"
  conditions:
    - rule:
        ">": [{ var: "entry.value" }, 10]
      next: "high_path"
    - rule:
        ">": [{ var: "entry.value" }, 0]
      next: "low_path"
  next: "zero_path"  # Default case if no conditions match
```

**Advanced Example with JSONata:**
```yaml
- id: "check_condition"
  type: "switch"
  conditions:
    - rule:
        "<": [
          { var: "$.increment_node.counter" },
          { var: "$.increment_node.target" }
        ]
      next: "increment_node"  # Loop back
  next: "exit_sum"  # Default: exit loop when counter >= target
```

### Exit Node

Exit point that returns the final result to the MCP tool caller.

**Properties:**
- `id`: Node identifier (typically "exit")
- `type`: `"exit"`
- **Note:** No `next` field - execution ends here

**Output:** The output from the previous node in the execution history

**Example:**
```yaml
- id: "exit"
  type: "exit"
```

## JSONata Expressions

JSONata is used in three places in mcpGraph for data transformation and access:
1. **Transform node expressions** - Transform data between nodes
2. **JSON Logic `var` operations** - Access context data in switch node conditions
3. **MCP tool node arguments** - Objects with `{ "expr": "..." }` are evaluated as JSONata expressions (recursively)

### Basic Syntax

- **Object construction**: `{ "key": value }`
- **Property access**: `$.node_id.property`
- **Functions**: `$count(array)`, `$split(string, delimiter)`, etc.
- **Conditional**: `condition ? trueValue : falseValue`

### Where JSONata is Used

**1. Transform Nodes:**
Transform nodes use JSONata expressions in the `transform.expr` field to transform data:
```yaml
transform:
  expr: '{ "count": $count($split($.list_directory_node.content, "\n")) }'
```

**2. MCP Tool Node Arguments:**
To use a JSONata expression in an MCP node argument, wrap it in an object with an `expr` property:
```yaml
args:
  path:
    expr: "$.entry.directory"  # JSONata expression accessing entry node output
  static: "literal/path"  # Literal value (not evaluated)
  count:
    expr: "$count($.previous_node.items)"  # JSONata expression with function
```

**Complex nested args:**
```yaml
args:
  path:
    expr: "'downloads/' & $.entry.filename"  # String concatenation
  options:
    recursive: true  # Literal value
    filter:
      expr: "$.entry.filterPattern"  # Nested expression
```

**Important:**
- Objects with only an `expr` property are evaluated as JSONata
- Objects with `expr` and other properties are invalid (error)
- All other values (strings, numbers, arrays, objects without `expr`) are passed as literals
- Arrays and objects are recursively evaluated

**3. JSON Logic `var` Operations:**
In switch node conditions, `var` operations are evaluated using JSONata:
```yaml
rule:
  ">": [{ var: "$.increment_node.counter" }, 10]
```

### Accessing Node Outputs

- `$.node_id` - Latest output of a node
- `$.node_id.property` - Property from node output
- `$.entry.paramName` - Parameter from entry node

### History Functions

For loops and accessing execution history:
- `$previousNode()` - Get the previous node's output
- `$previousNode(index)` - Get the node that executed N steps before current
- `$executionCount(nodeName)` - Count how many times a node executed
- `$nodeExecution(nodeName, index)` - Get a specific execution (0 = first, -1 = last)
- `$nodeExecutions(nodeName)` - Get all executions as an array

**Example:**
```yaml
transform:
  expr: |
    $executionCount("increment_node") = 0
      ? { "counter": 1, "sum": 1, "target": $.entry_sum.n }
      : {
          "counter": $nodeExecution("increment_node", -1).counter + 1,
          "sum": $nodeExecution("increment_node", -1).sum + $nodeExecution("increment_node", -1).counter + 1,
          "target": $.entry_sum.n
        }
```

### Expression Format in YAML

- **Simple expressions**: Use single-quoted strings
  ```yaml
  expr: '{ "count": $count($split($.list_directory_node.content, "\n")) }'
  ```

- **Complex expressions**: Use block scalars (`|`) for readability
  ```yaml
  expr: |
    $executionCount("increment_node") = 0
      ? { "counter": 1 }
      : { "counter": $nodeExecution("increment_node", -1).counter + 1 }
  ```

## JSON Logic

JSON Logic is used in switch nodes for conditional routing. It allows complex rules as pure JSON objects.

### Basic Syntax

- **Comparison**: `{ ">": [a, b] }`, `{ "<": [a, b] }`, `{ "==": [a, b] }`, etc.
- **Logical**: `{ "and": [rule1, rule2] }`, `{ "or": [rule1, rule2] }`, `{ "!": rule }`
- **Variable access**: `{ "var": "path" }` or `{ "var": "$.node_id.property" }`

**Important:** `var` operations are evaluated using JSONata, so you can use full JSONata expressions:
- `{ "var": "entry.value" }` - Simple property access
- `{ "var": "$.increment_node.counter" }` - JSONata expression
- `{ "var": "$previousNode().count" }` - JSONata with history function

### Examples

**Simple comparison:**
```yaml
rule:
  ">": [{ var: "entry.value" }, 10]
```

**Complex condition:**
```yaml
rule:
  and:
    - ">": [{ var: "entry.price" }, 100]
    - "==": [{ var: "entry.status" }, "active"]
```

**With JSONata:**
```yaml
rule:
  "<": [
    { var: "$.increment_node.counter" },
    { var: "$.increment_node.target" }
  ]
```

## Complete Example

Here's a complete example that counts files in a directory:

```yaml
version: "1.0"

server:
  name: "fileUtils"
  version: "1.0.0"
  title: "File utilities"
  instructions: "This server provides file utility tools for counting files and calculating total file sizes in directories."

mcpServers:
  filesystem:
    command: "npx"
    args:
      - "-y"
      - "@modelcontextprotocol/server-filesystem"
      - "./tests/counting"

tools:
  - name: "count_files"
    description: "Counts the number of files in a directory"
    inputSchema:
      type: "object"
      properties:
        directory:
          type: "string"
          description: "The directory path to count files in"
      required:
        - directory
    outputSchema:
      type: "object"
      properties:
        count:
          type: "number"
          description: "The number of files in the directory"
    nodes:
      # Entry node: Receives tool arguments
      - id: "entry"
        type: "entry"
        next: "list_directory_node"
      
      # List directory contents
      - id: "list_directory_node"
        type: "mcp"
        server: "filesystem"
        tool: "list_directory"
        args:
          path:
            expr: "$.entry.directory"
        next: "count_files_node"
      
      # Transform and count files
      - id: "count_files_node"
        type: "transform"
        transform:
          expr: '{ "count": $count($split($.list_directory_node.content, "\n")) }'
        next: "exit"
      
      # Exit node: Returns the count
      - id: "exit"
        type: "exit"
```

This graph:
1. Receives a directory path as input
2. Calls the filesystem MCP server's `list_directory` tool
3. Transforms the result to count files using JSONata
4. Returns the count

## Best Practices

1. **Use descriptive node IDs**: Make node IDs clear and meaningful (e.g., `list_directory_node` not `node1`)
2. **Format complex expressions**: Use block scalars (`|`) for multi-line JSONata expressions
3. **Document with comments**: Add YAML comments to explain complex logic
4. **Validate schemas**: Ensure `inputSchema` and `outputSchema` match actual data flow
5. **Test incrementally**: Build and test graphs node by node
6. **Use history functions carefully**: Understand execution context when nodes execute multiple times

## Common Patterns

### Sequential Tool Calls
Chain multiple MCP tool calls in sequence:
```yaml
entry -> mcp_node_1 -> mcp_node_2 -> transform -> exit
```

### Conditional Routing
Use switch nodes to route based on data:
```yaml
entry -> switch_node -> [path_a | path_b] -> exit
```

### Loops
Use switch nodes to loop back to previous nodes:
```yaml
entry -> increment_node -> check_condition -> [increment_node | exit]
```

### Data Transformation
Transform data between nodes using JSONata:
```yaml
mcp_node -> transform -> next_node
```

## Resources

- **JSONata Documentation**: https://jsonata.org/
- **JSON Logic Documentation**: https://jsonlogic.com/


