napistu 0.2.5.dev7__py3-none-any.whl → 0.3.1__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 (107) hide show
  1. napistu/__main__.py +126 -96
  2. napistu/constants.py +35 -41
  3. napistu/context/__init__.py +10 -0
  4. napistu/context/discretize.py +462 -0
  5. napistu/context/filtering.py +387 -0
  6. napistu/gcs/__init__.py +1 -1
  7. napistu/identifiers.py +74 -15
  8. napistu/indices.py +68 -0
  9. napistu/ingestion/__init__.py +1 -1
  10. napistu/ingestion/bigg.py +47 -62
  11. napistu/ingestion/constants.py +18 -133
  12. napistu/ingestion/gtex.py +113 -0
  13. napistu/ingestion/hpa.py +147 -0
  14. napistu/ingestion/sbml.py +0 -97
  15. napistu/ingestion/string.py +2 -2
  16. napistu/matching/__init__.py +10 -0
  17. napistu/matching/constants.py +18 -0
  18. napistu/matching/interactions.py +518 -0
  19. napistu/matching/mount.py +529 -0
  20. napistu/matching/species.py +510 -0
  21. napistu/mcp/__init__.py +7 -4
  22. napistu/mcp/__main__.py +128 -72
  23. napistu/mcp/client.py +16 -25
  24. napistu/mcp/codebase.py +201 -145
  25. napistu/mcp/component_base.py +170 -0
  26. napistu/mcp/config.py +223 -0
  27. napistu/mcp/constants.py +45 -2
  28. napistu/mcp/documentation.py +253 -136
  29. napistu/mcp/documentation_utils.py +13 -48
  30. napistu/mcp/execution.py +372 -305
  31. napistu/mcp/health.py +47 -65
  32. napistu/mcp/profiles.py +10 -6
  33. napistu/mcp/server.py +161 -80
  34. napistu/mcp/tutorials.py +139 -87
  35. napistu/modify/__init__.py +1 -1
  36. napistu/modify/gaps.py +1 -1
  37. napistu/network/__init__.py +1 -1
  38. napistu/network/constants.py +101 -34
  39. napistu/network/data_handling.py +388 -0
  40. napistu/network/ig_utils.py +351 -0
  41. napistu/network/napistu_graph_core.py +354 -0
  42. napistu/network/neighborhoods.py +40 -40
  43. napistu/network/net_create.py +373 -309
  44. napistu/network/net_propagation.py +47 -19
  45. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  46. napistu/network/paths.py +67 -51
  47. napistu/network/precompute.py +11 -11
  48. napistu/ontologies/__init__.py +10 -0
  49. napistu/ontologies/constants.py +129 -0
  50. napistu/ontologies/dogma.py +243 -0
  51. napistu/ontologies/genodexito.py +649 -0
  52. napistu/ontologies/mygene.py +369 -0
  53. napistu/ontologies/renaming.py +198 -0
  54. napistu/rpy2/__init__.py +229 -86
  55. napistu/rpy2/callr.py +47 -77
  56. napistu/rpy2/constants.py +24 -23
  57. napistu/rpy2/rids.py +61 -648
  58. napistu/sbml_dfs_core.py +587 -222
  59. napistu/scverse/__init__.py +15 -0
  60. napistu/scverse/constants.py +28 -0
  61. napistu/scverse/loading.py +727 -0
  62. napistu/utils.py +118 -10
  63. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
  64. napistu-0.3.1.dist-info/RECORD +133 -0
  65. tests/conftest.py +22 -0
  66. tests/test_context_discretize.py +56 -0
  67. tests/test_context_filtering.py +267 -0
  68. tests/test_identifiers.py +100 -0
  69. tests/test_indices.py +65 -0
  70. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  71. tests/test_matching_interactions.py +108 -0
  72. tests/test_matching_mount.py +305 -0
  73. tests/test_matching_species.py +394 -0
  74. tests/test_mcp_config.py +193 -0
  75. tests/test_mcp_documentation_utils.py +12 -3
  76. tests/test_mcp_server.py +156 -19
  77. tests/test_network_data_handling.py +397 -0
  78. tests/test_network_ig_utils.py +23 -0
  79. tests/test_network_neighborhoods.py +19 -0
  80. tests/test_network_net_create.py +459 -0
  81. tests/test_network_ng_utils.py +30 -0
  82. tests/test_network_paths.py +56 -0
  83. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  84. tests/test_ontologies_genodexito.py +58 -0
  85. tests/test_ontologies_mygene.py +39 -0
  86. tests/test_ontologies_renaming.py +110 -0
  87. tests/test_rpy2_callr.py +79 -0
  88. tests/test_rpy2_init.py +151 -0
  89. tests/test_sbml.py +0 -31
  90. tests/test_sbml_dfs_core.py +134 -10
  91. tests/test_scverse_loading.py +778 -0
  92. tests/test_set_coverage.py +2 -2
  93. tests/test_utils.py +121 -1
  94. napistu/mechanism_matching.py +0 -1353
  95. napistu/rpy2/netcontextr.py +0 -467
  96. napistu-0.2.5.dev7.dist-info/RECORD +0 -98
  97. tests/test_igraph.py +0 -367
  98. tests/test_mechanism_matching.py +0 -784
  99. tests/test_net_utils.py +0 -149
  100. tests/test_netcontextr.py +0 -105
  101. tests/test_rpy2.py +0 -61
  102. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  103. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
  104. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
  105. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
  106. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
  107. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
napistu/mcp/execution.py CHANGED
@@ -4,379 +4,446 @@ Function execution components for the Napistu MCP server.
4
4
 
5
5
  from typing import Dict, List, Any, Optional
6
6
  import inspect
7
+ import logging
7
8
 
8
- # Global storage for session context and objects
9
- _session_context = {}
10
- _session_objects = {}
9
+ from fastmcp import FastMCP
11
10
 
11
+ from napistu.mcp.component_base import ComponentState, MCPComponent
12
12
 
13
- async def initialize_components() -> bool:
14
- """
15
- Initialize execution components.
16
-
17
- Returns
18
- -------
19
- bool
20
- True if initialization is successful.
21
- """
22
- global _session_context, _session_objects
23
- import napistu
24
-
25
- _session_context["napistu"] = napistu
26
- return True
13
+ logger = logging.getLogger(__name__)
27
14
 
28
15
 
29
- def register_object(name: str, obj: Any) -> None:
30
- """
31
- Register an object with the execution component.
16
+ class ExecutionState(ComponentState):
17
+ """State management for execution component."""
32
18
 
33
- Args:
34
- name: Name to reference the object by
35
- obj: The object to register
36
- """
37
- global _session_objects
38
- _session_objects[name] = obj
39
- print(f"Registered object '{name}' with MCP server")
19
+ def __init__(
20
+ self,
21
+ session_context: Optional[Dict] = None,
22
+ object_registry: Optional[Dict] = None,
23
+ ):
24
+ super().__init__()
25
+ # Session context contains global functions and modules
26
+ self.session_context = session_context or {}
27
+ # Object registry contains user-registered objects
28
+ self.session_objects = object_registry or {}
40
29
 
30
+ def is_healthy(self) -> bool:
31
+ """Component is healthy if it has a session context."""
32
+ return bool(self.session_context)
41
33
 
42
- def register_components(mcp, session_context=None, object_registry=None):
43
- """
44
- Register function execution components with the MCP server.
34
+ def get_health_details(self) -> Dict[str, Any]:
35
+ """Provide execution-specific health details."""
36
+ return {
37
+ "session_context_items": len(self.session_context),
38
+ "registered_objects": len(self.session_objects),
39
+ "context_keys": list(self.session_context.keys()),
40
+ "object_names": list(self.session_objects.keys()),
41
+ }
45
42
 
46
- Args:
47
- mcp: FastMCP server instance
48
- session_context: Dictionary of the user's current session (e.g., globals())
49
- object_registry: Dictionary of named objects to make available
50
- """
51
- global _session_context, _session_objects
43
+ def register_object(self, name: str, obj: Any) -> None:
44
+ """Register an object with the execution component."""
45
+ self.session_objects[name] = obj
46
+ logger.info(f"Registered object '{name}' with MCP server")
52
47
 
53
- # Initialize context
54
- if session_context:
55
- _session_context = session_context
56
48
 
57
- if object_registry:
58
- _session_objects = object_registry
49
+ class ExecutionComponent(MCPComponent):
50
+ """MCP component for function execution and object management."""
59
51
 
60
- import napistu
52
+ def __init__(
53
+ self,
54
+ session_context: Optional[Dict] = None,
55
+ object_registry: Optional[Dict] = None,
56
+ ):
57
+ # Override parent constructor to pass context to state
58
+ self.state = ExecutionState(session_context, object_registry)
61
59
 
62
- _session_context["napistu"] = napistu
60
+ def _create_state(self) -> ExecutionState:
61
+ """This won't be called due to overridden constructor."""
62
+ pass
63
63
 
64
- # Register resources
65
- @mcp.resource("napistu://execution/registry")
66
- async def get_registry():
67
- """
68
- Get a summary of all objects registered with the server.
64
+ async def initialize(self) -> bool:
69
65
  """
70
- return {
71
- "object_count": len(_session_objects),
72
- "object_names": list(_session_objects.keys()),
73
- "object_types": {
74
- name: type(obj).__name__ for name, obj in _session_objects.items()
75
- },
76
- }
66
+ Initialize execution component by setting up the session context.
77
67
 
78
- @mcp.resource("napistu://execution/environment")
79
- async def get_environment_info() -> Dict[str, Any]:
80
- """
81
- Get information about the local Python environment.
68
+ Returns
69
+ -------
70
+ bool
71
+ True if initialization successful
82
72
  """
83
73
  try:
74
+ # Import and add napistu to session context
84
75
  import napistu
85
76
 
86
- napistu_version = getattr(napistu, "__version__", "unknown")
87
- except ImportError:
88
- napistu_version = "not installed"
77
+ self.state.session_context["napistu"] = napistu
89
78
 
90
- import sys
79
+ logger.info("Execution component initialized with napistu module")
80
+ return True
91
81
 
92
- return {
93
- "python_version": sys.version,
94
- "napistu_version": napistu_version,
95
- "platform": sys.platform,
96
- "registered_objects": list(_session_objects.keys()),
97
- }
82
+ except ImportError as e:
83
+ logger.error(f"Failed to import napistu module: {e}")
84
+ return False
98
85
 
99
- # Register tools
100
- @mcp.tool()
101
- async def list_registry() -> Dict[str, Any]:
86
+ def register_object(self, name: str, obj: Any) -> None:
102
87
  """
103
- List all objects registered with the server.
88
+ Register an object with the execution component.
104
89
 
105
- Returns:
106
- Dictionary with information about registered objects
90
+ Args:
91
+ name: Name to reference the object by
92
+ obj: The object to register
107
93
  """
108
- result = {}
94
+ self.state.register_object(name, obj)
109
95
 
110
- for name, obj in _session_objects.items():
111
- obj_type = type(obj).__name__
96
+ def register(self, mcp: FastMCP) -> None:
97
+ """
98
+ Register execution resources and tools with the MCP server.
112
99
 
113
- # Get additional info based on object type
114
- if hasattr(obj, "shape"): # For pandas DataFrame or numpy array
115
- obj_info = {
116
- "type": obj_type,
117
- "shape": str(obj.shape),
118
- }
119
- elif hasattr(obj, "__len__"): # For lists, dicts, etc.
120
- obj_info = {
121
- "type": obj_type,
122
- "length": len(obj),
123
- }
124
- else:
125
- obj_info = {
126
- "type": obj_type,
127
- }
100
+ Parameters
101
+ ----------
102
+ mcp : FastMCP
103
+ FastMCP server instance
104
+ """
128
105
 
129
- result[name] = obj_info
106
+ # Register resources
107
+ @mcp.resource("napistu://execution/registry")
108
+ async def get_registry():
109
+ """Get a summary of all objects registered with the server."""
110
+ return {
111
+ "object_count": len(self.state.session_objects),
112
+ "object_names": list(self.state.session_objects.keys()),
113
+ "object_types": {
114
+ name: type(obj).__name__
115
+ for name, obj in self.state.session_objects.items()
116
+ },
117
+ }
130
118
 
131
- return result
119
+ @mcp.resource("napistu://execution/environment")
120
+ async def get_environment_info() -> Dict[str, Any]:
121
+ """Get information about the local Python environment."""
122
+ try:
123
+ import napistu
132
124
 
133
- @mcp.tool()
134
- async def describe_object(object_name: str) -> Dict[str, Any]:
135
- """
136
- Get detailed information about a registered object.
125
+ napistu_version = getattr(napistu, "__version__", "unknown")
126
+ except ImportError:
127
+ napistu_version = "not installed"
137
128
 
138
- Args:
139
- object_name: Name of the registered object
129
+ import sys
140
130
 
141
- Returns:
142
- Dictionary with object information
143
- """
144
- if object_name not in _session_objects:
145
- return {"error": f"Object '{object_name}' not found in registry"}
146
-
147
- obj = _session_objects[object_name]
148
- obj_type = type(obj).__name__
149
-
150
- # Basic info for all objects
151
- result = {
152
- "name": object_name,
153
- "type": obj_type,
154
- "methods": [],
155
- "attributes": [],
156
- }
131
+ return {
132
+ "python_version": sys.version,
133
+ "napistu_version": napistu_version,
134
+ "platform": sys.platform,
135
+ "registered_objects": list(self.state.session_objects.keys()),
136
+ "session_context": list(self.state.session_context.keys()),
137
+ }
157
138
 
158
- # Add methods and attributes
159
- for name in dir(obj):
160
- if name.startswith("_"):
161
- continue
139
+ # Register tools
140
+ @mcp.tool()
141
+ async def list_registry() -> Dict[str, Any]:
142
+ """List all objects registered with the server."""
143
+ result = {}
162
144
 
163
- try:
164
- attr = getattr(obj, name)
165
-
166
- if callable(attr):
167
- # Method
168
- sig = str(inspect.signature(attr))
169
- doc = inspect.getdoc(attr) or ""
170
- result["methods"].append(
171
- {
172
- "name": name,
173
- "signature": sig,
174
- "docstring": doc,
175
- }
176
- )
145
+ for name, obj in self.state.session_objects.items():
146
+ obj_type = type(obj).__name__
147
+
148
+ # Get additional info based on object type
149
+ if hasattr(obj, "shape"): # For pandas DataFrame or numpy array
150
+ obj_info = {
151
+ "type": obj_type,
152
+ "shape": str(obj.shape),
153
+ }
154
+ elif hasattr(obj, "__len__"): # For lists, dicts, etc.
155
+ obj_info = {
156
+ "type": obj_type,
157
+ "length": len(obj),
158
+ }
177
159
  else:
178
- # Attribute
179
- attr_type = type(attr).__name__
180
- result["attributes"].append(
181
- {
182
- "name": name,
183
- "type": attr_type,
184
- }
185
- )
186
- except Exception:
187
- # Skip attributes that can't be accessed
188
- pass
189
-
190
- return result
191
-
192
- @mcp.tool()
193
- async def execute_function(
194
- function_name: str,
195
- object_name: Optional[str] = None,
196
- args: Optional[List] = None,
197
- kwargs: Optional[Dict] = None,
198
- ) -> Dict[str, Any]:
199
- """
200
- Execute a Napistu function on a registered object.
160
+ obj_info = {
161
+ "type": obj_type,
162
+ }
201
163
 
202
- Args:
203
- function_name: Name of the function to execute
204
- object_name: Name of the registered object to operate on (if method call)
205
- args: Positional arguments to pass to the function
206
- kwargs: Keyword arguments to pass to the function
164
+ result[name] = obj_info
207
165
 
208
- Returns:
209
- Dictionary with execution results
210
- """
211
- args = args or []
212
- kwargs = kwargs or {}
166
+ return result
213
167
 
214
- try:
215
- if object_name:
216
- # Method call on an object
217
- if object_name not in _session_objects:
218
- return {"error": f"Object '{object_name}' not found in registry"}
168
+ @mcp.tool()
169
+ async def describe_object(object_name: str) -> Dict[str, Any]:
170
+ """Get detailed information about a registered object."""
171
+ if object_name not in self.state.session_objects:
172
+ return {"error": f"Object '{object_name}' not found in registry"}
219
173
 
220
- obj = _session_objects[object_name]
174
+ obj = self.state.session_objects[object_name]
175
+ obj_type = type(obj).__name__
221
176
 
222
- if not hasattr(obj, function_name):
223
- return {
224
- "error": f"Method '{function_name}' not found on object '{object_name}'"
225
- }
177
+ # Basic info for all objects
178
+ result = {
179
+ "name": object_name,
180
+ "type": obj_type,
181
+ "methods": [],
182
+ "attributes": [],
183
+ }
184
+
185
+ # Add methods and attributes
186
+ for name in dir(obj):
187
+ if name.startswith("_"):
188
+ continue
189
+
190
+ try:
191
+ attr = getattr(obj, name)
192
+
193
+ if callable(attr):
194
+ # Method
195
+ sig = str(inspect.signature(attr))
196
+ doc = inspect.getdoc(attr) or ""
197
+ result["methods"].append(
198
+ {
199
+ "name": name,
200
+ "signature": sig,
201
+ "docstring": doc,
202
+ }
203
+ )
204
+ else:
205
+ # Attribute
206
+ attr_type = type(attr).__name__
207
+ result["attributes"].append(
208
+ {
209
+ "name": name,
210
+ "type": attr_type,
211
+ }
212
+ )
213
+ except Exception:
214
+ # Skip attributes that can't be accessed
215
+ pass
216
+
217
+ return result
218
+
219
+ @mcp.tool()
220
+ async def execute_function(
221
+ function_name: str,
222
+ object_name: Optional[str] = None,
223
+ args: Optional[List] = None,
224
+ kwargs: Optional[Dict] = None,
225
+ ) -> Dict[str, Any]:
226
+ """Execute a Napistu function on a registered object."""
227
+ args = args or []
228
+ kwargs = kwargs or {}
229
+
230
+ try:
231
+ if object_name:
232
+ # Method call on an object
233
+ if object_name not in self.state.session_objects:
234
+ return {
235
+ "error": f"Object '{object_name}' not found in registry"
236
+ }
237
+
238
+ obj = self.state.session_objects[object_name]
239
+
240
+ if not hasattr(obj, function_name):
241
+ return {
242
+ "error": f"Method '{function_name}' not found on object '{object_name}'"
243
+ }
226
244
 
227
- func = getattr(obj, function_name)
228
- result = func(*args, **kwargs)
229
- else:
230
- # Global function call
231
- if function_name in _session_context:
232
- # Function from session context
233
- func = _session_context[function_name]
245
+ func = getattr(obj, function_name)
234
246
  result = func(*args, **kwargs)
235
247
  else:
236
- # Try to find the function in Napistu
237
- try:
238
- import napistu
248
+ # Global function call
249
+ if function_name in self.state.session_context:
250
+ # Function from session context
251
+ func = self.state.session_context[function_name]
252
+ result = func(*args, **kwargs)
253
+ else:
254
+ # Try to find the function in Napistu
255
+ try:
256
+ import napistu
257
+
258
+ # Split function name by dots for nested modules
259
+ parts = function_name.split(".")
260
+ current = napistu
261
+
262
+ for part in parts[:-1]:
263
+ current = getattr(current, part)
264
+
265
+ func = getattr(current, parts[-1])
266
+ result = func(*args, **kwargs)
267
+ except (ImportError, AttributeError):
268
+ return {"error": f"Function '{function_name}' not found"}
269
+
270
+ # Register result if it's a return value
271
+ if result is not None:
272
+ result_name = f"result_{len(self.state.session_objects) + 1}"
273
+ self.state.session_objects[result_name] = result
274
+
275
+ # Basic type conversion for JSON serialization
276
+ if hasattr(result, "to_dict"):
277
+ # For pandas DataFrame or similar
278
+ return {
279
+ "success": True,
280
+ "result_name": result_name,
281
+ "result_type": type(result).__name__,
282
+ "result_preview": (
283
+ result.to_dict()
284
+ if hasattr(result, "__len__") and len(result) < 10
285
+ else "Result too large to preview"
286
+ ),
287
+ }
288
+ elif hasattr(result, "to_json"):
289
+ # For objects with JSON serialization
290
+ return {
291
+ "success": True,
292
+ "result_name": result_name,
293
+ "result_type": type(result).__name__,
294
+ "result_preview": result.to_json(),
295
+ }
296
+ elif hasattr(result, "__dict__"):
297
+ # For custom objects
298
+ return {
299
+ "success": True,
300
+ "result_name": result_name,
301
+ "result_type": type(result).__name__,
302
+ "result_preview": str(result),
303
+ }
304
+ else:
305
+ # For simple types
306
+ return {
307
+ "success": True,
308
+ "result_name": result_name,
309
+ "result_type": type(result).__name__,
310
+ "result_preview": str(result),
311
+ }
312
+ else:
313
+ return {
314
+ "success": True,
315
+ "result": None,
316
+ }
317
+ except Exception as e:
318
+ import traceback
319
+
320
+ return {
321
+ "error": str(e),
322
+ "traceback": traceback.format_exc(),
323
+ }
239
324
 
240
- # Split function name by dots for nested modules
241
- parts = function_name.split(".")
242
- current = napistu
325
+ @mcp.tool()
326
+ async def search_paths(
327
+ source_node: str,
328
+ target_node: str,
329
+ network_object: str,
330
+ max_depth: int = 3,
331
+ ) -> Dict[str, Any]:
332
+ """Find paths between two nodes in a network."""
333
+ if network_object not in self.state.session_objects:
334
+ return {
335
+ "error": f"Network object '{network_object}' not found in registry"
336
+ }
243
337
 
244
- for part in parts[:-1]:
245
- current = getattr(current, part)
338
+ network = self.state.session_objects[network_object]
246
339
 
247
- func = getattr(current, parts[-1])
248
- result = func(*args, **kwargs)
249
- except (ImportError, AttributeError):
250
- return {"error": f"Function '{function_name}' not found"}
340
+ try:
341
+ # Import necessary modules
342
+ import napistu
343
+
344
+ # Check if the object is a valid network type
345
+ if hasattr(network, "find_paths"):
346
+ # Direct method call
347
+ paths = network.find_paths(
348
+ source_node, target_node, max_depth=max_depth
349
+ )
350
+ elif hasattr(napistu.graph, "find_paths"):
351
+ # Function call
352
+ paths = napistu.graph.find_paths(
353
+ network, source_node, target_node, max_depth=max_depth
354
+ )
355
+ else:
356
+ return {"error": "Could not find appropriate path-finding function"}
251
357
 
252
- # Register result if it's a return value
253
- if result is not None:
254
- result_name = f"result_{len(_session_objects) + 1}"
255
- _session_objects[result_name] = result
358
+ # Register result
359
+ result_name = f"paths_{len(self.state.session_objects) + 1}"
360
+ self.state.session_objects[result_name] = paths
256
361
 
257
- # Basic type conversion for JSON serialization
258
- if hasattr(result, "to_dict"):
259
- # For pandas DataFrame or similar
362
+ # Return results
363
+ if hasattr(paths, "to_dict"):
260
364
  return {
261
365
  "success": True,
262
366
  "result_name": result_name,
263
- "result_type": type(result).__name__,
367
+ "paths_found": (
368
+ len(paths) if hasattr(paths, "__len__") else "unknown"
369
+ ),
264
370
  "result_preview": (
265
- result.to_dict()
266
- if hasattr(result, "__len__") and len(result) < 10
371
+ paths.to_dict()
372
+ if hasattr(paths, "__len__") and len(paths) < 10
267
373
  else "Result too large to preview"
268
374
  ),
269
375
  }
270
- elif hasattr(result, "to_json"):
271
- # For objects with JSON serialization
272
- return {
273
- "success": True,
274
- "result_name": result_name,
275
- "result_type": type(result).__name__,
276
- "result_preview": result.to_json(),
277
- }
278
- elif hasattr(result, "__dict__"):
279
- # For custom objects
280
- return {
281
- "success": True,
282
- "result_name": result_name,
283
- "result_type": type(result).__name__,
284
- "result_preview": str(result),
285
- }
286
376
  else:
287
- # For simple types
288
377
  return {
289
378
  "success": True,
290
379
  "result_name": result_name,
291
- "result_type": type(result).__name__,
292
- "result_preview": str(result),
380
+ "paths_found": (
381
+ len(paths) if hasattr(paths, "__len__") else "unknown"
382
+ ),
383
+ "result_preview": str(paths),
293
384
  }
294
- else:
385
+ except Exception as e:
386
+ import traceback
387
+
295
388
  return {
296
- "success": True,
297
- "result": None,
389
+ "error": str(e),
390
+ "traceback": traceback.format_exc(),
298
391
  }
299
- except Exception as e:
300
- import traceback
301
392
 
302
- return {
303
- "error": str(e),
304
- "traceback": traceback.format_exc(),
305
- }
306
393
 
307
- @mcp.tool()
308
- async def search_paths(
309
- source_node: str,
310
- target_node: str,
311
- network_object: str,
312
- max_depth: int = 3,
313
- ) -> Dict[str, Any]:
314
- """
315
- Find paths between two nodes in a network.
394
+ # Module-level component instance (will be created by server with proper context)
395
+ _component: Optional[ExecutionComponent] = None
316
396
 
317
- Args:
318
- source_node: Source node identifier
319
- target_node: Target node identifier
320
- network_object: Name of the registered network object
321
- max_depth: Maximum path length
322
397
 
323
- Returns:
324
- Dictionary with paths found
325
- """
326
- if network_object not in _session_objects:
327
- return {"error": f"Network object '{network_object}' not found in registry"}
398
+ def create_component(
399
+ session_context: Optional[Dict] = None, object_registry: Optional[Dict] = None
400
+ ) -> ExecutionComponent:
401
+ """
402
+ Create and configure the execution component with session context.
328
403
 
329
- network = _session_objects[network_object]
404
+ Args:
405
+ session_context: Dictionary of the user's current session (e.g., globals())
406
+ object_registry: Dictionary of named objects to make available
330
407
 
331
- try:
332
- # Import necessary modules
333
- import napistu
408
+ Returns:
409
+ ExecutionComponent: Configured execution component
410
+ """
411
+ global _component
412
+ _component = ExecutionComponent(session_context, object_registry)
413
+ return _component
334
414
 
335
- # Check if the object is a valid network type
336
- if hasattr(network, "find_paths"):
337
- # Direct method call
338
- paths = network.find_paths(
339
- source_node, target_node, max_depth=max_depth
340
- )
341
- elif hasattr(napistu.graph, "find_paths"):
342
- # Function call
343
- paths = napistu.graph.find_paths(
344
- network, source_node, target_node, max_depth=max_depth
345
- )
346
- else:
347
- return {"error": "Could not find appropriate path-finding function"}
348
-
349
- # Register result
350
- result_name = f"paths_{len(_session_objects) + 1}"
351
- _session_objects[result_name] = paths
352
-
353
- # Return results
354
- if hasattr(paths, "to_dict"):
355
- return {
356
- "success": True,
357
- "result_name": result_name,
358
- "paths_found": (
359
- len(paths) if hasattr(paths, "__len__") else "unknown"
360
- ),
361
- "result_preview": (
362
- paths.to_dict()
363
- if hasattr(paths, "__len__") and len(paths) < 10
364
- else "Result too large to preview"
365
- ),
366
- }
367
- else:
368
- return {
369
- "success": True,
370
- "result_name": result_name,
371
- "paths_found": (
372
- len(paths) if hasattr(paths, "__len__") else "unknown"
373
- ),
374
- "result_preview": str(paths),
375
- }
376
- except Exception as e:
377
- import traceback
378
415
 
379
- return {
380
- "error": str(e),
381
- "traceback": traceback.format_exc(),
382
- }
416
+ def get_component() -> ExecutionComponent:
417
+ """
418
+ Get the execution component instance.
419
+
420
+ Returns
421
+ -------
422
+ ExecutionComponent
423
+ The execution component instance
424
+
425
+ Raises
426
+ ------
427
+ RuntimeError
428
+ If component hasn't been created yet
429
+ """
430
+ if _component is None:
431
+ raise RuntimeError(
432
+ "Execution component not created. Call create_component() first."
433
+ )
434
+ return _component
435
+
436
+
437
+ def register_object(name: str, obj: Any) -> None:
438
+ """
439
+ Register an object with the execution component (legacy function).
440
+
441
+ Args:
442
+ name: Name to reference the object by
443
+ obj: The object to register
444
+ """
445
+ if _component is None:
446
+ raise RuntimeError(
447
+ "Execution component not created. Call create_component() first."
448
+ )
449
+ _component.register_object(name, obj)