Description
Replace the internal plugin framework (mcpgateway/plugins/framework/, mcpgateway/plugins/tools/, plugin_templates/) with the CPEX external package (cpex).
The implementation plan follows.
Phase 0: Preparation (no code changes)
- Install
cpex==0.1.0.dev10 in a test venv and verify the public API surface matches current mcpgateway.plugins.framework exports.
- Run
make test to establish green baseline.
- Run
tests/performance/test_plugins_performance.py to capture baseline numbers.
- Confirm CPEX has:
PluginMode.SEQUENTIAL, PluginMode.TRANSFORM, PluginMode.AUDIT, PluginMode.CONCURRENT, PluginMode.FIRE_AND_FORGET, PluginMode.DISABLED, and OnError.IGNORE.
Gate: All tests green. CPEX API verified.
Phase 1: Add CPEX dependency + compatibility shim
Goal: Add cpex and create re-export shims so all existing imports continue to work unchanged.
Steps
-
pyproject.toml:
- Add
"cpex>=0.1.0.dev10" to dependencies
- Add
"cpex" to known_third_party in [tool.isort] and [tool.ruff.lint.isort]
-
Replace mcpgateway/plugins/framework/ contents with thin shim files that re-export from cpex.framework. Every existing sub-module path (e.g., framework/models.py, framework/hooks/tools.py, framework/external/mcp/server.py) gets a 1-line from cpex.framework.<path> import * file.
-
Keep get_plugin_manager() in mcpgateway/plugins/framework/__init__.py — it imports gateway-side policy.py and can't live in cpex. The shim __init__.py re-exports everything from cpex.framework plus defines get_plugin_manager().
-
Replace mcpgateway/plugins/tools/ with shims re-exporting from cpex.tools.
-
Update mcpgateway/plugins/policy.py import:
from mcpgateway.plugins.framework.hooks.policies import HookPayloadPolicy → from cpex.framework.hooks.policies import HookPayloadPolicy
Gate: make test passes. make flake8 bandit pylint verify clean. Performance tests show no regression.
Phase 2: Migrate PluginMode enums
Goal: Replace old enum values with new CPEX modes throughout.
Mapping
| Old |
New |
ENFORCE |
SEQUENTIAL |
PERMISSIVE |
TRANSFORM |
ENFORCE_IGNORE_ERROR |
SEQUENTIAL + on_error: ignore |
DISABLED |
DISABLED (unchanged) |
Files to update
plugins/config.yaml: All mode values ("enforce" → "sequential", "permissive" → "transform", add on_error: ignore where applicable)
- Plugin source files using
PluginMode directly (e.g., plugins/resource_filter/resource_filter.py)
- Test files referencing old enum values (~19 files in
tests/unit/mcpgateway/plugins/)
- Test fixture plugins in
tests/unit/mcpgateway/plugins/fixtures/
Gate: make test passes. grep -rn 'ENFORCE\|PERMISSIVE\|ENFORCE_IGNORE_ERROR' --include='*.py' mcpgateway/ plugins/ tests/ returns zero hits (excluding comments/docs).
Phase 3: Migrate all imports to cpex
Goal: Replace from mcpgateway.plugins.framework with from cpex.framework everywhere so shims are no longer needed.
Sub-phase 3a: Gateway core (~11 files)
| File |
Key imports |
mcpgateway/main.py |
HttpHookType, PluginError, PluginManager, PluginViolationError |
mcpgateway/config.py |
settings from framework |
mcpgateway/auth.py |
get_plugin_manager, GlobalContext, HTTP payloads |
mcpgateway/services/tool_service.py |
Hook types, payloads, constants |
mcpgateway/services/prompt_service.py |
Hook types, payloads |
mcpgateway/services/resource_service.py |
Hook types, payloads |
mcpgateway/services/plugin_service.py |
PluginManager, PluginMode |
mcpgateway/middleware/http_auth_middleware.py |
HTTP payloads |
mcpgateway/middleware/rbac.py |
get_plugin_manager, HTTP auth payloads |
mcpgateway/middleware/observability_middleware.py |
current_trace_id |
Move get_plugin_manager() from mcpgateway/plugins/framework/__init__.py to mcpgateway/plugins/__init__.py. Update the ~5 callers to from mcpgateway.plugins import get_plugin_manager.
Sub-phase 3b: Plugin source files (~55 files)
Mechanical find-and-replace across all plugins/**/*.py:
from mcpgateway.plugins.framework → from cpex.framework
from mcpgateway.plugins.tools → from cpex.tools
Sub-phase 3c: Plugin docs
Update plugins/README.md and plugins/AGENTS.md code examples.
Gate: make test, make autoflake isort black, make flake8 bandit pylint verify all clean. grep -rn 'from mcpgateway.plugins.framework' mcpgateway/ plugins/ returns zero.
Phase 4: Migrate tests — remove framework internals, add acceptance tests
Sub-phase 4a: Remove framework-internal tests
Delete tests/unit/mcpgateway/plugins/framework/ entirely (~24 test files + subdirs hooks/, loader/, external/). These tests now live in the CPEX repo.
Sub-phase 4b: Create acceptance tests
Create tests/acceptance/plugins/test_cpex_contract.py with:
- API surface tests — verify all symbols the gateway imports from cpex exist and are callable/instantiable.
- Behavioral contract tests — verify:
PluginManager accepts hook_policies parameter
PluginPayload is frozen
PluginMode has SEQUENTIAL, TRANSFORM, AUDIT, CONCURRENT, FIRE_AND_FORGET, DISABLED
OnError has IGNORE
HookPayloadPolicy can be constructed with writable_fields
ObservabilityProvider protocol has expected methods
- Serialization contract tests — verify payload models round-trip correctly.
- Settings compatibility — verify cpex reads
PLUGINS_* env vars.
Sub-phase 4c: Update remaining test imports
tests/unit/mcpgateway/plugins/conftest.py
tests/unit/mcpgateway/plugins/fixtures/plugins/*.py (7 fixture files)
tests/unit/mcpgateway/plugins/plugins/ (30+ plugin test files)
- Service tests (
test_tool_service.py, test_prompt_service.py, test_resource_service.py, test_plugin_service.py)
- Middleware tests (
test_rbac.py, test_http_auth_*.py)
tests/unit/mcpgateway/test_main.py, test_auth.py
tests/unit/mcpgateway/plugins/tools/test_cli.py
tests/performance/test_plugins_performance.py
- Integration tests referencing framework
Gate: make test passes. Performance tests pass. grep -rn 'from mcpgateway.plugins.framework' tests/ returns zero.
Phase 5: Remove shims, internal framework, and plugin_templates
Deletions
mcpgateway/plugins/framework/ — entire directory (48 files)
mcpgateway/plugins/tools/ — entire directory (3 files)
plugin_templates/ — entire directory (moved to CPEX)
Config updates
pyproject.toml: Update CLI entry point mcpplugins to point to cpex.tools.cli:main. Remove framework-related paths from tool configs.
.pre-commit-config.yaml & .pre-commit-lite.yaml: Remove plugin_templates/ from exclude patterns.
.github/workflows/lint.yml: Remove plugin_templates/ exclusions.
CLAUDE.md: Remove plugin_templates/ from project structure, note cpex dependency.
llms/plugins-llms.md: Update import paths.
Verify mcpgateway/plugins/__init__.py contains only get_plugin_manager() factory + re-exports.
Gate: make test, make autoflake isort black pre-commit, make flake8 bandit pylint verify all clean. python -c "from cpex.framework import PluginManager" works. Performance tests pass.
Phase 6: Documentation updates
Files to update (under docs/docs/using/plugins/)
index.md, plugins.md, lifecycle.md, http-auth-hooks.md, mtls.md, grpc-transport.md, unix-socket-transport.md, rust-plugins.md
Do NOT update
docs/docs/architecture/plugins.md (original specification)
Content changes
- All
from mcpgateway.plugins.framework → from cpex.framework
- Document new PluginMode values and execution order
- Document
OnError enum
- Add links to CPEX repo
- Remove
plugin_templates/ references (bootstrap now via CPEX)
- Update example
config.yaml mode values
Gate: Docs build clean. No broken links.
Risk Mitigation
- Shim-first approach (Phase 1): All existing code works before any imports change — key risk reduction.
- Phase-per-commit: Each phase is independently committable and revertable.
- Acceptance tests (Phase 4b): Catch CPEX incompatibilities on future version bumps.
- Version pinning: Start with
cpex>=0.1.0.dev10,<0.2.0 to avoid surprise breaks.
get_plugin_manager() stays gateway-side: It bridges cpex and policy.py — cannot live in cpex.
Key Files Reference
| File |
Role |
mcpgateway/plugins/framework/__init__.py |
Current facade + get_plugin_manager() singleton |
mcpgateway/plugins/__init__.py |
Target home for get_plugin_manager() |
mcpgateway/plugins/policy.py |
Gateway-specific hook payload policies (stays) |
pyproject.toml |
Add cpex dep, update entry points |
plugins/config.yaml |
Plugin config — update mode values |
mcpgateway/services/tool_service.py |
Heaviest framework consumer (pattern for all services) |
tests/unit/mcpgateway/plugins/conftest.py |
Test fixture setup |
Description
Replace the internal plugin framework (
mcpgateway/plugins/framework/,mcpgateway/plugins/tools/,plugin_templates/) with the CPEX external package (cpex).The implementation plan follows.
Phase 0: Preparation (no code changes)
cpex==0.1.0.dev10in a test venv and verify the public API surface matches currentmcpgateway.plugins.frameworkexports.make testto establish green baseline.tests/performance/test_plugins_performance.pyto capture baseline numbers.PluginMode.SEQUENTIAL,PluginMode.TRANSFORM,PluginMode.AUDIT,PluginMode.CONCURRENT,PluginMode.FIRE_AND_FORGET,PluginMode.DISABLED, andOnError.IGNORE.Gate: All tests green. CPEX API verified.
Phase 1: Add CPEX dependency + compatibility shim
Goal: Add
cpexand create re-export shims so all existing imports continue to work unchanged.Steps
pyproject.toml:"cpex>=0.1.0.dev10"todependencies"cpex"toknown_third_partyin[tool.isort]and[tool.ruff.lint.isort]Replace
mcpgateway/plugins/framework/contents with thin shim files that re-export fromcpex.framework. Every existing sub-module path (e.g.,framework/models.py,framework/hooks/tools.py,framework/external/mcp/server.py) gets a 1-linefrom cpex.framework.<path> import *file.Keep
get_plugin_manager()inmcpgateway/plugins/framework/__init__.py— it imports gateway-sidepolicy.pyand can't live in cpex. The shim__init__.pyre-exports everything fromcpex.frameworkplus definesget_plugin_manager().Replace
mcpgateway/plugins/tools/with shims re-exporting fromcpex.tools.Update
mcpgateway/plugins/policy.pyimport:from mcpgateway.plugins.framework.hooks.policies import HookPayloadPolicy→from cpex.framework.hooks.policies import HookPayloadPolicyGate:
make testpasses.make flake8 bandit pylint verifyclean. Performance tests show no regression.Phase 2: Migrate PluginMode enums
Goal: Replace old enum values with new CPEX modes throughout.
Mapping
ENFORCESEQUENTIALPERMISSIVETRANSFORMENFORCE_IGNORE_ERRORSEQUENTIAL+on_error: ignoreDISABLEDDISABLED(unchanged)Files to update
plugins/config.yaml: All mode values ("enforce"→"sequential","permissive"→"transform", addon_error: ignorewhere applicable)PluginModedirectly (e.g.,plugins/resource_filter/resource_filter.py)tests/unit/mcpgateway/plugins/)tests/unit/mcpgateway/plugins/fixtures/Gate:
make testpasses.grep -rn 'ENFORCE\|PERMISSIVE\|ENFORCE_IGNORE_ERROR' --include='*.py' mcpgateway/ plugins/ tests/returns zero hits (excluding comments/docs).Phase 3: Migrate all imports to
cpexGoal: Replace
from mcpgateway.plugins.frameworkwithfrom cpex.frameworkeverywhere so shims are no longer needed.Sub-phase 3a: Gateway core (~11 files)
mcpgateway/main.pyHttpHookType,PluginError,PluginManager,PluginViolationErrormcpgateway/config.pysettingsfrom frameworkmcpgateway/auth.pyget_plugin_manager,GlobalContext, HTTP payloadsmcpgateway/services/tool_service.pymcpgateway/services/prompt_service.pymcpgateway/services/resource_service.pymcpgateway/services/plugin_service.pyPluginManager,PluginModemcpgateway/middleware/http_auth_middleware.pymcpgateway/middleware/rbac.pyget_plugin_manager, HTTP auth payloadsmcpgateway/middleware/observability_middleware.pycurrent_trace_idMove
get_plugin_manager()frommcpgateway/plugins/framework/__init__.pytomcpgateway/plugins/__init__.py. Update the ~5 callers tofrom mcpgateway.plugins import get_plugin_manager.Sub-phase 3b: Plugin source files (~55 files)
Mechanical find-and-replace across all
plugins/**/*.py:from mcpgateway.plugins.framework→from cpex.frameworkfrom mcpgateway.plugins.tools→from cpex.toolsSub-phase 3c: Plugin docs
Update
plugins/README.mdandplugins/AGENTS.mdcode examples.Gate:
make test,make autoflake isort black,make flake8 bandit pylint verifyall clean.grep -rn 'from mcpgateway.plugins.framework' mcpgateway/ plugins/returns zero.Phase 4: Migrate tests — remove framework internals, add acceptance tests
Sub-phase 4a: Remove framework-internal tests
Delete
tests/unit/mcpgateway/plugins/framework/entirely (~24 test files + subdirshooks/,loader/,external/). These tests now live in the CPEX repo.Sub-phase 4b: Create acceptance tests
Create
tests/acceptance/plugins/test_cpex_contract.pywith:PluginManageracceptshook_policiesparameterPluginPayloadis frozenPluginModehasSEQUENTIAL,TRANSFORM,AUDIT,CONCURRENT,FIRE_AND_FORGET,DISABLEDOnErrorhasIGNOREHookPayloadPolicycan be constructed withwritable_fieldsObservabilityProviderprotocol has expected methodsPLUGINS_*env vars.Sub-phase 4c: Update remaining test imports
tests/unit/mcpgateway/plugins/conftest.pytests/unit/mcpgateway/plugins/fixtures/plugins/*.py(7 fixture files)tests/unit/mcpgateway/plugins/plugins/(30+ plugin test files)test_tool_service.py,test_prompt_service.py,test_resource_service.py,test_plugin_service.py)test_rbac.py,test_http_auth_*.py)tests/unit/mcpgateway/test_main.py,test_auth.pytests/unit/mcpgateway/plugins/tools/test_cli.pytests/performance/test_plugins_performance.pyGate:
make testpasses. Performance tests pass.grep -rn 'from mcpgateway.plugins.framework' tests/returns zero.Phase 5: Remove shims, internal framework, and plugin_templates
Deletions
mcpgateway/plugins/framework/— entire directory (48 files)mcpgateway/plugins/tools/— entire directory (3 files)plugin_templates/— entire directory (moved to CPEX)Config updates
pyproject.toml: Update CLI entry pointmcppluginsto point tocpex.tools.cli:main. Remove framework-related paths from tool configs..pre-commit-config.yaml&.pre-commit-lite.yaml: Removeplugin_templates/from exclude patterns..github/workflows/lint.yml: Removeplugin_templates/exclusions.CLAUDE.md: Removeplugin_templates/from project structure, note cpex dependency.llms/plugins-llms.md: Update import paths.Verify
mcpgateway/plugins/__init__.pycontains onlyget_plugin_manager()factory + re-exports.Gate:
make test,make autoflake isort black pre-commit,make flake8 bandit pylint verifyall clean.python -c "from cpex.framework import PluginManager"works. Performance tests pass.Phase 6: Documentation updates
Files to update (under
docs/docs/using/plugins/)index.md,plugins.md,lifecycle.md,http-auth-hooks.md,mtls.md,grpc-transport.md,unix-socket-transport.md,rust-plugins.mdDo NOT update
docs/docs/architecture/plugins.md(original specification)Content changes
from mcpgateway.plugins.framework→from cpex.frameworkOnErrorenumplugin_templates/references (bootstrap now via CPEX)config.yamlmode valuesGate: Docs build clean. No broken links.
Risk Mitigation
cpex>=0.1.0.dev10,<0.2.0to avoid surprise breaks.get_plugin_manager()stays gateway-side: It bridges cpex andpolicy.py— cannot live in cpex.Key Files Reference
mcpgateway/plugins/framework/__init__.pyget_plugin_manager()singletonmcpgateway/plugins/__init__.pyget_plugin_manager()mcpgateway/plugins/policy.pypyproject.tomlplugins/config.yamlmcpgateway/services/tool_service.pytests/unit/mcpgateway/plugins/conftest.py