mishrabp commited on
Commit
f29cac7
·
verified ·
1 Parent(s): 29574e5

Upload folder using huggingface_hub

Browse files
pyproject.toml CHANGED
@@ -159,7 +159,7 @@ dependencies = [
159
  "google-auth-oauthlib>=0.4.6",
160
  "google-auth-httplib2>=0.1.0",
161
  "autoflake>=1.5.0",
162
-
163
  ]
164
 
165
  [dependency-groups]
 
159
  "google-auth-oauthlib>=0.4.6",
160
  "google-auth-httplib2>=0.1.0",
161
  "autoflake>=1.5.0",
162
+ "psycopg2-binary>=2.9.9",
163
  ]
164
 
165
  [dependency-groups]
src/core/mcp_telemetry.py CHANGED
@@ -10,8 +10,9 @@ from pathlib import Path
10
  # Configuration
11
  HUB_URL = os.environ.get("MCP_HUB_URL", "http://localhost:7860")
12
  IS_HUB = os.environ.get("MCP_IS_HUB", "false").lower() == "true"
 
13
 
14
- # Single SQLite DB for the Hub
15
  if os.path.exists("/app"):
16
  DB_FILE = Path("/tmp/mcp_logs.db")
17
  else:
@@ -19,6 +20,22 @@ else:
19
  DB_FILE = Path(__file__).parent.parent.parent / "mcp_logs.db"
20
 
21
  def _get_conn():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # Auto-init if missing (lazy creation)
23
  if IS_HUB and not os.path.exists(DB_FILE):
24
  _init_db()
@@ -27,6 +44,57 @@ def _get_conn():
27
  conn.row_factory = sqlite3.Row
28
  return conn
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def _init_db():
31
  """Initializes the SQLite database with required tables."""
32
  # Ensure parent dir exists
@@ -46,7 +114,34 @@ def _init_db():
46
  tool TEXT NOT NULL
47
  )
48
  """)
49
- conn.execute("CREATE INDEX IF NOT EXISTS idx_ts ON logs(timestamp)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  conn.close()
51
  except Exception as e:
52
  print(f"DB Init Failed: {e}")
@@ -56,45 +151,111 @@ def _init_db():
56
  def log_usage(server_name: str, tool_name: str):
57
  """Logs a usage event. Writes to DB if Hub, else POSTs to Hub API."""
58
  timestamp = datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # 1. If we are the Hub, write directly to DB
61
  if IS_HUB:
62
- try:
63
- with _get_conn() as conn:
64
- conn.execute("INSERT INTO logs (timestamp, server, tool) VALUES (?, ?, ?)",
65
- (timestamp, server_name, tool_name))
66
- except Exception as e:
67
- print(f"Local Log Failed: {e}")
68
-
69
- # 2. If we are an Agent, send to Hub API
70
  else:
71
- try:
72
- payload = {
73
- "server": server_name,
74
- "tool": tool_name,
75
- "timestamp": timestamp
76
- }
77
- # Fire and forget with short timeout
78
- requests.post(f"{HUB_URL}/api/telemetry", json=payload, timeout=2)
79
- except Exception as e:
80
- # excessive logging here would be spammy locally
81
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def get_metrics():
84
- """Aggregates metrics from SQLite."""
85
- if not DB_FILE.exists():
 
86
  return {}
87
 
88
  try:
89
- with _get_conn() as conn:
 
 
 
 
 
 
 
 
 
 
90
  rows = conn.execute("SELECT server, timestamp FROM logs").fetchall()
 
91
 
92
  now = datetime.now()
93
- metrics = {}
94
 
95
  for row in rows:
96
  server = row["server"]
97
- ts = datetime.fromisoformat(row["timestamp"])
 
 
 
98
 
99
  if server not in metrics:
100
  metrics[server] = {"hourly": 0, "weekly": 0, "monthly": 0}
@@ -104,6 +265,7 @@ def get_metrics():
104
  metrics[server]["hourly"] += 1
105
  if delta.days < 7:
106
  metrics[server]["weekly"] += 1
 
107
  metrics[server]["monthly"] += 1
108
 
109
  return metrics
@@ -113,7 +275,7 @@ def get_metrics():
113
 
114
  def get_usage_history(range_hours: int = 24, intervals: int = 12):
115
  """Returns time-series data for the chart."""
116
- if not DB_FILE.exists():
117
  return _generate_mock_history(range_hours, intervals)
118
 
119
  try:
@@ -121,11 +283,21 @@ def get_usage_history(range_hours: int = 24, intervals: int = 12):
121
  start_time = now - timedelta(hours=range_hours)
122
  bucket_size = (range_hours * 3600) / intervals
123
 
124
- with _get_conn() as conn:
 
 
 
 
 
 
 
 
 
125
  rows = conn.execute(
126
  "SELECT server, timestamp FROM logs WHERE timestamp >= ?",
127
  (start_time.isoformat(),)
128
  ).fetchall()
 
129
 
130
  if not rows:
131
  return _generate_mock_history(range_hours, intervals)
@@ -135,7 +307,10 @@ def get_usage_history(range_hours: int = 24, intervals: int = 12):
135
  datasets = {s: [0] * intervals for s in active_servers}
136
 
137
  for row in rows:
138
- ts = datetime.fromisoformat(row["timestamp"])
 
 
 
139
  delta = (ts - start_time).total_seconds()
140
  bucket_idx = int(delta // bucket_size)
141
  if 0 <= bucket_idx < intervals:
@@ -210,16 +385,28 @@ def get_system_metrics():
210
 
211
  def get_recent_logs(server_id: str, limit: int = 50):
212
  """Fetches the most recent logs for a specific server."""
213
- if not DB_FILE.exists():
214
  return []
215
 
216
  try:
217
- with _get_conn() as conn:
218
- # Simple match. For 'mcp-hub', we might want all, but usually filtered by server_id
 
 
 
 
 
 
 
 
 
 
 
219
  rows = conn.execute(
220
  "SELECT timestamp, tool FROM logs WHERE server = ? ORDER BY id DESC LIMIT ?",
221
  (server_id, limit)
222
  ).fetchall()
 
223
 
224
  return [dict(r) for r in rows]
225
  except Exception as e:
 
10
  # Configuration
11
  HUB_URL = os.environ.get("MCP_HUB_URL", "http://localhost:7860")
12
  IS_HUB = os.environ.get("MCP_IS_HUB", "false").lower() == "true"
13
+ PG_CONN_STR = os.environ.get("MCP_TRACES_DB")
14
 
15
+ # Single SQLite DB for the Hub (fallback)
16
  if os.path.exists("/app"):
17
  DB_FILE = Path("/tmp/mcp_logs.db")
18
  else:
 
20
  DB_FILE = Path(__file__).parent.parent.parent / "mcp_logs.db"
21
 
22
  def _get_conn():
23
+ # PostgreSQL Mode
24
+ if PG_CONN_STR and IS_HUB:
25
+ try:
26
+ import psycopg2
27
+ from psycopg2.extras import RealDictCursor
28
+ conn = psycopg2.connect(PG_CONN_STR)
29
+ # Init schema if needed (lazy check could be optimized)
30
+ _init_pg_db(conn)
31
+ return conn
32
+ except Exception as e:
33
+ print(f"Postgres Connection Failed: {e}")
34
+ # Fallback to SQLite not recommended if PG configured, but handling graceful failure might be needed.
35
+ # For now, we raise or assume SQLite fallback if PG fail? Let's error out to be safe.
36
+ raise e
37
+
38
+ # SQLite Mode (Default)
39
  # Auto-init if missing (lazy creation)
40
  if IS_HUB and not os.path.exists(DB_FILE):
41
  _init_db()
 
44
  conn.row_factory = sqlite3.Row
45
  return conn
46
 
47
+
48
+ def _init_pg_db(conn):
49
+ """Initializes the PostgreSQL database with required tables."""
50
+ try:
51
+ with conn.cursor() as cur:
52
+ # Logs
53
+ cur.execute("""
54
+ CREATE TABLE IF NOT EXISTS logs (
55
+ id SERIAL PRIMARY KEY,
56
+ timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
57
+ server VARCHAR(255) NOT NULL,
58
+ tool VARCHAR(255) NOT NULL
59
+ )
60
+ """)
61
+ cur.execute("CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs(timestamp)")
62
+
63
+ # Traces
64
+ cur.execute("""
65
+ CREATE TABLE IF NOT EXISTS traces (
66
+ id SERIAL PRIMARY KEY,
67
+ trace_id VARCHAR(64) NOT NULL,
68
+ span_id VARCHAR(64) NOT NULL,
69
+ parent_id VARCHAR(64),
70
+ name VARCHAR(255) NOT NULL,
71
+ status VARCHAR(50),
72
+ start_time TIMESTAMP,
73
+ end_time TIMESTAMP,
74
+ duration_ms FLOAT,
75
+ server VARCHAR(255)
76
+ )
77
+ """)
78
+ cur.execute("CREATE INDEX IF NOT EXISTS idx_traces_tid ON traces(trace_id)")
79
+
80
+ # Metrics
81
+ cur.execute("""
82
+ CREATE TABLE IF NOT EXISTS metrics (
83
+ id SERIAL PRIMARY KEY,
84
+ name VARCHAR(255) NOT NULL,
85
+ value FLOAT NOT NULL,
86
+ tags TEXT,
87
+ timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
88
+ server VARCHAR(255)
89
+ )
90
+ """)
91
+ cur.execute("CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(timestamp)")
92
+
93
+ conn.commit()
94
+ except Exception as e:
95
+ print(f"Postgres DB Init Failed: {e}")
96
+ conn.rollback()
97
+
98
  def _init_db():
99
  """Initializes the SQLite database with required tables."""
100
  # Ensure parent dir exists
 
114
  tool TEXT NOT NULL
115
  )
116
  """)
117
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs(timestamp)")
118
+
119
+ conn.execute("""
120
+ CREATE TABLE IF NOT EXISTS traces (
121
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
122
+ trace_id TEXT NOT NULL,
123
+ span_id TEXT NOT NULL,
124
+ parent_id TEXT,
125
+ name TEXT NOT NULL,
126
+ status TEXT,
127
+ start_time TEXT,
128
+ end_time TEXT,
129
+ duration_ms REAL,
130
+ server TEXT
131
+ )
132
+ """)
133
+
134
+ conn.execute("""
135
+ CREATE TABLE IF NOT EXISTS metrics (
136
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
137
+ name TEXT NOT NULL,
138
+ value REAL NOT NULL,
139
+ tags TEXT,
140
+ timestamp TEXT NOT NULL,
141
+ server TEXT
142
+ )
143
+ """)
144
+
145
  conn.close()
146
  except Exception as e:
147
  print(f"DB Init Failed: {e}")
 
151
  def log_usage(server_name: str, tool_name: str):
152
  """Logs a usage event. Writes to DB if Hub, else POSTs to Hub API."""
153
  timestamp = datetime.now().isoformat()
154
+ if IS_HUB:
155
+ _write_db("logs", {"timestamp": timestamp, "server": server_name, "tool": tool_name})
156
+ else:
157
+ _send_remote("log", {"timestamp": timestamp, "server": server_name, "tool": tool_name})
158
+
159
+ def log_trace(server_name: str, trace_id: str, span_id: str, name: str, duration_ms: float,
160
+ status: str = "ok", parent_id: str = None):
161
+ """Logs a trace span."""
162
+ now = datetime.now()
163
+ end_time = now.isoformat()
164
+ start_time = (now - timedelta(milliseconds=duration_ms)).isoformat()
165
+
166
+ data = {
167
+ "server": server_name,
168
+ "trace_id": trace_id,
169
+ "span_id": span_id,
170
+ "parent_id": parent_id,
171
+ "name": name,
172
+ "status": status,
173
+ "start_time": start_time,
174
+ "end_time": end_time,
175
+ "duration_ms": duration_ms
176
+ }
177
+
178
+ if IS_HUB:
179
+ _write_db("traces", data)
180
+ else:
181
+ _send_remote("trace", data)
182
+
183
+ def log_metric(server_name: str, name: str, value: float, tags: dict = None):
184
+ """Logs a metric point."""
185
+ timestamp = datetime.now().isoformat()
186
+ tags_str = json.dumps(tags) if tags else "{}"
187
+
188
+ data = {
189
+ "server": server_name,
190
+ "name": name,
191
+ "value": value,
192
+ "tags": tags_str,
193
+ "timestamp": timestamp
194
+ }
195
 
 
196
  if IS_HUB:
197
+ _write_db("metrics", data)
 
 
 
 
 
 
 
198
  else:
199
+ _send_remote("metric", data)
200
+
201
+ def _write_db(table: str, data: dict):
202
+ """Helper to write to DB (PG or SQLite)."""
203
+ try:
204
+ conn = _get_conn()
205
+ cols = list(data.keys())
206
+ placeholders = ["%s"] * len(cols) if PG_CONN_STR and IS_HUB else ["?"] * len(cols)
207
+ query = f"INSERT INTO {table} ({', '.join(cols)}) VALUES ({', '.join(placeholders)})"
208
+ values = list(data.values())
209
+
210
+ if PG_CONN_STR and IS_HUB:
211
+ with conn.cursor() as cur:
212
+ cur.execute(query, values)
213
+ conn.commit()
214
+ conn.close()
215
+ else:
216
+ with conn:
217
+ conn.execute(query, values)
218
+ conn.close()
219
+ except Exception as e:
220
+ print(f"Local Write Failed ({table}): {e}")
221
+
222
+ def _send_remote(type: str, data: dict):
223
+ """Helper to post to Hub."""
224
+ try:
225
+ # Fire and forget
226
+ requests.post(f"{HUB_URL}/api/telemetry/{type}", json=data, timeout=2)
227
+ except Exception:
228
+ pass
229
 
230
  def get_metrics():
231
+ """Aggregates metrics from DB."""
232
+ if not IS_HUB and not DB_FILE.exists():
233
+ # If not Hub and no local sqlite, nothing to show
234
  return {}
235
 
236
  try:
237
+ conn = _get_conn()
238
+ metrics = {}
239
+ rows = []
240
+
241
+ if PG_CONN_STR and IS_HUB:
242
+ from psycopg2.extras import RealDictCursor
243
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
244
+ cur.execute("SELECT server, timestamp FROM logs")
245
+ rows = cur.fetchall()
246
+ conn.close()
247
+ else:
248
  rows = conn.execute("SELECT server, timestamp FROM logs").fetchall()
249
+ conn.close()
250
 
251
  now = datetime.now()
 
252
 
253
  for row in rows:
254
  server = row["server"]
255
+ # Handle different timestamp formats (PG vs SQLite textual)
256
+ ts = row["timestamp"]
257
+ if isinstance(ts, str):
258
+ ts = datetime.fromisoformat(ts)
259
 
260
  if server not in metrics:
261
  metrics[server] = {"hourly": 0, "weekly": 0, "monthly": 0}
 
265
  metrics[server]["hourly"] += 1
266
  if delta.days < 7:
267
  metrics[server]["weekly"] += 1
268
+ if delta.days < 30: # Assuming a month is roughly 30 days for simplicity
269
  metrics[server]["monthly"] += 1
270
 
271
  return metrics
 
275
 
276
  def get_usage_history(range_hours: int = 24, intervals: int = 12):
277
  """Returns time-series data for the chart."""
278
+ if not IS_HUB and not DB_FILE.exists():
279
  return _generate_mock_history(range_hours, intervals)
280
 
281
  try:
 
283
  start_time = now - timedelta(hours=range_hours)
284
  bucket_size = (range_hours * 3600) / intervals
285
 
286
+ conn = _get_conn()
287
+ rows = []
288
+
289
+ if PG_CONN_STR and IS_HUB:
290
+ from psycopg2.extras import RealDictCursor
291
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
292
+ cur.execute("SELECT server, timestamp FROM logs WHERE timestamp >= %s", (start_time,))
293
+ rows = cur.fetchall()
294
+ conn.close()
295
+ else:
296
  rows = conn.execute(
297
  "SELECT server, timestamp FROM logs WHERE timestamp >= ?",
298
  (start_time.isoformat(),)
299
  ).fetchall()
300
+ conn.close()
301
 
302
  if not rows:
303
  return _generate_mock_history(range_hours, intervals)
 
307
  datasets = {s: [0] * intervals for s in active_servers}
308
 
309
  for row in rows:
310
+ ts = row["timestamp"]
311
+ if isinstance(ts, str):
312
+ ts = datetime.fromisoformat(ts)
313
+
314
  delta = (ts - start_time).total_seconds()
315
  bucket_idx = int(delta // bucket_size)
316
  if 0 <= bucket_idx < intervals:
 
385
 
386
  def get_recent_logs(server_id: str, limit: int = 50):
387
  """Fetches the most recent logs for a specific server."""
388
+ if not IS_HUB and not DB_FILE.exists():
389
  return []
390
 
391
  try:
392
+ conn = _get_conn()
393
+ rows = []
394
+
395
+ if PG_CONN_STR and IS_HUB:
396
+ from psycopg2.extras import RealDictCursor
397
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
398
+ cur.execute(
399
+ "SELECT timestamp, tool FROM logs WHERE server = %s ORDER BY id DESC LIMIT %s",
400
+ (server_id, limit)
401
+ )
402
+ rows = cur.fetchall()
403
+ conn.close()
404
+ else:
405
  rows = conn.execute(
406
  "SELECT timestamp, tool FROM logs WHERE server = ? ORDER BY id DESC LIMIT ?",
407
  (server_id, limit)
408
  ).fetchall()
409
+ conn.close()
410
 
411
  return [dict(r) for r in rows]
412
  except Exception as e:
src/mcp-azure-sre/server.py CHANGED
@@ -14,7 +14,9 @@ if src_dir not in sys.path:
14
  sys.path.append(src_dir)
15
 
16
  from mcp.server.fastmcp import FastMCP
17
- from core.mcp_telemetry import log_usage
 
 
18
 
19
  # Azure Imports
20
  try:
@@ -45,7 +47,11 @@ def list_resources(subscription_id: str, resource_group: Optional[str] = None) -
45
  """
46
  List Azure resources in a subscription or resource group.
47
  """
 
 
 
48
  log_usage("mcp-azure-sre", "list_resources")
 
49
  try:
50
  cred = get_credential()
51
  client = ResourceManagementClient(cred, subscription_id)
@@ -55,8 +61,15 @@ def list_resources(subscription_id: str, resource_group: Optional[str] = None) -
55
  else:
56
  resources = client.resources.list()
57
 
58
- return [{"name": r.name, "type": r.type, "location": r.location, "id": r.id} for r in resources]
 
 
 
 
 
59
  except Exception as e:
 
 
60
  return [{"error": str(e)}]
61
 
62
  @mcp.tool()
 
14
  sys.path.append(src_dir)
15
 
16
  from mcp.server.fastmcp import FastMCP
17
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
18
+ import uuid
19
+ import time
20
 
21
  # Azure Imports
22
  try:
 
47
  """
48
  List Azure resources in a subscription or resource group.
49
  """
50
+ start_time = time.time()
51
+ trace_id = str(uuid.uuid4())
52
+ span_id = str(uuid.uuid4())
53
  log_usage("mcp-azure-sre", "list_resources")
54
+
55
  try:
56
  cred = get_credential()
57
  client = ResourceManagementClient(cred, subscription_id)
 
61
  else:
62
  resources = client.resources.list()
63
 
64
+ results = [{"name": r.name, "type": r.type, "location": r.location, "id": r.id} for r in resources]
65
+
66
+ duration = (time.time() - start_time) * 1000
67
+ log_trace("mcp-azure-sre", trace_id, span_id, "list_resources", duration, "ok")
68
+ log_metric("mcp-azure-sre", "resources_scanned", len(results), {"sub": subscription_id})
69
+ return results
70
  except Exception as e:
71
+ duration = (time.time() - start_time) * 1000
72
+ log_trace("mcp-azure-sre", trace_id, span_id, "list_resources", duration, "error")
73
  return [{"error": str(e)}]
74
 
75
  @mcp.tool()
src/mcp-github/server.py CHANGED
@@ -6,7 +6,10 @@ import sys
6
  import os
7
  from mcp.server.fastmcp import FastMCP
8
  from typing import List, Dict, Any, Optional
9
- from core.mcp_telemetry import log_usage
 
 
 
10
 
11
  # Add src to pythonpath
12
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -39,7 +42,11 @@ def list_repositories() -> List[Dict[str, Any]]:
39
  """
40
  List all repositories for the authenticated user/owner.
41
  """
 
 
 
42
  log_usage("mcp-github", "list_repositories")
 
43
  try:
44
  g = get_client()
45
  # Get repos for the owner/authenticated user
@@ -56,8 +63,15 @@ def list_repositories() -> List[Dict[str, Any]]:
56
  "updated_at": str(repo.updated_at),
57
  "language": repo.language
58
  })
 
 
 
 
59
  return results
 
60
  except Exception as e:
 
 
61
  return [{"error": str(e)}]
62
 
63
  @mcp.tool()
 
6
  import os
7
  from mcp.server.fastmcp import FastMCP
8
  from typing import List, Dict, Any, Optional
9
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
10
+ import uuid
11
+ import time
12
+ import datetime
13
 
14
  # Add src to pythonpath
15
  current_dir = os.path.dirname(os.path.abspath(__file__))
 
42
  """
43
  List all repositories for the authenticated user/owner.
44
  """
45
+ start_time = time.time()
46
+ trace_id = str(uuid.uuid4())
47
+ span_id = str(uuid.uuid4())
48
  log_usage("mcp-github", "list_repositories")
49
+
50
  try:
51
  g = get_client()
52
  # Get repos for the owner/authenticated user
 
63
  "updated_at": str(repo.updated_at),
64
  "language": repo.language
65
  })
66
+
67
+ duration = (time.time() - start_time) * 1000
68
+ log_trace("mcp-github", trace_id, span_id, "list_repositories", duration, "ok")
69
+ log_metric("mcp-github", "repos_fetched", len(results), {"status": "ok"})
70
  return results
71
+
72
  except Exception as e:
73
+ duration = (time.time() - start_time) * 1000
74
+ log_trace("mcp-github", trace_id, span_id, "list_repositories", duration, "error")
75
  return [{"error": str(e)}]
76
 
77
  @mcp.tool()
src/mcp-hub/api.py CHANGED
@@ -13,52 +13,82 @@ from datetime import datetime, timedelta
13
  # Add parent dir to path for imports
14
  sys.path.append(str(Path(__file__).parent.parent))
15
 
 
 
 
 
16
  # Telemetry Import
17
  try:
18
- from core.mcp_telemetry import get_metrics, get_usage_history, get_system_metrics, log_usage, _get_conn, get_recent_logs
19
  except ImportError:
20
  # If standard import fails, try absolute path fallback
21
  sys.path.append(str(Path(__file__).parent.parent.parent))
22
- from src.core.mcp_telemetry import get_metrics, get_usage_history, get_system_metrics, log_usage, _get_conn, get_recent_logs
23
-
24
- # Optional: HF Hub for status checks
25
- try:
26
- from huggingface_hub import HfApi
27
- hf_api = HfApi()
28
- except ImportError:
29
- hf_api = None
30
 
31
  from pydantic import BaseModel
32
 
33
- class TelemetryEvent(BaseModel):
34
  server: str
35
  tool: str
36
  timestamp: Optional[str] = None
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  app = FastAPI()
39
 
40
  @app.post("/api/telemetry")
41
- async def ingest_telemetry(event: TelemetryEvent):
42
- """Ingests telemetry from remote MCP agents."""
43
- # We use the internal log_usage which handles DB writing
44
- # We must ensure we are in Hub mode for this to work, which we are since this is api.py
45
- # But wait, log_usage checks IS_HUB env var.
46
- # To be safe, we will write directly or ensure env var is set in Dockerfile.
47
-
48
- # Actually, simpler: we can just call the DB insert directly here to retrieve avoiding circular logic
49
- # or just use log_usage if configured correctly.
50
-
51
- # Let's import the specific DB function or use sqlite directly
52
- from core.mcp_telemetry import _get_conn
53
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  try:
55
  ts = event.timestamp or datetime.now().isoformat()
56
- with _get_conn() as conn:
57
- conn.execute("INSERT INTO logs (timestamp, server, tool) VALUES (?, ?, ?)",
58
- (ts, event.server, event.tool))
59
  return {"status": "ok"}
60
  except Exception as e:
61
- print(f"Telemetry Ingest Failed: {e}")
62
  return {"status": "error", "message": str(e)}
63
 
64
  app.add_middleware(
@@ -166,11 +196,32 @@ print(result.final_text)
166
 
167
  @app.on_event("startup")
168
  async def startup_event():
 
169
  token = os.environ.get("HF_TOKEN")
 
 
 
 
 
 
170
  if token:
171
- print(f"HF_TOKEN found: {token[:4]}...{token[-4:]}")
172
  else:
173
  print("WARNING: HF_TOKEN not set! Live status checks will fail.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  @app.get("/api/servers/{server_id}/logs")
176
  async def get_server_logs(server_id: str):
 
13
  # Add parent dir to path for imports
14
  sys.path.append(str(Path(__file__).parent.parent))
15
 
16
+ # Core Telemetry Imports for Metrics/History
17
+ from core.mcp_telemetry import get_metrics, get_usage_history, get_system_metrics, get_recent_logs
18
+
19
+
20
  # Telemetry Import
21
  try:
22
+ from core.mcp_telemetry import _write_db
23
  except ImportError:
24
  # If standard import fails, try absolute path fallback
25
  sys.path.append(str(Path(__file__).parent.parent.parent))
26
+ from src.core.mcp_telemetry import _write_db
 
 
 
 
 
 
 
27
 
28
  from pydantic import BaseModel
29
 
30
+ class LogEvent(BaseModel):
31
  server: str
32
  tool: str
33
  timestamp: Optional[str] = None
34
 
35
+ class TraceEvent(BaseModel):
36
+ server: str
37
+ trace_id: str
38
+ span_id: str
39
+ name: str
40
+ duration_ms: float
41
+ status: Optional[str] = "ok"
42
+ parent_id: Optional[str] = None
43
+ start_time: Optional[str] = None
44
+ end_time: Optional[str] = None
45
+
46
+ class MetricEvent(BaseModel):
47
+ server: str
48
+ name: str
49
+ value: float
50
+ tags: Optional[str] = "{}"
51
+ timestamp: Optional[str] = None
52
+
53
  app = FastAPI()
54
 
55
  @app.post("/api/telemetry")
56
+ @app.post("/api/telemetry/log")
57
+ async def ingest_log(event: LogEvent):
58
+ """Ingests usage logs."""
59
+ try:
60
+ ts = event.timestamp or datetime.now().isoformat()
61
+ _write_db("logs", {
62
+ "timestamp": ts,
63
+ "server": event.server,
64
+ "tool": event.tool
65
+ })
66
+ return {"status": "ok"}
67
+ except Exception as e:
68
+ print(f"Log Ingest Failed: {e}")
69
+ return {"status": "error", "message": str(e)}
70
+
71
+ @app.post("/api/telemetry/trace")
72
+ async def ingest_trace(event: TraceEvent):
73
+ """Ingests distributed traces."""
74
+ try:
75
+ _write_db("traces", event.dict())
76
+ return {"status": "ok"}
77
+ except Exception as e:
78
+ print(f"Trace Ingest Failed: {e}")
79
+ return {"status": "error", "message": str(e)}
80
+
81
+ @app.post("/api/telemetry/metric")
82
+ async def ingest_metric(event: MetricEvent):
83
+ """Ingests quantitative metrics."""
84
  try:
85
  ts = event.timestamp or datetime.now().isoformat()
86
+ data = event.dict()
87
+ data["timestamp"] = ts
88
+ _write_db("metrics", data)
89
  return {"status": "ok"}
90
  except Exception as e:
91
+ print(f"Metric Ingest Failed: {e}")
92
  return {"status": "error", "message": str(e)}
93
 
94
  app.add_middleware(
 
196
 
197
  @app.on_event("startup")
198
  async def startup_event():
199
+ # 1. Environment Check
200
  token = os.environ.get("HF_TOKEN")
201
+ is_hub = os.environ.get("MCP_IS_HUB", "false")
202
+ pg_db = os.environ.get("MCP_TRACES_DB")
203
+
204
+ print("--- MCP HUB BOOT SEQUENCE ---")
205
+ print(f"ENV MCP_IS_HUB: {is_hub}")
206
+
207
  if token:
208
+ print(f"ENV HF_TOKEN: Configured ({len(token)} chars)")
209
  else:
210
  print("WARNING: HF_TOKEN not set! Live status checks will fail.")
211
+
212
+ # 2. Database Check
213
+ if pg_db:
214
+ print(f"ENV MCP_TRACES_DB: Configured (Scheme: {pg_db.split(':')[0]})")
215
+ try:
216
+ import psycopg2
217
+ print("DEPENDENCY: psycopg2-binary installed.")
218
+ # We don't connect here to avoid blocking start, but telemetry module will try.
219
+ except ImportError:
220
+ print("CRITICAL ERROR: psycopg2-binary NOT installed. Postgres will fail!")
221
+ else:
222
+ print("ENV MCP_TRACES_DB: Not set. Using SQLite fallback.")
223
+
224
+ print("--- SEQUENCE COMPLETE ---")
225
 
226
  @app.get("/api/servers/{server_id}/logs")
227
  async def get_server_logs(server_id: str):
src/mcp-hub/src/App.vue CHANGED
@@ -161,7 +161,7 @@ const system = ref({ uptime: '99.9%', throughput: '0/hr', latency: '0ms' })
161
  const usageData = ref({ labels: [], datasets: [] })
162
  const selectedServer = ref(null)
163
  const currentLogs = ref('Initializing terminal...')
164
- const selectedRange = ref('24h')
165
  const ranges = ['1h', '24h', '7d', '30d']
166
  const hoverInfo = ref(null)
167
  let logTimer = null
@@ -327,7 +327,7 @@ onMounted(() => {
327
  setInterval(() => {
328
  fetchData()
329
  fetchUsage()
330
- }, 15000)
331
  })
332
  </script>
333
 
 
161
  const usageData = ref({ labels: [], datasets: [] })
162
  const selectedServer = ref(null)
163
  const currentLogs = ref('Initializing terminal...')
164
+ const selectedRange = ref('1h')
165
  const ranges = ['1h', '24h', '7d', '30d']
166
  const hoverInfo = ref(null)
167
  let logTimer = null
 
327
  setInterval(() => {
328
  fetchData()
329
  fetchUsage()
330
+ }, 300000)
331
  })
332
  </script>
333
 
src/mcp-rag-secure/server.py CHANGED
@@ -10,7 +10,8 @@ from chromadb.config import Settings
10
  from chromadb.utils import embedding_functions
11
  from mcp.server.fastmcp import FastMCP
12
  from typing import List, Dict, Any, Optional
13
- from core.mcp_telemetry import log_usage
 
14
 
15
  # Initialize FastMCP Server
16
  mcp = FastMCP("Secure RAG", host="0.0.0.0")
@@ -69,24 +70,35 @@ def query_knowledge_base(tenant_id: str, query: str, k: int = 3) -> List[Dict[st
69
  """
70
  Query the knowledge base. Results are strictly filtered by tenant_id.
71
  """
 
 
 
72
  log_usage("mcp-rag-secure", "query_knowledge_base")
73
- results = collection.query(
74
- query_texts=[query],
75
- n_results=k,
76
- where={"tenant_id": tenant_id} # Critical security filter
77
- )
78
 
79
- formatted_results = []
80
- if results["documents"]:
81
- for i, doc in enumerate(results["documents"][0]):
82
- meta = results["metadatas"][0][i]
83
- formatted_results.append({
84
- "content": doc,
85
- "metadata": meta,
86
- "score": results["distances"][0][i] if results["distances"] else None
87
- })
88
-
89
- return formatted_results
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  @mcp.tool()
92
  def delete_tenant_data(tenant_id: str) -> str:
 
10
  from chromadb.utils import embedding_functions
11
  from mcp.server.fastmcp import FastMCP
12
  from typing import List, Dict, Any, Optional
13
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
14
+ import time
15
 
16
  # Initialize FastMCP Server
17
  mcp = FastMCP("Secure RAG", host="0.0.0.0")
 
70
  """
71
  Query the knowledge base. Results are strictly filtered by tenant_id.
72
  """
73
+ start_time = time.time()
74
+ trace_id = str(uuid.uuid4())
75
+ span_id = str(uuid.uuid4())
76
  log_usage("mcp-rag-secure", "query_knowledge_base")
 
 
 
 
 
77
 
78
+ try:
79
+ results = collection.query(
80
+ query_texts=[query],
81
+ n_results=k,
82
+ where={"tenant_id": tenant_id} # Critical security filter
83
+ )
84
+
85
+ formatted_results = []
86
+ if results["documents"]:
87
+ for i, doc in enumerate(results["documents"][0]):
88
+ meta = results["metadatas"][0][i]
89
+ formatted_results.append({
90
+ "content": doc,
91
+ "metadata": meta,
92
+ "score": results["distances"][0][i] if results["distances"] else None
93
+ })
94
+
95
+ duration = (time.time() - start_time) * 1000
96
+ log_trace("mcp-rag-secure", trace_id, span_id, "query_knowledge_base", duration, "ok")
97
+ return formatted_results
98
+ except Exception as e:
99
+ duration = (time.time() - start_time) * 1000
100
+ log_trace("mcp-rag-secure", trace_id, span_id, "query_knowledge_base", duration, "error")
101
+ raise e
102
 
103
  @mcp.tool()
104
  def delete_tenant_data(tenant_id: str) -> str:
src/mcp-seo/server.py CHANGED
@@ -9,7 +9,9 @@ from bs4 import BeautifulSoup
9
  from urllib.parse import urljoin, urlparse
10
  from mcp.server.fastmcp import FastMCP
11
  from typing import List, Dict, Any, Set
12
- from core.mcp_telemetry import log_usage
 
 
13
 
14
  # Initialize FastMCP Server
15
  mcp = FastMCP("SEO & ADA Audit", host="0.0.0.0")
@@ -20,7 +22,11 @@ def analyze_seo(url: str) -> Dict[str, Any]:
20
  Perform a basic SEO audit of a webpage.
21
  Checks title, meta description, H1 tags, image alt attributes, and internal/external links.
22
  """
 
 
 
23
  log_usage("mcp-seo", "analyze_seo")
 
24
  try:
25
  response = requests.get(url, timeout=10)
26
  soup = BeautifulSoup(response.content, 'html.parser')
@@ -57,9 +63,15 @@ def analyze_seo(url: str) -> Dict[str, Any]:
57
  else:
58
  result["external_links"] += 1
59
 
 
 
 
 
60
  return result
61
  except Exception as e:
62
- return {"error": str(e)}
 
 
63
 
64
  @mcp.tool()
65
  def analyze_ada(url: str) -> Dict[str, Any]:
 
9
  from urllib.parse import urljoin, urlparse
10
  from mcp.server.fastmcp import FastMCP
11
  from typing import List, Dict, Any, Set
12
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
13
+ import uuid
14
+ import time
15
 
16
  # Initialize FastMCP Server
17
  mcp = FastMCP("SEO & ADA Audit", host="0.0.0.0")
 
22
  Perform a basic SEO audit of a webpage.
23
  Checks title, meta description, H1 tags, image alt attributes, and internal/external links.
24
  """
25
+ start_time = time.time()
26
+ trace_id = str(uuid.uuid4())
27
+ span_id = str(uuid.uuid4())
28
  log_usage("mcp-seo", "analyze_seo")
29
+
30
  try:
31
  response = requests.get(url, timeout=10)
32
  soup = BeautifulSoup(response.content, 'html.parser')
 
63
  else:
64
  result["external_links"] += 1
65
 
66
+ duration = (time.time() - start_time) * 1000
67
+ log_trace("mcp-seo", trace_id, span_id, "analyze_seo", duration, "ok")
68
+ log_metric("mcp-seo", "seo_links_found", result["internal_links"] + result["external_links"], {"url": url})
69
+
70
  return result
71
  except Exception as e:
72
+ duration = (time.time() - start_time) * 1000
73
+ log_trace("mcp-seo", trace_id, span_id, "analyze_seo", duration, "error")
74
+ return [{"error": str(e)}]
75
 
76
  @mcp.tool()
77
  def analyze_ada(url: str) -> Dict[str, Any]:
src/mcp-trader/server.py CHANGED
@@ -13,7 +13,9 @@ if src_dir not in sys.path:
13
 
14
  from mcp.server.fastmcp import FastMCP
15
  from typing import List, Dict, Any
16
- from core.mcp_telemetry import log_usage
 
 
17
 
18
  # Local imports (assuming src/mcp-trader is a package or run from src)
19
  try:
@@ -45,8 +47,25 @@ mcp = FastMCP("MCP Trader", host="0.0.0.0")
45
  @mcp.tool()
46
  def get_stock_price(symbol: str) -> float:
47
  """Get the current price for a stock symbol."""
 
 
 
 
48
  log_usage("mcp-trader", "get_stock_price")
49
- return get_current_price(symbol)
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  @mcp.tool()
52
  def get_stock_fundamentals(symbol: str) -> Dict[str, Any]:
 
13
 
14
  from mcp.server.fastmcp import FastMCP
15
  from typing import List, Dict, Any
16
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
17
+ import uuid
18
+ import time
19
 
20
  # Local imports (assuming src/mcp-trader is a package or run from src)
21
  try:
 
47
  @mcp.tool()
48
  def get_stock_price(symbol: str) -> float:
49
  """Get the current price for a stock symbol."""
50
+ start_time = time.time()
51
+ trace_id = str(uuid.uuid4())
52
+ span_id = str(uuid.uuid4())
53
+
54
  log_usage("mcp-trader", "get_stock_price")
55
+
56
+ try:
57
+ price = get_current_price(symbol)
58
+ duration = (time.time() - start_time) * 1000
59
+
60
+ # Metric
61
+ log_metric("mcp-trader", "stock_price", price, {"symbol": symbol})
62
+ log_trace("mcp-trader", trace_id, span_id, "get_stock_price", duration, "ok")
63
+
64
+ return price
65
+ except Exception as e:
66
+ duration = (time.time() - start_time) * 1000
67
+ log_trace("mcp-trader", trace_id, span_id, "get_stock_price", duration, "error")
68
+ raise e
69
 
70
  @mcp.tool()
71
  def get_stock_fundamentals(symbol: str) -> Dict[str, Any]:
src/mcp-trading-research/server.py CHANGED
@@ -8,7 +8,9 @@ import yfinance as yf
8
  from textblob import TextBlob
9
  from mcp.server.fastmcp import FastMCP
10
  from typing import List, Dict, Any
11
- from core.mcp_telemetry import log_usage
 
 
12
 
13
  # Initialize FastMCP Server
14
  mcp = FastMCP("Trading Research", host="0.0.0.0")
@@ -18,7 +20,11 @@ def get_news_sentiment(symbol: str) -> List[Dict[str, Any]]:
18
  """
19
  Get recent news and analyze sentiment for a stock symbol.
20
  """
 
 
 
21
  log_usage("mcp-trading-research", "get_news_sentiment")
 
22
  try:
23
  ticker = yf.Ticker(symbol)
24
  news = ticker.news
@@ -46,8 +52,13 @@ def get_news_sentiment(symbol: str) -> List[Dict[str, Any]]:
46
  "sentiment_score": round(sentiment, 2),
47
  "sentiment_label": sentiment_label
48
  })
 
 
 
49
  return results
50
  except Exception as e:
 
 
51
  return [{"error": str(e)}]
52
 
53
  @mcp.tool()
 
8
  from textblob import TextBlob
9
  from mcp.server.fastmcp import FastMCP
10
  from typing import List, Dict, Any
11
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
12
+ import uuid
13
+ import time
14
 
15
  # Initialize FastMCP Server
16
  mcp = FastMCP("Trading Research", host="0.0.0.0")
 
20
  """
21
  Get recent news and analyze sentiment for a stock symbol.
22
  """
23
+ start_time = time.time()
24
+ trace_id = str(uuid.uuid4())
25
+ span_id = str(uuid.uuid4())
26
  log_usage("mcp-trading-research", "get_news_sentiment")
27
+
28
  try:
29
  ticker = yf.Ticker(symbol)
30
  news = ticker.news
 
52
  "sentiment_score": round(sentiment, 2),
53
  "sentiment_label": sentiment_label
54
  })
55
+
56
+ duration = (time.time() - start_time) * 1000
57
+ log_trace("mcp-trading-research", trace_id, span_id, "get_news_sentiment", duration, "ok")
58
  return results
59
  except Exception as e:
60
+ duration = (time.time() - start_time) * 1000
61
+ log_trace("mcp-trading-research", trace_id, span_id, "get_news_sentiment", duration, "error")
62
  return [{"error": str(e)}]
63
 
64
  @mcp.tool()
src/mcp-weather/server.py CHANGED
@@ -11,7 +11,10 @@ if src_dir not in sys.path:
11
  sys.path.append(src_dir)
12
 
13
  from mcp.server.fastmcp import FastMCP
14
- from core.mcp_telemetry import log_usage
 
 
 
15
 
16
  # Initialize FastMCP Server
17
  mcp = FastMCP("Weather MCP", host="0.0.0.0")
@@ -30,25 +33,47 @@ def get_current_weather(location: str, units: str = "metric") -> Dict[str, Any]:
30
  Get current weather for a specific city or location.
31
  Units can be 'metric' (Celsius) or 'imperial' (Fahrenheit).
32
  """
 
 
 
 
33
  log_usage("mcp-weather", "get_current_weather")
34
  if not OPENWEATHER_API_KEY:
 
35
  return {"error": "OPENWEATHER_API_KEY not set"}
36
 
37
  url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={OPENWEATHER_API_KEY}&units={units}"
38
- response = requests.get(url)
39
- if response.status_code != 200:
40
- return {"error": f"Failed to fetch weather: {response.text}"}
41
-
42
- data = response.json()
43
- return {
44
- "location": data.get("name"),
45
- "condition": data["weather"][0]["description"],
46
- "temperature": format_temp(data["main"]["temp"], units),
47
- "feels_like": format_temp(data["main"]["feels_like"], units),
48
- "humidity": f"{data['main']['humidity']}%",
49
- "wind_speed": f"{data['wind']['speed']} m/s",
50
- "timestamp": data.get("dt")
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  @mcp.tool()
54
  def get_forecast(location: str, units: str = "metric") -> List[Dict[str, Any]]:
 
11
  sys.path.append(src_dir)
12
 
13
  from mcp.server.fastmcp import FastMCP
14
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
15
+ import uuid
16
+ import time
17
+ from datetime import datetime
18
 
19
  # Initialize FastMCP Server
20
  mcp = FastMCP("Weather MCP", host="0.0.0.0")
 
33
  Get current weather for a specific city or location.
34
  Units can be 'metric' (Celsius) or 'imperial' (Fahrenheit).
35
  """
36
+ start_time = time.time()
37
+ trace_id = str(uuid.uuid4())
38
+ span_id = str(uuid.uuid4())
39
+
40
  log_usage("mcp-weather", "get_current_weather")
41
  if not OPENWEATHER_API_KEY:
42
+ log_trace("mcp-weather", trace_id, span_id, "get_current_weather", 0, "error")
43
  return {"error": "OPENWEATHER_API_KEY not set"}
44
 
45
  url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={OPENWEATHER_API_KEY}&units={units}"
46
+ try:
47
+ response = requests.get(url, timeout=10)
48
+ duration = (time.time() - start_time) * 1000
49
+
50
+ if response.status_code != 200:
51
+ log_trace("mcp-weather", trace_id, span_id, "get_current_weather", duration, "error")
52
+ return {"error": f"Failed to fetch weather: {response.text}"}
53
+
54
+ data = response.json()
55
+ temp = data["main"]["temp"]
56
+
57
+ # Log Metrics
58
+ log_metric("mcp-weather", "weather_temperature", temp, {"location": location, "unit": units})
59
+ log_metric("mcp-weather", "api_latency", duration, {"endpoint": "weather"})
60
+
61
+ # Log Trace
62
+ log_trace("mcp-weather", trace_id, span_id, "get_current_weather", duration, "ok")
63
+
64
+ return {
65
+ "location": data.get("name"),
66
+ "condition": data["weather"][0]["description"],
67
+ "temperature": format_temp(temp, units),
68
+ "feels_like": format_temp(data["main"]["feels_like"], units),
69
+ "humidity": f"{data['main']['humidity']}%",
70
+ "wind_speed": f"{data['wind']['speed']} m/s",
71
+ "timestamp": data.get("dt")
72
+ }
73
+ except Exception as e:
74
+ duration = (time.time() - start_time) * 1000
75
+ log_trace("mcp-weather", trace_id, span_id, "get_current_weather", duration, "error")
76
+ return {"error": str(e)}
77
 
78
  @mcp.tool()
79
  def get_forecast(location: str, units: str = "metric") -> List[Dict[str, Any]]:
src/mcp-web/server.py CHANGED
@@ -13,7 +13,9 @@ if src_dir not in sys.path:
13
 
14
  from mcp.server.fastmcp import FastMCP
15
  from typing import List, Dict, Any, Union
16
- from core.mcp_telemetry import log_usage
 
 
17
 
18
  # Local imports
19
  try:
@@ -48,8 +50,21 @@ def search(query: str, max_results: int = 5) -> List[Dict[str, Any]]:
48
  Search the web for the given query using DuckDuckGo.
49
  Returns a list of results with title, url, snippet.
50
  """
 
 
 
51
  log_usage("mcp-web", "search")
52
- return search_web(query, max_results)
 
 
 
 
 
 
 
 
 
 
53
 
54
  @mcp.tool()
55
  def extract(url: str) -> str:
 
13
 
14
  from mcp.server.fastmcp import FastMCP
15
  from typing import List, Dict, Any, Union
16
+ from core.mcp_telemetry import log_usage, log_trace, log_metric
17
+ import uuid
18
+ import time
19
 
20
  # Local imports
21
  try:
 
50
  Search the web for the given query using DuckDuckGo.
51
  Returns a list of results with title, url, snippet.
52
  """
53
+ start_time = time.time()
54
+ trace_id = str(uuid.uuid4())
55
+ span_id = str(uuid.uuid4())
56
  log_usage("mcp-web", "search")
57
+
58
+ try:
59
+ results = search_web(query, max_results)
60
+ duration = (time.time() - start_time) * 1000
61
+ log_trace("mcp-web", trace_id, span_id, "search", duration, "ok")
62
+ log_metric("mcp-web", "search_results_count", len(results), {"query": query})
63
+ return results
64
+ except Exception as e:
65
+ duration = (time.time() - start_time) * 1000
66
+ log_trace("mcp-web", trace_id, span_id, "search", duration, "error")
67
+ raise e
68
 
69
  @mcp.tool()
70
  def extract(url: str) -> str: