A single-file JEB Decompiler engines plugin that augments JEB's built-in MCP
server (the one started from Tools -> MCP Server in the JEB UI) without
modifying jeb.jar or any vendor jars.
It adds a Python-script execution tool, hardens the server against a couple of known JEB bugs, and works directly with Cursor IDE / Claude Desktop / any other MCP client.
Tested on JEB 5.39.0 (
mcpsdk-fatjar-0.11.2, Jackson 2) and JEB 5.40.0 (mcpsdk-fatjar-1.1.2, Jackson 3). The plugin auto-detects the SDK shape at runtime — the same.javafile works on both.
JEB's built-in MCP server is great, but out of the box it has three problems that bite hard with modern MCP clients:
- No
run_python_script-style tool. You can't ask the LLM to drive JEB's own Jython API the wayFile -> Scripts...does. - Strict Jackson in older JEB rejects MCP clients that send unknown fields
like
capabilities.elicitation.form(Cursor 2025+ does this oninitialize), so the handshake never completes. **Tools -> MCP Server -> Stopis a one-way trip.** The staticjebmcpserverfield is never cleared, so subsequentStartreturns the dead instance and you have to fully relaunch JEB to get the server back.
This plugin fixes all three.
Executes a .py file inside the running JEB process using JEB's bundled
Jython 2.7 interpreter — the same engine that powers
File -> Scripts... in the GUI.
Pre-bound globals (when present):
| name | type | notes |
|---|---|---|
engines |
IEnginesContext |
the live engines context |
project |
IRuntimeProject |
None |
ctx |
IClientContext adapter |
minimal headless adapter; UI-only methods throw |
Inputs:
| name | type | default | notes |
|---|---|---|---|
script_path |
string | — | absolute path to the .py file |
script_args |
string[] | [] |
extra sys.argv (sys.argv[0] is always the script) |
timeout_seconds |
integer | 60 |
hard kill ceiling, capped at 600 |
Outputs: { success, stdout, stderr, duration_ms, timed_out, error? }.
Stdout / stderr are also tee'd live into JEB's Logger view (line by line) so you can watch a long-running script execute from inside JEB while still getting the full captured text back through MCP.
| # | Bug | Fix |
|---|---|---|
| 1 | Strict-Jackson initialize handshake fails (JEB ≤ 5.39). Cursor's capabilities.elicitation.form causes a Jackson UnrecognizedPropertyException. |
On every fresh server instance, the watchdog reflects on the live IJebMcpServer graph and disables DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES on every Jackson ObjectMapper it finds. Replaces a static jar patch entirely. |
| 2 | Restart bug. JebMcpServerInstance.stop() never clears the static jebmcpserver field; subsequent start() returns the dead instance. |
Watchdog detects Server.isStopped() == true while the static is still set and clears it under the same monitor that start()/stop() synchronize on. |
| 3 | One bad tool wedges the loop. McpServer.sync(...) dispatches handlers serially on a single thread. |
Each injected handler runs inside try/catch, and run_python_script runs on a daemon-thread executor with a hard timeout. The executor is static + lazy + self-healing, so it survives plugin hot-reload. |
The watchdog only injects/repairs our tool and the static field, and tweaks the SDK's ObjectMappers; it does not modify the original JEB-shipped tool handlers.
jeb_mcp/
├── mcp-ext-plugin/
│ └── MCPExtensionsPlugin.java ← the plugin (single Java file, JEB compiles it on demand)
├── install.cmd ← copies the .java into <JEB>/coreplugins/scripts/
├── mcp.json ← example Cursor / MCP-client config
├── smoketest.py ← Jython script to verify run_python_script end-to-end
├── README.md
git clone https://github.com/<you>/jeb_mcp.git
cd jeb_mcp
install.cmd :: defaults to C:\Work\tools\JEB-5.40.0
install.cmd "C:\Work\tools\JEB-5.39.0" :: or pass an explicit JEB rootThat copies MCPExtensionsPlugin.java into <JEB>/coreplugins/scripts/.
JEB compiles single-file Java plugins on demand at startup, so no separate
build step is needed.
Tools -> MCP Server -> Start (default port 8425).
You should see in JEB's Logger view:
[I] [mcp-ext] loaded; watching JEB MCP server for tool injection
[I] [mcp-ext] injected tools into JEB MCP server: [run_python_script]
On JEB 5.39 you'll additionally see:
[I] [mcp-ext] patched N Jackson ObjectMapper(s): FAIL_ON_UNKNOWN_PROPERTIES disabled
The injection happens automatically the first time the MCP server is started (watchdog polls every 2 s).
mcp.json (Cursor, Claude Desktop, etc.):
{
"mcpServers": {
"jeb": {
"url": "http://localhost:8425/mcp"
}
}
}No stdio proxy is needed — modern MCP clients support HTTP+SSE directly, and the plugin makes JEB's strict Jackson accept whatever capability fields the client sends.
From any MCP client (or via Cursor's tool runner):
Expected (truncated):
{
"success": true,
"duration_ms": 551,
"timed_out": false,
"stdout": "...\n[smoketest] hello from JEB MCP run_python_script ts=...\n... engines: com.pnfsoftware.jebglobal.Sr (projects=1)\n...",
"stderr": "",
}- Single-file Java plugin in
<JEB>/coreplugins/scripts/. JEB'sIEnginesPluginlifecycle callsload()/execute()/dispose()for us. **load()spawns a daemon "watchdog" thread** that polls every 2 s for changes toJebMcpServerInstance.get(). When it sees a freshIJebMcpServer, it:- (best-effort) walks the object graph and disables strict Jackson;
- reflects out the
McpSyncServer; - removes any stale tool registration with our names, then re-registers
run_python_scriptwith the current SDK API.
- Cross-version reflection. Both the schema setter
(
Tool$Builder.inputSchema(String)vs(McpJsonMapper, String)) and the result builder (structuredContent(Map)vsstructuredContent(Object)) changed between JEB 5.39 and 5.40. The plugin tries the legacy signature first, falls back to the new one onNoSuchMethodException, and caches aJacksonMcpJsonMapperSupplierinstance for the new path. - Obfuscation-resilient field lookups. Internal JEB classes
(
com.pnfsoftware.jeb.client.mcp.WH, etc.) get re-obfuscated between releases. Every reference goes throughfindFieldValueByTypeName, i.e. we match by Java type, not by field name, so renames don't break us. - No
jeb.jarmutation, no proxy, no jar swap — the plugin is the only artifact that needs to be installed.
| JEB | mcpsdk-fatjar | Jackson | inputSchema setter |
structuredContent setter |
Status |
|---|---|---|---|---|---|
| 5.39.0 | 0.11.2 | 2.x | (String) |
(Map) |
OK |
| 5.40.0 | 1.1.2 | 3.x | (McpJsonMapper, String) |
(Object) |
OK |
- Java target: 21 (matches JEB's required JDK 21).
- Jython: any 2.7.x bundled with JEB (5.39/5.40 both ship
2.7.3). - OS: developed and tested on Windows; nothing in the plugin is platform-
specific (
install.cmdis the only Windows-only piece — the.javafile itself just needs to land in<JEB>/coreplugins/scripts/).
| symptom | likely cause | what to do |
|---|---|---|
[E] [mcp-ext] forced injection failed: java.lang.reflect.InvocationTargetException |
JEB hot-reloaded the plugin and the previous incarnation already registered the tools. | The next watchdog tick (≤ 2 s) re-injects via removeTool + addTool — should self-heal. If it doesn't, full-restart JEB. |
RejectedExecutionException ... ThreadPoolExecutor [Terminated] |
JEB called dispose() on a previous plugin instance; a zombie tool registration is still live. |
Newer plugin builds make the executor static + self-healing — restart JEB once and the next call rebuilds the pool. |
Response missing structured content from the MCP client |
Old plugin running on a new SDK (or vice versa). | Reinstall and restart JEB. |
MCP initialize hangs / errors with UnrecognizedPropertyException on JEB 5.39 |
The Jackson lenient-mode patch didn't run yet. | Verify in the Logger view that [I] [mcp-ext] patched N Jackson ObjectMapper(s) appeared. If not, restart the MCP server from the menu — the watchdog will redo the patch. |
MIT. See the header of mcp-ext-plugin/MCPExtensionsPlugin.java.
Originally by d4rkmen.