memra 0.2.14__py3-none-any.whl → 0.2.15__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.
memra/cli.py CHANGED
@@ -37,11 +37,18 @@ def run_demo():
37
37
  print("⏳ Waiting for services to be ready...")
38
38
  wait_for_services()
39
39
 
40
- # Step 5: Run the demo
40
+ # Step 5: Start MCP bridge server
41
+ print("🔌 Starting MCP bridge server...")
42
+ if not start_mcp_bridge_server(demo_dir):
43
+ print("❌ Failed to start MCP bridge server.")
44
+ print(" You can start it manually: cd memra-ops && python mcp_bridge_server.py")
45
+ return False
46
+
47
+ # Step 6: Run the demo
41
48
  print("🎯 Running ETL workflow...")
42
49
  success = run_etl_workflow(demo_dir)
43
50
 
44
- # Step 6: Show results
51
+ # Step 7: Show results
45
52
  if success:
46
53
  print("=" * 50)
47
54
  print("🎉 Demo completed successfully!")
@@ -53,6 +60,7 @@ def run_demo():
53
60
  print(" • Check database: docker exec -it memra_postgres psql -U postgres -d local_workflow")
54
61
  print(" • View data: SELECT * FROM invoices ORDER BY created_at DESC;")
55
62
  print(" • Stop services: cd memra-ops && docker compose down")
63
+ print(" • Stop MCP server: pkill -f mcp_bridge_server.py")
56
64
  print(" • Explore code: Check the extracted files in the demo directory")
57
65
  else:
58
66
  print("❌ Demo failed. Check the logs above for details.")
@@ -158,36 +166,132 @@ import asyncio
158
166
  import aiohttp
159
167
  from aiohttp import web
160
168
  import json
169
+ import psycopg2
170
+ import os
161
171
 
162
- async def health_handler(request):
163
- return web.json_response({"status": "healthy"})
172
+ class MCPBridgeServer:
173
+ def __init__(self):
174
+ self.db_url = os.getenv('DATABASE_URL', 'postgresql://postgres:postgres@localhost:5432/local_workflow')
175
+
176
+ async def health_handler(self, request):
177
+ return web.json_response({"status": "healthy", "server": "MCP Bridge"})
178
+
179
+ async def execute_tool_handler(self, request):
180
+ try:
181
+ data = await request.json()
182
+ tool_name = data.get('tool_name', 'unknown')
183
+ tool_params = data.get('parameters', {})
184
+
185
+ if tool_name == 'SQLExecutor':
186
+ return await self.execute_sql(tool_params)
187
+ elif tool_name == 'PostgresInsert':
188
+ return await self.insert_data(tool_params)
189
+ elif tool_name == 'DataValidator':
190
+ return await self.validate_data(tool_params)
191
+ else:
192
+ return web.json_response({
193
+ "success": True,
194
+ "message": f"Demo {tool_name} executed",
195
+ "data": {"demo": True}
196
+ })
197
+ except Exception as e:
198
+ return web.json_response({
199
+ "success": False,
200
+ "error": str(e)
201
+ }, status=500)
202
+
203
+ async def execute_sql(self, params):
204
+ try:
205
+ query = params.get('query', 'SELECT 1')
206
+ conn = psycopg2.connect(self.db_url)
207
+ cursor = conn.cursor()
208
+ cursor.execute(query)
209
+ results = cursor.fetchall()
210
+ cursor.close()
211
+ conn.close()
212
+
213
+ return web.json_response({
214
+ "success": True,
215
+ "results": results,
216
+ "query": query
217
+ })
218
+ except Exception as e:
219
+ return web.json_response({
220
+ "success": False,
221
+ "error": f"SQL execution failed: {str(e)}"
222
+ }, status=500)
223
+
224
+ async def insert_data(self, params):
225
+ try:
226
+ table_name = params.get('table_name', 'invoices')
227
+ data = params.get('data', {})
228
+
229
+ conn = psycopg2.connect(self.db_url)
230
+ cursor = conn.cursor()
231
+
232
+ # Simple insert logic
233
+ columns = list(data.keys())
234
+ values = list(data.values())
235
+ placeholders = ', '.join(['%s'] * len(values))
236
+ column_list = ', '.join(columns)
237
+
238
+ query = f"INSERT INTO {table_name} ({column_list}) VALUES ({placeholders}) RETURNING id"
239
+ cursor.execute(query, values)
240
+ record_id = cursor.fetchone()[0]
241
+
242
+ conn.commit()
243
+ cursor.close()
244
+ conn.close()
245
+
246
+ return web.json_response({
247
+ "success": True,
248
+ "record_id": record_id,
249
+ "message": f"Inserted into {table_name}"
250
+ })
251
+ except Exception as e:
252
+ return web.json_response({
253
+ "success": False,
254
+ "error": f"Insert failed: {str(e)}"
255
+ }, status=500)
256
+
257
+ async def validate_data(self, params):
258
+ try:
259
+ data = params.get('data', {})
260
+
261
+ # Simple validation
262
+ is_valid = True
263
+ errors = []
264
+
265
+ if not data.get('vendor_name'):
266
+ is_valid = False
267
+ errors.append("Missing vendor name")
268
+
269
+ if not data.get('amount') or float(data.get('amount', 0)) <= 0:
270
+ is_valid = False
271
+ errors.append("Invalid amount")
272
+
273
+ return web.json_response({
274
+ "success": True,
275
+ "is_valid": is_valid,
276
+ "errors": errors,
277
+ "validated_data": data
278
+ })
279
+ except Exception as e:
280
+ return web.json_response({
281
+ "success": False,
282
+ "error": f"Validation failed: {str(e)}"
283
+ }, status=500)
164
284
 
165
- async def execute_tool_handler(request):
166
- data = await request.json()
167
- tool_name = data.get('tool_name', 'unknown')
168
-
169
- # Mock responses for demo
170
- if tool_name == 'SQLExecutor':
171
- return web.json_response({
172
- "success": True,
173
- "results": [{"message": "Demo SQL executed"}]
174
- })
175
- elif tool_name == 'PostgresInsert':
176
- return web.json_response({
177
- "success": True,
178
- "id": 1
179
- })
180
- else:
181
- return web.json_response({
182
- "success": True,
183
- "message": f"Demo {tool_name} executed"
184
- })
285
+ # Create server instance
286
+ server = MCPBridgeServer()
185
287
 
288
+ # Create web application
186
289
  app = web.Application()
187
- app.router.add_get('/health', health_handler)
188
- app.router.add_post('/execute_tool', execute_tool_handler)
290
+ app.router.add_get('/health', server.health_handler)
291
+ app.router.add_post('/execute_tool', server.execute_tool_handler)
189
292
 
190
293
  if __name__ == '__main__':
294
+ print("🚀 Starting MCP Bridge Server on port 8081...")
191
295
  web.run_app(app, host='0.0.0.0', port=8081)
192
296
  """
193
297
 
@@ -232,36 +336,132 @@ import asyncio
232
336
  import aiohttp
233
337
  from aiohttp import web
234
338
  import json
339
+ import psycopg2
340
+ import os
235
341
 
236
- async def health_handler(request):
237
- return web.json_response({"status": "healthy"})
342
+ class MCPBridgeServer:
343
+ def __init__(self):
344
+ self.db_url = os.getenv('DATABASE_URL', 'postgresql://postgres:postgres@localhost:5432/local_workflow')
345
+
346
+ async def health_handler(self, request):
347
+ return web.json_response({"status": "healthy", "server": "MCP Bridge"})
348
+
349
+ async def execute_tool_handler(self, request):
350
+ try:
351
+ data = await request.json()
352
+ tool_name = data.get('tool_name', 'unknown')
353
+ tool_params = data.get('parameters', {})
354
+
355
+ if tool_name == 'SQLExecutor':
356
+ return await self.execute_sql(tool_params)
357
+ elif tool_name == 'PostgresInsert':
358
+ return await self.insert_data(tool_params)
359
+ elif tool_name == 'DataValidator':
360
+ return await self.validate_data(tool_params)
361
+ else:
362
+ return web.json_response({
363
+ "success": True,
364
+ "message": f"Demo {tool_name} executed",
365
+ "data": {"demo": True}
366
+ })
367
+ except Exception as e:
368
+ return web.json_response({
369
+ "success": False,
370
+ "error": str(e)
371
+ }, status=500)
372
+
373
+ async def execute_sql(self, params):
374
+ try:
375
+ query = params.get('query', 'SELECT 1')
376
+ conn = psycopg2.connect(self.db_url)
377
+ cursor = conn.cursor()
378
+ cursor.execute(query)
379
+ results = cursor.fetchall()
380
+ cursor.close()
381
+ conn.close()
382
+
383
+ return web.json_response({
384
+ "success": True,
385
+ "results": results,
386
+ "query": query
387
+ })
388
+ except Exception as e:
389
+ return web.json_response({
390
+ "success": False,
391
+ "error": f"SQL execution failed: {str(e)}"
392
+ }, status=500)
393
+
394
+ async def insert_data(self, params):
395
+ try:
396
+ table_name = params.get('table_name', 'invoices')
397
+ data = params.get('data', {})
398
+
399
+ conn = psycopg2.connect(self.db_url)
400
+ cursor = conn.cursor()
401
+
402
+ # Simple insert logic
403
+ columns = list(data.keys())
404
+ values = list(data.values())
405
+ placeholders = ', '.join(['%s'] * len(values))
406
+ column_list = ', '.join(columns)
407
+
408
+ query = f"INSERT INTO {table_name} ({column_list}) VALUES ({placeholders}) RETURNING id"
409
+ cursor.execute(query, values)
410
+ record_id = cursor.fetchone()[0]
411
+
412
+ conn.commit()
413
+ cursor.close()
414
+ conn.close()
415
+
416
+ return web.json_response({
417
+ "success": True,
418
+ "record_id": record_id,
419
+ "message": f"Inserted into {table_name}"
420
+ })
421
+ except Exception as e:
422
+ return web.json_response({
423
+ "success": False,
424
+ "error": f"Insert failed: {str(e)}"
425
+ }, status=500)
426
+
427
+ async def validate_data(self, params):
428
+ try:
429
+ data = params.get('data', {})
430
+
431
+ # Simple validation
432
+ is_valid = True
433
+ errors = []
434
+
435
+ if not data.get('vendor_name'):
436
+ is_valid = False
437
+ errors.append("Missing vendor name")
438
+
439
+ if not data.get('amount') or float(data.get('amount', 0)) <= 0:
440
+ is_valid = False
441
+ errors.append("Invalid amount")
442
+
443
+ return web.json_response({
444
+ "success": True,
445
+ "is_valid": is_valid,
446
+ "errors": errors,
447
+ "validated_data": data
448
+ })
449
+ except Exception as e:
450
+ return web.json_response({
451
+ "success": False,
452
+ "error": f"Validation failed: {str(e)}"
453
+ }, status=500)
238
454
 
239
- async def execute_tool_handler(request):
240
- data = await request.json()
241
- tool_name = data.get('tool_name', 'unknown')
242
-
243
- # Mock responses for demo
244
- if tool_name == 'SQLExecutor':
245
- return web.json_response({
246
- "success": True,
247
- "results": [{"message": "Demo SQL executed"}]
248
- })
249
- elif tool_name == 'PostgresInsert':
250
- return web.json_response({
251
- "success": True,
252
- "id": 1
253
- })
254
- else:
255
- return web.json_response({
256
- "success": True,
257
- "message": f"Demo {tool_name} executed"
258
- })
455
+ # Create server instance
456
+ server = MCPBridgeServer()
259
457
 
458
+ # Create web application
260
459
  app = web.Application()
261
- app.router.add_get('/health', health_handler)
262
- app.router.add_post('/execute_tool', execute_tool_handler)
460
+ app.router.add_get('/health', server.health_handler)
461
+ app.router.add_post('/execute_tool', server.execute_tool_handler)
263
462
 
264
463
  if __name__ == '__main__':
464
+ print("🚀 Starting MCP Bridge Server on port 8081...")
265
465
  web.run_app(app, host='0.0.0.0', port=8081)
266
466
  """
267
467
 
@@ -548,6 +748,49 @@ def run_etl_workflow(demo_dir):
548
748
  print(f"❌ Error running ETL workflow: {e}")
549
749
  return False
550
750
 
751
+ def start_mcp_bridge_server(demo_dir):
752
+ """Start the MCP bridge server"""
753
+ try:
754
+ ops_dir = demo_dir / "memra-ops"
755
+ bridge_script = ops_dir / "mcp_bridge_server.py"
756
+
757
+ if not bridge_script.exists():
758
+ print("❌ MCP bridge server script not found")
759
+ return False
760
+
761
+ # Start the bridge server in the background
762
+ if os.name == 'nt': # Windows
763
+ # Use start command to run in background
764
+ result = subprocess.run([
765
+ 'start', '/B', 'python', str(bridge_script)
766
+ ], cwd=ops_dir, shell=True, capture_output=True, text=True)
767
+ else: # Unix/Linux/Mac
768
+ result = subprocess.run([
769
+ 'python', str(bridge_script)
770
+ ], cwd=ops_dir, start_new_session=True, capture_output=True, text=True)
771
+
772
+ # Wait a moment for the server to start
773
+ time.sleep(3)
774
+
775
+ # Check if the server is responding
776
+ try:
777
+ import requests
778
+ response = requests.get('http://localhost:8081/health', timeout=5)
779
+ if response.status_code == 200:
780
+ print("✅ MCP bridge server started successfully")
781
+ return True
782
+ else:
783
+ print(f"⚠️ MCP bridge server responded with status {response.status_code}")
784
+ return False
785
+ except Exception as e:
786
+ print(f"⚠️ Could not verify MCP bridge server: {e}")
787
+ print(" Server may still be starting up...")
788
+ return True # Assume it's working
789
+
790
+ except Exception as e:
791
+ print(f"❌ Error starting MCP bridge server: {e}")
792
+ return False
793
+
551
794
  def main():
552
795
  """Main CLI entry point"""
553
796
  if len(sys.argv) < 2:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: memra
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: Declarative framework for enterprise workflows with MCP integration - Client SDK
5
5
  Home-page: https://github.com/memra/memra-sdk
6
6
  Author: Memra
@@ -1,5 +1,5 @@
1
1
  memra/__init__.py,sha256=6i82jodsWZPgtRhUaDF3wuQuRDSaboIpew8D3zDN__s,1109
2
- memra/cli.py,sha256=UcLaBUOlLVZRhLCVzzSBLPxGAKLsLNuDmoFAvXLl2gc,20413
2
+ memra/cli.py,sha256=_IlOrTBlv_zBElxxQs13JYsdAuOn9wO1UiogmnwO1Qg,29430
3
3
  memra/discovery.py,sha256=yJIQnrDQu1nyzKykCIuzG_5SW5dIXHCEBLLKRWacIoY,480
4
4
  memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
5
5
  memra/execution.py,sha256=OXpBKxwBIjhACWL_qh8KHNndO8HUgB6gBF81AiQBBm0,34751
@@ -58,9 +58,9 @@ memra/demos/etl_invoice_processing/data/invoices/10352262702.PDF,sha256=aNWnxbYq
58
58
  memra/demos/etl_invoice_processing/data/invoices/10352262884.PDF,sha256=G0eszEhpTOS15hIlMyPMM6iyVw6UZPKycXvS3P42xRc,1010830
59
59
  memra/demos/etl_invoice_processing/data/invoices/10352263346.PDF,sha256=NMfsgrmaNtvNu6xk2aLtubI05I9cuVIbwJMxv_pYPhQ,1089624
60
60
  memra/demos/etl_invoice_processing/data/invoices/10352263429.PDF,sha256=1IzJbmnsKDE1cV6CtyNMENn0Rmpq2tA_BDnZYTYhNhQ,1082893
61
- memra-0.2.14.dist-info/LICENSE,sha256=8OrnTd8DWwLWmUEj5srSLvT4PREfW1Qo1T5gEUIHPws,1062
62
- memra-0.2.14.dist-info/METADATA,sha256=PlO1Afxj26BmFMIyInuh79JXmkzy929YxNa72poNPKg,9427
63
- memra-0.2.14.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
64
- memra-0.2.14.dist-info/entry_points.txt,sha256=LBVjwWoxWJRzNLgeByPn6xUvWFIRnqnemvAZgIoSt08,41
65
- memra-0.2.14.dist-info/top_level.txt,sha256=pXWcTRS1zctdiSUivW4iyKpJ4tcfIu-1BW_fpbal3OY,6
66
- memra-0.2.14.dist-info/RECORD,,
61
+ memra-0.2.15.dist-info/LICENSE,sha256=8OrnTd8DWwLWmUEj5srSLvT4PREfW1Qo1T5gEUIHPws,1062
62
+ memra-0.2.15.dist-info/METADATA,sha256=-a6F6PGuriDeFNJjtGn30f0XFdt64O1pmkZjKwvzCB8,9427
63
+ memra-0.2.15.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
64
+ memra-0.2.15.dist-info/entry_points.txt,sha256=LBVjwWoxWJRzNLgeByPn6xUvWFIRnqnemvAZgIoSt08,41
65
+ memra-0.2.15.dist-info/top_level.txt,sha256=pXWcTRS1zctdiSUivW4iyKpJ4tcfIu-1BW_fpbal3OY,6
66
+ memra-0.2.15.dist-info/RECORD,,
File without changes