Skip to content

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

Open
araujof wants to merge 13 commits intomainfrom
refactor/plugins_settings
Open

refactor(plugins): decouple plugin framework from gateway settings#2829
araujof wants to merge 13 commits intomainfrom
refactor/plugins_settings

Conversation

@araujof
Copy link
Member

@araujof araujof commented Feb 11, 2026

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

@araujof araujof marked this pull request as draft February 11, 2026 06:26
@araujof araujof 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 11, 2026
@araujof araujof force-pushed the refactor/plugins_settings branch from 420f906 to f0dd01b Compare February 11, 2026 06:31
@crivetimihai crivetimihai added this to the Release 1.0.0-GA milestone Feb 11, 2026
@araujof araujof marked this pull request as ready for review February 11, 2026 21:19
@araujof araujof force-pushed the refactor/plugins_settings branch 2 times, most recently from 479fe8e to 9b4721b Compare February 12, 2026 17:49
@araujof araujof requested a review from terylt February 13, 2026 02:20
Copy link
Member

@crivetimihai crivetimihai left a comment

Choose a reason for hiding this comment

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

Thanks for this refactor, @araujof. Centralizing scattered os.environ.get calls into a typed PluginsSettings class is a solid improvement. A few issues:

1. LazySettingsWrapper.enabled bypasses pydantic-settings (settings.py:153-162)
The enabled property reads os.getenv("PLUGINS_ENABLED") directly and falls back to False. This skips pydantic-settings' .env file loading. If a user sets PLUGINS_ENABLED=true only in .env (not exported), os.getenv returns None and the property returns False, while PluginsSettings().enabled would return True. The property should delegate to get_settings().enabled like __getattr__ does for all other attributes.

2. from_env() methods create redundant PluginsSettings() instances
Every from_env() call (~7 call sites) creates a new PluginsSettings(), re-parsing env vars each time. The module provides get_settings() with @lru_cache specifically for this — use it instead.

3. Tests are thorough — good coverage of defaults, env overrides, alias compatibility, and singleton stability.

@araujof
Copy link
Member Author

araujof commented Feb 13, 2026

Thanks for this refactor, @araujof. Centralizing scattered os.environ.get calls into a typed PluginsSettings class is a solid improvement. A few issues:

1. LazySettingsWrapper.enabled bypasses pydantic-settings (settings.py:153-162) The enabled property reads os.getenv("PLUGINS_ENABLED") directly and falls back to False. This skips pydantic-settings' .env file loading. If a user sets PLUGINS_ENABLED=true only in .env (not exported), os.getenv returns None and the property returns False, while PluginsSettings().enabled would return True. The property should delegate to get_settings().enabled like __getattr__ does for all other attributes.

2. from_env() methods create redundant PluginsSettings() instances Every from_env() call (~7 call sites) creates a new PluginsSettings(), re-parsing env vars each time. The module provides get_settings() with @lru_cache specifically for this — use it instead.

3. Tests are thorough — good coverage of defaults, env overrides, alias compatibility, and singleton stability.

Good catch. Fixing bugs.

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>
@araujof araujof force-pushed the refactor/plugins_settings branch from 9b4721b to 2263dac Compare February 14, 2026 03:49
@araujof araujof requested a review from crivetimihai February 14, 2026 03:56
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
@araujof
Copy link
Member Author

araujof commented Feb 14, 2026

@crivetimihai I rebased from main. Here is a summary of changes:

  1. Fixed the issue in LazySettingsWrapper.enabled . Thanks for catching that!

  2. from_env() is supposed to read from env. This was the original semantics before the refactoring. Resetting the LRU cache would have the same effect as instantiating a new pydantic settings. Modifying the models from_env() to use get_settings() will have some important cascading effects and break a lot of unit tests. We should be okay to reload env for these convenience methods. Let me know otherwise. Fixed the unit tests to reset the cache before tests. One caveat is that the conftest's clear_plugins_settings_cache has to explicit declare a dependency on reset_plugin_manager_state to avoid chaining a read of the environment via settings (through PluginManager initialization in refactor(plugins): decouple plugin framework data models #2895), which would cause some tests expecting an empty settings to fail. With the new changes, the from_env adapters use the cached settings.

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>
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