@agents-at-scale/ark 0.1.36-rc1 → 0.1.36

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.
Files changed (100) hide show
  1. package/package.json +1 -1
  2. package/dist/charts/charts.d.ts +0 -5
  3. package/dist/charts/charts.js +0 -6
  4. package/dist/charts/dependencies.d.ts +0 -6
  5. package/dist/charts/dependencies.js +0 -50
  6. package/dist/charts/types.d.ts +0 -40
  7. package/dist/charts/types.js +0 -1
  8. package/dist/commands/agents/selector.d.ts +0 -8
  9. package/dist/commands/agents/selector.js +0 -53
  10. package/dist/commands/agents.d.ts +0 -2
  11. package/dist/commands/agents.js +0 -53
  12. package/dist/commands/chat.d.ts +0 -2
  13. package/dist/commands/chat.js +0 -45
  14. package/dist/commands/cluster/get-ip.d.ts +0 -2
  15. package/dist/commands/cluster/get-ip.js +0 -32
  16. package/dist/commands/cluster/get-type.d.ts +0 -2
  17. package/dist/commands/cluster/get-type.js +0 -26
  18. package/dist/commands/completion.d.ts +0 -2
  19. package/dist/commands/completion.js +0 -265
  20. package/dist/commands/config.d.ts +0 -2
  21. package/dist/commands/config.js +0 -44
  22. package/dist/commands/dashboard.d.ts +0 -3
  23. package/dist/commands/dashboard.js +0 -39
  24. package/dist/commands/dev/index.d.ts +0 -3
  25. package/dist/commands/dev/index.js +0 -9
  26. package/dist/commands/dev/tool/check.d.ts +0 -2
  27. package/dist/commands/dev/tool/check.js +0 -142
  28. package/dist/commands/dev/tool/clean.d.ts +0 -2
  29. package/dist/commands/dev/tool/clean.js +0 -153
  30. package/dist/commands/dev/tool/generate.d.ts +0 -2
  31. package/dist/commands/dev/tool/generate.js +0 -28
  32. package/dist/commands/dev/tool/index.d.ts +0 -2
  33. package/dist/commands/dev/tool/index.js +0 -14
  34. package/dist/commands/dev/tool/init.d.ts +0 -2
  35. package/dist/commands/dev/tool/init.js +0 -320
  36. package/dist/commands/dev/tool/shared.d.ts +0 -5
  37. package/dist/commands/dev/tool/shared.js +0 -258
  38. package/dist/commands/dev/tool/status.d.ts +0 -2
  39. package/dist/commands/dev/tool/status.js +0 -136
  40. package/dist/commands/dev/tool-generate.spec.d.ts +0 -1
  41. package/dist/commands/dev/tool-generate.spec.js +0 -163
  42. package/dist/commands/dev/tool.d.ts +0 -2
  43. package/dist/commands/dev/tool.js +0 -559
  44. package/dist/commands/dev/tool.spec.d.ts +0 -1
  45. package/dist/commands/dev/tool.spec.js +0 -48
  46. package/dist/commands/install.d.ts +0 -3
  47. package/dist/commands/install.js +0 -147
  48. package/dist/commands/models/selector.d.ts +0 -8
  49. package/dist/commands/models/selector.js +0 -53
  50. package/dist/commands/routes.d.ts +0 -2
  51. package/dist/commands/routes.js +0 -101
  52. package/dist/commands/status.d.ts +0 -3
  53. package/dist/commands/status.js +0 -33
  54. package/dist/commands/targets.d.ts +0 -2
  55. package/dist/commands/targets.js +0 -65
  56. package/dist/commands/teams/selector.d.ts +0 -8
  57. package/dist/commands/teams/selector.js +0 -55
  58. package/dist/commands/tools/selector.d.ts +0 -8
  59. package/dist/commands/tools/selector.js +0 -53
  60. package/dist/commands/uninstall.d.ts +0 -2
  61. package/dist/commands/uninstall.js +0 -83
  62. package/dist/components/DashboardCLI.d.ts +0 -3
  63. package/dist/components/DashboardCLI.js +0 -149
  64. package/dist/components/StatusView.d.ts +0 -10
  65. package/dist/components/StatusView.js +0 -39
  66. package/dist/config.d.ts +0 -23
  67. package/dist/config.js +0 -92
  68. package/dist/lib/arkClient.d.ts +0 -32
  69. package/dist/lib/arkClient.js +0 -43
  70. package/dist/lib/commandUtils.d.ts +0 -4
  71. package/dist/lib/commandUtils.js +0 -18
  72. package/dist/lib/commandUtils.test.d.ts +0 -1
  73. package/dist/lib/commandUtils.test.js +0 -44
  74. package/dist/lib/config.test.d.ts +0 -1
  75. package/dist/lib/config.test.js +0 -93
  76. package/dist/lib/consts.d.ts +0 -9
  77. package/dist/lib/consts.js +0 -13
  78. package/dist/lib/consts.spec.d.ts +0 -1
  79. package/dist/lib/consts.spec.js +0 -15
  80. package/dist/lib/dev/tools/analyzer.d.ts +0 -30
  81. package/dist/lib/dev/tools/analyzer.js +0 -190
  82. package/dist/lib/dev/tools/discover_tools.py +0 -392
  83. package/dist/lib/dev/tools/mcp-types.d.ts +0 -28
  84. package/dist/lib/dev/tools/mcp-types.js +0 -86
  85. package/dist/lib/dev/tools/types.d.ts +0 -50
  86. package/dist/lib/dev/tools/types.js +0 -1
  87. package/dist/lib/exec.d.ts +0 -1
  88. package/dist/lib/exec.js +0 -9
  89. package/dist/lib/gatewayManager.d.ts +0 -24
  90. package/dist/lib/gatewayManager.js +0 -85
  91. package/dist/lib/kubernetes.d.ts +0 -28
  92. package/dist/lib/kubernetes.js +0 -122
  93. package/dist/lib/portUtils.d.ts +0 -8
  94. package/dist/lib/portUtils.js +0 -39
  95. package/dist/lib/progress.d.ts +0 -128
  96. package/dist/lib/progress.js +0 -273
  97. package/dist/lib/queryRunner.d.ts +0 -22
  98. package/dist/lib/queryRunner.js +0 -142
  99. package/dist/lib/wrappers/git.d.ts +0 -2
  100. package/dist/lib/wrappers/git.js +0 -43
@@ -1,190 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- export class ArkDevToolAnalyzer {
8
- constructor() {
9
- // The Python script is always adjacent to this file
10
- // In dev: src/lib/dev/tools/discover_tools.py
11
- // In prod: dist/lib/dev/tools/discover_tools.py (copied by postbuild)
12
- this.discoverToolsScript = path.join(__dirname, 'discover_tools.py');
13
- }
14
- /**
15
- * Analyze a tool directory and return its status
16
- */
17
- async analyzeToolDirectory(toolPath) {
18
- const absolutePath = path.resolve(toolPath);
19
- // Check if path exists
20
- if (!fs.existsSync(absolutePath)) {
21
- throw new Error(`Path not found: ${absolutePath}`);
22
- }
23
- // Get project info
24
- const projectInfo = this.getProjectInfo(absolutePath);
25
- // Discover tools using Python script
26
- const discovery = await this.discoverTools(absolutePath);
27
- // Extract all tools from discovery
28
- const tools = this.extractTools(discovery);
29
- return {
30
- ...projectInfo,
31
- discovery,
32
- tools,
33
- };
34
- }
35
- /**
36
- * Get project information by checking for Python project files
37
- */
38
- getProjectInfo(dirPath) {
39
- const info = {
40
- path: dirPath,
41
- platform: 'python3',
42
- projectType: 'unknown',
43
- hasVenv: false,
44
- fastMCP: false,
45
- };
46
- // Check for virtual environment
47
- info.hasVenv =
48
- fs.existsSync(path.join(dirPath, '.venv')) ||
49
- fs.existsSync(path.join(dirPath, 'venv'));
50
- // Check Python project type and FastMCP presence
51
- const pyprojectPath = path.join(dirPath, 'pyproject.toml');
52
- const requirementsPath = path.join(dirPath, 'requirements.txt');
53
- if (fs.existsSync(pyprojectPath)) {
54
- info.projectType = 'pyproject';
55
- const content = fs.readFileSync(pyprojectPath, 'utf-8');
56
- if (content.includes('fastmcp')) {
57
- info.fastMCP = true;
58
- // Try to extract version
59
- const versionMatch = content.match(/fastmcp[>=<~]*([0-9.]+)/);
60
- if (versionMatch) {
61
- info.fastMCPVersion = versionMatch[1];
62
- }
63
- }
64
- }
65
- else if (fs.existsSync(requirementsPath)) {
66
- info.projectType = 'requirements';
67
- const content = fs.readFileSync(requirementsPath, 'utf-8');
68
- if (content.includes('fastmcp')) {
69
- info.fastMCP = true;
70
- const versionMatch = content.match(/fastmcp[>=<~]*([0-9.]+)/);
71
- if (versionMatch) {
72
- info.fastMCPVersion = versionMatch[1];
73
- }
74
- }
75
- }
76
- return info;
77
- }
78
- /**
79
- * Discover project configuration
80
- */
81
- async discoverProject(targetPath) {
82
- try {
83
- // Check if Python is available
84
- try {
85
- execSync('python3 --version', { stdio: 'ignore' });
86
- }
87
- catch {
88
- console.warn('Python 3 not found');
89
- return undefined;
90
- }
91
- // Check if discover_tools.py exists
92
- if (!fs.existsSync(this.discoverToolsScript)) {
93
- console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
94
- return undefined;
95
- }
96
- // Run the discovery script with 'project' command
97
- const result = execSync(`python3 "${this.discoverToolsScript}" project "${targetPath}"`, {
98
- encoding: 'utf-8',
99
- maxBuffer: 1024 * 1024, // 1MB buffer
100
- });
101
- return JSON.parse(result);
102
- }
103
- catch (error) {
104
- console.error('Project discovery failed:', error);
105
- return undefined;
106
- }
107
- }
108
- /**
109
- * Discover tools using the Python script
110
- */
111
- async discoverTools(targetPath) {
112
- try {
113
- // Check if Python is available
114
- try {
115
- execSync('python3 --version', { stdio: 'ignore' });
116
- }
117
- catch {
118
- console.warn('Python 3 not found, skipping tool discovery');
119
- return undefined;
120
- }
121
- // Check if discover_tools.py exists
122
- if (!fs.existsSync(this.discoverToolsScript)) {
123
- console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
124
- return undefined;
125
- }
126
- // Run the discovery script with 'tools' command
127
- const result = execSync(`python3 "${this.discoverToolsScript}" tools "${targetPath}"`, {
128
- encoding: 'utf-8',
129
- maxBuffer: 1024 * 1024 * 10, // 10MB buffer
130
- });
131
- return JSON.parse(result);
132
- }
133
- catch (error) {
134
- console.error('Tool discovery failed:', error);
135
- return undefined;
136
- }
137
- }
138
- /**
139
- * Recursively find all MCP tools in a project
140
- * This is a naive implementation that searches all Python files in the project tree
141
- */
142
- async findProjectTools(projectRoot) {
143
- try {
144
- // Check if Python is available
145
- try {
146
- execSync('python3 --version', { stdio: 'ignore' });
147
- }
148
- catch {
149
- console.warn('Python 3 not found');
150
- return null;
151
- }
152
- // Check if discover_tools.py exists
153
- if (!fs.existsSync(this.discoverToolsScript)) {
154
- console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
155
- return null;
156
- }
157
- // Run the discovery script with 'project-tools' command
158
- const result = execSync(`python3 "${this.discoverToolsScript}" project-tools "${projectRoot}"`, {
159
- encoding: 'utf-8',
160
- maxBuffer: 1024 * 1024 * 10, // 10MB buffer
161
- });
162
- return JSON.parse(result);
163
- }
164
- catch (error) {
165
- console.error('Project tools discovery failed:', error);
166
- return null;
167
- }
168
- }
169
- /**
170
- * Extract all tools from discovery result
171
- */
172
- extractTools(discovery) {
173
- if (!discovery)
174
- return [];
175
- // Check if it's a directory result
176
- if ('files' in discovery) {
177
- const dirResult = discovery;
178
- const tools = [];
179
- for (const file of dirResult.files) {
180
- if (file.success && file.tools) {
181
- tools.push(...file.tools);
182
- }
183
- }
184
- return tools;
185
- }
186
- // Single file result
187
- const fileResult = discovery;
188
- return fileResult.success ? fileResult.tools : [];
189
- }
190
- }
@@ -1,392 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Discover MCP tools in Python files using static analysis.
4
- Uses only Python stdlib - no external dependencies required.
5
- """
6
-
7
- import ast
8
- import json
9
- import sys
10
- import os
11
- from typing import Dict, List, Any
12
-
13
-
14
- class MCPToolDiscoverer(ast.NodeVisitor):
15
- """AST visitor to find MCP tool definitions"""
16
-
17
- def __init__(self):
18
- self.tools = []
19
- self.mcp_var_name = None
20
- self.imports = {}
21
- self.current_function = None # Track if we're inside a function
22
-
23
- def visit_ImportFrom(self, node):
24
- """Track imports to identify FastMCP usage"""
25
- if node.module == 'fastmcp':
26
- for alias in node.names:
27
- if alias.name == 'FastMCP':
28
- self.imports['FastMCP'] = alias.asname or alias.name
29
- self.generic_visit(node)
30
-
31
- def visit_Assign(self, node):
32
- """Find mcp = FastMCP(...) assignments"""
33
- if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
34
- var_name = node.targets[0].id
35
- if isinstance(node.value, ast.Call):
36
- if isinstance(node.value.func, ast.Name):
37
- if node.value.func.id in self.imports.values():
38
- self.mcp_var_name = var_name
39
- # Extract server name if provided
40
- if node.value.args:
41
- if isinstance(node.value.args[0], ast.Constant):
42
- self.server_name = node.value.args[0].value
43
- self.generic_visit(node)
44
-
45
- def visit_FunctionDef(self, node):
46
- """Find functions decorated with @mcp.tool() - both at module level and nested"""
47
- # Save the parent function context
48
- parent_function = self.current_function
49
- self.current_function = node.name
50
-
51
- # Check if this function is decorated as a tool
52
- for decorator in node.decorator_list:
53
- is_mcp_tool = False
54
- tool_config = {}
55
-
56
- # Check for @mcp.tool() or @mcp.tool
57
- if isinstance(decorator, ast.Call):
58
- if isinstance(decorator.func, ast.Attribute):
59
- # Check for mcp.tool where mcp is a variable
60
- if (isinstance(decorator.func.value, ast.Name) and
61
- decorator.func.value.id == self.mcp_var_name and
62
- decorator.func.attr == 'tool'):
63
- is_mcp_tool = True
64
- # Extract any config from @mcp.tool(name="...", description="...")
65
- for keyword in decorator.keywords:
66
- if isinstance(keyword.value, ast.Constant):
67
- tool_config[keyword.arg] = keyword.value.value
68
- elif isinstance(decorator, ast.Attribute):
69
- # Check for @mcp.tool without parentheses
70
- if (isinstance(decorator.value, ast.Name) and
71
- decorator.value.id == self.mcp_var_name and
72
- decorator.attr == 'tool'):
73
- is_mcp_tool = True
74
- # Also check for @mcp.tool where mcp is a parameter (e.g., in register_tools(mcp))
75
- elif (decorator.value.id if isinstance(decorator.value, ast.Name) else None) == 'mcp' and \
76
- decorator.attr == 'tool':
77
- is_mcp_tool = True
78
-
79
- if is_mcp_tool:
80
- tool_info = self.extract_function_info(node)
81
- tool_info.update(tool_config)
82
- if parent_function:
83
- tool_info['registered_in'] = parent_function
84
- self.tools.append(tool_info)
85
-
86
- # Visit nested functions to find tools defined inside this function
87
- self.generic_visit(node)
88
-
89
- # Restore parent function context
90
- self.current_function = parent_function
91
-
92
- def visit_AsyncFunctionDef(self, node):
93
- """Handle async functions the same way as regular functions"""
94
- # Save the parent function context
95
- parent_function = self.current_function
96
- self.current_function = node.name
97
-
98
- # Check if this async function is decorated as a tool
99
- for decorator in node.decorator_list:
100
- is_mcp_tool = False
101
- tool_config = {}
102
-
103
- # Check for @mcp.tool() or @mcp.tool
104
- if isinstance(decorator, ast.Call):
105
- if isinstance(decorator.func, ast.Attribute):
106
- if (isinstance(decorator.func.value, ast.Name) and
107
- decorator.func.attr == 'tool'):
108
- # Could be mcp.tool where mcp is the var or a parameter
109
- if decorator.func.value.id == self.mcp_var_name or decorator.func.value.id == 'mcp':
110
- is_mcp_tool = True
111
- for keyword in decorator.keywords:
112
- if isinstance(keyword.value, ast.Constant):
113
- tool_config[keyword.arg] = keyword.value.value
114
- elif isinstance(decorator, ast.Attribute):
115
- if decorator.attr == 'tool':
116
- # Could be @mcp.tool where mcp is the var or a parameter
117
- if isinstance(decorator.value, ast.Name):
118
- if decorator.value.id == self.mcp_var_name or decorator.value.id == 'mcp':
119
- is_mcp_tool = True
120
-
121
- if is_mcp_tool:
122
- tool_info = self.extract_function_info(node)
123
- tool_info.update(tool_config)
124
- if parent_function:
125
- tool_info['registered_in'] = parent_function
126
- self.tools.append(tool_info)
127
-
128
- # Visit nested functions
129
- self.generic_visit(node)
130
-
131
- # Restore parent function context
132
- self.current_function = parent_function
133
-
134
- def extract_function_info(self, node):
135
- """Extract function name, parameters, and docstring"""
136
- info = {
137
- 'name': node.name,
138
- 'parameters': [],
139
- 'return_type': None,
140
- 'docstring': ast.get_docstring(node)
141
- }
142
-
143
- # Extract parameters
144
- for arg in node.args.args:
145
- param = {'name': arg.arg}
146
- if arg.annotation:
147
- param['type'] = ast.unparse(arg.annotation) if hasattr(ast, 'unparse') else self.unparse_annotation(arg.annotation)
148
- info['parameters'].append(param)
149
-
150
- # Extract return type
151
- if node.returns:
152
- info['return_type'] = ast.unparse(node.returns) if hasattr(ast, 'unparse') else self.unparse_annotation(node.returns)
153
-
154
- return info
155
-
156
- def unparse_annotation(self, annotation):
157
- """Fallback for Python < 3.9 without ast.unparse"""
158
- if isinstance(annotation, ast.Name):
159
- return annotation.id
160
- elif isinstance(annotation, ast.Constant):
161
- return repr(annotation.value)
162
- else:
163
- return 'Any'
164
-
165
-
166
- def discover_tools_in_file(filepath):
167
- """Discover MCP tools in a single Python file"""
168
- try:
169
- with open(filepath, 'r') as f:
170
- content = f.read()
171
-
172
- tree = ast.parse(content)
173
- discoverer = MCPToolDiscoverer()
174
- discoverer.visit(tree)
175
-
176
- return {
177
- 'success': True,
178
- 'file': filepath,
179
- 'tools': discoverer.tools,
180
- 'uses_fastmcp': bool(discoverer.mcp_var_name),
181
- 'mcp_instance': discoverer.mcp_var_name,
182
- 'server_name': getattr(discoverer, 'server_name', None)
183
- }
184
- except SyntaxError as e:
185
- return {
186
- 'success': False,
187
- 'file': filepath,
188
- 'error': f'Syntax error: {str(e)}',
189
- 'tools': []
190
- }
191
- except Exception as e:
192
- return {
193
- 'success': False,
194
- 'file': filepath,
195
- 'error': str(e),
196
- 'tools': []
197
- }
198
-
199
-
200
- def discover_tools_in_directory(dirpath):
201
- """Discover MCP tools in Python files in root directory only (no recursion)"""
202
- results = {
203
- 'directory': dirpath,
204
- 'files': [],
205
- 'total_tools': 0,
206
- 'uses_fastmcp': False
207
- }
208
-
209
- # Only check Python files in the root directory
210
- for file in os.listdir(dirpath):
211
- if file.endswith('.py'):
212
- filepath = os.path.join(dirpath, file)
213
- if os.path.isfile(filepath):
214
- file_result = discover_tools_in_file(filepath)
215
- results['files'].append(file_result)
216
- results['total_tools'] += len(file_result.get('tools', []))
217
- if file_result.get('uses_fastmcp'):
218
- results['uses_fastmcp'] = True
219
-
220
- return results
221
-
222
-
223
- def discover_project(dirpath):
224
- """Discover project configuration and type"""
225
- result = {
226
- 'path': dirpath,
227
- 'exists': os.path.exists(dirpath),
228
- 'is_directory': os.path.isdir(dirpath) if os.path.exists(dirpath) else False,
229
- 'platform': None,
230
- 'project_type': None,
231
- 'project_file': None,
232
- 'project_name': None,
233
- 'project_version': None,
234
- 'has_fastmcp': False,
235
- 'fastmcp_version': None
236
- }
237
-
238
- if not result['exists']:
239
- return result
240
-
241
- if not result['is_directory']:
242
- return result
243
-
244
- # Check for Python project files
245
- pyproject_path = os.path.join(dirpath, 'pyproject.toml')
246
- requirements_path = os.path.join(dirpath, 'requirements.txt')
247
-
248
- if os.path.exists(pyproject_path):
249
- result['platform'] = 'python3'
250
- result['project_type'] = 'pyproject'
251
- result['project_file'] = pyproject_path
252
-
253
- # Parse pyproject.toml
254
- with open(pyproject_path, 'r') as f:
255
- content = f.read()
256
-
257
- # Extract project name and version using basic parsing
258
- # Look for [project] section
259
- import re
260
-
261
- # Try to find name in [project] section
262
- name_match = re.search(r'^\s*name\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
263
- if name_match:
264
- result['project_name'] = name_match.group(1)
265
-
266
- # Try to find version in [project] section
267
- version_match = re.search(r'^\s*version\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
268
- if version_match:
269
- result['project_version'] = version_match.group(1)
270
-
271
- # Check for fastmcp
272
- if 'fastmcp' in content:
273
- result['has_fastmcp'] = True
274
- version_match = re.search(r'fastmcp[>=<~]*([0-9.]+)', content)
275
- if version_match:
276
- result['fastmcp_version'] = version_match.group(1)
277
-
278
- elif os.path.exists(requirements_path):
279
- result['platform'] = 'python3'
280
- result['project_type'] = 'requirements'
281
- result['project_file'] = requirements_path
282
-
283
- # Check for fastmcp
284
- with open(requirements_path, 'r') as f:
285
- content = f.read()
286
- if 'fastmcp' in content:
287
- result['has_fastmcp'] = True
288
- import re
289
- version_match = re.search(r'fastmcp[>=<~]*([0-9.]+)', content)
290
- if version_match:
291
- result['fastmcp_version'] = version_match.group(1)
292
-
293
- return result
294
-
295
-
296
- def find_project_tools(project_root):
297
- """
298
- Naive recursive search for MCP tools in a Python project.
299
- Searches all Python files in the project tree, excluding common non-source directories.
300
-
301
- This is intentionally naive - it doesn't try to be smart about which files to search,
302
- it just excludes obvious non-source directories and searches everything else.
303
- """
304
- # Common directories to exclude from search
305
- EXCLUDE_DIRS = {
306
- 'venv', '.venv', 'env', '.env', 'virtualenv',
307
- 'dist', 'build', '__pycache__', '.eggs', 'egg-info',
308
- '.git', '.pytest_cache', '.mypy_cache', '.tox', 'htmlcov',
309
- 'node_modules', '.coverage', 'site-packages',
310
- # Also exclude hidden directories
311
- }
312
-
313
- results = {
314
- 'project_root': project_root,
315
- 'files_searched': 0,
316
- 'files_with_tools': 0,
317
- 'total_tools': 0,
318
- 'tools': [],
319
- 'errors': []
320
- }
321
-
322
- for root, dirs, files in os.walk(project_root):
323
- # Modify dirs in-place to skip excluded directories
324
- dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS and not d.startswith('.')]
325
-
326
- # Process Python files in this directory
327
- for file in files:
328
- if file.endswith('.py'):
329
- filepath = os.path.join(root, file)
330
- results['files_searched'] += 1
331
-
332
- try:
333
- file_result = discover_tools_in_file(filepath)
334
- if file_result.get('success') and file_result.get('tools'):
335
- results['files_with_tools'] += 1
336
- for tool in file_result['tools']:
337
- # Add source file information to each tool
338
- tool['source_file'] = os.path.relpath(filepath, project_root)
339
- results['tools'].append(tool)
340
- results['total_tools'] += 1
341
- except Exception as e:
342
- results['errors'].append({
343
- 'file': os.path.relpath(filepath, project_root),
344
- 'error': str(e)
345
- })
346
-
347
- return results
348
-
349
-
350
- def main():
351
- if len(sys.argv) < 2:
352
- print(json.dumps({
353
- 'error': 'Usage: discover_tools.py <command> <path>'
354
- }))
355
- sys.exit(1)
356
-
357
- command = sys.argv[1]
358
-
359
- if command == 'project':
360
- if len(sys.argv) < 3:
361
- print(json.dumps({'error': 'Path required for project discovery'}))
362
- sys.exit(1)
363
- path = sys.argv[2]
364
- result = discover_project(path)
365
- elif command == 'tools':
366
- if len(sys.argv) < 3:
367
- print(json.dumps({'error': 'Path required for tool discovery'}))
368
- sys.exit(1)
369
- path = sys.argv[2]
370
- if os.path.isfile(path):
371
- result = discover_tools_in_file(path)
372
- elif os.path.isdir(path):
373
- result = discover_tools_in_directory(path)
374
- else:
375
- result = {'error': f'Path not found: {path}'}
376
- elif command == 'project-tools':
377
- if len(sys.argv) < 3:
378
- print(json.dumps({'error': 'Path required for project tools discovery'}))
379
- sys.exit(1)
380
- path = sys.argv[2]
381
- if os.path.isdir(path):
382
- result = find_project_tools(path)
383
- else:
384
- result = {'error': f'Path is not a directory: {path}'}
385
- else:
386
- result = {'error': f'Unknown command: {command}'}
387
-
388
- print(json.dumps(result, indent=2))
389
-
390
-
391
- if __name__ == '__main__':
392
- main()
@@ -1,28 +0,0 @@
1
- /**
2
- * MCP Tool type utilities for discovered tools
3
- * Uses the official Model Context Protocol types from @modelcontextprotocol/sdk
4
- */
5
- import type { Tool } from '@modelcontextprotocol/sdk/types.js';
6
- export type { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
7
- /**
8
- * Convert a discovered Python tool to MCP format
9
- */
10
- export declare function toMCPTool(discoveredTool: any): Tool;
11
- /**
12
- * Format tool for OpenAI-compatible function calling
13
- */
14
- export declare function toOpenAIFunction(tool: Tool): {
15
- type: "function";
16
- function: {
17
- name: string;
18
- description: string | undefined;
19
- parameters: {
20
- [x: string]: unknown;
21
- type: "object";
22
- properties?: {
23
- [x: string]: unknown;
24
- } | undefined;
25
- required?: string[] | undefined;
26
- };
27
- };
28
- };
@@ -1,86 +0,0 @@
1
- /**
2
- * MCP Tool type utilities for discovered tools
3
- * Uses the official Model Context Protocol types from @modelcontextprotocol/sdk
4
- */
5
- /**
6
- * Convert a discovered Python tool to MCP format
7
- */
8
- export function toMCPTool(discoveredTool) {
9
- // Extract first line of docstring as description
10
- const description = discoveredTool.docstring
11
- ? discoveredTool.docstring.split('\n')[0].trim()
12
- : undefined;
13
- // Build properties from parameters
14
- const properties = {};
15
- const required = [];
16
- if (discoveredTool.parameters) {
17
- for (const param of discoveredTool.parameters) {
18
- // Map Python types to JSON Schema types
19
- let jsonType = 'string';
20
- if (param.type) {
21
- const pythonType = param.type.toLowerCase();
22
- if (pythonType === 'int' || pythonType === 'float') {
23
- jsonType = 'number';
24
- }
25
- else if (pythonType === 'bool') {
26
- jsonType = 'boolean';
27
- }
28
- else if (pythonType.includes('list') ||
29
- pythonType.includes('array')) {
30
- jsonType = 'array';
31
- }
32
- else if (pythonType.includes('dict') || pythonType === 'object') {
33
- jsonType = 'object';
34
- }
35
- }
36
- properties[param.name] = {
37
- type: jsonType,
38
- description: `Parameter ${param.name}`,
39
- };
40
- // For now, assume all parameters are required
41
- // Could be enhanced to detect optional parameters
42
- required.push(param.name);
43
- }
44
- }
45
- // Build the base Tool object
46
- const tool = {
47
- name: discoveredTool.name,
48
- title: toTitleCase(discoveredTool.name),
49
- description,
50
- inputSchema: {
51
- type: 'object',
52
- properties: Object.keys(properties).length > 0 ? properties : undefined,
53
- required: required.length > 0 ? required : undefined,
54
- },
55
- };
56
- // Add metadata if available
57
- if (discoveredTool.source_file) {
58
- tool.source_file = discoveredTool.source_file;
59
- }
60
- if (discoveredTool.registered_in) {
61
- tool.registered_in = discoveredTool.registered_in;
62
- }
63
- return tool;
64
- }
65
- /**
66
- * Convert snake_case to Title Case
67
- */
68
- function toTitleCase(snakeCase) {
69
- return snakeCase
70
- .split('_')
71
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
72
- .join(' ');
73
- }
74
- /**
75
- * Format tool for OpenAI-compatible function calling
76
- */
77
- export function toOpenAIFunction(tool) {
78
- return {
79
- type: 'function',
80
- function: {
81
- name: tool.name,
82
- description: tool.description,
83
- parameters: tool.inputSchema,
84
- },
85
- };
86
- }