JJMCP is a local C++23 stdio MCP server for working through an existing tmux-based Julia REPL workflow. It binds to a tmux pane selected by the user, sends Julia code into that pane, and treats the visible REPL as the source of truth. JJMCP does not spawn hidden Julia workers by default! The goal of this project is to provide a useable yet simple mcp server for AI agents when working on Julia development while at the same time keeping it simple enough such that a human can clearly see what is going on.
JJMCP is designed to work in tmux session together with a revise based Julia workflow for Julia power users who are running several active Julia processes together with local or cloud based AI agents.
The goal of this project is to support Unix systems such as macOS and Linux or Windows if the user works inside WSL.
- Linux or macOS
- A C++23 compiler
makepkg-confignlohmann_jsontmuxonPATH- A tmux pane already running Julia for evaluation tools
make check-deps
make
make testThe server binary is written to:
build/jjmcpDirect usage:
build/jjmcp --help
build/jjmcp serveserve runs MCP JSON-RPC over stdio. During MCP mode, stdout is reserved for MCP frames only; diagnostics go to stderr.
make install PREFIX=/usr/localAfter install, use the installed command:
jjmcp --help
jjmcp serveIf $(PREFIX)/bin is not on PATH, use the full installed path instead:
/usr/local/bin/jjmcp serveStaged/package install:
make install DESTDIR=/tmp/jjmcp-package PREFIX=/usrA staged install writes files under $(DESTDIR)$(PREFIX) for packaging. It does not make /tmp/jjmcp-package/usr/bin/jjmcp the normal runtime path unless you explicitly run that staged binary. After package installation, the runtime command path would be /usr/bin/jjmcp.
Installed files:
$(PREFIX)/bin/jjmcp$(PREFIX)/share/man/man1/jjmcp.1$(PREFIX)/share/man/man5/jjmcp-config.5$(PREFIX)/share/man/man7/jjmcp-security.7$(PREFIX)/share/doc/jjmcp/README.md$(PREFIX)/share/doc/jjmcp/SECURITY.md$(PREFIX)/share/doc/jjmcp/DESIGN.md
Use an absolute path to the executable. If you have run make install PREFIX=/usr/local, use /usr/local/bin/jjmcp. If you have not installed JJMCP, use the absolute path to build/jjmcp in your checkout.
Codex CLI style TOML:
[mcp_servers.jjmcp]
command = "/usr/local/bin/jjmcp"
args = ["serve"]Claude Code style JSON:
{
"mcpServers": {
"jjmcp": {
"command": "/usr/local/bin/jjmcp",
"args": ["serve"]
}
}
}Claude Code command form:
claude mcp add jjmcp /usr/local/bin/jjmcp serveStart Julia in tmux:
tmux new-session -s julia
julia --project=.From an MCP client:
- Call
jjmcp_list_tmuxto find the Julia pane id, such as%3. - Call
jjmcp_bindwith{"target":"%3","project_root":"/path/to/project"}. - Call
jjmcp_evalwith Julia code, for example{"code":"1 + 1"}.
Bindings are stored in memory and persisted to .jjmcp/config.json under the supplied project_root, or under the server current directory if no project root is supplied.
Eval-style tools sent through the tmux transport are pasted as clean Julia macro calls after a
one-time JJMCPRuntime bootstrap in the bound REPL. The bootstrap defines @JJMCP_COMMAND; later
commands are visible and reusable instead of being large generated wrapper functions.
For example, jjmcp_eval with {"code":"x = 41\nx + 1","timeout_ms":10000} appears in the REPL as:
@JJMCP_COMMAND "<marker-id>" 10000 begin
x = 41
x + 1
endThe same macro path is used by all eval-style tmux tools:
@JJMCP_COMMAND "<marker-id>" 10000 begin
try
using Revise
Revise.revise()
println("Revise complete")
catch e
showerror(stdout, e, catch_backtrace())
println()
end
end@JJMCP_COMMAND "<marker-id>" 10000 begin
using Pkg
Pkg.activate("/path/to/project")
end@JJMCP_COMMAND "<marker-id>" 120000 begin
using Pkg
Pkg.test()
end@JJMCP_COMMAND "<marker-id>" 10000 begin
using Pkg
Pkg.status()
endInside the macro, JJMCP still emits the same private begin/end/error/value markers used for MCP
response extraction. User statements are evaluated in the caller module so globals, imports, package
state, and Revise state remain part of the bound Julia session. Non-eval MCP tools such as
jjmcp_bind, jjmcp_status, jjmcp_capture, and jjmcp_interrupt do not paste Julia code and
therefore do not appear as @JJMCP_COMMAND calls.
jjmcp_list_tmux: list sessions, windows, panes, pane ids, process names, titles, and paths.jjmcp_bind: bind to an existing tmux pane.jjmcp_status: report binding and pane liveness.jjmcp_eval: evaluate Julia code in the bound pane using@JJMCP_COMMAND.jjmcp_capture: capture recent output without sending input.jjmcp_interrupt: send Ctrl-C to the bound pane.jjmcp_revise: runusing Revise; Revise.revise()through@JJMCP_COMMAND.jjmcp_activate: runusing Pkg; Pkg.activate(path)through@JJMCP_COMMAND.jjmcp_test: runtest_expr, include a test file, or runPkg.test()through@JJMCP_COMMAND.jjmcp_pkg_status: runusing Pkg; Pkg.status()through@JJMCP_COMMAND.jjmcp_capture_test_results: parse the latest JuliaTest Summary:block from tmux output and return structured fields likefound_summary,test_pass,test_fail,test_total,status, andfailures.require_summarydefaults tofalse; when set totrue, missing summaries return a tool error.include_rawdefaults totrueand includes captured pane output inraw_output.
Eval-style tools accept timeout_ms; the default per-process ceiling is 600000 ms. To permit longer
runs, start the server with a larger ceiling, for example:
JJMCP_TIMEOUT_MS_MAX=14400000 build/jjmcp serveThat example allows calls up to four hours when the client passes a matching timeout_ms.
Oversized eval output is returned from the end of the captured output, with a truncation marker for
omitted earlier lines or bytes. The full output remains visible in the bound tmux pane scrollback.
The server sends code by loading a tmux buffer and pasting it into the bound pane. Evaluation output
is isolated by marker lines emitted inside @JJMCP_COMMAND and returned as MCP text content. Julia
exceptions are captured as tool errors with the Julia error text.
JJMCP is released under the MIT License. Copyright (c) 2026 John Tinnerholm. See LICENSE for the full text.