nia-mcp-server 1.0.10__py3-none-any.whl → 1.0.12__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.10"
5
+ __version__ = "1.0.12"
@@ -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
  ):
@@ -404,7 +404,9 @@ class NIAApiClient:
404
404
  max_age: int = None,
405
405
  only_main_content: bool = True,
406
406
  wait_for: int = None,
407
- include_screenshot: bool = None
407
+ include_screenshot: bool = None,
408
+ check_llms_txt: bool = None,
409
+ llms_txt_strategy: str = None
408
410
  ) -> Dict[str, Any]:
409
411
  """Create a new documentation/web data source."""
410
412
  try:
@@ -425,6 +427,10 @@ class NIAApiClient:
425
427
  payload["wait_for"] = wait_for
426
428
  if include_screenshot is not None:
427
429
  payload["include_screenshot"] = include_screenshot
430
+ if check_llms_txt is not None:
431
+ payload["check_llms_txt"] = check_llms_txt
432
+ if llms_txt_strategy is not None:
433
+ payload["llms_txt_strategy"] = llms_txt_strategy
428
434
 
429
435
  response = await self.client.post(
430
436
  f"{self.base_url}/v2/data-sources",
@@ -617,4 +623,30 @@ class NIAApiClient:
617
623
  except httpx.HTTPStatusError as e:
618
624
  raise self._handle_api_error(e)
619
625
  except Exception as e:
620
- 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/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}")
@@ -508,7 +575,9 @@ async def index_documentation(
508
575
  max_age: Optional[int] = None,
509
576
  only_main_content: Optional[bool] = True,
510
577
  wait_for: Optional[int] = None,
511
- include_screenshot: Optional[bool] = None
578
+ include_screenshot: Optional[bool] = None,
579
+ check_llms_txt: Optional[bool] = True,
580
+ llms_txt_strategy: Optional[str] = "prefer"
512
581
  ) -> List[TextContent]:
513
582
  """
514
583
  Index documentation or website for intelligent search.
@@ -521,6 +590,11 @@ async def index_documentation(
521
590
  only_main_content: Extract only main content (removes navigation, ads, etc.)
522
591
  wait_for: Time to wait for page to load in milliseconds (defaults to backend setting)
523
592
  include_screenshot: Whether to capture full page screenshots (defaults to backend setting)
593
+ check_llms_txt: Check for llms.txt file for curated documentation URLs (default: True)
594
+ llms_txt_strategy: How to use llms.txt if found:
595
+ - "prefer": Start with llms.txt URLs, then crawl additional pages if under limit
596
+ - "only": Only index URLs listed in llms.txt
597
+ - "ignore": Skip llms.txt check (traditional behavior)
524
598
 
525
599
  Returns:
526
600
  Status of the indexing operation
@@ -543,7 +617,9 @@ async def index_documentation(
543
617
  max_age=max_age,
544
618
  only_main_content=only_main_content,
545
619
  wait_for=wait_for,
546
- include_screenshot=include_screenshot
620
+ include_screenshot=include_screenshot,
621
+ check_llms_txt=check_llms_txt,
622
+ llms_txt_strategy=llms_txt_strategy
547
623
  )
548
624
 
549
625
  source_id = result.get("id")
@@ -569,7 +645,7 @@ async def index_documentation(
569
645
  logger.error(f"API Error indexing documentation: {e}")
570
646
  error_msg = f"❌ {str(e)}"
571
647
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
572
- 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."
573
649
  return [TextContent(type="text", text=error_msg)]
574
650
  except Exception as e:
575
651
  logger.error(f"Error indexing documentation: {e}")
@@ -628,7 +704,7 @@ async def list_documentation() -> List[TextContent]:
628
704
  logger.error(f"API Error listing documentation: {e}")
629
705
  error_msg = f"❌ {str(e)}"
630
706
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
631
- 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."
632
708
  return [TextContent(type="text", text=error_msg)]
633
709
  except Exception as e:
634
710
  logger.error(f"Error listing documentation: {e}")
@@ -694,7 +770,7 @@ async def check_documentation_status(source_id: str) -> List[TextContent]:
694
770
  logger.error(f"API Error checking documentation status: {e}")
695
771
  error_msg = f"❌ {str(e)}"
696
772
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
697
- 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."
698
774
  return [TextContent(type="text", text=error_msg)]
699
775
  except Exception as e:
700
776
  logger.error(f"Error checking documentation status: {e}")
@@ -733,7 +809,7 @@ async def delete_documentation(source_id: str) -> List[TextContent]:
733
809
  logger.error(f"API Error deleting documentation: {e}")
734
810
  error_msg = f"❌ {str(e)}"
735
811
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
736
- 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."
737
813
  return [TextContent(type="text", text=error_msg)]
738
814
  except Exception as e:
739
815
  logger.error(f"Error deleting documentation: {e}")
@@ -772,7 +848,7 @@ async def delete_repository(repository: str) -> List[TextContent]:
772
848
  logger.error(f"API Error deleting repository: {e}")
773
849
  error_msg = f"❌ {str(e)}"
774
850
  if e.status_code == 403 and "lifetime limit" in str(e).lower():
775
- 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."
776
852
  return [TextContent(type="text", text=error_msg)]
777
853
  except Exception as e:
778
854
  logger.error(f"Error deleting repository: {e}")
@@ -1024,7 +1100,7 @@ async def nia_web_search(
1024
1100
  except APIError as e:
1025
1101
  logger.error(f"API Error in web search: {e}")
1026
1102
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
1027
- if e.detail and "25 free API requests" in e.detail:
1103
+ if e.detail and "5 free API requests" in e.detail:
1028
1104
  return [TextContent(
1029
1105
  type="text",
1030
1106
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -1203,7 +1279,7 @@ async def nia_deep_research_agent(
1203
1279
  except APIError as e:
1204
1280
  logger.error(f"API Error in deep research: {e}")
1205
1281
  if e.status_code == 403 or "free tier limit" in str(e).lower() or "free api requests" in str(e).lower():
1206
- if e.detail and "25 free API requests" in e.detail:
1282
+ if e.detail and "5 free API requests" in e.detail:
1207
1283
  return [TextContent(
1208
1284
  type="text",
1209
1285
  text=f"❌ {e.detail}\n\n💡 Tip: Upgrade to Pro at https://trynia.ai/billing for unlimited API access."
@@ -1379,6 +1455,134 @@ async def initialize_project(
1379
1455
  "- The NIA MCP server is properly installed"
1380
1456
  )]
1381
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
+
1382
1586
  @mcp.tool()
1383
1587
  async def visualize_codebase(
1384
1588
  repository: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nia-mcp-server
3
- Version: 1.0.10
3
+ Version: 1.0.12
4
4
  Summary: Nia Knowledge Agent
5
5
  Project-URL: Homepage, https://trynia.ai
6
6
  Project-URL: Documentation, https://docs.trynia.ai
@@ -1,17 +1,17 @@
1
- nia_mcp_server/__init__.py,sha256=KyI9fMELt-32jL_i_v49z4TF3p3kOf7D_pfv62ZoxEU,85
1
+ nia_mcp_server/__init__.py,sha256=u5VyiO3beS875PBflORPjfQPUa57WihSs7aisJLlXkU,85
2
2
  nia_mcp_server/__main__.py,sha256=XY11ESL4hctu-BBgtPATFZyd1o-O7wE7y-UOSoNs-hw,152
3
- nia_mcp_server/api_client.py,sha256=_6Oh3ZTqNVCOQsQcV8ep9mHZ0giNbE_ht93pamU-PlI,24654
3
+ nia_mcp_server/api_client.py,sha256=6QFUg6jI9m0EAMrGm-HEAU5uYsUUxzhRau0iLCW9fpU,25815
4
4
  nia_mcp_server/profiles.py,sha256=2DD8PFRr5Ij4IK4sPUz0mH8aKjkrEtkKLC1R0iki2bA,7221
5
5
  nia_mcp_server/project_init.py,sha256=T0-ziJhofL4L8APwnM43BLhxtlmOHaYH-V9PF2yXLw4,7138
6
6
  nia_mcp_server/rule_transformer.py,sha256=wCxoQ1Kl_rI9mUFnh9kG5iCXYU4QInrmFQOReZfAFVo,11000
7
- nia_mcp_server/server.py,sha256=W3yyKiAsumVqVpS9rMiKtPhav8cXiw8VqG4DGuUOi0k,66246
7
+ nia_mcp_server/server.py,sha256=CuXj-YWd4QHhhE5v_hEGHWGalO3whVlFUOMK359vqgs,75827
8
8
  nia_mcp_server/assets/rules/claude_rules.md,sha256=HNL5GJMUbFxSpNbIAJUQWqAywjMl4lf530I1in69aNY,7380
9
9
  nia_mcp_server/assets/rules/cursor_rules.md,sha256=hd6lhzNrK1ULQUYIEVeOnyKnuLKq4hmwZPbMqGUI1Lk,1720
10
10
  nia_mcp_server/assets/rules/nia_rules.md,sha256=mvdYrkoiRgxeROhtnRXCV53TX5B9wqLiCJ6oYTqSPfY,6345
11
11
  nia_mcp_server/assets/rules/vscode_rules.md,sha256=fqn4aJO_bhftaCGkVoquruQHf3EaREQJQWHXq6a4FOk,6967
12
12
  nia_mcp_server/assets/rules/windsurf_rules.md,sha256=PzU2as5gaiVsV6PAzg8T_-GR7VCyRQGMjAHcSzYF_ms,3354
13
- nia_mcp_server-1.0.10.dist-info/METADATA,sha256=0AYn0fZjCz0tuB_vuCCpVuyRbr4Bi2nCgQkgXaZ77RY,6767
14
- nia_mcp_server-1.0.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- nia_mcp_server-1.0.10.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
16
- nia_mcp_server-1.0.10.dist-info/licenses/LICENSE,sha256=IrdVKi3bsiB2MTLM26MltBRpwyNi-8P6Cy0EnmAN76A,1557
17
- nia_mcp_server-1.0.10.dist-info/RECORD,,
13
+ nia_mcp_server-1.0.12.dist-info/METADATA,sha256=zpdgFJZ99Rm3AI_WUnBXg2aNCPem0HCDvHNuYf-JZDg,6767
14
+ nia_mcp_server-1.0.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ nia_mcp_server-1.0.12.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
16
+ nia_mcp_server-1.0.12.dist-info/licenses/LICENSE,sha256=IrdVKi3bsiB2MTLM26MltBRpwyNi-8P6Cy0EnmAN76A,1557
17
+ nia_mcp_server-1.0.12.dist-info/RECORD,,