Skip to content

refactor(plugins): decouple plugin framework from gateway settings#3141

Merged
jonpspri merged 15 commits intomainfrom
refactor/plugins_settings
Feb 24, 2026
Merged

refactor(plugins): decouple plugin framework from gateway settings#3141
jonpspri merged 15 commits intomainfrom
refactor/plugins_settings

Conversation

@crivetimihai
Copy link
Copy Markdown
Member

Note: This PR was re-created from #2829 due to repository maintenance. Your code and branch are intact. @araujof please verify everything looks good.

Description

Introduces a self-contained PluginsSettings configuration class for the plugin framework, eliminating all os.environ reads and imports from mcpgateway.config.settings and mcpgateway.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 settings from mcpgateway.config, the MCP client imported HTTP helpers from mcpgateway.services.http_client_service, and every from_env() factory method in models.py read os.environ directly 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

  • Created mcpgateway/plugins/framework/settings.py with a PluginsSettings class using pydantic_settings and the PLUGINS_ env var prefix
  • Consolidated all plugin-related env var reads into PluginsSettings: core config, HTTP client tuning, SSL/TLS (mTLS client, server SSL, gRPC client/server TLS), server bind, transport runtime, Unix socket, and CLI options
  • Replaced get_http_timeout() and get_default_verify() calls in the MCP client with direct usage of settings.httpx_* and settings.skip_ssl_verify
  • Migrated all from_env() factory methods in models.py (MCPClientTLSConfig, MCPServerTLSConfig, MCPServerConfig, GRPCClientTLSConfig, GRPCServerTLSConfig, GRPCServerConfig, UnixSocketServerConfig) from raw os.environ reads to PluginsSettings
  • Removed duplicate _parse_bool() static methods from MCPTransportTLSConfigBase and MCPServerConfig (pydantic handles type coercion)
  • Updated get_plugin_manager(), cli.py, and all external server runtimes (MCP, gRPC, Unix) to use the framework's own settings
  • Added settings.plugins property on LazySettingsWrapper so gateway code accesses plugin settings via settings.plugins.enabled instead of settings.plugins_enabled
  • Simplified gateway services (main.py, admin.py, prompt_service.py, resource_service.py, tool_service.py) by replacing manual os.getenv("PLUGINS_ENABLED") parsing with settings.plugins.enabled
  • Removed plugins_enabled and plugin_config_file fields from gateway Settings class
  • Removed unused PYTHON constant from constants.py
  • Standardized env var naming: PLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILE (legacy name supported via AliasChoices for backwards compatibility)

Configuration mapping

Field names are chosen so that the PLUGINS_ env prefix produces clean env var names (e.g., field enabledPLUGINS_ENABLED).

Gateway env var Plugin framework env var Default Notes
PLUGINS_ENABLED PLUGINS_ENABLED false Shared
PLUGIN_CONFIG_FILE PLUGINS_CONFIG_FILE plugins/config.yaml Legacy alias supported
HTTPX_CONNECT_TIMEOUT PLUGINS_HTTPX_CONNECT_TIMEOUT 5.0 Scoped to plugins
HTTPX_READ_TIMEOUT PLUGINS_HTTPX_READ_TIMEOUT 120.0 Scoped to plugins
SKIP_SSL_VERIFY PLUGINS_SKIP_SSL_VERIFY false Scoped to plugins
PLUGINS_CLI_COMPLETION PLUGINS_CLI_COMPLETION false Shared
PLUGINS_CLI_MARKUP_MODE PLUGINS_CLI_MARKUP_MODE (none) Shared
(various PLUGINS_*) (see PluginsSettings for full list: mTLS, server SSL, gRPC TLS, server bind, transport, Unix socket)

Changes

Core

File Change
mcpgateway/plugins/framework/settings.py NewPluginsSettings class with PLUGINS_ env prefix covering all plugin framework configuration (HTTP client, TLS/mTLS, server bind, transport, CLI)
mcpgateway/plugins/framework/models.py Migrated all 7 from_env() factory methods from os.environ reads to PluginsSettings; removed duplicate _parse_bool() methods
mcpgateway/plugins/framework/__init__.py get_plugin_manager() uses mcpgateway.plugins.framework.settings instead of mcpgateway.config
mcpgateway/plugins/framework/external/mcp/client.py Replaced mcpgateway.config.settings and mcpgateway.services.http_client_service imports with framework settings; httpx.Timeout built from settings.httpx_* values
mcpgateway/plugins/framework/constants.py Removed unused PYTHON constant
mcpgateway/plugins/tools/cli.py Replaced mcpgateway.config.settings with mcpgateway.plugins.framework.settings

External server runtimes

File Change
mcpgateway/plugins/framework/external/mcp/server/runtime.py PLUGINS_TRANSPORT env var read replaced with PluginsSettings().transport
mcpgateway/plugins/framework/external/mcp/server/server.py PLUGINS_CONFIG_PATH env var read replaced with PluginsSettings().config_path
mcpgateway/plugins/framework/external/grpc/server/runtime.py PLUGINS_CONFIG_PATH env var read replaced with PluginsSettings().config_path
mcpgateway/plugins/framework/external/unix/server/runtime.py PLUGINS_CONFIG_PATH and UNIX_SOCKET_PATH env var reads replaced with PluginsSettings()

Gateway integration

File Change
mcpgateway/config.py Removed plugins_enabled and plugin_config_file fields from Settings; added settings.plugins property on LazySettingsWrapper
mcpgateway/main.py Replaced manual os.getenv("PLUGINS_ENABLED") parsing with settings.plugins.enabled / settings.plugins.config_file
mcpgateway/admin.py settings.plugins_enabledsettings.plugins.enabled
mcpgateway/services/prompt_service.py Replaced manual env var parsing with settings.plugins.enabled / settings.plugins.config_file
mcpgateway/services/resource_service.py Replaced manual env var parsing with settings.plugins.enabled / settings.plugins.config_file
mcpgateway/services/tool_service.py Replaced manual env var parsing with settings.plugins.enabled / settings.plugins.config_file
mcpgateway/tools/builder/common.py PLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILE in K8s manifest generation

Documentation and configuration

File Change
.env.example Consolidated all PLUGINS_* env vars into a single "Plugin Framework Settings" section with descriptions
docs/docs/manage/configuration.md Added "Plugin Framework (Standalone) Settings" reference table
docs/docs/using/plugins/index.md Updated quick-start with PLUGINS_CONFIG_FILE; added standalone settings note
docs/docs/using/plugins/grpc-transport.md Added PLUGINS_CONFIG_FILE alongside PLUGIN_CONFIG_FILE
docs/docs/architecture/adr/019-modular-architecture-split.md Added PLUGINS_CONFIG_FILE
charts/mcp-stack/values.yaml Renamed PLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILE; added standalone framework settings
charts/mcp-stack/templates/deployment-mcpgateway.yaml Updated volume mount to prefer PLUGINS_CONFIG_FILE with fallback
charts/mcp-stack/README.md Updated Helm values table
plugins/AGENTS.md PLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILE
plugins/README.md Added PLUGINS_CONFIG_FILE
AGENTS.md PLUGIN_CONFIG_FILEPLUGINS_CONFIG_FILE

Tests

File Change
tests/unit/.../framework/test_settings.py New — 23 tests: default values, PLUGINS_-prefixed env var overrides, legacy alias (PLUGIN_CONFIG_FILE), module singleton stability
tests/unit/.../framework/test_plugin_models.py Updated from_env() tests to use PLUGINS_-prefixed env vars via PluginsSettings
tests/unit/.../framework/test_plugin_models_coverage.py Updated from_env() tests for PluginsSettings migration
tests/unit/.../grpc/test_grpc_models.py Updated gRPC from_env() tests for PluginsSettings migration
tests/unit/.../services/test_tool_service_coverage.py Updated plugin-enabled tests to monkeypatch PluginsSettings singleton
tests/unit/.../services/test_resource_service_plugins.py Updated plugin-enabled tests to monkeypatch PluginsSettings singleton
tests/unit/.../integration/test_cross_hook_context_sharing.py Minor import adjustment
tests/unit/.../integration/test_resource_plugin_integration.py Minor import adjustment
tests/unit/.../middleware/test_http_auth_integration.py Minor import adjustment

@crivetimihai crivetimihai added this to the Release 1.0.0-GA milestone Feb 24, 2026
@crivetimihai crivetimihai added enhancement New feature or request plugins SHOULD P2: Important but not vital; high-value items that are not crucial for the immediate release labels Feb 24, 2026
@jonpspri jonpspri force-pushed the refactor/plugins_settings branch from af5e7a9 to 9ac924d Compare February 24, 2026 18:48
araujof and others added 14 commits February 24, 2026 20:48
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>
@jonpspri jonpspri force-pushed the refactor/plugins_settings branch from 9ac924d to 43674d5 Compare February 24, 2026 20:48
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>
Copy link
Copy Markdown
Collaborator

@jonpspri jonpspri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full changeset. The decoupling of plugin framework settings from the gateway config is well-structured:

  • PluginsSettings with pydantic-settings and PLUGINS_ prefix is clean and type-safe
  • LazySettingsWrapper proxy avoids circular imports via deferred import — confirmed no circular import risk
  • from_env() migrations are consistent across all 7 model classes
  • Backwards compatibility for PLUGIN_CONFIG_FILE via AliasChoices is properly handled
  • Helm coalesce fallback 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_METRICS in mcp/server/runtime.py still reads os.getenv directly — could be folded into PluginsSettings for consistency
  • plugin_timeout field is defined in PluginsSettings but not wired into PluginManager
  • settings vs get_settings() usage is mixed across modules — consider standardizing

@jonpspri jonpspri merged commit 34a5369 into main Feb 24, 2026
70 checks passed
@jonpspri jonpspri deleted the refactor/plugins_settings branch February 24, 2026 21:41
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request plugins SHOULD P2: Important but not vital; high-value items that are not crucial for the immediate release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Create plugin framework settings

3 participants