nia-mcp-server 1.0.16__tar.gz → 1.0.21__tar.gz
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-1.0.16 → nia_mcp_server-1.0.21}/PKG-INFO +1 -1
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/pyproject.toml +1 -1
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/__init__.py +1 -1
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/api_client.py +211 -9
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/server.py +1273 -69
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/.gitignore +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/ARCHITECTURE.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/LICENSE +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/README.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/nia_analytics.log +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/nia_mcp_server.log +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/__main__.py +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/claude_rules.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/cursor_rules.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/nia_rules.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/vscode_rules.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/windsurf_rules.md +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/cli.py +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/profiles.py +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/project_init.py +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/rule_transformer.py +0 -0
- {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/setup.py +0 -0
|
@@ -28,7 +28,7 @@ class NIAApiClient:
|
|
|
28
28
|
self.client = httpx.AsyncClient(
|
|
29
29
|
headers={
|
|
30
30
|
"Authorization": f"Bearer {api_key}",
|
|
31
|
-
"User-Agent": "nia-mcp-server/1.0.
|
|
31
|
+
"User-Agent": "nia-mcp-server/1.0.21",
|
|
32
32
|
"Content-Type": "application/json"
|
|
33
33
|
},
|
|
34
34
|
timeout=720.0 # 12 minute timeout for deep research operations
|
|
@@ -176,7 +176,10 @@ class NIAApiClient:
|
|
|
176
176
|
# Regular repo URL - extract owner/repo
|
|
177
177
|
parts = clean_url.rstrip('/').split('/')
|
|
178
178
|
if len(parts) >= 2:
|
|
179
|
-
repo_name = parts[-1]
|
|
179
|
+
repo_name = parts[-1]
|
|
180
|
+
# Remove .git suffix if present
|
|
181
|
+
if repo_name.endswith('.git'):
|
|
182
|
+
repo_name = repo_name[:-4]
|
|
180
183
|
repository_path = f"{parts[-2]}/{repo_name}"
|
|
181
184
|
else:
|
|
182
185
|
repository_path = repo_url
|
|
@@ -549,15 +552,23 @@ class NIAApiClient:
|
|
|
549
552
|
for repo in repositories:
|
|
550
553
|
repo_list.append({"repository": repo})
|
|
551
554
|
|
|
552
|
-
# Build data source list
|
|
555
|
+
# Build data source list
|
|
553
556
|
source_list = []
|
|
554
557
|
if data_sources:
|
|
555
|
-
for
|
|
556
|
-
# Handle
|
|
557
|
-
|
|
558
|
-
|
|
558
|
+
for source in data_sources:
|
|
559
|
+
# Handle flexible identifier formats:
|
|
560
|
+
# 1. String directly (display_name, URL, or source_id) - NEW
|
|
561
|
+
# 2. Dict with "source_id" (backwards compatible)
|
|
562
|
+
# 3. Dict with "identifier" (new format)
|
|
563
|
+
if isinstance(source, str):
|
|
564
|
+
# Pass string directly - backend will resolve it
|
|
565
|
+
source_list.append(source)
|
|
566
|
+
elif isinstance(source, dict):
|
|
567
|
+
# Keep dict format as-is (backwards compatible)
|
|
568
|
+
source_list.append(source)
|
|
559
569
|
else:
|
|
560
|
-
|
|
570
|
+
# Convert other types to string
|
|
571
|
+
source_list.append(str(source))
|
|
561
572
|
|
|
562
573
|
# Validate at least one source
|
|
563
574
|
if not repo_list and not source_list:
|
|
@@ -685,4 +696,195 @@ class NIAApiClient:
|
|
|
685
696
|
except httpx.HTTPStatusError as e:
|
|
686
697
|
raise self._handle_api_error(e)
|
|
687
698
|
except Exception as e:
|
|
688
|
-
raise APIError(f"Failed to get source content: {str(e)}")
|
|
699
|
+
raise APIError(f"Failed to get source content: {str(e)}")
|
|
700
|
+
|
|
701
|
+
async def index_local_filesystem(
|
|
702
|
+
self,
|
|
703
|
+
directory_path: str,
|
|
704
|
+
inclusion_patterns: List[str] = None,
|
|
705
|
+
exclusion_patterns: List[str] = None,
|
|
706
|
+
max_file_size_mb: int = 50
|
|
707
|
+
) -> Dict[str, Any]:
|
|
708
|
+
"""Index a local filesystem directory."""
|
|
709
|
+
try:
|
|
710
|
+
payload = {
|
|
711
|
+
"directory_path": directory_path,
|
|
712
|
+
"inclusion_patterns": inclusion_patterns or [],
|
|
713
|
+
"exclusion_patterns": exclusion_patterns or [],
|
|
714
|
+
"max_file_size_mb": max_file_size_mb
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
response = await self.client.post(
|
|
718
|
+
f"{self.base_url}/v2/local-filesystem",
|
|
719
|
+
json=payload
|
|
720
|
+
)
|
|
721
|
+
response.raise_for_status()
|
|
722
|
+
return response.json()
|
|
723
|
+
|
|
724
|
+
except httpx.HTTPStatusError as e:
|
|
725
|
+
raise self._handle_api_error(e)
|
|
726
|
+
except Exception as e:
|
|
727
|
+
raise APIError(f"Failed to index local filesystem: {str(e)}")
|
|
728
|
+
|
|
729
|
+
async def scan_local_filesystem(
|
|
730
|
+
self,
|
|
731
|
+
directory_path: str,
|
|
732
|
+
inclusion_patterns: List[str] = None,
|
|
733
|
+
exclusion_patterns: List[str] = None,
|
|
734
|
+
max_file_size_mb: int = 50
|
|
735
|
+
) -> Dict[str, Any]:
|
|
736
|
+
"""Scan a local filesystem directory to preview what would be indexed."""
|
|
737
|
+
try:
|
|
738
|
+
payload = {
|
|
739
|
+
"directory_path": directory_path,
|
|
740
|
+
"inclusion_patterns": inclusion_patterns or [],
|
|
741
|
+
"exclusion_patterns": exclusion_patterns or [],
|
|
742
|
+
"max_file_size_mb": max_file_size_mb
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
response = await self.client.post(
|
|
746
|
+
f"{self.base_url}/v2/local-filesystem/scan",
|
|
747
|
+
json=payload
|
|
748
|
+
)
|
|
749
|
+
response.raise_for_status()
|
|
750
|
+
return response.json()
|
|
751
|
+
|
|
752
|
+
except httpx.HTTPStatusError as e:
|
|
753
|
+
raise self._handle_api_error(e)
|
|
754
|
+
except Exception as e:
|
|
755
|
+
raise APIError(f"Failed to scan local filesystem: {str(e)}")
|
|
756
|
+
|
|
757
|
+
async def check_local_filesystem_status(self, source_id: str) -> Dict[str, Any]:
|
|
758
|
+
"""Check the indexing status of a local filesystem source."""
|
|
759
|
+
try:
|
|
760
|
+
response = await self.client.get(
|
|
761
|
+
f"{self.base_url}/v2/local-filesystem/{source_id}"
|
|
762
|
+
)
|
|
763
|
+
response.raise_for_status()
|
|
764
|
+
return response.json()
|
|
765
|
+
|
|
766
|
+
except httpx.HTTPStatusError as e:
|
|
767
|
+
raise self._handle_api_error(e)
|
|
768
|
+
except Exception as e:
|
|
769
|
+
raise APIError(f"Failed to check local filesystem status: {str(e)}")
|
|
770
|
+
|
|
771
|
+
# ========================================================================
|
|
772
|
+
# CHROMA PACKAGE SEARCH METHODS
|
|
773
|
+
# ========================================================================
|
|
774
|
+
|
|
775
|
+
async def package_search_grep(
|
|
776
|
+
self,
|
|
777
|
+
registry: str,
|
|
778
|
+
package_name: str,
|
|
779
|
+
pattern: str,
|
|
780
|
+
version: Optional[str] = None,
|
|
781
|
+
language: Optional[str] = None,
|
|
782
|
+
filename_sha256: Optional[str] = None,
|
|
783
|
+
a: Optional[int] = None,
|
|
784
|
+
b: Optional[int] = None,
|
|
785
|
+
c: Optional[int] = None,
|
|
786
|
+
head_limit: Optional[int] = None,
|
|
787
|
+
output_mode: str = "content"
|
|
788
|
+
) -> Dict[str, Any]:
|
|
789
|
+
"""Execute grep search on package source code via Chroma."""
|
|
790
|
+
try:
|
|
791
|
+
payload = {
|
|
792
|
+
"registry": registry,
|
|
793
|
+
"package_name": package_name,
|
|
794
|
+
"pattern": pattern,
|
|
795
|
+
"version": version,
|
|
796
|
+
"language": language,
|
|
797
|
+
"filename_sha256": filename_sha256,
|
|
798
|
+
"a": a,
|
|
799
|
+
"b": b,
|
|
800
|
+
"c": c,
|
|
801
|
+
"head_limit": head_limit,
|
|
802
|
+
"output_mode": output_mode
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
# Remove None values
|
|
806
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
807
|
+
|
|
808
|
+
response = await self.client.post(
|
|
809
|
+
f"{self.base_url}/v2/package-search/grep",
|
|
810
|
+
json=payload
|
|
811
|
+
)
|
|
812
|
+
response.raise_for_status()
|
|
813
|
+
return response.json()
|
|
814
|
+
|
|
815
|
+
except httpx.HTTPStatusError as e:
|
|
816
|
+
raise self._handle_api_error(e)
|
|
817
|
+
except Exception as e:
|
|
818
|
+
raise APIError(f"Failed to search package with grep: {str(e)}")
|
|
819
|
+
|
|
820
|
+
async def package_search_hybrid(
|
|
821
|
+
self,
|
|
822
|
+
registry: str,
|
|
823
|
+
package_name: str,
|
|
824
|
+
semantic_queries: List[str],
|
|
825
|
+
version: Optional[str] = None,
|
|
826
|
+
filename_sha256: Optional[str] = None,
|
|
827
|
+
pattern: Optional[str] = None,
|
|
828
|
+
language: Optional[str] = None
|
|
829
|
+
) -> Dict[str, Any]:
|
|
830
|
+
"""Execute hybrid semantic search on package source code via Chroma."""
|
|
831
|
+
try:
|
|
832
|
+
payload = {
|
|
833
|
+
"registry": registry,
|
|
834
|
+
"package_name": package_name,
|
|
835
|
+
"semantic_queries": semantic_queries,
|
|
836
|
+
"version": version,
|
|
837
|
+
"filename_sha256": filename_sha256,
|
|
838
|
+
"pattern": pattern,
|
|
839
|
+
"language": language
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
# Remove None values
|
|
843
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
844
|
+
|
|
845
|
+
response = await self.client.post(
|
|
846
|
+
f"{self.base_url}/v2/package-search/hybrid",
|
|
847
|
+
json=payload
|
|
848
|
+
)
|
|
849
|
+
response.raise_for_status()
|
|
850
|
+
return response.json()
|
|
851
|
+
|
|
852
|
+
except httpx.HTTPStatusError as e:
|
|
853
|
+
raise self._handle_api_error(e)
|
|
854
|
+
except Exception as e:
|
|
855
|
+
raise APIError(f"Failed to search package with hybrid search: {str(e)}")
|
|
856
|
+
|
|
857
|
+
async def package_search_read_file(
|
|
858
|
+
self,
|
|
859
|
+
registry: str,
|
|
860
|
+
package_name: str,
|
|
861
|
+
filename_sha256: str,
|
|
862
|
+
start_line: int,
|
|
863
|
+
end_line: int,
|
|
864
|
+
version: Optional[str] = None
|
|
865
|
+
) -> Dict[str, Any]:
|
|
866
|
+
"""Read specific lines from a package file via Chroma."""
|
|
867
|
+
try:
|
|
868
|
+
payload = {
|
|
869
|
+
"registry": registry,
|
|
870
|
+
"package_name": package_name,
|
|
871
|
+
"filename_sha256": filename_sha256,
|
|
872
|
+
"start_line": start_line,
|
|
873
|
+
"end_line": end_line,
|
|
874
|
+
"version": version
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
# Remove None values
|
|
878
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
879
|
+
|
|
880
|
+
response = await self.client.post(
|
|
881
|
+
f"{self.base_url}/v2/package-search/read-file",
|
|
882
|
+
json=payload
|
|
883
|
+
)
|
|
884
|
+
response.raise_for_status()
|
|
885
|
+
return response.json()
|
|
886
|
+
|
|
887
|
+
except httpx.HTTPStatusError as e:
|
|
888
|
+
raise self._handle_api_error(e)
|
|
889
|
+
except Exception as e:
|
|
890
|
+
raise APIError(f"Failed to read package file: {str(e)}")
|