nia-mcp-server 1.0.5__py3-none-any.whl → 1.0.6__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.5"
5
+ __version__ = "1.0.6"
@@ -137,7 +137,18 @@ class NIAApiClient:
137
137
  try:
138
138
  response = await self.client.get(f"{self.base_url}/v2/repositories")
139
139
  response.raise_for_status()
140
- return response.json()
140
+ data = response.json()
141
+
142
+ # Ensure we always return a list
143
+ if not isinstance(data, list):
144
+ logger.error(f"Unexpected response type from list_repositories: {type(data)}, data: {data}")
145
+ # If it's a dict with an error message, raise it
146
+ if isinstance(data, dict) and "error" in data:
147
+ raise APIError(f"API returned error: {data['error']}")
148
+ # Otherwise return empty list
149
+ return []
150
+
151
+ return data
141
152
  except httpx.HTTPStatusError as e:
142
153
  logger.error(f"Caught HTTPStatusError in list_repositories: status={e.response.status_code}, detail={e.response.text}")
143
154
  raise self._handle_api_error(e)
@@ -336,6 +347,53 @@ class NIAApiClient:
336
347
  logger.error(f"Failed to delete repository: {e}")
337
348
  return False
338
349
 
350
+ async def rename_repository(self, owner_repo: str, new_name: str) -> Dict[str, Any]:
351
+ """Rename a repository's display name."""
352
+ try:
353
+ # Check if this looks like owner/repo format (contains /)
354
+ if '/' in owner_repo:
355
+ # First, get the repository ID
356
+ status = await self.get_repository_status(owner_repo)
357
+ if not status:
358
+ raise APIError(f"Repository {owner_repo} not found", 404)
359
+
360
+ # Extract the repository ID from status
361
+ repo_id = status.get("repository_id") or status.get("id")
362
+ if not repo_id:
363
+ # Try to get it from list as fallback
364
+ repos = await self.list_repositories()
365
+ for repo in repos:
366
+ if repo.get("repository") == owner_repo:
367
+ repo_id = repo.get("repository_id") or repo.get("id")
368
+ break
369
+
370
+ if not repo_id:
371
+ raise APIError(f"No repository ID found for {owner_repo}", 404)
372
+
373
+ # Rename using the ID
374
+ response = await self.client.patch(
375
+ f"{self.base_url}/v2/repositories/{repo_id}/rename",
376
+ json={"new_name": new_name}
377
+ )
378
+ response.raise_for_status()
379
+ return response.json()
380
+ else:
381
+ # Assume it's already a repository ID
382
+ response = await self.client.patch(
383
+ f"{self.base_url}/v2/repositories/{owner_repo}/rename",
384
+ json={"new_name": new_name}
385
+ )
386
+ response.raise_for_status()
387
+ return response.json()
388
+
389
+ except httpx.HTTPStatusError as e:
390
+ raise self._handle_api_error(e)
391
+ except APIError:
392
+ raise
393
+ except Exception as e:
394
+ logger.error(f"Failed to rename repository: {e}")
395
+ raise APIError(f"Failed to rename repository: {str(e)}")
396
+
339
397
  # Data Source methods
340
398
 
341
399
  async def create_data_source(
@@ -408,6 +466,22 @@ class NIAApiClient:
408
466
  logger.error(f"Failed to delete data source: {e}")
409
467
  return False
410
468
 
469
+ async def rename_data_source(self, source_id: str, new_name: str) -> Dict[str, Any]:
470
+ """Rename a data source's display name."""
471
+ try:
472
+ response = await self.client.patch(
473
+ f"{self.base_url}/v2/data-sources/{source_id}/rename",
474
+ json={"new_name": new_name}
475
+ )
476
+ response.raise_for_status()
477
+ return response.json()
478
+
479
+ except httpx.HTTPStatusError as e:
480
+ raise self._handle_api_error(e)
481
+ except Exception as e:
482
+ logger.error(f"Failed to rename data source: {e}")
483
+ raise APIError(f"Failed to rename data source: {str(e)}")
484
+
411
485
  async def query_unified(
412
486
  self,
413
487
  messages: List[Dict[str, str]],
@@ -0,0 +1,263 @@
1
+ """
2
+ Nia Profile Configurations
3
+ Defines IDE/Editor profile configurations for rule transformation
4
+ """
5
+ from typing import Dict, Any, Optional, List
6
+
7
+ # Profile configurations define how rules are transformed and where they're placed
8
+ PROFILE_CONFIGS: Dict[str, Dict[str, Any]] = {
9
+ "cursor": {
10
+ "name": "Cursor",
11
+ "target_dir": ".cursor/rules",
12
+ "file_extension": ".mdc",
13
+ "file_map": {
14
+ "cursor_rules.md": "nia.mdc"
15
+ },
16
+ "mcp_config": True,
17
+ "format": "mdc",
18
+ "features": ["mcp", "composer", "inline_edits"],
19
+ "global_replacements": {
20
+ "# Nia": "# Nia for Cursor",
21
+ "{{IDE}}": "Cursor"
22
+ }
23
+ },
24
+
25
+ "vscode": {
26
+ "name": "Visual Studio Code",
27
+ "target_dir": ".vscode",
28
+ "file_extension": ".md",
29
+ "file_map": {
30
+ "nia_rules.md": "nia-guide.md",
31
+ "vscode_rules.md": "nia-vscode-integration.md"
32
+ },
33
+ "mcp_config": False,
34
+ "format": "markdown",
35
+ "features": ["tasks", "snippets", "terminal_integration"],
36
+ "global_replacements": {
37
+ "# Nia": "# Nia for VSCode",
38
+ "{{IDE}}": "VSCode"
39
+ },
40
+ "additional_files": {
41
+ "tasks.json": "vscode_tasks_template",
42
+ "nia.code-snippets": "vscode_snippets_template"
43
+ }
44
+ },
45
+
46
+ "claude": {
47
+ "name": "Claude Desktop",
48
+ "target_dir": ".claude",
49
+ "file_extension": ".md",
50
+ "file_map": {
51
+ "nia_rules.md": "nia_rules.md",
52
+ "claude_rules.md": "nia_claude_integration.md"
53
+ },
54
+ "mcp_config": False,
55
+ "format": "markdown",
56
+ "features": ["conversational", "context_aware", "multi_step"],
57
+ "global_replacements": {
58
+ "# Nia": "# Nia for Claude Desktop",
59
+ "{{IDE}}": "Claude"
60
+ }
61
+ },
62
+
63
+ "windsurf": {
64
+ "name": "Windsurf",
65
+ "target_dir": ".windsurfrules",
66
+ "file_extension": ".md",
67
+ "file_map": {
68
+ "nia_rules.md": "nia_rules.md",
69
+ "windsurf_rules.md": "windsurf_nia_guide.md"
70
+ },
71
+ "mcp_config": True,
72
+ "format": "markdown",
73
+ "features": ["cascade", "memories", "flows"],
74
+ "global_replacements": {
75
+ "# Nia": "# Nia for Windsurf Cascade",
76
+ "{{IDE}}": "Windsurf"
77
+ }
78
+ },
79
+
80
+ "cline": {
81
+ "name": "Cline",
82
+ "target_dir": ".cline",
83
+ "file_extension": ".md",
84
+ "file_map": {
85
+ "nia_rules.md": "nia_rules.md"
86
+ },
87
+ "mcp_config": True,
88
+ "format": "markdown",
89
+ "features": ["autonomous", "task_planning"],
90
+ "global_replacements": {
91
+ "# Nia": "# Nia for Cline",
92
+ "{{IDE}}": "Cline"
93
+ }
94
+ },
95
+
96
+ "codex": {
97
+ "name": "OpenAI Codex",
98
+ "target_dir": ".codex",
99
+ "file_extension": ".md",
100
+ "file_map": {
101
+ "nia_rules.md": "nia_codex_guide.md"
102
+ },
103
+ "mcp_config": False,
104
+ "format": "markdown",
105
+ "features": ["completion", "generation"],
106
+ "global_replacements": {
107
+ "# Nia": "# Nia for Codex",
108
+ "{{IDE}}": "Codex"
109
+ }
110
+ },
111
+
112
+ "zed": {
113
+ "name": "Zed",
114
+ "target_dir": ".zed",
115
+ "file_extension": ".md",
116
+ "file_map": {
117
+ "nia_rules.md": "nia_assistant.md"
118
+ },
119
+ "mcp_config": False,
120
+ "format": "markdown",
121
+ "features": ["assistant", "collaboration"],
122
+ "global_replacements": {
123
+ "# Nia": "# Nia for Zed",
124
+ "{{IDE}}": "Zed"
125
+ }
126
+ },
127
+
128
+ "jetbrains": {
129
+ "name": "JetBrains IDEs",
130
+ "target_dir": ".idea/nia",
131
+ "file_extension": ".md",
132
+ "file_map": {
133
+ "nia_rules.md": "nia_guide.md"
134
+ },
135
+ "mcp_config": False,
136
+ "format": "markdown",
137
+ "features": ["ai_assistant", "code_completion"],
138
+ "global_replacements": {
139
+ "# Nia": "# Nia for JetBrains",
140
+ "{{IDE}}": "JetBrains IDE"
141
+ }
142
+ },
143
+
144
+ "neovim": {
145
+ "name": "Neovim",
146
+ "target_dir": ".config/nvim/nia",
147
+ "file_extension": ".md",
148
+ "file_map": {
149
+ "nia_rules.md": "nia_guide.md"
150
+ },
151
+ "mcp_config": False,
152
+ "format": "markdown",
153
+ "features": ["copilot", "cmp"],
154
+ "global_replacements": {
155
+ "# Nia": "# Nia for Neovim",
156
+ "{{IDE}}": "Neovim"
157
+ }
158
+ },
159
+
160
+ "sublime": {
161
+ "name": "Sublime Text",
162
+ "target_dir": ".sublime",
163
+ "file_extension": ".md",
164
+ "file_map": {
165
+ "nia_rules.md": "nia_guide.md"
166
+ },
167
+ "mcp_config": False,
168
+ "format": "markdown",
169
+ "features": ["copilot"],
170
+ "global_replacements": {
171
+ "# Nia": "# Nia for Sublime Text",
172
+ "{{IDE}}": "Sublime Text"
173
+ }
174
+ }
175
+ }
176
+
177
+ # Additional template configurations for specific file types
178
+ TEMPLATE_CONFIGS = {
179
+ "vscode_tasks_template": {
180
+ "content": """{
181
+ "version": "2.0.0",
182
+ "tasks": [
183
+ {
184
+ "label": "Nia: Index Repository",
185
+ "type": "shell",
186
+ "command": "echo 'Run: index_repository ${input:repoUrl}'",
187
+ "problemMatcher": []
188
+ },
189
+ {
190
+ "label": "Nia: Search Codebase",
191
+ "type": "shell",
192
+ "command": "echo 'Run: search_codebase \\"${input:searchQuery}\\"'",
193
+ "problemMatcher": []
194
+ },
195
+ {
196
+ "label": "Nia: List Repositories",
197
+ "type": "shell",
198
+ "command": "echo 'Run: list_repositories'",
199
+ "problemMatcher": []
200
+ }
201
+ ],
202
+ "inputs": [
203
+ {
204
+ "id": "repoUrl",
205
+ "type": "promptString",
206
+ "description": "GitHub repository URL"
207
+ },
208
+ {
209
+ "id": "searchQuery",
210
+ "type": "promptString",
211
+ "description": "Search query"
212
+ }
213
+ ]
214
+ }"""
215
+ },
216
+
217
+ "vscode_snippets_template": {
218
+ "content": """{
219
+ "Nia Index": {
220
+ "prefix": "nia-index",
221
+ "body": ["index_repository ${1:repo_url}"],
222
+ "description": "Index a repository with Nia"
223
+ },
224
+ "Nia Search": {
225
+ "prefix": "nia-search",
226
+ "body": ["search_codebase \\"${1:query}\\""],
227
+ "description": "Search indexed repositories"
228
+ },
229
+ "Nia Web Search": {
230
+ "prefix": "nia-web",
231
+ "body": ["nia_web_search \\"${1:query}\\""],
232
+ "description": "Search the web with Nia"
233
+ },
234
+ "Nia Research": {
235
+ "prefix": "nia-research",
236
+ "body": ["nia_deep_research_agent \\"${1:query}\\""],
237
+ "description": "Perform deep research with Nia"
238
+ }
239
+ }"""
240
+ }
241
+ }
242
+
243
+
244
+ def get_profile_config(profile: str) -> Optional[Dict[str, Any]]:
245
+ """Get configuration for a specific profile"""
246
+ return PROFILE_CONFIGS.get(profile)
247
+
248
+
249
+ def get_supported_profiles() -> List[str]:
250
+ """Get list of all supported profiles"""
251
+ return list(PROFILE_CONFIGS.keys())
252
+
253
+
254
+ def get_profile_features(profile: str) -> List[str]:
255
+ """Get features supported by a profile"""
256
+ config = get_profile_config(profile)
257
+ return config.get("features", []) if config else []
258
+
259
+
260
+ def is_mcp_enabled(profile: str) -> bool:
261
+ """Check if a profile supports MCP"""
262
+ config = get_profile_config(profile)
263
+ return config.get("mcp_config", False) if config else False
@@ -0,0 +1,193 @@
1
+ """
2
+ Nia Project Initialization Module
3
+ Handles creation of Nia-enabled project structures and configurations
4
+ """
5
+ import os
6
+ import json
7
+ import shutil
8
+ import logging
9
+ import subprocess
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+ from typing import List, Dict, Optional, Any
13
+ from .profiles import PROFILE_CONFIGS, get_profile_config
14
+ from .rule_transformer import transform_rules_for_profile
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class NIAProjectInitializer:
19
+ """Handles Nia project initialization with support for multiple IDE profiles"""
20
+
21
+ def __init__(self, project_root: str):
22
+ self.project_root = Path(project_root).resolve()
23
+ self.assets_dir = Path(__file__).parent.parent.parent / "assets"
24
+ self.templates_dir = self.assets_dir / "templates"
25
+ self.rules_dir = self.assets_dir / "rules"
26
+
27
+ # Validate that required directories exist
28
+ if not self.assets_dir.exists():
29
+ raise RuntimeError(f"Assets directory not found at {self.assets_dir}. Nia MCP server may not be installed correctly.")
30
+ if not self.rules_dir.exists():
31
+ raise RuntimeError(f"Rules directory not found at {self.rules_dir}. Nia MCP server may not be installed correctly.")
32
+
33
+ def initialize_project(
34
+ self,
35
+ profiles: List[str] = ["cursor"]
36
+ ) -> Dict[str, Any]:
37
+ """
38
+ Initialize a Nia project with specified profiles
39
+
40
+ Args:
41
+ profiles: List of IDE profiles to set up (cursor, vscode, claude, etc.)
42
+
43
+ Returns:
44
+ Dictionary with initialization results and status
45
+ """
46
+ try:
47
+ results = {
48
+ "success": True,
49
+ "project_root": str(self.project_root),
50
+ "profiles_initialized": [],
51
+ "files_created": [],
52
+ "warnings": [],
53
+ "next_steps": []
54
+ }
55
+
56
+ # Validate project root
57
+ if not self.project_root.exists():
58
+ os.makedirs(self.project_root, exist_ok=True)
59
+
60
+ # No longer creating .nia directory or config files
61
+
62
+ # Process each profile
63
+ for profile in profiles:
64
+ if profile not in PROFILE_CONFIGS:
65
+ results["warnings"].append(f"Unknown profile: {profile}")
66
+ continue
67
+
68
+ profile_results = self._initialize_profile(profile)
69
+ if profile_results["success"]:
70
+ results["profiles_initialized"].append(profile)
71
+ results["files_created"].extend(profile_results["files_created"])
72
+ else:
73
+ results["warnings"].append(f"Failed to initialize {profile}: {profile_results.get('error')}")
74
+
75
+ # Generate next steps
76
+ results["next_steps"].extend(self._generate_next_steps(profiles))
77
+
78
+ logger.info(f"Successfully initialized Nia project at {self.project_root}")
79
+ return results
80
+
81
+ except Exception as e:
82
+ logger.error(f"Failed to initialize project: {e}")
83
+ return {
84
+ "success": False,
85
+ "error": str(e),
86
+ "project_root": str(self.project_root)
87
+ }
88
+
89
+ def _create_nia_directories(self):
90
+ """Create the .nia directory structure"""
91
+ # Simplified - no unnecessary directories or files
92
+ pass
93
+
94
+
95
+
96
+
97
+ def _initialize_profile(self, profile: str) -> Dict[str, Any]:
98
+ """Initialize a specific IDE profile"""
99
+ try:
100
+ profile_config = get_profile_config(profile)
101
+ if not profile_config:
102
+ return {"success": False, "error": "Profile configuration not found"}
103
+
104
+ files_created = []
105
+
106
+ # Create profile directory
107
+ profile_dir = self.project_root / profile_config["target_dir"]
108
+ os.makedirs(profile_dir, exist_ok=True)
109
+
110
+ # Transform and copy rules
111
+ rule_files = transform_rules_for_profile(
112
+ profile,
113
+ self.rules_dir,
114
+ profile_dir,
115
+ self.project_root
116
+ )
117
+
118
+ for rule_file in rule_files:
119
+ files_created.append(str(Path(rule_file).relative_to(self.project_root)))
120
+
121
+ # Handle profile-specific setup
122
+ if profile == "vscode" and profile_config.get("additional_files"):
123
+ # VSCode gets tasks.json and snippets from additional_files
124
+ pass # Handled by transform_rules_for_profile
125
+ # No need to setup MCP config - user is already running through MCP!
126
+
127
+ return {
128
+ "success": True,
129
+ "files_created": files_created
130
+ }
131
+
132
+ except Exception as e:
133
+ logger.error(f"Failed to initialize profile {profile}: {e}")
134
+ return {"success": False, "error": str(e)}
135
+
136
+
137
+
138
+ def _generate_next_steps(self, profiles: List[str]) -> List[str]:
139
+ """Generate helpful next steps for the user"""
140
+ steps = []
141
+
142
+ # Check for current git repository
143
+ if (self.project_root / ".git").exists():
144
+ try:
145
+ # Use subprocess for safer command execution
146
+ result = subprocess.run(
147
+ ["git", "remote", "get-url", "origin"],
148
+ cwd=self.project_root,
149
+ capture_output=True,
150
+ text=True,
151
+ check=False # Don't raise exception if git command fails
152
+ )
153
+ git_remote = result.stdout.strip()
154
+ if git_remote and "github.com" in git_remote:
155
+ steps.append(f"Index this repository: index_repository {git_remote}")
156
+ except (subprocess.SubprocessError, FileNotFoundError):
157
+ # Git might not be installed or available
158
+ logger.debug("Could not get git remote URL")
159
+
160
+ # Profile-specific steps
161
+ if "cursor" in profiles:
162
+ steps.append("Restart Cursor to load Nia MCP server")
163
+ if "vscode" in profiles:
164
+ steps.append("Reload VSCode window to apply settings")
165
+
166
+ # General steps
167
+ steps.extend([
168
+ "Explore available commands with list_repositories",
169
+ "Search for code patterns with search_codebase",
170
+ "Find new libraries with nia_web_search"
171
+ ])
172
+
173
+ return steps
174
+
175
+
176
+ def initialize_nia_project(
177
+ project_root: str,
178
+ profiles: List[str] = ["cursor"]
179
+ ) -> Dict[str, Any]:
180
+ """
181
+ Convenience function to initialize a Nia project
182
+
183
+ Args:
184
+ project_root: Root directory of the project
185
+ profiles: List of IDE profiles to set up
186
+
187
+ Returns:
188
+ Dictionary with initialization results
189
+ """
190
+ initializer = NIAProjectInitializer(project_root)
191
+ return initializer.initialize_project(
192
+ profiles=profiles
193
+ )
@@ -0,0 +1,363 @@
1
+ """
2
+ Nia Rule Transformer
3
+ Handles transformation of rule files for different IDE profiles
4
+ """
5
+ import os
6
+ import re
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import List, Dict, Any, Optional
10
+ from .profiles import get_profile_config, TEMPLATE_CONFIGS
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def transform_rules_for_profile(
16
+ profile: str,
17
+ source_dir: Path,
18
+ target_dir: Path,
19
+ project_root: Path
20
+ ) -> List[str]:
21
+ """
22
+ Transform rule files for a specific profile
23
+
24
+ Args:
25
+ profile: Profile name (cursor, vscode, etc.)
26
+ source_dir: Directory containing source rule files
27
+ target_dir: Directory to write transformed rules
28
+ project_root: Project root directory for context
29
+
30
+ Returns:
31
+ List of created file paths
32
+ """
33
+ profile_config = get_profile_config(profile)
34
+ if not profile_config:
35
+ raise ValueError(f"Unknown profile: {profile}")
36
+
37
+ created_files = []
38
+ file_map = profile_config.get("file_map", {})
39
+
40
+ # Process each file in the file map
41
+ for source_file, target_file in file_map.items():
42
+ source_path = source_dir / source_file
43
+
44
+ if not source_path.exists():
45
+ logger.warning(f"Source file not found: {source_path}")
46
+ continue
47
+
48
+ # Read source content
49
+ content = source_path.read_text()
50
+
51
+ # Apply transformations
52
+ transformed_content = apply_transformations(
53
+ content,
54
+ profile_config,
55
+ project_root
56
+ )
57
+
58
+ # Write to target
59
+ target_path = target_dir / target_file
60
+ os.makedirs(target_path.parent, exist_ok=True)
61
+ target_path.write_text(transformed_content)
62
+ created_files.append(str(target_path))
63
+
64
+ logger.info(f"Created rule file: {target_path}")
65
+
66
+ # Handle additional files (like VSCode tasks.json)
67
+ additional_files = profile_config.get("additional_files", {})
68
+ for filename, template_key in additional_files.items():
69
+ if template_key in TEMPLATE_CONFIGS:
70
+ template_config = TEMPLATE_CONFIGS[template_key]
71
+ target_path = target_dir / filename
72
+
73
+ # Apply any necessary transformations to template
74
+ content = template_config["content"]
75
+ content = apply_template_variables(content, project_root)
76
+
77
+ target_path.write_text(content)
78
+ created_files.append(str(target_path))
79
+ logger.info(f"Created additional file: {target_path}")
80
+
81
+ return created_files
82
+
83
+
84
+ def apply_transformations(
85
+ content: str,
86
+ profile_config: Dict[str, Any],
87
+ project_root: Path
88
+ ) -> str:
89
+ """
90
+ Apply profile-specific transformations to content
91
+
92
+ Args:
93
+ content: Original content
94
+ profile_config: Profile configuration
95
+ project_root: Project root for context
96
+
97
+ Returns:
98
+ Transformed content
99
+ """
100
+ # Apply global replacements
101
+ global_replacements = profile_config.get("global_replacements", {})
102
+ for pattern, replacement in global_replacements.items():
103
+ content = content.replace(pattern, replacement)
104
+
105
+ # Apply project-specific variables
106
+ content = apply_template_variables(content, project_root)
107
+
108
+ # Apply format-specific transformations
109
+ if profile_config.get("format") == "markdown":
110
+ content = transform_markdown_format(content, profile_config)
111
+ elif profile_config.get("format") == "mdc":
112
+ content = transform_to_mdc_format(content, profile_config)
113
+
114
+ # Apply feature-specific enhancements
115
+ features = profile_config.get("features", [])
116
+ content = enhance_for_features(content, features, profile_config)
117
+
118
+ return content
119
+
120
+
121
+ def apply_template_variables(content: str, project_root: Path) -> str:
122
+ """
123
+ Replace template variables with actual values
124
+
125
+ Args:
126
+ content: Content with template variables
127
+ project_root: Project root directory
128
+
129
+ Returns:
130
+ Content with variables replaced
131
+ """
132
+ variables = {
133
+ "{{PROJECT_ROOT}}": str(project_root),
134
+ "{{PROJECT_NAME}}": project_root.name,
135
+ "{{WORKSPACE_FOLDER}}": "${workspaceFolder}",
136
+ "{{USER_HOME}}": str(Path.home()),
137
+ }
138
+
139
+ for var, value in variables.items():
140
+ content = content.replace(var, value)
141
+
142
+ return content
143
+
144
+
145
+ def transform_markdown_format(content: str, profile_config: Dict[str, Any]) -> str:
146
+ """
147
+ Apply markdown-specific transformations
148
+
149
+ Args:
150
+ content: Markdown content
151
+ profile_config: Profile configuration
152
+
153
+ Returns:
154
+ Transformed markdown
155
+ """
156
+ # Add profile-specific header if not present
157
+ profile_name = profile_config.get("name", "Unknown")
158
+ if not content.startswith(f"# Nia Integration for {profile_name}"):
159
+ # Check if it starts with a generic Nia header
160
+ if content.startswith("# Nia"):
161
+ # Replace the first line
162
+ lines = content.split('\n')
163
+ lines[0] = f"# Nia Integration for {profile_name}"
164
+ content = '\n'.join(lines)
165
+
166
+ # Enhance code blocks with profile-specific annotations
167
+ if "mcp" in profile_config.get("features", []):
168
+ content = enhance_mcp_code_blocks(content)
169
+
170
+ return content
171
+
172
+
173
+ def transform_to_mdc_format(content: str, profile_config: Dict[str, Any]) -> str:
174
+ """
175
+ Transform markdown content to MDC format for Cursor
176
+
177
+ Args:
178
+ content: Original markdown content
179
+ profile_config: Profile configuration
180
+
181
+ Returns:
182
+ MDC formatted content
183
+ """
184
+ # Extract the first line as description
185
+ lines = content.split('\n')
186
+ description = ""
187
+ content_start = 0
188
+
189
+ if lines and lines[0].startswith('#'):
190
+ # Use the first header as description
191
+ description = lines[0].replace('#', '').strip()
192
+ content_start = 1
193
+ else:
194
+ description = "Nia Knowledge Agent Integration Rules"
195
+
196
+ # Build MDC header
197
+ # For Nia rules, we want them always applied since they guide AI assistant behavior
198
+ mdc_header = f"""---
199
+ description: {description}
200
+ alwaysApply: true
201
+ ---
202
+ """
203
+
204
+ # Get the rest of the content
205
+ remaining_content = '\n'.join(lines[content_start:]).strip()
206
+
207
+ # Apply markdown transformations first
208
+ remaining_content = transform_markdown_format(remaining_content, profile_config)
209
+
210
+ return mdc_header + '\n' + remaining_content
211
+
212
+
213
+ def enhance_for_features(
214
+ content: str,
215
+ features: List[str],
216
+ profile_config: Dict[str, Any]
217
+ ) -> str:
218
+ """
219
+ Enhance content based on profile features
220
+
221
+ Args:
222
+ content: Original content
223
+ features: List of features supported by profile
224
+ profile_config: Profile configuration
225
+
226
+ Returns:
227
+ Enhanced content
228
+ """
229
+ # Add feature-specific sections if not present
230
+ enhancements = []
231
+
232
+ if "mcp" in features and "## MCP Integration" not in content:
233
+ enhancements.append(generate_mcp_section(profile_config))
234
+
235
+ if "composer" in features and "## Composer Usage" not in content:
236
+ enhancements.append(generate_composer_section())
237
+
238
+ if "tasks" in features and "## Task Automation" not in content:
239
+ enhancements.append(generate_tasks_section())
240
+
241
+ if "terminal_integration" in features and "## Terminal Commands" not in content:
242
+ enhancements.append(generate_terminal_section())
243
+
244
+ # Append enhancements to content
245
+ if enhancements:
246
+ content += "\n\n" + "\n\n".join(enhancements)
247
+
248
+ return content
249
+
250
+
251
+ def enhance_mcp_code_blocks(content: str) -> str:
252
+ """
253
+ Enhance code blocks for MCP-enabled profiles
254
+
255
+ Args:
256
+ content: Markdown content
257
+
258
+ Returns:
259
+ Enhanced content
260
+ """
261
+ # Pattern to find code blocks with Nia commands
262
+ pattern = r'```(\w*)\n(.*?nia.*?)\n```'
263
+
264
+ def replacer(match):
265
+ lang = match.group(1) or "bash"
266
+ code = match.group(2)
267
+
268
+ # If it's a NIA command, add annotation
269
+ if any(cmd in code for cmd in ["index_repository", "search_codebase", "list_repositories"]):
270
+ return f'```{lang}\n# MCP Command - Run this in your AI assistant\n{code}\n```'
271
+ return match.group(0)
272
+
273
+ return re.sub(pattern, replacer, content, flags=re.DOTALL)
274
+
275
+
276
+ def generate_mcp_section(profile_config: Dict[str, Any]) -> str:
277
+ """Generate MCP integration section"""
278
+ profile_name = profile_config.get("name", "IDE")
279
+ return f"""## MCP Integration
280
+
281
+ {profile_name} supports Nia through the Model Context Protocol (MCP). After initialization:
282
+
283
+ 1. **Restart {profile_name}** to load the Nia MCP server
284
+ 2. **Verify connection** by running: `list_repositories`
285
+ 3. **Set API key** in your environment or {profile_name} settings
286
+
287
+ The MCP server provides direct access to all Nia commands within your AI assistant."""
288
+
289
+
290
+ def generate_composer_section() -> str:
291
+ """Generate Composer-specific section"""
292
+ return """## Composer Usage
293
+
294
+ When using Cursor's Composer:
295
+
296
+ 1. **Start with context**: Always check indexed repositories first
297
+ 2. **Natural language**: Use complete questions, not keywords
298
+ 3. **Inline results**: Nia results appear directly in your code
299
+ 4. **Multi-file**: Reference multiple files from search results
300
+
301
+ Example:
302
+ ```
303
+ Composer: "How does authentication work in this Next.js app?"
304
+ [Nia searches indexed codebase and shows relevant files]
305
+ ```"""
306
+
307
+
308
+ def generate_tasks_section() -> str:
309
+ """Generate tasks automation section"""
310
+ return """## Task Automation
311
+
312
+ Quick tasks are configured in `.vscode/tasks.json`:
313
+
314
+ - **Ctrl+Shift+P** → "Tasks: Run Task"
315
+ - Select "Nia: Index Repository" or other Nia tasks
316
+ - Follow prompts for input
317
+
318
+ Custom keyboard shortcuts can be added in keybindings.json."""
319
+
320
+
321
+ def generate_terminal_section() -> str:
322
+ """Generate terminal integration section"""
323
+ return """## Terminal Commands
324
+
325
+ Quick aliases for your terminal:
326
+
327
+ ```bash
328
+ # Add to your shell profile (.bashrc, .zshrc, etc.)
329
+ alias nia-index='echo "index_repository"'
330
+ alias nia-search='echo "search_codebase"'
331
+ alias nia-list='echo "list_repositories"'
332
+ ```
333
+
334
+ Use these in the integrated terminal for quick access."""
335
+
336
+
337
+ def create_profile_specific_file(
338
+ profile: str,
339
+ filename: str,
340
+ content: str,
341
+ target_dir: Path
342
+ ) -> Optional[str]:
343
+ """
344
+ Create a profile-specific file
345
+
346
+ Args:
347
+ profile: Profile name
348
+ filename: Target filename
349
+ content: File content
350
+ target_dir: Target directory
351
+
352
+ Returns:
353
+ Created file path or None
354
+ """
355
+ try:
356
+ file_path = target_dir / filename
357
+ os.makedirs(file_path.parent, exist_ok=True)
358
+ file_path.write_text(content)
359
+ logger.info(f"Created {profile} file: {file_path}")
360
+ return str(file_path)
361
+ except Exception as e:
362
+ logger.error(f"Failed to create {filename} for {profile}: {e}")
363
+ return None
nia_mcp_server/server.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- NIA MCP Proxy Server - Lightweight server that communicates with NIA API
2
+ Nia MCP Proxy Server - Lightweight server that communicates with Nia API
3
3
  """
4
4
  import os
5
5
  import logging
@@ -12,6 +12,8 @@ from urllib.parse import urlparse
12
12
  from mcp.server.fastmcp import FastMCP
13
13
  from mcp.types import TextContent, Resource
14
14
  from .api_client import NIAApiClient, APIError
15
+ from .project_init import initialize_nia_project
16
+ from .profiles import get_supported_profiles
15
17
  from dotenv import load_dotenv
16
18
  import json
17
19
 
@@ -167,7 +169,26 @@ async def search_codebase(
167
169
  # Get all indexed repositories if not specified
168
170
  if not repositories:
169
171
  all_repos = await client.list_repositories()
170
- repositories = [repo["repository"] for repo in all_repos if repo.get("status") == "completed"]
172
+
173
+ # Ensure all_repos is a list and contains dictionaries
174
+ if not isinstance(all_repos, list):
175
+ logger.error(f"Unexpected type for all_repos: {type(all_repos)}")
176
+ return [TextContent(
177
+ type="text",
178
+ text="❌ Error retrieving repositories. The API returned an unexpected response."
179
+ )]
180
+
181
+ repositories = []
182
+ for repo in all_repos:
183
+ if isinstance(repo, dict) and repo.get("status") == "completed":
184
+ repo_name = repo.get("repository")
185
+ if repo_name:
186
+ repositories.append(repo_name)
187
+ else:
188
+ logger.warning(f"Repository missing 'repository' field: {repo}")
189
+ else:
190
+ logger.warning(f"Unexpected repository format: {type(repo)}, value: {repo}")
191
+
171
192
  if not repositories:
172
193
  return [TextContent(
173
194
  type="text",
@@ -364,7 +385,17 @@ async def list_repositories() -> List[TextContent]:
364
385
 
365
386
  for repo in repositories:
366
387
  status_icon = "✅" if repo.get("status") == "completed" else "⏳"
367
- lines.append(f"\n## {status_icon} {repo['repository']}")
388
+
389
+ # Show display name if available, otherwise show repository
390
+ display_name = repo.get("display_name")
391
+ repo_name = repo['repository']
392
+
393
+ if display_name:
394
+ lines.append(f"\n## {status_icon} {display_name}")
395
+ lines.append(f"- **Repository:** {repo_name}")
396
+ else:
397
+ lines.append(f"\n## {status_icon} {repo_name}")
398
+
368
399
  lines.append(f"- **Branch:** {repo.get('branch', 'main')}")
369
400
  lines.append(f"- **Status:** {repo.get('status', 'unknown')}")
370
401
  if repo.get("indexed_at"):
@@ -559,7 +590,17 @@ async def list_documentation() -> List[TextContent]:
559
590
 
560
591
  for source in sources:
561
592
  status_icon = "✅" if source.get("status") == "completed" else "⏳"
562
- lines.append(f"\n## {status_icon} {source.get('url', 'Unknown URL')}")
593
+
594
+ # Show display name if available, otherwise show URL
595
+ display_name = source.get("display_name")
596
+ url = source.get('url', 'Unknown URL')
597
+
598
+ if display_name:
599
+ lines.append(f"\n## {status_icon} {display_name}")
600
+ lines.append(f"- **URL:** {url}")
601
+ else:
602
+ lines.append(f"\n## {status_icon} {url}")
603
+
563
604
  lines.append(f"- **ID:** {source['id']}")
564
605
  lines.append(f"- **Status:** {source.get('status', 'unknown')}")
565
606
  lines.append(f"- **Type:** {source.get('source_type', 'web')}")
@@ -727,6 +768,100 @@ async def delete_repository(repository: str) -> List[TextContent]:
727
768
  text=f"❌ Error deleting repository: {str(e)}"
728
769
  )]
729
770
 
771
+ @mcp.tool()
772
+ async def rename_repository(repository: str, new_name: str) -> List[TextContent]:
773
+ """
774
+ Rename an indexed repository for better organization.
775
+
776
+ Args:
777
+ repository: Repository in owner/repo format
778
+ new_name: New display name for the repository (1-100 characters)
779
+
780
+ Returns:
781
+ Confirmation of rename operation
782
+ """
783
+ try:
784
+ # Validate name length
785
+ if not new_name or len(new_name) > 100:
786
+ return [TextContent(
787
+ type="text",
788
+ text="❌ Display name must be between 1 and 100 characters."
789
+ )]
790
+
791
+ client = await ensure_api_client()
792
+ result = await client.rename_repository(repository, new_name)
793
+
794
+ if result.get("success"):
795
+ return [TextContent(
796
+ type="text",
797
+ text=f"✅ Successfully renamed repository '{repository}' to '{new_name}'"
798
+ )]
799
+ else:
800
+ return [TextContent(
801
+ type="text",
802
+ text=f"❌ Failed to rename repository: {result.get('message', 'Unknown error')}"
803
+ )]
804
+
805
+ except APIError as e:
806
+ logger.error(f"API Error renaming repository: {e}")
807
+ error_msg = f"❌ {str(e)}"
808
+ if e.status_code == 403 and "lifetime limit" in str(e).lower():
809
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit. Upgrade to Pro for unlimited access."
810
+ return [TextContent(type="text", text=error_msg)]
811
+ except Exception as e:
812
+ logger.error(f"Error renaming repository: {e}")
813
+ return [TextContent(
814
+ type="text",
815
+ text=f"❌ Error renaming repository: {str(e)}"
816
+ )]
817
+
818
+ @mcp.tool()
819
+ async def rename_documentation(source_id: str, new_name: str) -> List[TextContent]:
820
+ """
821
+ Rename a documentation source for better organization.
822
+
823
+ Args:
824
+ source_id: Documentation source ID
825
+ new_name: New display name for the documentation (1-100 characters)
826
+
827
+ Returns:
828
+ Confirmation of rename operation
829
+ """
830
+ try:
831
+ # Validate name length
832
+ if not new_name or len(new_name) > 100:
833
+ return [TextContent(
834
+ type="text",
835
+ text="❌ Display name must be between 1 and 100 characters."
836
+ )]
837
+
838
+ client = await ensure_api_client()
839
+ result = await client.rename_data_source(source_id, new_name)
840
+
841
+ if result.get("success"):
842
+ return [TextContent(
843
+ type="text",
844
+ text=f"✅ Successfully renamed documentation source to '{new_name}'"
845
+ )]
846
+ else:
847
+ return [TextContent(
848
+ type="text",
849
+ text=f"❌ Failed to rename documentation: {result.get('message', 'Unknown error')}"
850
+ )]
851
+
852
+ except APIError as e:
853
+ logger.error(f"API Error renaming documentation: {e}")
854
+ error_msg = f"❌ {str(e)}"
855
+ if e.status_code == 403 and "lifetime limit" in str(e).lower():
856
+ error_msg += "\n\n💡 Tip: You've reached the free tier limit. Upgrade to Pro for unlimited access."
857
+ return [TextContent(type="text", text=error_msg)]
858
+ except Exception as e:
859
+ logger.error(f"Error renaming documentation: {e}")
860
+ return [TextContent(
861
+ type="text",
862
+ text=f"❌ Error renaming documentation: {str(e)}"
863
+ )]
864
+
730
865
  @mcp.tool()
731
866
  async def nia_web_search(
732
867
  query: str,
@@ -781,7 +916,7 @@ async def nia_web_search(
781
916
  other_content = result.get("other_content", [])
782
917
 
783
918
  # Format response to naturally guide next actions
784
- response_text = f"## 🔍 NIA Web Search Results for: \"{query}\"\n\n"
919
+ response_text = f"## 🔍 Nia Web Search Results for: \"{query}\"\n\n"
785
920
 
786
921
  if days_back:
787
922
  response_text += f"*Showing results from the last {days_back} days*\n\n"
@@ -806,7 +941,7 @@ async def nia_web_search(
806
941
 
807
942
  # Be more aggressive based on query specificity
808
943
  if len(github_repos) == 1 or any(specific_word in query.lower() for specific_word in ["specific", "exact", "particular", "find me", "looking for"]):
809
- response_text += "**🚀 RECOMMENDED ACTION - Index this repository with NIA:**\n"
944
+ response_text += "**🚀 RECOMMENDED ACTION - Index this repository with Nia:**\n"
810
945
  response_text += f"```\nIndex {github_repos[0]['owner_repo']}\n```\n"
811
946
  response_text += "✨ This will enable AI-powered code search, understanding, and analysis!\n\n"
812
947
  else:
@@ -1075,6 +1210,162 @@ async def nia_deep_research_agent(
1075
1210
  "Try simplifying your question or using the regular nia_web_search tool."
1076
1211
  )]
1077
1212
 
1213
+ @mcp.tool()
1214
+ async def initialize_project(
1215
+ project_root: str,
1216
+ profiles: Optional[List[str]] = None
1217
+ ) -> List[TextContent]:
1218
+ """
1219
+ Initialize a NIA-enabled project with IDE-specific rules and configurations.
1220
+
1221
+ This tool sets up your project with NIA integration, creating configuration files
1222
+ and rules tailored to your IDE or editor. It enables AI assistants to better
1223
+ understand and work with NIA's knowledge search capabilities.
1224
+
1225
+ Args:
1226
+ project_root: Absolute path to the project root directory
1227
+ profiles: List of IDE profiles to set up (default: ["cursor"]).
1228
+ Options: cursor, vscode, claude, windsurf, cline, codex, zed, jetbrains, neovim, sublime
1229
+
1230
+ Returns:
1231
+ Status of the initialization with created files and next steps
1232
+
1233
+ Examples:
1234
+ - Basic: initialize_project("/path/to/project")
1235
+ - Multiple IDEs: initialize_project("/path/to/project", profiles=["cursor", "vscode"])
1236
+ - Specific IDE: initialize_project("/path/to/project", profiles=["windsurf"])
1237
+ """
1238
+ try:
1239
+ # Validate project root
1240
+ project_path = Path(project_root)
1241
+ if not project_path.is_absolute():
1242
+ return [TextContent(
1243
+ type="text",
1244
+ text=f"❌ Error: project_root must be an absolute path. Got: {project_root}"
1245
+ )]
1246
+
1247
+ # Default to cursor profile if none specified
1248
+ if profiles is None:
1249
+ profiles = ["cursor"]
1250
+
1251
+ # Validate profiles
1252
+ supported = get_supported_profiles()
1253
+ invalid_profiles = [p for p in profiles if p not in supported]
1254
+ if invalid_profiles:
1255
+ return [TextContent(
1256
+ type="text",
1257
+ text=f"❌ Unknown profiles: {', '.join(invalid_profiles)}\n\n"
1258
+ f"Supported profiles: {', '.join(supported)}"
1259
+ )]
1260
+
1261
+ logger.info(f"Initializing NIA project at {project_root} with profiles: {profiles}")
1262
+
1263
+ # Initialize the project
1264
+ result = initialize_nia_project(
1265
+ project_root=project_root,
1266
+ profiles=profiles
1267
+ )
1268
+
1269
+ if not result.get("success"):
1270
+ return [TextContent(
1271
+ type="text",
1272
+ text=f"❌ Failed to initialize project: {result.get('error', 'Unknown error')}"
1273
+ )]
1274
+
1275
+ # Format success response
1276
+ response_lines = [
1277
+ f"✅ Successfully initialized NIA project at: {project_root}",
1278
+ "",
1279
+ "## 📁 Created Files:",
1280
+ ]
1281
+
1282
+ for file in result.get("files_created", []):
1283
+ response_lines.append(f"- {file}")
1284
+
1285
+ if result.get("profiles_initialized"):
1286
+ response_lines.extend([
1287
+ "",
1288
+ "## 🎨 Initialized Profiles:",
1289
+ ])
1290
+ for profile in result["profiles_initialized"]:
1291
+ response_lines.append(f"- {profile}")
1292
+
1293
+ if result.get("warnings"):
1294
+ response_lines.extend([
1295
+ "",
1296
+ "## ⚠️ Warnings:",
1297
+ ])
1298
+ for warning in result["warnings"]:
1299
+ response_lines.append(f"- {warning}")
1300
+
1301
+ if result.get("next_steps"):
1302
+ response_lines.extend([
1303
+ "",
1304
+ "## 🚀 Next Steps:",
1305
+ ])
1306
+ for i, step in enumerate(result["next_steps"], 1):
1307
+ response_lines.append(f"{i}. {step}")
1308
+
1309
+ # Add profile-specific instructions
1310
+ response_lines.extend([
1311
+ "",
1312
+ "## 💡 Quick Start:",
1313
+ ])
1314
+
1315
+ if "cursor" in profiles:
1316
+ response_lines.extend([
1317
+ "**For Cursor:**",
1318
+ "1. Restart Cursor to load the NIA MCP server",
1319
+ "2. Run `list_repositories` to verify connection",
1320
+ "3. Start indexing with `index_repository https://github.com/owner/repo`",
1321
+ ""
1322
+ ])
1323
+
1324
+ if "vscode" in profiles:
1325
+ response_lines.extend([
1326
+ "**For VSCode:**",
1327
+ "1. Reload the VSCode window (Cmd/Ctrl+R)",
1328
+ "2. Open command palette (Cmd/Ctrl+Shift+P)",
1329
+ "3. Run 'NIA: Index Repository' task",
1330
+ ""
1331
+ ])
1332
+
1333
+ if "claude" in profiles:
1334
+ response_lines.extend([
1335
+ "**For Claude Desktop:**",
1336
+ "1. The .claude directory has been created",
1337
+ "2. Claude will now understand NIA commands",
1338
+ "3. Try: 'Search for authentication patterns'",
1339
+ ""
1340
+ ])
1341
+
1342
+ # Add general tips
1343
+ response_lines.extend([
1344
+ "## 📚 Tips:",
1345
+ "- Use natural language for searches: 'How does X work?'",
1346
+ "- Index repositories before searching them",
1347
+ "- Use `nia_web_search` to discover new repositories",
1348
+ "- Check `list_repositories` to see what's already indexed",
1349
+ "",
1350
+ "Ready to supercharge your development with AI-powered code search! 🚀"
1351
+ ])
1352
+
1353
+ return [TextContent(
1354
+ type="text",
1355
+ text="\n".join(response_lines)
1356
+ )]
1357
+
1358
+ except Exception as e:
1359
+ logger.error(f"Error in initialize_project tool: {e}")
1360
+ return [TextContent(
1361
+ type="text",
1362
+ text=f"❌ Error initializing project: {str(e)}\n\n"
1363
+ "Please check:\n"
1364
+ "- The project_root path is correct and accessible\n"
1365
+ "- You have write permissions to the directory\n"
1366
+ "- The NIA MCP server is properly installed"
1367
+ )]
1368
+
1078
1369
  # Resources
1079
1370
 
1080
1371
  # Note: FastMCP doesn't have list_resources or read_resource decorators
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nia-mcp-server
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: NIA Knowledge Agent - MCP server for intelligent codebase search
5
5
  Project-URL: Homepage, https://trynia.ai
6
6
  Project-URL: Documentation, https://docs.trynia.ai
@@ -0,0 +1,12 @@
1
+ nia_mcp_server/__init__.py,sha256=xrM4qDGEvxPXQYqVksy08mhmfAj0wIljhJPnYZYHea8,84
2
+ nia_mcp_server/__main__.py,sha256=XY11ESL4hctu-BBgtPATFZyd1o-O7wE7y-UOSoNs-hw,152
3
+ nia_mcp_server/api_client.py,sha256=dk9FkMljuekyjIw7Ij3athsoVNnu7E2b1l2xi2XtB1Y,24255
4
+ nia_mcp_server/profiles.py,sha256=2DD8PFRr5Ij4IK4sPUz0mH8aKjkrEtkKLC1R0iki2bA,7221
5
+ nia_mcp_server/project_init.py,sha256=Mtxvlg7FfdWumc2AdMXifht3V1b6sZvX4b7USbbwMvw,7152
6
+ nia_mcp_server/rule_transformer.py,sha256=wCxoQ1Kl_rI9mUFnh9kG5iCXYU4QInrmFQOReZfAFVo,11000
7
+ nia_mcp_server/server.py,sha256=B6aLTXkX2Mq5eBqDCV_WTumqtSm4bzDf2bHPVyVsgcA,59579
8
+ nia_mcp_server-1.0.6.dist-info/METADATA,sha256=hN6ls8aDU9h83JuQ3AZ8VfLBsrKIVLGt-annSRXHbS8,6910
9
+ nia_mcp_server-1.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ nia_mcp_server-1.0.6.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
11
+ nia_mcp_server-1.0.6.dist-info/licenses/LICENSE,sha256=5jUPBVkZEicxSAZ91jOO7i8zXEPAHS6M0w8SSf6DftI,1071
12
+ nia_mcp_server-1.0.6.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- nia_mcp_server/__init__.py,sha256=P3fqcVlJ9fp8ojx6xaX5lrdUNO8rkc8bGYEv8Qm3SAw,84
2
- nia_mcp_server/__main__.py,sha256=XY11ESL4hctu-BBgtPATFZyd1o-O7wE7y-UOSoNs-hw,152
3
- nia_mcp_server/api_client.py,sha256=E7x6W2zvFrfyR8yfk8X4z6WGF6wQk_EP4TfQhZAV_4w,20932
4
- nia_mcp_server/server.py,sha256=UJaP4Q7xlZt_xW4bDD17AgYhwAZ2sZaoTZ6nf_2mNOE,48694
5
- nia_mcp_server-1.0.5.dist-info/METADATA,sha256=lyWMV-006_5q1vb_aEnHWwUE8lAYcZMpbL9ypEi5kiI,6910
6
- nia_mcp_server-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- nia_mcp_server-1.0.5.dist-info/entry_points.txt,sha256=V74FQEp48pfWxPCl7B9mihtqvIJNVjCSbRfCz4ww77I,64
8
- nia_mcp_server-1.0.5.dist-info/licenses/LICENSE,sha256=5jUPBVkZEicxSAZ91jOO7i8zXEPAHS6M0w8SSf6DftI,1071
9
- nia_mcp_server-1.0.5.dist-info/RECORD,,