Skip to main content
Glama
jflap-automata

jflap-automata

by gkavin2527

JFLAP Automata Assistant — MCP Server

Claude is the reasoning layer (classifies the problem, designs states/transitions, explains results). This server is the execution layer: it builds the formal machine, validates it independently, serializes it to a JFLAP .jff file, and simulates test strings with real nondeterministic search — Claude never fabricates acceptance/rejection results.

This README covers the overview and setup. For the complete 33-tool reference, resource limits, memory lifecycle, the GUI automation investigation, and project history, see DOCUMENTATION.md.

What's implemented

Component

Status

MCP server + tool discovery (33 tools)

done

PDA builder, validator, simulator (nondeterministic BFS), JFLAP export

done

DFA/NFA builder, validator, simulator (subset construction), JFLAP export

done

Turing Machine builder, validator, tape simulator, JFLAP export

done

TM reject-state export correctness (no outgoing transitions, never <final/>)

done

CFG builder, validator, epsilon/unit/useless transformations, bounded leftmost-derivation

done

CFG exact membership via Earley parsing

done

JFLAP GUI automation: process control, menu navigation, file opening

real-GUI-verified — see DOCUMENTATION.md

JFLAP GUI automation: simulation input entry

confirmed impossible on this Java/macOS bridge — Python simulators remain authoritative

AI-powered Automata Workbench: solve_automata_problem orchestration, readable transition tables

done

Bounded, LRU-evicting automaton/grammar storage + 6 management tools

done

Server-enforced resource limits (every search parameter has a hard maximum)

done

AppleScript-injection-hardened GUI automation, verified against the real interpreter

done

373/373 pytest tests pass. Most call the underlying builder/validator/ simulator/storage functions directly (in-process, no MCP transport) — the fastest way to cover algorithmic correctness. A separate, genuinely real check also exists: tests/test_mcp_stdio.py spawns server.py as an actual subprocess and drives it over the SDK's real stdio transport (stdio_client + ClientSession) for 3 representative tools (create_fa+simulate_automaton, create_cfg+check_cfg_membership, plus a full list_tools() call asserting all 6 of its spot-checked tools appear) — this is deliberately a protocol-wiring/schema check, not a claim that every one of the 33 tools has been exercised over the wire; the in-process tests are what cover each tool's actual behavior.

JFLAP 7.1 is installed on this machine and has been driven for real — launched, closed gracefully, had files opened into it via the MCP server with the result visually confirmed (title bar + canvas matching the generated automaton exactly), and had its menus navigated through real Accessibility calls. Automated simulation input entry was investigated via five independent real techniques and confirmed — not assumed — to be unsupported by this specific Java/AWT-to-Cocoa bridge. The project's settled design: Python simulators stay the sole source of truth for results; JFLAP is for visual display and optional manual verification. See DOCUMENTATION.md's "GUI automation" section for the full investigation and architecture.

storage/generated/*.jff holds every generated file; open it from JFLAP via File > Open, or ask Claude to call open_in_jflap (best-effort open -a JFLAP, falls back to manual instructions if JFLAP.app isn't installed).

Related MCP server: arc-diagram

Architecture

At its simplest, this is a straight pipeline:

Claude / MCP client
    |
    v
MCP server (server.py)
    |
    v
Builders        (builders/*.py        -- construct the Automaton/Grammar)
    |
    v
Validators      (validators/*.py      -- independent structural checks)
    |
    v
Simulators / CFG parser
    (simulators/*.py -- fa/pda/tm_simulator.py, cfg_processor.py, earley_parser.py)
    |
    v
JFF generator   (generators/jff_generator.py -- Automaton/Grammar -> JFLAP .jff XML)
    |
    v
Optional JFLAP desktop integration (gui/*.py -- launch, open, screenshot, close)

Python is the authoritative simulation engine; JFLAP is an optional visual companion. Every ACCEPTED/REJECTED/MEMBER result a caller receives comes from the Simulators/CFG-parser step above — never from JFLAP. The last step (JFLAP desktop integration) is entirely optional: nothing above it depends on JFLAP being installed, and nothing in it can override or substitute for a Python-computed result. The fuller diagram below shows the same pipeline with the branching construction paths (DFA/NFA/PDA/TM/CFG) and the two in-memory stores (automata, grammars) made explicit:

flowchart TD
    User["User"] --> Claude["Claude\n(classifies the problem, designs states/transitions, explains results)"]
    Claude --> MCPClient["MCP Client\n(Claude Desktop / Claude Code)"]
    MCPClient -->|stdio, MCP protocol| Server["MCP Server -- server.py\n(33 tools)"]

    Server --> Orchestration["Construction & orchestration\n(create_*, solve_automata_problem)"]
    Orchestration --> DFA["DFA / NFA\nbuilders/fa_builder.py"]
    Orchestration --> PDA["PDA\nbuilders/pda_builder.py"]
    Orchestration --> TM["Turing Machine\nbuilders/tm_builder.py"]
    Orchestration --> CFG["CFG\nbuilders/cfg_builder.py"]

    DFA --> Validators["Validators\nvalidators/*.py\n(independent structural checks)"]
    PDA --> Validators
    TM --> Validators
    CFG --> Validators

    Validators --> Simulators["Python Simulators -- AUTHORITATIVE EXECUTION\nsimulators/*.py\n(fa/pda/tm_simulator.py, cfg_processor.py, earley_parser.py)"]

    Simulators --> JFF["JFLAP .jff export\ngenerators/jff_generator.py"]
    JFF --> GUI["JFLAP 7.1 GUI integration -- gui/*.py\n(launch, open, screenshot, close -- SUPPORTED, non-authoritative)"]

    Simulators -.->|"ACCEPTED / REJECTED / MEMBER\n(always Python-verified)"| Server
    GUI -.->|"visual confirmation only\n(never a simulation result)"| Server
    Server --> MCPClient

    style Simulators fill:#2d6a4f,color:#fff
    style GUI fill:#555,color:#fff

The authority boundary, explicitly: every ACCEPTED/REJECTED/MEMBER result Claude reports comes from the Python simulators (green above), never from JFLAP. JFLAP's GUI integration (gray above) is a separate, genuinely useful but non-authoritative layer — launching the app, opening a generated .jff file so a human can see the automaton, and taking a screenshot. run_jflap_simulation deliberately reports NOT_SUPPORTED rather than a guessed result, because typing a test string into JFLAP's own dialogs cannot be automated on this platform (see DOCUMENTATION.md's "Known limitations" and "GUI automation" sections) — the Python simulators are never bypassed, with or without JFLAP installed.

Screenshots

All real captures from the actual running application — none are fabricated or AI-generated. See docs/screenshots/README.md for how each one was captured and verified. Captured before the storage- lifecycle tools were added, so the /mcp panel screenshot below shows 27 tools, not today's 33 — the panel itself is still genuine, just from an earlier point in the project.

DFA in JFLAP A generated DFA ("ends in 01") open in JFLAP 7.1

PDA construction The canonical a^(2n)b^n PDA, stack operations visible

Turing Machine The "even number of 1s" TM in JFLAP

Context-free grammar A generated CFG in JFLAP's grammar table view

MCP connected jflap-automata connected in Claude Code's /mcp panel, 27 tools

Automata workflow Real terminal output: DFA/PDA/TM construction, validation, and simulation results

TM compact trace Real terminal output: the compact TM trace mode and step reconstruction

End-to-End Demonstration

examples/end_to_end_demo.py is a standalone script that exercises the project's actual DFA/PDA/TM/CFG builders, validators, simulators, and JFLAP exporters directly — no Claude, no MCP protocol, no network, no API keys. It's the fastest way to confirm the whole non-GUI pipeline works on your machine.

python examples/end_to_end_demo.py

What it does

Six stages, each printing its own status: construct a DFA ("ends in 01") and simulate 4 test strings; construct the project's canonical a^(2n)b^n PDA and simulate an accepted/rejected pair; construct a small "even number of 1s" Turing Machine and report its final configuration; construct a small CFG and check membership of one member and one non-member string via the exact Earley parser (check_cfg_membership uses); re-run the TM with trace_format="compact" and reconstruct its initial and final configurations; and finally export all four as .jff files.

Example output

==========================================================
JFLAP MCP -- AUTOMATA WORKBENCH DEMO
==========================================================

[1/6] DFA CONSTRUCTION
Status: SUCCESS
Validation: VALID
Input '1101': ACCEPTED
Input '1010': REJECTED
Input '1111': REJECTED
Input '01': ACCEPTED

[2/6] PDA CONSTRUCTION
Status: SUCCESS
Validation: VALID
Input 'aab': ACCEPTED
Input 'aabb': REJECTED
...
[6/6] JFLAP EXPORT
DFA  -> .../storage/generated/demo/demo_dfa.jff
...

(Full output is reproducible by running the command yourself — see docs/screenshots/06-automata-workflow.png and 07-tm-compact-trace.png for real captures of this exact script's output.)

Optional arguments

Flag

Effect

--skip-jflap

Skip JFLAP entirely, including the export stage's JFLAP-open attempt.

--open-jflap

Open the generated DFA in a real JFLAP window after export (requires Java + a configured JFLAP.jar, see "Setup" below).

--output-dir PATH

Where generated .jff files are written (default: storage/generated/demo/).

Run python examples/end_to_end_demo.py --help for the same, from the script itself.

Requirements

Only this project's existing virtual environment (pip install -r requirements.txt) — no JFLAP, no Claude, and no network access are required for the default run. --open-jflap is the only thing that needs JFLAP/Java set up.

What this demo proves and doesn't

Proves the DFA/PDA/TM/CFG builders, validators, and simulators work correctly end-to-end, independent of Claude or MCP, that .jff export works for all four automaton/grammar types, and that the TM compact- trace architecture and reconstruct_at_step work as documented. Does not exercise the MCP protocol itself (see tests/test_mcp_stdio.py for that) or demonstrate JFLAP GUI simulation input, which remains unsupported on this platform regardless of what this script does.

Project layout

jflap-mcp/
├── server.py                    # MCP tool definitions, wires everything together
├── models.py                    # shared State/Transition/Automaton + Grammar/Production dataclasses
├── resource_limits.py           # per-operation default + hard-maximum resource limits, enforced server-side
├── storage.py                   # BoundedStore: thread-safe, LRU-evicting, pin-aware automaton/grammar storage
├── reporting.py                 # transition-table/production-list/summary formatters (pure, no new logic)
├── builders/
│   ├── common.py                # shared State-construction helper (build_states) used by all three builders below
│   ├── pda_builder.py
│   ├── fa_builder.py            # DFA + NFA
│   ├── tm_builder.py
│   └── cfg_builder.py
├── validators/
│   ├── common.py                # shared single-character-symbol check used by both validators below
│   ├── automata_validator.py    # structural + semantic checks for DFA/NFA/PDA/TM, optional test_cases cross-check
│   └── cfg_validator.py         # structural checks for Grammar objects
├── generators/jff_generator.py        # Automaton -> JFLAP .jff XML; Grammar -> JFLAP grammar .jff XML
├── simulators/
│   ├── pda_simulator.py         # nondeterministic BFS over (state, input, stack) configs
│   ├── fa_simulator.py          # epsilon-closure subset simulation
│   ├── tm_simulator.py          # tape/head simulation, halt-on-no-transition semantics
│   ├── cfg_processor.py         # nullable-set/epsilon-removal, unit-production elimination,
│   │                             # useless-symbol removal, bounded leftmost-derivation search
│   └── earley_parser.py         # exact CFG membership: Predictor/Scanner/Completer chart parser
├── jflap/simple_launcher.py     # minimal file-based open: best-effort `open -a JFLAP <file>` (renamed
│                                 # from jflap_launcher.py to avoid a naming collision with gui/jflap_launcher.py)
├── gui/                          # real JFLAP desktop automation (separate from simulators/)
│   ├── jflap_launcher.py        # locate jar (env var/config/search, never hardcoded), launch via java -jar
│   ├── jflap_automation.py      # AppleScript/Accessibility menu + keystroke automation; _applescript_quote
│   │                             # renders any non-printable-ASCII character as `character id <n>`, never
│   │                             # raw-interpolated, so a crafted value can't inject additional AppleScript
│   ├── screenshot_manager.py    # `screencapture`-based, unique non-overwriting filenames
│   ├── jflap_controller.py      # orchestrates the three above; the only module server.py imports from gui/;
│   │                             # also holds GUI_TOOL_CAPABILITIES, the structured FULL/PARTIAL matrix
│   ├── verification.py          # Python-vs-JFLAP comparison: MATCH/MISMATCH/INCONCLUSIVE/JFLAP_UNAVAILABLE
│   └── jflap_config.json        # optional: {"jar_path": "/full/path/to/JFLAP.jar"} (not checked in; user-provided)
├── storage/generated/           # output .jff files land here (gitignored, recursive)
├── outputs/screenshots/         # GUI automation screenshots land here (gitignored, recursive; unique filenames, never overwritten)
├── examples/end_to_end_demo.py  # standalone demo: no Claude, no MCP, no network -- see "End-to-End Demonstration"
├── docs/screenshots/            # real application screenshots for README.md -- see docs/screenshots/README.md
├── pyproject.toml / requirements.txt / uv.lock   # see "Setup" -- pip and uv are both supported, kept in sync
├── DOCUMENTATION.md             # full tool reference, architecture deep-dive, GUI investigation, project history
└── tests/                       # pytest suite (373 tests, all passing) + bench_tm_trace.py

Setup

Two supported install paths — pick one. Both install the exact same dependency versions (uv.lock and requirements.txt are kept in sync; see "Dependencies" below for the verified version range).

Option A: pip

git clone <this-repo-url> jflap-mcp
cd jflap-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 -m pytest tests/ -q        # sanity check

Option B: uv (faster, reproducible via the committed lockfile)

git clone <this-repo-url> jflap-mcp
cd jflap-mcp
uv sync --extra dev                # creates .venv, installs runtime + pytest from the lockfile
uv run pytest tests/ -q            # sanity check

--extra dev is required for the sanity-check command specifically — pytest is declared as an optional (dev) dependency, so a plain uv sync (confirmed by testing both) installs only the runtime dependency (mcp[cli]) and intentionally skips it, matching what an end user running the server (not its tests) actually needs. (Install uv itself first if you don't have it: pip install uv, or see https://docs.astral.sh/uv/getting-started/installation/.) Regenerate the lockfile with uv lock after any dependency change in pyproject.toml.

Requires Python 3.10+ (the codebase uses X | None union-type annotations, e.g. in simulators/tm_simulator.py, which need 3.10 or later). Tested on Python 3.10 and 3.14 by actually running the full test suite on both — not assumed from the requires-python declaration alone (see DOCUMENTATION.md's "Known limitations" for the exact versions tested). No OS-specific code runs in this path — the Python simulators, builders, validators, and MCP server all work identically on macOS, Linux, and Windows. GUI automation (the next section) is macOS-only and has not been tested on other platforms — see "Platform support" below.

Optional: enabling real JFLAP GUI automation

The Python simulators and MCP tools all work with zero setup. GUI automation (opening files in JFLAP, screenshots, visual comparison) additionally needs, all currently satisfied on this machine:

  1. Java — openjdk 26.0.2 via Homebrew. Check with java -version.

  2. JFLAP.jar — download from https://www.jflap.org, then either:

    • export JFLAP_JAR_PATH=/full/path/to/JFLAP.jar, or

    • write {"jar_path": "/full/path/to/JFLAP.jar"} to gui/jflap_config.json. Nothing here guesses a path that wasn't actually verified to exist — locate_jflap_jar() checks a short list of conventional locations (~/JFLAP/, ~/Downloads/, /Applications/, …) but only ever returns one that's confirmed on disk, never an assumption.

  3. macOS Automation permission for whatever process runs server.py — System Settings > Privacy & Security > Automation, allow it to control "System Events".

  4. macOS Accessibility permission for the same process — System Settings > Privacy & Security > Accessibility. Separate from Automation; required for menu clicks and window inspection.

  5. macOS Screen Recording permission for the same process — System Settings > Privacy & Security > Screen Recording. Required for capture_jflap_screenshot.

Call get_jflap_environment_status (an MCP tool, or gui.jflap_controller.get_environment_status() directly) at any time to see exactly which of these are satisfied right now — it reports Java, the jar path, the running JFLAP process (if any), and both Automation and Accessibility permission status independently (one can be granted without the other).

Not covered by any of the above: automated entry of a test string into JFLAP's own simulation dialogs. This was investigated thoroughly (five independent techniques against a real running JFLAP 7.1) and confirmed to be a structural limitation of this Java/macOS Accessibility bridge, not a missing permission — see DOCUMENTATION.md's "GUI automation" section for the full investigation and why the Python simulators remain the authoritative source of simulation results regardless.

Dependencies

mcp[cli]>=2.0,<3.0 is the only runtime dependency, and this range is verified, not assumed: mcp 1.x exposed a completely different API (FastMCP from mcp.server.fastmcp); 2.x renamed it to MCPServer (from mcp.server.mcpserver import MCPServer) and moved ToolError to mcp.server.mcpserver.exceptions — the exact imports server.py uses. Installing mcp 1.x and importing server.py fails immediately with an error message from the SDK itself confirming the rename. The lower bound (>=2.0) was confirmed by installing mcp==2.0.0 (the first stable 2.x release) in an isolated environment and running this project's entire pytest suite — including the real, non-mocked MCP stdio-protocol tests in tests/test_mcp_stdio.py — against it; everything passed, and the same suite was separately run against mcp==2.3.0 (what this project's own .venv actually uses). Neither claim extends to every 2.x point release in between — only these two were actually tested. <3.0 guards against a future rename of the same kind; no mcp 3.x exists yet.

Claude Desktop configuration

Edit ~/Library/Application Support/Claude/claude_desktop_config.json and add (merge, don't replace, if the file already has other servers). Replace /absolute/path/to/jflap-mcp with where you actually cloned this repo — both command and the arg must be absolute paths, not ~ or a relative path:

{
  "mcpServers": {
    "jflap-automata": {
      "command": "/absolute/path/to/jflap-mcp/.venv/bin/python3",
      "args": [
        "/absolute/path/to/jflap-mcp/server.py"
      ]
    }
  }
}

Claude Code / VS Code configuration

Claude Code reads the same shape from a project-local .mcp.json at the repo root (this file is gitignored — it's machine-specific, so each developer creates their own):

{
  "mcpServers": {
    "jflap-automata": {
      "type": "stdio",
      "command": "/absolute/path/to/jflap-mcp/.venv/bin/python3",
      "args": ["/absolute/path/to/jflap-mcp/server.py"],
      "env": {
        "JFLAP_JAR_PATH": "/absolute/path/to/JFLAP.jar",
        "PYTHONUNBUFFERED": "1"
      }
    }
  }
}

JFLAP_JAR_PATH and PYTHONUNBUFFERED are optional — omit the env block entirely if you haven't set up JFLAP GUI automation yet (see above); the Python simulators and every non-GUI tool work without it.

After editing either file, fully restart Claude Desktop / reload the Claude Code window, then verify the connection: open Settings > Developer/Extensions (Desktop) or run /mcp (Claude Code) and confirm jflap-automata shows as connected with 33 tools listed.

Verifying the server on its own (without Claude)

Before wiring it into Claude at all, confirm the server itself starts cleanly and the tool list is what you expect:

# Syntax/import check -- should produce no output on success
.venv/bin/python -c "import server"

# Full MCP tool listing over the real stdio protocol
.venv/bin/python -c "
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command='.venv/bin/python3', args=['server.py'])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print(len(tools.tools), 'tools:', sorted(t.name for t in tools.tools))

asyncio.run(main())
"

The 33 tools you should see listed (full reference in DOCUMENTATION.md):

  • Generic (work across DFA/NFA/PDA/TM): classify_automata, validate_automaton, generate_jff, simulate_automaton, get_execution_trace, open_in_jflap

  • Construction: create_pda, create_fa, create_tm, create_cfg

  • TM-specific: validate_tm, simulate_tm, generate_tm_jff

  • CFG-specific: validate_cfg, eliminate_unit_productions, derive_cfg, check_cfg_membership, generate_cfg_jff

  • GUI automation: get_jflap_environment_status, launch_jflap, close_jflap, open_jff_file, run_jflap_simulation, capture_jflap_screenshot, compare_python_jflap

  • Workbench orchestration: solve_automata_problem, get_transition_table

  • Storage lifecycle management: list_automata, list_grammars, delete_automaton, delete_grammar, clear_automata, clear_grammars

validate_automaton, generate_jff, simulate_automaton, and get_execution_trace all dispatch on the stored automaton's type, so they work for TM automatons too — the TM-specific tools exist alongside them for discoverability. There is no generic equivalent for CFG tools since a Grammar isn't a states-and-transitions automaton; it has its own tools and its own in-memory store (_GRAMMARS, separate from _AUTOMATA).

check_cfg_membership and get_jflap_environment_status weren't in the originally suggested tool lists — the first because without it Claude had no way to get a conclusive CFG membership answer through MCP at all (only a bounded search), the second because every other GUI tool needs that environment information to answer honestly rather than guessing. solve_automata_problem/get_transition_table are pure orchestration/formatting over all of the above — see DOCUMENTATION.md's "AI-Powered Automata Workbench" section. The six storage-lifecycle tools exist because _AUTOMATA/ _GRAMMARS are now bounded and LRU-evicting (see DOCUMENTATION.md's "Memory lifecycle" section) — they let a caller see what's stored and free space deliberately instead of only waiting for automatic eviction.

Platform support

Component

macOS

Linux

Windows

Builders, validators, Python simulators, MCP server

Tested, works

Not tested — no OS-specific code in this path, likely works

Not tested — no OS-specific code in this path, likely works

JFLAP GUI automation (gui/)

Tested, works

Not supported — gui/jflap_automation.py shells out to macOS-only osascript/System Events

Not supported — same reason

File-based JFLAP open (open_in_jflap)

Tested, works (open -a JFLAP)

Not supported — open is macOS-specific

Not supported — same reason

If you're on Linux or Windows: every construction/validation/simulation/ .jff-export tool works identically (they're pure Python with no platform-specific calls); only the gui/ automation layer and open_in_jflap are macOS-only today. You can still open a generated .jff file in JFLAP manually on any platform JFLAP itself supports.

Full documentation

DOCUMENTATION.md has everything not covered above: the complete 33-tool reference (every parameter, return shape, and error status), the resource-limits table (every default and server-enforced hard maximum), the memory-lifecycle design (bounded storage, LRU eviction, pinning), the recommended Claude custom-instructions prompt, the AI-Powered Automata Workbench walkthrough, the PDA/TM/CFG architecture deep-dives, the full GUI-automation investigation (five techniques tried, why none work, the capability matrix, mocked-vs-real tests), the project's 0.1.0 audit history, and the contributing guide.

Contributing

See DOCUMENTATION.md's "Contributing" section for the full house rules. Short version: run the full test suite first (.venv/bin/python -m pytest tests/ -q, or uv run pytest tests/ -q) and confirm it's green before making changes, add tests for anything you change, and open a PR describing what changed and why.

License

MIT — Copyright (c) 2026 Kavin.

Changelog

See CHANGELOG.md for the version history.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      B
      maintenance
      Enables natural language conversion of production and queueing systems into Petri net models, with simulation and PNML export via MCP tools.
      66
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Enables MCP agents to validate architecture descriptions and scaffold complete, interactive React Flow diagram applications with multiple visual dialects, themes, exports, and verification probes.
      4
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables users to create, build, and repack Айрен .itx test archives from JSON specifications, then structurally and statically validate their XML, questions, answers, tags, profiles, formulas, and scenarios. It also renders standalone HTML previews of all questions, images, and declared correct answers, with CLI access for local automation.
      MIT
    • A
      license
      B
      quality
      B
      maintenance
      Enables users to create, validate, view, and export system stock-and-flow maps from a versioned JSON model through MCP-capable hosts over local stdio or hosted Streamable HTTP. Supports causal-loop and nested-level mapping while keeping submitted model content with the caller and requiring no LLM provider key.
      5
      29 npm
      GPL 3.0