fa-mcp-sdk 0.4.5 → 0.4.9
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.
- package/bin/fa-mcp.js +1040 -1039
- package/cli-template/eslint.config.js +27 -147
- package/cli-template/package.json +3 -8
- package/cli-template/tsconfig.json +1 -0
- package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
- package/dist/core/_types_/config.d.ts +1 -1
- package/dist/core/_types_/config.d.ts.map +1 -1
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/ad/group-checker.d.ts.map +1 -1
- package/dist/core/ad/group-checker.js.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
- package/dist/core/agent-tester/agent-tester-router.js +8 -8
- package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
- package/dist/core/agent-tester/check-llm.d.ts.map +1 -1
- package/dist/core/agent-tester/check-llm.js +1 -1
- package/dist/core/agent-tester/check-llm.js.map +1 -1
- package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
- package/dist/core/agent-tester/services/TesterAgentService.js +53 -53
- package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
- package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
- package/dist/core/agent-tester/services/TesterMcpClientService.js +2 -2
- package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
- package/dist/core/auth/admin-auth.d.ts.map +1 -1
- package/dist/core/auth/admin-auth.js +3 -3
- package/dist/core/auth/admin-auth.js.map +1 -1
- package/dist/core/auth/basic.d.ts.map +1 -1
- package/dist/core/auth/basic.js.map +1 -1
- package/dist/core/auth/jwt.d.ts.map +1 -1
- package/dist/core/auth/jwt.js +6 -16
- package/dist/core/auth/jwt.js.map +1 -1
- package/dist/core/auth/middleware.d.ts.map +1 -1
- package/dist/core/auth/middleware.js +3 -2
- package/dist/core/auth/middleware.js.map +1 -1
- package/dist/core/auth/multi-auth.d.ts +0 -3
- package/dist/core/auth/multi-auth.d.ts.map +1 -1
- package/dist/core/auth/multi-auth.js +10 -7
- package/dist/core/auth/multi-auth.js.map +1 -1
- package/dist/core/auth/permanent.d.ts.map +1 -1
- package/dist/core/auth/permanent.js +1 -1
- package/dist/core/auth/permanent.js.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.d.ts.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js +2 -2
- package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.d.ts.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-templates.d.ts.map +1 -1
- package/dist/core/auth/token-generator/ntlm/ntlm-templates.js +222 -221
- package/dist/core/auth/token-generator/ntlm/ntlm-templates.js.map +1 -1
- package/dist/core/auth/token-generator/server.d.ts.map +1 -1
- package/dist/core/auth/token-generator/server.js +8 -8
- package/dist/core/auth/token-generator/server.js.map +1 -1
- package/dist/core/bootstrap/init-config.d.ts.map +1 -1
- package/dist/core/bootstrap/init-config.js +4 -4
- package/dist/core/bootstrap/init-config.js.map +1 -1
- package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
- package/dist/core/bootstrap/startup-info.js +4 -4
- package/dist/core/bootstrap/startup-info.js.map +1 -1
- package/dist/core/cache/cache.d.ts.map +1 -1
- package/dist/core/cache/cache.js +3 -3
- package/dist/core/cache/cache.js.map +1 -1
- package/dist/core/consul/access-points-updater.d.ts.map +1 -1
- package/dist/core/consul/access-points-updater.js +3 -3
- package/dist/core/consul/access-points-updater.js.map +1 -1
- package/dist/core/consul/deregister.d.ts.map +1 -1
- package/dist/core/consul/deregister.js +1 -1
- package/dist/core/consul/deregister.js.map +1 -1
- package/dist/core/consul/get-consul-api.d.ts.map +1 -1
- package/dist/core/consul/get-consul-api.js +3 -3
- package/dist/core/consul/get-consul-api.js.map +1 -1
- package/dist/core/db/pg-db.d.ts +1 -1
- package/dist/core/db/pg-db.d.ts.map +1 -1
- package/dist/core/db/pg-db.js +2 -2
- package/dist/core/db/pg-db.js.map +1 -1
- package/dist/core/debug.js +1 -1
- package/dist/core/debug.js.map +1 -1
- package/dist/core/init-mcp-server.d.ts.map +1 -1
- package/dist/core/init-mcp-server.js +9 -9
- package/dist/core/init-mcp-server.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +3 -3
- package/dist/core/logger.js.map +1 -1
- package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
- package/dist/core/mcp/create-mcp-server.js +1 -1
- package/dist/core/mcp/create-mcp-server.js.map +1 -1
- package/dist/core/mcp/prompts.d.ts.map +1 -1
- package/dist/core/mcp/prompts.js +1 -3
- package/dist/core/mcp/prompts.js.map +1 -1
- package/dist/core/mcp/resources.d.ts.map +1 -1
- package/dist/core/mcp/resources.js +8 -10
- package/dist/core/mcp/resources.js.map +1 -1
- package/dist/core/mcp/server-stdio.d.ts.map +1 -1
- package/dist/core/mcp/server-stdio.js.map +1 -1
- package/dist/core/utils/formatToolResult.d.ts.map +1 -1
- package/dist/core/utils/formatToolResult.js +1 -3
- package/dist/core/utils/formatToolResult.js.map +1 -1
- package/dist/core/utils/port-checker.d.ts.map +1 -1
- package/dist/core/utils/port-checker.js +1 -1
- package/dist/core/utils/port-checker.js.map +1 -1
- package/dist/core/utils/rate-limit.js +2 -2
- package/dist/core/utils/testing/McpSseClient.d.ts.map +1 -1
- package/dist/core/utils/testing/McpSseClient.js.map +1 -1
- package/dist/core/utils/testing/McpStdioClient.d.ts.map +1 -1
- package/dist/core/utils/testing/McpStdioClient.js.map +1 -1
- package/dist/core/utils/utils.d.ts.map +1 -1
- package/dist/core/utils/utils.js.map +1 -1
- package/dist/core/web/admin-router.d.ts.map +1 -1
- package/dist/core/web/admin-router.js +4 -4
- package/dist/core/web/admin-router.js.map +1 -1
- package/dist/core/web/cors.d.ts.map +1 -1
- package/dist/core/web/cors.js.map +1 -1
- package/dist/core/web/favicon-svg.d.ts.map +1 -1
- package/dist/core/web/favicon-svg.js.map +1 -1
- package/dist/core/web/home-api.d.ts.map +1 -1
- package/dist/core/web/home-api.js +4 -4
- package/dist/core/web/home-api.js.map +1 -1
- package/dist/core/web/openapi.d.ts.map +1 -1
- package/dist/core/web/openapi.js.map +1 -1
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +20 -22
- package/dist/core/web/server-http.js.map +1 -1
- package/dist/core/web/static/agent-tester/script.js +1503 -1513
- package/dist/core/web/static/home/script.js +646 -646
- package/dist/core/web/static/token-gen/script.js +561 -561
- package/dist/core/web/svg-icons.d.ts.map +1 -1
- package/dist/core/web/svg-icons.js +1 -1
- package/dist/core/web/svg-icons.js.map +1 -1
- package/package.json +2 -6
- package/scripts/copy-static.js +31 -31
- package/scripts/kill-port.js +107 -107
- package/scripts/npm/patch_node_modules.js +8 -8
- package/scripts/npm/run.js +31 -31
- package/scripts/remove-nul.js +53 -53
- package/scripts/update-doc.js +18 -18
- package/src/template/_types_/custom-config.ts +83 -83
- package/src/template/api/router.ts +86 -89
- package/src/template/custom-resources.ts +11 -11
- package/src/template/prompts/agent-brief.ts +8 -8
- package/src/template/prompts/agent-prompt.ts +10 -10
- package/src/template/prompts/custom-prompts.ts +12 -12
- package/src/template/start.ts +71 -72
- package/src/template/tools/handle-tool-call.ts +57 -56
- package/src/template/tools/tools.ts +89 -88
- package/src/tests/jest-simple-reporter.js +10 -10
- package/src/tests/mcp/sse/test-sse-npm-package.js +96 -96
- package/src/tests/mcp/test-cases.js +143 -143
- package/src/tests/mcp/test-http.js +76 -75
- package/src/tests/mcp/test-sse.js +80 -79
- package/src/tests/mcp/test-stdio.js +83 -81
- package/src/tests/utils.ts +157 -156
- package/cli-template/prompt_2026-03-17_13-53.md +0 -15
package/bin/fa-mcp.js
CHANGED
|
@@ -1,1039 +1,1040 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import yaml from 'js-yaml';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'.
|
|
45
|
-
'.
|
|
46
|
-
'.
|
|
47
|
-
'.
|
|
48
|
-
'.
|
|
49
|
-
'.
|
|
50
|
-
'.sublime-
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
* @param {string}
|
|
109
|
-
* @
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
value =
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
value =
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
value =
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
case '
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
console.log('
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
let
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
config.
|
|
682
|
-
config.
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
let
|
|
700
|
-
let
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
tp =
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
console.error(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
await
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
.replace(/"
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
let
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
'
|
|
889
|
-
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
await this.copyDirectory(path.join(PROJ_ROOT, '
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
await
|
|
908
|
-
await fs.
|
|
909
|
-
await fs.rename(path.join(targetPath, '
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
await
|
|
916
|
-
await fs.rm(path.join(targetPath, 'scripts/
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
await fs.
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
let
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
await fs.writeFile(
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
console.log('
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
console.log('\n
|
|
1016
|
-
console.log(
|
|
1017
|
-
console.log(
|
|
1018
|
-
console.log(' npm
|
|
1019
|
-
console.log(' npm
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
generator
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fss from 'fs';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const PRINT_FILLED = true;
|
|
16
|
+
const PROJ_ROOT = path.join(__dirname, '..');
|
|
17
|
+
|
|
18
|
+
const hl = (v) => chalk.bgGreen.black(v);
|
|
19
|
+
const hly = (v) => chalk.bgYellow.black(v);
|
|
20
|
+
const hp = (paramName) => ` [${chalk.magenta(paramName)}]`;
|
|
21
|
+
const formatDefaultValue = (v) => (v ? ` (default: ${hl(v)})` : '');
|
|
22
|
+
const OPTIONAL = chalk.gray(' (optional)');
|
|
23
|
+
const FROM_CONFIG = chalk.gray(' (from config)');
|
|
24
|
+
const trim = (s) => String(s || '').trim();
|
|
25
|
+
|
|
26
|
+
const printFilled = (paramName, paramValue) => {
|
|
27
|
+
if (PRINT_FILLED) {
|
|
28
|
+
console.log(` ${hp(paramName)}: ${hl(paramValue)}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const pjContent = fss.readFileSync(path.join(PROJ_ROOT, 'package.json'));
|
|
33
|
+
|
|
34
|
+
const faMcpSdkVersion = JSON.parse(pjContent).version;
|
|
35
|
+
|
|
36
|
+
// Print version and exit on -V or --version
|
|
37
|
+
const argv = process.argv.slice(2);
|
|
38
|
+
if (argv.includes('-V') || argv.includes('--version')) {
|
|
39
|
+
console.log(faMcpSdkVersion);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ALLOWED_FILES = [
|
|
44
|
+
'.git',
|
|
45
|
+
'.idea',
|
|
46
|
+
'.vscode',
|
|
47
|
+
'.swp',
|
|
48
|
+
'.swo',
|
|
49
|
+
'.DS_Store',
|
|
50
|
+
'.sublime-project',
|
|
51
|
+
'.sublime-workspace',
|
|
52
|
+
'node_modules',
|
|
53
|
+
'dist',
|
|
54
|
+
'__misc',
|
|
55
|
+
'_tmp',
|
|
56
|
+
'~last-cli-config.json',
|
|
57
|
+
'yarn.lock',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const getAsk = () => {
|
|
61
|
+
const rl = readline.createInterface({
|
|
62
|
+
input: process.stdin,
|
|
63
|
+
output: process.stdout,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const yn_ = (prompt, defaultAnswer = 'y') => new Promise((resolve) => {
|
|
67
|
+
rl.question(prompt, (v) => {
|
|
68
|
+
resolve((trim(v) || defaultAnswer).toLowerCase());
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
close: rl.close.bind(rl),
|
|
74
|
+
|
|
75
|
+
question: (prompt) => new Promise(resolve => {
|
|
76
|
+
rl.question(prompt, resolve);
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
optional: (title, paramName, defaultValue, example = undefined) => new Promise(resolve => {
|
|
80
|
+
const defaultText = formatDefaultValue(defaultValue);
|
|
81
|
+
example = example ? ` (example: ${example})` : '';
|
|
82
|
+
const prompt = `${title}${hp(paramName)}${defaultText}${example}${OPTIONAL}: `;
|
|
83
|
+
rl.question(prompt, (v) => {
|
|
84
|
+
resolve(trim(v) || trim(defaultValue));
|
|
85
|
+
});
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
yn: async (title, paramName, defaultValue = 'false') => {
|
|
89
|
+
const isTrue = /^(true|y)$/i.test(trim(defaultValue));
|
|
90
|
+
const y = isTrue ? `${hl('y')}` : 'y';
|
|
91
|
+
const n = isTrue ? 'n' : `${hl('n')}`;
|
|
92
|
+
|
|
93
|
+
const hpn = paramName ? hp(paramName) : '';
|
|
94
|
+
const prompt = `${title}${hpn} (${y}/${n}): `;
|
|
95
|
+
while (true) {
|
|
96
|
+
const answer = await yn_(prompt, defaultValue === 'true' ? 'y' : 'n');
|
|
97
|
+
if (answer === 'y' || answer === 'n') {
|
|
98
|
+
return answer === 'y';
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk.red('⚠️ Please enter "y" for yes or "n" for no.'));
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse configuration file (JSON or YAML)
|
|
108
|
+
* @param {string} filePath - Path to the configuration file
|
|
109
|
+
* @param {string} content - Content of the file
|
|
110
|
+
* @returns {object} Parsed configuration object
|
|
111
|
+
*/
|
|
112
|
+
const parseConfigFile = (filePath, content) => {
|
|
113
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
if (ext === '.json') {
|
|
117
|
+
return JSON.parse(content);
|
|
118
|
+
} else if (ext === '.yaml' || ext === '.yml') {
|
|
119
|
+
return yaml.load(content, { schema: yaml.DEFAULT_SCHEMA });
|
|
120
|
+
} else {
|
|
121
|
+
// Try to detect format by content
|
|
122
|
+
const trimmed = content.trim();
|
|
123
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
124
|
+
return JSON.parse(content);
|
|
125
|
+
} else {
|
|
126
|
+
return yaml.load(content, { schema: yaml.DEFAULT_SCHEMA });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(`Failed to parse configuration file ${filePath}: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const removeIfExists = async (targetPath, relPath, options = {}) => {
|
|
135
|
+
const fullPath = path.join(targetPath, relPath);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
let finalOptions = { force: true, ...options };
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const stat = await fs.lstat(fullPath);
|
|
142
|
+
if (stat.isDirectory() && finalOptions.recursive === undefined) {
|
|
143
|
+
finalOptions = { ...finalOptions, recursive: true };
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// lstat will crash if there is no file/folder – that's ok, just go to rm with the same options
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await fs.rm(fullPath, finalOptions);
|
|
150
|
+
} catch {
|
|
151
|
+
// ignore any deletion errors
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
class MCPGenerator {
|
|
156
|
+
constructor () {
|
|
157
|
+
this.lastConfigPath = path.join(process.cwd(), '~last-cli-config.json');
|
|
158
|
+
this.requiredParams = [
|
|
159
|
+
{
|
|
160
|
+
name: 'project.name',
|
|
161
|
+
defaultValue: '',
|
|
162
|
+
title: 'Project name for package.json and MCP server identification',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'project.description',
|
|
166
|
+
defaultValue: '',
|
|
167
|
+
title: 'Project description for package.json',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'project.productName',
|
|
171
|
+
defaultValue: '',
|
|
172
|
+
title: 'Product name displayed in UI and documentation',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'port',
|
|
176
|
+
defaultValue: '3000',
|
|
177
|
+
title: 'Web server port for HTTP endpoints and MCP protocol',
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
this.optionalParams = [
|
|
182
|
+
{
|
|
183
|
+
name: 'author.name',
|
|
184
|
+
defaultValue: '',
|
|
185
|
+
title: 'Author name for package.json',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'author.email',
|
|
189
|
+
defaultValue: '',
|
|
190
|
+
title: 'Author email for package.json',
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
name: 'git-base-url',
|
|
195
|
+
defaultValue: 'github.com/username',
|
|
196
|
+
title: 'Git repository base URL',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'consul.agent.dev.dc',
|
|
200
|
+
defaultValue: '',
|
|
201
|
+
title: 'Development Consul Datacenter to search for services',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'consul.agent.dev.host',
|
|
205
|
+
defaultValue: 'consul.my.ui',
|
|
206
|
+
title: 'Development Consul UI host',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'consul.agent.dev.token',
|
|
210
|
+
defaultValue: '***',
|
|
211
|
+
title: 'Token for accessing Development Consul Datacenter',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'consul.agent.prd.dc',
|
|
215
|
+
defaultValue: '',
|
|
216
|
+
title: 'Production Consul Datacenter to search for services',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'consul.agent.prd.host',
|
|
220
|
+
defaultValue: 'consul.my.ui',
|
|
221
|
+
title: 'Production Consul UI host',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'consul.agent.prd.token',
|
|
225
|
+
defaultValue: '***',
|
|
226
|
+
title: 'Token for accessing Production Consul Datacenter',
|
|
227
|
+
},
|
|
228
|
+
// Register in Consul
|
|
229
|
+
{
|
|
230
|
+
name: 'consul.service.enable',
|
|
231
|
+
defaultValue: 'false',
|
|
232
|
+
title: 'Whether to register service in Consul',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'consul.agent.reg.token',
|
|
236
|
+
defaultValue: '***',
|
|
237
|
+
title: 'Token for registering service with Consul agent',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'consul.agent.reg.host',
|
|
241
|
+
defaultValue: '',
|
|
242
|
+
title: 'The host of the consul agent where the service will be registered',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'consul.envCode.dev',
|
|
246
|
+
defaultValue: '<envCode.dev>',
|
|
247
|
+
title: 'Development environment code for Consul service ID generation',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'consul.envCode.prod',
|
|
251
|
+
defaultValue: '<envCode.prod>',
|
|
252
|
+
title: 'Production environment code for Consul service ID generation',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'NODE_CONSUL_ENV',
|
|
256
|
+
defaultValue: '',
|
|
257
|
+
title: 'Affects how the Consul service ID is formed - as a product or development ID. Valid values: "" | "development" | "production"',
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
{
|
|
261
|
+
name: 'mcp.domain',
|
|
262
|
+
defaultValue: '',
|
|
263
|
+
title: 'Domain name for nginx configuration',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'ssl-wildcard.conf.rel.path',
|
|
267
|
+
defaultValue: 'snippets/ssl-wildcard.conf',
|
|
268
|
+
title: `The relative path to the nginx configuration file
|
|
269
|
+
in the /etc/nginx folder that specifies the SSL
|
|
270
|
+
certificate's public and private keys`,
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
{
|
|
274
|
+
name: 'webServer.auth.enabled',
|
|
275
|
+
defaultValue: 'false',
|
|
276
|
+
title: 'Whether to enable authorization by token in the MCP server',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
skip: true,
|
|
280
|
+
name: 'webServer.auth.token.encryptKey',
|
|
281
|
+
defaultValue: '***',
|
|
282
|
+
title: 'Encryption key for MCP tokens',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'webServer.auth.token.checkMCPName',
|
|
286
|
+
defaultValue: 'false',
|
|
287
|
+
title: 'Whether to check MCP name in the token',
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
skip: true,
|
|
291
|
+
name: 'projectAbsPath',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
title: 'Is it Production mode',
|
|
295
|
+
defaultValue: 'false',
|
|
296
|
+
name: 'isProduction',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
skip: true,
|
|
300
|
+
name: 'NODE_ENV',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'SERVICE_INSTANCE',
|
|
304
|
+
defaultValue: '',
|
|
305
|
+
title: 'Suffix of the service name in Consul and process manager',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
skip: true,
|
|
309
|
+
name: 'PM2_NAMESPACE',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'maintainerUrl',
|
|
313
|
+
defaultValue: '',
|
|
314
|
+
title: 'Maintainer url',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'logger.useFileLogger',
|
|
318
|
+
defaultValue: '',
|
|
319
|
+
title: 'Whether to use file logger',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
skip: true,
|
|
323
|
+
name: 'logger.dir',
|
|
324
|
+
defaultValue: '',
|
|
325
|
+
title: 'Absolute path to the folder where logs will be written',
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: 'claude.isBypassPermissions',
|
|
329
|
+
defaultValue: 'false',
|
|
330
|
+
title: 'Enable GOD Mode for Claude Code',
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
createConfigProxy (config) {
|
|
336
|
+
const { lastConfigPath } = this; // Capture this in closure
|
|
337
|
+
|
|
338
|
+
return new Proxy(config, {
|
|
339
|
+
set (target, prop, value, receiver) {
|
|
340
|
+
// Check if the value is actually changing
|
|
341
|
+
const currentValue = target[prop];
|
|
342
|
+
if (currentValue === value) {
|
|
343
|
+
return Reflect.set(target, prop, value, receiver);
|
|
344
|
+
}
|
|
345
|
+
// Regular assignment behavior first
|
|
346
|
+
const result = Reflect.set(target, prop, value, receiver);
|
|
347
|
+
// Save to file asynchronously without blocking
|
|
348
|
+
fs.writeFile(lastConfigPath, JSON.stringify(target, null, 2), 'utf8')
|
|
349
|
+
.catch(error => console.warn('⚠️ Warning: Could not save config to file:', error.message));
|
|
350
|
+
return result;
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async collectConfigData (config, isRetry = false) {
|
|
356
|
+
const ask = getAsk();
|
|
357
|
+
// Collect required parameters
|
|
358
|
+
for (const param of this.requiredParams) {
|
|
359
|
+
const { title, name } = param;
|
|
360
|
+
const currentValue = config[name];
|
|
361
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
362
|
+
|
|
363
|
+
// Skip if already has value and not in retry mode
|
|
364
|
+
if (currentValue && !isRetry) {
|
|
365
|
+
printFilled(name, currentValue);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let value;
|
|
370
|
+
const defaultText = formatDefaultValue(defaultValue);
|
|
371
|
+
|
|
372
|
+
// Keep asking until we get a valid value for required parameters
|
|
373
|
+
while (true) {
|
|
374
|
+
switch (name) {
|
|
375
|
+
case 'port':
|
|
376
|
+
value = await ask.question(`Enter port${defaultText}: `);
|
|
377
|
+
value = value || defaultValue;
|
|
378
|
+
break;
|
|
379
|
+
default:
|
|
380
|
+
value = await ask.question(`Enter ${title} [${name}]${defaultText}: `);
|
|
381
|
+
value = value.trim() || defaultValue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check if we have a valid value
|
|
385
|
+
if (value && value.trim()) {
|
|
386
|
+
break; // Exit the loop - we have a valid value
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// If no default value and no input, continue asking
|
|
390
|
+
if (!defaultValue) {
|
|
391
|
+
console.error(chalk.red(`⚠️ ${name} is required. Please enter a value.`));
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Should not reach here, but just in case
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
config[name] = value;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Self register service in consul
|
|
403
|
+
{
|
|
404
|
+
const param = this.optionalParams.find((p) => p.name === 'consul.service.enable');
|
|
405
|
+
const { title, name } = param;
|
|
406
|
+
const currentValue = config[name];
|
|
407
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
408
|
+
let shouldSkipConsulRegisterParams = currentValue === 'false';
|
|
409
|
+
|
|
410
|
+
if (currentValue && !isRetry) {
|
|
411
|
+
printFilled(name, currentValue);
|
|
412
|
+
} else {
|
|
413
|
+
const enabled = await ask.yn(title, name, defaultValue);
|
|
414
|
+
config[name] = String(enabled);
|
|
415
|
+
shouldSkipConsulRegisterParams = !enabled;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// If consul registration is enabled, collect consul parameters immediately
|
|
419
|
+
if (!shouldSkipConsulRegisterParams) {
|
|
420
|
+
const consulParams = this.optionalParams.filter(({ name: n }) => n === 'consul.agent.reg.token' || n.startsWith('consul.envCode.'));
|
|
421
|
+
for (const param of consulParams) {
|
|
422
|
+
const { title, name } = param;
|
|
423
|
+
const currentValue = config[name];
|
|
424
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
425
|
+
|
|
426
|
+
// Skip if already has value and not in retry mode
|
|
427
|
+
if (currentValue && !isRetry) {
|
|
428
|
+
printFilled(name, currentValue);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const value = await ask.question(`${title} [${name}]${formatDefaultValue(defaultValue)}${OPTIONAL}: `);
|
|
433
|
+
config[name] = trim(value) || defaultValue;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Other consul parameters
|
|
438
|
+
const consulParams = this.optionalParams.filter(({ name: n }) => n.startsWith('consul.agent.dev') || n.startsWith('consul.agent.prd'));
|
|
439
|
+
for (const param of consulParams) {
|
|
440
|
+
const { title, name } = param;
|
|
441
|
+
const currentValue = config[name];
|
|
442
|
+
if (currentValue && !isRetry) {
|
|
443
|
+
printFilled(name, currentValue);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
447
|
+
config[name] = await ask.optional(title, name, defaultValue);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Handle webServer.auth.enabled to determine if auth parameters are needed
|
|
451
|
+
{
|
|
452
|
+
const param = this.optionalParams.find((p) => p.name === 'webServer.auth.enabled');
|
|
453
|
+
const { title, name } = param;
|
|
454
|
+
const currentValue = config[name];
|
|
455
|
+
let shouldSkipAuthParams = currentValue === 'false';
|
|
456
|
+
|
|
457
|
+
if (currentValue && !isRetry) {
|
|
458
|
+
printFilled(name, currentValue);
|
|
459
|
+
} else {
|
|
460
|
+
const enabled = await ask.yn(title, name, currentValue || param.defaultValue);
|
|
461
|
+
config[name] = String(enabled);
|
|
462
|
+
shouldSkipAuthParams = !enabled;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Generate encrypt key if auth is enabled
|
|
466
|
+
if (!shouldSkipAuthParams) {
|
|
467
|
+
config['webServer.auth.token.encryptKey'] = uuidv4();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// If authentication is enabled, collect auth parameters immediately
|
|
471
|
+
if (!shouldSkipAuthParams) {
|
|
472
|
+
const authParams = this.optionalParams.filter((p) => p.name.startsWith('webServer.auth.') && p.name !== 'webServer.auth.enabled');
|
|
473
|
+
|
|
474
|
+
for (const param of authParams) {
|
|
475
|
+
const { title, name, skip } = param;
|
|
476
|
+
const currentValue = config[name];
|
|
477
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
478
|
+
|
|
479
|
+
// Skip if already has value and not in retry mode
|
|
480
|
+
if (currentValue && !isRetry) {
|
|
481
|
+
printFilled(name, currentValue);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (skip) {
|
|
485
|
+
if (name === 'webServer.auth.token.encryptKey') {
|
|
486
|
+
config[name] = uuidv4();
|
|
487
|
+
}
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
switch (name) {
|
|
491
|
+
case 'webServer.auth.token.checkMCPName':
|
|
492
|
+
config[name] = String(await ask.yn(title, name, defaultValue));
|
|
493
|
+
break;
|
|
494
|
+
default:
|
|
495
|
+
config[name] = await ask.optional(title, name, defaultValue);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Collect optional parameters
|
|
502
|
+
for (const param of this.optionalParams) {
|
|
503
|
+
const { title, name, skip } = param;
|
|
504
|
+
// Skip already processed parameters
|
|
505
|
+
if (name.startsWith('consul.') || name.startsWith('webServer.auth.')) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const currentValue = config[name];
|
|
510
|
+
const defaultValue = currentValue || param.defaultValue;
|
|
511
|
+
|
|
512
|
+
// Skip if already has value and not in retry mode
|
|
513
|
+
if (currentValue && !isRetry) {
|
|
514
|
+
printFilled(name, currentValue);
|
|
515
|
+
|
|
516
|
+
if (name === 'mcp.domain' && !config['upstream']) {
|
|
517
|
+
config['upstream'] = currentValue.replace(/\./g, '-');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (skip) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
let value;
|
|
527
|
+
switch (name) {
|
|
528
|
+
case 'git-base-url':
|
|
529
|
+
value = await ask.optional(title, name, defaultValue, 'github.com/username OR gitlab.company.com/PROJECT');
|
|
530
|
+
value = value.trim() || defaultValue;
|
|
531
|
+
break;
|
|
532
|
+
case 'author.email': {
|
|
533
|
+
let go = true;
|
|
534
|
+
while (go) {
|
|
535
|
+
value = await ask.optional(title, name, defaultValue);
|
|
536
|
+
if (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/im.test(value)) {
|
|
537
|
+
go = false;
|
|
538
|
+
} else {
|
|
539
|
+
console.log(chalk.red('⚠️ Please enter valid email or leave empty.'));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
case 'mcp.domain':
|
|
546
|
+
value = await ask.optional(title, name, defaultValue);
|
|
547
|
+
if (value) {
|
|
548
|
+
// Auto-generate upstream from mcp.domain by replacing dots with dashes
|
|
549
|
+
config.upstream = value.replace(/\./g, '-');
|
|
550
|
+
}
|
|
551
|
+
continue;
|
|
552
|
+
|
|
553
|
+
case 'maintainerUrl':
|
|
554
|
+
value = await ask.optional(title, name, defaultValue);
|
|
555
|
+
if (value) {
|
|
556
|
+
config.maintainerHtml = `<a href="${value}" target="_blank" rel="noopener" class="clickable">Support</a>`;
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
case 'logger.useFileLogger': {
|
|
560
|
+
const enabled = await ask.yn(title, name, defaultValue);
|
|
561
|
+
config[name] = String(enabled);
|
|
562
|
+
const nm = 'logger.dir';
|
|
563
|
+
if (enabled) {
|
|
564
|
+
const p = this.optionalParams.find(({ name: n }) => n === nm);
|
|
565
|
+
value = await ask.optional(p.title, nm, config[nm] || p.defaultValue);
|
|
566
|
+
if (value) {
|
|
567
|
+
config[nm] = value;
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
config[nm] = '';
|
|
571
|
+
}
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
case 'isProduction':
|
|
575
|
+
case 'claude.isBypassPermissions': {
|
|
576
|
+
const enabled = await ask.yn(title, name, defaultValue);
|
|
577
|
+
config[name] = String(enabled);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
case 'NODE_CONSUL_ENV': {
|
|
581
|
+
if (currentValue === '') {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
value = await ask.optional(title, name, defaultValue);
|
|
585
|
+
if (value === '' || value === 'development' || value === 'production') {
|
|
586
|
+
config[name] = value;
|
|
587
|
+
} else {
|
|
588
|
+
config[name] = '';
|
|
589
|
+
}
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
default:
|
|
594
|
+
value = await ask.optional(title, name, defaultValue);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (value) {
|
|
598
|
+
config[name] = value;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
ask.close();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async confirmConfiguration (config) {
|
|
605
|
+
console.log('\n📋 Configuration Summary:');
|
|
606
|
+
console.log('========================');
|
|
607
|
+
|
|
608
|
+
// Show all parameters
|
|
609
|
+
const allParams = [...this.requiredParams, ...this.optionalParams];
|
|
610
|
+
for (const param of allParams) {
|
|
611
|
+
const value = config[param.name];
|
|
612
|
+
if (value !== undefined) {
|
|
613
|
+
console.log(` ${param.name}: ${hl(value)}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const ask = getAsk();
|
|
618
|
+
let confirmed;
|
|
619
|
+
const use = config.forceAcceptConfig;
|
|
620
|
+
// Check for automatic answer from config
|
|
621
|
+
if (use === 'y' || use === 'n') {
|
|
622
|
+
confirmed = use === 'y';
|
|
623
|
+
console.log(`\nUse this configuration: ${hl('y')}es${FROM_CONFIG}`);
|
|
624
|
+
} else {
|
|
625
|
+
confirmed = await ask.yn('\nUse this configuration?', '', 'y');
|
|
626
|
+
}
|
|
627
|
+
if (confirmed) {
|
|
628
|
+
config.forceAcceptConfig = 'y';
|
|
629
|
+
} else {
|
|
630
|
+
delete config.forceAcceptConfig;
|
|
631
|
+
}
|
|
632
|
+
ask.close();
|
|
633
|
+
|
|
634
|
+
return confirmed;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async collectConfiguration () {
|
|
638
|
+
const config = {};
|
|
639
|
+
const configFile = process.argv.find((arg) => arg.endsWith('.json') || arg.endsWith('.yaml') || arg.endsWith('.yml')) ||
|
|
640
|
+
process.argv.find((arg) => arg.startsWith('--config='))?.split('=')[1];
|
|
641
|
+
|
|
642
|
+
if (configFile) {
|
|
643
|
+
try {
|
|
644
|
+
const configData = await fs.readFile(configFile, 'utf8');
|
|
645
|
+
const parsedConfig = parseConfigFile(configFile, configData);
|
|
646
|
+
Object.assign(config, parsedConfig);
|
|
647
|
+
console.log(`📋 Loaded configuration from: ${hly(configFile)}`);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
console.warn(`⚠️ Warning: Could not load config file ${configFile}: ${error.message}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Create proxy for automatic saving before starting data collection
|
|
654
|
+
const configProxy = this.createConfigProxy(config);
|
|
655
|
+
|
|
656
|
+
// Save initial state if there's any pre-loaded config
|
|
657
|
+
if (Object.keys(config).length > 0) {
|
|
658
|
+
try {
|
|
659
|
+
await fs.writeFile(this.lastConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.warn('⚠️ Warning: Could not save initial config to file:', error.message);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (configProxy.NODE_ENV === 'development') {
|
|
666
|
+
configProxy.isProduction = 'false';
|
|
667
|
+
} else if (configProxy.NODE_ENV === 'production') {
|
|
668
|
+
configProxy.isProduction = 'true';
|
|
669
|
+
}
|
|
670
|
+
if (config['logger.useFileLogger'] !== 'true') {
|
|
671
|
+
config['logger.dir'] = '';
|
|
672
|
+
}
|
|
673
|
+
let confirmed = false;
|
|
674
|
+
let isRetry = false;
|
|
675
|
+
|
|
676
|
+
// Loop until configuration is confirmed
|
|
677
|
+
while (!confirmed) {
|
|
678
|
+
await this.collectConfigData(configProxy, isRetry);
|
|
679
|
+
|
|
680
|
+
// Set NODE_ENV and PM2_NAMESPACE based on isProduction
|
|
681
|
+
config.NODE_ENV = config.isProduction === 'true' ? 'production' : 'development';
|
|
682
|
+
config.PM2_NAMESPACE = config.isProduction === 'true' ? 'prod' : 'dev';
|
|
683
|
+
config.SERVICE_INSTANCE = config.PM2_NAMESPACE;
|
|
684
|
+
|
|
685
|
+
confirmed = await this.confirmConfiguration(config);
|
|
686
|
+
|
|
687
|
+
if (!confirmed) {
|
|
688
|
+
console.log('\n🔄 Let\'s re-enter the configuration:\n');
|
|
689
|
+
isRetry = true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return config;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async getTargetPath (config = {}) {
|
|
697
|
+
const ask = getAsk();
|
|
698
|
+
|
|
699
|
+
let tp = process.cwd();
|
|
700
|
+
let createInCurrent;
|
|
701
|
+
let pPath = trim(config.projectAbsPath);
|
|
702
|
+
if (pPath) {
|
|
703
|
+
tp = path.resolve(pPath);
|
|
704
|
+
console.log(`Create project in: ${hl(tp)}${FROM_CONFIG}`);
|
|
705
|
+
} else {
|
|
706
|
+
createInCurrent = await ask.yn(`Create project in current directory? (${hl(tp)})`, '', 'n');
|
|
707
|
+
if (!createInCurrent) {
|
|
708
|
+
tp = await ask.question('Enter absolute path for project: ');
|
|
709
|
+
tp = path.resolve(tp);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
config.projectAbsPath = tp;
|
|
714
|
+
// Create directory if it doesn't exist
|
|
715
|
+
try {
|
|
716
|
+
await fs.access(tp);
|
|
717
|
+
} catch {
|
|
718
|
+
console.log('Creating directory recursively...');
|
|
719
|
+
await fs.mkdir(tp, { recursive: true });
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const errMsg = `❌ Directory ${hl(tp)} not empty - cannot create project here. Use an empty directory or specify a different path.`;
|
|
723
|
+
|
|
724
|
+
// Check if directory is empty
|
|
725
|
+
try {
|
|
726
|
+
const files = await fs.readdir(tp);
|
|
727
|
+
const firstDeprecatedFile = files.find((file) => !ALLOWED_FILES.includes(file));
|
|
728
|
+
|
|
729
|
+
if (firstDeprecatedFile) {
|
|
730
|
+
console.error(errMsg);
|
|
731
|
+
console.error(` First deprecated file: ${hl(firstDeprecatedFile)}`);
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
} catch (error) {
|
|
735
|
+
if (error.message.includes('Directory not empty')) {
|
|
736
|
+
console.error(errMsg);
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
throw new Error(`Cannot access directory: ${error.message}`);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
ask.close();
|
|
743
|
+
return tp;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async copyDirectory (source, target) {
|
|
747
|
+
const entries = await fs.readdir(source, { withFileTypes: true });
|
|
748
|
+
if (!fss.existsSync(target)) {
|
|
749
|
+
await fs.mkdir(target, { recursive: true });
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
for (const entry of entries) {
|
|
753
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') {
|
|
754
|
+
continue; // Skip node_modules & dist directories
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const sourcePath = path.join(source, entry.name);
|
|
758
|
+
const targetPath = path.join(target, entry.name);
|
|
759
|
+
|
|
760
|
+
if (entry.isDirectory()) {
|
|
761
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
762
|
+
await this.copyDirectory(sourcePath, targetPath);
|
|
763
|
+
} else {
|
|
764
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async handlePackageJson (content, config) {
|
|
770
|
+
try {
|
|
771
|
+
content = content
|
|
772
|
+
.replace(/"project\.name"/g, '"{{project.name}}"')
|
|
773
|
+
.replace(/"node \.\.\/scripts/g, '"node ./scripts');
|
|
774
|
+
// First replace all template parameters in the content string
|
|
775
|
+
let updatedContent = content;
|
|
776
|
+
for (const [param, value] of Object.entries(config)) {
|
|
777
|
+
const template = `{{${param}}}`;
|
|
778
|
+
if (updatedContent.includes(template)) {
|
|
779
|
+
updatedContent = updatedContent.replace(new RegExp(escapeRegExp(template), 'g'), value);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Now parse the updated content and handle author fields
|
|
784
|
+
const packageJson = JSON.parse(updatedContent);
|
|
785
|
+
const authorName = config['author.name'];
|
|
786
|
+
const authorEmail = config['author.email'];
|
|
787
|
+
// Handle optional author fields
|
|
788
|
+
if (!authorName && !authorEmail) {
|
|
789
|
+
delete packageJson.author;
|
|
790
|
+
} else {
|
|
791
|
+
if (!packageJson.author) {packageJson.author = {};}
|
|
792
|
+
if (authorName) {
|
|
793
|
+
packageJson.author.name = authorName;
|
|
794
|
+
}
|
|
795
|
+
if (authorEmail) {
|
|
796
|
+
packageJson.author.email = authorEmail;
|
|
797
|
+
}
|
|
798
|
+
// Remove empty author object if no fields
|
|
799
|
+
if (Object.keys(packageJson.author).length === 0) {
|
|
800
|
+
delete packageJson.author;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
packageJson.dependencies['fa-mcp-sdk'] = `^${faMcpSdkVersion}`;
|
|
805
|
+
|
|
806
|
+
if (!config.keepPostinstall) {
|
|
807
|
+
delete packageJson.scripts.postinstall;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return JSON.stringify(packageJson, null, 2);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
throw new Error(`Error processing package.json: ${error.message}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async getAllFiles (dir, skipRootDirs) {
|
|
817
|
+
const files = [];
|
|
818
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
819
|
+
|
|
820
|
+
for (const entry of entries) {
|
|
821
|
+
if (skipRootDirs && skipRootDirs.includes(entry.name)) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const fullPath = path.join(dir, entry.name);
|
|
825
|
+
|
|
826
|
+
if (entry.isDirectory()) {
|
|
827
|
+
files.push(...await this.getAllFiles(fullPath));
|
|
828
|
+
} else {
|
|
829
|
+
files.push(fullPath);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return files;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async transformTargetFile (config, targetRelPath, transformFn) {
|
|
837
|
+
const targetPath = config.projectAbsPath;
|
|
838
|
+
const targetFullPath = path.join(targetPath, targetRelPath);
|
|
839
|
+
const content = await fs.readFile(targetFullPath, 'utf8');
|
|
840
|
+
const transformedContent = transformFn(content, config);
|
|
841
|
+
await fs.writeFile(targetFullPath, transformedContent, 'utf8');
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
async replaceTemplateParameters (config) {
|
|
845
|
+
const targetPath = config.projectAbsPath;
|
|
846
|
+
const files = await this.getAllFiles(targetPath, ALLOWED_FILES);
|
|
847
|
+
const importRe = /'[^']+\/core\/index.js'/ig;
|
|
848
|
+
for (const filePath of files) {
|
|
849
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
850
|
+
let modified = false;
|
|
851
|
+
|
|
852
|
+
// Special handling for package.json
|
|
853
|
+
if (filePath.endsWith('package.json')) {
|
|
854
|
+
content = await this.handlePackageJson(content, config);
|
|
855
|
+
modified = true;
|
|
856
|
+
} else {
|
|
857
|
+
// Replace all template parameters
|
|
858
|
+
for (const [param, value] of Object.entries(config)) {
|
|
859
|
+
const template = `{{${param}}}`;
|
|
860
|
+
if (content.includes(template)) {
|
|
861
|
+
content = content.replace(new RegExp(escapeRegExp(template), 'g'), value);
|
|
862
|
+
modified = true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (importRe.test(content)) {
|
|
866
|
+
content = content.replace(importRe, '\'fa-mcp-sdk\'');
|
|
867
|
+
modified = true;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (filePath.endsWith('test-sse-npm-package.js')) {
|
|
871
|
+
content = content.replace(/http:\/\/localhost:9876/g, `http://localhost:${config.port}`);
|
|
872
|
+
modified = true;
|
|
873
|
+
}
|
|
874
|
+
if (filePath.endsWith('test-stdio.js')) {
|
|
875
|
+
content = content.replace('../dist/template/start.js', 'dist/src/start.js');
|
|
876
|
+
modified = true;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (modified) {
|
|
880
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (config['NODE_CONSUL_ENV'] === '') {
|
|
884
|
+
await this.transformTargetFile(config, '.env', (c) => c.replace(/^(NODE_CONSUL_ENV)=([^\r\n]*)/m, '#$1=$2'));
|
|
885
|
+
}
|
|
886
|
+
if (config['claude.isBypassPermissions'] === 'true') {
|
|
887
|
+
const c1 = ['sudo cp', 'sudo', 'bash', 'chmod', 'curl', 'dir', 'echo', 'git', 'find', 'grep', 'jest',
|
|
888
|
+
'mkdir', 'node', 'npm install', 'npm run', 'npm test', 'npm', 'npx', 'pkill', 'set', 'playwright', 'powershell',
|
|
889
|
+
'rm', 'taskkill', 'tasklist', 'timeout', 'turbo run', 'wc'];
|
|
890
|
+
const c2 = ['jobs', 'npm start', 'unset http_proxy'];
|
|
891
|
+
const i = ' '.repeat(8);
|
|
892
|
+
const allowBashLines = [...c1.map((c) => `${i}"Bash(${c}:*)",`), ...c2.map((c) => `${i}"Bash(${c})",`)].join('\n');
|
|
893
|
+
const transformFn = (c) => c.replace('"acceptEdits"', '"bypassPermissions"')
|
|
894
|
+
.replace(/"allow": \[\s+"Edit",/, `"allow": [\n${allowBashLines}\n${i}"Edit",`);
|
|
895
|
+
await this.transformTargetFile(config, '.claude/settings.json', transformFn);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
async createProject (config) {
|
|
900
|
+
const targetPath = config.projectAbsPath;
|
|
901
|
+
// Copy template files
|
|
902
|
+
await this.copyDirectory(path.join(PROJ_ROOT, 'cli-template'), targetPath);
|
|
903
|
+
await this.copyDirectory(path.join(PROJ_ROOT, 'src/template'), path.join(targetPath, 'src'));
|
|
904
|
+
|
|
905
|
+
const testsTargetPath = path.join(targetPath, 'tests');
|
|
906
|
+
|
|
907
|
+
await this.copyDirectory(path.join(PROJ_ROOT, 'src/tests'), testsTargetPath);
|
|
908
|
+
await fs.copyFile(path.join(targetPath, '.env.example'), path.join(targetPath, '.env'));
|
|
909
|
+
await fs.rename(path.join(targetPath, 'gitignore'), path.join(targetPath, '.gitignore'));
|
|
910
|
+
await fs.rename(path.join(targetPath, 'r'), path.join(targetPath, '.run'));
|
|
911
|
+
|
|
912
|
+
await this.copyDirectory(path.join(PROJ_ROOT, 'config'), path.join(targetPath, 'config'));
|
|
913
|
+
|
|
914
|
+
const scriptsTargetPath = path.join(targetPath, 'scripts');
|
|
915
|
+
await this.copyDirectory(path.join(PROJ_ROOT, 'scripts'), scriptsTargetPath);
|
|
916
|
+
await fs.rm(path.join(targetPath, 'scripts/copy-static.js'), { force: true });
|
|
917
|
+
await fs.rm(path.join(targetPath, 'scripts/publish.sh'), { force: true });
|
|
918
|
+
|
|
919
|
+
// Rename all .xml files in .run directory to .run.xml
|
|
920
|
+
const runDirPath = path.join(targetPath, '.run');
|
|
921
|
+
const files = await fs.readdir(runDirPath);
|
|
922
|
+
|
|
923
|
+
for (const file of files) {
|
|
924
|
+
if (file.endsWith('.xml')) {
|
|
925
|
+
const oldFilePath = path.join(runDirPath, file);
|
|
926
|
+
const newFileName = file.slice(0, -4) + '.run.xml';
|
|
927
|
+
const newFilePath = path.join(runDirPath, newFileName);
|
|
928
|
+
await fs.rename(oldFilePath, newFilePath);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Rename mcp-template.com.conf if mcp.domain is provided
|
|
933
|
+
const mcpDomain = config['mcp.domain'];
|
|
934
|
+
if (mcpDomain) {
|
|
935
|
+
try {
|
|
936
|
+
const oldConfigPath = path.join(targetPath, 'deploy/NGINX/sites-enabled/mcp-template.com.conf');
|
|
937
|
+
const newConfigPath = path.join(targetPath, 'deploy/NGINX/sites-enabled', `${mcpDomain}.conf`);
|
|
938
|
+
|
|
939
|
+
await fs.access(oldConfigPath);
|
|
940
|
+
await fs.rename(oldConfigPath, newConfigPath);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
// File doesn't exist or rename failed, which is not critical
|
|
943
|
+
console.log('⚠️ Warning: Could not rename mcp-template.com.conf file', error);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Read _local.yaml into memory and rename it to local.yaml
|
|
948
|
+
let localYamlExampleContent = '';
|
|
949
|
+
const localYamlExamplePath = path.join(targetPath, 'config', '_local.yaml');
|
|
950
|
+
const localYamlPath = path.join(targetPath, 'config', 'local.yaml');
|
|
951
|
+
try {
|
|
952
|
+
|
|
953
|
+
localYamlExampleContent = await fs.readFile(localYamlExamplePath, 'utf8');
|
|
954
|
+
} catch (error) {
|
|
955
|
+
console.log('⚠️ Warning: Could not process config/_local.yaml file:', error.message);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Replace template parameters
|
|
959
|
+
await this.replaceTemplateParameters(config);
|
|
960
|
+
|
|
961
|
+
// Replace template placeholders with defaultValue from optionalParams and save as _local.yaml
|
|
962
|
+
if (localYamlExampleContent) {
|
|
963
|
+
try {
|
|
964
|
+
let localYamlExampleModifiedContent = localYamlExampleContent;
|
|
965
|
+
let localYamlModifiedContent = localYamlExampleContent;
|
|
966
|
+
// Replace with defaultValue from optionalParams
|
|
967
|
+
for (const param of this.optionalParams) {
|
|
968
|
+
const template = `{{${param.name}}}`;
|
|
969
|
+
if (localYamlExampleModifiedContent.includes(template)) {
|
|
970
|
+
const defaultValue = param.defaultValue || '';
|
|
971
|
+
localYamlExampleModifiedContent = localYamlExampleModifiedContent.replace(new RegExp(escapeRegExp(template), 'g'), defaultValue);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Replacement of the remaining substitution places with what is in the config
|
|
976
|
+
for (const [paramName, value] of Object.entries(config)) {
|
|
977
|
+
const template = `{{${paramName}}}`;
|
|
978
|
+
if (localYamlExampleModifiedContent.includes(template)) {
|
|
979
|
+
localYamlExampleModifiedContent = localYamlExampleModifiedContent.replace(new RegExp(escapeRegExp(template), 'g'), value);
|
|
980
|
+
}
|
|
981
|
+
if (localYamlModifiedContent.includes(template)) {
|
|
982
|
+
localYamlModifiedContent = localYamlModifiedContent.replace(new RegExp(escapeRegExp(template), 'g'), value);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (!config['consul.agent.reg.host']) {
|
|
986
|
+
localYamlModifiedContent = localYamlModifiedContent.replace(/(\n +)host: '[^']*'( # The host of the consul agent)/, '$1# host: \'\'$2');
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
await fs.writeFile(localYamlPath, localYamlModifiedContent, 'utf8');
|
|
990
|
+
await fs.writeFile(localYamlExamplePath, localYamlExampleModifiedContent, 'utf8');
|
|
991
|
+
} catch (error) {
|
|
992
|
+
console.log('⚠️ Warning: Could not create config/_local.yaml file:', error.message);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const pathsToRemove = [
|
|
996
|
+
{ rel: 'package-lock.json' },
|
|
997
|
+
];
|
|
998
|
+
|
|
999
|
+
await Promise.all(
|
|
1000
|
+
pathsToRemove.map(({ rel, options }) => removeIfExists(targetPath, rel, options)),
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
async run () {
|
|
1005
|
+
console.log('MCP Server Template Generator');
|
|
1006
|
+
console.log('==================================\n');
|
|
1007
|
+
|
|
1008
|
+
try {
|
|
1009
|
+
const config = await this.collectConfiguration();
|
|
1010
|
+
const targetPath = await this.getTargetPath(config);
|
|
1011
|
+
|
|
1012
|
+
console.log(`\n📁 Creating project in: ${targetPath}`);
|
|
1013
|
+
await this.createProject(config);
|
|
1014
|
+
|
|
1015
|
+
console.log('\n✅ MCP Server template created successfully!');
|
|
1016
|
+
console.log('\n📋 Next steps:');
|
|
1017
|
+
console.log(` cd ${targetPath}`);
|
|
1018
|
+
console.log(' npm install');
|
|
1019
|
+
console.log(' npm run build');
|
|
1020
|
+
console.log(' npm start');
|
|
1021
|
+
|
|
1022
|
+
process.exit(0);
|
|
1023
|
+
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
if (error.message && !(error.stack || '').includes(String(error.message))) {
|
|
1026
|
+
console.error('\n❌ Error:', error.message);
|
|
1027
|
+
}
|
|
1028
|
+
console.error(error.stack);
|
|
1029
|
+
process.exit(1);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function escapeRegExp (string) {
|
|
1035
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Run the generator
|
|
1039
|
+
const generator = new MCPGenerator();
|
|
1040
|
+
generator.run().catch(console.error);
|