Skip to main content

Plugin system

IdeGYM's extensibility rests on one idea: a single plugin package can hook into three distinct integration points. Each is optional, discovered via its own entry-point group, and consumed by a different part of the system. The PyCharm and IntelliJ IDEA integrations are the canonical examples; the built-in defaults (base-system, user, project, tools, rewards) are plugins too.

The three hook points (click a node for source)​

Adapted from website/docs/reference/diagrams/plugins.md.

The integration points​

HookHow you opt inWhere it's consumedWhat you get
Image@image_plugin("name") + apply() / render()Image.to_spec() (image builder)A Dockerfile fragment
MCP upstreamget_mcp_upstream() on the image pluginImage.to_spec() (auto-writes config)/etc/idegym/mcp-upstreams.d/<name>.json
Build context filesget_context_files() on the image pluginImage.to_spec() → the build backendBundled files staged so your COPY sources resolve
Build secretsget_build_secrets() on the image pluginImage.to_spec() → the build backendBuild-arg names forwarded as secrets (kept out of image layers)
Server@server_plugin + get_server_router()server/main.py at startupAn APIRouter at /api/<plugin>/*
Clientidegym.plugins.client entry pointIdeGYMServer.__init__server.<plugin>.<method>()

Every method has a no-op default, so a plugin implements only the points it needs. The MCP-upstream, build-context-files, and build-secrets hooks refine the image hook — they run at to_spec() time alongside render(), feeding the build rather than the runtime.

Shipping files your COPY needs. A plugin whose render() emits a COPY should ship that file inside its own package and declare it from get_context_files(), instead of assuming the caller builds from a checkout of the idegym repo. The build backend stages the declared files so COPY resolves from any working directory — see image builder → shipping COPY files and the reference.

The shared base lives in api/src/idegym/api/plugin.py — PluginBase, BuildContext, and both registries (@image_plugin, @server_plugin). The api package is a lightweight dependency the image builder, server, and client all import without creating cycles.

MCP upstream convention​

A plugin that runs an MCP server inside the container declares its URL with get_mcp_upstream(). When Image.to_spec() sees a non-None URL it auto-emits a Dockerfile instruction writing /etc/idegym/mcp-upstreams.d/<name>.json. At runtime the server's MCP gateway discovers those files and proxies each upstream under its stem as a namespace — that's how an IDE's MCP tools surface as pycharm_* / idea_* through the IdeGYM server.

Discovery & configuration​

  • idegym.plugins.image — loaded at import time (registry ready before YAML parsing).
  • idegym.plugins.server — loaded at server startup, filtered by /etc/idegym/plugins.json ({"server": ["tools", "rewards", "pycharm"]}). Absent file ⇒ load everything (development).
  • idegym.plugins.client — loaded per IdeGYMServer instance; failures are isolated per plugin.

Built-in plugins​

DistributionPlugins
idegym-plugins (defaults, always installed)base-system, user, permissions, mcp-upstream, project, idegym-server, tools, rewards
idegym-plugins[pycharm]PyCharm (image) + PyCharmPlugin (server) + PycharmClientOperations (client)
idegym-plugins[idea]Idea (image) + IdeaPlugin (server) + IdeaClientOperations (client)
idegym-plugins[openhands]OpenHands (image) + OpenHandsServerPlugin (server) + OpenHandsClientOperations (client) — agentless OpenHands tools over a loopback service

Baking external IDE plugins​

The Idea and PyCharm image plugins take an ordered external_plugins list to bake extra IntelliJ-platform plugins into the IDE at build time. Each PluginSource names a .zip URL that is downloaded and unzipped into the IDE's bundled plugins directory — the same mechanism that installs mcp-steroid:

from idegym.plugins.plugin_utils import PluginSource
from idegym.plugins.idea.image import Idea

Idea(
external_plugins=(
PluginSource(url="https://example.com/my-plugin.zip"),
PluginSource(url="https://registry.example.com/private.zip", auth_env="MY_TOKEN"),
)
)

For a download behind authentication, set auth_env to the name of a build-time environment variable holding the credential. It is consumed as a build secret (surfaced via get_build_secrets() and sent as an Authorization header), never promoted to an ENV or echoed to the build log, so it does not persist in an image layer.

View source​