eulerian-marketing-platform 0.2.5__tar.gz → 0.2.7__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.
Files changed (16) hide show
  1. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/PKG-INFO +1 -1
  2. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/pyproject.toml +1 -1
  3. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/setup.cfg +1 -1
  4. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform/__init__.py +1 -1
  5. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform/server.py +85 -61
  6. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/PKG-INFO +1 -1
  7. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/.env.example +0 -0
  8. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/LICENSE +0 -0
  9. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/MANIFEST.in +0 -0
  10. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/README.md +0 -0
  11. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/requirements.txt +0 -0
  12. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/SOURCES.txt +0 -0
  13. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/dependency_links.txt +0 -0
  14. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/entry_points.txt +0 -0
  15. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/requires.txt +0 -0
  16. {eulerian_marketing_platform-0.2.5 → eulerian_marketing_platform-0.2.7}/src/eulerian_marketing_platform.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eulerian_marketing_platform
3
- Version: 0.2.5
3
+ Version: 0.2.7
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "eulerian_marketing_platform"
7
- version = "0.2.5"
7
+ version = "0.2.7"
8
8
  description = "MCP server for Eulerian Marketing Platform - enables AI assistants to interact with Eulerian's marketing analytics and campaign management APIs"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = eulerian_marketing_platform
3
- version = 0.2.5
3
+ version = 0.2.7
4
4
 
5
5
  [options]
6
6
  package_dir =
@@ -4,7 +4,7 @@ This package provides a Model Context Protocol (MCP) server that enables
4
4
  AI assistants to interact with Eulerian Marketing Platform APIs.
5
5
  """
6
6
 
7
- __version__ = "0.2.5"
7
+ __version__ = "0.2.7"
8
8
  __author__ = "Eulerian Technologies"
9
9
  __all__ = []
10
10
 
@@ -74,13 +74,12 @@ def forward_request(request_data: dict) -> dict:
74
74
  request_data: The JSON-RPC request to forward
75
75
 
76
76
  Returns:
77
- The JSON-RPC response from the remote server, or None for notifications
77
+ The JSON-RPC response from the remote server
78
78
  """
79
79
  timeout = float(os.environ.get("EMP_TIMEOUT", "300"))
80
80
 
81
81
  request_id = request_data.get("id")
82
82
  method = request_data.get("method")
83
- is_notification = request_id is None
84
83
 
85
84
  logger.info(f">>> REQUEST: {method} (id: {request_id})")
86
85
  logger.debug(f" Full request: {json.dumps(request_data)[:200]}...")
@@ -101,30 +100,14 @@ def forward_request(request_data: dict) -> dict:
101
100
 
102
101
  logger.info(f"<<< RESPONSE: HTTP {response.status_code}")
103
102
 
104
- # Handle HTTP 204 No Content (proper response for notifications)
103
+ # Handle HTTP 204 No Content (common for notifications)
105
104
  if response.status_code == 204:
106
- logger.info(" HTTP 204 No Content - notification acknowledged")
107
- # For notifications, this is expected and correct
108
- if is_notification:
109
- logger.info(" Notification processed - no response sent")
110
- return None
111
- else:
112
- # For regular requests, 204 is unusual but we'll treat it as success with empty result
113
- return {
114
- "jsonrpc": "2.0",
115
- "id": request_id,
116
- "result": None
117
- }
105
+ logger.info(" HTTP 204 No Content - creating empty success response")
106
+ return None
118
107
 
119
108
  if response.status_code != 200:
120
109
  error_msg = f"HTTP {response.status_code}: {response.reason_phrase}"
121
110
  logger.error(f" Error: {response.text[:200]}")
122
-
123
- # For notifications, don't return error responses
124
- if is_notification:
125
- logger.info(" Notification error - no response sent")
126
- return None
127
-
128
111
  return {
129
112
  "jsonrpc": "2.0",
130
113
  "id": request_id,
@@ -134,36 +117,77 @@ def forward_request(request_data: dict) -> dict:
134
117
  }
135
118
  }
136
119
 
137
- # Parse response (only for HTTP 200 responses)
120
+ # Parse response for HTTP 200
138
121
  try:
139
122
  response_data = response.json()
140
123
  logger.debug(f" Response: {json.dumps(response_data)[:200]}...")
141
124
 
142
- # For notifications, we should not send back any response, regardless of what the remote server returns
143
- if is_notification:
144
- logger.info(f" Notification received response from server: {json.dumps(response_data)}")
145
- logger.info(" Notification processed - no response sent")
146
- return None
125
+ # Add detailed logging for different MCP methods
126
+ if "result" in response_data:
127
+ logger.info(" Has 'result' field [OK]")
128
+
129
+ result = response_data.get("result", {})
130
+
131
+ if method == "initialize":
132
+ logger.info(f" Initialize result: protocolVersion={result.get('protocolVersion')}")
133
+ capabilities = result.get("capabilities", {})
134
+ logger.info(f" Server capabilities: {list(capabilities.keys())}")
135
+ server_info = result.get("serverInfo", {})
136
+ logger.info(f" Server info: name={server_info.get('name')}, version={server_info.get('version')}")
137
+
138
+ elif method == "tools/list":
139
+ tools = result.get("tools", [])
140
+ logger.info(f" Tools available: {len(tools)} tools")
141
+ for i, tool in enumerate(tools):
142
+ tool_name = tool.get("name", "unnamed")
143
+ tool_desc = tool.get("description", "no description")[:50]
144
+ logger.info(f" Tool {i+1}: {tool_name} - {tool_desc}")
145
+ # Log input schema info
146
+ input_schema = tool.get("inputSchema", {})
147
+ if "properties" in input_schema:
148
+ props = list(input_schema["properties"].keys())[:3]
149
+ logger.info(f" Input properties: {props}")
150
+
151
+ elif method == "resources/list":
152
+ resources = result.get("resources", [])
153
+ logger.info(f" Resources available: {len(resources)} resources")
154
+ for i, resource in enumerate(resources[:3]):
155
+ res_uri = resource.get("uri", "no uri")
156
+ res_name = resource.get("name", "unnamed")
157
+ logger.info(f" Resource {i+1}: {res_name} ({res_uri})")
158
+ if len(resources) > 3:
159
+ logger.info(f" ... and {len(resources) - 3} more resources")
160
+
161
+ elif method.startswith("tools/call"):
162
+ logger.info(f" Tool call result keys: {list(result.keys())}")
163
+ if "content" in result:
164
+ content = result["content"]
165
+ if isinstance(content, list) and len(content) > 0:
166
+ logger.info(f" Tool returned {len(content)} content items")
167
+ first_item = content[0]
168
+ if isinstance(first_item, dict):
169
+ logger.info(f" First content type: {first_item.get('type', 'unknown')}")
170
+ elif isinstance(content, str):
171
+ logger.info(f" Tool returned string content: {len(content)} chars")
172
+
173
+ else:
174
+ # Generic logging for unknown methods
175
+ logger.info(f" Method '{method}' result keys: {list(result.keys())}")
176
+
177
+ elif "error" in response_data:
178
+ error = response_data["error"]
179
+ error_code = error.get("code", "no code")
180
+ error_message = error.get("message", "no message")
181
+ logger.info(f" Has 'error' field: code={error_code}, message={error_message}")
147
182
 
148
183
  # Validate JSON-RPC response
149
184
  if "jsonrpc" not in response_data:
150
185
  logger.warning(" WARNING: Missing 'jsonrpc' field")
151
186
 
152
- if "result" in response_data:
153
- logger.info(" Has 'result' field [OK]")
154
- elif "error" in response_data:
155
- logger.info(f" Has 'error' field: {response_data['error']}")
156
-
157
187
  return response_data
158
188
 
159
189
  except json.JSONDecodeError as e:
160
190
  logger.error(f" ERROR: Invalid JSON - {e}")
161
-
162
- # For notifications, don't return error responses
163
- if is_notification:
164
- logger.info(" Notification JSON error - no response sent")
165
- return None
166
-
167
191
  return {
168
192
  "jsonrpc": "2.0",
169
193
  "id": request_id,
@@ -175,12 +199,6 @@ def forward_request(request_data: dict) -> dict:
175
199
 
176
200
  except httpx.TimeoutException:
177
201
  logger.error("ERROR: Request timeout")
178
-
179
- # For notifications, don't return error responses
180
- if is_notification:
181
- logger.info(" Notification timeout - no response sent")
182
- return None
183
-
184
202
  return {
185
203
  "jsonrpc": "2.0",
186
204
  "id": request_id,
@@ -192,12 +210,6 @@ def forward_request(request_data: dict) -> dict:
192
210
 
193
211
  except httpx.RequestError as e:
194
212
  logger.error(f"ERROR: Request failed - {str(e)}")
195
-
196
- # For notifications, don't return error responses
197
- if is_notification:
198
- logger.info(" Notification request error - no response sent")
199
- return None
200
-
201
213
  return {
202
214
  "jsonrpc": "2.0",
203
215
  "id": request_id,
@@ -211,12 +223,6 @@ def forward_request(request_data: dict) -> dict:
211
223
  logger.error(f"ERROR: Unexpected error - {str(e)}")
212
224
  import traceback
213
225
  logger.error(f"Traceback: {traceback.format_exc()}")
214
-
215
- # For notifications, don't return error responses
216
- if is_notification:
217
- logger.info(" Notification unexpected error - no response sent")
218
- return None
219
-
220
226
  return {
221
227
  "jsonrpc": "2.0",
222
228
  "id": request_id,
@@ -267,21 +273,32 @@ def main() -> None:
267
273
  # Forward to remote server
268
274
  response_data = forward_request(request_data)
269
275
 
270
- # Only send response for non-notifications (when response_data is not None)
276
+ # Only send response if response_data is not None (handles HTTP 204 notifications)
271
277
  if response_data is not None:
272
278
  response_json = json.dumps(response_data)
273
279
  print(response_json, flush=True)
274
280
  sys.stdout.flush()
275
281
  logger.info(" Response forwarded [OK]")
276
282
  else:
277
- logger.info(" Notification processed (no response sent)")
278
-
283
+ logger.info(" No response sent (notification or HTTP 204)")
284
+
279
285
  except json.JSONDecodeError as e:
280
286
  logger.error(f"ERROR: Invalid JSON in request - {e}")
281
287
  logger.error(f" Problematic line: {line[:200]}")
288
+
289
+ # Try to extract request_id from partial JSON, fallback to None
290
+ request_id = None
291
+ try:
292
+ # Attempt to get ID from partial JSON
293
+ if '"id"' in line:
294
+ partial = json.loads(line.split('"method"')[0] + '"}')
295
+ request_id = partial.get("id")
296
+ except:
297
+ pass
298
+
282
299
  error_response = {
283
300
  "jsonrpc": "2.0",
284
- "id": None,
301
+ "id": request_id,
285
302
  "error": {
286
303
  "code": -32700,
287
304
  "message": f"Parse error: {str(e)}"
@@ -294,9 +311,16 @@ def main() -> None:
294
311
  logger.error(f"ERROR: Unexpected error processing request - {str(e)}")
295
312
  import traceback
296
313
  logger.error(f"Traceback: {traceback.format_exc()}")
314
+
315
+ # Try to get request_id if request_data was parsed successfully
316
+ try:
317
+ request_id = request_data.get("id") if 'request_data' in locals() else None
318
+ except:
319
+ request_id = None
320
+
297
321
  error_response = {
298
322
  "jsonrpc": "2.0",
299
- "id": None,
323
+ "id": request_id,
300
324
  "error": {
301
325
  "code": -32000,
302
326
  "message": f"Error: {str(e)}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eulerian-marketing-platform
3
- Version: 0.2.5
3
+ Version: 0.2.7
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