@@ -12,7 +12,9 @@ import { promisify } from 'util'
12
12
import { fileURLToPath } from "url" ;
13
13
import { ExitPromptError } from "@inquirer/core" ;
14
14
import isWsl from 'is-wsl' ;
15
+ import lodash from 'lodash' ;
15
16
17
+ const TRACES_DEFAULT_URL = "http://localhost:4318/v1/traces" ;
16
18
const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
17
19
async function getClaudeConfigDir ( ) : Promise < string > {
18
20
if ( isWsl ) {
@@ -150,7 +152,15 @@ async function createServer(directory: string, options: any = {}) {
150
152
installForClaude : {
151
153
message : "Would you like to install this server for Claude.app?" ,
152
154
required : false ,
153
- }
155
+ } ,
156
+ instrumentation : {
157
+ message : "Would you like to add OpenTelemetry instrumentation?" ,
158
+ required : false ,
159
+ } ,
160
+ traceExporterUrl : {
161
+ message : "What is the URL of the trace exporter?" ,
162
+ required : false ,
163
+ } ,
154
164
} ;
155
165
156
166
interface Question {
@@ -164,8 +174,11 @@ async function createServer(directory: string, options: any = {}) {
164
174
description: Question ;
165
175
tool: Question ;
166
176
installForClaude: Question ;
177
+ instrumentation: Question ;
178
+ traceExporterUrl: Question ;
167
179
}
168
180
181
+ // Ask questions to the user and return the answers
169
182
const answersInquirer = async ( questions : Questions ) : Promise < any > => {
170
183
const name = await input ( { ...questions . name , validate : ( value ) => value . length > 3 || 'Name must be at least 4 characters' } ) ;
171
184
const description = await input ( questions . description ) ;
@@ -180,30 +193,83 @@ async function createServer(directory: string, options: any = {}) {
180
193
message : "Would you like to install this server for Claude.app?" ,
181
194
default : true
182
195
} ) : false ;
183
- console . debug ( { name, description, tool, installForClaude } ) ;
184
- return { name, description, tool, installForClaude } ;
196
+ const instrumentation = await confirm ( {
197
+ message : "Would you like to add OpenTelemetry instrumentation?" ,
198
+ default : false
199
+ } ) ;
200
+ let traceExporterUrl ;
201
+ if ( instrumentation ) {
202
+ traceExporterUrl = await input ( {
203
+ ...questions . traceExporterUrl ,
204
+ message : "What is the URL of the trace exporter?" ,
205
+ required : true ,
206
+ default : TRACES_DEFAULT_URL ,
207
+ } ) ;
208
+ }
209
+ return { name, description, tool, installForClaude, instrumentation, traceExporterUrl } ;
185
210
} ;
186
211
187
212
const answers = await answersInquirer ( questions ) ;
188
- const { name, description, tool, installForClaude } = answers ;
189
213
214
+ // Tool name in PascalCase, removing special characters
215
+ const toolPascalCase = lodash . upperFirst ( lodash . camelCase ( answers . tool ) ) . replace ( / [ ^ a - z A - Z 0 - 9 _ ] / g, '' ) ;
216
+ // Configuration based on user answers, or default values
190
217
const config = {
191
- name : options . name || name ,
192
- description : options . description || description ,
193
- tool : options . tool || tool ,
194
- installForClaude : options . installForClaude || installForClaude
218
+ // Remove special characters from name
219
+ name : ( options . name || answers . name ) . replace ( / [ ^ a - z A - Z 0 - 9 _ ] / g, '' ) ,
220
+ description : options . description || answers . description ,
221
+ tool : options . tool || answers . tool ,
222
+ installForClaude : options . installForClaude || answers . installForClaude ,
223
+ instrumentation : options . instrumentation || answers . instrumentation ,
224
+ traceExporterUrl : options . traceExporterUrl || answers . traceExporterUrl ,
225
+ toolPascalCase,
195
226
} ;
196
227
228
+ console . debug ( `Using configuration: ${ JSON . stringify ( config , null , 2 ) } ` ) ;
197
229
const spinner = ora ( "Creating MCP server..." ) . start ( ) ;
198
230
199
231
try {
200
232
// Create project directory
201
233
await fs . mkdir ( directory ) ;
202
234
203
- // Copy template files
204
- const templateDir = path . join ( __dirname , "../template" ) ;
235
+ // render template files
236
+ const templateDir = path . join ( __dirname , "../template/project " ) ;
205
237
const files = await fs . readdir ( templateDir , { recursive : true } ) ;
238
+ await processTemplateFiles ( files , templateDir ) ;
239
+ // Process additional files if needed
240
+ if ( config . instrumentation ) {
241
+ const instrumentationDir = path . join ( __dirname , "../template/instrumentation" ) ;
242
+ const instrumentationFiles = await fs . readdir ( instrumentationDir , { recursive : true } ) ;
243
+ await processTemplateFiles ( instrumentationFiles , instrumentationDir ) ;
244
+ }
245
+
246
+ spinner . succeed ( chalk . green ( "MCP server created successfully!" ) ) ;
247
+
248
+ if ( answers . installForClaude ) {
249
+ await updateClaudeConfig ( config . name , directory ) ;
250
+ }
251
+
252
+ // Print next steps
253
+ console . log ( "\nNext steps:" ) ;
254
+ console . log ( chalk . cyan ( ` cd ${ directory } ` ) ) ;
255
+ console . log ( chalk . cyan ( " npm install" ) ) ;
256
+ console . log (
257
+ chalk . cyan ( ` npm run build ${ chalk . reset ( "# or: npm run watch" ) } ` ) ,
258
+ ) ;
259
+ console . log (
260
+ chalk . cyan (
261
+ ` npm link ${ chalk . reset ( "# optional, to make available globally" ) } \n` ,
262
+ ) ,
263
+ ) ;
264
+ console . log ( chalk . yellow ( "Test it in your browser:" ) ) ;
265
+ console . log ( chalk . cyan ( " npm run inspector\n" ) ) ;
266
+ } catch ( error ) {
267
+ spinner . fail ( chalk . red ( "Failed to create MCP server" ) ) ;
268
+ console . error ( error ) ;
269
+ process . exit ( 1 ) ;
270
+ }
206
271
272
+ async function processTemplateFiles ( files : string [ ] , templateDir : string ) {
207
273
for ( const file of files ) {
208
274
const sourcePath = path . join ( templateDir , file ) ;
209
275
const stats = await fs . stat ( sourcePath ) ;
@@ -231,37 +297,12 @@ async function createServer(directory: string, options: any = {}) {
231
297
// Write processed file
232
298
await fs . writeFile ( targetPath , content ) ;
233
299
}
234
-
235
- spinner . succeed ( chalk . green ( "MCP server created successfully!" ) ) ;
236
-
237
- if ( answers . installForClaude ) {
238
- await updateClaudeConfig ( config . name , directory ) ;
239
- }
240
-
241
- // Print next steps
242
- console . log ( "\nNext steps:" ) ;
243
- console . log ( chalk . cyan ( ` cd ${ directory } ` ) ) ;
244
- console . log ( chalk . cyan ( " npm install" ) ) ;
245
- console . log (
246
- chalk . cyan ( ` npm run build ${ chalk . reset ( "# or: npm run watch" ) } ` ) ,
247
- ) ;
248
- console . log (
249
- chalk . cyan (
250
- ` npm link ${ chalk . reset ( "# optional, to make available globally" ) } \n` ,
251
- ) ,
252
- ) ;
253
- console . log ( chalk . yellow ( "Test it in your browser:" ) ) ;
254
- console . log ( chalk . cyan ( " npm run inspector\n" ) ) ;
255
- } catch ( error ) {
256
- spinner . fail ( chalk . red ( "Failed to create MCP server" ) ) ;
257
- console . error ( error ) ;
258
- process . exit ( 1 ) ;
259
300
}
260
301
}
261
302
262
303
// detect ctrl+c and exit
263
304
process . on ( "SIGINT" , ( ) => {
264
- console . log ( chalk . yellow ( "\nAborted." ) ) ;
305
+ console . log ( chalk . red ( "\nAborted, process interrupted ." ) ) ;
265
306
process . exit ( 0 ) ;
266
307
} ) ;
267
308
@@ -273,6 +314,9 @@ try {
273
314
. option ( "-n, --name <name>" , "Name of the server" )
274
315
. option ( "-d, --description <description>" , "Description of the server" )
275
316
. option ( "-t, --tool <tool>" , "Name of the tool to create" )
317
+ // .option("--no-install", "Skip configure the server for Claude.app", true)
318
+ . option ( "-i, --instrumentation" , "Add OpenTelemetry instrumentation" , false )
319
+ . option ( "-e, --trace-exporter-url <url>" , "URL of the trace exporter, only if instrumentation" , TRACES_DEFAULT_URL )
276
320
. action ( createServer ) ;
277
321
278
322
program . showHelpAfterError ( '(add --help for additional information)' ) ;
0 commit comments