refactor(plugins): decouple plugin framework from gateway settings#3141
Merged
refactor(plugins): decouple plugin framework from gateway settings#3141
Conversation
af5e7a9 to
9ac924d
Compare
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
- Comment out PLUGINS_CONFIG_FILE default in values.yaml so legacy PLUGIN_CONFIG_FILE overrides are not shadowed by coalesce/AliasChoices - Return PluginsSettings from config.py plugins property (not the LazySettingsWrapper proxy) to match the type annotation - Use SecretStr for keyfile_password fields and .get_secret_value() in from_env adapters - Switch runtime servers from PluginsSettings() to get_settings() for cached singleton access - Simplify get_settings to no-arg lru_cache(maxsize=1) - Remove LazySettingsWrapper.enabled os.getenv bypass - Fix AliasChoices priority to prefer PLUGINS_ prefixed names - Add env_file and extra=ignore to model_config - Simplify test_tool_service_coverage to rely on autouse cache_clear Signed-off-by: Jonathan Springer <jps@s390x.com>
9ac924d to
43674d5
Compare
The get_plugin_manager() function accepted an observability parameter but silently discarded it after the settings migration. Also updates the observability test to patch the new framework settings instead of the removed gateway settings fields. Signed-off-by: Jonathan Springer <jps@s390x.com>
jonpspri
approved these changes
Feb 24, 2026
Collaborator
jonpspri
left a comment
There was a problem hiding this comment.
Reviewed the full changeset. The decoupling of plugin framework settings from the gateway config is well-structured:
PluginsSettingswith pydantic-settings andPLUGINS_prefix is clean and type-safeLazySettingsWrapperproxy avoids circular imports via deferred import — confirmed no circular import riskfrom_env()migrations are consistent across all 7 model classes- Backwards compatibility for
PLUGIN_CONFIG_FILEviaAliasChoicesis properly handled - Helm
coalescefallback chain (new → legacy → default) is correct - Test coverage is thorough with 23 new settings tests
One fix was applied in 81c5984: get_plugin_manager() was silently dropping the observability parameter after the migration, and test_observability.py was patching the removed gateway settings fields. Both are now corrected.
Minor notes for future consideration (non-blocking):
ENABLE_METRICSinmcp/server/runtime.pystill readsos.getenvdirectly — could be folded intoPluginsSettingsfor consistencyplugin_timeoutfield is defined inPluginsSettingsbut not wired intoPluginManagersettingsvsget_settings()usage is mixed across modules — consider standardizing
vishu-bh
pushed a commit
that referenced
this pull request
Feb 25, 2026
…3141) * refactor(plugins): decouple plugin framework from gateway settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: variable expansion in clean target Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: additional cleanups and documentation Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warning Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warnings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: plugin settings env override Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: minor issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * tests: update conftest Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: from_env should read from environment Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: expose cache_clear through settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: use cached settings in from_env adapters Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: preserve Helm backward compat and harden plugin settings - Comment out PLUGINS_CONFIG_FILE default in values.yaml so legacy PLUGIN_CONFIG_FILE overrides are not shadowed by coalesce/AliasChoices - Return PluginsSettings from config.py plugins property (not the LazySettingsWrapper proxy) to match the type annotation - Use SecretStr for keyfile_password fields and .get_secret_value() in from_env adapters - Switch runtime servers from PluginsSettings() to get_settings() for cached singleton access - Simplify get_settings to no-arg lru_cache(maxsize=1) - Remove LazySettingsWrapper.enabled os.getenv bypass - Fix AliasChoices priority to prefer PLUGINS_ prefixed names - Add env_file and extra=ignore to model_config - Simplify test_tool_service_coverage to rely on autouse cache_clear Signed-off-by: Jonathan Springer <jps@s390x.com> * fix: pass observability provider to PluginManager and update test The get_plugin_manager() function accepted an observability parameter but silently discarded it after the settings migration. Also updates the observability test to patch the new framework settings instead of the removed gateway settings fields. Signed-off-by: Jonathan Springer <jps@s390x.com> --------- Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Frederico Araujo <frederico.araujo@ibm.com> Co-authored-by: Jonathan Springer <jps@s390x.com>
vishu-bh
pushed a commit
that referenced
this pull request
Feb 25, 2026
…3141) * refactor(plugins): decouple plugin framework from gateway settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: variable expansion in clean target Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: additional cleanups and documentation Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warning Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warnings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: plugin settings env override Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: minor issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * tests: update conftest Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: from_env should read from environment Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: expose cache_clear through settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: use cached settings in from_env adapters Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: preserve Helm backward compat and harden plugin settings - Comment out PLUGINS_CONFIG_FILE default in values.yaml so legacy PLUGIN_CONFIG_FILE overrides are not shadowed by coalesce/AliasChoices - Return PluginsSettings from config.py plugins property (not the LazySettingsWrapper proxy) to match the type annotation - Use SecretStr for keyfile_password fields and .get_secret_value() in from_env adapters - Switch runtime servers from PluginsSettings() to get_settings() for cached singleton access - Simplify get_settings to no-arg lru_cache(maxsize=1) - Remove LazySettingsWrapper.enabled os.getenv bypass - Fix AliasChoices priority to prefer PLUGINS_ prefixed names - Add env_file and extra=ignore to model_config - Simplify test_tool_service_coverage to rely on autouse cache_clear Signed-off-by: Jonathan Springer <jps@s390x.com> * fix: pass observability provider to PluginManager and update test The get_plugin_manager() function accepted an observability parameter but silently discarded it after the settings migration. Also updates the observability test to patch the new framework settings instead of the removed gateway settings fields. Signed-off-by: Jonathan Springer <jps@s390x.com> --------- Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Frederico Araujo <frederico.araujo@ibm.com> Co-authored-by: Jonathan Springer <jps@s390x.com>
MohanLaksh
pushed a commit
that referenced
this pull request
Mar 12, 2026
…3141) * refactor(plugins): decouple plugin framework from gateway settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: variable expansion in clean target Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: additional cleanups and documentation Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warning Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: mypy warnings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: plugin settings env override Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: minor issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * tests: update conftest Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: from_env should read from environment Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: expose cache_clear through settings Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * feat: use cached settings in from_env adapters Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * chore: fix lint issues Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> * fix: preserve Helm backward compat and harden plugin settings - Comment out PLUGINS_CONFIG_FILE default in values.yaml so legacy PLUGIN_CONFIG_FILE overrides are not shadowed by coalesce/AliasChoices - Return PluginsSettings from config.py plugins property (not the LazySettingsWrapper proxy) to match the type annotation - Use SecretStr for keyfile_password fields and .get_secret_value() in from_env adapters - Switch runtime servers from PluginsSettings() to get_settings() for cached singleton access - Simplify get_settings to no-arg lru_cache(maxsize=1) - Remove LazySettingsWrapper.enabled os.getenv bypass - Fix AliasChoices priority to prefer PLUGINS_ prefixed names - Add env_file and extra=ignore to model_config - Simplify test_tool_service_coverage to rely on autouse cache_clear Signed-off-by: Jonathan Springer <jps@s390x.com> * fix: pass observability provider to PluginManager and update test The get_plugin_manager() function accepted an observability parameter but silently discarded it after the settings migration. Also updates the observability test to patch the new framework settings instead of the removed gateway settings fields. Signed-off-by: Jonathan Springer <jps@s390x.com> --------- Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Frederico Araujo <frederico.araujo@ibm.com> Co-authored-by: Jonathan Springer <jps@s390x.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Introduces a self-contained
PluginsSettingsconfiguration class for the plugin framework, eliminating allos.environreads and imports frommcpgateway.config.settingsandmcpgateway.services.http_client_service. The plugin framework now owns its configuration and can operate independently of the gateway.Closes: #2831
Problem
Multiple plugin framework modules imported
settingsfrommcpgateway.config, the MCP client imported HTTP helpers frommcpgateway.services.http_client_service, and everyfrom_env()factory method inmodels.pyreados.environdirectly with hand-rolled boolean parsing. This created tight coupling from the plugin framework back into the gateway — the opposite of the desired direction for ADR-019 modular architecture.Solution
mcpgateway/plugins/framework/settings.pywith aPluginsSettingsclass usingpydantic_settingsand thePLUGINS_env var prefixPluginsSettings: core config, HTTP client tuning, SSL/TLS (mTLS client, server SSL, gRPC client/server TLS), server bind, transport runtime, Unix socket, and CLI optionsget_http_timeout()andget_default_verify()calls in the MCP client with direct usage ofsettings.httpx_*andsettings.skip_ssl_verifyfrom_env()factory methods inmodels.py(MCPClientTLSConfig,MCPServerTLSConfig,MCPServerConfig,GRPCClientTLSConfig,GRPCServerTLSConfig,GRPCServerConfig,UnixSocketServerConfig) from rawos.environreads toPluginsSettings_parse_bool()static methods fromMCPTransportTLSConfigBaseandMCPServerConfig(pydantic handles type coercion)get_plugin_manager(),cli.py, and all external server runtimes (MCP, gRPC, Unix) to use the framework's own settingssettings.pluginsproperty onLazySettingsWrapperso gateway code accesses plugin settings viasettings.plugins.enabledinstead ofsettings.plugins_enabledmain.py,admin.py,prompt_service.py,resource_service.py,tool_service.py) by replacing manualos.getenv("PLUGINS_ENABLED")parsing withsettings.plugins.enabledplugins_enabledandplugin_config_filefields from gatewaySettingsclassPYTHONconstant fromconstants.pyPLUGIN_CONFIG_FILE→PLUGINS_CONFIG_FILE(legacy name supported viaAliasChoicesfor backwards compatibility)Configuration mapping
Field names are chosen so that the
PLUGINS_env prefix produces clean env var names (e.g., fieldenabled→PLUGINS_ENABLED).PLUGINS_ENABLEDPLUGINS_ENABLEDfalsePLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILEplugins/config.yamlHTTPX_CONNECT_TIMEOUTPLUGINS_HTTPX_CONNECT_TIMEOUT5.0HTTPX_READ_TIMEOUTPLUGINS_HTTPX_READ_TIMEOUT120.0SKIP_SSL_VERIFYPLUGINS_SKIP_SSL_VERIFYfalsePLUGINS_CLI_COMPLETIONPLUGINS_CLI_COMPLETIONfalsePLUGINS_CLI_MARKUP_MODEPLUGINS_CLI_MARKUP_MODEPLUGINS_*)PluginsSettingsfor full list: mTLS, server SSL, gRPC TLS, server bind, transport, Unix socket)Changes
Core
mcpgateway/plugins/framework/settings.pyPluginsSettingsclass withPLUGINS_env prefix covering all plugin framework configuration (HTTP client, TLS/mTLS, server bind, transport, CLI)mcpgateway/plugins/framework/models.pyfrom_env()factory methods fromos.environreads toPluginsSettings; removed duplicate_parse_bool()methodsmcpgateway/plugins/framework/__init__.pyget_plugin_manager()usesmcpgateway.plugins.framework.settingsinstead ofmcpgateway.configmcpgateway/plugins/framework/external/mcp/client.pymcpgateway.config.settingsandmcpgateway.services.http_client_serviceimports with framework settings;httpx.Timeoutbuilt fromsettings.httpx_*valuesmcpgateway/plugins/framework/constants.pyPYTHONconstantmcpgateway/plugins/tools/cli.pymcpgateway.config.settingswithmcpgateway.plugins.framework.settingsExternal server runtimes
mcpgateway/plugins/framework/external/mcp/server/runtime.pyPLUGINS_TRANSPORTenv var read replaced withPluginsSettings().transportmcpgateway/plugins/framework/external/mcp/server/server.pyPLUGINS_CONFIG_PATHenv var read replaced withPluginsSettings().config_pathmcpgateway/plugins/framework/external/grpc/server/runtime.pyPLUGINS_CONFIG_PATHenv var read replaced withPluginsSettings().config_pathmcpgateway/plugins/framework/external/unix/server/runtime.pyPLUGINS_CONFIG_PATHandUNIX_SOCKET_PATHenv var reads replaced withPluginsSettings()Gateway integration
mcpgateway/config.pyplugins_enabledandplugin_config_filefields fromSettings; addedsettings.pluginsproperty onLazySettingsWrappermcpgateway/main.pyos.getenv("PLUGINS_ENABLED")parsing withsettings.plugins.enabled/settings.plugins.config_filemcpgateway/admin.pysettings.plugins_enabled→settings.plugins.enabledmcpgateway/services/prompt_service.pysettings.plugins.enabled/settings.plugins.config_filemcpgateway/services/resource_service.pysettings.plugins.enabled/settings.plugins.config_filemcpgateway/services/tool_service.pysettings.plugins.enabled/settings.plugins.config_filemcpgateway/tools/builder/common.pyPLUGIN_CONFIG_FILE→PLUGINS_CONFIG_FILEin K8s manifest generationDocumentation and configuration
.env.examplePLUGINS_*env vars into a single "Plugin Framework Settings" section with descriptionsdocs/docs/manage/configuration.mddocs/docs/using/plugins/index.mdPLUGINS_CONFIG_FILE; added standalone settings notedocs/docs/using/plugins/grpc-transport.mdPLUGINS_CONFIG_FILEalongsidePLUGIN_CONFIG_FILEdocs/docs/architecture/adr/019-modular-architecture-split.mdPLUGINS_CONFIG_FILEcharts/mcp-stack/values.yamlPLUGIN_CONFIG_FILE→PLUGINS_CONFIG_FILE; added standalone framework settingscharts/mcp-stack/templates/deployment-mcpgateway.yamlPLUGINS_CONFIG_FILEwith fallbackcharts/mcp-stack/README.mdplugins/AGENTS.mdPLUGIN_CONFIG_FILE→PLUGINS_CONFIG_FILEplugins/README.mdPLUGINS_CONFIG_FILEAGENTS.mdPLUGIN_CONFIG_FILE→PLUGINS_CONFIG_FILETests
tests/unit/.../framework/test_settings.pyPLUGINS_-prefixed env var overrides, legacy alias (PLUGIN_CONFIG_FILE), module singleton stabilitytests/unit/.../framework/test_plugin_models.pyfrom_env()tests to usePLUGINS_-prefixed env vars viaPluginsSettingstests/unit/.../framework/test_plugin_models_coverage.pyfrom_env()tests forPluginsSettingsmigrationtests/unit/.../grpc/test_grpc_models.pyfrom_env()tests forPluginsSettingsmigrationtests/unit/.../services/test_tool_service_coverage.pyPluginsSettingssingletontests/unit/.../services/test_resource_service_plugins.pyPluginsSettingssingletontests/unit/.../integration/test_cross_hook_context_sharing.pytests/unit/.../integration/test_resource_plugin_integration.pytests/unit/.../middleware/test_http_auth_integration.py