4 releases

Uses new Rust 2024

new 0.0.4 May 8, 2026
0.0.3 Jun 4, 2025
0.0.2 Jan 2, 2025
0.0.1 Dec 1, 2024

#127 in Artificial intelligence

MIT license

155KB
4K SLoC

genify

MCP tool for AI coding agents to safely plan, diff, and apply structured file changes inside a project root.

Use genify for repeatable codebase edits: writing files, replacing text, inserting blocks around markers, moving/copying/deleting paths, creating directories, changing modes, and generating dry-run diffs before applying changes.

Crates.io Docs.rs CI Coverage Status

Turn one file into a complete project

The main idea is to have a single source file that can be used to generate or update a full project structure quickly and consistently using different configuration files.

There are three modes:

  • CLI interactive mode – for manual use and quick input.
  • Code mode – for automated and configurable project generation.
  • MCP server mode – for AI coding agents that call structured tools over STDIO.

Features:

  • Create files from templates based on config.
  • Replace content using regular expressions.
  • Append or prepend content to existing files.
  • Easily update multiple projects using shared configs.

Installation

Cargo

  • Install the rust toolchain in order to have cargo installed by following this guide.
  • run
    cargo install genify --features cli
    

Get Started

CLI

Turn one file into a complete project

Usage: genify [OPTIONS] [PATH]
       genify <COMMAND>

Arguments:
  [PATH]  Path to a config file or http(s) URL

Options:
  -p, --props-json <JSON>  Override props using a JSON object (Array/Map supported)
  -n, --no-interaction     Do not ask any interactive question
  -h, --help               Print help
  -V, --version            Print version

Commands:
  mcp   Start genify as an MCP server over STDIO
  help  Print this message or the help of the given subcommand(s)

example.toml

[props]
value = "value"
dir = "tmp"
val = "val"
other = "{{ val }}"
override = "1"

[[rules]]
type = "write"
path = "{{ dir }}/some.txt"  # if the file exists will be error
content = "{{ val }} {{ value }} {{ other | pascal_case }} {{ override }} - should be replaced"
if_exists = "error"

[[rules]]
type = "replace"
path = "{{ dir }}/some.txt"
replace = "should.*replaced"
content = "replaced {{ value }}"

[[rules]]
type = "prepend"
path = "{{ dir }}/some.txt"
content = "prepend {{ value }}"

[[rules]]
type = "append"
path = "{{ dir }}/some.txt"
content = "append {{ value }}"
genify example.toml

Override props from the CLI (including Array/Map) using JSON:

genify example.toml --props-json '{"tags": ["cli", "json"], "meta": {"license": "MIT"}}'

tmp/some.txt

prepend value
val value Val 1 - replaced value
append value

MCP

Run genify as an MCP server using the standard STDIO transport:

genify mcp --root .

For read-only agent sessions:

genify mcp --root . --read-only

Example Codex configuration:

[mcp_servers.genify]
command = "genify"
args = ["mcp", "--root", "."]

The MCP server writes protocol messages only to stdout; logs and diagnostics go to stderr. All filesystem access is constrained to --root.

Available tools:

Tool Behavior
genify_plan Returns planned file operations and affected paths without writing files.
genify_diff Runs generation in dry-run mode and returns a unified diff.
genify_apply Applies changes only when explicit_approval is true or confirm_token is "apply".
genify_validate_config Validates config parsing, rendering, and generated paths.
genify_list_templates Lists .toml configs and template files under the MCP root.

genify_plan, genify_diff, and genify_apply accept the config directly as JSON in MCP tool arguments. No temporary TOML config or template file is required.

{
  "config": {
    "rules": [
      {
        "type": "replace",
        "path": "src/application.rs",
        "replace": "old text",
        "content": "new text"
      }
    ]
  },
  "root": "."
}

Supported rule types:

Type Fields
write path, content, if_exists (overwrite, error, or skip)
delete path
rename / move from, to
copy from, to
mkdir path
chmod path, mode
append / append_once / prepend path, content
insert_before / insert_after path, marker, content
replace / replace_or_append path, replace, content, optional replace_all, optional expected_matches
managed_block path, start_marker, end_marker, content

replace is strict by default: when replace_all is false or omitted, it fails unless the regex match count equals expected_matches, which defaults to 1.

Supported rule examples:

{
  "config": {
    "rules": [
      {
        "type": "write",
        "path": "new/file.rs",
        "content": "...",
        "if_exists": "error"
      }
    ]
  }
}
{
  "config": {
    "rules": [
      {
        "type": "append_once",
        "path": "README.md",
        "content": "..."
      }
    ]
  }
}
{
  "config": {
    "rules": [
      {
        "type": "managed_block",
        "path": "README.md",
        "start_marker": "<!-- genify:start -->",
        "end_marker": "<!-- genify:end -->",
        "content": "..."
      }
    ]
  }
}
{
  "config": {
    "rules": [
      {
        "type": "insert_after",
        "path": "README.md",
        "marker": "## Usage",
        "content": "..."
      }
    ]
  }
}
{
  "config": {
    "rules": [
      {
        "type": "move",
        "from": "old/file.rs",
        "to": "new/file.rs"
      }
    ]
  }
}

genify_validate_config returns a structured hint.minimal_config and per-operation examples when the config is missing or invalid.

Supported Variable Types

Type Description CLI Interactive Support
String Text value ✅ Supported
Integer Whole number ✅ Supported
Float Decimal number ✅ Supported
Boolean true or false ✅ Supported
Array List of values ✅ JSON input
Map Key-value pairs ✅ JSON input

Note: For Array and Map, enter JSON when prompted or provide them up front with --props-json.

Code

fn main() {
    genify::generate(
        Path::new("."),
        &genify::parse_toml(
            fs::read_to_string("example.toml")
                .unwrap()
                .as_str(),
        )
        .expect("The config should be valid"),
        Some(vec![(
            "value".to_string(),
            genify::Value::String("val".to_string()),
        )]),
    )
    .expect("The generation should be successful");
}

License

Contribution

CONTRIBUTING.md

Dependencies

~8–17MB
~301K SLoC