memra 0.2.2__py3-none-any.whl → 0.2.4__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 (57) hide show
  1. memra/__init__.py +6 -2
  2. memra/execution.py +53 -0
  3. memra/tool_registry.py +162 -0
  4. memra-0.2.4.dist-info/METADATA +145 -0
  5. memra-0.2.4.dist-info/RECORD +58 -0
  6. {memra-0.2.2.dist-info → memra-0.2.4.dist-info}/WHEEL +1 -1
  7. memra-0.2.4.dist-info/top_level.txt +4 -0
  8. memra-ops/app.py +710 -0
  9. memra-ops/config/config.py +25 -0
  10. memra-ops/config.py +34 -0
  11. memra-ops/scripts/release.py +133 -0
  12. memra-ops/scripts/start_memra.py +334 -0
  13. memra-ops/scripts/stop_memra.py +132 -0
  14. memra-ops/server_tool_registry.py +188 -0
  15. memra-ops/tests/test_llm_text_to_sql.py +115 -0
  16. memra-ops/tests/test_llm_vs_pattern.py +130 -0
  17. memra-ops/tests/test_mcp_schema_aware.py +124 -0
  18. memra-ops/tests/test_schema_aware_sql.py +139 -0
  19. memra-ops/tests/test_schema_aware_sql_simple.py +66 -0
  20. memra-ops/tests/test_text_to_sql_demo.py +140 -0
  21. memra-ops/tools/mcp_bridge_server.py +851 -0
  22. memra-sdk/examples/accounts_payable.py +215 -0
  23. memra-sdk/examples/accounts_payable_client.py +217 -0
  24. memra-sdk/examples/accounts_payable_mcp.py +200 -0
  25. memra-sdk/examples/ask_questions.py +123 -0
  26. memra-sdk/examples/invoice_processing.py +116 -0
  27. memra-sdk/examples/propane_delivery.py +87 -0
  28. memra-sdk/examples/simple_text_to_sql.py +158 -0
  29. memra-sdk/memra/__init__.py +31 -0
  30. memra-sdk/memra/discovery.py +15 -0
  31. memra-sdk/memra/discovery_client.py +49 -0
  32. memra-sdk/memra/execution.py +481 -0
  33. memra-sdk/memra/models.py +99 -0
  34. memra-sdk/memra/tool_registry.py +343 -0
  35. memra-sdk/memra/tool_registry_client.py +106 -0
  36. memra-sdk/scripts/release.py +133 -0
  37. memra-sdk/setup.py +52 -0
  38. memra-workflows/accounts_payable/accounts_payable.py +215 -0
  39. memra-workflows/accounts_payable/accounts_payable_client.py +216 -0
  40. memra-workflows/accounts_payable/accounts_payable_mcp.py +200 -0
  41. memra-workflows/accounts_payable/accounts_payable_smart.py +221 -0
  42. memra-workflows/invoice_processing/invoice_processing.py +116 -0
  43. memra-workflows/invoice_processing/smart_invoice_processor.py +220 -0
  44. memra-workflows/logic/__init__.py +1 -0
  45. memra-workflows/logic/file_tools.py +50 -0
  46. memra-workflows/logic/invoice_tools.py +501 -0
  47. memra-workflows/logic/propane_agents.py +52 -0
  48. memra-workflows/mcp_bridge_server.py +230 -0
  49. memra-workflows/propane_delivery/propane_delivery.py +87 -0
  50. memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +208 -0
  51. memra-workflows/text_to_sql/complete_text_to_sql_system.py +266 -0
  52. memra-workflows/text_to_sql/file_discovery_demo.py +156 -0
  53. memra-0.2.2.dist-info/METADATA +0 -148
  54. memra-0.2.2.dist-info/RECORD +0 -13
  55. memra-0.2.2.dist-info/top_level.txt +0 -1
  56. {memra-0.2.2.dist-info → memra-0.2.4.dist-info}/entry_points.txt +0 -0
  57. {memra-0.2.2.dist-info → memra-0.2.4.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,188 @@
1
+ import importlib
2
+ import logging
3
+ import sys
4
+ import os
5
+ from typing import Dict, Any, List, Optional, Callable
6
+ from pathlib import Path
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class ServerToolRegistry:
11
+ """Server-side registry for managing and executing tools from logic directory"""
12
+
13
+ def __init__(self):
14
+ self.tools: Dict[str, Dict[str, Any]] = {}
15
+ self._add_project_to_path()
16
+ self._load_builtin_tools()
17
+
18
+ def _add_project_to_path(self):
19
+ """Add the project root to Python path so we can import logic modules"""
20
+ # Get the directory containing this file (project root)
21
+ current_dir = Path(__file__).parent
22
+
23
+ if str(current_dir) not in sys.path:
24
+ sys.path.insert(0, str(current_dir))
25
+
26
+ def _load_builtin_tools(self):
27
+ """Load tools from the logic directory"""
28
+ try:
29
+ # Load invoice tools
30
+ from logic.invoice_tools import (
31
+ DatabaseQueryTool, PDFProcessor, OCRTool,
32
+ InvoiceExtractionWorkflow, DataValidator, PostgresInsert
33
+ )
34
+
35
+ self.register_tool("DatabaseQueryTool", DatabaseQueryTool, "memra",
36
+ "Query database schemas and data")
37
+ self.register_tool("PDFProcessor", PDFProcessor, "memra",
38
+ "Process PDF files and extract content")
39
+ self.register_tool("OCRTool", OCRTool, "memra",
40
+ "Perform OCR on images and documents")
41
+ self.register_tool("InvoiceExtractionWorkflow", InvoiceExtractionWorkflow, "memra",
42
+ "Extract structured data from invoices")
43
+ self.register_tool("DataValidator", DataValidator, "memra",
44
+ "Validate data against schemas")
45
+ self.register_tool("PostgresInsert", PostgresInsert, "memra",
46
+ "Insert data into PostgreSQL database")
47
+
48
+ # Load file tools
49
+ from logic.file_tools import FileReader
50
+ self.register_tool("FileReader", FileReader, "memra",
51
+ "Read files from the filesystem")
52
+
53
+ logger.info(f"Loaded {len(self.tools)} builtin tools")
54
+
55
+ except ImportError as e:
56
+ logger.warning(f"Could not load some tools: {e}")
57
+
58
+ def register_tool(self, name: str, tool_class: type, hosted_by: str, description: str):
59
+ """Register a tool in the registry"""
60
+ self.tools[name] = {
61
+ "class": tool_class,
62
+ "hosted_by": hosted_by,
63
+ "description": description
64
+ }
65
+ logger.debug(f"Registered tool: {name} (hosted by {hosted_by})")
66
+
67
+ def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
68
+ """Discover available tools, optionally filtered by host"""
69
+ tools = []
70
+ for name, info in self.tools.items():
71
+ if hosted_by is None or info["hosted_by"] == hosted_by:
72
+ tools.append({
73
+ "name": name,
74
+ "hosted_by": info["hosted_by"],
75
+ "description": info["description"]
76
+ })
77
+ return tools
78
+
79
+ def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
80
+ config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
81
+ """Execute a tool with the given input data"""
82
+ if tool_name not in self.tools:
83
+ return {
84
+ "success": False,
85
+ "error": f"Tool '{tool_name}' not found in registry"
86
+ }
87
+
88
+ tool_info = self.tools[tool_name]
89
+ if tool_info["hosted_by"] != hosted_by:
90
+ return {
91
+ "success": False,
92
+ "error": f"Tool '{tool_name}' is hosted by '{tool_info['hosted_by']}', not '{hosted_by}'"
93
+ }
94
+
95
+ try:
96
+ # Instantiate tool
97
+ tool_class = tool_info["class"]
98
+
99
+ # Some tools need credentials/config for initialization
100
+ if tool_name in ["DatabaseQueryTool", "PostgresInsert"]:
101
+ if "connection" in input_data:
102
+ # Parse connection string or use credentials
103
+ credentials = self._parse_connection(input_data["connection"])
104
+ tool_instance = tool_class(credentials)
105
+ else:
106
+ return {
107
+ "success": False,
108
+ "error": f"Tool '{tool_name}' requires database credentials"
109
+ }
110
+ elif tool_name == "InvoiceExtractionWorkflow":
111
+ # This tool needs to be instantiated to initialize the LLM client
112
+ tool_instance = tool_class()
113
+ else:
114
+ tool_instance = tool_class()
115
+
116
+ # Execute tool based on its type
117
+ result = self._execute_tool_method(tool_instance, tool_name, input_data, config)
118
+
119
+ return {
120
+ "success": True,
121
+ "data": result
122
+ }
123
+
124
+ except Exception as e:
125
+ logger.error(f"Tool execution failed for {tool_name}: {str(e)}")
126
+ return {
127
+ "success": False,
128
+ "error": str(e)
129
+ }
130
+
131
+ def _execute_tool_method(self, tool_instance: Any, tool_name: str,
132
+ input_data: Dict[str, Any], config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
133
+ """Execute the appropriate method on the tool instance"""
134
+
135
+ if tool_name == "DatabaseQueryTool":
136
+ return tool_instance.get_schema("invoices") # Default to invoices table
137
+
138
+ elif tool_name == "PDFProcessor":
139
+ file_path = input_data.get("file", "")
140
+ return tool_instance.process_pdf(file_path)
141
+
142
+ elif tool_name == "OCRTool":
143
+ # Assume PDF processor output is passed as input
144
+ return {"extracted_text": tool_instance.extract_text(input_data)}
145
+
146
+ elif tool_name == "InvoiceExtractionWorkflow":
147
+ text = input_data.get("extracted_text", "")
148
+ schema = input_data.get("invoice_schema", {})
149
+ return tool_instance.extract_data(text, schema)
150
+
151
+ elif tool_name == "DataValidator":
152
+ data = input_data.get("invoice_data", {})
153
+ schema = input_data.get("invoice_schema", {})
154
+ return tool_instance.validate(data, schema)
155
+
156
+ elif tool_name == "PostgresInsert":
157
+ data = input_data.get("invoice_data", {})
158
+ return tool_instance.insert_record("invoices", data)
159
+
160
+ elif tool_name == "FileReader":
161
+ file_path = config.get("path") if config else input_data.get("file_path")
162
+ if not file_path:
163
+ raise ValueError("FileReader requires a file path")
164
+ return tool_instance.read_file(file_path)
165
+
166
+ else:
167
+ raise ValueError(f"Unknown tool execution method for {tool_name}")
168
+
169
+ def _parse_connection(self, connection_string: str) -> Dict[str, Any]:
170
+ """Parse a connection string into credentials"""
171
+ # Simple parser for postgres://user:pass@host:port/database
172
+ if connection_string.startswith("postgres://"):
173
+ # This is a simplified parser - in production you'd use a proper URL parser
174
+ parts = connection_string.replace("postgres://", "").split("/")
175
+ db_part = parts[1] if len(parts) > 1 else "finance"
176
+ auth_host = parts[0].split("@")
177
+ host_port = auth_host[1].split(":") if len(auth_host) > 1 else ["localhost", "5432"]
178
+ user_pass = auth_host[0].split(":") if len(auth_host) > 1 else ["user", "pass"]
179
+
180
+ return {
181
+ "host": host_port[0],
182
+ "port": int(host_port[1]) if len(host_port) > 1 else 5432,
183
+ "database": db_part,
184
+ "user": user_pass[0],
185
+ "password": user_pass[1] if len(user_pass) > 1 else ""
186
+ }
187
+
188
+ return {"connection_string": connection_string}
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify LLM-based text-to-SQL generation
4
+ """
5
+
6
+ import requests
7
+ import json
8
+ import time
9
+
10
+ def test_llm_text_to_sql():
11
+ """Test the new LLM-based text-to-SQL generation"""
12
+ bridge_url = "http://localhost:8081"
13
+ bridge_secret = "test-secret-for-development"
14
+
15
+ # Test questions to verify LLM vs pattern matching
16
+ test_questions = [
17
+ "Show me all invoices from Air Liquide",
18
+ "What is the total amount of all invoices?",
19
+ "How many invoices do we have?",
20
+ "Show me the 3 most recent invoices",
21
+ "What is the average invoice amount?",
22
+ "Find invoices with amounts greater than 1000", # This should test LLM capabilities
23
+ "Show me invoices from last month", # This should test LLM capabilities
24
+ "Which vendor has the highest total invoice amount?" # Complex query for LLM
25
+ ]
26
+
27
+ # Schema info for context
28
+ schema_info = {
29
+ "schema": {
30
+ "invoices": {
31
+ "columns": [
32
+ {"name": "id", "type": "integer"},
33
+ {"name": "vendor_name", "type": "text"},
34
+ {"name": "invoice_number", "type": "text"},
35
+ {"name": "invoice_date", "type": "date"},
36
+ {"name": "total_amount", "type": "numeric"},
37
+ {"name": "tax_amount", "type": "numeric"},
38
+ {"name": "line_items", "type": "jsonb"},
39
+ {"name": "status", "type": "text"}
40
+ ]
41
+ }
42
+ }
43
+ }
44
+
45
+ headers = {
46
+ "Content-Type": "application/json",
47
+ "X-Bridge-Secret": bridge_secret
48
+ }
49
+
50
+ print("🧪 Testing LLM-based Text-to-SQL Generation")
51
+ print("=" * 60)
52
+
53
+ for i, question in enumerate(test_questions, 1):
54
+ print(f"\n🎯 Test {i}: {question}")
55
+ print("-" * 50)
56
+
57
+ # Prepare request
58
+ request_data = {
59
+ "tool_name": "TextToSQLGenerator",
60
+ "input_data": {
61
+ "question": question,
62
+ "schema_info": schema_info
63
+ }
64
+ }
65
+
66
+ try:
67
+ start_time = time.time()
68
+
69
+ # Make request to MCP bridge
70
+ response = requests.post(
71
+ f"{bridge_url}/execute_tool",
72
+ json=request_data,
73
+ headers=headers,
74
+ timeout=30 # Longer timeout for LLM calls
75
+ )
76
+
77
+ end_time = time.time()
78
+ duration = end_time - start_time
79
+
80
+ if response.status_code == 200:
81
+ result = response.json()
82
+
83
+ if result.get("success"):
84
+ data = result.get("data", {})
85
+ sql_query = data.get("generated_sql", "")
86
+ explanation = data.get("explanation", "")
87
+ confidence = data.get("confidence", "unknown")
88
+ method = data.get("method", "unknown")
89
+
90
+ print(f"✅ Success ({duration:.1f}s)")
91
+ print(f"📝 SQL: {sql_query}")
92
+ print(f"💡 Method: {method}")
93
+ print(f"🎯 Confidence: {confidence}")
94
+ print(f"📖 Explanation: {explanation}")
95
+
96
+ # Highlight if this is using LLM
97
+ if method == "llm":
98
+ print("🚀 Using LLM generation!")
99
+ elif method == "pattern_matching":
100
+ print("⚠️ Fallback to pattern matching")
101
+
102
+ else:
103
+ print(f"❌ Failed: {result.get('error', 'Unknown error')}")
104
+
105
+ else:
106
+ print(f"❌ HTTP Error: {response.status_code}")
107
+ print(f"Response: {response.text}")
108
+
109
+ except Exception as e:
110
+ print(f"❌ Exception: {str(e)}")
111
+
112
+ print(f"\n✨ LLM Text-to-SQL testing completed!")
113
+
114
+ if __name__ == "__main__":
115
+ test_llm_text_to_sql()
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to demonstrate LLM vs Pattern Matching for text-to-SQL
4
+ """
5
+
6
+ import requests
7
+ import json
8
+ import time
9
+
10
+ def test_query(question, description=""):
11
+ """Test a single query and return results"""
12
+ bridge_url = "http://localhost:8081"
13
+ bridge_secret = "test-secret-for-development"
14
+
15
+ schema_info = {
16
+ "schema": {
17
+ "invoices": {
18
+ "columns": [
19
+ {"name": "id", "type": "integer"},
20
+ {"name": "vendor_name", "type": "text"},
21
+ {"name": "invoice_number", "type": "text"},
22
+ {"name": "invoice_date", "type": "date"},
23
+ {"name": "total_amount", "type": "numeric"},
24
+ {"name": "tax_amount", "type": "numeric"},
25
+ {"name": "line_items", "type": "jsonb"},
26
+ {"name": "status", "type": "text"}
27
+ ]
28
+ }
29
+ }
30
+ }
31
+
32
+ headers = {
33
+ "Content-Type": "application/json",
34
+ "X-Bridge-Secret": bridge_secret
35
+ }
36
+
37
+ request_data = {
38
+ "tool_name": "TextToSQLGenerator",
39
+ "input_data": {
40
+ "question": question,
41
+ "schema_info": schema_info
42
+ }
43
+ }
44
+
45
+ print(f"\n🎯 {description}")
46
+ print(f"❓ Question: {question}")
47
+ print("-" * 60)
48
+
49
+ try:
50
+ start_time = time.time()
51
+
52
+ response = requests.post(
53
+ f"{bridge_url}/execute_tool",
54
+ json=request_data,
55
+ headers=headers,
56
+ timeout=30
57
+ )
58
+
59
+ end_time = time.time()
60
+ duration = end_time - start_time
61
+
62
+ if response.status_code == 200:
63
+ result = response.json()
64
+
65
+ if result.get("success"):
66
+ data = result.get("data", {})
67
+ sql_query = data.get("generated_sql", "")
68
+ method = data.get("method", "unknown")
69
+ confidence = data.get("confidence", "unknown")
70
+
71
+ print(f"✅ Success ({duration:.1f}s)")
72
+ print(f"📝 SQL: {sql_query}")
73
+ print(f"💡 Method: {method}")
74
+ print(f"🎯 Confidence: {confidence}")
75
+
76
+ if method == "llm":
77
+ print("🚀 LLM Generation - Advanced natural language understanding!")
78
+ elif method == "pattern_matching":
79
+ print("⚙️ Pattern Matching - Rule-based fallback")
80
+ else:
81
+ print("❓ Unknown method")
82
+
83
+ return True
84
+ else:
85
+ print(f"❌ Failed: {result.get('error', 'Unknown error')}")
86
+ return False
87
+ else:
88
+ print(f"❌ HTTP Error: {response.status_code}")
89
+ return False
90
+
91
+ except Exception as e:
92
+ print(f"❌ Exception: {str(e)}")
93
+ return False
94
+
95
+ def main():
96
+ """Test various queries to demonstrate LLM capabilities"""
97
+ print("🧪 LLM vs Pattern Matching Text-to-SQL Comparison")
98
+ print("=" * 70)
99
+
100
+ # Test cases that should showcase LLM capabilities
101
+ test_cases = [
102
+ ("Show me all invoices from Air Liquide", "Simple vendor filtering (both methods can handle)"),
103
+ ("Find invoices with amounts greater than 1000", "Numeric comparison (LLM advantage)"),
104
+ ("Show me invoices from last month", "Date range query (LLM advantage)"),
105
+ ("Which vendor has the highest total invoice amount?", "Complex aggregation with grouping (LLM advantage)"),
106
+ ("Find invoices where the tax amount is more than 10% of total", "Complex calculation (LLM advantage)"),
107
+ ("Show me all pending invoices sorted by amount", "Status filtering with sorting (LLM advantage)"),
108
+ ("What is the average invoice amount?", "Simple aggregation (both methods can handle)"),
109
+ ("List all unique vendors", "Distinct query (both methods can handle)"),
110
+ ("Find invoices with line items containing 'software'", "JSON field querying (LLM advantage)"),
111
+ ("Show me the top 5 vendors by total invoice value", "Complex grouping and ranking (LLM advantage)")
112
+ ]
113
+
114
+ success_count = 0
115
+ llm_count = 0
116
+ pattern_count = 0
117
+
118
+ for question, description in test_cases:
119
+ if test_query(question, description):
120
+ success_count += 1
121
+ # Note: We'd need to parse the response to count methods accurately
122
+
123
+ print(f"\n✨ Testing completed!")
124
+ print(f"📊 {success_count}/{len(test_cases)} queries successful")
125
+ print(f"\n🎉 The LLM-based text-to-SQL system is now active!")
126
+ print(f"🔄 It automatically falls back to pattern matching if LLM fails")
127
+ print(f"🚀 Complex queries now benefit from advanced natural language understanding")
128
+
129
+ if __name__ == "__main__":
130
+ main()
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Direct test of MCP bridge server schema-aware SQL generation
4
+ """
5
+
6
+ import requests
7
+ import json
8
+
9
+ def test_schema_aware_sql():
10
+ """Test schema-aware SQL generation directly via MCP bridge"""
11
+ print("🧪 Testing Schema-Aware SQL Generation (Direct MCP)")
12
+ print("=" * 55)
13
+
14
+ bridge_url = "http://localhost:8081"
15
+ bridge_secret = "test-secret-for-development"
16
+
17
+ # First, get the schema information (simulated)
18
+ schema_info = {
19
+ "schema": {
20
+ "invoices": {
21
+ "columns": [
22
+ {"name": "id", "type": "integer"},
23
+ {"name": "vendor_name", "type": "text"},
24
+ {"name": "invoice_number", "type": "text"},
25
+ {"name": "invoice_date", "type": "date"},
26
+ {"name": "total_amount", "type": "numeric"},
27
+ {"name": "line_items", "type": "jsonb"}
28
+ ]
29
+ }
30
+ }
31
+ }
32
+
33
+ # Test questions
34
+ test_questions = [
35
+ "Show me all invoices from Air Liquide",
36
+ "What is the total amount of all invoices?",
37
+ "How many invoices do we have?",
38
+ "Show me the 3 most recent invoices",
39
+ "What is the average invoice amount?",
40
+ "Count invoices from Microsoft"
41
+ ]
42
+
43
+ print(f"\n🔍 Testing {len(test_questions)} questions with schema context...")
44
+
45
+ for i, question in enumerate(test_questions, 1):
46
+ print(f"\n--- Test {i}: {question} ---")
47
+
48
+ # Prepare request data
49
+ request_data = {
50
+ "tool_name": "TextToSQLGenerator",
51
+ "input_data": {
52
+ "question": question,
53
+ "schema_info": schema_info
54
+ }
55
+ }
56
+
57
+ headers = {
58
+ "Content-Type": "application/json",
59
+ "X-Bridge-Secret": bridge_secret
60
+ }
61
+
62
+ try:
63
+ # Make request to MCP bridge
64
+ response = requests.post(
65
+ f"{bridge_url}/execute_tool",
66
+ json=request_data,
67
+ headers=headers,
68
+ timeout=10
69
+ )
70
+
71
+ if response.status_code == 200:
72
+ result = response.json()
73
+
74
+ if result.get("success"):
75
+ data = result.get("data", {})
76
+ generated_sql = data.get("generated_sql", "")
77
+ explanation = data.get("explanation", "")
78
+ confidence = data.get("confidence", "unknown")
79
+ schema_used = data.get("schema_used", {})
80
+
81
+ print(f"✅ SQL Generated (confidence: {confidence})")
82
+ print(f" Query: {generated_sql}")
83
+ print(f" Explanation: {explanation}")
84
+
85
+ if schema_used:
86
+ columns = schema_used.get("columns", [])
87
+ print(f" Schema columns used: {', '.join(columns)}")
88
+
89
+ # Check if SQL uses actual column names
90
+ schema_columns = ["vendor_name", "total_amount", "invoice_date", "invoice_number"]
91
+ uses_schema = any(col in generated_sql for col in schema_columns)
92
+
93
+ if uses_schema:
94
+ print("🎯 SQL appears to use actual schema column names!")
95
+ else:
96
+ print("⚠️ SQL might not be using schema information")
97
+
98
+ else:
99
+ print(f"❌ SQL generation failed: {result.get('error')}")
100
+ else:
101
+ print(f"❌ HTTP error: {response.status_code}")
102
+ print(f" Response: {response.text}")
103
+
104
+ except Exception as e:
105
+ print(f"❌ Request failed: {str(e)}")
106
+
107
+ print(f"\n✨ Schema-aware SQL generation test completed!")
108
+
109
+ def main():
110
+ """Main function"""
111
+ try:
112
+ # Check if MCP bridge is running
113
+ response = requests.get("http://localhost:8081/health", timeout=5)
114
+ if response.status_code == 200:
115
+ print("✅ MCP Bridge server is running")
116
+ test_schema_aware_sql()
117
+ else:
118
+ print("❌ MCP Bridge server is not responding")
119
+ except Exception as e:
120
+ print(f"❌ Cannot connect to MCP Bridge server: {str(e)}")
121
+ print("💡 Make sure the MCP bridge server is running on port 8081")
122
+
123
+ if __name__ == "__main__":
124
+ main()