yijun-lee commited on
Commit
6279b2d
·
verified ·
1 Parent(s): 17902fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +308 -79
app.py CHANGED
@@ -1,12 +1,12 @@
1
  import gradio as gr
2
  import requests
3
- from typing import List, Dict, Optional
4
  from huggingface_hub import HfApi
5
  import os
6
  from dotenv import load_dotenv
7
- import csv
8
  from pinecone import Pinecone
9
  from openai import OpenAI
 
10
 
11
  # Load environment variables
12
  load_dotenv()
@@ -27,8 +27,6 @@ def keyword_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
27
  Dictionary containing search results with MCP information
28
  """
29
  try:
30
- print(f"Debug - Search query: '{query}'") # Debug log
31
-
32
  # Use list_spaces API with mcp-server filter and sort by likes
33
  spaces = list(api.list_spaces(
34
  search=query,
@@ -40,15 +38,35 @@ def keyword_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
40
  results = []
41
  for space in spaces[:limit]: # Process up to limit matches
42
  try:
 
 
 
 
 
 
 
 
43
  space_info = {
44
  "id": space.id,
45
  "likes": space.likes,
46
  "trending_score": space.trending_score,
47
- "source": "huggingface"
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  results.append(space_info)
50
  except Exception as e:
51
- print(f"Error processing space {space.id}: {str(e)}")
52
  continue
53
 
54
  return {
@@ -56,20 +74,20 @@ def keyword_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
56
  "total": len(results)
57
  }
58
  except Exception as e:
59
- print(f"Debug - Critical error in keyword_search_hf_spaces: {str(e)}")
60
  return {
61
  "error": str(e),
62
  "results": [],
63
  "total": 0
64
  }
65
 
66
- def keyword_search_smithery(query: str = "", limit: int = 3) -> Dict:
67
  """
68
  Search for MCPs in Smithery Registry.
69
 
70
  Args:
71
  query: Search query string
72
  limit: Maximum number of results to return (default: 3)
 
73
 
74
  Returns:
75
  Dictionary containing search results with MCP information
@@ -120,13 +138,72 @@ def keyword_search_smithery(query: str = "", limit: int = 3) -> Dict:
120
  servers = sorted(data.get('servers', []), key=lambda x: x.get('useCount', 0), reverse=True)[:limit]
121
 
122
  for server in servers:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  server_info = {
124
- "id": server.get('qualifiedName'),
125
  "name": server.get('displayName'),
126
  "description": server.get('description'),
127
  "likes": server.get('useCount', 0),
128
- "source": "smithery"
 
129
  }
 
130
  results.append(server_info)
131
 
132
  return {
@@ -141,7 +218,7 @@ def keyword_search_smithery(query: str = "", limit: int = 3) -> Dict:
141
  "total": 0
142
  }
143
 
144
- def keyword_search(query: str, sources: List[str], limit: int = 3) -> Dict:
145
  """
146
  Search for MCPs using keyword matching.
147
 
@@ -149,6 +226,7 @@ def keyword_search(query: str, sources: List[str], limit: int = 3) -> Dict:
149
  query: Keyword search query
150
  sources: List of sources to search from ('huggingface', 'smithery')
151
  limit: Maximum number of results to return (default: 3)
 
152
 
153
  Returns:
154
  Dictionary containing combined search results
@@ -160,7 +238,7 @@ def keyword_search(query: str, sources: List[str], limit: int = 3) -> Dict:
160
  all_results.extend(hf_results.get("results", []))
161
 
162
  if "smithery" in sources:
163
- smithery_results = keyword_search_smithery(query, limit)
164
  all_results.extend(smithery_results.get("results", []))
165
 
166
  return {
@@ -169,7 +247,7 @@ def keyword_search(query: str, sources: List[str], limit: int = 3) -> Dict:
169
  "search_type": "keyword"
170
  }
171
 
172
- def embedding_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
173
  """
174
  Search for MCPs in Hugging Face Spaces using semantic embedding matching.
175
 
@@ -181,84 +259,100 @@ def embedding_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
181
  Dictionary containing search results with MCP information
182
  """
183
  try:
184
- print("[DEBUG] embedding_search_hf_spaces called")
185
  pinecone_api_key = os.getenv('PINECONE_API_KEY')
186
  openai_api_key = os.getenv('OPENAI_API_KEY')
187
- print(f"[DEBUG] pinecone_api_key exists: {pinecone_api_key is not None}, openai_api_key exists: {openai_api_key is not None}")
188
  if not pinecone_api_key or not openai_api_key:
189
- print("[ERROR] API keys not found")
190
  return {
191
  "error": "API keys not found",
192
  "results": [],
193
  "total": 0
194
  }
195
- print("[DEBUG] Initializing Pinecone and OpenAI clients")
196
  pc = Pinecone(api_key=pinecone_api_key)
197
  index = pc.Index("hf-mcp")
198
  client = OpenAI(api_key=openai_api_key)
199
- print("[DEBUG] Generating embedding with OpenAI")
200
  response = client.embeddings.create(
201
  input=query,
202
  model="text-embedding-3-large"
203
  )
204
  query_embedding = response.data[0].embedding
205
- print(f"[DEBUG] Embedding generated: {type(query_embedding)}, len={len(query_embedding)}")
206
- print("[DEBUG] Querying Pinecone index")
207
  results = index.query(
208
  namespace="",
209
  vector=query_embedding,
210
  top_k=limit
211
  )
212
- print(f"[DEBUG] Pinecone query results: {results}")
213
  space_results = []
214
  if not results.matches:
215
- print("[DEBUG] No matches found in Pinecone results")
216
  return {
217
  "results": [],
218
  "total": 0
219
  }
 
220
  for match in results.matches:
221
  space_id = match.id
222
  try:
223
  repo_id = space_id.replace('spaces/', '')
224
- print(f"[DEBUG] Fetching space info for repo_id: {repo_id}")
225
  space = api.space_info(repo_id)
 
 
 
 
 
 
 
 
 
226
  space_info = {
227
  "id": space.id,
228
  "likes": space.likes,
229
  "trending_score": space.trending_score,
230
  "source": "huggingface",
231
- "score": match.score
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  }
233
  space_results.append(space_info)
234
  except Exception as e:
235
- print(f"[ERROR] Error fetching space info for {space_id}: {str(e)}")
236
  continue
 
237
  return {
238
  "results": space_results,
239
  "total": len(space_results)
240
  }
241
  except Exception as e:
242
- print(f"[CRITICAL ERROR] in embedding_search_hf_spaces: {str(e)}")
243
  return {
244
  "error": str(e),
245
  "results": [],
246
  "total": 0
247
  }
248
 
249
- def embedding_search_smithery(query: str = "", limit: int = 3) -> Dict:
250
  """
251
  Search for MCPs in Smithery Registry using semantic embedding matching.
252
 
253
  Args:
254
  query: Natural language search query
255
  limit: Maximum number of results to return (default: 3)
 
256
 
257
  Returns:
258
  Dictionary containing search results with MCP information
259
  """
260
  try:
261
- print("[DEBUG] embedding_search_smithery called")
262
  from pinecone import Pinecone
263
  from openai import OpenAI
264
  import os
@@ -266,39 +360,37 @@ def embedding_search_smithery(query: str = "", limit: int = 3) -> Dict:
266
  pinecone_api_key = os.getenv('PINECONE_API_KEY')
267
  openai_api_key = os.getenv('OPENAI_API_KEY')
268
  smithery_token = os.getenv('SMITHERY_TOKEN')
269
- print(f"[DEBUG] pinecone_api_key exists: {pinecone_api_key is not None}, openai_api_key exists: {openai_api_key is not None}, smithery_token exists: {smithery_token is not None}")
270
  if not pinecone_api_key or not openai_api_key or not smithery_token:
271
- print("[ERROR] API keys not found")
272
  return {
273
  "error": "API keys not found",
274
  "results": [],
275
  "total": 0
276
  }
277
- print("[DEBUG] Initializing Pinecone and OpenAI clients")
278
  pc = Pinecone(api_key=pinecone_api_key)
279
  index = pc.Index("smithery-mcp")
280
  client = OpenAI(api_key=openai_api_key)
281
- print("[DEBUG] Generating embedding with OpenAI")
282
  response = client.embeddings.create(
283
  input=query,
284
  model="text-embedding-3-large"
285
  )
286
  query_embedding = response.data[0].embedding
287
- print(f"[DEBUG] Embedding generated: {type(query_embedding)}, len={len(query_embedding)}")
288
- print("[DEBUG] Querying Pinecone index")
289
  results = index.query(
290
  namespace="",
291
  vector=query_embedding,
292
  top_k=limit
293
  )
294
- print(f"[DEBUG] Pinecone query results: {results}")
295
  server_results = []
296
  if not results.matches:
297
- print("[DEBUG] No matches found in Pinecone results")
298
  return {
299
  "results": [],
300
  "total": 0
301
  }
 
302
  headers = {
303
  'Authorization': f'Bearer {smithery_token}'
304
  }
@@ -306,40 +398,96 @@ def embedding_search_smithery(query: str = "", limit: int = 3) -> Dict:
306
  for match in results.matches:
307
  server_id = match.id
308
  try:
309
- print(f"[DEBUG] Fetching server info for server_id: {server_id}")
310
  response = requests.get(
311
  f'https://registry.smithery.ai/servers/{server_id}',
312
  headers=headers
313
  )
314
  if response.status_code != 200:
315
- print(f"[ERROR] Smithery API error for {server_id}: {response.status_code}")
316
  continue
 
317
  server = response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  server_info = {
319
- "id": server.get('qualifiedName'),
320
  "name": server.get('displayName'),
321
  "description": server.get('description'),
322
  "likes": server.get('useCount', 0),
323
  "source": "smithery",
324
- "score": match.score
 
325
  }
326
  server_results.append(server_info)
327
  except Exception as e:
328
- print(f"[ERROR] Error fetching server info for {server_id}: {str(e)}")
329
  continue
 
330
  return {
331
  "results": server_results,
332
  "total": len(server_results)
333
  }
334
  except Exception as e:
335
- print(f"[CRITICAL ERROR] in embedding_search_smithery: {str(e)}")
336
  return {
337
  "error": str(e),
338
  "results": [],
339
  "total": 0
340
  }
341
 
342
- def embedding_search(query: str, sources: List[str], limit: int = 3) -> Dict:
343
  """
344
  Search for MCPs using semantic embedding matching.
345
 
@@ -347,6 +495,7 @@ def embedding_search(query: str, sources: List[str], limit: int = 3) -> Dict:
347
  query: Natural language search query
348
  sources: List of sources to search from ('huggingface', 'smithery')
349
  limit: Maximum number of results to return (default: 3)
 
350
 
351
  Returns:
352
  Dictionary containing combined search results
@@ -355,7 +504,7 @@ def embedding_search(query: str, sources: List[str], limit: int = 3) -> Dict:
355
 
356
  if "huggingface" in sources:
357
  try:
358
- hf_results = embedding_search_hf_spaces(query, limit)
359
  all_results.extend(hf_results.get("results", []))
360
  except Exception as e:
361
  # Fallback to keyword search if vector search fails
@@ -364,34 +513,105 @@ def embedding_search(query: str, sources: List[str], limit: int = 3) -> Dict:
364
 
365
  if "smithery" in sources:
366
  try:
367
- smithery_results = embedding_search_smithery(query, limit)
368
  all_results.extend(smithery_results.get("results", []))
369
  except Exception as e:
370
  # Fallback to keyword search if vector search fails
371
- smithery_results = keyword_search_smithery(query, limit)
372
  all_results.extend(smithery_results.get("results", []))
373
 
374
  return {
375
  "results": all_results,
376
  "total": len(all_results),
377
- "search_type": "embedding"
378
  }
379
 
380
  # Create the Gradio interface
381
  with gr.Blocks(title="🚦 Router MCP", css="""
382
- #client_radio {
383
- margin-top: 0 !important;
384
- padding-top: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
386
- #client_radio .radio-group {
387
- gap: 0.5rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  }
389
  """) as demo:
390
- gr.Markdown("# 🚦 Router MCP")
391
- gr.Markdown("### Search MCP compatible spaces using natural language")
 
 
 
 
392
 
393
  with gr.Row():
394
  with gr.Column():
 
395
  query_input = gr.Textbox(
396
  label="Describe the MCP Server you're looking for",
397
  placeholder="e.g., 'I need an MCP Server that can generate images'"
@@ -410,79 +630,88 @@ with gr.Blocks(title="🚦 Router MCP", css="""
410
  step=1
411
  )
412
 
413
- gr.Markdown("### Select your MCP Client")
414
  client_radio = gr.Radio(
415
- choices=["Cursor", "Windsurf", "Claude Desktop", "VS Code", "Gradio"],
416
- label="",
417
- value="Cursor",
418
  interactive=True,
419
  elem_id="client_radio"
420
  )
421
 
422
  with gr.Row():
423
  keyword_search_button = gr.Button("Keyword Search")
424
- embedding_search_button = gr.Button("Semantic Search")
425
 
426
  with gr.Column():
427
- results_output = gr.JSON(label="Search Results")
 
 
 
428
 
429
  # Set up event handlers
430
  def get_sources():
431
  return ["huggingface" if hf_checkbox.value else "", "smithery" if smithery_checkbox.value else ""]
432
 
433
- def handle_keyword_mcp_search(query: str, hf: bool, sm: bool, limit: int) -> Dict:
434
  """
435
  Handle keyword-based search for MCP servers across selected sources. If the client (such as Cursor or Claude) encounters a task it cannot handle, it can use MCP to search for other tools that can.
 
436
 
437
  Args:
438
  query (str): The search query string to find matching MCP servers
439
- hf (bool): Whether to include Hugging Face Spaces in the search (converted to "huggingface" string if True)
440
- sm (bool): Whether to include Smithery in the search (converted to "smithery" string if True)
441
- limit (int): Maximum number of results to return per source (default: 3)
 
442
 
443
  Returns:
444
  Dict: A dictionary containing the search results with the following keys:
445
- - results: List of found MCP servers
446
  - total: Total number of results
447
  - search_type: Type of search performed ("keyword")
448
  """
449
  return keyword_search(
450
  query,
451
  ["huggingface" if hf else "", "smithery" if sm else ""],
452
- int(limit)
 
453
  )
454
 
455
- def handle_embedding_mcp_search(query: str, hf: bool, sm: bool, limit: int) -> Dict:
456
  """
457
  Handle semantic embedding-based search for MCP servers across selected sources. If the client (such as Cursor or Claude) encounters a task it cannot handle, it can use MCP to search for other tools that can.
 
458
 
459
  Args:
460
  query (str): The natural language search query to find semantically similar MCP servers
461
- hf (bool): Whether to include Hugging Face Spaces in the search (converted to "huggingface" string if True)
462
- sm (bool): Whether to include Smithery in the search (converted to "smithery" string if True)
463
- limit (int): Maximum number of results to return per source (default: 3)
 
464
 
465
  Returns:
466
  Dict: A dictionary containing the search results with the following keys:
467
- - results: List of found MCP servers with similarity scores
468
  - total: Total number of results
469
- - search_type: Type of search performed ("embedding")
470
  """
471
- return embedding_search(
472
  query,
473
  ["huggingface" if hf else "", "smithery" if sm else ""],
474
- int(limit)
 
475
  )
476
 
477
  keyword_search_button.click(
478
  fn=handle_keyword_mcp_search,
479
- inputs=[query_input, hf_checkbox, smithery_checkbox, result_limit],
480
  outputs=results_output
481
  )
482
 
483
- embedding_search_button.click(
484
- fn=handle_embedding_mcp_search,
485
- inputs=[query_input, hf_checkbox, smithery_checkbox, result_limit],
486
  outputs=results_output
487
  )
488
 
 
1
  import gradio as gr
2
  import requests
3
+ from typing import List, Dict
4
  from huggingface_hub import HfApi
5
  import os
6
  from dotenv import load_dotenv
 
7
  from pinecone import Pinecone
8
  from openai import OpenAI
9
+ import re
10
 
11
  # Load environment variables
12
  load_dotenv()
 
27
  Dictionary containing search results with MCP information
28
  """
29
  try:
 
 
30
  # Use list_spaces API with mcp-server filter and sort by likes
31
  spaces = list(api.list_spaces(
32
  search=query,
 
38
  results = []
39
  for space in spaces[:limit]: # Process up to limit matches
40
  try:
41
+ # Convert space ID to URL format - replace all special chars with hyphens
42
+ space_id_lower = re.sub(r'[^a-z0-9]', '-', space.id.lower())
43
+ # Remove consecutive hyphens
44
+ space_id_lower = re.sub(r'-+', '-', space_id_lower)
45
+ # Remove leading and trailing hyphens
46
+ space_id_lower = space_id_lower.strip('-')
47
+ sse_url = f"https://{space_id_lower}.hf.space/gradio_api/mcp/sse"
48
+
49
  space_info = {
50
  "id": space.id,
51
  "likes": space.likes,
52
  "trending_score": space.trending_score,
53
+ "source": "huggingface",
54
+ "configuration": {
55
+ "mcpServers": {
56
+ "gradio": {
57
+ "command": "npx", # Use npx to run MCP-Remote
58
+ "args": [
59
+ "mcp-remote",
60
+ sse_url,
61
+ "--transport",
62
+ "sse-only"
63
+ ]
64
+ }
65
+ }
66
+ }
67
+ }
68
  results.append(space_info)
69
  except Exception as e:
 
70
  continue
71
 
72
  return {
 
74
  "total": len(results)
75
  }
76
  except Exception as e:
 
77
  return {
78
  "error": str(e),
79
  "results": [],
80
  "total": 0
81
  }
82
 
83
+ def keyword_search_smithery(query: str = "", limit: int = 3, os_type: str = "Mac/Linux") -> Dict:
84
  """
85
  Search for MCPs in Smithery Registry.
86
 
87
  Args:
88
  query: Search query string
89
  limit: Maximum number of results to return (default: 3)
90
+ os_type: Operating system type ("Mac/Linux", "Windows", "WSL")
91
 
92
  Returns:
93
  Dictionary containing search results with MCP information
 
138
  servers = sorted(data.get('servers', []), key=lambda x: x.get('useCount', 0), reverse=True)[:limit]
139
 
140
  for server in servers:
141
+ server_id = server.get('qualifiedName')
142
+ # Extract server ID without @author/ prefix for configuration
143
+ config_server_id = server_id.split('/')[-1] if '/' in server_id else server_id
144
+
145
+ # Create configuration based on OS type
146
+ if os_type == "Mac/Linux":
147
+ configuration = {
148
+ "mcpServers": {
149
+ f"{config_server_id}": {
150
+ "command": "npx",
151
+ "args": [
152
+ "-y",
153
+ "@smithery/cli@latest",
154
+ "run",
155
+ f"{server_id}",
156
+ "--key",
157
+ "YOUR_SMITHERY_KEY"
158
+ ]
159
+ }
160
+ }
161
+ }
162
+ elif os_type == "Windows":
163
+ configuration = {
164
+ "mcpServers": {
165
+ f"{config_server_id}": {
166
+ "command": "cmd",
167
+ "args": [
168
+ "/c",
169
+ "npx",
170
+ "-y",
171
+ "@smithery/cli@latest",
172
+ "run",
173
+ f"{server_id}",
174
+ "--key",
175
+ "YOUR_SMITHERY_KEY"
176
+ ]
177
+ }
178
+ }
179
+ }
180
+ elif os_type == "WSL":
181
+ configuration = {
182
+ "mcpServers": {
183
+ f"{config_server_id}": {
184
+ "command": "wsl",
185
+ "args": [
186
+ "npx",
187
+ "-y",
188
+ "@smithery/cli@latest",
189
+ "run",
190
+ f"{server_id}",
191
+ "--key",
192
+ "YOUR_SMITHERY_KEY"
193
+ ]
194
+ }
195
+ }
196
+ }
197
+
198
  server_info = {
199
+ "id": server_id,
200
  "name": server.get('displayName'),
201
  "description": server.get('description'),
202
  "likes": server.get('useCount', 0),
203
+ "source": "smithery",
204
+ "configuration": configuration
205
  }
206
+
207
  results.append(server_info)
208
 
209
  return {
 
218
  "total": 0
219
  }
220
 
221
+ def keyword_search(query: str, sources: List[str], limit: int = 3, os_type: str = "Mac/Linux") -> Dict:
222
  """
223
  Search for MCPs using keyword matching.
224
 
 
226
  query: Keyword search query
227
  sources: List of sources to search from ('huggingface', 'smithery')
228
  limit: Maximum number of results to return (default: 3)
229
+ os_type: Operating system type ("Mac/Linux", "Windows", "WSL")
230
 
231
  Returns:
232
  Dictionary containing combined search results
 
238
  all_results.extend(hf_results.get("results", []))
239
 
240
  if "smithery" in sources:
241
+ smithery_results = keyword_search_smithery(query, limit, os_type)
242
  all_results.extend(smithery_results.get("results", []))
243
 
244
  return {
 
247
  "search_type": "keyword"
248
  }
249
 
250
+ def semantic_search_hf_spaces(query: str = "", limit: int = 3) -> Dict:
251
  """
252
  Search for MCPs in Hugging Face Spaces using semantic embedding matching.
253
 
 
259
  Dictionary containing search results with MCP information
260
  """
261
  try:
 
262
  pinecone_api_key = os.getenv('PINECONE_API_KEY')
263
  openai_api_key = os.getenv('OPENAI_API_KEY')
 
264
  if not pinecone_api_key or not openai_api_key:
 
265
  return {
266
  "error": "API keys not found",
267
  "results": [],
268
  "total": 0
269
  }
270
+
271
  pc = Pinecone(api_key=pinecone_api_key)
272
  index = pc.Index("hf-mcp")
273
  client = OpenAI(api_key=openai_api_key)
274
+
275
  response = client.embeddings.create(
276
  input=query,
277
  model="text-embedding-3-large"
278
  )
279
  query_embedding = response.data[0].embedding
280
+
 
281
  results = index.query(
282
  namespace="",
283
  vector=query_embedding,
284
  top_k=limit
285
  )
286
+
287
  space_results = []
288
  if not results.matches:
 
289
  return {
290
  "results": [],
291
  "total": 0
292
  }
293
+
294
  for match in results.matches:
295
  space_id = match.id
296
  try:
297
  repo_id = space_id.replace('spaces/', '')
 
298
  space = api.space_info(repo_id)
299
+
300
+ # Convert space ID to URL format - replace all special chars with hyphens
301
+ space_id_lower = re.sub(r'[^a-z0-9]', '-', space.id.lower())
302
+ # Remove consecutive hyphens
303
+ space_id_lower = re.sub(r'-+', '-', space_id_lower)
304
+ # Remove leading and trailing hyphens
305
+ space_id_lower = space_id_lower.strip('-')
306
+ sse_url = f"https://{space_id_lower}.hf.space/gradio_api/mcp/sse"
307
+
308
  space_info = {
309
  "id": space.id,
310
  "likes": space.likes,
311
  "trending_score": space.trending_score,
312
  "source": "huggingface",
313
+ "score": match.score,
314
+ "configuration": {
315
+ "mcpServers": {
316
+ "gradio": {
317
+ "command": "npx",
318
+ "args": [
319
+ "mcp-remote",
320
+ sse_url,
321
+ "--transport",
322
+ "sse-only"
323
+ ]
324
+ }
325
+ }
326
+ }
327
  }
328
  space_results.append(space_info)
329
  except Exception as e:
 
330
  continue
331
+
332
  return {
333
  "results": space_results,
334
  "total": len(space_results)
335
  }
336
  except Exception as e:
 
337
  return {
338
  "error": str(e),
339
  "results": [],
340
  "total": 0
341
  }
342
 
343
+ def semantic_search_smithery(query: str = "", limit: int = 3, os_type: str = "Mac/Linux") -> Dict:
344
  """
345
  Search for MCPs in Smithery Registry using semantic embedding matching.
346
 
347
  Args:
348
  query: Natural language search query
349
  limit: Maximum number of results to return (default: 3)
350
+ os_type: Operating system type ("Mac/Linux", "Windows", "WSL")
351
 
352
  Returns:
353
  Dictionary containing search results with MCP information
354
  """
355
  try:
 
356
  from pinecone import Pinecone
357
  from openai import OpenAI
358
  import os
 
360
  pinecone_api_key = os.getenv('PINECONE_API_KEY')
361
  openai_api_key = os.getenv('OPENAI_API_KEY')
362
  smithery_token = os.getenv('SMITHERY_TOKEN')
363
+
364
  if not pinecone_api_key or not openai_api_key or not smithery_token:
 
365
  return {
366
  "error": "API keys not found",
367
  "results": [],
368
  "total": 0
369
  }
370
+
371
  pc = Pinecone(api_key=pinecone_api_key)
372
  index = pc.Index("smithery-mcp")
373
  client = OpenAI(api_key=openai_api_key)
374
+
375
  response = client.embeddings.create(
376
  input=query,
377
  model="text-embedding-3-large"
378
  )
379
  query_embedding = response.data[0].embedding
380
+
 
381
  results = index.query(
382
  namespace="",
383
  vector=query_embedding,
384
  top_k=limit
385
  )
386
+
387
  server_results = []
388
  if not results.matches:
 
389
  return {
390
  "results": [],
391
  "total": 0
392
  }
393
+
394
  headers = {
395
  'Authorization': f'Bearer {smithery_token}'
396
  }
 
398
  for match in results.matches:
399
  server_id = match.id
400
  try:
 
401
  response = requests.get(
402
  f'https://registry.smithery.ai/servers/{server_id}',
403
  headers=headers
404
  )
405
  if response.status_code != 200:
 
406
  continue
407
+
408
  server = response.json()
409
+
410
+ # Extract server ID without @author/ prefix for configuration
411
+ config_server_id = server_id.split('/')[-1] if '/' in server_id else server_id
412
+
413
+ # Create configuration based on OS type
414
+ if os_type == "Mac/Linux":
415
+ configuration = {
416
+ "mcpServers": {
417
+ f"{config_server_id}": {
418
+ "command": "npx",
419
+ "args": [
420
+ "-y",
421
+ "@smithery/cli@latest",
422
+ "run",
423
+ f"{server_id}",
424
+ "--key",
425
+ "YOUR_SMITHERY_KEY"
426
+ ]
427
+ }
428
+ }
429
+ }
430
+ elif os_type == "Windows":
431
+ configuration = {
432
+ "mcpServers": {
433
+ f"{config_server_id}": {
434
+ "command": "cmd",
435
+ "args": [
436
+ "/c",
437
+ "npx",
438
+ "-y",
439
+ "@smithery/cli@latest",
440
+ "run",
441
+ f"{server_id}",
442
+ "--key",
443
+ "YOUR_SMITHERY_KEY"
444
+ ]
445
+ }
446
+ }
447
+ }
448
+ elif os_type == "WSL":
449
+ configuration = {
450
+ "mcpServers": {
451
+ f"{config_server_id}": {
452
+ "command": "wsl",
453
+ "args": [
454
+ "npx",
455
+ "-y",
456
+ "@smithery/cli@latest",
457
+ "run",
458
+ f"{server_id}",
459
+ "--key",
460
+ "YOUR_SMITHERY_KEY"
461
+ ]
462
+ }
463
+ }
464
+ }
465
+
466
  server_info = {
467
+ "id": server_id,
468
  "name": server.get('displayName'),
469
  "description": server.get('description'),
470
  "likes": server.get('useCount', 0),
471
  "source": "smithery",
472
+ "score": match.score,
473
+ "configuration": configuration
474
  }
475
  server_results.append(server_info)
476
  except Exception as e:
 
477
  continue
478
+
479
  return {
480
  "results": server_results,
481
  "total": len(server_results)
482
  }
483
  except Exception as e:
 
484
  return {
485
  "error": str(e),
486
  "results": [],
487
  "total": 0
488
  }
489
 
490
+ def semantic_search(query: str, sources: List[str], limit: int = 3, os_type: str = "Mac/Linux") -> Dict:
491
  """
492
  Search for MCPs using semantic embedding matching.
493
 
 
495
  query: Natural language search query
496
  sources: List of sources to search from ('huggingface', 'smithery')
497
  limit: Maximum number of results to return (default: 3)
498
+ os_type: Operating system type ("Mac/Linux", "Windows", "WSL")
499
 
500
  Returns:
501
  Dictionary containing combined search results
 
504
 
505
  if "huggingface" in sources:
506
  try:
507
+ hf_results = semantic_search_hf_spaces(query, limit)
508
  all_results.extend(hf_results.get("results", []))
509
  except Exception as e:
510
  # Fallback to keyword search if vector search fails
 
513
 
514
  if "smithery" in sources:
515
  try:
516
+ smithery_results = semantic_search_smithery(query, limit, os_type)
517
  all_results.extend(smithery_results.get("results", []))
518
  except Exception as e:
519
  # Fallback to keyword search if vector search fails
520
+ smithery_results = keyword_search_smithery(query, limit, os_type)
521
  all_results.extend(smithery_results.get("results", []))
522
 
523
  return {
524
  "results": all_results,
525
  "total": len(all_results),
526
+ "search_type": "semantic"
527
  }
528
 
529
  # Create the Gradio interface
530
  with gr.Blocks(title="🚦 Router MCP", css="""
531
+ /* Make JSON output expanded by default */
532
+ .json-viewer-container {
533
+ display: block !important;
534
+ }
535
+ .json-viewer-container > .json-viewer-header {
536
+ display: none !important;
537
+ }
538
+ .json-viewer-container > .json-viewer-content {
539
+ display: block !important;
540
+ max-height: none !important;
541
+ }
542
+ .json-viewer-container .json-viewer-item {
543
+ display: block !important;
544
+ }
545
+ .json-viewer-container .json-viewer-item > .json-viewer-header {
546
+ display: none !important;
547
  }
548
+ .json-viewer-container .json-viewer-item > .json-viewer-content {
549
+ display: block !important;
550
+ max-height: none !important;
551
+ }
552
+ /* Additional selectors for nested items */
553
+ .json-viewer-container .json-viewer-item .json-viewer-item {
554
+ display: block !important;
555
+ }
556
+ .json-viewer-container .json-viewer-item .json-viewer-item > .json-viewer-header {
557
+ display: none !important;
558
+ }
559
+ .json-viewer-container .json-viewer-item .json-viewer-item > .json-viewer-content {
560
+ display: block !important;
561
+ max-height: none !important;
562
+ }
563
+ /* Title styling */
564
+ .title-container {
565
+ text-align: center;
566
+ margin: 0.5rem 0;
567
+ position: relative;
568
+ padding: 0.5rem 0;
569
+ overflow: hidden;
570
+ }
571
+ .title-container h1 {
572
+ display: inline-block;
573
+ position: relative;
574
+ z-index: 1;
575
+ font-size: 1.8rem;
576
+ margin: 0;
577
+ line-height: 1.2;
578
+ mix-blend-mode: multiply;
579
+ }
580
+ .title-container p {
581
+ position: relative;
582
+ z-index: 1;
583
+ font-size: 1rem;
584
+ margin: 0.5rem 0 0 0;
585
+ color: #666;
586
+ mix-blend-mode: multiply;
587
+ }
588
+ .traffic-light {
589
+ position: absolute;
590
+ top: 50%;
591
+ left: 50%;
592
+ transform: translate(-50%, -50%);
593
+ width: 300px;
594
+ height: 40px;
595
+ background: linear-gradient(90deg,
596
+ rgba(255, 0, 0, 0.3) 0%,
597
+ rgba(255, 165, 0, 0.3) 50%,
598
+ rgba(0, 255, 0, 0.3) 100%
599
+ );
600
+ border-radius: 20px;
601
+ z-index: 0;
602
+ filter: blur(20px);
603
  }
604
  """) as demo:
605
+ with gr.Column(elem_classes=["title-container"]):
606
+ gr.HTML('''
607
+ <div class="traffic-light"></div>
608
+ <h1>🚦 Router MCP</h1>
609
+ <p>Your Gateway to Optimal MCP Servers in Seconds</p>
610
+ ''')
611
 
612
  with gr.Row():
613
  with gr.Column():
614
+ gr.Markdown("### Search MCP servers using natural language query")
615
  query_input = gr.Textbox(
616
  label="Describe the MCP Server you're looking for",
617
  placeholder="e.g., 'I need an MCP Server that can generate images'"
 
630
  step=1
631
  )
632
 
633
+ gr.Markdown("### Select your OS")
634
  client_radio = gr.Radio(
635
+ choices=["Mac/Linux", "Windows", "WSL"],
636
+ label="Choose your operating system to get the appropriate command format",
637
+ value="Mac/Linux", # Default back to Mac/Linux
638
  interactive=True,
639
  elem_id="client_radio"
640
  )
641
 
642
  with gr.Row():
643
  keyword_search_button = gr.Button("Keyword Search")
644
+ semantic_search_button = gr.Button("Semantic Search")
645
 
646
  with gr.Column():
647
+ results_output = gr.JSON(
648
+ label="Search Results",
649
+ elem_id="results_output"
650
+ )
651
 
652
  # Set up event handlers
653
  def get_sources():
654
  return ["huggingface" if hf_checkbox.value else "", "smithery" if smithery_checkbox.value else ""]
655
 
656
+ def handle_keyword_mcp_search(query: str, hf: bool, sm: bool, limit: int, os_type: str) -> Dict:
657
  """
658
  Handle keyword-based search for MCP servers across selected sources. If the client (such as Cursor or Claude) encounters a task it cannot handle, it can use MCP to search for other tools that can.
659
+ Use this search when you know the specific name or keywords of the MCP Server you're looking for.
660
 
661
  Args:
662
  query (str): The search query string to find matching MCP servers
663
+ hf (bool): Whether to include Hugging Face Spaces in the search
664
+ sm (bool): Whether to include Smithery in the search
665
+ limit (int): Maximum number of results to return per source
666
+ os_type (str): Operating system type ("Mac/Linux", "Windows", "WSL")
667
 
668
  Returns:
669
  Dict: A dictionary containing the search results with the following keys:
670
+ - results: List of found MCP servers with their configurations. Each configuration can be added to the MCP Client's config file to register the server.
671
  - total: Total number of results
672
  - search_type: Type of search performed ("keyword")
673
  """
674
  return keyword_search(
675
  query,
676
  ["huggingface" if hf else "", "smithery" if sm else ""],
677
+ int(limit),
678
+ os_type
679
  )
680
 
681
+ def handle_semantic_mcp_search(query: str, hf: bool, sm: bool, limit: int, os_type: str) -> Dict:
682
  """
683
  Handle semantic embedding-based search for MCP servers across selected sources. If the client (such as Cursor or Claude) encounters a task it cannot handle, it can use MCP to search for other tools that can.
684
+ Use this search when your query is more abstract or conceptual, as it can understand the meaning and context of your request.
685
 
686
  Args:
687
  query (str): The natural language search query to find semantically similar MCP servers
688
+ hf (bool): Whether to include Hugging Face Spaces in the search
689
+ sm (bool): Whether to include Smithery in the search
690
+ limit (int): Maximum number of results to return per source
691
+ os_type (str): Operating system type ("Mac/Linux", "Windows", "WSL")
692
 
693
  Returns:
694
  Dict: A dictionary containing the search results with the following keys:
695
+ - results: List of found MCP servers with their configurations and similarity scores. Each configuration can be added to the MCP Client's config file to register the server.
696
  - total: Total number of results
697
+ - search_type: Type of search performed ("semantic")
698
  """
699
+ return semantic_search(
700
  query,
701
  ["huggingface" if hf else "", "smithery" if sm else ""],
702
+ int(limit),
703
+ os_type
704
  )
705
 
706
  keyword_search_button.click(
707
  fn=handle_keyword_mcp_search,
708
+ inputs=[query_input, hf_checkbox, smithery_checkbox, result_limit, client_radio],
709
  outputs=results_output
710
  )
711
 
712
+ semantic_search_button.click(
713
+ fn=handle_semantic_mcp_search,
714
+ inputs=[query_input, hf_checkbox, smithery_checkbox, result_limit, client_radio],
715
  outputs=results_output
716
  )
717