
Pyjab Mcp
io.github.gaozhao1989v0.1.1更新于 Oct 10, 2026
Drive Java desktop apps from an AI agent, through the JVM's own accessibility tree
概览
让 AI 助手通过 JVM 无障碍树查看并操作 Java 桌面应用(Swing、AWT、JavaFX)。
- 功能
- 提供 15 个 Java 桌面自动化工具:java_diagnostics、java_list_windows、java_attach_window、java_snapshot、java_find、java_get_element、java_get_text、java_get_table、java_click、java_set_text、java_select、java_fill_form、java_wait_for、java_screenshot 和 java_send_keys。它通过 Java Access Bridge 读取 JVM 自身的无障碍树,因此可以查找、读取并操作按钮、菜单、表格、树、列表和文本字段。每次操作前都会重新定位元素并核对角色、名称、同级位置和边界。它只做附加连接:从不启动、修改、重启或终止目标应用。
- 适用场景
- 适合自动化或测试暴露无障碍信息的 Java 桌面客户端,尤其是无法修改、也无法用不同启动参数重启的生产客户端。不适合画布自绘界面、浏览器内嵌 Java、插件宿主中的 applet、非 Java 的 Windows 应用,也不适合任何靠像素猜测的方式。
- 运行要求
- Windows 桌面会话;Python 3.10 或更高版本;目标应用所需的 JVM;随包安装的 pyjab>=1.10.0(pip install pyjab-mcp,或用 uvx 运行)。目标应用必须在启动前启用 JAB。未声明账号、API 密钥或环境变量。
安装
在 SourceWeft 中
- 打开 控制台中的 Pyjab Mcp,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
pyjab-mcp
An MCP server that lets an AI agent drive Java desktop applications — Swing, AWT, JavaFX — through pyjab, which reads the JVM's own accessibility tree via Java Access Bridge.
mcp-name: io.github.gaozhao1989/pyjab-mcp
It never modifies, restarts or injects into the target application. That constraint is the point: launch parameters, classpaths and dependency JARs stay exactly as they are, which is what makes this usable against a production Java client that you cannot change.
Status
0.1.0 is released — 15 tools: java_diagnostics, java_list_windows,
java_attach_window, java_snapshot, java_find, java_get_element, java_get_text,
java_get_table, java_click, java_set_text, java_select, java_fill_form,
java_wait_for, java_screenshot, and java_send_keys. It needs pyjab>=1.10.0: 1.9.0
brought the tree walk a snapshot is built on, and 1.10.0 brought the public window listing and
JABDriver.detach(), which is what lets a session end without terminating the application.
java_send_keys sends a chord where the installed pyjab provides one, and reports the
requirement where it does not: no released pyjab can send alt+y yet — the API is written
and merged upstream (pyjab#168) — and the tool probes for it rather than assuming, so the
release that carries it makes the tool work with no change here. Until then the reply says
what to do instead.
Two elements can share a name, which is why a handle carries where an element is as well as what it is called: searching by name returns every match, each gets its own handle, and the end-to-end suite checks that clicking one acts on that one and not the other.
Nothing is acted on until it has been checked. Every action re-finds the element and compares role, name, position among siblings and bounds against what the snapshot recorded, then acts on the element it just checked — and if anything differs, it does nothing and reports the difference.
java_get_table is where this substrate earns its place: it reads a JTable through the
JVM's own AccessibleTable interface — row and column counts, then cell text, a page of rows
at a time — rather than through a platform bridge that does not carry that information at all.
Live behaviour is verified on a hosted Windows runner by windows-gui.yml: the end-to-end
suite attaches to a real Swing application, snapshots it, resolves a handle against the real
element, and checks that a window opened after the bridge had been idle is still seen. That
last one is the reason the layer exists — a message pump on the wrong thread passes every
other test in this repository. What it still cannot cover is your application: the run drives
pyjab's test app, and the one that matters is the one you care about.
It needs pyjab>=1.10.0, which brought the two APIs this project spent M1 and M2 waiting on:
list_java_windows(), so the window list comes from the bridge directly rather than through
pyjab's CLI, and JABDriver.detach(), so a session can be released without terminating the
application — the only teardown before it killed the bound process.
One gap is still recorded rather than worked around (tracked as
gaozhao1989/pyjab#168): there is no public
way to send a key sequence, so java_send_keys reports that requirement instead of
pretending send_text can express alt+y.
A handle from java_snapshot is a position, not a reference. A later call re-finds the
element and checks its role, name, position and bounds before doing anything, so a window
that moved on produces a message rather than an action on the wrong control.
The one thing that was already settled is the question the whole direction rested on.
Measured on a real Java window, through the same UIAutomationCore that generic Windows
automation uses:
The control is what makes the six mean anything: the same measurement over the whole desktop finishes, so the measurement works and the Java window is the hard case.
Where to read first
Maintainer working notes are kept out of this repository deliberately. Everything a user or a
contributor needs is above, and the rules that are not prose are enforced by the guards in
tools/ — which run in CI on every push.
Requirements
- Windows
- Python 3.10 or newer
- A JVM on the machine, for the application being driven
pyjab, which brings the rest
Install
Working on the server itself is pip install -e ".[dev]" — see Working on it.
Use
The console script the package installs:
Claude Desktop reads %APPDATA%\Claude\claude_desktop_config.json, and takes this (double
the backslashes — JSON needs it):
Claude Code writes the same entry for you: claude mcp add pyjab-mcp -- pyjab-mcp.
docs/CLIENTS.md has Cursor and VS Code as well, the config file each one reads, the Windows path problem that makes most setups fail, and what to do when a tool answers with an error.
Start with java_diagnostics. It reports which part of the environment is missing —
Java Access Bridge not enabled, no bridge DLL, a 32-bit interpreter against a 64-bit JVM —
instead of leaving a tool call to fail with a locator error.
What it does, and what it cannot
Java Access Bridge exposes what an application declares about itself: a tree of roles, names, values, states and the actions a control supports. That is what this server drives, and it sets the boundary of what it can reach.
It works on Java applications that expose accessibility — Swing and AWT applications and
applets running as desktop applications, and JavaFX where the accessibility bridge is
present. Buttons, menus, tables, trees, lists, text fields and their values are all readable
and actionable, and java_get_table reads a table's cells through the table's own interface
rather than the pixels.
It cannot reach, because the information is not there to read:
Also worth knowing:
- The application must be JAB-enabled before it starts. Enabling JAB afterwards does not add the tree to a process already running; restart it.
- This server only attaches. It never launches an application, and it never terminates one
—
JABDriver.detach()releases the binding and leaves the process running. - A picture is not structure.
java_screenshotis for looking; anything you want to act on has to be found in the tree, because that is where handles and names live.
Support
- Free support — bugs, questions and feature requests go to
GitHub issues. Include the output of
pyjab-mcp --versionand of thejava_diagnosticstool: between them they answer most environmental questions before they are asked. - Commercial and enterprise support — deployment inside a restricted network, adaptation to a particular application, or an SLA: [email protected] (the address in this package's metadata). The open-source package is complete and unrestricted; there is no licence key, no quota and no feature held back for it.
Verifying it
The suite that runs on every push cannot drive an application: it needs Windows, a JDK, a
desktop session and a running Swing app. That layer is tests/e2e/, skipped unless
PYJAB_MCP_E2E=1, and windows-gui.yml runs it on a hosted windows-latest runner — by
hand, or on a weekly schedule. It starts a second JVM after the bridge has been idling,
because a message pump on the wrong thread passes everything else and fails exactly that.
It is not a status check: nobody runs it unless somebody asks (or the week comes round), so a green default CI run still says nothing about a live window.
Releasing
Tag-driven: bump src/pyjab_mcp/__init__.py, move the changelog's Unreleased section under
the new version, set server.json, then python tools/check_release_version.py v0.1.0 and tag.
docs/RELEASING.md has the whole procedure, the two one-time setups (a
PyPI pending publisher and the registry namespace), and what the four release jobs do.
Working on it
The guards are the project's rules made mechanical, in the same shape pyjab uses for its own:
a registry of what is allowed, checked in both directions, so adding a call site fails until
somebody writes down why it is acceptable. docs/ARCHITECTURE.md lists them and what each
one enforces.
来源:README.md,提交 8e1c253
工具
0版本历史
1- v0.1.1最新Oct 10, 2026


