@aria_asi/cli 0.2.0

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 (153) hide show
  1. package/bin/aria.js +168 -0
  2. package/dist/aria-connector/src/auth-commands.d.ts +28 -0
  3. package/dist/aria-connector/src/auth-commands.d.ts.map +1 -0
  4. package/dist/aria-connector/src/auth-commands.js +129 -0
  5. package/dist/aria-connector/src/auth-commands.js.map +1 -0
  6. package/dist/aria-connector/src/auth.d.ts +12 -0
  7. package/dist/aria-connector/src/auth.d.ts.map +1 -0
  8. package/dist/aria-connector/src/auth.js +31 -0
  9. package/dist/aria-connector/src/auth.js.map +1 -0
  10. package/dist/aria-connector/src/auto-mcp.d.ts +23 -0
  11. package/dist/aria-connector/src/auto-mcp.d.ts.map +1 -0
  12. package/dist/aria-connector/src/auto-mcp.js +994 -0
  13. package/dist/aria-connector/src/auto-mcp.js.map +1 -0
  14. package/dist/aria-connector/src/chat.d.ts +21 -0
  15. package/dist/aria-connector/src/chat.d.ts.map +1 -0
  16. package/dist/aria-connector/src/chat.js +332 -0
  17. package/dist/aria-connector/src/chat.js.map +1 -0
  18. package/dist/aria-connector/src/codebase-scanner.d.ts +7 -0
  19. package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -0
  20. package/dist/aria-connector/src/codebase-scanner.js +6 -0
  21. package/dist/aria-connector/src/codebase-scanner.js.map +1 -0
  22. package/dist/aria-connector/src/cognition-log.d.ts +17 -0
  23. package/dist/aria-connector/src/cognition-log.d.ts.map +1 -0
  24. package/dist/aria-connector/src/cognition-log.js +19 -0
  25. package/dist/aria-connector/src/cognition-log.js.map +1 -0
  26. package/dist/aria-connector/src/config.d.ts +41 -0
  27. package/dist/aria-connector/src/config.d.ts.map +1 -0
  28. package/dist/aria-connector/src/config.js +50 -0
  29. package/dist/aria-connector/src/config.js.map +1 -0
  30. package/dist/aria-connector/src/connectors/claude-code.d.ts +4 -0
  31. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -0
  32. package/dist/aria-connector/src/connectors/claude-code.js +204 -0
  33. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -0
  34. package/dist/aria-connector/src/connectors/cursor.d.ts +4 -0
  35. package/dist/aria-connector/src/connectors/cursor.d.ts.map +1 -0
  36. package/dist/aria-connector/src/connectors/cursor.js +63 -0
  37. package/dist/aria-connector/src/connectors/cursor.js.map +1 -0
  38. package/dist/aria-connector/src/connectors/opencode.d.ts +4 -0
  39. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -0
  40. package/dist/aria-connector/src/connectors/opencode.js +102 -0
  41. package/dist/aria-connector/src/connectors/opencode.js.map +1 -0
  42. package/dist/aria-connector/src/connectors/shell.d.ts +4 -0
  43. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -0
  44. package/dist/aria-connector/src/connectors/shell.js +58 -0
  45. package/dist/aria-connector/src/connectors/shell.js.map +1 -0
  46. package/dist/aria-connector/src/garden-client.d.ts +19 -0
  47. package/dist/aria-connector/src/garden-client.d.ts.map +1 -0
  48. package/dist/aria-connector/src/garden-client.js +85 -0
  49. package/dist/aria-connector/src/garden-client.js.map +1 -0
  50. package/dist/aria-connector/src/garden-control-plane.d.ts +22 -0
  51. package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -0
  52. package/dist/aria-connector/src/garden-control-plane.js +43 -0
  53. package/dist/aria-connector/src/garden-control-plane.js.map +1 -0
  54. package/dist/aria-connector/src/harness-client.d.ts +166 -0
  55. package/dist/aria-connector/src/harness-client.d.ts.map +1 -0
  56. package/dist/aria-connector/src/harness-client.js +344 -0
  57. package/dist/aria-connector/src/harness-client.js.map +1 -0
  58. package/dist/aria-connector/src/hive-client.d.ts +32 -0
  59. package/dist/aria-connector/src/hive-client.d.ts.map +1 -0
  60. package/dist/aria-connector/src/hive-client.js +69 -0
  61. package/dist/aria-connector/src/hive-client.js.map +1 -0
  62. package/dist/aria-connector/src/index.d.ts +19 -0
  63. package/dist/aria-connector/src/index.d.ts.map +1 -0
  64. package/dist/aria-connector/src/index.js +13 -0
  65. package/dist/aria-connector/src/index.js.map +1 -0
  66. package/dist/aria-connector/src/install-hooks.d.ts +18 -0
  67. package/dist/aria-connector/src/install-hooks.d.ts.map +1 -0
  68. package/dist/aria-connector/src/install-hooks.js +224 -0
  69. package/dist/aria-connector/src/install-hooks.js.map +1 -0
  70. package/dist/aria-connector/src/model-context.d.ts +8 -0
  71. package/dist/aria-connector/src/model-context.d.ts.map +1 -0
  72. package/dist/aria-connector/src/model-context.js +83 -0
  73. package/dist/aria-connector/src/model-context.js.map +1 -0
  74. package/dist/aria-connector/src/persona.d.ts +27 -0
  75. package/dist/aria-connector/src/persona.d.ts.map +1 -0
  76. package/dist/aria-connector/src/persona.js +86 -0
  77. package/dist/aria-connector/src/persona.js.map +1 -0
  78. package/dist/aria-connector/src/providers/anthropic.d.ts +4 -0
  79. package/dist/aria-connector/src/providers/anthropic.d.ts.map +1 -0
  80. package/dist/aria-connector/src/providers/anthropic.js +92 -0
  81. package/dist/aria-connector/src/providers/anthropic.js.map +1 -0
  82. package/dist/aria-connector/src/providers/deepseek.d.ts +3 -0
  83. package/dist/aria-connector/src/providers/deepseek.d.ts.map +1 -0
  84. package/dist/aria-connector/src/providers/deepseek.js +28 -0
  85. package/dist/aria-connector/src/providers/deepseek.js.map +1 -0
  86. package/dist/aria-connector/src/providers/google.d.ts +3 -0
  87. package/dist/aria-connector/src/providers/google.d.ts.map +1 -0
  88. package/dist/aria-connector/src/providers/google.js +38 -0
  89. package/dist/aria-connector/src/providers/google.js.map +1 -0
  90. package/dist/aria-connector/src/providers/ollama.d.ts +3 -0
  91. package/dist/aria-connector/src/providers/ollama.d.ts.map +1 -0
  92. package/dist/aria-connector/src/providers/ollama.js +28 -0
  93. package/dist/aria-connector/src/providers/ollama.js.map +1 -0
  94. package/dist/aria-connector/src/providers/openai.d.ts +4 -0
  95. package/dist/aria-connector/src/providers/openai.d.ts.map +1 -0
  96. package/dist/aria-connector/src/providers/openai.js +84 -0
  97. package/dist/aria-connector/src/providers/openai.js.map +1 -0
  98. package/dist/aria-connector/src/providers/openrouter.d.ts +3 -0
  99. package/dist/aria-connector/src/providers/openrouter.d.ts.map +1 -0
  100. package/dist/aria-connector/src/providers/openrouter.js +30 -0
  101. package/dist/aria-connector/src/providers/openrouter.js.map +1 -0
  102. package/dist/aria-connector/src/providers/types.d.ts +20 -0
  103. package/dist/aria-connector/src/providers/types.d.ts.map +1 -0
  104. package/dist/aria-connector/src/providers/types.js +2 -0
  105. package/dist/aria-connector/src/providers/types.js.map +1 -0
  106. package/dist/aria-connector/src/setup-wizard.d.ts +2 -0
  107. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -0
  108. package/dist/aria-connector/src/setup-wizard.js +140 -0
  109. package/dist/aria-connector/src/setup-wizard.js.map +1 -0
  110. package/dist/aria-connector/src/types.d.ts +30 -0
  111. package/dist/aria-connector/src/types.d.ts.map +1 -0
  112. package/dist/aria-connector/src/types.js +5 -0
  113. package/dist/aria-connector/src/types.js.map +1 -0
  114. package/dist/aria-web/src/lib/codebase-scanner.d.ts +127 -0
  115. package/dist/aria-web/src/lib/codebase-scanner.d.ts.map +1 -0
  116. package/dist/aria-web/src/lib/codebase-scanner.js +1730 -0
  117. package/dist/aria-web/src/lib/codebase-scanner.js.map +1 -0
  118. package/dist/cli-0.2.0.tgz +0 -0
  119. package/dist/install.sh +13 -0
  120. package/hooks/aria-harness-via-sdk.mjs +317 -0
  121. package/hooks/aria-pre-tool-gate.mjs +596 -0
  122. package/hooks/aria-preprompt-consult.mjs +175 -0
  123. package/hooks/aria-stop-gate.mjs +222 -0
  124. package/package.json +47 -0
  125. package/src/__tests__/auth-commands.test.ts +132 -0
  126. package/src/auth-commands.ts +175 -0
  127. package/src/auth.ts +33 -0
  128. package/src/auto-mcp.ts +1172 -0
  129. package/src/chat.ts +387 -0
  130. package/src/codebase-scanner.ts +18 -0
  131. package/src/cognition-log.ts +30 -0
  132. package/src/config.ts +94 -0
  133. package/src/connectors/claude-code.ts +213 -0
  134. package/src/connectors/cursor.ts +75 -0
  135. package/src/connectors/opencode.ts +115 -0
  136. package/src/connectors/shell.ts +72 -0
  137. package/src/garden-client.ts +98 -0
  138. package/src/garden-control-plane.ts +108 -0
  139. package/src/harness-client.ts +454 -0
  140. package/src/hive-client.ts +104 -0
  141. package/src/index.ts +26 -0
  142. package/src/install-hooks.ts +259 -0
  143. package/src/model-context.ts +88 -0
  144. package/src/persona.ts +113 -0
  145. package/src/providers/anthropic.ts +120 -0
  146. package/src/providers/deepseek.ts +40 -0
  147. package/src/providers/google.ts +57 -0
  148. package/src/providers/ollama.ts +43 -0
  149. package/src/providers/openai.ts +108 -0
  150. package/src/providers/openrouter.ts +42 -0
  151. package/src/providers/types.ts +35 -0
  152. package/src/setup-wizard.ts +177 -0
  153. package/src/types.ts +32 -0
@@ -0,0 +1,994 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
2
+ import { basename, extname, join, relative, resolve } from 'path';
3
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
4
+ function readFileSafe(p) {
5
+ try {
6
+ return readFileSync(p, 'utf-8');
7
+ }
8
+ catch {
9
+ return '';
10
+ }
11
+ }
12
+ function walkDir(dir, extensions) {
13
+ const results = [];
14
+ if (!existsSync(dir))
15
+ return results;
16
+ try {
17
+ const entries = readdirSync(dir);
18
+ for (const entry of entries) {
19
+ const fullPath = join(dir, entry);
20
+ let stat;
21
+ try {
22
+ stat = statSync(fullPath);
23
+ }
24
+ catch {
25
+ continue;
26
+ }
27
+ if (stat.isDirectory() && entry !== 'node_modules' && !entry.startsWith('.')) {
28
+ results.push(...walkDir(fullPath, extensions));
29
+ }
30
+ else if (stat.isFile()) {
31
+ const ext = extname(entry).toLowerCase();
32
+ if (extensions.includes(ext)) {
33
+ results.push(fullPath);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ // Permission errors, etc.
40
+ }
41
+ return results;
42
+ }
43
+ function extractJSDoc(source, beforeLineIdx) {
44
+ const lines = source.split('\n');
45
+ const docLines = [];
46
+ let i = beforeLineIdx - 1;
47
+ while (i >= 0 && lines[i].trim().startsWith('*')) {
48
+ const cleaned = lines[i].replace(/^\s*\*\s?/, '').trim();
49
+ if (cleaned && cleaned !== '/' && !cleaned.startsWith('@')) {
50
+ docLines.unshift(cleaned);
51
+ }
52
+ i--;
53
+ }
54
+ if (i >= 0 && lines[i].trim().startsWith('/**')) {
55
+ return docLines.join(' ');
56
+ }
57
+ return '';
58
+ }
59
+ function getJsDocForLine(source, lineIdx) {
60
+ return extractJSDoc(source, lineIdx);
61
+ }
62
+ function snakeCase(str) {
63
+ return str
64
+ .replace(/([A-Z])/g, '_$1')
65
+ .toLowerCase()
66
+ .replace(/^_/, '')
67
+ .replace(/[^a-z0-9_]+/g, '_')
68
+ .replace(/_+/g, '_')
69
+ .replace(/^_|_$/g, '');
70
+ }
71
+ function inferTsTypeFromValue(val) {
72
+ if (/^\d+$/.test(val))
73
+ return 'number';
74
+ if (/^(true|false)$/.test(val))
75
+ return 'boolean';
76
+ return 'string';
77
+ }
78
+ function paramFromTsSignature(param) {
79
+ const parts = param.trim().split(':');
80
+ const name = parts[0].trim().replace(/^\?/, '');
81
+ const required = !param.trim().includes('?');
82
+ let type = 'string';
83
+ let description = '';
84
+ if (parts.length > 1) {
85
+ const rawType = parts.slice(1).join(':').trim().split('=')[0].trim();
86
+ if (rawType === 'string' ||
87
+ rawType === 'number' ||
88
+ rawType === 'boolean' ||
89
+ rawType === 'string[]' ||
90
+ rawType === 'number[]') {
91
+ type = rawType;
92
+ }
93
+ }
94
+ description = type === 'number' ? `Numeric value for ${name}` : `Value for ${name}`;
95
+ return { name, type, required: required && !param.trim().startsWith('?'), description };
96
+ }
97
+ function sanitizeToolName(raw) {
98
+ return snakeCase(raw)
99
+ .replace(/^get_/, 'get_')
100
+ .replace(/[^a-z0-9_]/g, '_')
101
+ .replace(/_+/g, '_')
102
+ .replace(/^_|_$/g, '')
103
+ .substring(0, 64);
104
+ }
105
+ // ─── Express route detection ──────────────────────────────────────────────────
106
+ function detectExpressRoutes(projectPath) {
107
+ const candidates = [];
108
+ const allTsFiles = walkDir(join(projectPath, 'src'), ['.ts', '.tsx', '.js']);
109
+ const allFiles = [
110
+ ...allTsFiles,
111
+ ...walkDir(projectPath, ['.ts', '.tsx', '.js']),
112
+ ];
113
+ const pkgJsonPath = join(projectPath, 'package.json');
114
+ let isExpressProject = false;
115
+ if (existsSync(pkgJsonPath)) {
116
+ try {
117
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
118
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
119
+ isExpressProject = 'express' in allDeps;
120
+ }
121
+ catch {
122
+ // ignore
123
+ }
124
+ }
125
+ const routeFiles = allFiles.filter((f) => f.includes('/routes/') ||
126
+ f.includes('/routers/') ||
127
+ f.includes('router') ||
128
+ (isExpressProject && f.endsWith('.ts')));
129
+ for (const filePath of routeFiles) {
130
+ const content = readFileSafe(filePath);
131
+ if (!content)
132
+ continue;
133
+ const routePatterns = [];
134
+ // app.<method>(path, ...handler)
135
+ const methodRegex = /(?:app|router)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
136
+ let m;
137
+ while ((m = methodRegex.exec(content)) !== null) {
138
+ routePatterns.push({
139
+ method: m[1].toLowerCase(),
140
+ path: m[2],
141
+ filePath,
142
+ handlerBody: content.substring(m.index, content.indexOf('\n', m.index + 200) || m.index + 200),
143
+ jsdoc: getJsDocForLine(content, content.substring(0, m.index).split('\n').length - 1),
144
+ });
145
+ }
146
+ // router.route('/path').get(...)
147
+ const routeRegex = /(?:app|router)\s*\.\s*route\s*\(\s*['"`]([^'"`]+)['"`]\)\s*\.\s*(get|post|put|patch|delete)\s*\(/gi;
148
+ while ((m = routeRegex.exec(content)) !== null) {
149
+ routePatterns.push({
150
+ method: m[2].toLowerCase(),
151
+ path: m[1],
152
+ filePath,
153
+ handlerBody: content.substring(m.index, content.indexOf('\n', m.index + 200) || m.index + 200),
154
+ jsdoc: getJsDocForLine(content, content.substring(0, m.index).split('\n').length - 1),
155
+ });
156
+ }
157
+ for (const r of routePatterns) {
158
+ const params = extractRouteParams(r.path, r.handlerBody, r.jsdoc);
159
+ const toolName = generateRouteToolName(r.method, r.path, basename(filePath));
160
+ candidates.push({
161
+ name: toolName,
162
+ description: r.jsdoc || `${r.method.toUpperCase()} ${r.path}`,
163
+ source: 'express-route',
164
+ filePath,
165
+ parameters: params,
166
+ httpMethod: r.method,
167
+ routePath: r.path,
168
+ });
169
+ }
170
+ }
171
+ return candidates;
172
+ }
173
+ function extractRouteParams(routePath, handlerBody, jsdoc) {
174
+ const params = [];
175
+ // Extract :param from route
176
+ const pathParams = routePath.match(/:(\w+)/g);
177
+ if (pathParams) {
178
+ for (const p of pathParams) {
179
+ const name = p.substring(1);
180
+ params.push({ name, type: 'string', required: true, description: `Route parameter: ${name}` });
181
+ }
182
+ }
183
+ // Extract req.query.X from handler body
184
+ const queryRegex = /req\s*\.\s*query\s*\.\s*(\w+)/g;
185
+ let m;
186
+ while ((m = queryRegex.exec(handlerBody)) !== null) {
187
+ if (!params.find((p) => p.name === m[1])) {
188
+ params.push({
189
+ name: m[1],
190
+ type: 'string',
191
+ required: false,
192
+ description: `Query parameter: ${m[1]}`,
193
+ });
194
+ }
195
+ }
196
+ // Extract req.body.X from handler body
197
+ const bodyRegex = /req\s*\.\s*body\s*\.\s*(\w+)/g;
198
+ while ((m = bodyRegex.exec(handlerBody)) !== null) {
199
+ if (!params.find((p) => p.name === m[1])) {
200
+ params.push({
201
+ name: m[1],
202
+ type: 'string',
203
+ required: false,
204
+ description: `Request body field: ${m[1]}`,
205
+ });
206
+ }
207
+ }
208
+ return params;
209
+ }
210
+ function generateRouteToolName(method, routePath, fileName) {
211
+ const parts = routePath.split('/').filter(Boolean);
212
+ const resource = parts.find((p) => !p.startsWith(':') && !p.startsWith('['));
213
+ const baseName = resource || basename(fileName, extname(fileName));
214
+ const prefixMap = {
215
+ get: 'get',
216
+ post: 'create',
217
+ put: 'update',
218
+ patch: 'update',
219
+ delete: 'delete',
220
+ };
221
+ const prefix = prefixMap[method] || method;
222
+ const hasIdParam = parts.some((p) => p.startsWith(':') || p.startsWith('['));
223
+ if (hasIdParam) {
224
+ return sanitizeToolName(`${prefix}_${baseName}`);
225
+ }
226
+ if (method === 'get') {
227
+ return sanitizeToolName(`list_${baseName}`);
228
+ }
229
+ return sanitizeToolName(`${prefix}_${baseName}`);
230
+ }
231
+ // ─── Next.js API route detection ──────────────────────────────────────────────
232
+ function detectNextApiRoutes(projectPath) {
233
+ const candidates = [];
234
+ const appApiDir = join(projectPath, 'app', 'api');
235
+ const pagesApiDir = join(projectPath, 'pages', 'api');
236
+ // App Router: app/api/**/route.ts
237
+ const appRoutes = walkDir(appApiDir, ['.ts', '.tsx']);
238
+ // Pages Router: pages/api/**/*.ts
239
+ const pagesRoutes = walkDir(pagesApiDir, ['.ts', '.tsx']);
240
+ const allRouteFiles = [...new Set([...appRoutes, ...pagesRoutes])];
241
+ for (const filePath of allRouteFiles) {
242
+ const content = readFileSafe(filePath);
243
+ if (!content)
244
+ continue;
245
+ const isAppRouter = filePath.includes('/app/api/');
246
+ const relativePath = isAppRouter
247
+ ? relative(join(projectPath, 'app', 'api'), filePath).replace(/\/route\.(ts|tsx)$/, '')
248
+ : relative(join(projectPath, 'pages', 'api'), filePath).replace(/\.(ts|tsx)$/, '');
249
+ const segments = relativePath.split('/').filter(Boolean);
250
+ const patterns = [];
251
+ if (isAppRouter) {
252
+ // App Router: export async function GET/POST/PUT/PATCH/DELETE
253
+ const methodExportRegex = /export\s+async\s+function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(/gi;
254
+ let m;
255
+ while ((m = methodExportRegex.exec(content)) !== null) {
256
+ const method = m[1].toLowerCase();
257
+ const lineIdx = content.substring(0, m.index).split('\n').length - 1;
258
+ patterns.push({
259
+ method,
260
+ path: `/${segments.join('/')}`,
261
+ filePath,
262
+ handlerBody: content.substring(m.index, content.indexOf('\n', m.index + 500) || m.index + 500),
263
+ jsdoc: getJsDocForLine(content, lineIdx),
264
+ });
265
+ }
266
+ }
267
+ else {
268
+ // Pages Router: export default function handler(req, res)
269
+ const handlerRegex = /export\s+default\s+(?:async\s+)?function\s+\w*\s*\(/gi;
270
+ let m;
271
+ while ((m = handlerRegex.exec(content)) !== null) {
272
+ const handlerBody = content.substring(m.index, content.indexOf('\n', m.index + 1000) || m.index + 1000);
273
+ // Detect method from req.method branching
274
+ const methods = detectPagesMethods(handlerBody);
275
+ for (const method of methods) {
276
+ patterns.push({
277
+ method,
278
+ path: `/${segments.join('/')}`,
279
+ filePath,
280
+ handlerBody,
281
+ jsdoc: getJsDocForLine(content, content.substring(0, m.index).split('\n').length - 1),
282
+ });
283
+ }
284
+ }
285
+ }
286
+ for (const r of patterns) {
287
+ const params = extractNextParams(segments, r.handlerBody, r.jsdoc);
288
+ const fullPath = buildNextPath(segments);
289
+ const toolName = generateRouteToolName(r.method, fullPath, basename(filePath));
290
+ candidates.push({
291
+ name: toolName,
292
+ description: r.jsdoc || `${r.method.toUpperCase()} /api/${segments.join('/')}`,
293
+ source: 'next-api',
294
+ filePath,
295
+ parameters: params,
296
+ httpMethod: r.method,
297
+ routePath: `/api/${segments.join('/')}`,
298
+ });
299
+ }
300
+ }
301
+ return candidates;
302
+ }
303
+ function detectPagesMethods(handlerBody) {
304
+ const methods = [];
305
+ if (/req\.method\s*===?\s*['"]GET['"]/i.test(handlerBody) || handlerBody.includes('switch')) {
306
+ methods.push('get');
307
+ }
308
+ if (/req\.method\s*===?\s*['"]POST['"]/i.test(handlerBody))
309
+ methods.push('post');
310
+ if (/req\.method\s*===?\s*['"]PUT['"]/i.test(handlerBody))
311
+ methods.push('put');
312
+ if (/req\.method\s*===?\s*['"]PATCH['"]/i.test(handlerBody))
313
+ methods.push('patch');
314
+ if (/req\.method\s*===?\s*['"]DELETE['"]/i.test(handlerBody))
315
+ methods.push('delete');
316
+ if (methods.length === 0) {
317
+ methods.push('get');
318
+ }
319
+ return methods;
320
+ }
321
+ function extractNextParams(segments, handlerBody, _jsdoc) {
322
+ const params = [];
323
+ for (const seg of segments) {
324
+ // [...slug] or [param] or [[...param]]
325
+ const paramMatch = seg.match(/^\[(?:\.{3})?(\w+)\]$/);
326
+ if (paramMatch) {
327
+ const name = paramMatch[1];
328
+ const type = name.endsWith('Id') || name.endsWith('ID') || name.endsWith('id')
329
+ ? 'string'
330
+ : 'string';
331
+ params.push({
332
+ name: sanatizeParamName(name),
333
+ type,
334
+ required: !seg.startsWith('[['),
335
+ description: `Route parameter: ${name}`,
336
+ });
337
+ }
338
+ }
339
+ return params;
340
+ }
341
+ function sanatizeParamName(name) {
342
+ return name === 'id' || name === 'ID' || name === 'Id' ? 'id' : snakeCase(name);
343
+ }
344
+ function buildNextPath(segments) {
345
+ return '/' + segments.map((s) => (s.startsWith('[') ? `:${s.replace(/[\[\]]/g, '')}` : s)).join('/');
346
+ }
347
+ // ─── CLI script detection ─────────────────────────────────────────────────────
348
+ function detectCliScripts(projectPath) {
349
+ const candidates = [];
350
+ const pkgJsonPath = join(projectPath, 'package.json');
351
+ // Parse package.json bin field
352
+ if (existsSync(pkgJsonPath)) {
353
+ try {
354
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
355
+ if (pkg.bin) {
356
+ const bins = typeof pkg.bin === 'string' ? { [pkg.name]: pkg.bin } : pkg.bin;
357
+ for (const [binName, binPath] of Object.entries(bins)) {
358
+ const fullPath = resolve(projectPath, binPath);
359
+ if (existsSync(fullPath)) {
360
+ const content = readFileSafe(fullPath);
361
+ const params = extractCliParams(content);
362
+ const description = getJsDocForLine(content, 0) || `${binName} CLI command`;
363
+ candidates.push({
364
+ name: sanitizeToolName(`run_${binName}`),
365
+ description: pkg.description || description,
366
+ source: 'cli-script',
367
+ filePath: fullPath,
368
+ parameters: params,
369
+ });
370
+ }
371
+ }
372
+ }
373
+ // Parse scripts field too
374
+ if (pkg.scripts) {
375
+ for (const [scriptName, scriptCmd] of Object.entries(pkg.scripts)) {
376
+ const params = extractScriptParams(scriptCmd);
377
+ candidates.push({
378
+ name: sanitizeToolName(`run_${scriptName}`),
379
+ description: `Run npm script: ${scriptName} — ${scriptCmd}`,
380
+ source: 'cli-script',
381
+ filePath: pkgJsonPath,
382
+ parameters: params,
383
+ });
384
+ }
385
+ }
386
+ }
387
+ catch {
388
+ // ignore
389
+ }
390
+ }
391
+ // Scan bin/ and scripts/ directories
392
+ for (const dir of ['bin', 'scripts']) {
393
+ const dirPath = join(projectPath, dir);
394
+ if (!existsSync(dirPath))
395
+ continue;
396
+ const scriptFiles = walkDir(dirPath, ['.ts', '.js', '.sh', '.mjs', '.cjs']);
397
+ for (const filePath of scriptFiles) {
398
+ const fname = basename(filePath, extname(filePath));
399
+ const content = readFileSafe(filePath);
400
+ const params = extractCliParams(content);
401
+ candidates.push({
402
+ name: sanitizeToolName(`run_${fname}`),
403
+ description: `CLI script: ${fname}`,
404
+ source: 'cli-script',
405
+ filePath,
406
+ parameters: params,
407
+ });
408
+ }
409
+ }
410
+ return candidates;
411
+ }
412
+ function extractCliParams(content) {
413
+ const params = [];
414
+ // commander/yargs style: .option('--name <type>', 'desc')
415
+ const optionRegex = /\.(?:option|requiredOption)\s*\(\s*['"](--?\w+)\s*(?:<(\w+)>)?['"],\s*['"]([^'"]*)['"]/g;
416
+ let m;
417
+ while ((m = optionRegex.exec(content)) !== null) {
418
+ const flag = m[1].replace(/^--?/, '');
419
+ const type = m[2] || 'string';
420
+ params.push({
421
+ name: flag,
422
+ type: type === 'number' ? 'number' : 'string',
423
+ required: content.includes('requiredOption') || false,
424
+ description: m[3] || `CLI flag: ${m[1]}`,
425
+ });
426
+ }
427
+ // process.argv style
428
+ const argRegex = /process\.argv\[\s*(\d+)\s*\]/g;
429
+ let posFound = false;
430
+ while ((m = argRegex.exec(content)) !== null) {
431
+ posFound = true;
432
+ }
433
+ if (posFound && params.length === 0) {
434
+ params.push({
435
+ name: 'args',
436
+ type: 'string',
437
+ required: false,
438
+ description: 'Command arguments',
439
+ });
440
+ }
441
+ return params;
442
+ }
443
+ function extractScriptParams(scriptCmd) {
444
+ const params = [];
445
+ // Look for environment variable references in script commands
446
+ const envRegex = /\$\{?(\w+)\}?/g;
447
+ let m;
448
+ while ((m = envRegex.exec(scriptCmd)) !== null) {
449
+ const envName = m[1];
450
+ if (!/^(npm_|npm_config|PATH|HOME|USER|NODE)/.test(envName)) {
451
+ params.push({
452
+ name: snakeCase(envName),
453
+ type: 'string',
454
+ required: false,
455
+ description: `Environment variable: ${envName}`,
456
+ });
457
+ }
458
+ }
459
+ return params;
460
+ }
461
+ // ─── TypeScript exported async function detection ─────────────────────────────
462
+ function detectExportedFunctions(projectPath) {
463
+ const candidates = [];
464
+ const allTsFiles = walkDir(join(projectPath, 'src'), ['.ts', '.tsx']);
465
+ const extraFiles = walkDir(join(projectPath, 'lib'), ['.ts', '.tsx']);
466
+ const files = [...new Set([...allTsFiles, ...extraFiles])];
467
+ for (const filePath of files) {
468
+ const content = readFileSafe(filePath);
469
+ if (!content || content.length > 200_000)
470
+ continue;
471
+ // Match: export async function name(args): ReturnType {
472
+ const funcRegex = /export\s+async\s+function\s+(\w+)\s*\(\s*([^)]*)\)\s*(?::\s*([^{]+?))?\s*\{/g;
473
+ let m;
474
+ while ((m = funcRegex.exec(content)) !== null) {
475
+ const funcName = m[1];
476
+ const rawParams = m[2] || '';
477
+ const returnType = (m[3] || '').trim();
478
+ const lineIdx = content.substring(0, m.index).split('\n').length - 1;
479
+ const jsdoc = getJsDocForLine(content, lineIdx);
480
+ const params = parseTsParams(rawParams);
481
+ // Skip internal/private-looking functions
482
+ if (funcName.startsWith('_') || funcName.startsWith('#'))
483
+ continue;
484
+ // Skip React components
485
+ if (/^[A-Z]/.test(funcName) && !returnType.includes('Promise'))
486
+ continue;
487
+ const toolName = sanitizeToolName(funcName);
488
+ const description = jsdoc ||
489
+ (returnType
490
+ ? `Calls ${funcName} (returns ${returnType})`
491
+ : `Calls ${funcName}`);
492
+ candidates.push({
493
+ name: toolName,
494
+ description,
495
+ source: 'exported-function',
496
+ filePath,
497
+ parameters: params,
498
+ });
499
+ }
500
+ }
501
+ return deduplicateTools(candidates);
502
+ }
503
+ function parseTsParams(rawParams) {
504
+ if (!rawParams.trim())
505
+ return [];
506
+ const params = [];
507
+ let depth = 0;
508
+ let current = '';
509
+ for (const ch of rawParams) {
510
+ if (ch === '<' || ch === '(' || ch === '{' || ch === '[')
511
+ depth++;
512
+ else if (ch === '>' || ch === ')' || ch === '}' || ch === ']')
513
+ depth--;
514
+ else if (ch === ',' && depth === 0) {
515
+ if (current.trim()) {
516
+ params.push(paramFromTsSignature(current.trim()));
517
+ }
518
+ current = '';
519
+ continue;
520
+ }
521
+ current += ch;
522
+ }
523
+ if (current.trim()) {
524
+ params.push(paramFromTsSignature(current.trim()));
525
+ }
526
+ return params;
527
+ }
528
+ function deduplicateTools(candidates) {
529
+ const seen = new Map();
530
+ for (const c of candidates) {
531
+ if (!seen.has(c.name)) {
532
+ seen.set(c.name, c);
533
+ }
534
+ }
535
+ return Array.from(seen.values());
536
+ }
537
+ // ─── Python FastAPI detection ─────────────────────────────────────────────────
538
+ function detectFastApiRoutes(projectPath) {
539
+ const candidates = [];
540
+ const pyFiles = walkDir(projectPath, ['.py']);
541
+ for (const filePath of pyFiles) {
542
+ const content = readFileSafe(filePath);
543
+ if (!content)
544
+ continue;
545
+ // @app.get('/path') or @router.get('/path')
546
+ const decoratorRegex = /@(?:app|router)\s*\.\s*(get|post|put|patch|delete|options)\s*\(\s*['"]([^'"']+)['"]/gi;
547
+ let m;
548
+ while ((m = decoratorRegex.exec(content)) !== null) {
549
+ const method = m[1].toLowerCase();
550
+ const routePath = m[2];
551
+ const lineIdx = content.substring(0, m.index).split('\n').length - 1;
552
+ // Get the function line that follows
553
+ const afterDecorator = content.substring(m.index + m[0].length);
554
+ const funcMatch = afterDecorator.match(/def\s+(\w+)\s*\(/);
555
+ const funcName = funcMatch ? funcMatch[1] : 'handler';
556
+ // Get the function body for parameter detection
557
+ const restOfFile = content.substring(m.index);
558
+ const bodyMatch = restOfFile.match(/def\s+\w+\s*\(([^)]*)\)/) || [''];
559
+ const paramStr = bodyMatch[1] || '';
560
+ const params = parsePythonParams(paramStr, routePath);
561
+ const toolName = generateRouteToolName(method, routePath, funcName);
562
+ candidates.push({
563
+ name: toolName,
564
+ description: `${method.toUpperCase()} ${routePath} (${funcName})`,
565
+ source: 'fastapi-route',
566
+ filePath,
567
+ parameters: params,
568
+ httpMethod: method,
569
+ routePath,
570
+ });
571
+ }
572
+ }
573
+ return candidates;
574
+ }
575
+ function parsePythonParams(paramStr, routePath) {
576
+ const params = [];
577
+ // Extract route params
578
+ const pathParamRegex = /\{(\w+)\}/g;
579
+ let m;
580
+ while ((m = pathParamRegex.exec(routePath)) !== null) {
581
+ params.push({
582
+ name: m[1],
583
+ type: 'string',
584
+ required: true,
585
+ description: `Path parameter: ${m[1]}`,
586
+ });
587
+ }
588
+ // Extract function params that are not 'self', 'request', 'response'
589
+ const funcParams = paramStr
590
+ .split(',')
591
+ .map((p) => p.trim())
592
+ .filter((p) => p && p !== 'self' && p !== 'request' && p !== 'response');
593
+ for (const p of funcParams) {
594
+ const parts = p.split(':');
595
+ const name = parts[0].trim();
596
+ const type = parts.length > 1 ? parts[1].trim().split('=')[0].trim() : 'str';
597
+ const hasDefault = p.includes('=');
598
+ const tsType = mapPythonTypeToTs(type);
599
+ if (!params.find((x) => x.name === name)) {
600
+ params.push({
601
+ name,
602
+ type: tsType,
603
+ required: !hasDefault,
604
+ description: `Function parameter: ${name}`,
605
+ });
606
+ }
607
+ }
608
+ return params;
609
+ }
610
+ function mapPythonTypeToTs(pyType) {
611
+ const typeMap = {
612
+ int: 'number',
613
+ float: 'number',
614
+ str: 'string',
615
+ bool: 'boolean',
616
+ list: 'string[]',
617
+ dict: 'object',
618
+ None: 'string',
619
+ };
620
+ return typeMap[pyType] || 'string';
621
+ }
622
+ // ─── Main detection ───────────────────────────────────────────────────────────
623
+ export async function detectToolCandidates(projectPath) {
624
+ const absPath = resolve(projectPath);
625
+ if (!existsSync(absPath)) {
626
+ throw new Error(`Project path does not exist: ${absPath}`);
627
+ }
628
+ const isTypescript = existsSync(join(absPath, 'tsconfig.json'));
629
+ const isPython = !isTypescript && walkDir(absPath, ['.py']).length > 0;
630
+ let allCandidates = [];
631
+ if (isTypescript) {
632
+ allCandidates = [
633
+ ...detectExpressRoutes(absPath),
634
+ ...detectNextApiRoutes(absPath),
635
+ ...detectCliScripts(absPath),
636
+ ...detectExportedFunctions(absPath),
637
+ ];
638
+ }
639
+ if (isPython) {
640
+ allCandidates = [...detectFastApiRoutes(absPath), ...detectCliScripts(absPath)];
641
+ }
642
+ // Fallback: scan both
643
+ if (!isTypescript && !isPython) {
644
+ allCandidates = [
645
+ ...detectExpressRoutes(absPath),
646
+ ...detectNextApiRoutes(absPath),
647
+ ...detectFastApiRoutes(absPath),
648
+ ...detectCliScripts(absPath),
649
+ ...detectExportedFunctions(absPath),
650
+ ];
651
+ }
652
+ return deduplicateTools(allCandidates);
653
+ }
654
+ // ─── MCP Server Code Generation ───────────────────────────────────────────────
655
+ function generateServerCode(tools) {
656
+ const toolDefinitions = tools
657
+ .map((t) => {
658
+ const inputSchema = generateInputSchema(t.parameters);
659
+ return ` {
660
+ name: ${JSON.stringify(t.name)},
661
+ description: ${JSON.stringify(t.description)},
662
+ inputSchema: ${JSON.stringify(inputSchema, null, 6).replace(/\n/g, '\n ')},
663
+ }`;
664
+ })
665
+ .join(',\n');
666
+ const toolHandlers = tools
667
+ .map((t) => {
668
+ const paramExtract = t.parameters
669
+ .map((p) => ` const ${p.name} = args.${p.name};`)
670
+ .join('\n');
671
+ return ` case '${t.name}':
672
+ ${t.parameters.length > 0 ? paramExtract + '\n' : ''} // Source: ${t.filePath}
673
+ return {
674
+ content: [{ type: 'text', text: JSON.stringify({ tool: '${t.name}', status: 'ok', args, message: 'Tool ${t.name} invoked. Implement handler at ${t.filePath}' }, null, 2) }],
675
+ };`;
676
+ })
677
+ .join('\n\n');
678
+ const isTypescript = tools.some((t) => t.source !== 'fastapi-route');
679
+ return `#!/usr/bin/env node
680
+ /**
681
+ * Auto-generated MCP Server
682
+ * Generated by @aria/connector auto-mcp
683
+ *
684
+ * Tools detected: ${tools.length}
685
+ * Sources: ${[...new Set(tools.map((t) => t.source))].join(', ')}
686
+ */
687
+ ${isTypescript ? `import process from 'node:process';` : ''}
688
+ ${isTypescript ? `import { readFileSync } from 'node:fs';` : ''}
689
+ ${isTypescript ? `import { resolve, dirname } from 'node:path';` : ''}
690
+ ${isTypescript ? `import { fileURLToPath } from 'node:url';` : ''}
691
+
692
+ ${isTypescript ? `const __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);` : ''}
693
+
694
+ // ── Tool definitions ──────────────────────────────────────────────────────────
695
+
696
+ const TOOLS = [
697
+ ${toolDefinitions}
698
+ ];
699
+
700
+ // ── Tool handlers ────────────────────────────────────────────────────────────
701
+
702
+ async function callTool(name, args) {
703
+ switch (name) {
704
+ ${toolHandlers}
705
+
706
+ default:
707
+ return {
708
+ content: [{ type: 'text', text: JSON.stringify({ error: \`Unknown tool: \${name}\` }) }],
709
+ isError: true,
710
+ };
711
+ }
712
+ }
713
+
714
+ // ── MCP stdio protocol ──────────────────────────────────────────────────────
715
+
716
+ interface McpRequest {
717
+ jsonrpc: '2.0';
718
+ id?: number | string;
719
+ method: string;
720
+ params?: Record<string, unknown>;
721
+ }
722
+
723
+ interface McpResponse {
724
+ jsonrpc: '2.0';
725
+ id?: number | string;
726
+ result?: unknown;
727
+ error?: { code: number; message: string; data?: unknown };
728
+ }
729
+
730
+ function sendResponse(response: McpResponse): void {
731
+ process.stdout.write(JSON.stringify(response) + '\\n');
732
+ }
733
+
734
+ function sendNotification(method: string, params: Record<string, unknown>): void {
735
+ const notification = {
736
+ jsonrpc: '2.0' as const,
737
+ method,
738
+ params,
739
+ };
740
+ process.stdout.write(JSON.stringify(notification) + '\\n');
741
+ }
742
+
743
+ async function handleRequest(req: McpRequest): Promise<void> {
744
+ try {
745
+ switch (req.method) {
746
+ case 'initialize':
747
+ sendResponse({
748
+ jsonrpc: '2.0',
749
+ id: req.id,
750
+ result: {
751
+ protocolVersion: '2024-11-05',
752
+ serverInfo: {
753
+ name: 'auto-mcp-server',
754
+ version: '0.1.0',
755
+ },
756
+ capabilities: {
757
+ tools: {},
758
+ },
759
+ },
760
+ });
761
+ break;
762
+
763
+ case 'tools/list':
764
+ sendResponse({
765
+ jsonrpc: '2.0',
766
+ id: req.id,
767
+ result: { tools: TOOLS },
768
+ });
769
+ break;
770
+
771
+ case 'tools/call':
772
+ const result = await callTool(
773
+ (req.params as Record<string, string>)?.name,
774
+ (req.params as Record<string, unknown>)?.arguments || {},
775
+ );
776
+ sendResponse({
777
+ jsonrpc: '2.0',
778
+ id: req.id,
779
+ result,
780
+ });
781
+ break;
782
+
783
+ case 'notifications/initialized':
784
+ void 0;
785
+ break;
786
+
787
+ case 'ping':
788
+ sendResponse({
789
+ jsonrpc: '2.0',
790
+ id: req.id,
791
+ result: {},
792
+ });
793
+ break;
794
+
795
+ default:
796
+ sendResponse({
797
+ jsonrpc: '2.0',
798
+ id: req.id,
799
+ error: {
800
+ code: -32601,
801
+ message: \`Method not found: \${req.method}\`,
802
+ },
803
+ });
804
+ }
805
+ } catch (error) {
806
+ const message = error instanceof Error ? error.message : String(error);
807
+ sendResponse({
808
+ jsonrpc: '2.0',
809
+ id: req.id,
810
+ error: {
811
+ code: -32603,
812
+ message,
813
+ },
814
+ });
815
+ }
816
+ }
817
+
818
+ function main(): void {
819
+ let buffer = '';
820
+
821
+ process.stdin.setEncoding('utf-8');
822
+ process.stdin.on('data', (chunk: string) => {
823
+ buffer += chunk;
824
+
825
+ let newlineIdx: number;
826
+ while ((newlineIdx = buffer.indexOf('\\n')) !== -1) {
827
+ const line = buffer.substring(0, newlineIdx).trim();
828
+ buffer = buffer.substring(newlineIdx + 1);
829
+
830
+ if (!line) continue;
831
+
832
+ try {
833
+ const req = JSON.parse(line) as McpRequest;
834
+ handleRequest(req);
835
+ } catch {
836
+ sendResponse({
837
+ jsonrpc: '2.0',
838
+ id: undefined,
839
+ error: { code: -32700, message: 'Parse error' },
840
+ });
841
+ }
842
+ }
843
+ });
844
+
845
+ process.stdin.on('end', () => {
846
+ process.exit(0);
847
+ });
848
+
849
+ process.on('uncaughtException', (error: Error) => {
850
+ console.error('Uncaught exception:', error.message);
851
+ process.exit(1);
852
+ });
853
+
854
+ process.on('unhandledRejection', (reason: unknown) => {
855
+ console.error('Unhandled rejection:', reason instanceof Error ? reason.message : String(reason));
856
+ process.exit(1);
857
+ });
858
+ }
859
+
860
+ main();
861
+ `;
862
+ }
863
+ function generateInputSchema(params) {
864
+ const properties = {};
865
+ const required = [];
866
+ for (const p of params) {
867
+ properties[p.name] = {
868
+ type: p.type,
869
+ description: p.description,
870
+ };
871
+ if (p.required) {
872
+ required.push(p.name);
873
+ }
874
+ }
875
+ return {
876
+ type: 'object',
877
+ properties,
878
+ ...(required.length > 0 ? { required } : {}),
879
+ };
880
+ }
881
+ function generateReadme(toolCount, sourceTypes) {
882
+ const sources = sourceTypes
883
+ .map((s) => `- ${s}`)
884
+ .join('\n');
885
+ return `# Auto-MCP Server
886
+
887
+ Auto-generated MCP server with ${toolCount} detected tools.
888
+
889
+ ## Sources Detected
890
+
891
+ ${sources}
892
+
893
+ ## Usage
894
+
895
+ \`\`\`bash
896
+ # Install dependencies
897
+ npm install
898
+
899
+ # Build
900
+ npm run build
901
+
902
+ # Run (via stdio)
903
+ node dist/server.js
904
+ \`\`\`
905
+
906
+ ## Adding to Claude Desktop / Cursor / OpenCode
907
+
908
+ \`\`\`json
909
+ {
910
+ "mcpServers": {
911
+ "auto-mcp": {
912
+ "command": "node",
913
+ "args": ["dist/server.js"],
914
+ "cwd": "."
915
+ }
916
+ }
917
+ }
918
+ \`\`\`
919
+
920
+ ## Tools
921
+
922
+ ${toolCount} tools available. See \`src/server.ts\` for full tool definitions.
923
+
924
+ ## Generated by
925
+
926
+ @aria/connector auto-mcp generator
927
+ `;
928
+ }
929
+ // ─── Main generation ──────────────────────────────────────────────────────────
930
+ export async function generateMcpServer(projectPath, outputPath) {
931
+ const absProjectPath = resolve(projectPath);
932
+ const absOutputPath = resolve(outputPath);
933
+ const tools = await detectToolCandidates(absProjectPath);
934
+ // Create output directory structure
935
+ const srcDir = join(absOutputPath, 'src');
936
+ mkdirSync(srcDir, { recursive: true });
937
+ // Write package.json
938
+ const pkgJson = {
939
+ name: 'auto-mcp-server',
940
+ version: '0.1.0',
941
+ description: `Auto-generated MCP server for ${basename(absProjectPath)}`,
942
+ type: 'module',
943
+ main: './dist/src/server.js',
944
+ types: './dist/src/server.d.ts',
945
+ scripts: {
946
+ build: 'tsc',
947
+ start: 'node dist/src/server.js',
948
+ dev: 'tsc --watch',
949
+ },
950
+ dependencies: {},
951
+ devDependencies: {
952
+ '@types/node': '^22.0.0',
953
+ typescript: '^5.7.0',
954
+ },
955
+ engines: {
956
+ node: '>=20.0.0',
957
+ },
958
+ license: 'UNLICENSED',
959
+ private: true,
960
+ };
961
+ writeFileSync(join(absOutputPath, 'package.json'), JSON.stringify(pkgJson, null, 2));
962
+ // Write tsconfig.json
963
+ const tsConfig = {
964
+ compilerOptions: {
965
+ target: 'ES2022',
966
+ module: 'ES2022',
967
+ moduleResolution: 'bundler',
968
+ outDir: './dist',
969
+ strict: true,
970
+ esModuleInterop: true,
971
+ skipLibCheck: true,
972
+ forceConsistentCasingInFileNames: true,
973
+ declaration: true,
974
+ sourceMap: true,
975
+ noEmitOnError: true,
976
+ },
977
+ include: ['src/**/*.ts'],
978
+ exclude: ['node_modules', 'dist'],
979
+ };
980
+ writeFileSync(join(absOutputPath, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
981
+ // Write server.ts
982
+ const serverCode = generateServerCode(tools);
983
+ writeFileSync(join(srcDir, 'server.ts'), serverCode);
984
+ // Write README.md
985
+ const sourceTypes = [...new Set(tools.map((t) => t.source))];
986
+ const readme = generateReadme(tools.length, sourceTypes);
987
+ writeFileSync(join(absOutputPath, 'README.md'), readme);
988
+ return {
989
+ serverPath: absOutputPath,
990
+ toolCount: tools.length,
991
+ tools: tools.map((t) => t.name),
992
+ };
993
+ }
994
+ //# sourceMappingURL=auto-mcp.js.map