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.
- nia_mcp_server/__init__.py +1 -1
- nia_mcp_server/api_client.py +36 -4
- nia_mcp_server/server.py +236 -32
- {nia_mcp_server-1.0.10.dist-info → nia_mcp_server-1.0.12.dist-info}/METADATA +1 -1
- {nia_mcp_server-1.0.10.dist-info → nia_mcp_server-1.0.12.dist-info}/RECORD +8 -8
- {nia_mcp_server-1.0.10.dist-info → nia_mcp_server-1.0.12.dist-info}/WHEEL +0 -0
- {nia_mcp_server-1.0.10.dist-info → nia_mcp_server-1.0.12.dist-info}/entry_points.txt +0 -0
- {nia_mcp_server-1.0.10.dist-info → nia_mcp_server-1.0.12.dist-info}/licenses/LICENSE +0 -0
nia_mcp_server/__init__.py
CHANGED
nia_mcp_server/api_client.py
CHANGED
|
@@ -75,7 +75,7 @@ class NIAApiClient:
|
|
|
75
75
|
"lifetime limit",
|
|
76
76
|
"no chat credits",
|
|
77
77
|
"free api requests",
|
|
78
|
-
"
|
|
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
|
-
"
|
|
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 "
|
|
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[:
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
if
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 "
|
|
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
|
|
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[:
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
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 "
|
|
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,17 +1,17 @@
|
|
|
1
|
-
nia_mcp_server/__init__.py,sha256=
|
|
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=
|
|
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=
|
|
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.
|
|
14
|
-
nia_mcp_server-1.0.
|
|
15
|
-
nia_mcp_server-1.0.
|
|
16
|
-
nia_mcp_server-1.0.
|
|
17
|
-
nia_mcp_server-1.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|