omnibase_infra 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
  3. omnibase_infra/capabilities/__init__.py +15 -0
  4. omnibase_infra/capabilities/capability_inference_rules.py +211 -0
  5. omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
  6. omnibase_infra/capabilities/intent_type_extractor.py +160 -0
  7. omnibase_infra/cli/commands.py +1 -1
  8. omnibase_infra/configs/widget_mapping.yaml +176 -0
  9. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
  10. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
  11. omnibase_infra/enums/__init__.py +6 -0
  12. omnibase_infra/enums/enum_handler_error_type.py +10 -0
  13. omnibase_infra/enums/enum_handler_source_mode.py +72 -0
  14. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  15. omnibase_infra/errors/error_compute_registry.py +4 -1
  16. omnibase_infra/errors/error_event_bus_registry.py +4 -1
  17. omnibase_infra/errors/error_infra.py +3 -1
  18. omnibase_infra/errors/error_policy_registry.py +4 -1
  19. omnibase_infra/event_bus/event_bus_kafka.py +1 -1
  20. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
  21. omnibase_infra/handlers/__init__.py +8 -1
  22. omnibase_infra/handlers/handler_consul.py +7 -1
  23. omnibase_infra/handlers/handler_db.py +10 -3
  24. omnibase_infra/handlers/handler_graph.py +10 -5
  25. omnibase_infra/handlers/handler_http.py +8 -2
  26. omnibase_infra/handlers/handler_intent.py +387 -0
  27. omnibase_infra/handlers/handler_mcp.py +745 -63
  28. omnibase_infra/handlers/handler_vault.py +11 -5
  29. omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
  30. omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
  31. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
  32. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
  33. omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
  34. omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
  35. omnibase_infra/mixins/mixin_node_introspection.py +42 -7
  36. omnibase_infra/mixins/mixin_retry_execution.py +1 -1
  37. omnibase_infra/models/discovery/model_introspection_config.py +11 -0
  38. omnibase_infra/models/handlers/__init__.py +48 -5
  39. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  40. omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
  41. omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
  42. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  43. omnibase_infra/models/mcp/__init__.py +15 -0
  44. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  45. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  46. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  47. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  48. omnibase_infra/models/registration/model_node_capabilities.py +11 -0
  49. omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
  50. omnibase_infra/models/runtime/model_handler_contract.py +25 -9
  51. omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
  52. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
  53. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
  54. omnibase_infra/nodes/effects/contract.yaml +0 -5
  55. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
  56. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
  57. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
  58. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
  59. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
  60. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
  61. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
  62. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
  63. omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
  64. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
  65. omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
  66. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
  67. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
  68. omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
  69. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
  70. omnibase_infra/plugins/plugin_compute_base.py +16 -2
  71. omnibase_infra/protocols/__init__.py +2 -0
  72. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  73. omnibase_infra/protocols/protocol_event_projector.py +1 -1
  74. omnibase_infra/runtime/__init__.py +90 -1
  75. omnibase_infra/runtime/binding_config_resolver.py +102 -37
  76. omnibase_infra/runtime/constants_notification.py +75 -0
  77. omnibase_infra/runtime/contract_handler_discovery.py +6 -1
  78. omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
  79. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  80. omnibase_infra/runtime/handler_contract_source.py +267 -186
  81. omnibase_infra/runtime/handler_identity.py +81 -0
  82. omnibase_infra/runtime/handler_plugin_loader.py +19 -2
  83. omnibase_infra/runtime/handler_registry.py +11 -3
  84. omnibase_infra/runtime/handler_source_resolver.py +326 -0
  85. omnibase_infra/runtime/mixin_semver_cache.py +25 -1
  86. omnibase_infra/runtime/mixins/__init__.py +7 -0
  87. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  88. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
  89. omnibase_infra/runtime/models/__init__.py +24 -0
  90. omnibase_infra/runtime/models/model_health_check_result.py +2 -1
  91. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  92. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  93. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  94. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  95. omnibase_infra/runtime/projector_plugin_loader.py +1 -1
  96. omnibase_infra/runtime/projector_shell.py +229 -1
  97. omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
  98. omnibase_infra/runtime/protocols/__init__.py +10 -0
  99. omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
  100. omnibase_infra/runtime/registry_contract_source.py +693 -0
  101. omnibase_infra/runtime/registry_policy.py +9 -326
  102. omnibase_infra/runtime/secret_resolver.py +4 -2
  103. omnibase_infra/runtime/service_kernel.py +11 -3
  104. omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
  105. omnibase_infra/runtime/service_runtime_host_process.py +589 -106
  106. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  107. omnibase_infra/runtime/transition_notification_publisher.py +764 -0
  108. omnibase_infra/runtime/util_container_wiring.py +6 -5
  109. omnibase_infra/runtime/util_wiring.py +17 -4
  110. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  111. omnibase_infra/services/__init__.py +21 -0
  112. omnibase_infra/services/corpus_capture.py +7 -1
  113. omnibase_infra/services/mcp/__init__.py +31 -0
  114. omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
  115. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  116. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  117. omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
  118. omnibase_infra/services/registry_api/__init__.py +40 -0
  119. omnibase_infra/services/registry_api/main.py +261 -0
  120. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  121. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  122. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  123. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  124. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  125. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  126. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  127. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  128. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  129. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  130. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  131. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  132. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  133. omnibase_infra/services/registry_api/routes.py +371 -0
  134. omnibase_infra/services/registry_api/service.py +837 -0
  135. omnibase_infra/services/service_capability_query.py +4 -4
  136. omnibase_infra/services/service_health.py +3 -2
  137. omnibase_infra/services/service_timeout_emitter.py +20 -3
  138. omnibase_infra/services/service_timeout_scanner.py +7 -3
  139. omnibase_infra/services/session/__init__.py +56 -0
  140. omnibase_infra/services/session/config_consumer.py +120 -0
  141. omnibase_infra/services/session/config_store.py +139 -0
  142. omnibase_infra/services/session/consumer.py +1007 -0
  143. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  144. omnibase_infra/services/session/store.py +997 -0
  145. omnibase_infra/utils/__init__.py +19 -0
  146. omnibase_infra/utils/util_atomic_file.py +261 -0
  147. omnibase_infra/utils/util_db_transaction.py +239 -0
  148. omnibase_infra/utils/util_dsn_validation.py +1 -1
  149. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  150. omnibase_infra/validation/__init__.py +3 -19
  151. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  152. omnibase_infra/validation/infra_validators.py +35 -24
  153. omnibase_infra/validation/validation_exemptions.yaml +140 -9
  154. omnibase_infra/validation/validator_chain_propagation.py +2 -2
  155. omnibase_infra/validation/validator_runtime_shape.py +1 -1
  156. omnibase_infra/validation/validator_security.py +473 -370
  157. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
  158. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
  159. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
  160. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
  161. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,220 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Handler Source Configuration Model.
4
+
5
+ This module provides ModelHandlerSourceConfig, a Pydantic model for
6
+ configuring handler source mode selection at runtime.
7
+
8
+ The configuration controls how handlers are discovered and loaded:
9
+ - BOOTSTRAP: Hardcoded handlers from _KNOWN_HANDLERS dict (MVP mode)
10
+ - CONTRACT: YAML contracts from handler_contract.yaml files (production)
11
+ - HYBRID: Contract-first with bootstrap fallback per-handler identity
12
+
13
+ Production hardening features:
14
+ - Bootstrap expiry enforcement: If bootstrap_expires_at is set and now > expires_at,
15
+ the runtime will refuse to start in BOOTSTRAP mode (or force CONTRACT mode)
16
+ - Structured logging of expiry status at startup
17
+ - Override control for hybrid mode handler resolution
18
+
19
+ .. versionadded:: 0.7.0
20
+ Created as part of OMN-1095 handler source mode configuration.
21
+
22
+ See Also:
23
+ - HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md: Full architecture documentation
24
+ - EnumHandlerSourceMode: Enum defining valid source modes
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from datetime import UTC, datetime, timezone
30
+
31
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
32
+
33
+ from omnibase_infra.enums.enum_handler_source_mode import EnumHandlerSourceMode
34
+
35
+
36
+ class ModelHandlerSourceConfig(BaseModel):
37
+ """Configuration for handler source mode selection.
38
+
39
+ Controls how handlers are discovered and loaded at runtime. This model
40
+ is used by RuntimeHostProcess and related components to determine the
41
+ handler loading strategy.
42
+
43
+ Configuration Options:
44
+ - handler_source_mode: Selects the loading strategy (BOOTSTRAP, CONTRACT, HYBRID)
45
+ - allow_bootstrap_override: Controls handler resolution in HYBRID mode
46
+ - bootstrap_expires_at: Production safety - forces CONTRACT after expiry
47
+
48
+ Production Hardening:
49
+ When bootstrap_expires_at is set and the current time exceeds it:
50
+ - BOOTSTRAP mode: Runtime refuses to start (safety mechanism)
51
+ - HYBRID mode: Bootstrap fallback disabled, contract-only resolution
52
+ - CONTRACT mode: No effect (already contract-only)
53
+
54
+ This prevents accidental deployment with hardcoded handlers in production.
55
+
56
+ Attributes:
57
+ handler_source_mode: Handler loading source mode.
58
+ - BOOTSTRAP: Load from hardcoded _KNOWN_HANDLERS dict (MVP)
59
+ - CONTRACT: Load from handler_contract.yaml files (production)
60
+ - HYBRID: Contract-first with bootstrap fallback per-handler identity
61
+ Defaults to HYBRID as recommended for gradual migration.
62
+
63
+ allow_bootstrap_override: If True, bootstrap handlers can override
64
+ contract handlers in HYBRID mode. Default is False, meaning
65
+ contract handlers take precedence (inverse of naive HYBRID).
66
+ Has no effect in BOOTSTRAP or CONTRACT modes.
67
+ When parsing from environment or config, string values "true",
68
+ "yes", "1", "on" (case-insensitive) are accepted as truthy.
69
+
70
+ bootstrap_expires_at: If set and expired, refuse BOOTSTRAP mode and
71
+ force CONTRACT. This is a production safety mechanism to ensure
72
+ hardcoded handlers are not accidentally deployed to production
73
+ after a migration deadline. Set to None to disable expiry checking.
74
+
75
+ Example:
76
+ >>> from datetime import datetime, timezone
77
+ >>> from omnibase_infra.models.handlers import ModelHandlerSourceConfig
78
+ >>> from omnibase_infra.enums import EnumHandlerSourceMode
79
+ >>>
80
+ >>> # Production configuration (recommended)
81
+ >>> config = ModelHandlerSourceConfig(
82
+ ... handler_source_mode=EnumHandlerSourceMode.CONTRACT,
83
+ ... )
84
+ >>>
85
+ >>> # Migration configuration with safety expiry (must be timezone-aware)
86
+ >>> config = ModelHandlerSourceConfig(
87
+ ... handler_source_mode=EnumHandlerSourceMode.HYBRID,
88
+ ... bootstrap_expires_at=datetime(2025, 3, 1, 0, 0, 0, tzinfo=timezone.utc),
89
+ ... )
90
+ >>>
91
+ >>> # Check if bootstrap is expired
92
+ >>> if config.is_bootstrap_expired:
93
+ ... print("Bootstrap mode has expired - must use CONTRACT")
94
+ """
95
+
96
+ model_config = ConfigDict(
97
+ strict=True,
98
+ frozen=True,
99
+ extra="forbid",
100
+ )
101
+
102
+ handler_source_mode: EnumHandlerSourceMode = Field(
103
+ default=EnumHandlerSourceMode.HYBRID,
104
+ description="Handler loading source mode: BOOTSTRAP, CONTRACT, or HYBRID. "
105
+ "Defaults to HYBRID (contract-first with bootstrap fallback).",
106
+ )
107
+
108
+ allow_bootstrap_override: bool = Field(
109
+ default=False,
110
+ description=(
111
+ "If True, bootstrap handlers can override contract handlers in HYBRID mode. "
112
+ "Default is False (contract handlers take precedence). "
113
+ "When parsing from config, string values 'true', 'yes', '1', 'on' "
114
+ "(case-insensitive) are accepted as truthy."
115
+ ),
116
+ )
117
+
118
+ bootstrap_expires_at: datetime | None = Field(
119
+ default=None,
120
+ description=(
121
+ "If set and expired, refuse BOOTSTRAP mode and force CONTRACT. "
122
+ "Production safety mechanism for migration deadlines. "
123
+ "Must be timezone-aware (UTC recommended); naive datetimes are rejected."
124
+ ),
125
+ )
126
+
127
+ @field_validator("allow_bootstrap_override", mode="before")
128
+ @classmethod
129
+ def _coerce_allow_bootstrap_override(cls, value: object) -> bool:
130
+ """Coerce string and numeric values to boolean for config file compatibility.
131
+
132
+ Environment variables and YAML/JSON config files often represent booleans
133
+ as strings. This validator handles common truthy string representations
134
+ before Pydantic's strict type validation.
135
+
136
+ Args:
137
+ value: The raw value to coerce (may be str, bool, int, float, None, or other).
138
+
139
+ Returns:
140
+ True if value is a truthy string ("true", "yes", "1", "on") or
141
+ a truthy boolean, False otherwise.
142
+
143
+ Type Handling:
144
+ - bool: Passed through unchanged.
145
+ - str: Case-insensitive check for "true", "yes", "1", "on".
146
+ - int/float: 0 and 0.0 are False, all other numbers are True.
147
+ - None: Returns False.
148
+ - Unknown types: Default to False for safety.
149
+ """
150
+ if isinstance(value, bool):
151
+ return value
152
+ if isinstance(value, str):
153
+ return value.lower() in ("true", "yes", "1", "on")
154
+ if isinstance(value, (int, float)):
155
+ # Explicit: 0/0.0 = False, any other number = True
156
+ return bool(value)
157
+ # Unknown types default to False for safety
158
+ return False
159
+
160
+ @field_validator("bootstrap_expires_at")
161
+ @classmethod
162
+ def _validate_expires_at_timezone(cls, value: datetime | None) -> datetime | None:
163
+ """Validate and normalize bootstrap_expires_at to UTC.
164
+
165
+ Args:
166
+ value: The datetime value to validate.
167
+
168
+ Returns:
169
+ None if value is None, otherwise the datetime normalized to UTC.
170
+
171
+ Raises:
172
+ ValueError: If the datetime is naive (no timezone info).
173
+ """
174
+ if value is None:
175
+ return None
176
+ if value.tzinfo is None:
177
+ raise ValueError(
178
+ "bootstrap_expires_at must be timezone-aware (UTC recommended). "
179
+ "Use datetime.now(timezone.utc) or datetime(..., tzinfo=timezone.utc)."
180
+ )
181
+ return value.astimezone(UTC)
182
+
183
+ @property
184
+ def is_bootstrap_expired(self) -> bool:
185
+ """Check if bootstrap mode has expired.
186
+
187
+ Returns:
188
+ True if bootstrap_expires_at is set and current time exceeds it,
189
+ False otherwise.
190
+
191
+ Note:
192
+ Uses UTC-aware comparison. The bootstrap_expires_at field is
193
+ validated and normalized to UTC at construction time.
194
+ """
195
+ if self.bootstrap_expires_at is None:
196
+ return False
197
+ return datetime.now(UTC) > self.bootstrap_expires_at
198
+
199
+ @property
200
+ def effective_mode(self) -> EnumHandlerSourceMode:
201
+ """Get the effective handler source mode after expiry check.
202
+
203
+ If bootstrap_expires_at is set and expired, returns CONTRACT
204
+ regardless of the configured handler_source_mode. Otherwise
205
+ returns the configured mode.
206
+
207
+ Returns:
208
+ The effective handler source mode to use at runtime.
209
+
210
+ Note:
211
+ This property should be used by runtime components instead of
212
+ directly accessing handler_source_mode to ensure expiry
213
+ enforcement is applied.
214
+ """
215
+ if self.is_bootstrap_expired:
216
+ return EnumHandlerSourceMode.CONTRACT
217
+ return self.handler_source_mode
218
+
219
+
220
+ __all__ = ["ModelHandlerSourceConfig"]
@@ -0,0 +1,15 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """MCP models for Model Context Protocol integration."""
4
+
5
+ from omnibase_infra.models.mcp.model_mcp_contract_config import ModelMCPContractConfig
6
+ from omnibase_infra.models.mcp.model_mcp_server_config import ModelMCPServerConfig
7
+ from omnibase_infra.models.mcp.model_mcp_tool_definition import ModelMCPToolDefinition
8
+ from omnibase_infra.models.mcp.model_mcp_tool_parameter import ModelMCPToolParameter
9
+
10
+ __all__ = [
11
+ "ModelMCPContractConfig",
12
+ "ModelMCPServerConfig",
13
+ "ModelMCPToolDefinition",
14
+ "ModelMCPToolParameter",
15
+ ]
@@ -0,0 +1,80 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """MCP contract configuration model for enabling nodes as MCP tools.
4
+
5
+ This model defines the `mcp` field that can be added to ONEX contracts to expose
6
+ orchestrator nodes as MCP tools for AI agent integration.
7
+
8
+ Example contract.yaml:
9
+ node_type: ORCHESTRATOR_GENERIC
10
+ mcp:
11
+ expose: true
12
+ tool_name: "workflow_execute"
13
+ description: "Execute a workflow"
14
+ timeout_seconds: 30
15
+
16
+ Enforcement Rule:
17
+ The `mcp.expose` field is ONLY valid for ORCHESTRATOR_GENERIC nodes.
18
+ Non-orchestrator nodes with `mcp.expose: true` will be ignored during
19
+ registration - the MCP tags will not be added to Consul.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from pydantic import BaseModel, Field
25
+
26
+
27
+ class ModelMCPContractConfig(BaseModel):
28
+ """MCP configuration for exposing a node as an MCP tool.
29
+
30
+ This configuration is embedded in a node's contract.yaml to enable
31
+ AI agents to discover and invoke the node as an MCP tool.
32
+
33
+ Attributes:
34
+ expose: Whether to expose this node as an MCP tool. When True and
35
+ the node is an orchestrator, it will be registered with Consul
36
+ using MCP-specific tags for tool discovery.
37
+ tool_name: Optional stable name for the MCP tool. If not provided,
38
+ defaults to the node's name. Use this to provide a consistent
39
+ tool name across node version changes.
40
+ description: Optional AI-friendly description of what this tool does.
41
+ If not provided, the node's description from the contract is used.
42
+ timeout_seconds: Optional execution timeout in seconds. Defaults to 30.
43
+ This is enforced when AI agents invoke the tool.
44
+
45
+ Example:
46
+ >>> config = ModelMCPContractConfig(
47
+ ... expose=True,
48
+ ... tool_name="my_workflow",
49
+ ... description="Execute my custom workflow",
50
+ ... timeout_seconds=60,
51
+ ... )
52
+ >>> config.expose
53
+ True
54
+ """
55
+
56
+ expose: bool = Field(
57
+ default=False,
58
+ description="Whether to expose this node as an MCP tool. "
59
+ "Only valid for ORCHESTRATOR_GENERIC nodes.",
60
+ )
61
+ tool_name: str | None = Field(
62
+ default=None,
63
+ description="Optional stable name for the MCP tool. "
64
+ "Defaults to the node's name if not specified.",
65
+ )
66
+ description: str | None = Field(
67
+ default=None,
68
+ description="Optional AI-friendly description of what this tool does. "
69
+ "If not provided, the node's description from the contract is used.",
70
+ )
71
+ timeout_seconds: int = Field(
72
+ default=30,
73
+ ge=1,
74
+ le=300,
75
+ description="Execution timeout in seconds for tool invocations. "
76
+ "Minimum: 1, Maximum: 300 (5 minutes).",
77
+ )
78
+
79
+
80
+ __all__ = ["ModelMCPContractConfig"]
@@ -0,0 +1,67 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """MCP server configuration model.
4
+
5
+ This model defines the configuration for the MCP server lifecycle,
6
+ including Consul discovery, Kafka hot reload, and HTTP server settings.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class ModelMCPServerConfig(BaseModel):
15
+ """Configuration for the MCP server lifecycle.
16
+
17
+ This model captures all configuration needed for the MCP server:
18
+ - Consul connection settings for service discovery
19
+ - Kafka settings for hot reload
20
+ - HTTP server binding
21
+ - Execution defaults
22
+
23
+ Attributes:
24
+ consul_host: Consul server hostname for service discovery.
25
+ consul_port: Consul server port.
26
+ consul_scheme: HTTP scheme for Consul (http/https).
27
+ consul_token: Optional ACL token for Consul authentication.
28
+ kafka_enabled: Whether to enable Kafka for hot reload.
29
+ http_host: Host to bind the MCP HTTP server.
30
+ http_port: Port for the MCP HTTP server.
31
+ default_timeout: Default execution timeout for tools.
32
+ dev_mode: Whether to run in development mode (local contracts).
33
+ contracts_dir: Directory for contract scanning in dev mode.
34
+ """
35
+
36
+ consul_host: str = Field(default="localhost", description="Consul server hostname")
37
+ consul_port: int = Field(
38
+ default=8500, ge=1, le=65535, description="Consul server port"
39
+ )
40
+ consul_scheme: str = Field(
41
+ default="http", pattern="^https?$", description="HTTP scheme for Consul"
42
+ )
43
+ consul_token: str | None = Field(
44
+ default=None, description="Optional ACL token for Consul authentication"
45
+ )
46
+ kafka_enabled: bool = Field(
47
+ default=True, description="Whether to enable Kafka for hot reload"
48
+ )
49
+ http_host: str = Field(
50
+ default="0.0.0.0", # noqa: S104 - Intentional bind-all for server
51
+ description="Host to bind the MCP HTTP server",
52
+ )
53
+ http_port: int = Field(
54
+ default=8090, ge=1, le=65535, description="Port for the MCP HTTP server"
55
+ )
56
+ default_timeout: float = Field(
57
+ default=30.0, gt=0, le=300, description="Default execution timeout for tools"
58
+ )
59
+ dev_mode: bool = Field(
60
+ default=False, description="Whether to run in development mode"
61
+ )
62
+ contracts_dir: str | None = Field(
63
+ default=None, description="Directory for contract scanning in dev mode"
64
+ )
65
+
66
+
67
+ __all__ = ["ModelMCPServerConfig"]
@@ -0,0 +1,73 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """MCP tool definition model for representing ONEX nodes as MCP tools.
4
+
5
+ This model is used by the MCP adapter layer to:
6
+ 1. Cache discovered tools in the registry
7
+ 2. Generate MCP tool schemas for AI agents
8
+ 3. Route tool invocations to ONEX orchestrators
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+ from omnibase_infra.models.mcp.model_mcp_tool_parameter import ModelMCPToolParameter
16
+
17
+
18
+ class ModelMCPToolDefinition(BaseModel):
19
+ """Complete MCP tool definition derived from an ONEX orchestrator node.
20
+
21
+ This model captures all information needed to:
22
+ - Expose the tool to AI agents via MCP protocol
23
+ - Route invocations to the correct ONEX orchestrator
24
+ - Enforce execution constraints (timeout, etc.)
25
+
26
+ Attributes:
27
+ name: Stable tool name for AI agent invocation. This is derived from
28
+ contract.mcp.tool_name or falls back to the node name.
29
+ description: AI-friendly description of tool functionality.
30
+ version: Tool version from the node contract.
31
+ parameters: List of input parameters with type information.
32
+ input_schema: JSON Schema for input validation.
33
+ orchestrator_node_id: UUID of the ONEX orchestrator node.
34
+ orchestrator_service_id: Consul service ID for routing.
35
+ endpoint: HTTP endpoint for direct invocation (if available).
36
+ timeout_seconds: Execution timeout for tool invocations.
37
+ metadata: Additional metadata for routing and observability.
38
+ """
39
+
40
+ name: str = Field(description="Stable tool name for MCP invocation")
41
+ description: str = Field(description="AI-friendly tool description")
42
+ version: str = Field(default="1.0.0", description="Tool version")
43
+ parameters: list[ModelMCPToolParameter] = Field(
44
+ default_factory=list, description="Input parameters"
45
+ )
46
+ input_schema: dict[str, object] = Field(
47
+ default_factory=lambda: dict[str, object]({"type": "object", "properties": {}}),
48
+ description="JSON Schema for input validation",
49
+ )
50
+ orchestrator_node_id: str | None = Field(
51
+ default=None, description="UUID of the source orchestrator node"
52
+ )
53
+ orchestrator_service_id: str | None = Field(
54
+ default=None, description="Consul service ID for routing"
55
+ )
56
+ endpoint: str | None = Field(
57
+ default=None, description="HTTP endpoint for direct invocation"
58
+ )
59
+ timeout_seconds: int = Field(
60
+ default=30, ge=1, le=300, description="Execution timeout in seconds"
61
+ )
62
+ metadata: dict[str, object] = Field(
63
+ default_factory=dict,
64
+ description="Additional metadata (tags, input_model module, etc.)",
65
+ )
66
+
67
+ @property
68
+ def tool_type(self) -> str:
69
+ """Return the MCP tool type. Always 'function' for ONEX nodes."""
70
+ return "function"
71
+
72
+
73
+ __all__ = ["ModelMCPToolDefinition"]
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """MCP tool parameter model for representing tool input parameters."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class ModelMCPToolParameter(BaseModel):
11
+ """Parameter definition for an MCP tool.
12
+
13
+ Represents a single parameter that can be passed to an MCP tool,
14
+ including its type, validation constraints, and documentation.
15
+ """
16
+
17
+ name: str = Field(description="Parameter name")
18
+ parameter_type: str = Field(
19
+ default="string",
20
+ description="JSON Schema type: string, number, boolean, array, object",
21
+ )
22
+ description: str = Field(default="", description="Human-readable description")
23
+ required: bool = Field(
24
+ default=True, description="Whether this parameter is required"
25
+ )
26
+ default_value: object | None = Field(
27
+ default=None, description="Default value if not provided"
28
+ )
29
+ json_schema: dict[str, object] | None = Field(
30
+ default=None,
31
+ description="Additional JSON Schema constraints (enum, format, etc.)",
32
+ )
33
+
34
+
35
+ __all__ = ["ModelMCPToolParameter"]
@@ -12,9 +12,12 @@ Note:
12
12
  would not correctly handle this distinction.
13
13
  """
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from pydantic import BaseModel, ConfigDict, Field
16
18
 
17
19
  from omnibase_core.types import JsonType
20
+ from omnibase_infra.models.mcp.model_mcp_contract_config import ModelMCPContractConfig
18
21
 
19
22
 
20
23
  class ModelNodeCapabilities(BaseModel):
@@ -94,6 +97,14 @@ class ModelNodeCapabilities(BaseModel):
94
97
  description="Nested configuration (JSON-serializable values)",
95
98
  )
96
99
 
100
+ # MCP configuration for exposing node as AI agent tool
101
+ # Only valid for ORCHESTRATOR nodes - ignored for other node types
102
+ mcp: ModelMCPContractConfig | None = Field(
103
+ default=None,
104
+ description="MCP configuration for exposing node as AI agent tool. "
105
+ "Only valid for ORCHESTRATOR_GENERIC nodes.",
106
+ )
107
+
97
108
  def __getitem__(self, key: str) -> object:
98
109
  """Enable dict-like access to capabilities.
99
110
 
@@ -14,6 +14,7 @@ from uuid import UUID
14
14
  from pydantic import BaseModel, ConfigDict, Field, field_validator
15
15
 
16
16
  from omnibase_core.enums import EnumNodeKind
17
+ from omnibase_core.models.capabilities import ModelContractCapabilities
17
18
  from omnibase_core.models.primitives.model_semver import ModelSemVer
18
19
  from omnibase_infra.enums import EnumIntrospectionReason
19
20
  from omnibase_infra.models.discovery.model_discovered_capabilities import (
@@ -49,6 +50,8 @@ class ModelNodeIntrospectionEvent(BaseModel):
49
50
  node_version: Semantic version of the node.
50
51
  declared_capabilities: Contract-declared capabilities (feature flags).
51
52
  discovered_capabilities: Runtime-discovered capabilities (reflection).
53
+ contract_capabilities: Contract-derived capabilities (design-time truth).
54
+ Populated from the node's contract when available, None otherwise.
52
55
  endpoints: Dictionary of exposed endpoints (name -> URL).
53
56
  current_state: Current FSM state if the node has state management.
54
57
  reason: Why this introspection event was emitted.
@@ -98,6 +101,12 @@ class ModelNodeIntrospectionEvent(BaseModel):
98
101
  default_factory=ModelDiscoveredCapabilities,
99
102
  description="Capabilities discovered via runtime reflection",
100
103
  )
104
+ contract_capabilities: ModelContractCapabilities | None = Field(
105
+ default=None,
106
+ description="Contract-derived capabilities (design-time truth, deterministic). "
107
+ "Populated by ContractCapabilityExtractor from the node's contract. "
108
+ "None when contract is not available or extraction fails.",
109
+ )
101
110
 
102
111
  # Endpoints and state
103
112
  endpoints: dict[str, str] = Field(
@@ -27,6 +27,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
27
27
 
28
28
  logger = logging.getLogger(__name__)
29
29
 
30
+ from omnibase_core.models.primitives import ModelSemVer
30
31
  from omnibase_infra.enums.enum_handler_type_category import EnumHandlerTypeCategory
31
32
  from omnibase_infra.errors import ProtocolConfigurationError
32
33
  from omnibase_infra.models.runtime.model_contract_security_config import (
@@ -131,6 +132,10 @@ class ModelHandlerContract(BaseModel):
131
132
  default=None,
132
133
  description="Optional security configuration for the handler",
133
134
  )
135
+ handler_version: ModelSemVer | None = Field(
136
+ default=None,
137
+ description="Handler version in semantic versioning format. If not provided, defaults to 1.0.0",
138
+ )
134
139
 
135
140
  @field_validator("handler_type", mode="before")
136
141
  @classmethod
@@ -207,18 +212,22 @@ class ModelHandlerContract(BaseModel):
207
212
  return []
208
213
 
209
214
  @model_validator(mode="after")
210
- def set_default_protocol_type(self) -> ModelHandlerContract:
211
- """Set protocol_type from handler_name if not explicitly provided.
215
+ def set_defaults(self) -> ModelHandlerContract:
216
+ """Set default values for protocol_type and handler_version.
212
217
 
213
- If protocol_type is None, derives it from handler_name by stripping
214
- the 'handler-' prefix. If handler_name doesn't have that prefix,
215
- uses the full handler_name as protocol_type.
218
+ Protocol Type:
219
+ If protocol_type is None, derives it from handler_name by stripping
220
+ the 'handler-' prefix. If handler_name doesn't have that prefix,
221
+ uses the full handler_name as protocol_type.
216
222
 
217
- Guards against empty derived protocol_type which would produce
218
- invalid registry keys.
223
+ Guards against empty derived protocol_type which would produce
224
+ invalid registry keys.
225
+
226
+ Handler Version:
227
+ If handler_version is None, sets it to the default version 1.0.0.
219
228
 
220
229
  Returns:
221
- Self with protocol_type populated.
230
+ Self with protocol_type and handler_version populated.
222
231
 
223
232
  Raises:
224
233
  ValueError: If derived protocol_type would be empty (e.g., handler_name
@@ -232,6 +241,8 @@ class ModelHandlerContract(BaseModel):
232
241
  ... )
233
242
  >>> contract.protocol_type
234
243
  'db'
244
+ >>> contract.handler_version
245
+ ModelSemVer(major=1, minor=0, patch=0)
235
246
 
236
247
  >>> contract = ModelHandlerContract(
237
248
  ... handler_name="custom-handler",
@@ -241,6 +252,10 @@ class ModelHandlerContract(BaseModel):
241
252
  >>> contract.protocol_type
242
253
  'custom-handler'
243
254
  """
255
+ # Set default handler_version if not provided
256
+ if self.handler_version is None:
257
+ self.handler_version = ModelSemVer(major=1, minor=0, patch=0)
258
+
244
259
  if self.protocol_type is None:
245
260
  prefix = "handler-"
246
261
  if self.handler_name.startswith(prefix):
@@ -263,7 +278,7 @@ class ModelHandlerContract(BaseModel):
263
278
  f"Provide a non-empty protocol_type or handler_name."
264
279
  )
265
280
 
266
- # Log successful protocol_type derivation for debugging
281
+ # Log successful contract validation for debugging
267
282
  logger.debug(
268
283
  "Handler contract validated successfully",
269
284
  extra={
@@ -271,6 +286,7 @@ class ModelHandlerContract(BaseModel):
271
286
  "handler_class": self.handler_class,
272
287
  "protocol_type": self.protocol_type,
273
288
  "handler_type": self.handler_type.value,
289
+ "handler_version": str(self.handler_version),
274
290
  },
275
291
  )
276
292
 
@@ -24,6 +24,7 @@ from pathlib import Path
24
24
 
25
25
  from pydantic import BaseModel, ConfigDict, Field
26
26
 
27
+ from omnibase_core.models.primitives import ModelSemVer
27
28
  from omnibase_infra.enums.enum_handler_type_category import EnumHandlerTypeCategory
28
29
 
29
30
 
@@ -53,10 +54,13 @@ class ModelLoadedHandler(BaseModel):
53
54
  Examples: ['auth', 'validation', 'http-client'].
54
55
  loaded_at: Timestamp when the handler was successfully loaded.
55
56
  Used for diagnostics and cache invalidation.
57
+ handler_version: Semantic version of the handler from the contract.
58
+ Used for version tracking and compatibility checks.
56
59
 
57
60
  Example:
58
61
  >>> from datetime import datetime, UTC
59
62
  >>> from pathlib import Path
63
+ >>> from omnibase_core.models.primitives import ModelSemVer
60
64
  >>> from omnibase_infra.enums import EnumHandlerTypeCategory
61
65
  >>> handler = ModelLoadedHandler(
62
66
  ... handler_name="auth.validate_token",
@@ -65,6 +69,7 @@ class ModelLoadedHandler(BaseModel):
65
69
  ... contract_path=Path("/app/handlers/auth/handler_contract.yaml"),
66
70
  ... capability_tags=["auth", "validation", "jwt"],
67
71
  ... loaded_at=datetime.now(UTC),
72
+ ... handler_version=ModelSemVer(major=1, minor=0, patch=0),
68
73
  ... )
69
74
  >>> handler.handler_name
70
75
  'auth.validate_token'
@@ -115,6 +120,10 @@ class ModelLoadedHandler(BaseModel):
115
120
  ...,
116
121
  description="Timestamp when the handler was successfully loaded",
117
122
  )
123
+ handler_version: ModelSemVer = Field(
124
+ ...,
125
+ description="Handler semantic version from contract",
126
+ )
118
127
 
119
128
 
120
129
  __all__ = ["ModelLoadedHandler"]