nia-mcp-server 1.0.11__py3-none-any.whl → 1.0.13__py3-none-any.whl

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.

Potentially problematic release.


This version of nia-mcp-server might be problematic. Click here for more details.

@@ -2,4 +2,4 @@
2
2
  NIA MCP Server - Proxy server for NIA Knowledge Agent
3
3
  """
4
4
 
5
- __version__ = "1.0.11"
5
+ __version__ = "1.0.13"
@@ -1,11 +1,21 @@
1
1
  """
2
2
  Entry point for NIA MCP Server
3
3
  """
4
+ import sys
4
5
  from .server import run
6
+ from .cli import main as cli_main
7
+
5
8
 
6
9
  def main():
7
10
  """Main entry point."""
8
- run()
11
+ # Check if running as CLI command
12
+ if len(sys.argv) > 1 and sys.argv[1] in ["setup", "--help", "-h"]:
13
+ # Run CLI interface
14
+ cli_main()
15
+ else:
16
+ # Run MCP server
17
+ run()
18
+
9
19
 
10
20
  if __name__ == "__main__":
11
21
  main()
@@ -75,7 +75,7 @@ class NIAApiClient:
75
75
  "lifetime limit",
76
76
  "no chat credits",
77
77
  "free api requests",
78
- "25 free",
78
+ "5 free",
79
79
  "usage limit",
80
80
  ]
81
81
  ):
@@ -99,7 +99,7 @@ class NIAApiClient:
99
99
  for phrase in [
100
100
  "lifetime limit",
101
101
  "free api requests",
102
- "25 free",
102
+ "5 free",
103
103
  "usage limit",
104
104
  ]
105
105
  ):
@@ -623,4 +623,30 @@ class NIAApiClient:
623
623
  except httpx.HTTPStatusError as e:
624
624
  raise self._handle_api_error(e)
625
625
  except Exception as e:
626
- raise APIError(f"Deep research failed: {str(e)}")
626
+ raise APIError(f"Deep research failed: {str(e)}")
627
+
628
+ async def get_source_content(
629
+ self,
630
+ source_type: str,
631
+ source_identifier: str,
632
+ metadata: Dict[str, Any] = None
633
+ ) -> Dict[str, Any]:
634
+ """Get full content of a specific source file or document."""
635
+ try:
636
+ payload = {
637
+ "source_type": source_type,
638
+ "source_identifier": source_identifier,
639
+ "metadata": metadata or {}
640
+ }
641
+
642
+ response = await self.client.post(
643
+ f"{self.base_url}/v2/sources/content",
644
+ json=payload
645
+ )
646
+ response.raise_for_status()
647
+ return response.json()
648
+
649
+ except httpx.HTTPStatusError as e:
650
+ raise self._handle_api_error(e)
651
+ except Exception as e:
652
+ raise APIError(f"Failed to get source content: {str(e)}")
nia_mcp_server/cli.py ADDED
@@ -0,0 +1,69 @@
1
+ """
2
+ CLI interface for NIA MCP Server
3
+ """
4
+ import sys
5
+ import argparse
6
+ from typing import Optional
7
+ from .setup import setup_mcp_config
8
+
9
+
10
+ def validate_api_key(api_key: str) -> bool:
11
+ """Validate API key format."""
12
+ if not api_key:
13
+ return False
14
+ # Check if it starts with the expected prefix
15
+ if not api_key.startswith("nk_"):
16
+ return False
17
+ # Check minimum length (prefix + reasonable key length)
18
+ if len(api_key) < 10:
19
+ return False
20
+ return True
21
+
22
+
23
+ def main():
24
+ """Main CLI entry point."""
25
+ parser = argparse.ArgumentParser(
26
+ description="NIA MCP Server - AI-powered code search",
27
+ prog="nia-mcp-server"
28
+ )
29
+
30
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
31
+
32
+ # Setup command
33
+ setup_parser = subparsers.add_parser(
34
+ "setup",
35
+ help="Set up NIA MCP Server for your IDE"
36
+ )
37
+ setup_parser.add_argument(
38
+ "api_key",
39
+ help="Your NIA API key (get it from https://app.trynia.ai/api-keys)"
40
+ )
41
+ setup_parser.add_argument(
42
+ "--ide",
43
+ choices=["cursor", "vscode", "continue"],
44
+ default="cursor",
45
+ help="IDE to configure (default: cursor)"
46
+ )
47
+
48
+ # Parse arguments
49
+ args = parser.parse_args()
50
+
51
+ # Handle commands
52
+ if args.command == "setup":
53
+ # Validate API key
54
+ if not validate_api_key(args.api_key):
55
+ print("❌ Invalid API key format. API key should start with 'nk_'")
56
+ print(" Get your API key from: https://app.trynia.ai/api-keys")
57
+ sys.exit(1)
58
+
59
+ # Run setup
60
+ success = setup_mcp_config(args.api_key, args.ide)
61
+ sys.exit(0 if success else 1)
62
+
63
+ # If no command specified, show help
64
+ parser.print_help()
65
+ sys.exit(1)
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
nia_mcp_server/server.py CHANGED
@@ -25,7 +25,7 @@ load_dotenv(env_path)
25
25
 
26
26
  # Configure logging
27
27
  logging.basicConfig(
28
- level=logging.INFO,
28
+ level=logging.INFO, # Changed from INFO to DEBUG for troubleshooting
29
29
  format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
30
30
  )
31
31
  logger = logging.getLogger(__name__)
@@ -122,7 +122,7 @@ async def index_repository(
122
122
  except APIError as e:
123
123
  logger.error(f"API Error indexing repository: {e} (status_code={e.status_code}, detail={e.detail})")
124
124
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
125
- if e.detail and "25 free API requests" in e.detail:
125
+ if e.detail and "5 free API requests" in e.detail:
126
126
  return [TextContent(
127
127
  type="text",
128
128
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -222,9 +222,11 @@ async def search_codebase(
222
222
  response_parts.append(data["content"])
223
223
 
224
224
  if "sources" in data and data["sources"]:
225
+ logger.debug(f"Received sources data: {type(data['sources'])}, count: {len(data['sources'])}")
225
226
  sources_parts.extend(data["sources"])
226
227
 
227
- except json.JSONDecodeError:
228
+ except json.JSONDecodeError as e:
229
+ logger.warning(f"Failed to parse JSON chunk: {chunk}, error: {e}")
228
230
  continue
229
231
 
230
232
  # Format the response
@@ -232,21 +234,53 @@ async def search_codebase(
232
234
 
233
235
  if sources_parts and include_sources:
234
236
  response_text += "\n\n## Sources\n\n"
235
- for i, source in enumerate(sources_parts[:5], 1): # Limit to 5 sources
237
+ for i, source in enumerate(sources_parts[:10], 1): # Limit to 10 sources (matches backend)
236
238
  response_text += f"### Source {i}\n"
237
- if "repository" in source:
238
- response_text += f"**Repository:** {source['repository']}\n"
239
- if "file" in source:
240
- response_text += f"**File:** `{source['file']}`\n"
241
- if "preview" in source:
242
- response_text += f"```\n{source['preview']}\n```\n\n"
239
+
240
+ # Handle both string sources (file paths) and dictionary sources
241
+ if isinstance(source, str):
242
+ # Source is just a file path string
243
+ response_text += f"**File:** `{source}`\n\n"
244
+ continue
245
+ elif not isinstance(source, dict):
246
+ logger.warning(f"Expected source to be dict or str, got {type(source)}: {source}")
247
+ response_text += f"**Source:** {str(source)}\n\n"
248
+ continue
249
+
250
+ # Handle dictionary sources with metadata
251
+ metadata = source.get("metadata", {})
252
+
253
+ # Repository name
254
+ repository = source.get("repository") or metadata.get("source_name") or metadata.get("repository")
255
+ if repository:
256
+ response_text += f"**Repository:** {repository}\n"
257
+
258
+ # File path
259
+ file_path = source.get("file") or source.get("file_path") or metadata.get("file_path")
260
+ if file_path:
261
+ response_text += f"**File:** `{file_path}`\n"
262
+
263
+ # Content/preview
264
+ content = source.get("preview") or source.get("content")
265
+ if content:
266
+ # Truncate very long content
267
+ if len(content) > 500:
268
+ content = content[:500] + "..."
269
+ response_text += f"```\n{content}\n```\n\n"
270
+ else:
271
+ # If no content, at least show that this is a valid source
272
+ response_text += f"*Referenced source*\n\n"
273
+
274
+ # Add helpful text about read_source_content tool
275
+ response_text += "\n💡 **Need more details from a source?**\n\n"
276
+ response_text += "If you need more information from the source links provided above, use the `read_source_content` tool from the available tools provided by Nia to get full context about that particular source.\n"
243
277
 
244
278
  return [TextContent(type="text", text=response_text)]
245
279
 
246
280
  except APIError as e:
247
281
  logger.error(f"API Error searching codebase: {e} (status_code={e.status_code}, detail={e.detail})")
248
282
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
249
- if e.detail and "25 free API requests" in e.detail:
283
+ if e.detail and "5 free API requests" in e.detail:
250
284
  return [TextContent(
251
285
  type="text",
252
286
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -287,6 +321,9 @@ async def search_documentation(
287
321
 
288
322
  Returns:
289
323
  Search results with relevant documentation excerpts
324
+
325
+ Important:
326
+ - Always use Source ID. If you don't have it, use `list_documentation` tool to get it.
290
327
  """
291
328
  try:
292
329
  client = await ensure_api_client()
@@ -316,7 +353,7 @@ async def search_documentation(
316
353
  messages=messages,
317
354
  repositories=[], # No repositories
318
355
  data_sources=sources,
319
- search_mode="unified", # Use unified for full answers with documentation
356
+ search_mode="unified", # Use unified mode for intelligent LLM processing
320
357
  stream=True,
321
358
  include_sources=include_sources
322
359
  ):
@@ -327,9 +364,11 @@ async def search_documentation(
327
364
  response_parts.append(data["content"])
328
365
 
329
366
  if "sources" in data and data["sources"]:
367
+ logger.debug(f"Received doc sources data: {type(data['sources'])}, count: {len(data['sources'])}")
330
368
  sources_parts.extend(data["sources"])
331
369
 
332
- except json.JSONDecodeError:
370
+ except json.JSONDecodeError as e:
371
+ logger.warning(f"Failed to parse JSON chunk in documentation search: {chunk}, error: {e}")
333
372
  continue
334
373
 
335
374
  # Format the response
@@ -337,14 +376,42 @@ async def search_documentation(
337
376
 
338
377
  if sources_parts and include_sources:
339
378
  response_text += "\n\n## Sources\n\n"
340
- for i, source in enumerate(sources_parts[:5], 1): # Limit to 5 sources
379
+ for i, source in enumerate(sources_parts[:10], 1): # Limit to 10 sources (matches backend)
341
380
  response_text += f"### Source {i}\n"
342
- if "url" in source:
343
- response_text += f"**URL:** {source['url']}\n"
344
- elif "file" in source:
345
- response_text += f"**Page:** {source['file']}\n"
346
- if "preview" in source:
347
- response_text += f"```\n{source['preview']}\n```\n\n"
381
+
382
+ # Handle both string sources and dictionary sources
383
+ if isinstance(source, str):
384
+ # Source is just a URL or file path string
385
+ response_text += f"**Document:** {source}\n\n"
386
+ continue
387
+ elif not isinstance(source, dict):
388
+ logger.warning(f"Expected source to be dict or str, got {type(source)}: {source}")
389
+ response_text += f"**Source:** {str(source)}\n\n"
390
+ continue
391
+
392
+ # Handle dictionary sources with metadata
393
+ metadata = source.get("metadata", {})
394
+
395
+ # URL or file
396
+ url = source.get("url") or metadata.get("url") or metadata.get("source") or metadata.get("sourceURL")
397
+ file_path = source.get("file") or source.get("file_path") or metadata.get("file_path") or metadata.get("document_name")
398
+
399
+ if url:
400
+ response_text += f"**URL:** {url}\n"
401
+ elif file_path:
402
+ response_text += f"**Document:** {file_path}\n"
403
+
404
+ # Title if available
405
+ title = source.get("title") or metadata.get("title")
406
+ if title:
407
+ response_text += f"**Title:** {title}\n"
408
+
409
+ # Add spacing after each source
410
+ response_text += "\n"
411
+
412
+ # Add helpful text about read_source_content tool
413
+ response_text += "\n💡 **Need more details from a source?**\n\n"
414
+ response_text += "If you need more information from the source links provided above, use the `read_source_content` tool from the available tools provided by Nia to get full context about that particular source.\n"
348
415
 
349
416
  return [TextContent(type="text", text=response_text)]
350
417
 
@@ -352,7 +419,7 @@ async def search_documentation(
352
419
  logger.error(f"API Error searching documentation: {e}")
353
420
  error_msg = f"❌ {str(e)}"
354
421
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
355
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
422
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
356
423
  return [TextContent(type="text", text=error_msg)]
357
424
  except Exception as e:
358
425
  logger.error(f"Error searching documentation: {e}")
@@ -411,7 +478,7 @@ async def list_repositories() -> List[TextContent]:
411
478
  # Check for free tier limit errors
412
479
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
413
480
  # Extract the specific limit message
414
- if e.detail and "25 free API requests" in e.detail:
481
+ if e.detail and "5 free API requests" in e.detail:
415
482
  return [TextContent(
416
483
  type="text",
417
484
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -491,7 +558,7 @@ async def check_repository_status(repository: str) -> List[TextContent]:
491
558
  logger.error(f"API Error checking repository status: {e}")
492
559
  error_msg = f"❌ {str(e)}"
493
560
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
494
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
561
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
495
562
  return [TextContent(type="text", text=error_msg)]
496
563
  except Exception as e:
497
564
  logger.error(f"Error checking repository status: {e}")
@@ -578,7 +645,7 @@ async def index_documentation(
578
645
  logger.error(f"API Error indexing documentation: {e}")
579
646
  error_msg = f"❌ {str(e)}"
580
647
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
581
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
648
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
582
649
  return [TextContent(type="text", text=error_msg)]
583
650
  except Exception as e:
584
651
  logger.error(f"Error indexing documentation: {e}")
@@ -637,7 +704,7 @@ async def list_documentation() -> List[TextContent]:
637
704
  logger.error(f"API Error listing documentation: {e}")
638
705
  error_msg = f"❌ {str(e)}"
639
706
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
640
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
707
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
641
708
  return [TextContent(type="text", text=error_msg)]
642
709
  except Exception as e:
643
710
  logger.error(f"Error listing documentation: {e}")
@@ -703,7 +770,7 @@ async def check_documentation_status(source_id: str) -> List[TextContent]:
703
770
  logger.error(f"API Error checking documentation status: {e}")
704
771
  error_msg = f"❌ {str(e)}"
705
772
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
706
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
773
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
707
774
  return [TextContent(type="text", text=error_msg)]
708
775
  except Exception as e:
709
776
  logger.error(f"Error checking documentation status: {e}")
@@ -742,7 +809,7 @@ async def delete_documentation(source_id: str) -> List[TextContent]:
742
809
  logger.error(f"API Error deleting documentation: {e}")
743
810
  error_msg = f"❌ {str(e)}"
744
811
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
745
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
812
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
746
813
  return [TextContent(type="text", text=error_msg)]
747
814
  except Exception as e:
748
815
  logger.error(f"Error deleting documentation: {e}")
@@ -781,7 +848,7 @@ async def delete_repository(repository: str) -> List[TextContent]:
781
848
  logger.error(f"API Error deleting repository: {e}")
782
849
  error_msg = f"❌ {str(e)}"
783
850
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
784
- error_msg += "\n\n💡 Tip: You've reached the free tier limit of 25 API requests. Upgrade to Pro for unlimited access."
851
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit of 5 API requests. Upgrade to Pro for unlimited access."
785
852
  return [TextContent(type="text", text=error_msg)]
786
853
  except Exception as e:
787
854
  logger.error(f"Error deleting repository: {e}")
@@ -1033,7 +1100,7 @@ async def nia_web_search(
1033
1100
  except APIError as e:
1034
1101
  logger.error(f"API Error in web search: {e}")
1035
1102
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
1036
- if e.detail and "25 free API requests" in e.detail:
1103
+ if e.detail and "5 free API requests" in e.detail:
1037
1104
  return [TextContent(
1038
1105
  type="text",
1039
1106
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -1212,7 +1279,7 @@ async def nia_deep_research_agent(
1212
1279
  except APIError as e:
1213
1280
  logger.error(f"API Error in deep research: {e}")
1214
1281
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
1215
- if e.detail and "25 free API requests" in e.detail:
1282
+ if e.detail and "5 free API requests" in e.detail:
1216
1283
  return [TextContent(
1217
1284
  type="text",
1218
1285
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -1388,6 +1455,134 @@ async def initialize_project(
1388
1455
  "- The NIA MCP server is properly installed"
1389
1456
  )]
1390
1457
 
1458
+ @mcp.tool()
1459
+ async def read_source_content(
1460
+ source_type: str,
1461
+ source_identifier: str,
1462
+ metadata: Optional[Dict[str, Any]] = None
1463
+ ) -> List[TextContent]:
1464
+ """
1465
+ Read the full content of a specific source file or document.
1466
+
1467
+ This tool allows AI to fetch complete content from sources identified during search,
1468
+ enabling deeper analysis when the truncated search results are insufficient.
1469
+
1470
+ Args:
1471
+ source_type: Type of source - "repository" or "documentation"
1472
+ source_identifier:
1473
+ - For repository: "owner/repo:path/to/file.py" (e.g., "facebook/react:src/React.js")
1474
+ - For documentation: The source URL or document ID
1475
+ metadata: Optional metadata from search results to help locate the source
1476
+
1477
+ Returns:
1478
+ Full content of the requested source with metadata
1479
+
1480
+ Examples:
1481
+ - read_source_content("repository", "langchain-ai/langchain:libs/core/langchain_core/runnables/base.py")
1482
+ - read_source_content("documentation", "https://docs.python.org/3/library/asyncio.html")
1483
+ """
1484
+ try:
1485
+ client = await ensure_api_client()
1486
+
1487
+ logger.info(f"Reading source content - type: {source_type}, identifier: {source_identifier}")
1488
+
1489
+ # Call the API to get source content
1490
+ result = await client.get_source_content(
1491
+ source_type=source_type,
1492
+ source_identifier=source_identifier,
1493
+ metadata=metadata or {}
1494
+ )
1495
+
1496
+ if not result or not result.get("success"):
1497
+ error_msg = result.get("error", "Unknown error") if result else "Failed to fetch source content"
1498
+ return [TextContent(
1499
+ type="text",
1500
+ text=f"❌ Error reading source: {error_msg}"
1501
+ )]
1502
+
1503
+ # Format the response
1504
+ content = result.get("content", "")
1505
+ source_metadata = result.get("metadata", {})
1506
+
1507
+ # Build response with metadata header
1508
+ response_lines = []
1509
+
1510
+ if source_type == "repository":
1511
+ repo_name = source_metadata.get("repository", "Unknown")
1512
+ file_path = source_metadata.get("file_path", source_identifier.split(":", 1)[-1] if ":" in source_identifier else "Unknown")
1513
+ branch = source_metadata.get("branch", "main")
1514
+
1515
+ response_lines.extend([
1516
+ f"# Source: {repo_name}",
1517
+ f"**File:** `{file_path}`",
1518
+ f"**Branch:** {branch}",
1519
+ ""
1520
+ ])
1521
+
1522
+ if source_metadata.get("url"):
1523
+ response_lines.append(f"**GitHub URL:** {source_metadata['url']}")
1524
+ response_lines.append("")
1525
+
1526
+ # Add file info if available
1527
+ if source_metadata.get("size"):
1528
+ response_lines.append(f"**Size:** {source_metadata['size']} bytes")
1529
+ if source_metadata.get("language"):
1530
+ response_lines.append(f"**Language:** {source_metadata['language']}")
1531
+
1532
+ response_lines.extend(["", "## Content", ""])
1533
+
1534
+ # Add code block with language hint
1535
+ language = source_metadata.get("language", "").lower() or "text"
1536
+ response_lines.append(f"```{language}")
1537
+ response_lines.append(content)
1538
+ response_lines.append("```")
1539
+
1540
+ elif source_type == "documentation":
1541
+ url = source_metadata.get("url", source_identifier)
1542
+ title = source_metadata.get("title", "Documentation")
1543
+
1544
+ response_lines.extend([
1545
+ f"# Documentation: {title}",
1546
+ f"**URL:** {url}",
1547
+ ""
1548
+ ])
1549
+
1550
+ if source_metadata.get("last_updated"):
1551
+ response_lines.append(f"**Last Updated:** {source_metadata['last_updated']}")
1552
+ response_lines.append("")
1553
+
1554
+ response_lines.extend(["## Content", "", content])
1555
+
1556
+ else:
1557
+ # Generic format for unknown source types
1558
+ response_lines.extend([
1559
+ f"# Source Content",
1560
+ f"**Type:** {source_type}",
1561
+ f"**Identifier:** {source_identifier}",
1562
+ "",
1563
+ "## Content",
1564
+ "",
1565
+ content
1566
+ ])
1567
+
1568
+ return [TextContent(type="text", text="\n".join(response_lines))]
1569
+
1570
+ except APIError as e:
1571
+ logger.error(f"API Error reading source content: {e}")
1572
+ if e.status_code == 403 or "free tier limit" in str(e).lower():
1573
+ return [TextContent(
1574
+ type="text",
1575
+ text=f"❌ {str(e)}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited access."
1576
+ )]
1577
+ else:
1578
+ return [TextContent(type="text", text=f"❌ {str(e)}")]
1579
+ except Exception as e:
1580
+ logger.error(f"Error reading source content: {e}")
1581
+ return [TextContent(
1582
+ type="text",
1583
+ text=f"❌ Error reading source content: {str(e)}"
1584
+ )]
1585
+
1391
1586
  @mcp.tool()
1392
1587
  async def visualize_codebase(
1393
1588
  repository: str
@@ -0,0 +1,177 @@
1
+ """
2
+ Setup utilities for NIA MCP Server configuration
3
+ """
4
+ import os
5
+ import json
6
+ import platform
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Dict, Optional, Any
10
+
11
+
12
+ def find_mcp_config_path(ide: str = "cursor") -> Path:
13
+ """
14
+ Find the MCP configuration file path based on OS and IDE.
15
+
16
+ Args:
17
+ ide: IDE to configure (cursor, vscode, continue)
18
+
19
+ Returns:
20
+ Path to the MCP configuration file
21
+ """
22
+ system = platform.system()
23
+ home = Path.home()
24
+
25
+ if ide == "cursor":
26
+ if system == "Darwin": # macOS
27
+ return home / ".cursor" / "mcp.json"
28
+ elif system == "Windows":
29
+ appdata = os.environ.get("APPDATA", home / "AppData" / "Roaming")
30
+ return Path(appdata) / "Cursor" / "mcp.json"
31
+ else: # Linux and others
32
+ return home / ".config" / "cursor" / "mcp.json"
33
+
34
+ elif ide == "vscode":
35
+ # VS Code uses different config locations
36
+ if system == "Darwin":
37
+ return home / "Library" / "Application Support" / "Code" / "User" / "mcp.json"
38
+ elif system == "Windows":
39
+ appdata = os.environ.get("APPDATA", home / "AppData" / "Roaming")
40
+ return Path(appdata) / "Code" / "User" / "mcp.json"
41
+ else:
42
+ return home / ".config" / "Code" / "User" / "mcp.json"
43
+
44
+ elif ide == "continue":
45
+ # Continue.dev uses .continue directory
46
+ return home / ".continue" / "config.json"
47
+
48
+ else:
49
+ raise ValueError(f"Unsupported IDE: {ide}")
50
+
51
+
52
+ def backup_config(config_path: Path) -> Optional[Path]:
53
+ """
54
+ Create a backup of existing configuration file.
55
+
56
+ Args:
57
+ config_path: Path to the configuration file
58
+
59
+ Returns:
60
+ Path to the backup file if created, None otherwise
61
+ """
62
+ if config_path.exists():
63
+ backup_path = config_path.with_suffix(".json.backup")
64
+ # If backup already exists, add timestamp
65
+ if backup_path.exists():
66
+ import time
67
+ timestamp = int(time.time())
68
+ backup_path = config_path.with_suffix(f".json.backup.{timestamp}")
69
+
70
+ shutil.copy2(config_path, backup_path)
71
+ return backup_path
72
+ return None
73
+
74
+
75
+ def create_nia_config(api_key: str) -> Dict[str, Any]:
76
+ """
77
+ Create NIA MCP server configuration.
78
+
79
+ Args:
80
+ api_key: NIA API key
81
+
82
+ Returns:
83
+ Dictionary with NIA server configuration
84
+ """
85
+ return {
86
+ "command": "pipx",
87
+ "args": ["run", "nia-mcp-server"],
88
+ "env": {
89
+ "NIA_API_KEY": api_key,
90
+ "NIA_API_URL": "https://apigcp.trynia.ai/"
91
+ }
92
+ }
93
+
94
+
95
+ def update_mcp_config(config_path: Path, api_key: str) -> bool:
96
+ """
97
+ Update or create MCP configuration file with NIA server.
98
+
99
+ Args:
100
+ config_path: Path to the MCP configuration file
101
+ api_key: NIA API key
102
+
103
+ Returns:
104
+ True if successful, False otherwise
105
+ """
106
+ try:
107
+ # Ensure directory exists
108
+ config_path.parent.mkdir(parents=True, exist_ok=True)
109
+
110
+ # Load existing config or create new one
111
+ if config_path.exists():
112
+ with open(config_path, 'r') as f:
113
+ config = json.load(f)
114
+ else:
115
+ config = {}
116
+
117
+ # Ensure mcpServers section exists
118
+ if "mcpServers" not in config:
119
+ config["mcpServers"] = {}
120
+
121
+ # Add or update NIA server configuration
122
+ config["mcpServers"]["nia"] = create_nia_config(api_key)
123
+
124
+ # Write updated configuration
125
+ with open(config_path, 'w') as f:
126
+ json.dump(config, f, indent=2)
127
+
128
+ return True
129
+
130
+ except Exception as e:
131
+ print(f"❌ Error updating configuration: {e}")
132
+ return False
133
+
134
+
135
+ def setup_mcp_config(api_key: str, ide: str = "cursor") -> bool:
136
+ """
137
+ Main setup function to configure NIA MCP Server.
138
+
139
+ Args:
140
+ api_key: NIA API key
141
+ ide: IDE to configure
142
+
143
+ Returns:
144
+ True if successful, False otherwise
145
+ """
146
+ print(f"\n🚀 Setting up NIA MCP Server for {ide.title()}...\n")
147
+
148
+ # Find config path
149
+ config_path = find_mcp_config_path(ide)
150
+ print(f"📍 Configuration path: {config_path}")
151
+
152
+ # Backup existing config
153
+ backup_path = backup_config(config_path)
154
+ if backup_path:
155
+ print(f"📦 Backed up existing config to: {backup_path}")
156
+
157
+ # Update configuration
158
+ if update_mcp_config(config_path, api_key):
159
+ print(f"\n✅ NIA MCP Server setup complete!")
160
+ print(f"\n📝 Configuration written to: {config_path}")
161
+ print(f"🔑 API Key: {api_key[:10]}...")
162
+
163
+ print("\n📌 Next steps:")
164
+ print(f" 1. Restart {ide.title()} to load the NIA MCP server")
165
+ print(f" 2. Test with: \"Claude, list my repositories\"")
166
+ print(f" 3. Get started: \"Claude, index https://github.com/owner/repo\"")
167
+
168
+ print("\n💡 Learn more:")
169
+ print(" - Documentation: https://docs.trynia.ai")
170
+ print(" - Get help: https://discord.gg/BBSwUMrrfn")
171
+
172
+ return True
173
+ else:
174
+ print(f"\n❌ Setup failed. Please check the error messages above.")
175
+ if backup_path:
176
+ print(f" Your original config is safe at: {backup_path}")
177
+ return False
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: nia-mcp-server
3
+ Version: 1.0.13
4
+ Summary: Nia Knowledge Agent
5
+ Project-URL: Homepage, https://trynia.ai
6
+ Project-URL: Documentation, https://docs.trynia.ai
7
+ Author-email: Nia Team <founders@nozomio.com>
8
+ License-Expression: AGPL-3.0
9
+ License-File: LICENSE
10
+ Keywords: ai,codebase,mcp,nia,search
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Requires-Dist: httpx>=0.24.0
22
+ Requires-Dist: mcp>=0.1.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # NIA MCP Server
28
+
29
+ The NIA MCP Server enables AI assistants like Claude to search and understand your indexed codebases through the Model Context Protocol (MCP).
30
+
31
+ ## Quick Start
32
+
33
+ ### Automatic Setup (Recommended) ✨
34
+
35
+ Get your API key from [https://trynia.ai/api-keys](https://trynia.ai/api-keys) and run:
36
+
37
+ ```bash
38
+ pipx run nia-mcp-server setup YOUR_API_KEY
39
+ ```
@@ -1,17 +1,19 @@
1
- nia_mcp_server/__init__.py,sha256=PIwoKKoMquY_o5GY6__kwKO3_G2yz-zYewTJRWJc9W0,85
2
- nia_mcp_server/__main__.py,sha256=XY11ESL4hctu-BBgtPATFZyd1o-O7wE7y-UOSoNs-hw,152
3
- nia_mcp_server/api_client.py,sha256=Ix-GB8tw2FCALqkwzjIQySeeGkY-PqnHX-n3UQBa5sk,24943
1
+ nia_mcp_server/__init__.py,sha256=meeT4feLJfGvMAng90TkSl96ykJI_siO0HtnR-5RR5w,85
2
+ nia_mcp_server/__main__.py,sha256=YQSpFtDeKp18r8mKr084cHnRFV4416_EKCu9FTM8_ik,394
3
+ nia_mcp_server/api_client.py,sha256=6QFUg6jI9m0EAMrGm-HEAU5uYsUUxzhRau0iLCW9fpU,25815
4
+ nia_mcp_server/cli.py,sha256=32VSPNIocXtDgVBDZNZsxvj3kytBn54_a1pIE84vOdY,1834
4
5
  nia_mcp_server/profiles.py,sha256=2DD8PFRr5Ij4IK4sPUz0mH8aKjkrEtkKLC1R0iki2bA,7221
5
6
  nia_mcp_server/project_init.py,sha256=T0-ziJhofL4L8APwnM43BLhxtlmOHaYH-V9PF2yXLw4,7138
6
7
  nia_mcp_server/rule_transformer.py,sha256=wCxoQ1Kl_rI9mUFnh9kG5iCXYU4QInrmFQOReZfAFVo,11000
7
- nia_mcp_server/server.py,sha256=cyPv_Za4HEUj00QCLeeadcqqmSWRzVUXBrttCqXHhqk,66799
8
+ nia_mcp_server/server.py,sha256=CuXj-YWd4QHhhE5v_hEGHWGalO3whVlFUOMK359vqgs,75827
9
+ nia_mcp_server/setup.py,sha256=nJXVY8NHGtWROtoH8DW-3uOgyuPs4F9dW0cNhcbCLrM,5355
8
10
  nia_mcp_server/assets/rules/claude_rules.md,sha256=HNL5GJMUbFxSpNbIAJUQWqAywjMl4lf530I1in69aNY,7380
9
11
  nia_mcp_server/assets/rules/cursor_rules.md,sha256=hd6lhzNrK1ULQUYIEVeOnyKnuLKq4hmwZPbMqGUI1Lk,1720
10
12
  nia_mcp_server/assets/rules/nia_rules.md,sha256=mvdYrkoiRgxeROhtnRXCV53TX5B9wqLiCJ6oYTqSPfY,6345
11
13
  nia_mcp_server/assets/rules/vscode_rules.md,sha256=fqn4aJO_bhftaCGkVoquruQHf3EaREQJQWHXq6a4FOk,6967
12
14
  nia_mcp_server/assets/rules/windsurf_rules.md,sha256=PzU2as5gaiVsV6PAzg8T_-GR7VCyRQGMjAHcSzYF_ms,3354
13
- nia_mcp_server-1.0.11.dist-info/METADATA,sha256=4okbTVyaTi-09qZC2su-R1iGeqqB4fGPjy6ZbpYayZQ,6767
14
- nia_mcp_server-1.0.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- nia_mcp_server-1.0.11.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
16
- nia_mcp_server-1.0.11.dist-info/licenses/LICENSE,sha256=IrdVKi3bsiB2MTLM26MltBRpwyNi-8P6Cy0EnmAN76A,1557
17
- nia_mcp_server-1.0.11.dist-info/RECORD,,
15
+ nia_mcp_server-1.0.13.dist-info/METADATA,sha256=uUDkIiPkBZLCuZk8uqUUo0aCIF-dw8iZKuXGqHqQwe4,1324
16
+ nia_mcp_server-1.0.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ nia_mcp_server-1.0.13.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
18
+ nia_mcp_server-1.0.13.dist-info/licenses/LICENSE,sha256=IrdVKi3bsiB2MTLM26MltBRpwyNi-8P6Cy0EnmAN76A,1557
19
+ nia_mcp_server-1.0.13.dist-info/RECORD,,
@@ -1,246 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: nia-mcp-server
3
- Version: 1.0.11
4
- Summary: Nia Knowledge Agent
5
- Project-URL: Homepage, https://trynia.ai
6
- Project-URL: Documentation, https://docs.trynia.ai
7
- Author-email: Nia Team <founders@nozomio.com>
8
- License-Expression: AGPL-3.0
9
- License-File: LICENSE
10
- Keywords: ai,codebase,mcp,nia,search
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.12
20
- Requires-Python: >=3.8
21
- Requires-Dist: httpx>=0.24.0
22
- Requires-Dist: mcp>=0.1.0
23
- Requires-Dist: pydantic>=2.0.0
24
- Requires-Dist: python-dotenv>=1.0.0
25
- Description-Content-Type: text/markdown
26
-
27
- # NIA MCP Server
28
-
29
- The NIA MCP Server enables AI assistants like Claude to search and understand your indexed codebases through the Model Context Protocol (MCP).
30
-
31
- ## Quick Start
32
-
33
- ### 1. Get your NIA API Key
34
-
35
- Sign up and get your API key at [https://trynia.ai/api-keys](https://trynia.ai/api-keys)
36
-
37
- ### 2. Install via pip
38
-
39
- ```bash
40
- pip install nia-mcp-server
41
- ```
42
-
43
- ### 3. Configure with Claude Desktop
44
-
45
- Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
46
-
47
- ```json
48
- {
49
- "mcpServers": {
50
- "nia": {
51
- "command": "nia-mcp-server",
52
- "env": {
53
- "NIA_API_KEY": "your-api-key-here"
54
- }
55
- }
56
- }
57
- }
58
- ```
59
-
60
- ### 4. Restart Claude Desktop
61
-
62
- That's it! You can now ask Claude to index and search codebases.
63
-
64
- ## Usage Examples
65
-
66
- ### Index a repository
67
- ```
68
- Claude, please index https://github.com/facebook/react
69
- ```
70
-
71
- ### Index documentation
72
- ```
73
- Index the documentation at https://docs.python.org
74
- ```
75
-
76
- ### Search across everything
77
- ```
78
- How does async/await work? Search both my code and documentation.
79
- ```
80
-
81
- ### Search only repositories
82
- ```
83
- Find the authentication logic in my repositories
84
- ```
85
-
86
- ### Search only documentation
87
- ```
88
- What are the best practices for error handling according to the docs?
89
- ```
90
-
91
- ### Search and index new content
92
- ```
93
- Find the best RAG implementations out there
94
- ```
95
- Claude will:
96
- 1. Use the `nia_web_search` tool to find trending RAG repos
97
- 2. Show you the results with summaries
98
- 3. Prompt you to index the ones you want
99
- 4. You say "Index the first two" and it indexes them!
100
-
101
- ```
102
- What are the hottest new Rust web frameworks this week?
103
- ```
104
- Claude searches trending repos and guides you through indexing them.
105
-
106
- Advanced search examples:
107
- ```
108
- Find GitHub repos similar to langchain/langchain
109
-
110
- Search for AI papers published in the last 30 days
111
-
112
- What are the trending machine learning frameworks this month?
113
- ```
114
-
115
- ### Deep research questions
116
- ```
117
- Compare the top 3 vector databases for RAG applications
118
-
119
- What are the pros and cons of different LLM orchestration frameworks?
120
-
121
- Research the latest developments in AI agent architectures
122
- ```
123
-
124
- ### List your resources
125
- ```
126
- Show me all my indexed repositories and documentation
127
- ```
128
-
129
- ## Available Tools
130
-
131
- ### Search & Research
132
- - **`nia_web_search`** - AI-powered search of repositories, documentation, and content
133
- - Finds trending GitHub repos, relevant documentation, and more
134
- - Returns structured results that guide you to index the best content
135
- - Advanced options:
136
- - `category`: Filter by type (github, company, research paper, news, etc.)
137
- - `days_back`: Find content from the last N days (great for trending)
138
- - `find_similar_to`: Search for content similar to a given URL
139
- - Built into NIA's advanced search capabilities
140
-
141
- - **`nia_deep_research_agent`** - Multi-step AI research for complex questions
142
- - Best for comparative analysis, comprehensive overviews
143
- - Returns structured data with citations
144
- - Examples: "Compare top RAG frameworks", "Analyze trends in AI safety"
145
-
146
- ### Repository Management
147
- - **`index_repository`** - Index a GitHub repository
148
- - **`list_repositories`** - List all indexed repositories
149
- - **`check_repository_status`** - Check repository indexing progress
150
- - **`delete_repository`** - Remove an indexed repository
151
-
152
- ### Documentation Management
153
- - **`index_documentation`** - Index documentation or any website
154
- - **`list_documentation`** - List all indexed documentation sources
155
- - **`check_documentation_status`** - Check documentation indexing progress
156
- - **`delete_documentation`** - Remove indexed documentation
157
-
158
- ### Unified Search
159
- - **`search_codebase`** - Search across repositories and/or documentation
160
- - `search_mode`: "repositories", "sources", or "unified" (default)
161
- - Automatically searches all indexed content if not specified
162
-
163
- ## Other MCP Clients
164
-
165
- ### Continue.dev
166
-
167
- Add to your `~/.continue/config.json`:
168
-
169
- ```json
170
- {
171
- "models": [...],
172
- "mcpServers": [
173
- {
174
- "name": "nia",
175
- "command": "nia-mcp-server",
176
- "env": {
177
- "NIA_API_KEY": "your-api-key-here"
178
- }
179
- }
180
- ]
181
- }
182
- ```
183
-
184
- ### VS Code Cline
185
-
186
- Add to your Cline settings:
187
-
188
- ```json
189
- {
190
- "mcpServers": {
191
- "nia": {
192
- "command": "nia-mcp-server",
193
- "env": {
194
- "NIA_API_KEY": "your-api-key-here"
195
- }
196
- }
197
- }
198
- }
199
- ```
200
-
201
- ## Environment Variables
202
-
203
- - `NIA_API_KEY` (required) - Your NIA API key
204
- - `NIA_API_URL` (optional) - API endpoint (defaults to https://apigcp.trynia.ai)
205
-
206
- ## Pricing
207
-
208
- NIA offers simple, transparent pricing:
209
-
210
- - **Free Tier**: Limited usage, public repos only
211
- - **Pro**: Unlimited API calls, private repos, advanced features
212
-
213
- See [https://trynia.ai/pricing](https://trynia.ai/pricing) for details.
214
-
215
- ## Features
216
-
217
- - 🔍 **Unified Search** - Search across code AND documentation seamlessly
218
- - 📚 **Documentation Indexing** - Index any website or documentation
219
- - 🚀 **Fast Indexing** - Index repositories and websites quickly
220
- - 🔒 **Private Repos** - Support for private repositories (Pro)
221
- - 📊 **Smart Understanding** - AI-powered code and content comprehension
222
- - 🌐 **Works Everywhere** - Any MCP-compatible client
223
-
224
- ## Troubleshooting
225
-
226
- ### "No API key provided"
227
- Make sure `NIA_API_KEY` is set in your MCP client configuration.
228
-
229
- ### "Invalid API key"
230
- Check your API key at [https://trynia.ai/api-keys](https://trynia.ai/api-keys)
231
-
232
- ### "Rate limit exceeded"
233
- You've hit your monthly limit. Upgrade at [https://trynia.ai/billing](https://trynia.ai/billing)
234
-
235
- ### Repository not indexing
236
- Large repositories can take a few minutes. Use `check_repository_status` to monitor progress.
237
-
238
- ## Support
239
-
240
- - Documentation: [https://docs.trynia.ai](https://docs.trynia.ai)
241
- - Discord: [https://discord.gg/BBSwUMrrfn](https://discord.gg/BBSwUMrrfn)
242
- - Email: support@trynia.ai
243
-
244
- ## License
245
-
246
- MIT License - see LICENSE file for details.