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
| Hook | How you opt in | Where it's consumed | What you get |
|---|---|---|---|
| Image | @image_plugin("name") + apply() / render() | Image.to_spec() (image builder) | A Dockerfile fragment |
| MCP upstream | get_mcp_upstream() on the image plugin | Image.to_spec() (auto-writes config) | /etc/idegym/mcp-upstreams.d/<name>.json |
| Build context files | get_context_files() on the image plugin | Image.to_spec() → the build backend | Bundled files staged so your COPY sources resolve |
| Build secrets | get_build_secrets() on the image plugin | Image.to_spec() → the build backend | Build-arg names forwarded as secrets (kept out of image layers) |
| Server | @server_plugin + get_server_router() | server/main.py at startup | An APIRouter at /api/<plugin>/* |
| Client | idegym.plugins.client entry point | IdeGYMServer.__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
COPYneeds. A plugin whoserender()emits aCOPYshould ship that file inside its own package and declare it fromget_context_files(), instead of assuming the caller builds from a checkout of the idegym repo. The build backend stages the declared files soCOPYresolves 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 perIdeGYMServerinstance; failures are isolated per plugin.
Built-in plugins
| Distribution | Plugins |
|---|---|
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
- Plugin base & registries →
api/src/idegym/api/plugin.py - Built-in defaults →
plugins/defaults/ - PyCharm / IDEA extras →
plugins/pycharm/·plugins/idea/ - Shared helpers (external plugins, packaged assets) →
plugins/plugin-utils/ - Authoring guide → Plugin Architecture docs