memra 0.2.0__tar.gz → 0.2.2__tar.gz
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-0.2.2/CHANGELOG.md +63 -0
- memra-0.2.2/MANIFEST.in +32 -0
- {memra-0.2.0/memra.egg-info → memra-0.2.2}/PKG-INFO +34 -47
- {memra-0.2.0 → memra-0.2.2}/README.md +28 -38
- memra-0.2.2/mcp_bridge_server.py +230 -0
- memra-0.2.2/memra/__init__.py +27 -0
- {memra-0.2.0/memra-sdk-package → memra-0.2.2}/memra/execution.py +29 -9
- {memra-0.2.0/memra-sdk-package → memra-0.2.2}/memra/models.py +1 -0
- memra-0.2.2/memra/tool_registry.py +189 -0
- memra-0.2.2/memra.egg-info/SOURCES.txt +14 -0
- {memra-0.2.0 → memra-0.2.2}/pyproject.toml +8 -5
- {memra-0.2.0 → memra-0.2.2}/setup.py +6 -4
- memra-0.2.0/PKG-INFO +0 -161
- memra-0.2.0/memra/__init__.py +0 -24
- memra-0.2.0/memra/discovery_client.py +0 -49
- memra-0.2.0/memra/execution.py +0 -434
- memra-0.2.0/memra/models.py +0 -98
- memra-0.2.0/memra/tool_registry.py +0 -190
- memra-0.2.0/memra-sdk-package/examples/accounts_payable_client.py +0 -207
- memra-0.2.0/memra-sdk-package/memra/__init__.py +0 -28
- memra-0.2.0/memra-sdk-package/memra/tool_registry_client.py +0 -105
- memra-0.2.0/memra.egg-info/SOURCES.txt +0 -23
- memra-0.2.0/memra.egg-info/dependency_links.txt +0 -1
- memra-0.2.0/memra.egg-info/entry_points.txt +0 -2
- memra-0.2.0/memra.egg-info/requires.txt +0 -12
- memra-0.2.0/memra.egg-info/top_level.txt +0 -2
- {memra-0.2.0 → memra-0.2.2}/LICENSE +0 -0
- {memra-0.2.0 → memra-0.2.2}/memra/discovery.py +0 -0
- {memra-0.2.0/memra-sdk-package → memra-0.2.2}/memra/discovery_client.py +0 -0
- {memra-0.2.0 → memra-0.2.2}/memra/tool_registry_client.py +0 -0
- {memra-0.2.0 → memra-0.2.2}/setup.cfg +0 -0
memra-0.2.2/CHANGELOG.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to the Memra SDK will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.2.2] - 2025-05-28
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- **MCP Integration**: Fixed broken MCP tool execution after repository separation
|
12
|
+
- **Tool Registry**: Updated MCP tool routing to use correct endpoints
|
13
|
+
- **Bridge Server**: Added working MCP bridge server implementation
|
14
|
+
- **Real Work Detection**: Improved detection of real vs mock work for MCP tools
|
15
|
+
|
16
|
+
### Added
|
17
|
+
- Complete MCP bridge server with DataValidator and PostgresInsert tools
|
18
|
+
- Health check endpoint for MCP bridge monitoring
|
19
|
+
- Better error handling and fallback for MCP tool execution
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- MCP tools now perform real database operations instead of mock responses
|
23
|
+
- Improved logging and debugging for MCP tool execution flow
|
24
|
+
|
25
|
+
## [0.2.1] - 2025-05-27
|
26
|
+
|
27
|
+
## [0.2.0] - 2024-01-17
|
28
|
+
|
29
|
+
### Added
|
30
|
+
- **MCP (Model Context Protocol) Integration**: Execute operations on local infrastructure while leveraging cloud AI processing
|
31
|
+
- New `mcp_bridge_server.py` for local resource bridging
|
32
|
+
- HMAC authentication for secure cloud-to-local communication
|
33
|
+
- Support for `hosted_by: "mcp"` in agent tool configurations
|
34
|
+
- PostgreSQL integration via MCP bridge
|
35
|
+
- Tool-level configuration support in execution engine
|
36
|
+
- New MCP tools: `PostgresInsert`, `DataValidator`
|
37
|
+
|
38
|
+
### Enhanced
|
39
|
+
- **Execution Engine**: Updated to support tool-level configuration and MCP routing
|
40
|
+
- **Tool Registry Client**: Enhanced API client with better error handling and MCP support
|
41
|
+
- **Agent Configuration**: Added support for tool-specific configuration alongside agent-level config
|
42
|
+
|
43
|
+
### Examples
|
44
|
+
- `examples/accounts_payable_mcp.py` - Complete invoice processing with MCP database integration
|
45
|
+
- `test_mcp_success.py` - Simple MCP integration test
|
46
|
+
|
47
|
+
### Documentation
|
48
|
+
- `docs/mcp_integration.md` - Comprehensive MCP integration guide
|
49
|
+
- Updated README with MCP overview and quick start
|
50
|
+
|
51
|
+
### Dependencies
|
52
|
+
- Added `aiohttp>=3.8.0` for MCP bridge server
|
53
|
+
- Added `aiohttp-cors>=0.7.0` for CORS support
|
54
|
+
- Added `psycopg2-binary>=2.9.0` for PostgreSQL integration
|
55
|
+
|
56
|
+
## [0.1.0] - 2024-01-01
|
57
|
+
|
58
|
+
### Added
|
59
|
+
- Initial release of Memra SDK
|
60
|
+
- Core agent and department framework
|
61
|
+
- API client for Memra cloud services
|
62
|
+
- Basic tool registry and execution engine
|
63
|
+
- Examples for accounts payable and propane delivery workflows
|
memra-0.2.2/MANIFEST.in
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Include only the client SDK files
|
2
|
+
include README.md
|
3
|
+
include LICENSE
|
4
|
+
include CHANGELOG.md
|
5
|
+
include requirements.txt
|
6
|
+
include examples/*.py
|
7
|
+
include docs/*.md
|
8
|
+
include docs/*.sql
|
9
|
+
include mcp_bridge_server.py
|
10
|
+
recursive-include memra *.py
|
11
|
+
|
12
|
+
# Explicitly exclude server-only files and directories
|
13
|
+
exclude app.py
|
14
|
+
exclude server_tool_registry.py
|
15
|
+
exclude config.py
|
16
|
+
exclude fly.toml
|
17
|
+
exclude Dockerfile
|
18
|
+
exclude Procfile
|
19
|
+
exclude docker-compose.yml
|
20
|
+
recursive-exclude logic *
|
21
|
+
recursive-exclude scripts *
|
22
|
+
recursive-exclude docs *
|
23
|
+
recursive-exclude examples *
|
24
|
+
recursive-exclude tests *
|
25
|
+
recursive-exclude temp_processing *
|
26
|
+
recursive-exclude invoices *
|
27
|
+
recursive-exclude local *
|
28
|
+
recursive-exclude dist *
|
29
|
+
recursive-exclude __pycache__ *
|
30
|
+
recursive-exclude *.egg-info *
|
31
|
+
exclude .DS_Store
|
32
|
+
exclude .dockerignore
|
@@ -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)**
|
@@ -64,6 +64,16 @@ echo 'export MEMRA_API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
64
64
|
python examples/accounts_payable_client.py
|
65
65
|
```
|
66
66
|
|
67
|
+
## Architecture
|
68
|
+
|
69
|
+
The Memra platform consists of three main components:
|
70
|
+
|
71
|
+
- **Memra SDK** (this repository): Client library for building and executing workflows
|
72
|
+
- **Memra Server**: Hosted infrastructure for heavy AI processing tools
|
73
|
+
- **MCP Bridge**: Local execution environment for database operations
|
74
|
+
|
75
|
+
Tools are automatically routed between server and local execution based on their `hosted_by` configuration.
|
76
|
+
|
67
77
|
## Documentation
|
68
78
|
|
69
79
|
Documentation is coming soon. For now, see the examples below and in the `examples/` directory.
|
@@ -80,44 +90,24 @@ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) f
|
|
80
90
|
|
81
91
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
82
92
|
|
83
|
-
##
|
93
|
+
## Repository Structure
|
84
94
|
|
85
95
|
```
|
86
|
-
├── examples/
|
87
|
-
│ ├── accounts_payable_client.py # API-based
|
88
|
-
│ ├──
|
89
|
-
│ ├── invoice_processing.py # Simple
|
90
|
-
│ └── propane_delivery.py #
|
91
|
-
├── memra/
|
92
|
-
├──
|
93
|
-
├──
|
94
|
-
└──
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
- ⚡ **Hybrid processing** - AI processing in the cloud, data operations locally
|
104
|
-
- 🔐 **Secure communication** - HMAC-authenticated requests between cloud and local
|
105
|
-
- 🛠️ **Easy setup** - Simple bridge server connects your local resources
|
106
|
-
|
107
|
-
**Quick Example:**
|
108
|
-
```python
|
109
|
-
# Agent that uses local database via MCP
|
110
|
-
agent = Agent(
|
111
|
-
role="Data Writer",
|
112
|
-
tools=[{
|
113
|
-
"name": "PostgresInsert",
|
114
|
-
"hosted_by": "mcp", # Routes to your local infrastructure
|
115
|
-
"config": {
|
116
|
-
"bridge_url": "http://localhost:8081",
|
117
|
-
"bridge_secret": "your-secret"
|
118
|
-
}
|
119
|
-
}]
|
120
|
-
)
|
96
|
+
├── examples/ # Example workflows and use cases
|
97
|
+
│ ├── accounts_payable_client.py # API-based accounts payable workflow
|
98
|
+
│ ├── accounts_payable_mcp.py # MCP-enabled accounts payable workflow
|
99
|
+
│ ├── invoice_processing.py # Simple invoice processing example
|
100
|
+
│ └── propane_delivery.py # Propane delivery domain example
|
101
|
+
├── memra/ # Core SDK package
|
102
|
+
│ ├── __init__.py # Package initialization
|
103
|
+
│ ├── tool_registry.py # Tool discovery and routing
|
104
|
+
│ └── sdk/ # SDK components
|
105
|
+
│ ├── __init__.py
|
106
|
+
│ ├── client.py # API client
|
107
|
+
│ ├── execution_engine.py # Workflow execution
|
108
|
+
│ └── models.py # Core data models
|
109
|
+
├── docs/ # Documentation
|
110
|
+
├── tests/ # Test suite
|
111
|
+
├── local/dependencies/ # Local development setup
|
112
|
+
└── scripts/ # Utility scripts
|
121
113
|
```
|
122
|
-
|
123
|
-
📖 **[Complete MCP Integration Guide →](docs/mcp_integration.md)**
|
@@ -0,0 +1,230 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Simple MCP Bridge Server for local tool execution
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import json
|
8
|
+
import hmac
|
9
|
+
import hashlib
|
10
|
+
import logging
|
11
|
+
import asyncio
|
12
|
+
import psycopg2
|
13
|
+
from aiohttp import web, web_request
|
14
|
+
from typing import Dict, Any, Optional
|
15
|
+
|
16
|
+
logging.basicConfig(level=logging.INFO)
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
class MCPBridgeServer:
|
20
|
+
def __init__(self, postgres_url: str, bridge_secret: str):
|
21
|
+
self.postgres_url = postgres_url
|
22
|
+
self.bridge_secret = bridge_secret
|
23
|
+
|
24
|
+
def verify_signature(self, request_body: str, signature: str) -> bool:
|
25
|
+
"""Verify HMAC signature"""
|
26
|
+
expected = hmac.new(
|
27
|
+
self.bridge_secret.encode(),
|
28
|
+
request_body.encode(),
|
29
|
+
hashlib.sha256
|
30
|
+
).hexdigest()
|
31
|
+
return hmac.compare_digest(expected, signature)
|
32
|
+
|
33
|
+
async def execute_tool(self, request: web_request.Request) -> web.Response:
|
34
|
+
"""Execute MCP tool endpoint"""
|
35
|
+
try:
|
36
|
+
# Get request body
|
37
|
+
body = await request.text()
|
38
|
+
data = json.loads(body)
|
39
|
+
|
40
|
+
# Verify signature
|
41
|
+
signature = request.headers.get('X-Bridge-Secret')
|
42
|
+
if not signature or signature != self.bridge_secret:
|
43
|
+
logger.warning("Invalid or missing bridge secret")
|
44
|
+
return web.json_response({
|
45
|
+
"success": False,
|
46
|
+
"error": "Invalid authentication"
|
47
|
+
}, status=401)
|
48
|
+
|
49
|
+
tool_name = data.get('tool_name')
|
50
|
+
input_data = data.get('input_data', {})
|
51
|
+
|
52
|
+
logger.info(f"Executing MCP tool: {tool_name}")
|
53
|
+
|
54
|
+
if tool_name == "DataValidator":
|
55
|
+
result = await self.data_validator(input_data)
|
56
|
+
elif tool_name == "PostgresInsert":
|
57
|
+
result = await self.postgres_insert(input_data)
|
58
|
+
else:
|
59
|
+
return web.json_response({
|
60
|
+
"success": False,
|
61
|
+
"error": f"Unknown tool: {tool_name}"
|
62
|
+
}, status=400)
|
63
|
+
|
64
|
+
return web.json_response(result)
|
65
|
+
|
66
|
+
except Exception as e:
|
67
|
+
logger.error(f"Tool execution failed: {str(e)}")
|
68
|
+
return web.json_response({
|
69
|
+
"success": False,
|
70
|
+
"error": str(e)
|
71
|
+
}, status=500)
|
72
|
+
|
73
|
+
async def data_validator(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
74
|
+
"""Validate data against schema"""
|
75
|
+
try:
|
76
|
+
invoice_data = input_data.get('invoice_data', {})
|
77
|
+
|
78
|
+
# Perform basic validation
|
79
|
+
validation_errors = []
|
80
|
+
|
81
|
+
# Check required fields
|
82
|
+
required_fields = ['headerSection', 'billingDetails', 'chargesSummary']
|
83
|
+
for field in required_fields:
|
84
|
+
if field not in invoice_data:
|
85
|
+
validation_errors.append(f"Missing required field: {field}")
|
86
|
+
|
87
|
+
# Validate header section
|
88
|
+
if 'headerSection' in invoice_data:
|
89
|
+
header = invoice_data['headerSection']
|
90
|
+
if not header.get('vendorName'):
|
91
|
+
validation_errors.append("Missing vendor name in header")
|
92
|
+
if not header.get('subtotal'):
|
93
|
+
validation_errors.append("Missing subtotal in header")
|
94
|
+
|
95
|
+
# Validate billing details
|
96
|
+
if 'billingDetails' in invoice_data:
|
97
|
+
billing = invoice_data['billingDetails']
|
98
|
+
if not billing.get('invoiceNumber'):
|
99
|
+
validation_errors.append("Missing invoice number")
|
100
|
+
if not billing.get('invoiceDate'):
|
101
|
+
validation_errors.append("Missing invoice date")
|
102
|
+
|
103
|
+
is_valid = len(validation_errors) == 0
|
104
|
+
|
105
|
+
logger.info(f"Data validation completed: {'valid' if is_valid else 'invalid'}")
|
106
|
+
|
107
|
+
return {
|
108
|
+
"success": True,
|
109
|
+
"data": {
|
110
|
+
"is_valid": is_valid,
|
111
|
+
"validation_errors": validation_errors,
|
112
|
+
"validated_data": invoice_data
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
except Exception as e:
|
117
|
+
logger.error(f"Data validation failed: {str(e)}")
|
118
|
+
return {
|
119
|
+
"success": False,
|
120
|
+
"error": str(e)
|
121
|
+
}
|
122
|
+
|
123
|
+
async def postgres_insert(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
124
|
+
"""Insert data into PostgreSQL"""
|
125
|
+
try:
|
126
|
+
invoice_data = input_data.get('invoice_data', {})
|
127
|
+
table_name = input_data.get('table_name', 'invoices')
|
128
|
+
|
129
|
+
# Extract key fields from invoice data
|
130
|
+
header = invoice_data.get('headerSection', {})
|
131
|
+
billing = invoice_data.get('billingDetails', {})
|
132
|
+
charges = invoice_data.get('chargesSummary', {})
|
133
|
+
|
134
|
+
# Prepare insert data
|
135
|
+
insert_data = {
|
136
|
+
'invoice_number': billing.get('invoiceNumber', ''),
|
137
|
+
'vendor_name': header.get('vendorName', ''),
|
138
|
+
'invoice_date': billing.get('invoiceDate', ''),
|
139
|
+
'total_amount': charges.get('document_total', 0),
|
140
|
+
'tax_amount': charges.get('secondary_tax', 0),
|
141
|
+
'line_items': json.dumps(charges.get('lineItemsBreakdown', [])),
|
142
|
+
'status': 'processed'
|
143
|
+
}
|
144
|
+
|
145
|
+
# Connect to database and insert
|
146
|
+
conn = psycopg2.connect(self.postgres_url)
|
147
|
+
cursor = conn.cursor()
|
148
|
+
|
149
|
+
# Build insert query
|
150
|
+
columns = ', '.join(insert_data.keys())
|
151
|
+
placeholders = ', '.join(['%s'] * len(insert_data))
|
152
|
+
query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING id"
|
153
|
+
|
154
|
+
cursor.execute(query, list(insert_data.values()))
|
155
|
+
record_id = cursor.fetchone()[0]
|
156
|
+
|
157
|
+
conn.commit()
|
158
|
+
cursor.close()
|
159
|
+
conn.close()
|
160
|
+
|
161
|
+
logger.info(f"Successfully inserted record with ID: {record_id}")
|
162
|
+
|
163
|
+
return {
|
164
|
+
"success": True,
|
165
|
+
"data": {
|
166
|
+
"success": True,
|
167
|
+
"record_id": record_id,
|
168
|
+
"database_table": table_name,
|
169
|
+
"inserted_data": insert_data
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
except Exception as e:
|
174
|
+
logger.error(f"Database insert failed: {str(e)}")
|
175
|
+
return {
|
176
|
+
"success": False,
|
177
|
+
"error": str(e)
|
178
|
+
}
|
179
|
+
|
180
|
+
async def health_check(self, request: web_request.Request) -> web.Response:
|
181
|
+
"""Health check endpoint"""
|
182
|
+
return web.json_response({"status": "healthy", "service": "mcp-bridge"})
|
183
|
+
|
184
|
+
def create_app(self) -> web.Application:
|
185
|
+
"""Create aiohttp application"""
|
186
|
+
app = web.Application()
|
187
|
+
|
188
|
+
# Add routes
|
189
|
+
app.router.add_post('/execute_tool', self.execute_tool)
|
190
|
+
app.router.add_get('/health', self.health_check)
|
191
|
+
|
192
|
+
return app
|
193
|
+
|
194
|
+
async def start(self, port: int = 8081):
|
195
|
+
"""Start the server"""
|
196
|
+
app = self.create_app()
|
197
|
+
runner = web.AppRunner(app)
|
198
|
+
await runner.setup()
|
199
|
+
|
200
|
+
site = web.TCPSite(runner, 'localhost', port)
|
201
|
+
await site.start()
|
202
|
+
|
203
|
+
logger.info(f"MCP Bridge Server started on http://localhost:{port}")
|
204
|
+
logger.info(f"Available endpoints:")
|
205
|
+
logger.info(f" POST /execute_tool - Execute MCP tools")
|
206
|
+
logger.info(f" GET /health - Health check")
|
207
|
+
|
208
|
+
# Keep running
|
209
|
+
try:
|
210
|
+
await asyncio.Future() # Run forever
|
211
|
+
except KeyboardInterrupt:
|
212
|
+
logger.info("Shutting down server...")
|
213
|
+
finally:
|
214
|
+
await runner.cleanup()
|
215
|
+
|
216
|
+
def main():
|
217
|
+
# Get configuration from environment
|
218
|
+
postgres_url = os.getenv('MCP_POSTGRES_URL', 'postgresql://tarpus@localhost:5432/memra_invoice_db')
|
219
|
+
bridge_secret = os.getenv('MCP_BRIDGE_SECRET', 'test-secret-for-development')
|
220
|
+
|
221
|
+
logger.info(f"Starting MCP Bridge Server...")
|
222
|
+
logger.info(f"PostgreSQL URL: {postgres_url}")
|
223
|
+
logger.info(f"Bridge Secret: {'*' * len(bridge_secret)}")
|
224
|
+
|
225
|
+
# Create and start server
|
226
|
+
server = MCPBridgeServer(postgres_url, bridge_secret)
|
227
|
+
asyncio.run(server.start())
|
228
|
+
|
229
|
+
if __name__ == '__main__':
|
230
|
+
main()
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"""
|
2
|
+
Memra SDK - Declarative AI Workflows
|
3
|
+
|
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.
|
7
|
+
"""
|
8
|
+
|
9
|
+
__version__ = "0.2.2"
|
10
|
+
|
11
|
+
# Core imports
|
12
|
+
from .models import Agent, Department, Tool
|
13
|
+
from .execution import ExecutionEngine
|
14
|
+
|
15
|
+
# Make key classes available at package level
|
16
|
+
__all__ = [
|
17
|
+
"Agent",
|
18
|
+
"Department",
|
19
|
+
"Tool",
|
20
|
+
"ExecutionEngine",
|
21
|
+
"__version__"
|
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")
|
@@ -2,6 +2,7 @@ import time
|
|
2
2
|
import logging
|
3
3
|
from typing import Dict, Any, List, Optional
|
4
4
|
from .models import Department, Agent, DepartmentResult, ExecutionTrace, DepartmentAudit
|
5
|
+
from .tool_registry import ToolRegistry
|
5
6
|
from .tool_registry_client import ToolRegistryClient
|
6
7
|
|
7
8
|
logger = logging.getLogger(__name__)
|
@@ -10,7 +11,8 @@ class ExecutionEngine:
|
|
10
11
|
"""Engine that executes department workflows by coordinating agents and tools"""
|
11
12
|
|
12
13
|
def __init__(self):
|
13
|
-
self.tool_registry =
|
14
|
+
self.tool_registry = ToolRegistry()
|
15
|
+
self.api_client = ToolRegistryClient()
|
14
16
|
self.last_execution_audit: Optional[DepartmentAudit] = None
|
15
17
|
|
16
18
|
def execute_department(self, department: Department, input_data: Dict[str, Any]) -> DepartmentResult:
|
@@ -199,12 +201,28 @@ class ExecutionEngine:
|
|
199
201
|
trace.tools_invoked.append(tool_name)
|
200
202
|
|
201
203
|
# Get tool from registry and execute
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
+
)
|
208
226
|
|
209
227
|
if not tool_result.get("success", False):
|
210
228
|
print(f"😟 {agent.role}: Oh no! Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}")
|
@@ -292,7 +310,8 @@ class ExecutionEngine:
|
|
292
310
|
isinstance(tool_data["validation_errors"], list) and
|
293
311
|
"is_valid" in tool_data and
|
294
312
|
# Check if it's validating real extracted data (not just mock data)
|
295
|
-
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
|
296
315
|
)
|
297
316
|
|
298
317
|
elif tool_name == "PostgresInsert":
|
@@ -302,7 +321,8 @@ class ExecutionEngine:
|
|
302
321
|
tool_data["success"] == True and
|
303
322
|
"record_id" in tool_data and
|
304
323
|
isinstance(tool_data["record_id"], int) and # Real DB returns integer IDs
|
305
|
-
"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
|
306
326
|
)
|
307
327
|
|
308
328
|
# Default to mock work
|
@@ -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
|