-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrun_graphistry_mcp.py
More file actions
executable file
·385 lines (339 loc) · 16.9 KB
/
run_graphistry_mcp.py
File metadata and controls
executable file
·385 lines (339 loc) · 16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
#!/usr/bin/env python3
"""
Runner script for Graphistry MCP server.
This script is designed to be invoked by the MCP system.
"""
import os
import sys
import logging
import subprocess
import asyncio
import time
import socket
from pathlib import Path
# Record start time for reporting
START_TIME = time.time()
# Load environment variables from .env file at startup
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(dotenv_path):
try:
# We'll attempt to import and use dotenv later if needed
# For now, just note that we found the .env file
print(f"Found .env file at {dotenv_path}", file=sys.stderr)
except Exception as e:
print(f"Note: Found .env file but couldn't process immediately: {e}", file=sys.stderr)
# Create logs directory if it doesn't exist
current_dir = Path(__file__).parent
logs_dir = current_dir / 'logs'
logs_dir.mkdir(exist_ok=True)
# Configure basic logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename=str(logs_dir / 'graphistry-mcp.log'),
filemode='a'
)
logger = logging.getLogger('graphistry-mcp')
# Function to check if a port is already in use
def is_port_available(port, host='localhost'):
"""
Check if a port is available to use.
Args:
port (int): The port number to check
host (str): The host to check against (default: 'localhost')
Returns:
bool: True if the port is available, False if it's in use
"""
try:
# Create a socket object
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# Try to bind to the port
s.bind((host, port))
# If we get here, the port is available
return True
except socket.error:
# Port is already in use
return False
def find_available_port(start_port=8080, max_attempts=20):
"""
Find an available port starting from a specified port.
Args:
start_port (int): The port to start checking from
max_attempts (int): Maximum number of ports to check
Returns:
int: First available port found, or None if no port is available
"""
for port_offset in range(max_attempts):
port = start_port + port_offset
if is_port_available(port):
return port
return None
# Check if MCP server port is available (commonly uses port 8080)
if not is_port_available(8080):
logger.warning("⚠️ Port 8080 is already in use by another process")
print("⚠️ Warning: Port 8080 is already in use by another process.", file=sys.stderr)
# Find an alternative port
alt_port = find_available_port(8081)
if alt_port:
print(f"💡 Alternative port {alt_port} is available for use if needed.", file=sys.stderr)
logger.info(f"Alternative port {alt_port} is available for use")
# Set environment variable with alternative port suggestion
os.environ["GRAPHISTRY_SUGGESTED_PORT"] = str(alt_port)
else:
print("❌ No alternative ports available in range 8081-8100.", file=sys.stderr)
logger.warning("No alternative ports available in range 8081-8100")
try:
# Try to identify what's using the port
if sys.platform.startswith('linux') or sys.platform == 'darwin': # Linux or macOS
logger.info("Attempting to identify process using port 8080...")
try:
result = subprocess.run(['lsof', '-i', ':8080'], capture_output=True, text=True)
if result.stdout:
print(f"Process using port 8080:\n{result.stdout}", file=sys.stderr)
logger.info(f"Process using port 8080:\n{result.stdout}")
print("To free the port, you can terminate the process using: kill <PID>", file=sys.stderr)
except Exception as e:
logger.error(f"Unable to check what's using port 8080: {e}")
except Exception as e:
logger.error(f"Error while checking port usage: {e}")
# Function to install dependencies
def install_dependency(package):
try:
logger.info(f"Installing {package} with uvx...")
print(f"Installing {package} with uvx...", file=sys.stderr)
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
logger.info(f"Successfully installed {package}")
return True
except Exception as e:
logger.error(f"Failed to install {package}: {e}")
print(f"Failed to install {package}: {e}", file=sys.stderr)
return False
# Check if we're running in the virtual environment
venv_dir = current_dir / '.venv'
venv_python = venv_dir / 'bin' / 'python'
if venv_dir.exists() and venv_python.exists() and not os.environ.get('GRAPHISTRY_VENV_ACTIVE'):
logger.info(f"Activating virtual environment at {venv_dir}")
# Re-execute the script with the virtual environment's Python
os.environ['GRAPHISTRY_VENV_ACTIVE'] = '1'
try:
os.execv(str(venv_python), [str(venv_python), __file__] + sys.argv[1:])
except Exception as e:
logger.error(f"Failed to activate virtual environment: {e}")
print(f"Failed to activate virtual environment: {e}", file=sys.stderr)
logger.info("Running with Python interpreter: " + sys.executable)
print(f"Python version: {sys.version}", file=sys.stderr)
print(f"Python executable: {sys.executable}", file=sys.stderr)
# Add the src directory to the Python path
src_path = current_dir / 'src'
sys.path.insert(0, str(src_path))
sys.path.insert(0, str(current_dir))
# Ensure dependencies are installed
required_packages = ["fastmcp>=2.2.6", "graphistry", "pandas", "networkx", "python-igraph", "python-dotenv", "pydantic"]
for package in required_packages:
try:
if package.startswith("fastmcp"):
import fastmcp
logger.info(f"Successfully imported {package}")
elif package == "graphistry":
import graphistry
logger.info(f"Successfully imported {package}")
elif package == "pandas":
import pandas
logger.info(f"Successfully imported {package}")
elif package == "networkx":
import networkx
logger.info(f"Successfully imported {package}")
elif package == "python-dotenv":
from dotenv import load_dotenv
logger.info(f"Successfully imported {package}")
# Now that dotenv is imported, actually load the .env file
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(dotenv_path):
load_dotenv(dotenv_path)
logger.info(f"Loaded environment variables from {dotenv_path}")
print(f"Loaded environment variables from {dotenv_path}", file=sys.stderr)
elif package == "pydantic":
from pydantic import BaseModel
logger.info(f"Successfully imported {package}")
except ImportError:
logger.warning(f"Dependency {package} not found, installing...")
install_dependency(package)
# Now try to run the server
try:
# Import the FastMCP server module and FastMCP class
try:
from fastmcp import FastMCP
logger.info("Successfully imported FastMCP")
except ImportError:
logger.error("Could not import FastMCP - attempting to install...")
if install_dependency("fastmcp>=2.2.6"):
from fastmcp import FastMCP
logger.info("Successfully imported FastMCP after installation")
else:
logger.error("Failed to import FastMCP even after installation attempt")
print("Failed to import FastMCP even after installation attempt", file=sys.stderr)
sys.exit(1)
# Attempt to import module directly
sys.path.insert(0, str(current_dir))
try:
from src.graphistry_mcp_server.server import mcp
logger.info("Successfully imported mcp from src.graphistry_mcp_server.server")
module_path = "src.graphistry_mcp_server.server"
except ImportError:
try:
from graphistry_mcp_server.server import mcp
logger.info("Successfully imported mcp from graphistry_mcp_server.server")
module_path = "graphistry_mcp_server.server"
except ImportError as e:
logger.error(f"Could not import server module: {e}")
print(f"Could not import server module: {e}", file=sys.stderr)
print("Contents of src directory:", file=sys.stderr)
for item in os.listdir(src_path):
print(f" {item}", file=sys.stderr)
sys.exit(1)
# Run the main function
logger.info(f"Starting Graphistry MCP server from module: {module_path}")
print(f"Starting Graphistry MCP server from module: {module_path}", file=sys.stderr)
# Check if test-connection flag was passed
if len(sys.argv) > 1 and sys.argv[1] == "--test-connection":
# Check port availability as part of the test
print("\n===== PORT AVAILABILITY TEST =====")
default_port = 8080
port_status = "✅ Available" if is_port_available(default_port) else "❌ In use by another process"
print(f"Default port {default_port}: {port_status}")
if not is_port_available(default_port):
# Check if we can identify the process using the port
if sys.platform.startswith('linux') or sys.platform == 'darwin':
try:
result = subprocess.run(['lsof', '-i', f':{default_port}'], capture_output=True, text=True)
if result.stdout:
print(f"\nProcess using port {default_port}:")
print(result.stdout)
except Exception:
pass
# Find and suggest alternative ports
alt_port = find_available_port(default_port + 1)
if alt_port:
print(f"💡 Alternative port {alt_port} is available for use")
os.environ["GRAPHISTRY_SUGGESTED_PORT"] = str(alt_port)
else:
print(f"❌ No alternative ports available in range {default_port+1}-{default_port+20}")
print("\n===== GRAPHISTRY API TEST =====")
print("Testing connection to Graphistry API...")
try:
import graphistry
print("Successfully imported graphistry")
# Get credentials from environment variables
env_username = os.environ.get("GRAPHISTRY_USERNAME")
env_password = os.environ.get("GRAPHISTRY_PASSWORD")
print("Environment variables:")
print(f"GRAPHISTRY_USERNAME found: {'Yes' if env_username else 'No'}")
print(f"GRAPHISTRY_PASSWORD found: {'Yes' if env_password else 'No'}")
# Get credentials from .env file
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
dotenv_username = None
dotenv_password = None
if os.path.exists(dotenv_path):
from dotenv import load_dotenv, dotenv_values
env_values = dotenv_values(dotenv_path)
dotenv_username = env_values.get('GRAPHISTRY_USERNAME')
dotenv_password = env_values.get('GRAPHISTRY_PASSWORD')
print("Credentials from .env file:")
print(f"GRAPHISTRY_USERNAME found: {'Yes' if dotenv_username else 'No'}")
print(f"GRAPHISTRY_PASSWORD found: {'Yes' if dotenv_password else 'No'}")
# Compare credentials
if env_username and dotenv_username and env_username != dotenv_username:
print(f"⚠️ WARNING: Username in environment ({env_username}) differs from .env file ({dotenv_username})")
# Try with environment credentials first
if env_username and env_password:
print(f"\nTest 1: Using credentials from environment variables - Username: {env_username}")
try:
# Get connection parameters from environment variables with defaults
api_version = os.environ.get("GRAPHISTRY_API", "3")
protocol = os.environ.get("GRAPHISTRY_PROTOCOL", "https")
server = os.environ.get("GRAPHISTRY_HOST", "hub.graphistry.com")
client_protocol_hostname = os.environ.get("GRAPHISTRY_CLIENT_PROTOCOL_HOSTNAME", f"{protocol}://{server}")
# Try to convert api to int if possible
try:
api_version = int(api_version)
except ValueError:
api_version = 3
print(f"Connecting with: API={api_version}, protocol={protocol}, server={server}")
graphistry.register(
api=api_version,
protocol=protocol,
server=server,
client_protocol_hostname=client_protocol_hostname,
username=env_username,
password=env_password
)
print("✅ Successfully registered with Graphistry API using environment credentials")
except Exception as auth_error:
print(f"❌ Authentication error with environment credentials: {auth_error}")
else:
print("\nNo credentials found in environment variables to test.")
# Try with .env file credentials
if dotenv_username and dotenv_password:
print(f"\nTest 2: Using credentials from .env file - Username: {dotenv_username}")
try:
# Get connection parameters from environment variables with defaults
api_version = os.environ.get("GRAPHISTRY_API", "3")
protocol = os.environ.get("GRAPHISTRY_PROTOCOL", "https")
server = os.environ.get("GRAPHISTRY_HOST", "hub.graphistry.com")
client_protocol_hostname = os.environ.get("GRAPHISTRY_CLIENT_PROTOCOL_HOSTNAME", f"{protocol}://{server}")
# Try to convert api to int if possible
try:
api_version = int(api_version)
except ValueError:
api_version = 3
print(f"Connecting with: API={api_version}, protocol={protocol}, server={server}")
graphistry.register(
api=api_version,
protocol=protocol,
server=server,
client_protocol_hostname=client_protocol_hostname,
username=dotenv_username,
password=dotenv_password
)
print("✅ Successfully registered with Graphistry API using .env file credentials")
except Exception as auth_error:
print(f"❌ Authentication error with .env file credentials: {auth_error}")
else:
print("\nNo credentials found in .env file to test.")
except Exception as e:
print(f"❌ Error testing Graphistry API connection: {e}")
print(f"Error testing Graphistry API connection: {e}", file=sys.stderr)
sys.exit(1)
print(f"Server startup took {time.time() - START_TIME:.2f} seconds", file=sys.stderr)
print("Graphistry MCP server ready to process requests", file=sys.stderr)
# Run the server using the appropriate method
# We need to handle both run_stdio_async and run methods
if hasattr(mcp, 'run_stdio_async'):
logger.info("Using run_stdio_async transport method")
print("Using run_stdio_async transport method", file=sys.stderr)
asyncio.run(mcp.run_stdio_async())
elif hasattr(mcp, 'run'):
logger.info("Using run transport method")
print("Using run transport method", file=sys.stderr)
asyncio.run(mcp.run())
else:
logger.error("No valid transport method found on mcp object")
print("Error: No valid transport method found on mcp object", file=sys.stderr)
print(f"Available attributes on mcp: {dir(mcp)}", file=sys.stderr)
sys.exit(1)
except ImportError as e:
logger.error(f"Import error: {e}")
print(f"Import error: {e}", file=sys.stderr)
# Print Python path to help debug
logger.error(f"Python path: {sys.path}")
print(f"Python path: {sys.path}", file=sys.stderr)
# Print sys.prefix to check installation location
print(f"sys.prefix: {sys.prefix}", file=sys.stderr)
print(f"sys.base_prefix: {sys.base_prefix}", file=sys.stderr)
sys.exit(1)
except Exception as e:
logger.error(f"Error running server: {e}")
print(f"Error running server: {e}", file=sys.stderr)
import traceback
traceback.print_exc(file=sys.stderr)
sys.exit(1)