eulerian-marketing-platform 0.1.2__py3-none-any.whl → 0.2.1__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.
- eulerian_marketing_platform/__init__.py +1 -1
- eulerian_marketing_platform/server.py +158 -167
- {eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/METADATA +18 -184
- eulerian_marketing_platform-0.2.1.dist-info/RECORD +8 -0
- eulerian_marketing_platform-0.1.2.dist-info/RECORD +0 -8
- {eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/LICENSE +0 -0
- {eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/WHEEL +0 -0
- {eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/entry_points.txt +0 -0
- {eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Eulerian Marketing Platform MCP Proxy Server.
|
|
3
3
|
|
|
4
|
-
This server acts as a proxy
|
|
4
|
+
This server acts as a transparent proxy between local MCP clients (Claude Desktop, etc.)
|
|
5
5
|
and a remote Eulerian Marketing Platform MCP server via HTTP.
|
|
6
6
|
|
|
7
|
-
It
|
|
8
|
-
and
|
|
7
|
+
It forwards ALL MCP requests directly to the remote server, making all remote tools
|
|
8
|
+
and resources available to the client without any intermediary logic.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import os
|
|
@@ -14,27 +14,19 @@ import json
|
|
|
14
14
|
import logging
|
|
15
15
|
import tempfile
|
|
16
16
|
from datetime import datetime
|
|
17
|
-
from typing import Any
|
|
18
|
-
from pathlib import Path
|
|
19
17
|
import httpx
|
|
20
18
|
|
|
21
|
-
from mcp.server.fastmcp import FastMCP
|
|
22
|
-
from mcp.server import Server
|
|
23
|
-
from mcp.server.stdio import stdio_server
|
|
24
|
-
from mcp import types
|
|
25
|
-
|
|
26
19
|
# Configuration
|
|
27
20
|
EMP_API_ENDPOINT = os.environ.get("EMP_API_ENDPOINT")
|
|
28
21
|
EMP_API_TOKEN = os.environ.get("EMP_API_TOKEN")
|
|
29
22
|
|
|
30
23
|
# Logging setup - Use cross-platform temp directory
|
|
31
|
-
# Default to system temp directory if EMP_LOG_FILE not set
|
|
32
24
|
DEFAULT_LOG_FILE = os.path.join(tempfile.gettempdir(), "eulerian-mcp-proxy.log")
|
|
33
25
|
LOG_FILE = os.environ.get("EMP_LOG_FILE", DEFAULT_LOG_FILE)
|
|
34
26
|
|
|
35
27
|
# Ensure log directory exists
|
|
36
28
|
log_dir = os.path.dirname(LOG_FILE)
|
|
37
|
-
if log_dir:
|
|
29
|
+
if log_dir:
|
|
38
30
|
os.makedirs(log_dir, exist_ok=True)
|
|
39
31
|
|
|
40
32
|
# Configure logging to file and stderr with UTF-8 encoding for cross-platform compatibility
|
|
@@ -75,176 +67,175 @@ def validate_config() -> None:
|
|
|
75
67
|
sys.exit(1)
|
|
76
68
|
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
"""
|
|
70
|
+
def forward_request(request_data: dict) -> dict:
|
|
71
|
+
"""Forward a JSON-RPC request to the remote MCP server.
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.endpoint = EMP_API_ENDPOINT
|
|
84
|
-
self.token = EMP_API_TOKEN
|
|
85
|
-
self.timeout = float(os.environ.get("EMP_TIMEOUT", "300"))
|
|
73
|
+
Args:
|
|
74
|
+
request_data: The JSON-RPC request to forward
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
76
|
+
Returns:
|
|
77
|
+
The JSON-RPC response from the remote server
|
|
78
|
+
"""
|
|
79
|
+
timeout = float(os.environ.get("EMP_TIMEOUT", "300"))
|
|
80
|
+
|
|
81
|
+
request_id = request_data.get("id")
|
|
82
|
+
method = request_data.get("method")
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
logger.info(f">>> REQUEST: {method} (id: {request_id})")
|
|
85
|
+
logger.debug(f" Full request: {json.dumps(request_data)[:200]}...")
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
logger.info(f"Forwarding to {EMP_API_ENDPOINT}")
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"id": 1,
|
|
108
|
-
"method": method,
|
|
109
|
-
}
|
|
110
|
-
if params:
|
|
111
|
-
request_data["params"] = params
|
|
90
|
+
# Use httpx for sync HTTP request
|
|
91
|
+
with httpx.Client(timeout=timeout) as client:
|
|
92
|
+
response = client.post(
|
|
93
|
+
EMP_API_ENDPOINT,
|
|
94
|
+
headers={
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
"Authorization": f"Bearer {EMP_API_TOKEN}"
|
|
97
|
+
},
|
|
98
|
+
json=request_data
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
logger.info(f"<<< RESPONSE: HTTP {response.status_code}")
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
if response.status_code != 200:
|
|
104
|
+
error_msg = f"HTTP {response.status_code}: {response.reason_phrase}"
|
|
105
|
+
logger.error(f" Error: {response.text[:200]}")
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
"jsonrpc": "2.0",
|
|
109
|
+
"id": request_id,
|
|
110
|
+
"error": {
|
|
111
|
+
"code": -32000,
|
|
112
|
+
"message": error_msg
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
115
|
|
|
116
|
+
# Parse response
|
|
116
117
|
try:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if response.status_code != 200:
|
|
132
|
-
error_msg = f"HTTP {response.status_code}: {response.reason_phrase}"
|
|
133
|
-
logger.error(f" Error: {response.text[:200]}")
|
|
134
|
-
raise Exception(error_msg)
|
|
135
|
-
|
|
136
|
-
response_data = response.json()
|
|
137
|
-
logger.debug(f" Response: {json.dumps(response_data)[:200]}...")
|
|
138
|
-
|
|
139
|
-
# Validate JSON-RPC response
|
|
140
|
-
if "jsonrpc" not in response_data:
|
|
141
|
-
logger.warning(" WARNING: Missing 'jsonrpc' field")
|
|
142
|
-
|
|
143
|
-
if "result" in response_data:
|
|
144
|
-
logger.info(" Has 'result' field [OK]")
|
|
145
|
-
return response_data["result"]
|
|
146
|
-
elif "error" in response_data:
|
|
147
|
-
logger.error(f" Has 'error' field: {response_data['error']}")
|
|
148
|
-
raise Exception(f"Remote error: {response_data['error']}")
|
|
149
|
-
else:
|
|
150
|
-
logger.warning(" No 'result' or 'error' field")
|
|
151
|
-
return response_data
|
|
152
|
-
|
|
153
|
-
except httpx.TimeoutException:
|
|
154
|
-
logger.error("ERROR: Request timeout")
|
|
155
|
-
raise Exception("Request timeout")
|
|
156
|
-
except httpx.RequestError as e:
|
|
157
|
-
logger.error(f"ERROR: Request failed - {str(e)}")
|
|
158
|
-
raise Exception(f"Request failed: {str(e)}")
|
|
118
|
+
response_data = response.json()
|
|
119
|
+
logger.debug(f" Response: {json.dumps(response_data)[:200]}...")
|
|
120
|
+
|
|
121
|
+
# Validate JSON-RPC response
|
|
122
|
+
if "jsonrpc" not in response_data:
|
|
123
|
+
logger.warning(" WARNING: Missing 'jsonrpc' field")
|
|
124
|
+
|
|
125
|
+
if "result" in response_data:
|
|
126
|
+
logger.info(" Has 'result' field [OK]")
|
|
127
|
+
elif "error" in response_data:
|
|
128
|
+
logger.info(f" Has 'error' field: {response_data['error']}")
|
|
129
|
+
|
|
130
|
+
return response_data
|
|
131
|
+
|
|
159
132
|
except json.JSONDecodeError as e:
|
|
160
|
-
logger.error(f"ERROR: Invalid JSON
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"""Entry point for the MCP proxy server."""
|
|
170
|
-
# Validate configuration before starting
|
|
171
|
-
validate_config()
|
|
172
|
-
|
|
173
|
-
# Create proxy and server instances (moved here to avoid running on import)
|
|
174
|
-
global proxy, mcp
|
|
175
|
-
proxy = EulerianMCPProxy()
|
|
176
|
-
mcp = FastMCP("eulerian-marketing-platform")
|
|
177
|
-
|
|
178
|
-
# Register tools
|
|
179
|
-
@mcp.tool()
|
|
180
|
-
async def list_remote_tools() -> dict[str, Any]:
|
|
181
|
-
"""List all available tools from the remote Eulerian MCP server."""
|
|
182
|
-
try:
|
|
183
|
-
result = await proxy.forward_request("tools/list")
|
|
184
|
-
return result
|
|
185
|
-
except Exception as e:
|
|
186
|
-
logger.error(f"Failed to list tools: {str(e)}")
|
|
187
|
-
return {"error": str(e), "tools": []}
|
|
133
|
+
logger.error(f" ERROR: Invalid JSON - {e}")
|
|
134
|
+
return {
|
|
135
|
+
"jsonrpc": "2.0",
|
|
136
|
+
"id": request_id,
|
|
137
|
+
"error": {
|
|
138
|
+
"code": -32700,
|
|
139
|
+
"message": f"Invalid JSON: {str(e)}"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
188
142
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.error(f"Failed to call tool '{tool_name}': {str(e)}")
|
|
200
|
-
return {"error": str(e), "tool": tool_name}
|
|
143
|
+
except httpx.TimeoutException:
|
|
144
|
+
logger.error("ERROR: Request timeout")
|
|
145
|
+
return {
|
|
146
|
+
"jsonrpc": "2.0",
|
|
147
|
+
"id": request_id,
|
|
148
|
+
"error": {
|
|
149
|
+
"code": -32000,
|
|
150
|
+
"message": "Request timeout"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
201
153
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
154
|
+
except httpx.RequestError as e:
|
|
155
|
+
logger.error(f"ERROR: Request failed - {str(e)}")
|
|
156
|
+
return {
|
|
157
|
+
"jsonrpc": "2.0",
|
|
158
|
+
"id": request_id,
|
|
159
|
+
"error": {
|
|
160
|
+
"code": -32000,
|
|
161
|
+
"message": f"Request failed: {str(e)}"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
211
164
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"ERROR: Unexpected error - {str(e)}")
|
|
167
|
+
import traceback
|
|
168
|
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
|
169
|
+
return {
|
|
170
|
+
"jsonrpc": "2.0",
|
|
171
|
+
"id": request_id,
|
|
172
|
+
"error": {
|
|
173
|
+
"code": -32000,
|
|
174
|
+
"message": f"Error: {str(e)}"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main() -> None:
|
|
180
|
+
"""Entry point for the MCP proxy server.
|
|
222
181
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"protocolVersion": "2024-11-05",
|
|
229
|
-
"capabilities": {},
|
|
230
|
-
"clientInfo": {"name": "eulerian-mcp-proxy", "version": "0.1.1"}
|
|
231
|
-
})
|
|
232
|
-
return result
|
|
233
|
-
except Exception as e:
|
|
234
|
-
logger.error(f"Failed to get server info: {str(e)}")
|
|
235
|
-
return {"error": str(e)}
|
|
182
|
+
Runs a transparent stdio proxy that forwards all JSON-RPC requests
|
|
183
|
+
to the remote Eulerian MCP server.
|
|
184
|
+
"""
|
|
185
|
+
# Validate configuration
|
|
186
|
+
validate_config()
|
|
236
187
|
|
|
237
|
-
logger.info("
|
|
238
|
-
logger.info("
|
|
239
|
-
logger.info("
|
|
240
|
-
logger.info("
|
|
241
|
-
logger.info("
|
|
242
|
-
logger.info(" - read_eulerian_resource: Read a specific resource")
|
|
243
|
-
logger.info(" - get_server_info: Get remote server information")
|
|
188
|
+
logger.info("=== EULERIAN MCP PROXY START ===")
|
|
189
|
+
logger.info(f"Endpoint: {EMP_API_ENDPOINT}")
|
|
190
|
+
logger.info(f"Token: {EMP_API_TOKEN[:10] if EMP_API_TOKEN else 'None'}...")
|
|
191
|
+
logger.info(f"Timeout: {float(os.environ.get('EMP_TIMEOUT', '300'))}s")
|
|
192
|
+
logger.info("Starting stdio proxy - all remote tools will be available")
|
|
244
193
|
|
|
245
|
-
# Run the server in stdio mode (default for MCP)
|
|
246
194
|
try:
|
|
247
|
-
|
|
195
|
+
# Read from stdin line by line
|
|
196
|
+
for line in sys.stdin:
|
|
197
|
+
line = line.strip()
|
|
198
|
+
if not line:
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
# Parse request
|
|
203
|
+
request_data = json.loads(line)
|
|
204
|
+
|
|
205
|
+
# Forward to remote server
|
|
206
|
+
response_data = forward_request(request_data)
|
|
207
|
+
|
|
208
|
+
# Send response to stdout
|
|
209
|
+
response_json = json.dumps(response_data)
|
|
210
|
+
print(response_json, flush=True)
|
|
211
|
+
logger.info(" Response forwarded [OK]")
|
|
212
|
+
|
|
213
|
+
except json.JSONDecodeError as e:
|
|
214
|
+
logger.error(f"ERROR: Invalid JSON in request - {e}")
|
|
215
|
+
error_response = {
|
|
216
|
+
"jsonrpc": "2.0",
|
|
217
|
+
"id": None,
|
|
218
|
+
"error": {
|
|
219
|
+
"code": -32700,
|
|
220
|
+
"message": f"Parse error: {str(e)}"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
print(json.dumps(error_response), flush=True)
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(f"ERROR: Unexpected error processing request - {str(e)}")
|
|
227
|
+
import traceback
|
|
228
|
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
|
229
|
+
error_response = {
|
|
230
|
+
"jsonrpc": "2.0",
|
|
231
|
+
"id": None,
|
|
232
|
+
"error": {
|
|
233
|
+
"code": -32000,
|
|
234
|
+
"message": f"Error: {str(e)}"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
print(json.dumps(error_response), flush=True)
|
|
238
|
+
|
|
248
239
|
except KeyboardInterrupt:
|
|
249
240
|
logger.info("Server stopped by user")
|
|
250
241
|
except Exception as e:
|
{eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: eulerian-marketing-platform
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: MCP server for Eulerian Marketing Platform - enables AI assistants to interact with Eulerian's marketing analytics and campaign management APIs
|
|
5
5
|
Author-email: Eulerian Technologies <mathieu@eulerian.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,7 +24,6 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: httpx (>=0.25.0)
|
|
27
|
-
Requires-Dist: mcp (>=1.2.0)
|
|
28
27
|
Provides-Extra: deployment
|
|
29
28
|
Requires-Dist: uv (>=0.5.0) ; extra == 'deployment'
|
|
30
29
|
Provides-Extra: dev
|
|
@@ -37,7 +36,7 @@ Requires-Dist: ruff (>=0.1.0) ; extra == 'dev'
|
|
|
37
36
|
|
|
38
37
|
# Eulerian Marketing Platform MCP Server
|
|
39
38
|
|
|
40
|
-
A Model Context Protocol (MCP) **proxy server** that bridges AI assistants (Claude Desktop, Gemini CLI,
|
|
39
|
+
A Model Context Protocol (MCP) **proxy server** that bridges AI assistants (Claude Desktop, Gemini CLI, etc ...) to a remote Eulerian Marketing Platform MCP server. This proxy handles authentication, request forwarding, and provides a local MCP interface to your remote Eulerian instance.
|
|
41
40
|
|
|
42
41
|
## How It Works
|
|
43
42
|
|
|
@@ -62,12 +61,11 @@ The proxy:
|
|
|
62
61
|
- **🔌 Proxy Architecture**: Bridges local MCP clients to remote Eulerian MCP server via HTTP
|
|
63
62
|
- **🔐 Secure Authentication**: Uses Bearer token authentication for remote server access
|
|
64
63
|
- **🌐 Cross-platform support**: Works on Windows, Linux, and macOS
|
|
65
|
-
- **🤖 Multiple AI clients**: Compatible with Claude Desktop
|
|
64
|
+
- **🤖 Multiple AI clients**: Compatible with Claude Desktop and Gemini CLI
|
|
66
65
|
- **📝 Comprehensive Logging**: Logs all requests/responses for debugging
|
|
67
66
|
- **⚡ Async HTTP**: Non-blocking requests using httpx for better performance
|
|
68
67
|
- **🛠️ Tool Discovery**: Automatically discovers and exposes remote tools
|
|
69
68
|
- **⏱️ Configurable Timeouts**: Adjustable request timeouts
|
|
70
|
-
- **🔍 Easy Installation**: Deploy with a single command using `uvx`
|
|
71
69
|
|
|
72
70
|
## Prerequisites
|
|
73
71
|
|
|
@@ -77,63 +75,18 @@ The proxy:
|
|
|
77
75
|
- One of the following AI clients:
|
|
78
76
|
- Claude Desktop (Windows, macOS, Linux)
|
|
79
77
|
- Gemini CLI
|
|
80
|
-
- Mistral AI Le Chat (requires remote deployment)
|
|
81
78
|
|
|
82
79
|
## Available Tools
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
1. **`list_remote_tools()`** - Discover all tools available on the remote Eulerian server
|
|
87
|
-
2. **`call_eulerian_tool(tool_name, arguments)`** - Call any remote tool with arguments
|
|
88
|
-
3. **`get_eulerian_resources()`** - List available resources (data sources)
|
|
89
|
-
4. **`read_eulerian_resource(uri)`** - Read a specific resource by URI
|
|
90
|
-
5. **`get_server_info()`** - Get remote server version and capabilities
|
|
91
|
-
|
|
92
|
-
For detailed information on how these tools work, see [FASTMCP_PROXY_GUIDE.md](FASTMCP_PROXY_GUIDE.md).
|
|
81
|
+
All API Endpoints supported by the [Eulerian API](https://doc.api.eulerian.com) can be queried through the current MCP.
|
|
93
82
|
|
|
94
83
|
## Installation
|
|
95
84
|
|
|
96
|
-
### Prerequisites Check
|
|
97
|
-
|
|
98
|
-
Before installing, verify you have `uvx` installed. This is the recommended way to run the MCP server.
|
|
99
|
-
|
|
100
|
-
**Check if uvx is installed:**
|
|
101
|
-
```bash
|
|
102
|
-
uvx --version
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**If not installed, see [UVX_DEPLOYMENT_GUIDE.md](UVX_DEPLOYMENT_GUIDE.md) for detailed installation instructions.**
|
|
106
|
-
|
|
107
|
-
**Quick install (Linux/macOS):**
|
|
108
|
-
```bash
|
|
109
|
-
# Run the included script
|
|
110
|
-
chmod +x install-uvx.sh
|
|
111
|
-
./install-uvx.sh
|
|
112
|
-
|
|
113
|
-
# Or install manually
|
|
114
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Quick install (Windows PowerShell - Run as Administrator):**
|
|
118
|
-
```powershell
|
|
119
|
-
# Run the included script
|
|
120
|
-
.\install-uvx.ps1
|
|
121
|
-
|
|
122
|
-
# Or install manually
|
|
123
|
-
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
85
|
### Quick Start (Recommended)
|
|
129
86
|
|
|
130
|
-
The easiest way to use this MCP server is with `
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
# No installation needed! Just configure your AI client (see below)
|
|
134
|
-
```
|
|
87
|
+
The easiest way to use this MCP server is with `pip`.
|
|
135
88
|
|
|
136
|
-
###
|
|
89
|
+
### Install via pip
|
|
137
90
|
|
|
138
91
|
If you prefer to install the package globally:
|
|
139
92
|
|
|
@@ -154,8 +107,8 @@ pip install -e .
|
|
|
154
107
|
### Required Environment Variables
|
|
155
108
|
|
|
156
109
|
- `EMP_API_ENDPOINT`: Your remote Eulerian Marketing Platform MCP server URL (HTTP endpoint)
|
|
157
|
-
- Example: `https://
|
|
158
|
-
- `EMP_API_TOKEN`: Your authentication token for the remote server
|
|
110
|
+
- Example: `https://dem.api.eulerian.com/mcp`
|
|
111
|
+
- `EMP_API_TOKEN`: Your authentication token for the remote server, it is the one linked to your Eulerian account.
|
|
159
112
|
|
|
160
113
|
### Optional Environment Variables
|
|
161
114
|
|
|
@@ -197,23 +150,6 @@ Claude Desktop supports local MCP servers via stdio transport.
|
|
|
197
150
|
|
|
198
151
|
3. **Add the server configuration**:
|
|
199
152
|
|
|
200
|
-
```json
|
|
201
|
-
{
|
|
202
|
-
"mcpServers": {
|
|
203
|
-
"eulerian-marketing-platform": {
|
|
204
|
-
"command": "uvx",
|
|
205
|
-
"args": ["eulerian-marketing-platform"],
|
|
206
|
-
"env": {
|
|
207
|
-
"EMP_API_ENDPOINT": "https://your-eulerian-instance.com/mcp",
|
|
208
|
-
"EMP_API_TOKEN": "your_authentication_token_here"
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Alternative: If using pip installation**:
|
|
216
|
-
|
|
217
153
|
```json
|
|
218
154
|
{
|
|
219
155
|
"mcpServers": {
|
|
@@ -244,7 +180,6 @@ Claude Desktop supports local MCP servers via stdio transport.
|
|
|
244
180
|
|
|
245
181
|
**Linux**:
|
|
246
182
|
- The config directory may not exist initially - create it with: `mkdir -p ~/.config/Claude`
|
|
247
|
-
- Ensure `uvx` is installed: `pip install uv`
|
|
248
183
|
|
|
249
184
|
**macOS**:
|
|
250
185
|
- Access the config via Finder: `Cmd + Shift + G` → `~/Library/Application Support/Claude/`
|
|
@@ -285,8 +220,8 @@ nano ~/.gemini/settings.json
|
|
|
285
220
|
{
|
|
286
221
|
"mcpServers": {
|
|
287
222
|
"eulerian-marketing-platform": {
|
|
288
|
-
"command": "
|
|
289
|
-
"args": ["
|
|
223
|
+
"command": "python",
|
|
224
|
+
"args": ["-m", "eulerian_marketing_platform.server"],
|
|
290
225
|
"env": {
|
|
291
226
|
"EMP_API_ENDPOINT": "https://your-eulerian-instance.com/mcp",
|
|
292
227
|
"EMP_API_TOKEN": "your_authentication_token_here"
|
|
@@ -317,102 +252,6 @@ gemini
|
|
|
317
252
|
|
|
318
253
|
---
|
|
319
254
|
|
|
320
|
-
### 3. Mistral AI (Le Chat)
|
|
321
|
-
|
|
322
|
-
Mistral's Le Chat web interface supports MCP servers through **Custom Connectors**, but they must be deployed as **remote servers** with HTTP/SSE transport.
|
|
323
|
-
|
|
324
|
-
#### Important Limitations
|
|
325
|
-
|
|
326
|
-
- ❌ **Local stdio servers are NOT supported** by Mistral Le Chat
|
|
327
|
-
- ✅ **Only remote HTTP/SSE servers** are supported
|
|
328
|
-
- 📡 Your server must be publicly accessible via HTTPS
|
|
329
|
-
|
|
330
|
-
#### Deployment Options
|
|
331
|
-
|
|
332
|
-
You'll need to deploy your MCP server to a cloud platform. Popular options include:
|
|
333
|
-
|
|
334
|
-
- **Render** (https://render.com)
|
|
335
|
-
- **Railway** (https://railway.app)
|
|
336
|
-
- **Fly.io** (https://fly.io)
|
|
337
|
-
- **AWS Lambda** (with API Gateway)
|
|
338
|
-
- **Google Cloud Run**
|
|
339
|
-
- **Azure Container Instances**
|
|
340
|
-
|
|
341
|
-
#### Server Modification for Remote Deployment
|
|
342
|
-
|
|
343
|
-
Modify the `server.py` to support HTTP/SSE transport:
|
|
344
|
-
|
|
345
|
-
```python
|
|
346
|
-
# In your server.py main() function
|
|
347
|
-
def main() -> None:
|
|
348
|
-
"""Entry point for the MCP server."""
|
|
349
|
-
validate_config()
|
|
350
|
-
|
|
351
|
-
# For remote deployment (Mistral AI)
|
|
352
|
-
import sys
|
|
353
|
-
if "--remote" in sys.argv:
|
|
354
|
-
mcp.run(transport="sse", port=8000)
|
|
355
|
-
else:
|
|
356
|
-
# Default stdio for local clients
|
|
357
|
-
mcp.run()
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
#### Example: Deploy to Render
|
|
361
|
-
|
|
362
|
-
1. **Create a `render.yaml` file**:
|
|
363
|
-
|
|
364
|
-
```yaml
|
|
365
|
-
services:
|
|
366
|
-
- type: web
|
|
367
|
-
name: eulerian-mcp-server
|
|
368
|
-
env: python
|
|
369
|
-
buildCommand: pip install -e .
|
|
370
|
-
startCommand: python -m eulerian_marketing_platform.server --remote
|
|
371
|
-
envVars:
|
|
372
|
-
- key: EMP_API_ENDPOINT
|
|
373
|
-
sync: false
|
|
374
|
-
- key: EMP_API_TOKEN
|
|
375
|
-
sync: false
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
2. **Push to GitHub and connect to Render**
|
|
379
|
-
|
|
380
|
-
3. **Set environment variables in Render dashboard**
|
|
381
|
-
|
|
382
|
-
#### Setup in Mistral Le Chat
|
|
383
|
-
|
|
384
|
-
1. **Open Le Chat** (https://chat.mistral.ai)
|
|
385
|
-
|
|
386
|
-
2. **Navigate to Connectors**:
|
|
387
|
-
- Click the sidebar toggle
|
|
388
|
-
- Go to `Intelligence` → `Connectors`
|
|
389
|
-
- Click `+ Add Connector`
|
|
390
|
-
- Select `Add custom connector`
|
|
391
|
-
|
|
392
|
-
3. **Configure the connector**:
|
|
393
|
-
- **Name**: Eulerian Marketing Platform
|
|
394
|
-
- **URL**: `https://your-deployed-server.render.com/mcp`
|
|
395
|
-
- **Description**: Access to Eulerian Marketing Platform analytics and campaigns
|
|
396
|
-
- **Authentication**:
|
|
397
|
-
- Select `API Token Authentication` if your deployment requires it
|
|
398
|
-
- Or `No Authentication` if your server handles auth via environment variables
|
|
399
|
-
|
|
400
|
-
4. **Connect and test**:
|
|
401
|
-
- Click `Connect`
|
|
402
|
-
- Once connected, enable it in a chat session
|
|
403
|
-
- Ask: "What Eulerian Marketing Platform capabilities do you have?"
|
|
404
|
-
|
|
405
|
-
#### Security Recommendations for Remote Deployment
|
|
406
|
-
|
|
407
|
-
- ✅ Always use HTTPS (not HTTP)
|
|
408
|
-
- ✅ Implement rate limiting
|
|
409
|
-
- ✅ Add request origin validation
|
|
410
|
-
- ✅ Use environment variables for secrets (never hardcode)
|
|
411
|
-
- ✅ Monitor server logs for unusual activity
|
|
412
|
-
- ✅ Consider adding IP allowlisting if possible
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
255
|
## Usage Examples
|
|
417
256
|
|
|
418
257
|
Once configured with any client, you can interact with your remote Eulerian Marketing Platform:
|
|
@@ -421,17 +260,14 @@ Once configured with any client, you can interact with your remote Eulerian Mark
|
|
|
421
260
|
User: "What tools are available from Eulerian?"
|
|
422
261
|
→ Proxy calls list_remote_tools() and returns all available tools
|
|
423
262
|
|
|
424
|
-
User: "Call the
|
|
425
|
-
→ Proxy forwards to remote server and
|
|
263
|
+
User: "Call the update_goal tool"
|
|
264
|
+
→ Proxy forwards to remote server and update goal settings
|
|
426
265
|
|
|
427
266
|
User: "Show me campaign details for CAMP-12345"
|
|
428
267
|
→ Claude uses call_eulerian_tool() to fetch specific campaign
|
|
429
268
|
|
|
430
269
|
User: "What resources are available?"
|
|
431
270
|
→ Proxy lists all available data sources
|
|
432
|
-
|
|
433
|
-
User: "Read the configuration at eulerian://config/settings"
|
|
434
|
-
→ Proxy fetches and returns the configuration
|
|
435
271
|
```
|
|
436
272
|
|
|
437
273
|
The AI assistant will automatically use the appropriate proxy tools to fulfill your requests.
|
|
@@ -481,13 +317,6 @@ You'll see detailed logging of:
|
|
|
481
317
|
- Verify the settings.json is valid JSON
|
|
482
318
|
- Restart Gemini CLI
|
|
483
319
|
|
|
484
|
-
#### Mistral connector fails to connect
|
|
485
|
-
- **Solution**:
|
|
486
|
-
- Verify your server is publicly accessible via `curl https://your-server.com/mcp`
|
|
487
|
-
- Check server logs for errors
|
|
488
|
-
- Ensure you're using HTTPS (not HTTP)
|
|
489
|
-
- Verify the `/mcp` endpoint path is correct
|
|
490
|
-
|
|
491
320
|
### Debug Mode
|
|
492
321
|
|
|
493
322
|
For detailed debugging:
|
|
@@ -556,9 +385,14 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
556
385
|
|
|
557
386
|
## Changelog
|
|
558
387
|
|
|
388
|
+
### 0.2.0
|
|
389
|
+
- Move to full Proxy mode
|
|
390
|
+
- Remove instructions for uvx deployment
|
|
391
|
+
- Remove instructions for Mistral integration (too complex)
|
|
392
|
+
|
|
559
393
|
### 0.1.0 (Initial Release)
|
|
560
394
|
- Initial MCP server implementation
|
|
561
|
-
- Support for Claude Desktop, Gemini CLI
|
|
395
|
+
- Support for Claude Desktop, Gemini CLI
|
|
562
396
|
- Cross-platform support (Windows, Linux, macOS)
|
|
563
397
|
- Environment-based configuration
|
|
564
398
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
eulerian_marketing_platform/__init__.py,sha256=HibL07V6VMhqFcQSFGJm8ogUu9Qcfl2SSenCfdKtBFY,428
|
|
2
|
+
eulerian_marketing_platform/server.py,sha256=y1vy-LvSjG18dvDv65hO875s_7vogbb5bdNwTetKKcU,8335
|
|
3
|
+
eulerian_marketing_platform-0.2.1.dist-info/LICENSE,sha256=eIqBqE_fRsqQJ8F-2v0e-8WzZqdshsCqnzmqLAWrNHU,1078
|
|
4
|
+
eulerian_marketing_platform-0.2.1.dist-info/METADATA,sha256=WDzHWUPBn0P6MC3PhFXOx70BRGa2xGHIdwoycImLCaA,12111
|
|
5
|
+
eulerian_marketing_platform-0.2.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
6
|
+
eulerian_marketing_platform-0.2.1.dist-info/entry_points.txt,sha256=rrPZptATSS9PUtH9gzCYq0WuP6eahkF-DkdUP1FaYfk,88
|
|
7
|
+
eulerian_marketing_platform-0.2.1.dist-info/top_level.txt,sha256=nidh3T6fw-mLjUqZwQ8AiMScS4usuH0WXW4ZgG4HYCo,28
|
|
8
|
+
eulerian_marketing_platform-0.2.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
eulerian_marketing_platform/__init__.py,sha256=osuJ8iP6O20ElZXPoZlOQ7sNbZ93kgMz3we8mm_B2-Y,428
|
|
2
|
-
eulerian_marketing_platform/server.py,sha256=bUVn_b05ZritTtNflldCxoFn8YwyrQBqrcbQZ5ceZAg,9810
|
|
3
|
-
eulerian_marketing_platform-0.1.2.dist-info/LICENSE,sha256=eIqBqE_fRsqQJ8F-2v0e-8WzZqdshsCqnzmqLAWrNHU,1078
|
|
4
|
-
eulerian_marketing_platform-0.1.2.dist-info/METADATA,sha256=gfE3v09r1B4dd8Vi_snjgd9JQDWaHplKnZMsp1ML-34,16957
|
|
5
|
-
eulerian_marketing_platform-0.1.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
6
|
-
eulerian_marketing_platform-0.1.2.dist-info/entry_points.txt,sha256=rrPZptATSS9PUtH9gzCYq0WuP6eahkF-DkdUP1FaYfk,88
|
|
7
|
-
eulerian_marketing_platform-0.1.2.dist-info/top_level.txt,sha256=nidh3T6fw-mLjUqZwQ8AiMScS4usuH0WXW4ZgG4HYCo,28
|
|
8
|
-
eulerian_marketing_platform-0.1.2.dist-info/RECORD,,
|
{eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/LICENSE
RENAMED
|
File without changes
|
{eulerian_marketing_platform-0.1.2.dist-info → eulerian_marketing_platform-0.2.1.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|