tooluniverse 0.2.0__py3-none-any.whl → 1.0.0__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.

Potentially problematic release.


This version of tooluniverse might be problematic. Click here for more details.

Files changed (190) hide show
  1. tooluniverse/__init__.py +340 -4
  2. tooluniverse/admetai_tool.py +84 -0
  3. tooluniverse/agentic_tool.py +563 -0
  4. tooluniverse/alphafold_tool.py +96 -0
  5. tooluniverse/base_tool.py +129 -6
  6. tooluniverse/boltz_tool.py +207 -0
  7. tooluniverse/chem_tool.py +192 -0
  8. tooluniverse/compose_scripts/__init__.py +1 -0
  9. tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
  10. tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
  11. tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
  12. tooluniverse/compose_scripts/literature_tool.py +34 -0
  13. tooluniverse/compose_scripts/output_summarizer.py +279 -0
  14. tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
  15. tooluniverse/compose_scripts/tool_discover.py +705 -0
  16. tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
  17. tooluniverse/compose_tool.py +371 -0
  18. tooluniverse/ctg_tool.py +1002 -0
  19. tooluniverse/custom_tool.py +81 -0
  20. tooluniverse/dailymed_tool.py +108 -0
  21. tooluniverse/data/admetai_tools.json +155 -0
  22. tooluniverse/data/agentic_tools.json +1156 -0
  23. tooluniverse/data/alphafold_tools.json +87 -0
  24. tooluniverse/data/boltz_tools.json +9 -0
  25. tooluniverse/data/chembl_tools.json +16 -0
  26. tooluniverse/data/clait_tools.json +108 -0
  27. tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
  28. tooluniverse/data/compose_tools.json +202 -0
  29. tooluniverse/data/dailymed_tools.json +70 -0
  30. tooluniverse/data/dataset_tools.json +646 -0
  31. tooluniverse/data/disease_target_score_tools.json +712 -0
  32. tooluniverse/data/efo_tools.json +17 -0
  33. tooluniverse/data/embedding_tools.json +319 -0
  34. tooluniverse/data/enrichr_tools.json +31 -0
  35. tooluniverse/data/europe_pmc_tools.json +22 -0
  36. tooluniverse/data/expert_feedback_tools.json +10 -0
  37. tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
  38. tooluniverse/data/fda_drug_labeling_tools.json +1 -1
  39. tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
  40. tooluniverse/data/finder_tools.json +209 -0
  41. tooluniverse/data/gene_ontology_tools.json +113 -0
  42. tooluniverse/data/gwas_tools.json +1082 -0
  43. tooluniverse/data/hpa_tools.json +333 -0
  44. tooluniverse/data/humanbase_tools.json +47 -0
  45. tooluniverse/data/idmap_tools.json +74 -0
  46. tooluniverse/data/mcp_client_tools_example.json +113 -0
  47. tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
  48. tooluniverse/data/medlineplus_tools.json +141 -0
  49. tooluniverse/data/monarch_tools.json +1 -1
  50. tooluniverse/data/openalex_tools.json +36 -0
  51. tooluniverse/data/opentarget_tools.json +1 -1
  52. tooluniverse/data/output_summarization_tools.json +101 -0
  53. tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
  54. tooluniverse/data/packages/categorized_tools.txt +206 -0
  55. tooluniverse/data/packages/cheminformatics_tools.json +347 -0
  56. tooluniverse/data/packages/earth_sciences_tools.json +74 -0
  57. tooluniverse/data/packages/genomics_tools.json +776 -0
  58. tooluniverse/data/packages/image_processing_tools.json +38 -0
  59. tooluniverse/data/packages/machine_learning_tools.json +789 -0
  60. tooluniverse/data/packages/neuroscience_tools.json +62 -0
  61. tooluniverse/data/packages/original_tools.txt +0 -0
  62. tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
  63. tooluniverse/data/packages/scientific_computing_tools.json +560 -0
  64. tooluniverse/data/packages/single_cell_tools.json +453 -0
  65. tooluniverse/data/packages/software_tools.json +4954 -0
  66. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  67. tooluniverse/data/packages/visualization_tools.json +399 -0
  68. tooluniverse/data/pubchem_tools.json +215 -0
  69. tooluniverse/data/pubtator_tools.json +68 -0
  70. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  71. tooluniverse/data/reactome_tools.json +19 -0
  72. tooluniverse/data/semantic_scholar_tools.json +26 -0
  73. tooluniverse/data/special_tools.json +2 -25
  74. tooluniverse/data/tool_composition_tools.json +88 -0
  75. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  76. tooluniverse/data/txagent_client_tools.json +9 -0
  77. tooluniverse/data/uniprot_tools.json +211 -0
  78. tooluniverse/data/url_fetch_tools.json +94 -0
  79. tooluniverse/data/uspto_downloader_tools.json +9 -0
  80. tooluniverse/data/uspto_tools.json +811 -0
  81. tooluniverse/data/xml_tools.json +3275 -0
  82. tooluniverse/dataset_tool.py +296 -0
  83. tooluniverse/default_config.py +165 -0
  84. tooluniverse/efo_tool.py +42 -0
  85. tooluniverse/embedding_database.py +630 -0
  86. tooluniverse/embedding_sync.py +396 -0
  87. tooluniverse/enrichr_tool.py +266 -0
  88. tooluniverse/europe_pmc_tool.py +52 -0
  89. tooluniverse/execute_function.py +1775 -95
  90. tooluniverse/extended_hooks.py +444 -0
  91. tooluniverse/gene_ontology_tool.py +194 -0
  92. tooluniverse/graphql_tool.py +158 -36
  93. tooluniverse/gwas_tool.py +358 -0
  94. tooluniverse/hpa_tool.py +1645 -0
  95. tooluniverse/humanbase_tool.py +389 -0
  96. tooluniverse/logging_config.py +254 -0
  97. tooluniverse/mcp_client_tool.py +764 -0
  98. tooluniverse/mcp_integration.py +413 -0
  99. tooluniverse/mcp_tool_registry.py +925 -0
  100. tooluniverse/medlineplus_tool.py +337 -0
  101. tooluniverse/openalex_tool.py +228 -0
  102. tooluniverse/openfda_adv_tool.py +283 -0
  103. tooluniverse/openfda_tool.py +393 -160
  104. tooluniverse/output_hook.py +1122 -0
  105. tooluniverse/package_tool.py +195 -0
  106. tooluniverse/pubchem_tool.py +158 -0
  107. tooluniverse/pubtator_tool.py +168 -0
  108. tooluniverse/rcsb_pdb_tool.py +38 -0
  109. tooluniverse/reactome_tool.py +108 -0
  110. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  111. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  112. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  113. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  114. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  115. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  116. tooluniverse/remote/expert_feedback_mcp/human_expert_mcp_server.py +1611 -0
  117. tooluniverse/remote/expert_feedback_mcp/simple_test.py +34 -0
  118. tooluniverse/remote/expert_feedback_mcp/start_web_interface.py +91 -0
  119. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  120. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  121. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  122. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  123. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  124. tooluniverse/remote_tool.py +99 -0
  125. tooluniverse/restful_tool.py +53 -30
  126. tooluniverse/scripts/generate_tool_graph.py +408 -0
  127. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  128. tooluniverse/semantic_scholar_tool.py +62 -0
  129. tooluniverse/smcp.py +2452 -0
  130. tooluniverse/smcp_server.py +975 -0
  131. tooluniverse/test/mcp_server_test.py +0 -0
  132. tooluniverse/test/test_admetai_tool.py +370 -0
  133. tooluniverse/test/test_agentic_tool.py +129 -0
  134. tooluniverse/test/test_alphafold_tool.py +71 -0
  135. tooluniverse/test/test_chem_tool.py +37 -0
  136. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  137. tooluniverse/test/test_compose_tool.py +448 -0
  138. tooluniverse/test/test_dailymed.py +69 -0
  139. tooluniverse/test/test_dataset_tool.py +200 -0
  140. tooluniverse/test/test_disease_target_score.py +56 -0
  141. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  142. tooluniverse/test/test_efo.py +31 -0
  143. tooluniverse/test/test_enrichr_tool.py +21 -0
  144. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  145. tooluniverse/test/test_fda_adv.py +95 -0
  146. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  147. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  148. tooluniverse/test/test_gwas_tool.py +139 -0
  149. tooluniverse/test/test_hpa.py +625 -0
  150. tooluniverse/test/test_humanbase_tool.py +20 -0
  151. tooluniverse/test/test_idmap_tools.py +61 -0
  152. tooluniverse/test/test_mcp_server.py +211 -0
  153. tooluniverse/test/test_mcp_tool.py +247 -0
  154. tooluniverse/test/test_medlineplus.py +220 -0
  155. tooluniverse/test/test_openalex_tool.py +32 -0
  156. tooluniverse/test/test_opentargets.py +28 -0
  157. tooluniverse/test/test_pubchem_tool.py +116 -0
  158. tooluniverse/test/test_pubtator_tool.py +37 -0
  159. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  160. tooluniverse/test/test_reactome.py +54 -0
  161. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  162. tooluniverse/test/test_software_tools.py +147 -0
  163. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  164. tooluniverse/test/test_tool_finder.py +26 -0
  165. tooluniverse/test/test_tool_finder_llm.py +252 -0
  166. tooluniverse/test/test_tools_find.py +195 -0
  167. tooluniverse/test/test_uniprot_tools.py +74 -0
  168. tooluniverse/test/test_uspto_tool.py +72 -0
  169. tooluniverse/test/test_xml_tool.py +113 -0
  170. tooluniverse/tool_finder_embedding.py +267 -0
  171. tooluniverse/tool_finder_keyword.py +693 -0
  172. tooluniverse/tool_finder_llm.py +699 -0
  173. tooluniverse/tool_graph_web_ui.py +955 -0
  174. tooluniverse/tool_registry.py +416 -0
  175. tooluniverse/uniprot_tool.py +155 -0
  176. tooluniverse/url_tool.py +253 -0
  177. tooluniverse/uspto_tool.py +240 -0
  178. tooluniverse/utils.py +369 -41
  179. tooluniverse/xml_tool.py +369 -0
  180. tooluniverse-1.0.0.dist-info/METADATA +377 -0
  181. tooluniverse-1.0.0.dist-info/RECORD +186 -0
  182. tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
  183. tooluniverse/generate_mcp_tools.py +0 -113
  184. tooluniverse/mcp_server.py +0 -3340
  185. tooluniverse-0.2.0.dist-info/METADATA +0 -139
  186. tooluniverse-0.2.0.dist-info/RECORD +0 -21
  187. tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
  188. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/WHEEL +0 -0
  189. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
  190. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2013 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Human Expert MCP Tools - Refactored with @register_mcp_tool
4
+ ==========================================================
5
+
6
+ This module contains human expert consultation tools that have been refactored
7
+ to use the new @register_mcp_tool decorator system instead of FastMCP.
8
+
9
+ Tools available:
10
+ - consult_human_expert: Submit questions to human scientific experts
11
+ - get_expert_response: Check for expert responses
12
+ - list_pending_expert_requests: View pending requests (for experts)
13
+ - submit_expert_response: Submit expert responses (for experts)
14
+ - get_expert_status: Get system status
15
+
16
+ Usage:
17
+ from tooluniverse.mcp_tool_registry import start_mcp_server
18
+ # Tools are automatically registered and available on startup
19
+ """
20
+
21
+ import threading
22
+ import time
23
+ import uuid
24
+ import argparse
25
+ import sys
26
+ import webbrowser
27
+ from concurrent.futures import ThreadPoolExecutor
28
+ from datetime import datetime
29
+ from threading import Timer
30
+ from typing import Dict, List, Optional, Any
31
+
32
+ # Import the new registration system
33
+ from tooluniverse.mcp_tool_registry import register_mcp_tool, start_mcp_server
34
+ import requests
35
+
36
+ # Check Flask availability for web interface
37
+ try:
38
+ from flask import Flask, render_template_string, request, jsonify, redirect, url_for
39
+
40
+ FLASK_AVAILABLE = True
41
+ except ImportError:
42
+ FLASK_AVAILABLE = False
43
+
44
+ # =============================================================================
45
+ # HUMAN EXPERT SYSTEM (Same as original)
46
+ # =============================================================================
47
+
48
+
49
+ class HumanExpertSystem:
50
+ """
51
+ Expert consultation system for human-in-the-loop scientific decision support.
52
+
53
+ This system manages the flow of expert consultation requests:
54
+ 1. Receives consultation requests from tools/agents
55
+ 2. Queues requests for human expert review
56
+ 3. Provides interfaces for experts to respond
57
+ 4. Returns expert responses to requesting tools
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ expert_name: str = "Scientific Expert",
63
+ specialty: str = "General Medicine",
64
+ ):
65
+ """Initialize the expert system"""
66
+
67
+ # Expert information
68
+ self.expert_info = {
69
+ "name": expert_name,
70
+ "specialty": specialty,
71
+ "status": "available",
72
+ "last_activity": datetime.now().isoformat(),
73
+ }
74
+
75
+ # Request management
76
+ self.pending_requests: List[Dict] = [] # Requests waiting for expert response
77
+ self.responses: Dict[str, Dict] = {} # Completed expert responses
78
+ self.request_status: Dict[str, str] = {} # Request status tracking
79
+
80
+ # Thread safety
81
+ self.lock = threading.Lock()
82
+
83
+ print("šŸ§‘ā€āš•ļø Human Expert System Initialized")
84
+ print(f" šŸ‘Øā€āš•ļø Expert: {expert_name} ({specialty})")
85
+ print(" šŸ”„ Status: Ready for consultation requests")
86
+
87
+ def submit_request(
88
+ self, request_id: str, question: str, context: Optional[Dict] = None
89
+ ) -> None:
90
+ """Submit a new expert consultation request"""
91
+
92
+ request_data = {
93
+ "id": request_id,
94
+ "question": question,
95
+ "context": context or {},
96
+ "timestamp": datetime.now().isoformat(),
97
+ "status": "pending",
98
+ }
99
+
100
+ with self.lock:
101
+ self.pending_requests.append(request_data)
102
+ self.request_status[request_id] = "pending"
103
+
104
+ print(f"šŸ“ New expert request submitted: {request_id}")
105
+ print(f" ā“ Question: {question[:100]}{'...' if len(question) > 100 else ''}")
106
+
107
+ if context:
108
+ print(f" šŸŽÆ Specialty: {context.get('specialty', 'general')}")
109
+ print(f" ⚔ Priority: {context.get('priority', 'normal')}")
110
+
111
+ def get_pending_requests(self) -> List[Dict]:
112
+ """Get all pending expert consultation requests"""
113
+ with self.lock:
114
+ return self.pending_requests.copy()
115
+
116
+ def submit_response(self, request_id: str, response: str) -> bool:
117
+ """Submit expert response to a consultation request"""
118
+
119
+ with self.lock:
120
+ # Find and remove the request from pending
121
+ for i, req in enumerate(self.pending_requests):
122
+ if req["id"] == request_id:
123
+ request_data = self.pending_requests.pop(i)
124
+
125
+ # Store the response
126
+ self.responses[request_id] = {
127
+ "request": request_data,
128
+ "response": response,
129
+ "expert": self.expert_info["name"],
130
+ "timestamp": datetime.now().isoformat(),
131
+ }
132
+
133
+ # Update status
134
+ self.request_status[request_id] = "completed"
135
+ self.expert_info["last_activity"] = datetime.now().isoformat()
136
+
137
+ print(f"āœ… Expert response submitted for request: {request_id}")
138
+ return True
139
+
140
+ return False
141
+
142
+ def get_response(
143
+ self, request_id: str, timeout_seconds: int = 300
144
+ ) -> Optional[Dict]:
145
+ """Wait for and retrieve expert response"""
146
+
147
+ start_time = time.time()
148
+
149
+ while time.time() - start_time < timeout_seconds:
150
+ with self.lock:
151
+ if request_id in self.responses:
152
+ return self.responses[request_id]
153
+
154
+ time.sleep(1) # Check every second
155
+
156
+ return None # Timeout
157
+
158
+
159
+ # Initialize global expert system
160
+ expert_system = HumanExpertSystem()
161
+
162
+ # Thread executor for async operations
163
+ executor = ThreadPoolExecutor(max_workers=4)
164
+
165
+ # =============================================================================
166
+ # 🧰 MCP TOOL REGISTRATION (Following tutorial standards)
167
+ # =============================================================================
168
+
169
+
170
+ # Register Human Expert Consultation Tool
171
+ @register_mcp_tool(
172
+ tool_type_name="consult_human_expert",
173
+ config={
174
+ "description": "Consult a human expert for complex scientific questions requiring human judgment",
175
+ "parameter_schema": {
176
+ "type": "object",
177
+ "properties": {
178
+ "question": {
179
+ "type": "string",
180
+ "description": "The scientific question or case requiring expert consultation",
181
+ },
182
+ "specialty": {
183
+ "type": "string",
184
+ "default": "general",
185
+ "description": "Area of expertise needed (e.g., 'cardiology', 'oncology', 'pharmacology')",
186
+ },
187
+ "priority": {
188
+ "type": "string",
189
+ "default": "normal",
190
+ "enum": ["low", "normal", "high", "urgent"],
191
+ "description": "Request priority",
192
+ },
193
+ "context": {
194
+ "type": "string",
195
+ "default": "",
196
+ "description": "Additional context or background information",
197
+ },
198
+ "timeout_minutes": {
199
+ "type": "integer",
200
+ "default": 5,
201
+ "description": "How long to wait for expert response (default: 5 minutes)",
202
+ },
203
+ },
204
+ "required": ["question"],
205
+ },
206
+ },
207
+ mcp_config={
208
+ "server_name": "Human Expert Consultation Server",
209
+ "host": "0.0.0.0",
210
+ "port": 9876,
211
+ },
212
+ )
213
+ class ConsultHumanExpertTool:
214
+ """
215
+ Consult a human expert for complex scientific questions requiring human judgment.
216
+
217
+ This tool submits questions to human scientific experts who can provide:
218
+ - Clinical decision support
219
+ - Drug interaction analysis validation
220
+ - Treatment recommendation review
221
+ - Complex case interpretation
222
+ - Quality assurance for AI recommendations
223
+ """
224
+
225
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
226
+ """Execute human expert consultation"""
227
+
228
+ # Extract parameters
229
+ question_any = arguments.get("question")
230
+ if not isinstance(question_any, str) or not question_any:
231
+ return {"status": "error", "error": "'question' must be a non-empty string"}
232
+ question = question_any
233
+ specialty = arguments.get("specialty", "general")
234
+ priority = arguments.get("priority", "normal")
235
+ context = arguments.get("context", "")
236
+ timeout_minutes = arguments.get("timeout_minutes", 5)
237
+
238
+ request_id = str(uuid.uuid4())[:8]
239
+ timeout_seconds = timeout_minutes * 60
240
+
241
+ print(f"\nšŸ”” EXPERT CONSULTATION REQUEST [{request_id}]")
242
+ print(f"šŸŽÆ Specialty: {specialty}")
243
+ print(f"⚔ Priority: {priority}")
244
+ print(f"ā±ļø Timeout: {timeout_minutes} minutes")
245
+
246
+ try:
247
+ # Submit request to expert system
248
+ context_data = {
249
+ "specialty": specialty,
250
+ "priority": priority,
251
+ "context": context,
252
+ }
253
+
254
+ expert_system.submit_request(request_id, question, context_data)
255
+
256
+ # Wait for expert response
257
+ print(f"ā³ Waiting for expert response (max {timeout_minutes} minutes)...")
258
+
259
+ response_data = expert_system.get_response(request_id, timeout_seconds)
260
+
261
+ if response_data:
262
+ return {
263
+ "status": "completed",
264
+ "expert_response": response_data["response"],
265
+ "expert_name": response_data["expert"],
266
+ "response_time": response_data["timestamp"],
267
+ "request_id": request_id,
268
+ "specialty": specialty,
269
+ "priority": priority,
270
+ }
271
+ else:
272
+ return {
273
+ "status": "timeout",
274
+ "message": f"No expert response received within {timeout_minutes} minutes",
275
+ "request_id": request_id,
276
+ "note": "Request may still be processed. Check with get_expert_response tool later.",
277
+ }
278
+
279
+ except Exception as e:
280
+ print(f"āŒ Expert consultation failed: {str(e)}")
281
+ return {
282
+ "status": "error",
283
+ "error": f"Expert consultation failed: {str(e)}",
284
+ "request_id": request_id,
285
+ }
286
+
287
+
288
+ # Register Get Expert Response Tool
289
+ @register_mcp_tool(
290
+ tool_type_name="get_expert_response",
291
+ config={
292
+ "description": "Check if an expert response is available for a previous request",
293
+ "parameter_schema": {
294
+ "type": "object",
295
+ "properties": {
296
+ "request_id": {
297
+ "type": "string",
298
+ "description": "The ID of the expert consultation request",
299
+ }
300
+ },
301
+ "required": ["request_id"],
302
+ },
303
+ },
304
+ mcp_config={"port": 9876}, # Same server as consultation tool
305
+ )
306
+ class GetExpertResponseTool:
307
+ """Tool to check if an expert response is available for a previous request."""
308
+
309
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
310
+ """Check for expert response"""
311
+
312
+ request_id = arguments.get("request_id")
313
+
314
+ try:
315
+ with expert_system.lock:
316
+ if request_id in expert_system.responses:
317
+ response_data = expert_system.responses[request_id]
318
+ return {
319
+ "status": "completed",
320
+ "expert_response": response_data["response"],
321
+ "expert_name": response_data["expert"],
322
+ "response_time": response_data["timestamp"],
323
+ "request_id": request_id,
324
+ }
325
+ elif request_id in expert_system.request_status:
326
+ status = expert_system.request_status[request_id]
327
+ return {
328
+ "status": status,
329
+ "message": f"Request {request_id} is {status}",
330
+ "request_id": request_id,
331
+ }
332
+ else:
333
+ return {
334
+ "status": "not_found",
335
+ "message": f"Request {request_id} not found",
336
+ "request_id": request_id,
337
+ }
338
+
339
+ except Exception as e:
340
+ return {
341
+ "status": "error",
342
+ "error": f"Failed to check expert response: {str(e)}",
343
+ "request_id": request_id,
344
+ }
345
+
346
+
347
+ # Register List Pending Expert Requests Tool
348
+ @register_mcp_tool(
349
+ tool_type_name="list_pending_expert_requests",
350
+ config={
351
+ "description": "List all pending expert consultation requests (for expert use)",
352
+ "parameter_schema": {"type": "object", "properties": {}, "required": []},
353
+ },
354
+ mcp_config={"port": 9876}, # Same server
355
+ )
356
+ class ListPendingExpertRequestsTool:
357
+ """Tool to list all pending expert consultation requests (for expert use)."""
358
+
359
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
360
+ """List pending expert requests"""
361
+
362
+ try:
363
+ pending = expert_system.get_pending_requests()
364
+
365
+ if not pending:
366
+ return {
367
+ "status": "no_requests",
368
+ "message": "No pending expert requests",
369
+ "count": 0,
370
+ }
371
+
372
+ requests_summary = []
373
+ for req in pending:
374
+ age_seconds = (
375
+ datetime.now() - datetime.fromisoformat(req["timestamp"])
376
+ ).total_seconds()
377
+ requests_summary.append(
378
+ {
379
+ "request_id": req["id"],
380
+ "question": req["question"],
381
+ "specialty": req.get("context", {}).get("specialty", "general"),
382
+ "priority": req.get("context", {}).get("priority", "normal"),
383
+ "age_minutes": round(age_seconds / 60, 1),
384
+ "timestamp": req["timestamp"],
385
+ }
386
+ )
387
+
388
+ return {
389
+ "status": "success",
390
+ "pending_requests": requests_summary,
391
+ "count": len(requests_summary),
392
+ "expert_info": expert_system.expert_info,
393
+ }
394
+
395
+ except Exception as e:
396
+ return {
397
+ "status": "error",
398
+ "error": f"Failed to list pending requests: {str(e)}",
399
+ }
400
+
401
+
402
+ # Register Submit Expert Response Tool
403
+ @register_mcp_tool(
404
+ tool_type_name="submit_expert_response",
405
+ config={
406
+ "description": "Submit expert response to a consultation request (for expert use)",
407
+ "parameter_schema": {
408
+ "type": "object",
409
+ "properties": {
410
+ "request_id": {
411
+ "type": "string",
412
+ "description": "The ID of the request to respond to",
413
+ },
414
+ "response": {
415
+ "type": "string",
416
+ "description": "The expert's response and recommendations",
417
+ },
418
+ },
419
+ "required": ["request_id", "response"],
420
+ },
421
+ },
422
+ mcp_config={"port": 9876}, # Same server
423
+ )
424
+ class SubmitExpertResponseTool:
425
+ """Tool to submit expert response to a consultation request (for expert use)."""
426
+
427
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
428
+ """Submit expert response"""
429
+
430
+ request_id_any = arguments.get("request_id")
431
+ response_any = arguments.get("response")
432
+ if not isinstance(request_id_any, str) or not isinstance(response_any, str):
433
+ return {
434
+ "status": "error",
435
+ "error": "'request_id' and 'response' must be strings",
436
+ }
437
+ request_id = request_id_any
438
+ response = response_any
439
+
440
+ try:
441
+ success = expert_system.submit_response(request_id, response)
442
+
443
+ if success:
444
+ return {
445
+ "status": "success",
446
+ "message": f"Expert response submitted for request {request_id}",
447
+ "request_id": request_id,
448
+ "expert": expert_system.expert_info["name"],
449
+ "timestamp": datetime.now().isoformat(),
450
+ }
451
+ else:
452
+ return {
453
+ "status": "failed",
454
+ "message": f"Request {request_id} not found or already completed",
455
+ "request_id": request_id,
456
+ }
457
+
458
+ except Exception as e:
459
+ return {
460
+ "status": "error",
461
+ "error": f"Failed to submit expert response: {str(e)}",
462
+ "request_id": request_id,
463
+ }
464
+
465
+
466
+ # Register Get Expert Status Tool
467
+ @register_mcp_tool(
468
+ tool_type_name="get_expert_status",
469
+ config={
470
+ "description": "Get current expert system status and statistics",
471
+ "parameter_schema": {"type": "object", "properties": {}, "required": []},
472
+ },
473
+ mcp_config={"port": 9876}, # Same server
474
+ )
475
+ class GetExpertStatusTool:
476
+ """Tool to get current expert system status and statistics."""
477
+
478
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
479
+ """Get expert system status"""
480
+
481
+ try:
482
+ pending = expert_system.get_pending_requests()
483
+
484
+ with expert_system.lock:
485
+ total_responses = len(expert_system.responses)
486
+ total_requests = len(expert_system.request_status)
487
+
488
+ return {
489
+ "status": "active",
490
+ "expert_info": expert_system.expert_info,
491
+ "statistics": {
492
+ "pending_requests": len(pending),
493
+ "total_requests": total_requests,
494
+ "completed_responses": total_responses,
495
+ "response_rate": round(
496
+ total_responses / max(total_requests, 1) * 100, 1
497
+ ),
498
+ },
499
+ "system_time": datetime.now().isoformat(),
500
+ "mcp_server_port": 9876,
501
+ }
502
+
503
+ except Exception as e:
504
+ return {
505
+ "status": "error",
506
+ "error": f"Failed to get expert status: {str(e)}",
507
+ }
508
+
509
+
510
+ # =============================================================================
511
+ # 🌐 HTTP API SERVER (Independent from Web Interface)
512
+ # =============================================================================
513
+
514
+
515
+ def create_http_api_server():
516
+ """Create independent HTTP API server for expert system communication"""
517
+ if not FLASK_AVAILABLE:
518
+ return None
519
+
520
+ api_app = Flask(__name__)
521
+
522
+ @api_app.route("/health", methods=["GET"])
523
+ def health_check():
524
+ """Health check endpoint"""
525
+ return {
526
+ "status": "healthy",
527
+ "timestamp": datetime.now().isoformat(),
528
+ "expert_system": "active",
529
+ }
530
+
531
+ @api_app.route("/api/requests", methods=["GET"])
532
+ def get_pending_requests():
533
+ """Get all pending expert requests via HTTP API"""
534
+ try:
535
+ pending = expert_system.get_pending_requests()
536
+
537
+ requests_summary = []
538
+ for req in pending:
539
+ age_seconds = (
540
+ datetime.now() - datetime.fromisoformat(req["timestamp"])
541
+ ).total_seconds()
542
+ requests_summary.append(
543
+ {
544
+ "request_id": req["id"],
545
+ "question": req["question"],
546
+ "specialty": req.get("context", {}).get("specialty", "general"),
547
+ "priority": req.get("context", {}).get("priority", "normal"),
548
+ "age_minutes": round(age_seconds / 60, 1),
549
+ "timestamp": req["timestamp"],
550
+ "context": req.get("context", {}),
551
+ }
552
+ )
553
+
554
+ with expert_system.lock:
555
+ total_responses = len(expert_system.responses)
556
+ total_requests = len(expert_system.request_status)
557
+
558
+ return {
559
+ "status": "success",
560
+ "pending_requests": requests_summary,
561
+ "count": len(requests_summary),
562
+ "expert_info": expert_system.expert_info,
563
+ "statistics": {
564
+ "pending_requests": len(pending),
565
+ "total_requests": total_requests,
566
+ "completed_responses": total_responses,
567
+ "response_rate": round(
568
+ total_responses / max(total_requests, 1) * 100, 1
569
+ ),
570
+ },
571
+ "timestamp": datetime.now().isoformat(),
572
+ }
573
+
574
+ except Exception as e:
575
+ return {"status": "error", "error": str(e)}, 500
576
+
577
+ @api_app.route("/api/requests/<request_id>/respond", methods=["POST"])
578
+ def submit_expert_response_api(request_id):
579
+ """Submit expert response via HTTP API"""
580
+ try:
581
+ data = request.get_json()
582
+ response_text = data.get("response", "").strip()
583
+
584
+ if not response_text:
585
+ return {"status": "error", "error": "Response text is required"}, 400
586
+
587
+ success = expert_system.submit_response(request_id, response_text)
588
+
589
+ if success:
590
+ return {
591
+ "status": "success",
592
+ "message": f"Expert response submitted for request {request_id}",
593
+ "request_id": request_id,
594
+ "expert": expert_system.expert_info["name"],
595
+ "timestamp": datetime.now().isoformat(),
596
+ }
597
+ else:
598
+ return {
599
+ "status": "failed",
600
+ "error": f"Request {request_id} not found or already completed",
601
+ }, 404
602
+
603
+ except Exception as e:
604
+ return {"status": "error", "error": str(e)}, 500
605
+
606
+ @api_app.route("/api/status", methods=["GET"])
607
+ def get_system_status():
608
+ """Get expert system status via HTTP API"""
609
+ try:
610
+ pending = expert_system.get_pending_requests()
611
+
612
+ with expert_system.lock:
613
+ total_responses = len(expert_system.responses)
614
+ total_requests = len(expert_system.request_status)
615
+
616
+ return {
617
+ "status": "active",
618
+ "expert_info": expert_system.expert_info,
619
+ "statistics": {
620
+ "pending_requests": len(pending),
621
+ "total_requests": total_requests,
622
+ "completed_responses": total_responses,
623
+ "response_rate": round(
624
+ total_responses / max(total_requests, 1) * 100, 1
625
+ ),
626
+ },
627
+ "system_time": datetime.now().isoformat(),
628
+ "mcp_server_port": 9876,
629
+ "api_server_port": 9877,
630
+ }
631
+
632
+ except Exception as e:
633
+ return {"status": "error", "error": str(e)}, 500
634
+
635
+ return api_app
636
+
637
+
638
+ # =============================================================================
639
+ # 🌐 WEB INTERFACE (Modified to use HTTP API)
640
+ # =============================================================================
641
+
642
+
643
+ def create_web_app():
644
+ """Create Flask web application for expert interface"""
645
+ if not FLASK_AVAILABLE:
646
+ return None
647
+
648
+ app = Flask(__name__)
649
+
650
+ # Web interface HTML template with modern UI improvements
651
+ WEB_TEMPLATE = """
652
+ <!DOCTYPE html>
653
+ <html lang="en">
654
+ <head>
655
+ <title>šŸ§‘ā€āš•ļø ToolUniverse Human Expert Interface</title>
656
+ <meta name="viewport" content="width=device-width, initial-scale=1">
657
+ <meta charset="UTF-8">
658
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
659
+ <style>
660
+ * { margin: 0; padding: 0; box-sizing: border-box; }
661
+
662
+ body {
663
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
664
+ line-height: 1.6;
665
+ color: #333;
666
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
667
+ min-height: 100vh;
668
+ padding: 20px;
669
+ }
670
+
671
+ .container {
672
+ max-width: 1400px;
673
+ margin: 0 auto;
674
+ background: white;
675
+ border-radius: 20px;
676
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
677
+ overflow: hidden;
678
+ animation: slideUp 0.8s ease-out;
679
+ }
680
+
681
+ @keyframes slideUp {
682
+ from { transform: translateY(30px); opacity: 0; }
683
+ to { transform: translateY(0); opacity: 1; }
684
+ }
685
+
686
+ .header {
687
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
688
+ color: white;
689
+ padding: 30px;
690
+ text-align: center;
691
+ position: relative;
692
+ overflow: hidden;
693
+ }
694
+
695
+ .header::before {
696
+ content: '';
697
+ position: absolute;
698
+ top: -50%;
699
+ left: -50%;
700
+ width: 200%;
701
+ height: 200%;
702
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
703
+ animation: pulse 4s ease-in-out infinite;
704
+ }
705
+
706
+ @keyframes pulse {
707
+ 0%, 100% { transform: scale(1); opacity: 0.5; }
708
+ 50% { transform: scale(1.1); opacity: 0.8; }
709
+ }
710
+
711
+ .header h1 {
712
+ font-size: 2.5em;
713
+ margin-bottom: 10px;
714
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
715
+ position: relative;
716
+ z-index: 1;
717
+ }
718
+
719
+ .header p {
720
+ font-size: 1.2em;
721
+ opacity: 0.9;
722
+ position: relative;
723
+ z-index: 1;
724
+ }
725
+
726
+ .status-bar {
727
+ background: #f8f9fa;
728
+ padding: 15px 30px;
729
+ border-bottom: 1px solid #e9ecef;
730
+ display: flex;
731
+ justify-content: space-between;
732
+ align-items: center;
733
+ flex-wrap: wrap;
734
+ gap: 15px;
735
+ }
736
+
737
+ .status-indicator {
738
+ display: flex;
739
+ align-items: center;
740
+ gap: 10px;
741
+ padding: 8px 15px;
742
+ background: white;
743
+ border-radius: 25px;
744
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
745
+ transition: transform 0.2s;
746
+ }
747
+
748
+ .status-indicator:hover {
749
+ transform: translateY(-2px);
750
+ }
751
+
752
+ .status-dot {
753
+ width: 12px;
754
+ height: 12px;
755
+ border-radius: 50%;
756
+ background: #28a745;
757
+ animation: heartbeat 2s ease-in-out infinite;
758
+ }
759
+
760
+ .status-dot.online {
761
+ background: #28a745;
762
+ animation: heartbeat 2s ease-in-out infinite;
763
+ }
764
+
765
+ .status-dot.offline {
766
+ background: #ffc107;
767
+ animation: blink 1.5s ease-in-out infinite;
768
+ }
769
+
770
+ @keyframes heartbeat {
771
+ 0%, 100% { transform: scale(1); }
772
+ 50% { transform: scale(1.1); }
773
+ }
774
+
775
+ @keyframes blink {
776
+ 0%, 100% { opacity: 1; }
777
+ 50% { opacity: 0.5; }
778
+ }
779
+
780
+ .auto-refresh {
781
+ display: flex;
782
+ align-items: center;
783
+ gap: 8px;
784
+ font-size: 0.9em;
785
+ color: #6c757d;
786
+ }
787
+
788
+ .refresh-countdown {
789
+ background: #667eea;
790
+ color: white;
791
+ padding: 4px 8px;
792
+ border-radius: 12px;
793
+ font-weight: bold;
794
+ min-width: 30px;
795
+ text-align: center;
796
+ }
797
+
798
+ .main-content {
799
+ padding: 30px;
800
+ }
801
+
802
+ .section {
803
+ margin: 30px 0;
804
+ padding: 25px;
805
+ border: 1px solid #e9ecef;
806
+ border-radius: 15px;
807
+ background: white;
808
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
809
+ transition: transform 0.2s, box-shadow 0.2s;
810
+ }
811
+
812
+ .section:hover {
813
+ transform: translateY(-2px);
814
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
815
+ }
816
+
817
+ .section h2 {
818
+ margin-bottom: 20px;
819
+ color: #495057;
820
+ border-bottom: 2px solid #667eea;
821
+ padding-bottom: 10px;
822
+ display: flex;
823
+ align-items: center;
824
+ gap: 10px;
825
+ }
826
+
827
+ .stats {
828
+ display: grid;
829
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
830
+ gap: 20px;
831
+ margin: 25px 0;
832
+ }
833
+
834
+ .stat {
835
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
836
+ color: white;
837
+ padding: 25px;
838
+ border-radius: 15px;
839
+ text-align: center;
840
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
841
+ transition: transform 0.3s, box-shadow 0.3s;
842
+ position: relative;
843
+ overflow: hidden;
844
+ }
845
+
846
+ .stat::before {
847
+ content: '';
848
+ position: absolute;
849
+ top: -50%;
850
+ left: -50%;
851
+ width: 200%;
852
+ height: 200%;
853
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
854
+ transform: scale(0);
855
+ transition: transform 0.3s;
856
+ }
857
+
858
+ .stat:hover::before {
859
+ transform: scale(1);
860
+ }
861
+
862
+ .stat:hover {
863
+ transform: translateY(-5px) scale(1.02);
864
+ box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4);
865
+ }
866
+
867
+ .stat h3 {
868
+ font-size: 2.5em;
869
+ margin-bottom: 10px;
870
+ position: relative;
871
+ z-index: 1;
872
+ }
873
+
874
+ .stat p {
875
+ font-size: 1.1em;
876
+ opacity: 0.9;
877
+ position: relative;
878
+ z-index: 1;
879
+ }
880
+
881
+ .expert-info {
882
+ background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
883
+ color: white;
884
+ padding: 20px;
885
+ border-radius: 15px;
886
+ margin-top: 20px;
887
+ display: flex;
888
+ align-items: center;
889
+ gap: 15px;
890
+ }
891
+
892
+ .expert-avatar {
893
+ width: 60px;
894
+ height: 60px;
895
+ background: rgba(255,255,255,0.2);
896
+ border-radius: 50%;
897
+ display: flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ font-size: 1.5em;
901
+ }
902
+
903
+ .request {
904
+ background: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%);
905
+ margin: 20px 0;
906
+ padding: 25px;
907
+ border-left: 5px solid #667eea;
908
+ border-radius: 15px;
909
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
910
+ transition: all 0.3s ease;
911
+ position: relative;
912
+ overflow: hidden;
913
+ }
914
+
915
+ .request::before {
916
+ content: '';
917
+ position: absolute;
918
+ top: 0;
919
+ left: 0;
920
+ right: 0;
921
+ height: 3px;
922
+ background: linear-gradient(90deg, #667eea, #764ba2);
923
+ opacity: 0;
924
+ transition: opacity 0.3s;
925
+ }
926
+
927
+ .request:hover::before {
928
+ opacity: 1;
929
+ }
930
+
931
+ .request:hover {
932
+ transform: translateY(-3px);
933
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.15);
934
+ }
935
+
936
+ .priority-high { border-left-color: #fd7e14; }
937
+ .priority-urgent {
938
+ border-left-color: #dc3545;
939
+ background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
940
+ animation: urgentPulse 2s infinite;
941
+ }
942
+
943
+ @keyframes urgentPulse {
944
+ 0%, 100% { box-shadow: 0 5px 15px rgba(0,0,0,0.08); }
945
+ 50% { box-shadow: 0 10px 25px rgba(220, 53, 69, 0.2); }
946
+ }
947
+
948
+ .question {
949
+ font-weight: 600;
950
+ margin: 15px 0;
951
+ font-size: 1.1em;
952
+ color: #495057;
953
+ line-height: 1.5;
954
+ }
955
+
956
+ .context {
957
+ color: #6c757d;
958
+ margin: 15px 0;
959
+ padding: 15px;
960
+ background: rgba(108, 117, 125, 0.05);
961
+ border-radius: 10px;
962
+ display: flex;
963
+ flex-wrap: wrap;
964
+ gap: 15px;
965
+ align-items: center;
966
+ }
967
+
968
+ .context-item {
969
+ display: flex;
970
+ align-items: center;
971
+ gap: 5px;
972
+ padding: 5px 10px;
973
+ background: white;
974
+ border-radius: 15px;
975
+ font-size: 0.9em;
976
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
977
+ }
978
+
979
+ .timestamp {
980
+ color: #adb5bd;
981
+ font-size: 0.85em;
982
+ margin-top: 10px;
983
+ display: flex;
984
+ align-items: center;
985
+ gap: 5px;
986
+ }
987
+
988
+ .response-form {
989
+ margin-top: 20px;
990
+ padding: 20px;
991
+ background: rgba(102, 126, 234, 0.02);
992
+ border-radius: 12px;
993
+ border: 1px dashed #667eea;
994
+ }
995
+
996
+ textarea {
997
+ width: 100%;
998
+ min-height: 120px;
999
+ padding: 15px;
1000
+ border: 2px solid #e9ecef;
1001
+ border-radius: 12px;
1002
+ resize: vertical;
1003
+ font-family: inherit;
1004
+ font-size: 1em;
1005
+ line-height: 1.5;
1006
+ transition: border-color 0.3s, box-shadow 0.3s;
1007
+ }
1008
+
1009
+ textarea:focus {
1010
+ outline: none;
1011
+ border-color: #667eea;
1012
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
1013
+ }
1014
+
1015
+ button {
1016
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1017
+ color: white;
1018
+ border: none;
1019
+ padding: 12px 25px;
1020
+ border-radius: 25px;
1021
+ cursor: pointer;
1022
+ font-size: 1em;
1023
+ font-weight: 600;
1024
+ transition: all 0.3s ease;
1025
+ display: inline-flex;
1026
+ align-items: center;
1027
+ gap: 8px;
1028
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
1029
+ }
1030
+
1031
+ button:hover {
1032
+ transform: translateY(-2px);
1033
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
1034
+ }
1035
+
1036
+ button:active {
1037
+ transform: translateY(0);
1038
+ }
1039
+
1040
+ .no-requests {
1041
+ text-align: center;
1042
+ padding: 60px 20px;
1043
+ color: #6c757d;
1044
+ }
1045
+
1046
+ .no-requests i {
1047
+ font-size: 4em;
1048
+ margin-bottom: 20px;
1049
+ color: #28a745;
1050
+ }
1051
+
1052
+ .instructions {
1053
+ background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
1054
+ color: white;
1055
+ border-radius: 15px;
1056
+ padding: 25px;
1057
+ }
1058
+
1059
+ .instructions ul {
1060
+ list-style: none;
1061
+ padding: 0;
1062
+ }
1063
+
1064
+ .instructions li {
1065
+ padding: 8px 0;
1066
+ display: flex;
1067
+ align-items: center;
1068
+ gap: 10px;
1069
+ }
1070
+
1071
+ .instructions li::before {
1072
+ content: 'āœ“';
1073
+ background: rgba(255,255,255,0.2);
1074
+ width: 24px;
1075
+ height: 24px;
1076
+ border-radius: 50%;
1077
+ display: flex;
1078
+ align-items: center;
1079
+ justify-content: center;
1080
+ font-weight: bold;
1081
+ }
1082
+
1083
+ .loading {
1084
+ display: none;
1085
+ text-align: center;
1086
+ padding: 20px;
1087
+ color: #6c757d;
1088
+ }
1089
+
1090
+ .loading i {
1091
+ animation: spin 1s linear infinite;
1092
+ }
1093
+
1094
+ @keyframes spin {
1095
+ from { transform: rotate(0deg); }
1096
+ to { transform: rotate(360deg); }
1097
+ }
1098
+
1099
+ /* Responsive design */
1100
+ @media (max-width: 768px) {
1101
+ body { padding: 10px; }
1102
+ .container { border-radius: 10px; }
1103
+ .header { padding: 20px; }
1104
+ .header h1 { font-size: 2em; }
1105
+ .main-content { padding: 20px; }
1106
+ .section { padding: 15px; }
1107
+ .stats { grid-template-columns: 1fr; }
1108
+ .context { flex-direction: column; align-items: flex-start; }
1109
+ .status-bar { flex-direction: column; }
1110
+ }
1111
+
1112
+ /* Notification styles */
1113
+ .notification {
1114
+ position: fixed;
1115
+ top: 20px;
1116
+ right: 20px;
1117
+ background: #28a745;
1118
+ color: white;
1119
+ padding: 15px 20px;
1120
+ border-radius: 10px;
1121
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
1122
+ transform: translateX(400px);
1123
+ transition: transform 0.3s;
1124
+ z-index: 1000;
1125
+ }
1126
+
1127
+ .notification.show {
1128
+ transform: translateX(0);
1129
+ }
1130
+ </style>
1131
+ <script>
1132
+ let countdownInterval;
1133
+ let refreshInterval = 15; // 15 seconds instead of 30
1134
+ let currentCountdown = refreshInterval;
1135
+
1136
+ function updateCountdown() {
1137
+ const countdownEl = document.getElementById('countdown');
1138
+ if (countdownEl) {
1139
+ countdownEl.textContent = currentCountdown;
1140
+ currentCountdown--;
1141
+
1142
+ if (currentCountdown < 0) {
1143
+ currentCountdown = refreshInterval;
1144
+ refreshPage();
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ function refreshPage() {
1150
+ const loadingEl = document.querySelector('.loading');
1151
+ if (loadingEl) {
1152
+ loadingEl.style.display = 'block';
1153
+ }
1154
+
1155
+ // Add a small delay to show loading animation
1156
+ setTimeout(() => {
1157
+ location.reload();
1158
+ }, 500);
1159
+ }
1160
+
1161
+ function startAutoRefresh() {
1162
+ // Update countdown every second
1163
+ countdownInterval = setInterval(updateCountdown, 1000);
1164
+
1165
+ // Show notification on first load
1166
+ showNotification('Auto-refresh enabled: Updates every ' + refreshInterval + ' seconds', 'info');
1167
+ }
1168
+
1169
+ function showNotification(message, type = 'success') {
1170
+ const notification = document.createElement('div');
1171
+ notification.className = 'notification';
1172
+ notification.innerHTML = `<i class="fas fa-${type === 'success' ? 'check-circle' : 'info-circle'}"></i> ${message}`;
1173
+ document.body.appendChild(notification);
1174
+
1175
+ setTimeout(() => notification.classList.add('show'), 100);
1176
+ setTimeout(() => {
1177
+ notification.classList.remove('show');
1178
+ setTimeout(() => notification.remove(), 300);
1179
+ }, 3000);
1180
+ }
1181
+
1182
+ function submitResponse(form) {
1183
+ const submitBtn = form.querySelector('button[type="submit"]');
1184
+ const originalText = submitBtn.innerHTML;
1185
+
1186
+ submitBtn.disabled = true;
1187
+ submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Submitting...';
1188
+
1189
+ // Let the form submit naturally
1190
+ return true;
1191
+ }
1192
+
1193
+ // Initialize when page loads
1194
+ window.addEventListener('load', function() {
1195
+ startAutoRefresh();
1196
+
1197
+ // Add click handlers to forms
1198
+ document.querySelectorAll('.response-form').forEach(form => {
1199
+ form.addEventListener('submit', function() {
1200
+ return submitResponse(this);
1201
+ });
1202
+ });
1203
+ });
1204
+
1205
+ // Handle page visibility changes (pause refresh when tab is not active)
1206
+ document.addEventListener('visibilitychange', function() {
1207
+ if (document.hidden) {
1208
+ clearInterval(countdownInterval);
1209
+ } else {
1210
+ currentCountdown = refreshInterval;
1211
+ startAutoRefresh();
1212
+ }
1213
+ });
1214
+ </script>
1215
+ </head>
1216
+ <body>
1217
+ <div class="container">
1218
+ <div class="header">
1219
+ <h1><i class="fas fa-user-md"></i> Human Expert Interface</h1>
1220
+ <p>ToolUniverse Scientific Consultation Response System</p>
1221
+ </div>
1222
+
1223
+ <div class="status-bar">
1224
+ <div class="status-indicator">
1225
+ <div class="status-dot"></div>
1226
+ <span><strong>System Active</strong></span>
1227
+ </div>
1228
+ <div class="status-indicator">
1229
+ <div class="status-dot {{ 'online' if mcp_info.connected else 'offline' }}"></div>
1230
+ <span><strong>MCP: {{ mcp_info.host }}:{{ mcp_info.port }}</strong></span>
1231
+ <span class="status-text">({{ 'Connected' if mcp_info.connected else 'Local Mode' }})</span>
1232
+ </div>
1233
+ <div class="auto-refresh">
1234
+ <i class="fas fa-sync-alt"></i>
1235
+ <span>Auto-refresh in</span>
1236
+ <span class="refresh-countdown" id="countdown">{{ refreshInterval|default(15) }}</span>
1237
+ <span>seconds</span>
1238
+ </div>
1239
+ <div class="status-indicator">
1240
+ <i class="fas fa-clock"></i>
1241
+ <span id="current-time"></span>
1242
+ </div>
1243
+ </div>
1244
+
1245
+ <div class="main-content">
1246
+ <div class="section">
1247
+ <h2><i class="fas fa-chart-bar"></i> System Status</h2>
1248
+ <div class="stats">
1249
+ <div class="stat">
1250
+ <h3>{{ status.statistics.pending_requests }}</h3>
1251
+ <p>Pending Requests</p>
1252
+ </div>
1253
+ <div class="stat">
1254
+ <h3>{{ status.statistics.total_requests }}</h3>
1255
+ <p>Total Requests</p>
1256
+ </div>
1257
+ <div class="stat">
1258
+ <h3>{{ status.statistics.response_rate }}%</h3>
1259
+ <p>Response Rate</p>
1260
+ </div>
1261
+ </div>
1262
+ <div class="expert-info">
1263
+ <div class="expert-avatar">
1264
+ <i class="fas fa-user-md"></i>
1265
+ </div>
1266
+ <div>
1267
+ <strong>{{ status.expert_info.name }}</strong><br>
1268
+ <span>{{ status.expert_info.specialty }}</span>
1269
+ </div>
1270
+ </div>
1271
+ </div>
1272
+
1273
+ <div class="section">
1274
+ <h2><i class="fas fa-clipboard-list"></i> Pending Consultation Requests</h2>
1275
+ {% if requests.count == 0 %}
1276
+ <div class="no-requests">
1277
+ <i class="fas fa-check-circle"></i>
1278
+ <h3>All caught up!</h3>
1279
+ <p>No pending requests at this time</p>
1280
+ </div>
1281
+ {% else %}
1282
+ <p><strong>{{ requests.count }}</strong> request(s) waiting for expert response:</p>
1283
+ {% for req in requests.pending_requests %}
1284
+ <div class="request {% if req.priority == 'high' %}priority-high{% elif req.priority == 'urgent' %}priority-urgent{% endif %}">
1285
+ <div class="question">
1286
+ <i class="fas fa-question-circle"></i> {{ req.question }}
1287
+ </div>
1288
+ <div class="context">
1289
+ <div class="context-item">
1290
+ <i class="fas fa-stethoscope"></i>
1291
+ <strong>Specialty:</strong> {{ req.specialty }}
1292
+ </div>
1293
+ <div class="context-item">
1294
+ <i class="fas fa-exclamation-triangle"></i>
1295
+ <strong>Priority:</strong> {{ req.priority }}
1296
+ </div>
1297
+ <div class="context-item">
1298
+ <i class="fas fa-clock"></i>
1299
+ <strong>Age:</strong> {{ req.age_minutes }} minutes
1300
+ </div>
1301
+ </div>
1302
+ <div class="timestamp">
1303
+ <i class="fas fa-calendar-alt"></i>
1304
+ {{ req.timestamp }}
1305
+ </div>
1306
+
1307
+ <form method="POST" action="/submit_response" class="response-form">
1308
+ <input type="hidden" name="request_id" value="{{ req.request_id }}">
1309
+ <textarea name="response" placeholder="Enter your expert response and clinical recommendations here..." required></textarea>
1310
+ <button type="submit">
1311
+ <i class="fas fa-paper-plane"></i>
1312
+ Submit Expert Response
1313
+ </button>
1314
+ </form>
1315
+ </div>
1316
+ {% endfor %}
1317
+ {% endif %}
1318
+ </div>
1319
+
1320
+ <div class="section instructions">
1321
+ <h2><i class="fas fa-info-circle"></i> Instructions</h2>
1322
+ <ul>
1323
+ <li>This page auto-refreshes every 15 seconds for real-time updates</li>
1324
+ <li>Review each consultation request carefully</li>
1325
+ <li>Provide detailed clinical recommendations</li>
1326
+ <li>Prioritize urgent and high-priority requests</li>
1327
+ <li>All responses are logged and timestamped</li>
1328
+ </ul>
1329
+ </div>
1330
+
1331
+ <div class="loading">
1332
+ <i class="fas fa-spinner fa-spin"></i>
1333
+ <p>Refreshing data...</p>
1334
+ </div>
1335
+ </div>
1336
+ </div>
1337
+
1338
+ <script>
1339
+ // Update current time
1340
+ function updateTime() {
1341
+ const now = new Date();
1342
+ const timeStr = now.toLocaleTimeString();
1343
+ const timeEl = document.getElementById('current-time');
1344
+ if (timeEl) {
1345
+ timeEl.textContent = timeStr;
1346
+ }
1347
+ }
1348
+
1349
+ // Update time immediately and then every second
1350
+ updateTime();
1351
+ setInterval(updateTime, 1000);
1352
+ </script>
1353
+ </body>
1354
+ </html>
1355
+ """
1356
+
1357
+ @app.route("/")
1358
+ def index():
1359
+ """Main expert interface page"""
1360
+ try:
1361
+ # Get HTTP API server URL from environment or default to localhost
1362
+ import os
1363
+
1364
+ api_host = os.getenv("EXPERT_FEEDBACK_API_HOST", "localhost")
1365
+ api_port = os.getenv("EXPERT_FEEDBACK_API_PORT", "9877")
1366
+ api_url = f"http://{api_host}:{api_port}/api/requests"
1367
+ api_connected = False
1368
+
1369
+ try:
1370
+ # Get pending requests from HTTP API server
1371
+
1372
+ response = requests.get(api_url, timeout=5)
1373
+
1374
+ if response.status_code == 200:
1375
+ api_data = response.json()
1376
+ api_connected = True
1377
+
1378
+ status = {
1379
+ "expert_info": api_data.get(
1380
+ "expert_info",
1381
+ {
1382
+ "name": "Scientific Expert",
1383
+ "specialty": "General Medicine",
1384
+ },
1385
+ ),
1386
+ "statistics": api_data.get(
1387
+ "statistics",
1388
+ {
1389
+ "pending_requests": 0,
1390
+ "total_requests": 0,
1391
+ "completed_responses": 0,
1392
+ "response_rate": 0.0,
1393
+ },
1394
+ ),
1395
+ }
1396
+
1397
+ requests_data = {
1398
+ "pending_requests": api_data.get("pending_requests", []),
1399
+ "count": api_data.get("count", 0),
1400
+ }
1401
+
1402
+ else:
1403
+ print(f"āš ļø HTTP API returned status {response.status_code}")
1404
+ api_connected = False
1405
+
1406
+ except requests.exceptions.ConnectinError:
1407
+ print(f"āš ļø Cannot connect to HTTP API server at {api_host}:{api_port}")
1408
+ api_connected = False
1409
+ except requests.exceptions.Timeout:
1410
+ print(f"āš ļø HTTP API server timeout at {api_host}:{api_port}")
1411
+ api_connected = False
1412
+ except Exception as e:
1413
+ print(f"āš ļø Error connecting to HTTP API server: {e}")
1414
+ api_connected = False
1415
+
1416
+ # If API connection failed, show error page
1417
+ if not api_connected:
1418
+ status = {
1419
+ "expert_info": {
1420
+ "name": "Connection Error",
1421
+ "specialty": f"Cannot connect to API server at {api_host}:{api_port}",
1422
+ },
1423
+ "statistics": {
1424
+ "pending_requests": 0,
1425
+ "total_requests": 0,
1426
+ "completed_responses": 0,
1427
+ "response_rate": 0.0,
1428
+ },
1429
+ }
1430
+
1431
+ requests_data = {
1432
+ "pending_requests": [],
1433
+ "count": 0,
1434
+ "connection_error": True,
1435
+ }
1436
+
1437
+ # Add API connection info
1438
+ api_info = {
1439
+ "host": api_host,
1440
+ "port": api_port,
1441
+ "url": api_url,
1442
+ "connected": api_connected,
1443
+ }
1444
+
1445
+ return render_template_string(
1446
+ WEB_TEMPLATE, status=status, requests=requests_data, mcp_info=api_info
1447
+ )
1448
+
1449
+ except Exception as e:
1450
+ return f"Error loading interface: {str(e)}", 500
1451
+
1452
+ @app.route("/submit_response", methods=["POST"])
1453
+ def submit_response():
1454
+ """Handle expert response submission via HTTP API"""
1455
+ try:
1456
+ import os
1457
+
1458
+ request_id = request.form.get("request_id")
1459
+ response_text = request.form.get("response")
1460
+
1461
+ if not request_id or not response_text:
1462
+ return "Missing request ID or response", 400
1463
+
1464
+ # Get HTTP API server URL
1465
+ api_host = os.getenv("EXPERT_FEEDBACK_API_HOST", "localhost")
1466
+ api_port = os.getenv("EXPERT_FEEDBACK_API_PORT", "9877")
1467
+ api_url = f"http://{api_host}:{api_port}/api/requests/{request_id}/respond"
1468
+
1469
+ # Submit response via HTTP API
1470
+ payload = {"response": response_text}
1471
+ headers = {"Content-Type": "application/json"}
1472
+
1473
+ try:
1474
+ api_response = requests.post(
1475
+ api_url, json=payload, headers=headers, timeout=10
1476
+ )
1477
+
1478
+ if api_response.status_code == 200:
1479
+ print(
1480
+ f"āœ… Web interface: Expert response submitted via API for {request_id}"
1481
+ )
1482
+ return redirect(url_for("index"))
1483
+ else:
1484
+ print(
1485
+ f"āš ļø API submission failed: {api_response.status_code} - {api_response.text}"
1486
+ )
1487
+ return f"API Error: {api_response.status_code}", 500
1488
+
1489
+ except requests.exceptions.ConnectinError:
1490
+ return "Cannot connect to API server", 503
1491
+ except requests.exceptions.Timeout:
1492
+ return "API server timeout", 504
1493
+ except Exception as e:
1494
+ print(f"āš ļø Error submitting via API: {e}")
1495
+ return f"Submission failed: {str(e)}", 500
1496
+
1497
+ except Exception as e:
1498
+ return f"Error: {str(e)}", 500
1499
+
1500
+ @app.route("/api/status")
1501
+ def api_status():
1502
+ """API endpoint for status information"""
1503
+ try:
1504
+ pending = expert_system.get_pending_requests()
1505
+
1506
+ with expert_system.lock:
1507
+ total_responses = len(expert_system.responses)
1508
+ total_requests = len(expert_system.request_status)
1509
+
1510
+ return jsonify(
1511
+ {
1512
+ "status": "active",
1513
+ "expert_info": expert_system.expert_info,
1514
+ "statistics": {
1515
+ "pending_requests": len(pending),
1516
+ "total_requests": total_requests,
1517
+ "completed_responses": total_responses,
1518
+ "response_rate": round(
1519
+ total_responses / max(total_requests, 1) * 100, 1
1520
+ ),
1521
+ },
1522
+ "system_time": datetime.now().isoformat(),
1523
+ }
1524
+ )
1525
+
1526
+ except Exception as e:
1527
+ return jsonify({"error": str(e)}), 500
1528
+
1529
+ return app
1530
+
1531
+
1532
+ # =============================================================================
1533
+ # šŸ’» TERMINAL INTERFACE (Same as original)
1534
+ # =============================================================================
1535
+
1536
+
1537
+ class ExpertInterface:
1538
+ """Terminal-based expert interface for responding to consultation requests"""
1539
+
1540
+ def __init__(self):
1541
+ self.running = True
1542
+
1543
+ def display_banner(self):
1544
+ """Display welcome banner"""
1545
+ print("\n" + "=" * 80)
1546
+ print("šŸ§‘ā€āš•ļø HUMAN EXPERT TERMINAL INTERFACE")
1547
+ print("=" * 80)
1548
+ print(f"šŸ‘Øā€āš•ļø Expert: {expert_system.expert_info['name']}")
1549
+ print(f"šŸŽÆ Specialty: {expert_system.expert_info['specialty']}")
1550
+ print(f"šŸ•’ System Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
1551
+ print("=" * 80)
1552
+
1553
+ def display_menu(self):
1554
+ """Display main menu options"""
1555
+ print("\nšŸ“‹ EXPERT ACTIONS:")
1556
+ print(" 1ļøāƒ£ View Pending Consultation Requests")
1557
+ print(" 2ļøāƒ£ Respond to Specific Request")
1558
+ print(" 3ļøāƒ£ View System Status")
1559
+ print(" 4ļøāƒ£ View Recent Responses")
1560
+ print(" šŸ”„ Auto-refresh (every 30 seconds)")
1561
+ print(" āŒ Quit")
1562
+ print("-" * 50)
1563
+
1564
+ def view_pending_requests(self):
1565
+ """Display all pending consultation requests"""
1566
+ pending = expert_system.get_pending_requests()
1567
+
1568
+ if not pending:
1569
+ print("\nāœ… No pending consultation requests!")
1570
+ return
1571
+
1572
+ print(f"\nšŸ“‹ PENDING CONSULTATION REQUESTS ({len(pending)}):")
1573
+ print("=" * 80)
1574
+
1575
+ for i, req in enumerate(pending, 1):
1576
+ age_seconds = (
1577
+ datetime.now() - datetime.fromisoformat(req["timestamp"])
1578
+ ).total_seconds()
1579
+ age_minutes = round(age_seconds / 60, 1)
1580
+
1581
+ specialty = req.get("context", {}).get("specialty", "general")
1582
+ priority = req.get("context", {}).get("priority", "normal")
1583
+
1584
+ print(f"\n[{i}] REQUEST ID: {req['id']}")
1585
+ print(
1586
+ f"šŸŽÆ Specialty: {specialty} | ⚔ Priority: {priority} | ā° Age: {age_minutes} min"
1587
+ )
1588
+ print(f"ā“ Question: {req['question']}")
1589
+
1590
+ if req.get("context", {}).get("context"):
1591
+ print(f"šŸ“ Context: {req['context']['context']}")
1592
+
1593
+ print(f"šŸ“… Submitted: {req['timestamp']}")
1594
+ print("-" * 50)
1595
+
1596
+ def respond_to_request(self):
1597
+ """Handle expert response to a specific request"""
1598
+ pending = expert_system.get_pending_requests()
1599
+
1600
+ if not pending:
1601
+ print("\nāœ… No pending requests to respond to!")
1602
+ return
1603
+
1604
+ # Show pending requests
1605
+ self.view_pending_requests()
1606
+
1607
+ # Get request selection
1608
+ try:
1609
+ choice = input(f"\nSelect request (1-{len(pending)}) or 'back': ").strip()
1610
+
1611
+ if choice.lower() == "back":
1612
+ return
1613
+
1614
+ request_idx = int(choice) - 1
1615
+ if request_idx < 0 or request_idx >= len(pending):
1616
+ print("āŒ Invalid selection!")
1617
+ return
1618
+
1619
+ selected_request = pending[request_idx]
1620
+ request_id = selected_request["id"]
1621
+
1622
+ print(f"\nšŸ“ RESPONDING TO REQUEST: {request_id}")
1623
+ print(f"ā“ Question: {selected_request['question']}")
1624
+ print(
1625
+ "\nšŸ’” Enter your expert response (type 'END' on a new line when finished):"
1626
+ )
1627
+
1628
+ # Multi-line response input
1629
+ response_lines = []
1630
+ while True:
1631
+ line = input()
1632
+ if line.strip() == "END":
1633
+ break
1634
+ response_lines.append(line)
1635
+
1636
+ response = "\n".join(response_lines).strip()
1637
+
1638
+ if not response:
1639
+ print("āŒ Empty response! Response not submitted.")
1640
+ return
1641
+
1642
+ # Confirm before submitting
1643
+ print("\nšŸ“‹ RESPONSE PREVIEW:")
1644
+ print("-" * 40)
1645
+ print(response)
1646
+ print("-" * 40)
1647
+
1648
+ confirm = input("\nSubmit this response? (y/N): ").strip().lower()
1649
+
1650
+ if confirm == "y":
1651
+ success = expert_system.submit_response(request_id, response)
1652
+ if success:
1653
+ print(
1654
+ f"āœ… Response submitted successfully for request {request_id}"
1655
+ )
1656
+ else:
1657
+ print(f"āŒ Failed to submit response for request {request_id}")
1658
+ else:
1659
+ print("āŒ Response cancelled.")
1660
+
1661
+ except ValueError:
1662
+ print("āŒ Invalid input! Please enter a number.")
1663
+ except KeyboardInterrupt:
1664
+ print("\nāŒ Response cancelled.")
1665
+
1666
+ def view_system_status(self):
1667
+ """Display system status and statistics"""
1668
+ pending = expert_system.get_pending_requests()
1669
+
1670
+ with expert_system.lock:
1671
+ total_responses = len(expert_system.responses)
1672
+ total_requests = len(expert_system.request_status)
1673
+
1674
+ print("\nšŸ“Š EXPERT SYSTEM STATUS:")
1675
+ print("=" * 50)
1676
+ print(f"šŸ‘Øā€āš•ļø Expert: {expert_system.expert_info['name']}")
1677
+ print(f"šŸŽÆ Specialty: {expert_system.expert_info['specialty']}")
1678
+ print(f"šŸ“Š Pending Requests: {len(pending)}")
1679
+ print(f"šŸ“ˆ Total Requests: {total_requests}")
1680
+ print(f"āœ… Completed Responses: {total_responses}")
1681
+ print(
1682
+ f"šŸ“Š Response Rate: {round(total_responses / max(total_requests, 1) * 100, 1)}%"
1683
+ )
1684
+ print(f"šŸ•’ System Time: {datetime.now().isoformat()}")
1685
+ print("=" * 50)
1686
+
1687
+ def view_recent_responses(self):
1688
+ """Display recent expert responses"""
1689
+ with expert_system.lock:
1690
+ responses = list(expert_system.responses.values())
1691
+
1692
+ if not responses:
1693
+ print("\nšŸ“­ No responses submitted yet!")
1694
+ return
1695
+
1696
+ # Sort by timestamp (most recent first)
1697
+ responses.sort(key=lambda x: x["timestamp"], reverse=True)
1698
+ recent = responses[:5] # Show last 5 responses
1699
+
1700
+ print(f"\nšŸ“¬ RECENT EXPERT RESPONSES ({len(recent)} of {len(responses)}):")
1701
+ print("=" * 80)
1702
+
1703
+ for i, resp in enumerate(recent, 1):
1704
+ req = resp["request"]
1705
+ print(f"\n[{i}] REQUEST ID: {req['id']}")
1706
+ print(
1707
+ f"ā“ Question: {req['question'][:100]}{'...' if len(req['question']) > 100 else ''}"
1708
+ )
1709
+ print(
1710
+ f"āœ… Response: {resp['response'][:150]}{'...' if len(resp['response']) > 150 else ''}"
1711
+ )
1712
+ print(f"šŸ‘Øā€āš•ļø Expert: {resp['expert']}")
1713
+ print(f"šŸ“… Responded: {resp['timestamp']}")
1714
+ print("-" * 50)
1715
+
1716
+ def auto_refresh_loop(self):
1717
+ """Auto-refresh pending requests every 30 seconds"""
1718
+ print("\nšŸ”„ Auto-refresh mode (Ctrl+C to stop)")
1719
+ print("=" * 50)
1720
+
1721
+ try:
1722
+ while self.running:
1723
+ print(
1724
+ f"\nšŸ•’ {datetime.now().strftime('%H:%M:%S')} - Checking for new requests..."
1725
+ )
1726
+
1727
+ pending = expert_system.get_pending_requests()
1728
+ if pending:
1729
+ print(f"šŸ“‹ {len(pending)} pending request(s) found:")
1730
+ for req in pending:
1731
+ age_seconds = (
1732
+ datetime.now() - datetime.fromisoformat(req["timestamp"])
1733
+ ).total_seconds()
1734
+ print(
1735
+ f" • {req['id']}: {req['question'][:60]}{'...' if len(req['question']) > 60 else ''} ({round(age_seconds/60, 1)} min old)"
1736
+ )
1737
+ else:
1738
+ print("āœ… No pending requests")
1739
+
1740
+ print(" (Press Ctrl+C to return to menu)")
1741
+ time.sleep(30)
1742
+
1743
+ except KeyboardInterrupt:
1744
+ print("\nšŸ”„ Auto-refresh stopped")
1745
+
1746
+ def run(self):
1747
+ """Main interface loop"""
1748
+ self.display_banner()
1749
+
1750
+ try:
1751
+ while self.running:
1752
+ self.display_menu()
1753
+ choice = input("Select option: ").strip()
1754
+
1755
+ if choice == "1":
1756
+ self.view_pending_requests()
1757
+ elif choice == "2":
1758
+ self.respond_to_request()
1759
+ elif choice == "3":
1760
+ self.view_system_status()
1761
+ elif choice == "4":
1762
+ self.view_recent_responses()
1763
+ elif choice.lower() in ["refresh", "r", "šŸ”„"]:
1764
+ self.auto_refresh_loop()
1765
+ elif choice.lower() in ["quit", "q", "exit", "āŒ"]:
1766
+ self.running = False
1767
+ print("\nšŸ‘‹ Expert interface closed. Have a great day!")
1768
+ break
1769
+ else:
1770
+ print("āŒ Invalid option! Please try again.")
1771
+
1772
+ if self.running:
1773
+ input("\nPress Enter to continue...")
1774
+
1775
+ except KeyboardInterrupt:
1776
+ print("\n\nšŸ‘‹ Expert interface closed. Have a great day!")
1777
+
1778
+
1779
+ # =============================================================================
1780
+ # šŸš€ STARTUP FUNCTIONS
1781
+ # =============================================================================
1782
+
1783
+
1784
+ def start_http_api_server(port=9877):
1785
+ """Start the HTTP API server for expert system communication"""
1786
+ if not FLASK_AVAILABLE:
1787
+ print("āš ļø Cannot start HTTP API server: Flask not installed")
1788
+ return
1789
+
1790
+ api_app = create_http_api_server()
1791
+ if api_app is None:
1792
+ print("āš ļø Failed to create HTTP API server")
1793
+ return
1794
+
1795
+ import threading
1796
+
1797
+ def run_api_server():
1798
+ print(f"🌐 Starting HTTP API server on port {port}")
1799
+ print(f"šŸ“” API endpoints available at http://0.0.0.0:{port}/api/")
1800
+ try:
1801
+ api_app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False)
1802
+ except Exception as e:
1803
+ print(f"āŒ HTTP API server failed: {e}")
1804
+
1805
+ # Start API server in background thread
1806
+ api_thread = threading.Thread(target=run_api_server, daemon=True)
1807
+ api_thread.start()
1808
+
1809
+ # Give the server a moment to start
1810
+ import time
1811
+
1812
+ time.sleep(1)
1813
+
1814
+ return api_thread
1815
+
1816
+
1817
+ def start_web_server():
1818
+ """Start the Flask web server for expert interface"""
1819
+ if not FLASK_AVAILABLE:
1820
+ print("āŒ Cannot start web interface: Flask not installed")
1821
+ print(" Install with: pip install flask")
1822
+ return
1823
+
1824
+ app = create_web_app()
1825
+ if app:
1826
+ print("🌐 Starting web interface on http://localhost:8090")
1827
+ app.run(host="0.0.0.0", port=8090, debug=False, use_reloader=False)
1828
+
1829
+
1830
+ def run_expert_interface():
1831
+ """Run the terminal-based expert interface"""
1832
+ interface = ExpertInterface()
1833
+ interface.run()
1834
+
1835
+
1836
+ def open_web_interface():
1837
+ """Open web interface in default browser"""
1838
+ if not FLASK_AVAILABLE:
1839
+ print("āš ļø Web interface not available (Flask not installed)")
1840
+ return
1841
+
1842
+ def open_browser():
1843
+ try:
1844
+ webbrowser.open("http://localhost:8090")
1845
+ except Exception as e:
1846
+ print(f"Could not open browser automatically: {str(e)}")
1847
+ print("Please manually open: http://localhost:8090")
1848
+
1849
+ # Delay browser opening to allow server to start
1850
+ Timer(2.0, open_browser).start()
1851
+
1852
+
1853
+ def start_monitoring_thread():
1854
+ """Start background monitoring thread for system health"""
1855
+
1856
+ def monitor():
1857
+ while True:
1858
+ try:
1859
+ # Basic system monitoring
1860
+ pending_count = len(expert_system.get_pending_requests())
1861
+ if pending_count > 0:
1862
+ print(
1863
+ f"šŸ”” {pending_count} pending expert request(s) - experts needed!"
1864
+ )
1865
+ time.sleep(30) # Check every 30 seconds
1866
+ except Exception as e:
1867
+ print(f"Monitoring error: {e}")
1868
+ time.sleep(60)
1869
+
1870
+ monitor_thread = threading.Thread(target=monitor, daemon=True)
1871
+ monitor_thread.start()
1872
+
1873
+
1874
+ # =============================================================================
1875
+ # šŸŽÆ MAIN ENTRY POINT
1876
+ # =============================================================================
1877
+
1878
+
1879
+ def main():
1880
+ """Main entry point for console script"""
1881
+ parser = argparse.ArgumentParser(
1882
+ description="Human Expert Tools - MCP Registration System"
1883
+ )
1884
+ parser.add_argument(
1885
+ "--interface-only",
1886
+ action="store_true",
1887
+ help="Start only the expert terminal interface",
1888
+ )
1889
+ parser.add_argument(
1890
+ "--web-only", action="store_true", help="Start only the web interface"
1891
+ )
1892
+ parser.add_argument(
1893
+ "--no-browser",
1894
+ action="store_true",
1895
+ help="Do not automatically open browser for web interface",
1896
+ )
1897
+ parser.add_argument(
1898
+ "--start-server",
1899
+ action="store_true",
1900
+ help="Start the MCP server with registered tools",
1901
+ )
1902
+ parser.add_argument(
1903
+ "--port",
1904
+ type=int,
1905
+ default=None,
1906
+ help="Specify the MCP server port (default: random available port)",
1907
+ )
1908
+ parser.add_argument(
1909
+ "--mcp-host",
1910
+ type=str,
1911
+ default="localhost",
1912
+ help="MCP server host for web interface to connect to (default: localhost)",
1913
+ )
1914
+ parser.add_argument(
1915
+ "--mcp-port",
1916
+ type=int,
1917
+ default=9876,
1918
+ help="MCP server port for web interface to connect to (default: 9876)",
1919
+ )
1920
+ args = parser.parse_args()
1921
+
1922
+ # Set environment variables for API server connection (used by web interface)
1923
+ import os
1924
+
1925
+ if args.mcp_host != "localhost":
1926
+ os.environ["EXPERT_FEEDBACK_API_HOST"] = args.mcp_host
1927
+ # Map MCP port to API port for backward compatibility
1928
+ api_port = args.mcp_port + 1 if args.mcp_port != 9876 else 9877
1929
+ os.environ["EXPERT_FEEDBACK_API_PORT"] = str(api_port)
1930
+
1931
+ if args.interface_only:
1932
+ # Run only the terminal expert interface
1933
+ print("šŸ’» Starting Expert Terminal Interface...")
1934
+ run_expert_interface()
1935
+ elif args.web_only:
1936
+ # Run only the web interface
1937
+ if not FLASK_AVAILABLE:
1938
+ print("āŒ Cannot start web interface: Flask not installed")
1939
+ print(" Install with: pip install flask")
1940
+ sys.exit(1)
1941
+
1942
+ print("🌐 Starting Human Expert Web Interface...")
1943
+ print(f"šŸ”— Will connect to API server at: {args.mcp_host}:{api_port}")
1944
+ print("šŸ’” To connect to a different API server, use:")
1945
+ print(
1946
+ " tooluniverse-expert-feedback --web-only --mcp-host <api-host> --mcp-port <mcp-port>"
1947
+ )
1948
+ print(" (API port will be automatically calculated as MCP port + 1)")
1949
+ if not args.no_browser:
1950
+ open_web_interface()
1951
+ start_web_server()
1952
+ elif args.start_server:
1953
+ # Determine the port to use
1954
+ port = args.port if args.port is not None else 9876
1955
+
1956
+ # Tools are already registered via decorators at module import time
1957
+ print("šŸš€ Starting MCP Server with Expert Tools...")
1958
+ print(f"šŸ”Œ MCP Server Port: {port}")
1959
+ print("šŸ“‹ Registered tools:")
1960
+ print(" - consult_human_expert: Submit questions to human experts")
1961
+ print(" - get_expert_response: Check for expert responses")
1962
+ print(" - list_pending_expert_requests: View pending requests (for experts)")
1963
+ print(" - submit_expert_response: Submit expert responses (for experts)")
1964
+ print(" - get_expert_status: Get system status")
1965
+
1966
+ print("\nšŸ“” Starting HTTP API Server...")
1967
+ api_port = 9877 # Default API port
1968
+ try:
1969
+ start_http_api_server(api_port)
1970
+ print(f"āœ… HTTP API server started on port {api_port}")
1971
+ print(f"🌐 API endpoints: http://localhost:{api_port}/api/")
1972
+ except Exception as e:
1973
+ print(f"āš ļø HTTP API server failed to start: {e}")
1974
+
1975
+ print("\nšŸ”„ Starting background monitoring...")
1976
+ start_monitoring_thread()
1977
+
1978
+ print("\nšŸŽÆ Expert Interface Options:")
1979
+ print(
1980
+ f" 🌐 Web Interface: tooluniverse-expert-feedback --web-only --mcp-host localhost --mcp-port {api_port}"
1981
+ )
1982
+ print(" šŸ’» Terminal Interface: tooluniverse-expert-feedback --interface-only")
1983
+ print("\nšŸ’” For remote experts, use:")
1984
+ print(" export EXPERT_FEEDBACK_API_HOST=<this-server-ip>")
1985
+ print(f" export EXPERT_FEEDBACK_API_PORT={api_port}")
1986
+
1987
+ # Start the MCP server using the standard method
1988
+ print(f"\nāœ… Starting MCP server on port {port}...")
1989
+ start_mcp_server()
1990
+ else:
1991
+ # Default: show usage information
1992
+ print("šŸ§‘ā€āš•ļø Human Expert MCP Tools - Console Script")
1993
+ print("=" * 50)
1994
+ print("This tool provides human expert consultation through MCP protocol.")
1995
+ print()
1996
+ print("Usage options:")
1997
+ print(" --start-server Start MCP server with expert tools")
1998
+ print(" --port PORT Specify MCP server port (default: 9876)")
1999
+ print(" --interface-only Start expert terminal interface")
2000
+ print(" --web-only Start expert web interface")
2001
+ print(" --no-browser Don't auto-open browser (with --web-only)")
2002
+ print()
2003
+ print("Examples:")
2004
+ print(" tooluniverse-expert-feedback --start-server")
2005
+ print(" tooluniverse-expert-feedback --start-server --port 8000")
2006
+ print(" tooluniverse-expert-feedback --web-only")
2007
+ print()
2008
+ print("The tools are automatically registered and available when imported")
2009
+ print("by ToolUniverse or when the MCP server is started.")
2010
+
2011
+
2012
+ if __name__ == "__main__":
2013
+ main()