memra 0.2.0__py3-none-any.whl → 0.2.2__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/__init__.py +12 -9
- memra/execution.py +30 -26
- memra/models.py +1 -0
- memra/tool_registry.py +140 -141
- {memra-0.2.0.dist-info → memra-0.2.2.dist-info}/METADATA +34 -47
- memra-0.2.2.dist-info/RECORD +13 -0
- {memra-0.2.0.dist-info → memra-0.2.2.dist-info}/WHEEL +1 -1
- memra-0.2.2.dist-info/top_level.txt +1 -0
- memra-0.2.0.dist-info/RECORD +0 -19
- memra-0.2.0.dist-info/top_level.txt +0 -2
- memra-sdk-package/examples/accounts_payable_client.py +0 -207
- memra-sdk-package/memra/__init__.py +0 -28
- memra-sdk-package/memra/discovery_client.py +0 -49
- memra-sdk-package/memra/execution.py +0 -418
- memra-sdk-package/memra/models.py +0 -98
- memra-sdk-package/memra/tool_registry_client.py +0 -105
- {memra-0.2.0.dist-info/licenses → memra-0.2.2.dist-info}/LICENSE +0 -0
- {memra-0.2.0.dist-info → memra-0.2.2.dist-info}/entry_points.txt +0 -0
memra/__init__.py
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
"""
|
2
|
-
Memra SDK - Declarative
|
2
|
+
Memra SDK - Declarative AI Workflows
|
3
3
|
|
4
|
-
A
|
5
|
-
|
4
|
+
A framework for building AI-powered business workflows using a declarative approach.
|
5
|
+
Think of it as "Kubernetes for business logic" where agents are the pods and
|
6
|
+
departments are the deployments.
|
6
7
|
"""
|
7
8
|
|
8
|
-
__version__ = "0.2.
|
9
|
-
__author__ = "Memra"
|
10
|
-
__email__ = "info@memra.co"
|
9
|
+
__version__ = "0.2.2"
|
11
10
|
|
12
11
|
# Core imports
|
13
|
-
from .models import Agent, Department,
|
12
|
+
from .models import Agent, Department, Tool
|
14
13
|
from .execution import ExecutionEngine
|
15
14
|
|
16
15
|
# Make key classes available at package level
|
17
16
|
__all__ = [
|
18
17
|
"Agent",
|
19
18
|
"Department",
|
20
|
-
"LLM",
|
21
19
|
"Tool",
|
22
20
|
"ExecutionEngine",
|
23
21
|
"__version__"
|
24
|
-
]
|
22
|
+
]
|
23
|
+
|
24
|
+
# Optional: Add version check for compatibility
|
25
|
+
import sys
|
26
|
+
if sys.version_info < (3, 8):
|
27
|
+
raise RuntimeError("Memra requires Python 3.8 or higher")
|
memra/execution.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
import time
|
2
2
|
import logging
|
3
|
-
import os
|
4
3
|
from typing import Dict, Any, List, Optional
|
5
4
|
from .models import Department, Agent, DepartmentResult, ExecutionTrace, DepartmentAudit
|
5
|
+
from .tool_registry import ToolRegistry
|
6
|
+
from .tool_registry_client import ToolRegistryClient
|
6
7
|
|
7
8
|
logger = logging.getLogger(__name__)
|
8
9
|
|
@@ -10,16 +11,8 @@ class ExecutionEngine:
|
|
10
11
|
"""Engine that executes department workflows by coordinating agents and tools"""
|
11
12
|
|
12
13
|
def __init__(self):
|
13
|
-
|
14
|
-
|
15
|
-
from .tool_registry_client import ToolRegistryClient
|
16
|
-
self.tool_registry = ToolRegistryClient()
|
17
|
-
logger.info("Using API client for tool execution")
|
18
|
-
else:
|
19
|
-
from .tool_registry import ToolRegistry
|
20
|
-
self.tool_registry = ToolRegistry()
|
21
|
-
logger.info("Using local tool registry")
|
22
|
-
|
14
|
+
self.tool_registry = ToolRegistry()
|
15
|
+
self.api_client = ToolRegistryClient()
|
23
16
|
self.last_execution_audit: Optional[DepartmentAudit] = None
|
24
17
|
|
25
18
|
def execute_department(self, department: Department, input_data: Dict[str, Any]) -> DepartmentResult:
|
@@ -203,24 +196,33 @@ class ExecutionEngine:
|
|
203
196
|
tool_name = tool_spec["name"] if isinstance(tool_spec, dict) else tool_spec.name
|
204
197
|
hosted_by = tool_spec.get("hosted_by", "memra") if isinstance(tool_spec, dict) else tool_spec.hosted_by
|
205
198
|
|
206
|
-
# Extract tool-level config if available, otherwise use agent config
|
207
|
-
tool_config = None
|
208
|
-
if isinstance(tool_spec, dict) and "config" in tool_spec:
|
209
|
-
tool_config = tool_spec["config"]
|
210
|
-
elif agent.config:
|
211
|
-
tool_config = agent.config
|
212
|
-
|
213
199
|
print(f"⚡ {agent.role}: Using tool {i}/{len(agent.tools)}: {tool_name}")
|
214
200
|
|
215
201
|
trace.tools_invoked.append(tool_name)
|
216
202
|
|
217
203
|
# Get tool from registry and execute
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
204
|
+
print(f"🔍 {agent.role}: Tool {tool_name} is hosted by: {hosted_by}")
|
205
|
+
if hosted_by == "memra":
|
206
|
+
# Use API client for server-hosted tools
|
207
|
+
print(f"🌐 {agent.role}: Using API client for {tool_name}")
|
208
|
+
config_to_pass = tool_spec.get("config") if isinstance(tool_spec, dict) else tool_spec.config
|
209
|
+
tool_result = self.api_client.execute_tool(
|
210
|
+
tool_name,
|
211
|
+
hosted_by,
|
212
|
+
agent_input,
|
213
|
+
config_to_pass
|
214
|
+
)
|
215
|
+
else:
|
216
|
+
# Use local registry for MCP and other local tools
|
217
|
+
print(f"🏠 {agent.role}: Using local registry for {tool_name}")
|
218
|
+
config_to_pass = tool_spec.get("config") if isinstance(tool_spec, dict) else tool_spec.config
|
219
|
+
print(f"🔧 {agent.role}: Config for {tool_name}: {config_to_pass}")
|
220
|
+
tool_result = self.tool_registry.execute_tool(
|
221
|
+
tool_name,
|
222
|
+
hosted_by,
|
223
|
+
agent_input,
|
224
|
+
config_to_pass
|
225
|
+
)
|
224
226
|
|
225
227
|
if not tool_result.get("success", False):
|
226
228
|
print(f"😟 {agent.role}: Oh no! Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}")
|
@@ -308,7 +310,8 @@ class ExecutionEngine:
|
|
308
310
|
isinstance(tool_data["validation_errors"], list) and
|
309
311
|
"is_valid" in tool_data and
|
310
312
|
# Check if it's validating real extracted data (not just mock data)
|
311
|
-
len(str(tool_data)) > 100 # Real validation results are more substantial
|
313
|
+
len(str(tool_data)) > 100 and # Real validation results are more substantial
|
314
|
+
not tool_data.get("_mock", False) # Not mock data
|
312
315
|
)
|
313
316
|
|
314
317
|
elif tool_name == "PostgresInsert":
|
@@ -318,7 +321,8 @@ class ExecutionEngine:
|
|
318
321
|
tool_data["success"] == True and
|
319
322
|
"record_id" in tool_data and
|
320
323
|
isinstance(tool_data["record_id"], int) and # Real DB returns integer IDs
|
321
|
-
"database_table" in tool_data # Real implementation includes table name
|
324
|
+
"database_table" in tool_data and # Real implementation includes table name
|
325
|
+
not tool_data.get("_mock", False) # Not mock data
|
322
326
|
)
|
323
327
|
|
324
328
|
# Default to mock work
|
memra/models.py
CHANGED
@@ -12,6 +12,7 @@ class Tool(BaseModel):
|
|
12
12
|
hosted_by: str = "memra" # or "mcp" for customer's Model Context Protocol
|
13
13
|
description: Optional[str] = None
|
14
14
|
parameters: Optional[Dict[str, Any]] = None
|
15
|
+
config: Optional[Dict[str, Any]] = None
|
15
16
|
|
16
17
|
class Agent(BaseModel):
|
17
18
|
role: str
|
memra/tool_registry.py
CHANGED
@@ -2,65 +2,48 @@ import importlib
|
|
2
2
|
import logging
|
3
3
|
import sys
|
4
4
|
import os
|
5
|
+
import httpx
|
5
6
|
from typing import Dict, Any, List, Optional, Callable
|
6
7
|
from pathlib import Path
|
7
8
|
|
8
9
|
logger = logging.getLogger(__name__)
|
9
10
|
|
10
11
|
class ToolRegistry:
|
11
|
-
"""Registry for managing and executing tools"""
|
12
|
+
"""Registry for managing and executing tools via API calls only"""
|
12
13
|
|
13
14
|
def __init__(self):
|
14
15
|
self.tools: Dict[str, Dict[str, Any]] = {}
|
15
|
-
self.
|
16
|
-
self._load_builtin_tools()
|
16
|
+
self._register_known_tools()
|
17
17
|
|
18
|
-
def
|
19
|
-
"""
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
def _register_known_tools(self):
|
19
|
+
"""Register known tools with their metadata (no actual implementations)"""
|
20
|
+
# Server-hosted tools (executed via Memra API)
|
21
|
+
server_tools = [
|
22
|
+
("DatabaseQueryTool", "Query database schemas and data"),
|
23
|
+
("PDFProcessor", "Process PDF files and extract content"),
|
24
|
+
("OCRTool", "Perform OCR on images and documents"),
|
25
|
+
("InvoiceExtractionWorkflow", "Extract structured data from invoices"),
|
26
|
+
("FileReader", "Read files from the filesystem"),
|
27
|
+
]
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
)
|
36
|
-
|
37
|
-
|
38
|
-
"Query database schemas and data")
|
39
|
-
self.register_tool("PDFProcessor", PDFProcessor, "memra",
|
40
|
-
"Process PDF files and extract content")
|
41
|
-
self.register_tool("OCRTool", OCRTool, "memra",
|
42
|
-
"Perform OCR on images and documents")
|
43
|
-
self.register_tool("InvoiceExtractionWorkflow", InvoiceExtractionWorkflow, "memra",
|
44
|
-
"Extract structured data from invoices")
|
45
|
-
self.register_tool("DataValidator", DataValidator, "memra",
|
46
|
-
"Validate data against schemas")
|
47
|
-
self.register_tool("PostgresInsert", PostgresInsert, "memra",
|
48
|
-
"Insert data into PostgreSQL database")
|
49
|
-
|
50
|
-
# Load file tools
|
51
|
-
from logic.file_tools import FileReader
|
52
|
-
self.register_tool("FileReader", FileReader, "memra",
|
53
|
-
"Read files from the filesystem")
|
54
|
-
|
55
|
-
logger.info(f"Loaded {len(self.tools)} builtin tools")
|
56
|
-
|
57
|
-
except ImportError as e:
|
58
|
-
logger.warning(f"Could not load some tools: {e}")
|
29
|
+
for tool_name, description in server_tools:
|
30
|
+
self.register_tool(tool_name, None, "memra", description)
|
31
|
+
|
32
|
+
# MCP-hosted tools (executed via MCP bridge)
|
33
|
+
mcp_tools = [
|
34
|
+
("DataValidator", "Validate data against schemas"),
|
35
|
+
("PostgresInsert", "Insert data into PostgreSQL database"),
|
36
|
+
]
|
37
|
+
|
38
|
+
for tool_name, description in mcp_tools:
|
39
|
+
self.register_tool(tool_name, None, "mcp", description)
|
40
|
+
|
41
|
+
logger.info(f"Registered {len(self.tools)} tool definitions")
|
59
42
|
|
60
|
-
def register_tool(self, name: str, tool_class: type, hosted_by: str, description: str):
|
61
|
-
"""Register a tool in the registry"""
|
43
|
+
def register_tool(self, name: str, tool_class: Optional[type], hosted_by: str, description: str):
|
44
|
+
"""Register a tool in the registry (metadata only)"""
|
62
45
|
self.tools[name] = {
|
63
|
-
"class": tool_class,
|
46
|
+
"class": tool_class, # Will be None for API-based tools
|
64
47
|
"hosted_by": hosted_by,
|
65
48
|
"description": description
|
66
49
|
}
|
@@ -80,111 +63,127 @@ class ToolRegistry:
|
|
80
63
|
|
81
64
|
def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
|
82
65
|
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
83
|
-
"""Execute a tool
|
84
|
-
if
|
85
|
-
return
|
86
|
-
|
87
|
-
|
88
|
-
}
|
89
|
-
|
90
|
-
tool_info = self.tools[tool_name]
|
91
|
-
if tool_info["hosted_by"] != hosted_by:
|
66
|
+
"""Execute a tool - handles MCP tools via bridge, rejects direct server tool execution"""
|
67
|
+
if hosted_by == "mcp":
|
68
|
+
return self._execute_mcp_tool(tool_name, input_data, config)
|
69
|
+
else:
|
70
|
+
logger.warning(f"Direct tool execution attempted for {tool_name}. Use API client instead.")
|
92
71
|
return {
|
93
72
|
"success": False,
|
94
|
-
"error":
|
73
|
+
"error": "Direct tool execution not supported. Use API client for tool execution."
|
95
74
|
}
|
96
|
-
|
75
|
+
|
76
|
+
def _execute_mcp_tool(self, tool_name: str, input_data: Dict[str, Any],
|
77
|
+
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
78
|
+
"""Execute an MCP tool via the bridge"""
|
97
79
|
try:
|
98
|
-
#
|
99
|
-
|
80
|
+
# Debug logging
|
81
|
+
logger.info(f"Executing MCP tool {tool_name} with config: {config}")
|
100
82
|
|
101
|
-
#
|
102
|
-
if
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
return {
|
109
|
-
"success": False,
|
110
|
-
"error": f"Tool '{tool_name}' requires database credentials"
|
111
|
-
}
|
112
|
-
elif tool_name == "InvoiceExtractionWorkflow":
|
113
|
-
# This tool needs to be instantiated to initialize the LLM client
|
114
|
-
tool_instance = tool_class()
|
115
|
-
else:
|
116
|
-
tool_instance = tool_class()
|
83
|
+
# Get bridge configuration
|
84
|
+
if not config:
|
85
|
+
logger.error(f"MCP tool {tool_name} requires bridge configuration")
|
86
|
+
return {
|
87
|
+
"success": False,
|
88
|
+
"error": "MCP bridge configuration required"
|
89
|
+
}
|
117
90
|
|
118
|
-
|
119
|
-
|
91
|
+
bridge_url = config.get("bridge_url", "http://localhost:8081")
|
92
|
+
bridge_secret = config.get("bridge_secret")
|
120
93
|
|
121
|
-
|
122
|
-
"
|
123
|
-
|
94
|
+
if not bridge_secret:
|
95
|
+
logger.error(f"MCP tool {tool_name} requires bridge_secret in config")
|
96
|
+
return {
|
97
|
+
"success": False,
|
98
|
+
"error": "MCP bridge secret required"
|
99
|
+
}
|
100
|
+
|
101
|
+
# Try different endpoint patterns that might exist
|
102
|
+
endpoints_to_try = [
|
103
|
+
f"{bridge_url}/execute_tool",
|
104
|
+
f"{bridge_url}/tool/{tool_name}",
|
105
|
+
f"{bridge_url}/mcp/execute",
|
106
|
+
f"{bridge_url}/api/execute"
|
107
|
+
]
|
108
|
+
|
109
|
+
# Prepare request
|
110
|
+
payload = {
|
111
|
+
"tool_name": tool_name,
|
112
|
+
"input_data": input_data
|
124
113
|
}
|
125
114
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
"success": False,
|
130
|
-
"error": str(e)
|
115
|
+
headers = {
|
116
|
+
"Content-Type": "application/json",
|
117
|
+
"X-Bridge-Secret": bridge_secret
|
131
118
|
}
|
132
|
-
|
133
|
-
def _execute_tool_method(self, tool_instance: Any, tool_name: str,
|
134
|
-
input_data: Dict[str, Any], config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
135
|
-
"""Execute the appropriate method on the tool instance"""
|
136
|
-
|
137
|
-
if tool_name == "DatabaseQueryTool":
|
138
|
-
return tool_instance.get_schema("invoices") # Default to invoices table
|
139
|
-
|
140
|
-
elif tool_name == "PDFProcessor":
|
141
|
-
file_path = input_data.get("file", "")
|
142
|
-
return tool_instance.process_pdf(file_path)
|
143
|
-
|
144
|
-
elif tool_name == "OCRTool":
|
145
|
-
# Assume PDF processor output is passed as input
|
146
|
-
return {"extracted_text": tool_instance.extract_text(input_data)}
|
147
|
-
|
148
|
-
elif tool_name == "InvoiceExtractionWorkflow":
|
149
|
-
text = input_data.get("extracted_text", "")
|
150
|
-
schema = input_data.get("invoice_schema", {})
|
151
|
-
return tool_instance.extract_data(text, schema)
|
152
|
-
|
153
|
-
elif tool_name == "DataValidator":
|
154
|
-
data = input_data.get("invoice_data", {})
|
155
|
-
schema = input_data.get("invoice_schema", {})
|
156
|
-
return tool_instance.validate(data, schema)
|
157
|
-
|
158
|
-
elif tool_name == "PostgresInsert":
|
159
|
-
data = input_data.get("invoice_data", {})
|
160
|
-
return tool_instance.insert_record("invoices", data)
|
161
|
-
|
162
|
-
elif tool_name == "FileReader":
|
163
|
-
file_path = config.get("path") if config else input_data.get("file_path")
|
164
|
-
if not file_path:
|
165
|
-
raise ValueError("FileReader requires a file path")
|
166
|
-
return tool_instance.read_file(file_path)
|
167
|
-
|
168
|
-
else:
|
169
|
-
raise ValueError(f"Unknown tool execution method for {tool_name}")
|
170
|
-
|
171
|
-
def _parse_connection(self, connection_string: str) -> Dict[str, Any]:
|
172
|
-
"""Parse a connection string into credentials"""
|
173
|
-
# Simple parser for postgres://user:pass@host:port/database
|
174
|
-
if connection_string.startswith("postgres://"):
|
175
|
-
# This is a simplified parser - in production you'd use a proper URL parser
|
176
|
-
parts = connection_string.replace("postgres://", "").split("/")
|
177
|
-
db_part = parts[1] if len(parts) > 1 else "finance"
|
178
|
-
auth_host = parts[0].split("@")
|
179
|
-
host_port = auth_host[1].split(":") if len(auth_host) > 1 else ["localhost", "5432"]
|
180
|
-
user_pass = auth_host[0].split(":") if len(auth_host) > 1 else ["user", "pass"]
|
181
119
|
|
120
|
+
# Try each endpoint
|
121
|
+
logger.info(f"Executing MCP tool {tool_name} via bridge at {bridge_url}")
|
122
|
+
|
123
|
+
last_error = None
|
124
|
+
for endpoint in endpoints_to_try:
|
125
|
+
try:
|
126
|
+
with httpx.Client(timeout=60.0) as client:
|
127
|
+
response = client.post(endpoint, json=payload, headers=headers)
|
128
|
+
|
129
|
+
if response.status_code == 200:
|
130
|
+
result = response.json()
|
131
|
+
logger.info(f"MCP tool {tool_name} executed successfully via {endpoint}")
|
132
|
+
return result
|
133
|
+
elif response.status_code == 404:
|
134
|
+
continue # Try next endpoint
|
135
|
+
else:
|
136
|
+
response.raise_for_status()
|
137
|
+
|
138
|
+
except httpx.HTTPStatusError as e:
|
139
|
+
if e.response.status_code == 404:
|
140
|
+
continue # Try next endpoint
|
141
|
+
last_error = e
|
142
|
+
continue
|
143
|
+
except Exception as e:
|
144
|
+
last_error = e
|
145
|
+
continue
|
146
|
+
|
147
|
+
# If we get here, none of the endpoints worked
|
148
|
+
# For now, return mock data to keep the workflow working
|
149
|
+
logger.warning(f"MCP bridge endpoints not available, returning mock data for {tool_name}")
|
150
|
+
|
151
|
+
if tool_name == "DataValidator":
|
152
|
+
return {
|
153
|
+
"success": True,
|
154
|
+
"data": {
|
155
|
+
"is_valid": True,
|
156
|
+
"validation_errors": [],
|
157
|
+
"validated_data": input_data.get("invoice_data", {}),
|
158
|
+
"_mock": True
|
159
|
+
}
|
160
|
+
}
|
161
|
+
elif tool_name == "PostgresInsert":
|
162
|
+
return {
|
163
|
+
"success": True,
|
164
|
+
"data": {
|
165
|
+
"success": True,
|
166
|
+
"record_id": 999, # Mock ID
|
167
|
+
"database_table": "invoices",
|
168
|
+
"inserted_data": input_data.get("invoice_data", {}),
|
169
|
+
"_mock": True
|
170
|
+
}
|
171
|
+
}
|
172
|
+
else:
|
173
|
+
return {
|
174
|
+
"success": False,
|
175
|
+
"error": f"MCP bridge not available and no mock data for {tool_name}"
|
176
|
+
}
|
177
|
+
|
178
|
+
except httpx.TimeoutException:
|
179
|
+
logger.error(f"MCP tool {tool_name} execution timed out")
|
182
180
|
return {
|
183
|
-
"
|
184
|
-
"
|
185
|
-
"database": db_part,
|
186
|
-
"user": user_pass[0],
|
187
|
-
"password": user_pass[1] if len(user_pass) > 1 else ""
|
181
|
+
"success": False,
|
182
|
+
"error": f"MCP tool execution timed out after 60 seconds"
|
188
183
|
}
|
189
|
-
|
190
|
-
|
184
|
+
except Exception as e:
|
185
|
+
logger.error(f"MCP tool execution failed for {tool_name}: {str(e)}")
|
186
|
+
return {
|
187
|
+
"success": False,
|
188
|
+
"error": str(e)
|
189
|
+
}
|
@@ -1,10 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.1
|
2
2
|
Name: memra
|
3
|
-
Version: 0.2.
|
4
|
-
Summary: Declarative framework for enterprise workflows with MCP integration
|
3
|
+
Version: 0.2.2
|
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
|
7
|
-
Author-email: Memra <
|
7
|
+
Author-email: Memra <support@memra.com>
|
8
8
|
License: MIT
|
9
9
|
Project-URL: Homepage, https://memra.co
|
10
10
|
Project-URL: Repository, https://github.com/memra-platform/memra-sdk
|
@@ -25,16 +25,13 @@ Requires-Dist: httpx>=0.24.0
|
|
25
25
|
Requires-Dist: typing-extensions>=4.0.0
|
26
26
|
Requires-Dist: aiohttp>=3.8.0
|
27
27
|
Requires-Dist: aiohttp-cors>=0.7.0
|
28
|
-
Requires-Dist: psycopg2-binary>=2.9.0
|
29
28
|
Provides-Extra: dev
|
30
29
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
31
30
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
32
31
|
Requires-Dist: black; extra == "dev"
|
33
32
|
Requires-Dist: flake8; extra == "dev"
|
34
|
-
|
35
|
-
|
36
|
-
Dynamic: license-file
|
37
|
-
Dynamic: requires-python
|
33
|
+
Provides-Extra: mcp
|
34
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "mcp"
|
38
35
|
|
39
36
|
# Memra SDK
|
40
37
|
|
@@ -102,6 +99,16 @@ echo 'export MEMRA_API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
102
99
|
python examples/accounts_payable_client.py
|
103
100
|
```
|
104
101
|
|
102
|
+
## Architecture
|
103
|
+
|
104
|
+
The Memra platform consists of three main components:
|
105
|
+
|
106
|
+
- **Memra SDK** (this repository): Client library for building and executing workflows
|
107
|
+
- **Memra Server**: Hosted infrastructure for heavy AI processing tools
|
108
|
+
- **MCP Bridge**: Local execution environment for database operations
|
109
|
+
|
110
|
+
Tools are automatically routed between server and local execution based on their `hosted_by` configuration.
|
111
|
+
|
105
112
|
## Documentation
|
106
113
|
|
107
114
|
Documentation is coming soon. For now, see the examples below and in the `examples/` directory.
|
@@ -118,44 +125,24 @@ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) f
|
|
118
125
|
|
119
126
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
120
127
|
|
121
|
-
##
|
128
|
+
## Repository Structure
|
122
129
|
|
123
130
|
```
|
124
|
-
├── examples/
|
125
|
-
│ ├── accounts_payable_client.py # API-based
|
126
|
-
│ ├──
|
127
|
-
│ ├── invoice_processing.py # Simple
|
128
|
-
│ └── propane_delivery.py #
|
129
|
-
├── memra/
|
130
|
-
├──
|
131
|
-
├──
|
132
|
-
└──
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
- ⚡ **Hybrid processing** - AI processing in the cloud, data operations locally
|
142
|
-
- 🔐 **Secure communication** - HMAC-authenticated requests between cloud and local
|
143
|
-
- 🛠️ **Easy setup** - Simple bridge server connects your local resources
|
144
|
-
|
145
|
-
**Quick Example:**
|
146
|
-
```python
|
147
|
-
# Agent that uses local database via MCP
|
148
|
-
agent = Agent(
|
149
|
-
role="Data Writer",
|
150
|
-
tools=[{
|
151
|
-
"name": "PostgresInsert",
|
152
|
-
"hosted_by": "mcp", # Routes to your local infrastructure
|
153
|
-
"config": {
|
154
|
-
"bridge_url": "http://localhost:8081",
|
155
|
-
"bridge_secret": "your-secret"
|
156
|
-
}
|
157
|
-
}]
|
158
|
-
)
|
131
|
+
├── examples/ # Example workflows and use cases
|
132
|
+
│ ├── accounts_payable_client.py # API-based accounts payable workflow
|
133
|
+
│ ├── accounts_payable_mcp.py # MCP-enabled accounts payable workflow
|
134
|
+
│ ├── invoice_processing.py # Simple invoice processing example
|
135
|
+
│ └── propane_delivery.py # Propane delivery domain example
|
136
|
+
├── memra/ # Core SDK package
|
137
|
+
│ ├── __init__.py # Package initialization
|
138
|
+
│ ├── tool_registry.py # Tool discovery and routing
|
139
|
+
│ └── sdk/ # SDK components
|
140
|
+
│ ├── __init__.py
|
141
|
+
│ ├── client.py # API client
|
142
|
+
│ ├── execution_engine.py # Workflow execution
|
143
|
+
│ └── models.py # Core data models
|
144
|
+
├── docs/ # Documentation
|
145
|
+
├── tests/ # Test suite
|
146
|
+
├── local/dependencies/ # Local development setup
|
147
|
+
└── scripts/ # Utility scripts
|
159
148
|
```
|
160
|
-
|
161
|
-
📖 **[Complete MCP Integration Guide →](docs/mcp_integration.md)**
|
@@ -0,0 +1,13 @@
|
|
1
|
+
memra/__init__.py,sha256=5WPh9vku8_ZV4T6WayAqArKAj1RDkbL47SsnA9GWD7A,662
|
2
|
+
memra/discovery.py,sha256=yJIQnrDQu1nyzKykCIuzG_5SW5dIXHCEBLLKRWacIoY,480
|
3
|
+
memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
|
4
|
+
memra/execution.py,sha256=bg822ED6yYN7APjPac1LRhv48gtxV4DUPvzpyLyBa2I,21443
|
5
|
+
memra/models.py,sha256=sXMPRnMB_mUVtJdBFyd0ElCf_uh1yqx7iLssIYNm0vI,3333
|
6
|
+
memra/tool_registry.py,sha256=N7kpYQxgJcSMDDCX-_6og1-of3QKEaoz6H16ptCCg48,7784
|
7
|
+
memra/tool_registry_client.py,sha256=uzMQ4COvRams9vuPLcqcdljUpDlAYU_tyFxrRhrA0Lc,4009
|
8
|
+
memra-0.2.2.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
memra-0.2.2.dist-info/METADATA,sha256=jrZ9AwcGtPK-pc9TGGGoi1oJzXxcGx7LUsbsd6NxcxA,4856
|
10
|
+
memra-0.2.2.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
11
|
+
memra-0.2.2.dist-info/entry_points.txt,sha256=LBVjwWoxWJRzNLgeByPn6xUvWFIRnqnemvAZgIoSt08,41
|
12
|
+
memra-0.2.2.dist-info/top_level.txt,sha256=pXWcTRS1zctdiSUivW4iyKpJ4tcfIu-1BW_fpbal3OY,6
|
13
|
+
memra-0.2.2.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
memra
|
memra-0.2.0.dist-info/RECORD
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
memra/__init__.py,sha256=XLSWpo42Ffp_pi5mvk4xvdYBZ8eNLAJF4_3Oi102i90,560
|
2
|
-
memra/discovery.py,sha256=yJIQnrDQu1nyzKykCIuzG_5SW5dIXHCEBLLKRWacIoY,480
|
3
|
-
memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
|
4
|
-
memra/execution.py,sha256=5NIyFVtQEeatYQ-fxexT0eWMtCh28k1hRC2Y6cfQaac,20917
|
5
|
-
memra/models.py,sha256=nTaYLAp0tRzQ0CQaBLNBURfhBQ5_gyty0ams4mghyIc,3289
|
6
|
-
memra/tool_registry.py,sha256=zdyKRShcmKtG7BVfmAHflW9FDl7rooPPAgbdVV4gJ8o,8268
|
7
|
-
memra/tool_registry_client.py,sha256=uzMQ4COvRams9vuPLcqcdljUpDlAYU_tyFxrRhrA0Lc,4009
|
8
|
-
memra-0.2.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
memra-sdk-package/examples/accounts_payable_client.py,sha256=Vu_h5C-qc6_80uz5dXJH4G3zfIbgUNAhQ2y8mWauao0,7401
|
10
|
-
memra-sdk-package/memra/__init__.py,sha256=QRk72YETLgL15GVt26tN_rBraCQkhZO7UB9T6d4u_uU,543
|
11
|
-
memra-sdk-package/memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
|
12
|
-
memra-sdk-package/memra/execution.py,sha256=UJ_MJ4getuSk4HJW1sCi7lc26avX-G6-GxnvE-DiSwk,20191
|
13
|
-
memra-sdk-package/memra/models.py,sha256=nTaYLAp0tRzQ0CQaBLNBURfhBQ5_gyty0ams4mghyIc,3289
|
14
|
-
memra-sdk-package/memra/tool_registry_client.py,sha256=KyNNxj84248E-8MoWNj6pJmlllUG8s0lmeXXmbu0U7o,3996
|
15
|
-
memra-0.2.0.dist-info/METADATA,sha256=eOuvH39VFUh-QxTdE5RwT6isgRIJkptEC2lsqlF2AA4,4816
|
16
|
-
memra-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
17
|
-
memra-0.2.0.dist-info/entry_points.txt,sha256=LBVjwWoxWJRzNLgeByPn6xUvWFIRnqnemvAZgIoSt08,41
|
18
|
-
memra-0.2.0.dist-info/top_level.txt,sha256=5dqePB77aj_pPFavlwxtBvdkUM-kP-WiQD3LRbQswwc,24
|
19
|
-
memra-0.2.0.dist-info/RECORD,,
|