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.

Files changed (22) hide show
  1. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/PKG-INFO +1 -1
  2. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/pyproject.toml +1 -1
  3. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/__init__.py +1 -1
  4. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/api_client.py +211 -9
  5. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/server.py +1273 -69
  6. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/.gitignore +0 -0
  7. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/ARCHITECTURE.md +0 -0
  8. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/LICENSE +0 -0
  9. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/README.md +0 -0
  10. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/nia_analytics.log +0 -0
  11. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/nia_mcp_server.log +0 -0
  12. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/__main__.py +0 -0
  13. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/claude_rules.md +0 -0
  14. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/cursor_rules.md +0 -0
  15. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/nia_rules.md +0 -0
  16. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/vscode_rules.md +0 -0
  17. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/assets/rules/windsurf_rules.md +0 -0
  18. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/cli.py +0 -0
  19. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/profiles.py +0 -0
  20. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/project_init.py +0 -0
  21. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/rule_transformer.py +0 -0
  22. {nia_mcp_server-1.0.16 → nia_mcp_server-1.0.21}/src/nia_mcp_server/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nia-mcp-server
3
- Version: 1.0.16
3
+ Version: 1.0.21
4
4
  Summary: Nia Knowledge Agent
5
5
  Project-URL: Homepage, https://trynia.ai
6
6
  Project-URL: Documentation, https://docs.trynia.ai
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nia-mcp-server"
7
- version = "1.0.16"
7
+ version = "1.0.21"
8
8
  description = "Nia Knowledge Agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -2,4 +2,4 @@
2
2
  NIA MCP Server - Proxy server for NIA Knowledge Agent
3
3
  """
4
4
 
5
- __version__ = "1.0.16"
5
+ __version__ = "1.0.21"
@@ -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.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].rstrip('.git') # Remove .git suffix
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 source_id in data_sources:
556
- # Handle both list of IDs and list of dicts
557
- if isinstance(source_id, dict):
558
- source_list.append(source_id)
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
- source_list.append({"source_id": source_id})
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)}")