eulerian-marketing-platform 0.1.2__tar.gz → 0.2.0__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 (17) hide show
  1. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/PKG-INFO +1 -1
  2. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/pyproject.toml +1 -9
  3. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/setup.cfg +1 -1
  4. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform/__init__.py +1 -1
  5. eulerian_marketing_platform-0.2.0/src/eulerian_marketing_platform/server.py +247 -0
  6. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform.egg-info/PKG-INFO +1 -1
  7. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform.egg-info/requires.txt +0 -1
  8. eulerian_marketing_platform-0.1.2/src/eulerian_marketing_platform/server.py +0 -256
  9. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/.env.example +0 -0
  10. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/LICENSE +0 -0
  11. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/MANIFEST.in +0 -0
  12. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/README.md +0 -0
  13. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/requirements.txt +0 -0
  14. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform.egg-info/SOURCES.txt +0 -0
  15. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform.egg-info/dependency_links.txt +0 -0
  16. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/src/eulerian_marketing_platform.egg-info/entry_points.txt +0 -0
  17. {eulerian_marketing_platform-0.1.2 → eulerian_marketing_platform-0.2.0}/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.1.2
3
+ Version: 0.2.0
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.1.2"
7
+ version = "0.2.0"
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 = [
@@ -26,7 +26,6 @@ classifiers = [
26
26
  "Topic :: Software Development :: Libraries :: Python Modules",
27
27
  ]
28
28
  dependencies = [
29
- "mcp>=1.2.0",
30
29
  "httpx>=0.25.0",
31
30
  ]
32
31
  requires-python = ">=3.10"
@@ -53,12 +52,5 @@ Issues = "https://github.com/EulerianTechnologies/eulerian-marketing-platform-mc
53
52
  Documentation = "https://github.com/EulerianTechnologies/eulerian-marketing-platform-mcp#readme"
54
53
  Repository = "https://github.com/EulerianTechnologies/eulerian-marketing-platform-mcp"
55
54
 
56
- [tool.setuptools]
57
- package-dir = {"" = "src"}
58
-
59
55
  [tool.setuptools.packages.find]
60
56
  where = ["src"]
61
- namespaces = false
62
-
63
- [tool.setuptools.package-data]
64
- eulerian_marketing_platform = ["py.typed"]
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = eulerian_marketing_platform
3
- version = 0.1.2
3
+ version = 0.2.0
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.1.2"
7
+ __version__ = "0.2.0"
8
8
  __author__ = "Eulerian Technologies"
9
9
  __all__ = []
10
10
 
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env python3
2
+ """Eulerian Marketing Platform MCP Proxy Server.
3
+
4
+ This server acts as a transparent proxy between local MCP clients (Claude Desktop, etc.)
5
+ and a remote Eulerian Marketing Platform MCP server via HTTP.
6
+
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
+ """
10
+
11
+ import os
12
+ import sys
13
+ import json
14
+ import logging
15
+ import tempfile
16
+ from datetime import datetime
17
+ import httpx
18
+
19
+ # Configuration
20
+ EMP_API_ENDPOINT = os.environ.get("EMP_API_ENDPOINT")
21
+ EMP_API_TOKEN = os.environ.get("EMP_API_TOKEN")
22
+
23
+ # Logging setup - Use cross-platform temp directory
24
+ DEFAULT_LOG_FILE = os.path.join(tempfile.gettempdir(), "eulerian-mcp-proxy.log")
25
+ LOG_FILE = os.environ.get("EMP_LOG_FILE", DEFAULT_LOG_FILE)
26
+
27
+ # Ensure log directory exists
28
+ log_dir = os.path.dirname(LOG_FILE)
29
+ if log_dir:
30
+ os.makedirs(log_dir, exist_ok=True)
31
+
32
+ # Configure logging to file and stderr with UTF-8 encoding for cross-platform compatibility
33
+ file_handler = logging.FileHandler(LOG_FILE, encoding='utf-8')
34
+ file_handler.setLevel(logging.INFO)
35
+ file_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
36
+
37
+ # For stderr, use system encoding with error handling
38
+ stream_handler = logging.StreamHandler(sys.stderr)
39
+ stream_handler.setLevel(logging.INFO)
40
+ stream_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
41
+
42
+ # Configure root logger
43
+ logging.basicConfig(
44
+ level=logging.INFO,
45
+ handlers=[file_handler, stream_handler]
46
+ )
47
+ logger = logging.getLogger(__name__)
48
+
49
+
50
+ def validate_config() -> None:
51
+ """Validate that required environment variables are set."""
52
+ missing_vars = []
53
+
54
+ if not EMP_API_ENDPOINT:
55
+ missing_vars.append("EMP_API_ENDPOINT")
56
+ if not EMP_API_TOKEN:
57
+ missing_vars.append("EMP_API_TOKEN")
58
+
59
+ if missing_vars:
60
+ logger.error("The following required environment variables are missing:")
61
+ for var in missing_vars:
62
+ logger.error(f" - {var}")
63
+ logger.error("\nPlease set these environment variables before running the server.")
64
+ logger.error("Example:")
65
+ logger.error(" export EMP_API_ENDPOINT=https://your-eulerian-instance.com/api/mcp")
66
+ logger.error(" export EMP_API_TOKEN=your_authentication_token")
67
+ sys.exit(1)
68
+
69
+
70
+ def forward_request(request_data: dict) -> dict:
71
+ """Forward a JSON-RPC request to the remote MCP server.
72
+
73
+ Args:
74
+ request_data: The JSON-RPC request to forward
75
+
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")
83
+
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}")
89
+
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}")
102
+
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
+
116
+ # Parse response
117
+ try:
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
+
132
+ except json.JSONDecodeError as e:
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
+ }
142
+
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
+ }
153
+
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
+ }
164
+
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.
181
+
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()
187
+
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")
193
+
194
+ try:
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
+
239
+ except KeyboardInterrupt:
240
+ logger.info("Server stopped by user")
241
+ except Exception as e:
242
+ logger.error(f"Server error: {str(e)}")
243
+ raise
244
+
245
+
246
+ if __name__ == "__main__":
247
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eulerian-marketing-platform
3
- Version: 0.1.2
3
+ Version: 0.2.0
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
@@ -1,256 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Eulerian Marketing Platform MCP Proxy Server.
3
-
4
- This server acts as a proxy/bridge between local MCP clients (Claude Desktop, Gemini CLI, etc.)
5
- and a remote Eulerian Marketing Platform MCP server via HTTP.
6
-
7
- It uses EMP_API_ENDPOINT and EMP_API_TOKEN environment variables to authenticate
8
- and forward requests to the remote MCP server.
9
- """
10
-
11
- import os
12
- import sys
13
- import json
14
- import logging
15
- import tempfile
16
- from datetime import datetime
17
- from typing import Any
18
- from pathlib import Path
19
- import httpx
20
-
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
- # Configuration
27
- EMP_API_ENDPOINT = os.environ.get("EMP_API_ENDPOINT")
28
- EMP_API_TOKEN = os.environ.get("EMP_API_TOKEN")
29
-
30
- # Logging setup - Use cross-platform temp directory
31
- # Default to system temp directory if EMP_LOG_FILE not set
32
- DEFAULT_LOG_FILE = os.path.join(tempfile.gettempdir(), "eulerian-mcp-proxy.log")
33
- LOG_FILE = os.environ.get("EMP_LOG_FILE", DEFAULT_LOG_FILE)
34
-
35
- # Ensure log directory exists
36
- log_dir = os.path.dirname(LOG_FILE)
37
- if log_dir: # Only create if there's a directory part
38
- os.makedirs(log_dir, exist_ok=True)
39
-
40
- # Configure logging to file and stderr with UTF-8 encoding for cross-platform compatibility
41
- file_handler = logging.FileHandler(LOG_FILE, encoding='utf-8')
42
- file_handler.setLevel(logging.INFO)
43
- file_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
44
-
45
- # For stderr, use system encoding with error handling
46
- stream_handler = logging.StreamHandler(sys.stderr)
47
- stream_handler.setLevel(logging.INFO)
48
- stream_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
49
-
50
- # Configure root logger
51
- logging.basicConfig(
52
- level=logging.INFO,
53
- handlers=[file_handler, stream_handler]
54
- )
55
- logger = logging.getLogger(__name__)
56
-
57
-
58
- def validate_config() -> None:
59
- """Validate that required environment variables are set."""
60
- missing_vars = []
61
-
62
- if not EMP_API_ENDPOINT:
63
- missing_vars.append("EMP_API_ENDPOINT")
64
- if not EMP_API_TOKEN:
65
- missing_vars.append("EMP_API_TOKEN")
66
-
67
- if missing_vars:
68
- logger.error("The following required environment variables are missing:")
69
- for var in missing_vars:
70
- logger.error(f" - {var}")
71
- logger.error("\nPlease set these environment variables before running the server.")
72
- logger.error("Example:")
73
- logger.error(" export EMP_API_ENDPOINT=https://your-eulerian-instance.com/api/mcp")
74
- logger.error(" export EMP_API_TOKEN=your_authentication_token")
75
- sys.exit(1)
76
-
77
-
78
- class EulerianMCPProxy:
79
- """Proxy that forwards MCP requests to remote Eulerian server."""
80
-
81
- def __init__(self):
82
- """Initialize the proxy with configuration."""
83
- self.endpoint = EMP_API_ENDPOINT
84
- self.token = EMP_API_TOKEN
85
- self.timeout = float(os.environ.get("EMP_TIMEOUT", "300"))
86
-
87
- logger.info("=== EULERIAN MCP PROXY START ===")
88
- logger.info(f"Endpoint: {self.endpoint}")
89
- logger.info(f"Token: {self.token[:10] if self.token else 'None'}...")
90
- logger.info(f"Timeout: {self.timeout}s")
91
-
92
- async def forward_request(self, method: str, params: dict = None) -> dict[str, Any]:
93
- """Forward a JSON-RPC request to the remote MCP server.
94
-
95
- Args:
96
- method: The JSON-RPC method name
97
- params: The parameters for the method
98
-
99
- Returns:
100
- The response from the remote server
101
-
102
- Raises:
103
- Exception: If the request fails
104
- """
105
- request_data = {
106
- "jsonrpc": "2.0",
107
- "id": 1,
108
- "method": method,
109
- }
110
- if params:
111
- request_data["params"] = params
112
-
113
- logger.info(f">>> REQUEST: {method}")
114
- logger.debug(f" Full request: {json.dumps(request_data)[:200]}...")
115
-
116
- try:
117
- async with httpx.AsyncClient() as client:
118
- logger.info(f"Forwarding to {self.endpoint}")
119
- response = await client.post(
120
- self.endpoint,
121
- headers={
122
- "Content-Type": "application/json",
123
- "Authorization": f"Bearer {self.token}"
124
- },
125
- json=request_data,
126
- timeout=self.timeout
127
- )
128
-
129
- logger.info(f"<<< RESPONSE: HTTP {response.status_code}")
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)}")
159
- except json.JSONDecodeError as e:
160
- logger.error(f"ERROR: Invalid JSON response - {str(e)}")
161
- raise Exception(f"Invalid JSON response: {str(e)}")
162
-
163
-
164
- # Dynamically fetch and register tools from remote server
165
- # (Tools are registered in main() to avoid running on import)
166
-
167
-
168
- def main() -> None:
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": []}
188
-
189
- @mcp.tool()
190
- async def call_eulerian_tool(tool_name: str, arguments: dict[str, Any] = None) -> dict[str, Any]:
191
- """Call a tool on the remote Eulerian MCP server."""
192
- if arguments is None:
193
- arguments = {}
194
- try:
195
- params = {"name": tool_name, "arguments": arguments}
196
- result = await proxy.forward_request("tools/call", params)
197
- return result
198
- except Exception as e:
199
- logger.error(f"Failed to call tool '{tool_name}': {str(e)}")
200
- return {"error": str(e), "tool": tool_name}
201
-
202
- @mcp.tool()
203
- async def get_eulerian_resources() -> dict[str, Any]:
204
- """List all available resources from the remote Eulerian MCP server."""
205
- try:
206
- result = await proxy.forward_request("resources/list")
207
- return result
208
- except Exception as e:
209
- logger.error(f"Failed to list resources: {str(e)}")
210
- return {"error": str(e), "resources": []}
211
-
212
- @mcp.tool()
213
- async def read_eulerian_resource(uri: str) -> dict[str, Any]:
214
- """Read a specific resource from the remote Eulerian MCP server."""
215
- try:
216
- params = {"uri": uri}
217
- result = await proxy.forward_request("resources/read", params)
218
- return result
219
- except Exception as e:
220
- logger.error(f"Failed to read resource '{uri}': {str(e)}")
221
- return {"error": str(e), "uri": uri}
222
-
223
- @mcp.tool()
224
- async def get_server_info() -> dict[str, Any]:
225
- """Get information about the remote Eulerian MCP server."""
226
- try:
227
- result = await proxy.forward_request("initialize", {
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)}
236
-
237
- logger.info("Starting Eulerian MCP Proxy Server...")
238
- logger.info("Available tools:")
239
- logger.info(" - list_remote_tools: List all tools from remote server")
240
- logger.info(" - call_eulerian_tool: Call any remote tool")
241
- logger.info(" - get_eulerian_resources: List available resources")
242
- logger.info(" - read_eulerian_resource: Read a specific resource")
243
- logger.info(" - get_server_info: Get remote server information")
244
-
245
- # Run the server in stdio mode (default for MCP)
246
- try:
247
- mcp.run()
248
- except KeyboardInterrupt:
249
- logger.info("Server stopped by user")
250
- except Exception as e:
251
- logger.error(f"Server error: {str(e)}")
252
- raise
253
-
254
-
255
- if __name__ == "__main__":
256
- main()