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.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
- omnibase_infra/capabilities/__init__.py +15 -0
- omnibase_infra/capabilities/capability_inference_rules.py +211 -0
- omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
- omnibase_infra/capabilities/intent_type_extractor.py +160 -0
- omnibase_infra/cli/commands.py +1 -1
- omnibase_infra/configs/widget_mapping.yaml +176 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
- omnibase_infra/enums/__init__.py +6 -0
- omnibase_infra/enums/enum_handler_error_type.py +10 -0
- omnibase_infra/enums/enum_handler_source_mode.py +72 -0
- omnibase_infra/enums/enum_kafka_acks.py +99 -0
- omnibase_infra/errors/error_compute_registry.py +4 -1
- omnibase_infra/errors/error_event_bus_registry.py +4 -1
- omnibase_infra/errors/error_infra.py +3 -1
- omnibase_infra/errors/error_policy_registry.py +4 -1
- omnibase_infra/event_bus/event_bus_kafka.py +1 -1
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
- omnibase_infra/handlers/__init__.py +8 -1
- omnibase_infra/handlers/handler_consul.py +7 -1
- omnibase_infra/handlers/handler_db.py +10 -3
- omnibase_infra/handlers/handler_graph.py +10 -5
- omnibase_infra/handlers/handler_http.py +8 -2
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_mcp.py +745 -63
- omnibase_infra/handlers/handler_vault.py +11 -5
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
- omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
- omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
- omnibase_infra/mixins/mixin_node_introspection.py +42 -7
- omnibase_infra/mixins/mixin_retry_execution.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +11 -0
- omnibase_infra/models/handlers/__init__.py +48 -5
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
- omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/mcp/__init__.py +15 -0
- omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
- omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
- omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
- omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
- omnibase_infra/models/registration/model_node_capabilities.py +11 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
- omnibase_infra/models/runtime/model_handler_contract.py +25 -9
- omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
- omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
- omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
- omnibase_infra/nodes/effects/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
- omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
- omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
- omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
- omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
- omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
- omnibase_infra/plugins/plugin_compute_base.py +16 -2
- omnibase_infra/protocols/__init__.py +2 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/protocols/protocol_event_projector.py +1 -1
- omnibase_infra/runtime/__init__.py +90 -1
- omnibase_infra/runtime/binding_config_resolver.py +102 -37
- omnibase_infra/runtime/constants_notification.py +75 -0
- omnibase_infra/runtime/contract_handler_discovery.py +6 -1
- omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
- omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
- omnibase_infra/runtime/handler_contract_source.py +267 -186
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +19 -2
- omnibase_infra/runtime/handler_registry.py +11 -3
- omnibase_infra/runtime/handler_source_resolver.py +326 -0
- omnibase_infra/runtime/mixin_semver_cache.py +25 -1
- omnibase_infra/runtime/mixins/__init__.py +7 -0
- omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
- omnibase_infra/runtime/models/__init__.py +24 -0
- omnibase_infra/runtime/models/model_health_check_result.py +2 -1
- omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
- omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
- omnibase_infra/runtime/projector_plugin_loader.py +1 -1
- omnibase_infra/runtime/projector_shell.py +229 -1
- omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
- omnibase_infra/runtime/protocols/__init__.py +10 -0
- omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/registry_policy.py +9 -326
- omnibase_infra/runtime/secret_resolver.py +4 -2
- omnibase_infra/runtime/service_kernel.py +11 -3
- omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
- omnibase_infra/runtime/service_runtime_host_process.py +589 -106
- omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
- omnibase_infra/runtime/transition_notification_publisher.py +764 -0
- omnibase_infra/runtime/util_container_wiring.py +6 -5
- omnibase_infra/runtime/util_wiring.py +17 -4
- omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
- omnibase_infra/services/__init__.py +21 -0
- omnibase_infra/services/corpus_capture.py +7 -1
- omnibase_infra/services/mcp/__init__.py +31 -0
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
- omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
- omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
- omnibase_infra/services/registry_api/__init__.py +40 -0
- omnibase_infra/services/registry_api/main.py +261 -0
- omnibase_infra/services/registry_api/models/__init__.py +66 -0
- omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
- omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
- omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
- omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
- omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
- omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
- omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
- omnibase_infra/services/registry_api/models/model_warning.py +49 -0
- omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
- omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
- omnibase_infra/services/registry_api/routes.py +371 -0
- omnibase_infra/services/registry_api/service.py +837 -0
- omnibase_infra/services/service_capability_query.py +4 -4
- omnibase_infra/services/service_health.py +3 -2
- omnibase_infra/services/service_timeout_emitter.py +20 -3
- omnibase_infra/services/service_timeout_scanner.py +7 -3
- omnibase_infra/services/session/__init__.py +56 -0
- omnibase_infra/services/session/config_consumer.py +120 -0
- omnibase_infra/services/session/config_store.py +139 -0
- omnibase_infra/services/session/consumer.py +1007 -0
- omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
- omnibase_infra/services/session/store.py +997 -0
- omnibase_infra/utils/__init__.py +19 -0
- omnibase_infra/utils/util_atomic_file.py +261 -0
- omnibase_infra/utils/util_db_transaction.py +239 -0
- omnibase_infra/utils/util_dsn_validation.py +1 -1
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/validation/__init__.py +3 -19
- omnibase_infra/validation/contracts/security.validation.yaml +114 -0
- omnibase_infra/validation/infra_validators.py +35 -24
- omnibase_infra/validation/validation_exemptions.yaml +140 -9
- omnibase_infra/validation/validator_chain_propagation.py +2 -2
- omnibase_infra/validation/validator_runtime_shape.py +1 -1
- omnibase_infra/validation/validator_security.py +473 -370
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""MCP Tool Registry - Event-loop safe in-memory cache of MCP tool definitions.
|
|
4
|
+
|
|
5
|
+
This service provides a thread-safe registry for MCP tool definitions, supporting:
|
|
6
|
+
- Event-driven updates from Kafka (hot reload)
|
|
7
|
+
- Idempotent operations with version tracking
|
|
8
|
+
- Concurrent access within a single event loop
|
|
9
|
+
|
|
10
|
+
The registry uses asyncio.Lock for coroutine-safe access. It is NOT thread-safe
|
|
11
|
+
across multiple threads/event loops - use within a single async context.
|
|
12
|
+
|
|
13
|
+
Version Tracking:
|
|
14
|
+
Each tool has an associated version (event_id) to handle out-of-order
|
|
15
|
+
Kafka messages. Operations only succeed if the event_id is newer than
|
|
16
|
+
the last recorded version for that tool.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import logging
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
from uuid import uuid4
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from omnibase_infra.models.mcp.model_mcp_tool_definition import (
|
|
28
|
+
ModelMCPToolDefinition,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ServiceMCPToolRegistry:
|
|
35
|
+
"""Event-loop safe in-memory cache of MCP tool definitions.
|
|
36
|
+
|
|
37
|
+
Uses asyncio.Lock for coroutine-safe access within a single event loop.
|
|
38
|
+
NOT thread-safe across multiple threads/event loops.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
_tools: Dictionary mapping tool names to tool definitions.
|
|
42
|
+
_versions: Dictionary mapping tool names to their last event_id.
|
|
43
|
+
_lock: asyncio.Lock for coroutine-safe access.
|
|
44
|
+
|
|
45
|
+
Version Tracking:
|
|
46
|
+
The registry tracks event_id for each tool to handle idempotency:
|
|
47
|
+
- Kafka events may arrive out of order
|
|
48
|
+
- Duplicate events may be delivered
|
|
49
|
+
- Only newer events (higher event_id) should update the registry
|
|
50
|
+
|
|
51
|
+
Event IDs should be monotonically increasing (e.g., Kafka offset,
|
|
52
|
+
timestamp-based UUID, or sequential counter).
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
56
|
+
>>> tool = ModelMCPToolDefinition(name="my_tool", description="...")
|
|
57
|
+
>>> await registry.upsert_tool(tool, event_id="event-001")
|
|
58
|
+
True
|
|
59
|
+
>>> await registry.get_tool("my_tool")
|
|
60
|
+
ModelMCPToolDefinition(name='my_tool', ...)
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
"""Initialize the tool registry with empty state."""
|
|
65
|
+
self._tools: dict[str, ModelMCPToolDefinition] = {}
|
|
66
|
+
self._versions: dict[str, str] = {} # tool_name → last_event_id (normalized)
|
|
67
|
+
self._lock: asyncio.Lock = asyncio.Lock()
|
|
68
|
+
|
|
69
|
+
logger.debug("ServiceMCPToolRegistry initialized")
|
|
70
|
+
|
|
71
|
+
def _normalize_event_id(self, event_id: str) -> str:
|
|
72
|
+
"""Normalize event_id for correct lexicographic comparison.
|
|
73
|
+
|
|
74
|
+
Numeric IDs (e.g., Kafka offsets) are zero-padded to 20 digits to ensure
|
|
75
|
+
correct lexicographic ordering. Non-numeric IDs are returned unchanged.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
event_id: The event identifier to normalize.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Normalized event_id suitable for lexicographic comparison.
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
85
|
+
>>> registry._normalize_event_id("9")
|
|
86
|
+
'00000000000000000009'
|
|
87
|
+
>>> registry._normalize_event_id("10")
|
|
88
|
+
'00000000000000000010'
|
|
89
|
+
>>> registry._normalize_event_id("event-001")
|
|
90
|
+
'event-001'
|
|
91
|
+
"""
|
|
92
|
+
if event_id.isdigit():
|
|
93
|
+
return event_id.zfill(20)
|
|
94
|
+
return event_id
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def tool_count(self) -> int:
|
|
98
|
+
"""Return the number of registered tools.
|
|
99
|
+
|
|
100
|
+
Note: This is a snapshot and may change immediately after reading.
|
|
101
|
+
"""
|
|
102
|
+
return len(self._tools)
|
|
103
|
+
|
|
104
|
+
async def upsert_tool(
|
|
105
|
+
self,
|
|
106
|
+
tool: ModelMCPToolDefinition,
|
|
107
|
+
event_id: str,
|
|
108
|
+
) -> bool:
|
|
109
|
+
"""Upsert tool if event_id is newer. Returns True if updated.
|
|
110
|
+
|
|
111
|
+
This method is idempotent - calling with the same event_id multiple
|
|
112
|
+
times will only update the registry once. Out-of-order events with
|
|
113
|
+
older event_ids are ignored.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
tool: The tool definition to upsert.
|
|
117
|
+
event_id: Unique event identifier for version tracking.
|
|
118
|
+
Should be monotonically increasing (e.g., Kafka offset).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if the tool was updated (event_id was newer).
|
|
122
|
+
False if the event was stale (existing event_id >= new event_id).
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
126
|
+
>>> tool = ModelMCPToolDefinition(name="my_tool", ...)
|
|
127
|
+
>>> await registry.upsert_tool(tool, "event-002")
|
|
128
|
+
True
|
|
129
|
+
>>> await registry.upsert_tool(tool, "event-001") # Older event
|
|
130
|
+
False
|
|
131
|
+
"""
|
|
132
|
+
correlation_id = uuid4()
|
|
133
|
+
normalized_event_id = self._normalize_event_id(event_id)
|
|
134
|
+
|
|
135
|
+
async with self._lock:
|
|
136
|
+
existing_version = self._versions.get(tool.name)
|
|
137
|
+
|
|
138
|
+
# Stale event check: ignore if normalized event_id <= existing version
|
|
139
|
+
if existing_version and normalized_event_id <= existing_version:
|
|
140
|
+
logger.debug(
|
|
141
|
+
"Ignoring stale event for tool",
|
|
142
|
+
extra={
|
|
143
|
+
"tool_name": tool.name,
|
|
144
|
+
"event_id": event_id,
|
|
145
|
+
"normalized_event_id": normalized_event_id,
|
|
146
|
+
"existing_version": existing_version,
|
|
147
|
+
"correlation_id": str(correlation_id),
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
# Update tool and version (store normalized form)
|
|
153
|
+
self._tools[tool.name] = tool
|
|
154
|
+
self._versions[tool.name] = normalized_event_id
|
|
155
|
+
|
|
156
|
+
logger.info(
|
|
157
|
+
"Tool upserted in registry",
|
|
158
|
+
extra={
|
|
159
|
+
"tool_name": tool.name,
|
|
160
|
+
"event_id": event_id,
|
|
161
|
+
"normalized_event_id": normalized_event_id,
|
|
162
|
+
"previous_version": existing_version,
|
|
163
|
+
"correlation_id": str(correlation_id),
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
async def remove_tool(self, tool_name: str, event_id: str) -> bool:
|
|
169
|
+
"""Remove tool if event_id is newer. Returns True if removed.
|
|
170
|
+
|
|
171
|
+
This method is idempotent - calling with the same event_id multiple
|
|
172
|
+
times will only remove the tool once. Out-of-order events with
|
|
173
|
+
older event_ids are ignored.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
tool_name: Name of the tool to remove.
|
|
177
|
+
event_id: Unique event identifier for version tracking.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if the tool was removed (event_id was newer).
|
|
181
|
+
False if the event was stale or tool didn't exist.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
185
|
+
>>> await registry.remove_tool("my_tool", "event-003")
|
|
186
|
+
True
|
|
187
|
+
>>> await registry.remove_tool("my_tool", "event-002") # Older
|
|
188
|
+
False
|
|
189
|
+
"""
|
|
190
|
+
correlation_id = uuid4()
|
|
191
|
+
normalized_event_id = self._normalize_event_id(event_id)
|
|
192
|
+
|
|
193
|
+
async with self._lock:
|
|
194
|
+
existing_version = self._versions.get(tool_name)
|
|
195
|
+
|
|
196
|
+
# Stale event check: ignore if normalized event_id <= existing version
|
|
197
|
+
if existing_version and normalized_event_id <= existing_version:
|
|
198
|
+
logger.debug(
|
|
199
|
+
"Ignoring stale remove event for tool",
|
|
200
|
+
extra={
|
|
201
|
+
"tool_name": tool_name,
|
|
202
|
+
"event_id": event_id,
|
|
203
|
+
"normalized_event_id": normalized_event_id,
|
|
204
|
+
"existing_version": existing_version,
|
|
205
|
+
"correlation_id": str(correlation_id),
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
# Remove tool if it exists
|
|
211
|
+
removed = self._tools.pop(tool_name, None) is not None
|
|
212
|
+
# Always update version to prevent re-adding with older event (store normalized form)
|
|
213
|
+
self._versions[tool_name] = normalized_event_id
|
|
214
|
+
|
|
215
|
+
if removed:
|
|
216
|
+
logger.info(
|
|
217
|
+
"Tool removed from registry",
|
|
218
|
+
extra={
|
|
219
|
+
"tool_name": tool_name,
|
|
220
|
+
"event_id": event_id,
|
|
221
|
+
"correlation_id": str(correlation_id),
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
logger.debug(
|
|
226
|
+
"Tool not found in registry for removal",
|
|
227
|
+
extra={
|
|
228
|
+
"tool_name": tool_name,
|
|
229
|
+
"event_id": event_id,
|
|
230
|
+
"correlation_id": str(correlation_id),
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return removed
|
|
235
|
+
|
|
236
|
+
async def get_tool(self, tool_name: str) -> ModelMCPToolDefinition | None:
|
|
237
|
+
"""Get a tool definition by name.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
tool_name: Name of the tool to retrieve.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The tool definition if found, None otherwise.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
247
|
+
>>> tool = await registry.get_tool("my_tool")
|
|
248
|
+
>>> if tool:
|
|
249
|
+
... print(tool.description)
|
|
250
|
+
"""
|
|
251
|
+
async with self._lock:
|
|
252
|
+
return self._tools.get(tool_name)
|
|
253
|
+
|
|
254
|
+
async def list_tools(self) -> list[ModelMCPToolDefinition]:
|
|
255
|
+
"""List all registered tool definitions.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
List of all tool definitions in the registry.
|
|
259
|
+
The list is a snapshot - modifications after this call
|
|
260
|
+
won't affect the returned list.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> registry = ServiceMCPToolRegistry()
|
|
264
|
+
>>> tools = await registry.list_tools()
|
|
265
|
+
>>> for tool in tools:
|
|
266
|
+
... print(f"{tool.name}: {tool.description}")
|
|
267
|
+
"""
|
|
268
|
+
async with self._lock:
|
|
269
|
+
return list(self._tools.values())
|
|
270
|
+
|
|
271
|
+
async def clear(self) -> None:
|
|
272
|
+
"""Clear all tools and versions from the registry.
|
|
273
|
+
|
|
274
|
+
This is useful for testing or server restart scenarios.
|
|
275
|
+
"""
|
|
276
|
+
correlation_id = uuid4()
|
|
277
|
+
|
|
278
|
+
async with self._lock:
|
|
279
|
+
tool_count = len(self._tools)
|
|
280
|
+
self._tools.clear()
|
|
281
|
+
self._versions.clear()
|
|
282
|
+
|
|
283
|
+
logger.info(
|
|
284
|
+
"Registry cleared",
|
|
285
|
+
extra={
|
|
286
|
+
"cleared_tool_count": tool_count,
|
|
287
|
+
"correlation_id": str(correlation_id),
|
|
288
|
+
},
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
async def has_tool(self, tool_name: str) -> bool:
|
|
292
|
+
"""Check if a tool exists in the registry.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
tool_name: Name of the tool to check.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
True if the tool exists, False otherwise.
|
|
299
|
+
"""
|
|
300
|
+
async with self._lock:
|
|
301
|
+
return tool_name in self._tools
|
|
302
|
+
|
|
303
|
+
async def get_tool_version(self, tool_name: str) -> str | None:
|
|
304
|
+
"""Get the last event_id for a tool.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
tool_name: Name of the tool.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
The last event_id (normalized) if found, None otherwise.
|
|
311
|
+
Numeric IDs are zero-padded to 20 digits.
|
|
312
|
+
"""
|
|
313
|
+
async with self._lock:
|
|
314
|
+
return self._versions.get(tool_name)
|
|
315
|
+
|
|
316
|
+
def describe(self) -> dict[str, object]:
|
|
317
|
+
"""Return registry metadata for observability.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Dictionary with registry state information.
|
|
321
|
+
"""
|
|
322
|
+
return {
|
|
323
|
+
"service_name": "ServiceMCPToolRegistry",
|
|
324
|
+
"tool_count": len(self._tools),
|
|
325
|
+
"version_count": len(self._versions),
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
__all__ = ["ServiceMCPToolRegistry"]
|