@stackkedjohn/mcp-factory-cli 0.1.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 (60) hide show
  1. package/README.md +100 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +33 -0
  4. package/dist/commands/create.d.ts +4 -0
  5. package/dist/commands/create.js +56 -0
  6. package/dist/commands/install.d.ts +1 -0
  7. package/dist/commands/install.js +79 -0
  8. package/dist/commands/list.d.ts +1 -0
  9. package/dist/commands/list.js +24 -0
  10. package/dist/commands/validate.d.ts +1 -0
  11. package/dist/commands/validate.js +27 -0
  12. package/dist/generator/analyzer.d.ts +2 -0
  13. package/dist/generator/analyzer.js +14 -0
  14. package/dist/generator/engine.d.ts +10 -0
  15. package/dist/generator/engine.js +46 -0
  16. package/dist/parsers/ai-parser.d.ts +2 -0
  17. package/dist/parsers/ai-parser.js +7 -0
  18. package/dist/parsers/detector.d.ts +6 -0
  19. package/dist/parsers/detector.js +38 -0
  20. package/dist/parsers/openapi.d.ts +5 -0
  21. package/dist/parsers/openapi.js +205 -0
  22. package/dist/parsers/postman.d.ts +2 -0
  23. package/dist/parsers/postman.js +4 -0
  24. package/dist/registry/manager.d.ts +13 -0
  25. package/dist/registry/manager.js +43 -0
  26. package/dist/schema/api-schema.d.ts +77 -0
  27. package/dist/schema/api-schema.js +1 -0
  28. package/dist/utils/errors.d.ts +13 -0
  29. package/dist/utils/errors.js +26 -0
  30. package/dist/utils/logger.d.ts +7 -0
  31. package/dist/utils/logger.js +19 -0
  32. package/docs/plans/2026-02-02-mcp-factory-design.md +306 -0
  33. package/docs/plans/2026-02-02-mcp-factory-implementation.md +1866 -0
  34. package/package.json +48 -0
  35. package/src/cli.ts +41 -0
  36. package/src/commands/create.ts +65 -0
  37. package/src/commands/install.ts +92 -0
  38. package/src/commands/list.ts +28 -0
  39. package/src/commands/validate.ts +29 -0
  40. package/src/generator/analyzer.ts +20 -0
  41. package/src/generator/engine.ts +73 -0
  42. package/src/parsers/ai-parser.ts +10 -0
  43. package/src/parsers/detector.ts +49 -0
  44. package/src/parsers/openapi.ts +238 -0
  45. package/src/parsers/postman.ts +6 -0
  46. package/src/registry/manager.ts +62 -0
  47. package/src/schema/api-schema.ts +87 -0
  48. package/src/utils/errors.ts +27 -0
  49. package/src/utils/logger.ts +23 -0
  50. package/templates/README.md.hbs +40 -0
  51. package/templates/client.ts.hbs +45 -0
  52. package/templates/index.ts.hbs +36 -0
  53. package/templates/package.json.hbs +20 -0
  54. package/templates/test.ts.hbs +1 -0
  55. package/templates/tools.ts.hbs +38 -0
  56. package/templates/tsconfig.json.hbs +13 -0
  57. package/templates/types.ts.hbs +1 -0
  58. package/templates/validation.ts.hbs +1 -0
  59. package/test-fixtures/weather-api.json +49 -0
  60. package/tsconfig.json +17 -0
@@ -0,0 +1,1866 @@
1
+ # MCP Factory Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Build a CLI tool that generates production-ready MCP servers from API documentation in one command.
6
+
7
+ **Architecture:** Three-stage pipeline (input processing → normalization → code generation) with template-based generation, smart format detection, and optional AI parsing for unstructured docs.
8
+
9
+ **Tech Stack:** TypeScript, Commander.js, Handlebars, Zod, Anthropic SDK, openapi-typescript
10
+
11
+ ---
12
+
13
+ ## Phase 1: Project Foundation
14
+
15
+ ### Task 1: Initialize Project Structure
16
+
17
+ **Files:**
18
+ - Create: `package.json`
19
+ - Create: `tsconfig.json`
20
+ - Create: `.gitignore`
21
+ - Create: `src/cli.ts`
22
+
23
+ **Step 1: Initialize npm package**
24
+
25
+ Run: `npm init -y`
26
+ Expected: Creates package.json
27
+
28
+ **Step 2: Update package.json**
29
+
30
+ ```json
31
+ {
32
+ "name": "@mcp-factory/cli",
33
+ "version": "0.1.0",
34
+ "description": "Generate production-ready MCP servers from API documentation",
35
+ "main": "dist/cli.js",
36
+ "bin": {
37
+ "mcp-factory": "./dist/cli.js"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "dev": "tsc --watch",
42
+ "test": "node --test dist/**/*.test.js"
43
+ },
44
+ "keywords": ["mcp", "api", "codegen", "cli"],
45
+ "author": "",
46
+ "license": "MIT"
47
+ }
48
+ ```
49
+
50
+ **Step 3: Install dependencies**
51
+
52
+ Run: `npm install commander handlebars zod @anthropic-ai/sdk yaml`
53
+ Run: `npm install -D typescript @types/node tsx`
54
+
55
+ Expected: All packages installed
56
+
57
+ **Step 4: Create tsconfig.json**
58
+
59
+ ```json
60
+ {
61
+ "compilerOptions": {
62
+ "target": "ES2022",
63
+ "module": "NodeNext",
64
+ "moduleResolution": "NodeNext",
65
+ "outDir": "./dist",
66
+ "rootDir": "./src",
67
+ "strict": true,
68
+ "esModuleInterop": true,
69
+ "skipLibCheck": true,
70
+ "forceConsistentCasingInFileNames": true,
71
+ "resolveJsonModule": true,
72
+ "declaration": true
73
+ },
74
+ "include": ["src/**/*"],
75
+ "exclude": ["node_modules", "dist"]
76
+ }
77
+ ```
78
+
79
+ **Step 5: Create basic CLI entry point**
80
+
81
+ ```typescript
82
+ #!/usr/bin/env node
83
+
84
+ import { Command } from 'commander';
85
+
86
+ const program = new Command();
87
+
88
+ program
89
+ .name('mcp-factory')
90
+ .description('Generate production-ready MCP servers from API documentation')
91
+ .version('0.1.0');
92
+
93
+ program.parse();
94
+ ```
95
+
96
+ **Step 6: Test CLI runs**
97
+
98
+ Run: `npm run build && node dist/cli.js --version`
99
+ Expected: Outputs "0.1.0"
100
+
101
+ **Step 7: Commit**
102
+
103
+ ```bash
104
+ git add package.json package-lock.json tsconfig.json src/cli.ts
105
+ git commit -m "feat: initialize MCP Factory CLI project
106
+
107
+ Set up TypeScript project with Commander.js for CLI framework.
108
+
109
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Phase 2: Core Schema and Utilities
115
+
116
+ ### Task 2: Define APISchema Types
117
+
118
+ **Files:**
119
+ - Create: `src/schema/api-schema.ts`
120
+
121
+ **Step 1: Create schema type definitions**
122
+
123
+ ```typescript
124
+ export interface APISchema {
125
+ name: string;
126
+ baseUrl: string;
127
+ auth: AuthConfig;
128
+ endpoints: Endpoint[];
129
+ commonHeaders?: Record<string, string>;
130
+ rateLimit?: RateLimitConfig;
131
+ pagination?: PaginationConfig;
132
+ }
133
+
134
+ export interface AuthConfig {
135
+ type: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
136
+ location?: 'header' | 'query';
137
+ name?: string;
138
+ description?: string;
139
+ }
140
+
141
+ export interface Endpoint {
142
+ id: string;
143
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
144
+ path: string;
145
+ description: string;
146
+ parameters: Parameter[];
147
+ requestBody?: RequestBody;
148
+ response: ResponseSchema;
149
+ errors: ErrorSchema[];
150
+ }
151
+
152
+ export interface Parameter {
153
+ name: string;
154
+ in: 'path' | 'query' | 'header';
155
+ description?: string;
156
+ required: boolean;
157
+ schema: SchemaType;
158
+ }
159
+
160
+ export interface RequestBody {
161
+ description?: string;
162
+ required: boolean;
163
+ contentType: string;
164
+ schema: SchemaType;
165
+ }
166
+
167
+ export interface ResponseSchema {
168
+ statusCode: number;
169
+ description?: string;
170
+ contentType: string;
171
+ schema: SchemaType;
172
+ }
173
+
174
+ export interface ErrorSchema {
175
+ statusCode: number;
176
+ description: string;
177
+ schema?: SchemaType;
178
+ }
179
+
180
+ export interface SchemaType {
181
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
182
+ properties?: Record<string, SchemaType>;
183
+ items?: SchemaType;
184
+ required?: string[];
185
+ enum?: string[];
186
+ format?: string;
187
+ }
188
+
189
+ export interface RateLimitConfig {
190
+ strategy: 'header-based' | 'retry-after' | 'none';
191
+ headerName?: string;
192
+ requestsPerWindow?: number;
193
+ windowSeconds?: number;
194
+ }
195
+
196
+ export interface PaginationConfig {
197
+ style: 'cursor' | 'offset' | 'page' | 'link-header';
198
+ cursorParam?: string;
199
+ limitParam?: string;
200
+ offsetParam?: string;
201
+ pageParam?: string;
202
+ }
203
+
204
+ export interface DetectedPatterns {
205
+ authPattern: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
206
+ paginationStyle?: 'cursor' | 'offset' | 'page' | 'link-header';
207
+ rateLimitStrategy?: 'header-based' | 'retry-after' | 'none';
208
+ errorFormat: 'standard' | 'custom';
209
+ hasWebhooks: boolean;
210
+ }
211
+ ```
212
+
213
+ **Step 2: Build to verify types are valid**
214
+
215
+ Run: `npm run build`
216
+ Expected: Compiles successfully
217
+
218
+ **Step 3: Commit**
219
+
220
+ ```bash
221
+ git add src/schema/api-schema.ts
222
+ git commit -m "feat: define unified APISchema type definitions
223
+
224
+ Core types for normalized API representation used across all parsers.
225
+
226
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
227
+ ```
228
+
229
+ ---
230
+
231
+ ### Task 3: Create Utility Modules
232
+
233
+ **Files:**
234
+ - Create: `src/utils/errors.ts`
235
+ - Create: `src/utils/logger.ts`
236
+
237
+ **Step 1: Create error utilities**
238
+
239
+ ```typescript
240
+ export class MCPFactoryError extends Error {
241
+ constructor(message: string, public code: string) {
242
+ super(message);
243
+ this.name = 'MCPFactoryError';
244
+ }
245
+ }
246
+
247
+ export class ParseError extends MCPFactoryError {
248
+ constructor(message: string) {
249
+ super(message, 'PARSE_ERROR');
250
+ this.name = 'ParseError';
251
+ }
252
+ }
253
+
254
+ export class ValidationError extends MCPFactoryError {
255
+ constructor(message: string) {
256
+ super(message, 'VALIDATION_ERROR');
257
+ this.name = 'ValidationError';
258
+ }
259
+ }
260
+
261
+ export class GenerationError extends MCPFactoryError {
262
+ constructor(message: string) {
263
+ super(message, 'GENERATION_ERROR');
264
+ this.name = 'GenerationError';
265
+ }
266
+ }
267
+ ```
268
+
269
+ **Step 2: Create logger utility**
270
+
271
+ ```typescript
272
+ export const logger = {
273
+ info: (message: string) => {
274
+ console.log(`ℹ ${message}`);
275
+ },
276
+
277
+ success: (message: string) => {
278
+ console.log(`✓ ${message}`);
279
+ },
280
+
281
+ error: (message: string) => {
282
+ console.error(`✗ ${message}`);
283
+ },
284
+
285
+ warn: (message: string) => {
286
+ console.warn(`⚠ ${message}`);
287
+ },
288
+
289
+ debug: (message: string) => {
290
+ if (process.env.DEBUG) {
291
+ console.log(`[DEBUG] ${message}`);
292
+ }
293
+ }
294
+ };
295
+ ```
296
+
297
+ **Step 3: Build to verify**
298
+
299
+ Run: `npm run build`
300
+ Expected: Compiles successfully
301
+
302
+ **Step 4: Commit**
303
+
304
+ ```bash
305
+ git add src/utils/
306
+ git commit -m "feat: add error handling and logging utilities
307
+
308
+ Custom error types and logger for CLI output.
309
+
310
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Phase 3: Input Processing
316
+
317
+ ### Task 4: Implement Format Detector
318
+
319
+ **Files:**
320
+ - Create: `src/parsers/detector.ts`
321
+
322
+ **Step 1: Create format detector**
323
+
324
+ ```typescript
325
+ import * as fs from 'fs/promises';
326
+ import * as yaml from 'yaml';
327
+ import { ParseError } from '../utils/errors.js';
328
+
329
+ export type InputFormat = 'openapi' | 'swagger' | 'postman' | 'unknown';
330
+
331
+ export interface DetectionResult {
332
+ format: InputFormat;
333
+ content: any;
334
+ }
335
+
336
+ export async function detectFormat(input: string): Promise<DetectionResult> {
337
+ let content: string;
338
+
339
+ // Check if input is a file path
340
+ try {
341
+ content = await fs.readFile(input, 'utf-8');
342
+ } catch {
343
+ throw new ParseError(`Could not read file: ${input}`);
344
+ }
345
+
346
+ // Try parsing as JSON
347
+ let parsed: any;
348
+ try {
349
+ parsed = JSON.parse(content);
350
+ } catch {
351
+ // Try parsing as YAML
352
+ try {
353
+ parsed = yaml.parse(content);
354
+ } catch {
355
+ throw new ParseError('Could not parse input as JSON or YAML');
356
+ }
357
+ }
358
+
359
+ // Detect format from parsed content
360
+ if (parsed.openapi) {
361
+ return { format: 'openapi', content: parsed };
362
+ }
363
+
364
+ if (parsed.swagger) {
365
+ return { format: 'swagger', content: parsed };
366
+ }
367
+
368
+ if (parsed.info?.schema?.includes('postman')) {
369
+ return { format: 'postman', content: parsed };
370
+ }
371
+
372
+ return { format: 'unknown', content: parsed };
373
+ }
374
+ ```
375
+
376
+ **Step 2: Build to verify**
377
+
378
+ Run: `npm run build`
379
+ Expected: Compiles successfully
380
+
381
+ **Step 3: Commit**
382
+
383
+ ```bash
384
+ git add src/parsers/detector.ts
385
+ git commit -m "feat: implement format detection for API specs
386
+
387
+ Auto-detect OpenAPI, Swagger, and Postman collection formats.
388
+
389
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
390
+ ```
391
+
392
+ ---
393
+
394
+ ### Task 5: Implement OpenAPI Parser
395
+
396
+ **Files:**
397
+ - Create: `src/parsers/openapi.ts`
398
+
399
+ **Step 1: Create OpenAPI parser**
400
+
401
+ ```typescript
402
+ import { APISchema, Endpoint, Parameter, AuthConfig, SchemaType } from '../schema/api-schema.js';
403
+ import { ParseError } from '../utils/errors.js';
404
+
405
+ export function parseOpenAPI(spec: any): APISchema {
406
+ if (!spec.openapi && !spec.swagger) {
407
+ throw new ParseError('Invalid OpenAPI/Swagger specification');
408
+ }
409
+
410
+ const name = spec.info?.title?.toLowerCase().replace(/\s+/g, '-') || 'api';
411
+ const baseUrl = getBaseUrl(spec);
412
+ const auth = detectAuth(spec);
413
+ const endpoints = parseEndpoints(spec);
414
+
415
+ return {
416
+ name,
417
+ baseUrl,
418
+ auth,
419
+ endpoints,
420
+ };
421
+ }
422
+
423
+ function getBaseUrl(spec: any): string {
424
+ // OpenAPI 3.x
425
+ if (spec.servers && spec.servers.length > 0) {
426
+ return spec.servers[0].url;
427
+ }
428
+
429
+ // Swagger 2.0
430
+ if (spec.host) {
431
+ const scheme = spec.schemes?.[0] || 'https';
432
+ const basePath = spec.basePath || '';
433
+ return `${scheme}://${spec.host}${basePath}`;
434
+ }
435
+
436
+ throw new ParseError('Could not determine base URL from specification');
437
+ }
438
+
439
+ function detectAuth(spec: any): AuthConfig {
440
+ // OpenAPI 3.x
441
+ if (spec.components?.securitySchemes) {
442
+ const schemes = spec.components.securitySchemes;
443
+ const firstScheme = Object.values(schemes)[0] as any;
444
+
445
+ if (firstScheme.type === 'apiKey') {
446
+ return {
447
+ type: 'api-key',
448
+ location: firstScheme.in,
449
+ name: firstScheme.name,
450
+ description: firstScheme.description,
451
+ };
452
+ }
453
+
454
+ if (firstScheme.type === 'http' && firstScheme.scheme === 'bearer') {
455
+ return {
456
+ type: 'bearer',
457
+ location: 'header',
458
+ name: 'Authorization',
459
+ };
460
+ }
461
+
462
+ if (firstScheme.type === 'oauth2') {
463
+ return {
464
+ type: 'oauth',
465
+ description: firstScheme.description,
466
+ };
467
+ }
468
+ }
469
+
470
+ // Swagger 2.0
471
+ if (spec.securityDefinitions) {
472
+ const firstScheme = Object.values(spec.securityDefinitions)[0] as any;
473
+
474
+ if (firstScheme.type === 'apiKey') {
475
+ return {
476
+ type: 'api-key',
477
+ location: firstScheme.in,
478
+ name: firstScheme.name,
479
+ };
480
+ }
481
+ }
482
+
483
+ return { type: 'none' };
484
+ }
485
+
486
+ function parseEndpoints(spec: any): Endpoint[] {
487
+ const endpoints: Endpoint[] = [];
488
+ const paths = spec.paths || {};
489
+
490
+ for (const [path, methods] of Object.entries(paths)) {
491
+ for (const [method, operation] of Object.entries(methods as any)) {
492
+ if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
493
+ endpoints.push(parseOperation(path, method, operation));
494
+ }
495
+ }
496
+ }
497
+
498
+ return endpoints;
499
+ }
500
+
501
+ function parseOperation(path: string, method: string, operation: any): Endpoint {
502
+ const id = operation.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
503
+ const description = operation.summary || operation.description || `${method.toUpperCase()} ${path}`;
504
+ const parameters = parseParameters(operation.parameters || []);
505
+
506
+ return {
507
+ id,
508
+ method: method.toUpperCase() as any,
509
+ path,
510
+ description,
511
+ parameters,
512
+ response: {
513
+ statusCode: 200,
514
+ description: 'Successful response',
515
+ contentType: 'application/json',
516
+ schema: { type: 'object' },
517
+ },
518
+ errors: [],
519
+ };
520
+ }
521
+
522
+ function parseParameters(params: any[]): Parameter[] {
523
+ return params.map(param => ({
524
+ name: param.name,
525
+ in: param.in,
526
+ description: param.description,
527
+ required: param.required || false,
528
+ schema: parseSchema(param.schema || { type: param.type || 'string' }),
529
+ }));
530
+ }
531
+
532
+ function parseSchema(schema: any): SchemaType {
533
+ return {
534
+ type: schema.type || 'string',
535
+ properties: schema.properties,
536
+ items: schema.items ? parseSchema(schema.items) : undefined,
537
+ required: schema.required,
538
+ enum: schema.enum,
539
+ format: schema.format,
540
+ };
541
+ }
542
+ ```
543
+
544
+ **Step 2: Build to verify**
545
+
546
+ Run: `npm run build`
547
+ Expected: Compiles successfully
548
+
549
+ **Step 3: Commit**
550
+
551
+ ```bash
552
+ git add src/parsers/openapi.ts
553
+ git commit -m "feat: implement OpenAPI/Swagger parser
554
+
555
+ Parse OpenAPI 3.x and Swagger 2.0 specs into unified APISchema.
556
+
557
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
558
+ ```
559
+
560
+ ---
561
+
562
+ ### Task 6: Implement Postman Parser (Stub)
563
+
564
+ **Files:**
565
+ - Create: `src/parsers/postman.ts`
566
+
567
+ **Step 1: Create stub Postman parser**
568
+
569
+ ```typescript
570
+ import { APISchema } from '../schema/api-schema.js';
571
+ import { ParseError } from '../utils/errors.js';
572
+
573
+ export function parsePostman(collection: any): APISchema {
574
+ throw new ParseError('Postman collection parsing not yet implemented');
575
+ }
576
+ ```
577
+
578
+ **Step 2: Build to verify**
579
+
580
+ Run: `npm run build`
581
+ Expected: Compiles successfully
582
+
583
+ **Step 3: Commit**
584
+
585
+ ```bash
586
+ git add src/parsers/postman.ts
587
+ git commit -m "feat: add Postman parser stub
588
+
589
+ Placeholder for future Postman collection support.
590
+
591
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
592
+ ```
593
+
594
+ ---
595
+
596
+ ### Task 7: Implement AI Parser (Stub)
597
+
598
+ **Files:**
599
+ - Create: `src/parsers/ai-parser.ts`
600
+
601
+ **Step 1: Create stub AI parser**
602
+
603
+ ```typescript
604
+ import { APISchema } from '../schema/api-schema.js';
605
+ import { ParseError } from '../utils/errors.js';
606
+
607
+ export async function parseWithAI(content: string): Promise<APISchema> {
608
+ if (!process.env.ANTHROPIC_API_KEY) {
609
+ throw new ParseError('ANTHROPIC_API_KEY environment variable required for AI parsing');
610
+ }
611
+
612
+ throw new ParseError('AI-powered parsing not yet implemented');
613
+ }
614
+ ```
615
+
616
+ **Step 2: Build to verify**
617
+
618
+ Run: `npm run build`
619
+ Expected: Compiles successfully
620
+
621
+ **Step 3: Commit**
622
+
623
+ ```bash
624
+ git add src/parsers/ai-parser.ts
625
+ git commit -m "feat: add AI parser stub
626
+
627
+ Placeholder for Claude API-powered parsing of unstructured docs.
628
+
629
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Phase 4: Code Generation
635
+
636
+ ### Task 8: Implement Pattern Analyzer
637
+
638
+ **Files:**
639
+ - Create: `src/generator/analyzer.ts`
640
+
641
+ **Step 1: Create pattern analyzer**
642
+
643
+ ```typescript
644
+ import { APISchema, DetectedPatterns } from '../schema/api-schema.js';
645
+
646
+ export function analyzePatterns(schema: APISchema): DetectedPatterns {
647
+ return {
648
+ authPattern: schema.auth.type,
649
+ paginationStyle: schema.pagination?.style,
650
+ rateLimitStrategy: schema.rateLimit?.strategy || 'none',
651
+ errorFormat: detectErrorFormat(schema),
652
+ hasWebhooks: false,
653
+ };
654
+ }
655
+
656
+ function detectErrorFormat(schema: APISchema): 'standard' | 'custom' {
657
+ // Check if any endpoint has custom error schemas
658
+ const hasCustomErrors = schema.endpoints.some(
659
+ endpoint => endpoint.errors.length > 0 && endpoint.errors.some(e => e.schema)
660
+ );
661
+
662
+ return hasCustomErrors ? 'custom' : 'standard';
663
+ }
664
+ ```
665
+
666
+ **Step 2: Build to verify**
667
+
668
+ Run: `npm run build`
669
+ Expected: Compiles successfully
670
+
671
+ **Step 3: Commit**
672
+
673
+ ```bash
674
+ git add src/generator/analyzer.ts
675
+ git commit -m "feat: implement pattern detection analyzer
676
+
677
+ Analyze APISchema to detect auth, pagination, rate limiting patterns.
678
+
679
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
680
+ ```
681
+
682
+ ---
683
+
684
+ ### Task 9: Create Template Files
685
+
686
+ **Files:**
687
+ - Create: `templates/package.json.hbs`
688
+ - Create: `templates/tsconfig.json.hbs`
689
+ - Create: `templates/README.md.hbs`
690
+ - Create: `templates/index.ts.hbs`
691
+ - Create: `templates/client.ts.hbs`
692
+ - Create: `templates/tools.ts.hbs`
693
+ - Create: `templates/types.ts.hbs`
694
+ - Create: `templates/validation.ts.hbs`
695
+ - Create: `templates/test.ts.hbs`
696
+
697
+ **Step 1: Create package.json template**
698
+
699
+ ```handlebars
700
+ {
701
+ "name": "{{name}}-mcp",
702
+ "version": "1.0.0",
703
+ "description": "MCP server for {{name}} API",
704
+ "type": "module",
705
+ "main": "build/index.js",
706
+ "scripts": {
707
+ "build": "tsc",
708
+ "test": "node --test test.ts"
709
+ },
710
+ "dependencies": {
711
+ "@modelcontextprotocol/sdk": "^1.0.0",
712
+ "zod": "^3.22.0",
713
+ "node-fetch": "^3.3.0"
714
+ },
715
+ "devDependencies": {
716
+ "typescript": "^5.3.0",
717
+ "@types/node": "^20.0.0"
718
+ }
719
+ }
720
+ ```
721
+
722
+ **Step 2: Create tsconfig.json template**
723
+
724
+ ```handlebars
725
+ {
726
+ "compilerOptions": {
727
+ "target": "ES2022",
728
+ "module": "NodeNext",
729
+ "moduleResolution": "NodeNext",
730
+ "outDir": "./build",
731
+ "rootDir": "./src",
732
+ "strict": true,
733
+ "esModuleInterop": true,
734
+ "skipLibCheck": true
735
+ },
736
+ "include": ["src/**/*"]
737
+ }
738
+ ```
739
+
740
+ **Step 3: Create README template**
741
+
742
+ ```handlebars
743
+ # {{name}} MCP Server
744
+
745
+ MCP server for the {{name}} API.
746
+
747
+ ## Installation
748
+
749
+ ```bash
750
+ npm install
751
+ npm run build
752
+ ```
753
+
754
+ ## Configuration
755
+
756
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
757
+
758
+ ```json
759
+ {
760
+ "mcpServers": {
761
+ "{{name}}": {
762
+ "command": "node",
763
+ "args": ["{{absolutePath}}/build/index.js"],
764
+ "env": {
765
+ {{#if (eq patterns.authPattern 'api-key')}}
766
+ "API_KEY": "your-api-key-here"
767
+ {{/if}}
768
+ {{#if (eq patterns.authPattern 'bearer')}}
769
+ "BEARER_TOKEN": "your-bearer-token-here"
770
+ {{/if}}
771
+ }
772
+ }
773
+ }
774
+ }
775
+ ```
776
+
777
+ ## Usage
778
+
779
+ Available tools:
780
+ {{#each endpoints}}
781
+ - `{{id}}`: {{description}}
782
+ {{/each}}
783
+ ```
784
+
785
+ **Step 4: Create minimal index.ts template**
786
+
787
+ ```handlebars
788
+ #!/usr/bin/env node
789
+
790
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
791
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
792
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
793
+ import { createClient } from './client.js';
794
+ import { tools, handleToolCall } from './tools.js';
795
+
796
+ const server = new Server(
797
+ {
798
+ name: '{{name}}-mcp',
799
+ version: '1.0.0',
800
+ },
801
+ {
802
+ capabilities: {
803
+ tools: {},
804
+ },
805
+ }
806
+ );
807
+
808
+ const client = createClient('{{baseUrl}}');
809
+
810
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
811
+ tools,
812
+ }));
813
+
814
+ server.setRequestHandler(CallToolRequestSchema, async (request) =>
815
+ handleToolCall(request, client)
816
+ );
817
+
818
+ async function main() {
819
+ const transport = new StdioServerTransport();
820
+ await server.connect(transport);
821
+ }
822
+
823
+ main().catch(console.error);
824
+ ```
825
+
826
+ **Step 5: Create client.ts template**
827
+
828
+ ```handlebars
829
+ import fetch from 'node-fetch';
830
+
831
+ export interface APIClient {
832
+ baseUrl: string;
833
+ request: (method: string, path: string, params?: any) => Promise<any>;
834
+ }
835
+
836
+ export function createClient(baseUrl: string): APIClient {
837
+ return {
838
+ baseUrl,
839
+ async request(method: string, path: string, params?: any) {
840
+ const headers: Record<string, string> = {
841
+ 'Content-Type': 'application/json',
842
+ };
843
+
844
+ {{#if (eq patterns.authPattern 'api-key')}}
845
+ if (process.env.API_KEY) {
846
+ {{#if (eq auth.location 'header')}}
847
+ headers['{{auth.name}}'] = process.env.API_KEY;
848
+ {{/if}}
849
+ }
850
+ {{/if}}
851
+
852
+ {{#if (eq patterns.authPattern 'bearer')}}
853
+ if (process.env.BEARER_TOKEN) {
854
+ headers['Authorization'] = `Bearer ${process.env.BEARER_TOKEN}`;
855
+ }
856
+ {{/if}}
857
+
858
+ const url = new URL(path, baseUrl);
859
+
860
+ const response = await fetch(url.toString(), {
861
+ method,
862
+ headers,
863
+ body: params ? JSON.stringify(params) : undefined,
864
+ });
865
+
866
+ if (!response.ok) {
867
+ throw new Error(`API request failed: ${response.status} ${response.statusText}`);
868
+ }
869
+
870
+ return response.json();
871
+ },
872
+ };
873
+ }
874
+ ```
875
+
876
+ **Step 6: Create tools.ts template**
877
+
878
+ ```handlebars
879
+ import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
880
+ import { APIClient } from './client.js';
881
+
882
+ export const tools = [
883
+ {{#each endpoints}}
884
+ {
885
+ name: '{{id}}',
886
+ description: '{{description}}',
887
+ inputSchema: {
888
+ type: 'object',
889
+ properties: {
890
+ {{#each parameters}}
891
+ {{name}}: {
892
+ type: '{{schema.type}}',
893
+ {{#if description}}description: '{{description}}',{{/if}}
894
+ },
895
+ {{/each}}
896
+ },
897
+ required: [{{#each parameters}}{{#if required}}'{{name}}',{{/if}}{{/each}}],
898
+ },
899
+ },
900
+ {{/each}}
901
+ ];
902
+
903
+ export async function handleToolCall(request: CallToolRequest, client: APIClient) {
904
+ const { name, arguments: args } = request.params;
905
+
906
+ {{#each endpoints}}
907
+ if (name === '{{id}}') {
908
+ const result = await client.request('{{method}}', '{{path}}', args);
909
+ return {
910
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
911
+ };
912
+ }
913
+ {{/each}}
914
+
915
+ throw new Error(`Unknown tool: ${name}`);
916
+ }
917
+ ```
918
+
919
+ **Step 7: Create stub templates for types, validation, test**
920
+
921
+ Create empty/minimal templates:
922
+ - `templates/types.ts.hbs`: `export {}; // Types generated from API schema`
923
+ - `templates/validation.ts.hbs`: `export {}; // Validation schemas`
924
+ - `templates/test.ts.hbs`: `console.log('Tests not yet implemented');`
925
+
926
+ **Step 8: Commit**
927
+
928
+ ```bash
929
+ git add templates/
930
+ git commit -m "feat: add Handlebars templates for MCP server generation
931
+
932
+ Core templates for package.json, tsconfig, index, client, tools, README.
933
+
934
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
935
+ ```
936
+
937
+ ---
938
+
939
+ ### Task 10: Implement Template Engine
940
+
941
+ **Files:**
942
+ - Create: `src/generator/engine.ts`
943
+
944
+ **Step 1: Create template engine**
945
+
946
+ ```typescript
947
+ import * as fs from 'fs/promises';
948
+ import * as path from 'path';
949
+ import Handlebars from 'handlebars';
950
+ import { APISchema, DetectedPatterns } from '../schema/api-schema.js';
951
+ import { GenerationError } from '../utils/errors.js';
952
+ import { fileURLToPath } from 'url';
953
+
954
+ const __filename = fileURLToPath(import.meta.url);
955
+ const __dirname = path.dirname(__filename);
956
+
957
+ // Register Handlebars helper for equality check
958
+ Handlebars.registerHelper('eq', (a, b) => a === b);
959
+
960
+ export interface GenerationContext {
961
+ name: string;
962
+ baseUrl: string;
963
+ auth: any;
964
+ endpoints: any[];
965
+ patterns: DetectedPatterns;
966
+ absolutePath?: string;
967
+ }
968
+
969
+ export async function generateServer(
970
+ schema: APISchema,
971
+ patterns: DetectedPatterns,
972
+ outputDir: string
973
+ ): Promise<void> {
974
+ const context: GenerationContext = {
975
+ name: schema.name,
976
+ baseUrl: schema.baseUrl,
977
+ auth: schema.auth,
978
+ endpoints: schema.endpoints,
979
+ patterns,
980
+ absolutePath: path.resolve(outputDir),
981
+ };
982
+
983
+ // Create output directory structure
984
+ await fs.mkdir(path.join(outputDir, 'src'), { recursive: true });
985
+
986
+ // Get template directory
987
+ const templateDir = path.join(__dirname, '..', '..', 'templates');
988
+
989
+ // Generate files from templates
990
+ await generateFile(templateDir, outputDir, 'package.json.hbs', 'package.json', context);
991
+ await generateFile(templateDir, outputDir, 'tsconfig.json.hbs', 'tsconfig.json', context);
992
+ await generateFile(templateDir, outputDir, 'README.md.hbs', 'README.md', context);
993
+ await generateFile(templateDir, outputDir, 'index.ts.hbs', 'src/index.ts', context);
994
+ await generateFile(templateDir, outputDir, 'client.ts.hbs', 'src/client.ts', context);
995
+ await generateFile(templateDir, outputDir, 'tools.ts.hbs', 'src/tools.ts', context);
996
+ await generateFile(templateDir, outputDir, 'types.ts.hbs', 'src/types.ts', context);
997
+ await generateFile(templateDir, outputDir, 'validation.ts.hbs', 'src/validation.ts', context);
998
+ await generateFile(templateDir, outputDir, 'test.ts.hbs', 'test.ts', context);
999
+ }
1000
+
1001
+ async function generateFile(
1002
+ templateDir: string,
1003
+ outputDir: string,
1004
+ templateFile: string,
1005
+ outputFile: string,
1006
+ context: GenerationContext
1007
+ ): Promise<void> {
1008
+ try {
1009
+ const templatePath = path.join(templateDir, templateFile);
1010
+ const templateContent = await fs.readFile(templatePath, 'utf-8');
1011
+ const template = Handlebars.compile(templateContent);
1012
+ const output = template(context);
1013
+
1014
+ const outputPath = path.join(outputDir, outputFile);
1015
+ await fs.writeFile(outputPath, output, 'utf-8');
1016
+ } catch (error) {
1017
+ throw new GenerationError(`Failed to generate ${outputFile}: ${error}`);
1018
+ }
1019
+ }
1020
+ ```
1021
+
1022
+ **Step 2: Build to verify**
1023
+
1024
+ Run: `npm run build`
1025
+ Expected: Compiles successfully
1026
+
1027
+ **Step 3: Commit**
1028
+
1029
+ ```bash
1030
+ git add src/generator/engine.ts
1031
+ git commit -m "feat: implement Handlebars template engine
1032
+
1033
+ Generate MCP server files from templates using APISchema context.
1034
+
1035
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1036
+ ```
1037
+
1038
+ ---
1039
+
1040
+ ## Phase 5: Registry Management
1041
+
1042
+ ### Task 11: Implement Registry Manager
1043
+
1044
+ **Files:**
1045
+ - Create: `src/registry/manager.ts`
1046
+
1047
+ **Step 1: Create registry manager**
1048
+
1049
+ ```typescript
1050
+ import * as fs from 'fs/promises';
1051
+ import * as path from 'path';
1052
+ import * as os from 'os';
1053
+
1054
+ export interface RegistryEntry {
1055
+ name: string;
1056
+ path: string;
1057
+ createdAt: string;
1058
+ }
1059
+
1060
+ export interface Registry {
1061
+ servers: RegistryEntry[];
1062
+ }
1063
+
1064
+ const REGISTRY_DIR = path.join(os.homedir(), '.mcp-factory');
1065
+ const REGISTRY_FILE = path.join(REGISTRY_DIR, 'registry.json');
1066
+
1067
+ async function ensureRegistry(): Promise<void> {
1068
+ try {
1069
+ await fs.access(REGISTRY_FILE);
1070
+ } catch {
1071
+ await fs.mkdir(REGISTRY_DIR, { recursive: true });
1072
+ await fs.writeFile(REGISTRY_FILE, JSON.stringify({ servers: [] }, null, 2));
1073
+ }
1074
+ }
1075
+
1076
+ export async function loadRegistry(): Promise<Registry> {
1077
+ await ensureRegistry();
1078
+ const content = await fs.readFile(REGISTRY_FILE, 'utf-8');
1079
+ return JSON.parse(content);
1080
+ }
1081
+
1082
+ export async function saveRegistry(registry: Registry): Promise<void> {
1083
+ await ensureRegistry();
1084
+ await fs.writeFile(REGISTRY_FILE, JSON.stringify(registry, null, 2));
1085
+ }
1086
+
1087
+ export async function addServer(name: string, serverPath: string): Promise<void> {
1088
+ const registry = await loadRegistry();
1089
+
1090
+ // Remove existing entry if present
1091
+ registry.servers = registry.servers.filter(s => s.name !== name);
1092
+
1093
+ // Add new entry
1094
+ registry.servers.push({
1095
+ name,
1096
+ path: path.resolve(serverPath),
1097
+ createdAt: new Date().toISOString(),
1098
+ });
1099
+
1100
+ await saveRegistry(registry);
1101
+ }
1102
+
1103
+ export async function getServer(name: string): Promise<RegistryEntry | undefined> {
1104
+ const registry = await loadRegistry();
1105
+ return registry.servers.find(s => s.name === name);
1106
+ }
1107
+
1108
+ export async function listServers(): Promise<RegistryEntry[]> {
1109
+ const registry = await loadRegistry();
1110
+ return registry.servers;
1111
+ }
1112
+ ```
1113
+
1114
+ **Step 2: Build to verify**
1115
+
1116
+ Run: `npm run build`
1117
+ Expected: Compiles successfully
1118
+
1119
+ **Step 3: Commit**
1120
+
1121
+ ```bash
1122
+ git add src/registry/manager.ts
1123
+ git commit -m "feat: implement registry manager for tracking generated servers
1124
+
1125
+ Manage ~/.mcp-factory/registry.json for listing and installing servers.
1126
+
1127
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1128
+ ```
1129
+
1130
+ ---
1131
+
1132
+ ## Phase 6: CLI Commands
1133
+
1134
+ ### Task 12: Implement Create Command
1135
+
1136
+ **Files:**
1137
+ - Create: `src/commands/create.ts`
1138
+
1139
+ **Step 1: Create command implementation**
1140
+
1141
+ ```typescript
1142
+ import * as path from 'path';
1143
+ import { detectFormat } from '../parsers/detector.js';
1144
+ import { parseOpenAPI } from '../parsers/openapi.js';
1145
+ import { parsePostman } from '../parsers/postman.js';
1146
+ import { parseWithAI } from '../parsers/ai-parser.js';
1147
+ import { analyzePatterns } from '../generator/analyzer.js';
1148
+ import { generateServer } from '../generator/engine.js';
1149
+ import { addServer } from '../registry/manager.js';
1150
+ import { logger } from '../utils/logger.js';
1151
+ import { ParseError } from '../utils/errors.js';
1152
+
1153
+ export async function createCommand(
1154
+ input: string,
1155
+ options: { aiParse?: boolean; output?: string }
1156
+ ): Promise<void> {
1157
+ try {
1158
+ logger.info(`Detecting format for: ${input}`);
1159
+
1160
+ // Detect format
1161
+ const detection = await detectFormat(input);
1162
+ logger.info(`Detected format: ${detection.format}`);
1163
+
1164
+ // Parse to APISchema
1165
+ let schema;
1166
+ if (detection.format === 'openapi' || detection.format === 'swagger') {
1167
+ schema = parseOpenAPI(detection.content);
1168
+ } else if (detection.format === 'postman') {
1169
+ schema = parsePostman(detection.content);
1170
+ } else if (options.aiParse) {
1171
+ logger.info('Using AI parser for unstructured docs...');
1172
+ schema = await parseWithAI(JSON.stringify(detection.content));
1173
+ } else {
1174
+ throw new ParseError('Could not detect format. Use --ai-parse for unstructured docs.');
1175
+ }
1176
+
1177
+ logger.success(`Parsed API: ${schema.name}`);
1178
+
1179
+ // Analyze patterns
1180
+ const patterns = analyzePatterns(schema);
1181
+ logger.info(`Detected patterns: auth=${patterns.authPattern}, pagination=${patterns.paginationStyle || 'none'}`);
1182
+
1183
+ // Generate server
1184
+ const outputDir = options.output || path.join(process.cwd(), `${schema.name}-mcp`);
1185
+ logger.info(`Generating server in: ${outputDir}`);
1186
+
1187
+ await generateServer(schema, patterns, outputDir);
1188
+
1189
+ // Add to registry
1190
+ await addServer(schema.name, outputDir);
1191
+
1192
+ logger.success(`Generated MCP server: ${schema.name}`);
1193
+ logger.info(`Next steps:`);
1194
+ logger.info(` cd ${outputDir}`);
1195
+ logger.info(` npm install`);
1196
+ logger.info(` npm run build`);
1197
+ logger.info(` npm test`);
1198
+
1199
+ } catch (error) {
1200
+ if (error instanceof Error) {
1201
+ logger.error(error.message);
1202
+ process.exit(1);
1203
+ }
1204
+ throw error;
1205
+ }
1206
+ }
1207
+ ```
1208
+
1209
+ **Step 2: Build to verify**
1210
+
1211
+ Run: `npm run build`
1212
+ Expected: Compiles successfully
1213
+
1214
+ **Step 3: Commit**
1215
+
1216
+ ```bash
1217
+ git add src/commands/create.ts
1218
+ git commit -m "feat: implement create command
1219
+
1220
+ Parse API docs and generate MCP server with full pipeline.
1221
+
1222
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1223
+ ```
1224
+
1225
+ ---
1226
+
1227
+ ### Task 13: Implement Validate Command
1228
+
1229
+ **Files:**
1230
+ - Create: `src/commands/validate.ts`
1231
+
1232
+ **Step 1: Create validate command**
1233
+
1234
+ ```typescript
1235
+ import { detectFormat } from '../parsers/detector.js';
1236
+ import { parseOpenAPI } from '../parsers/openapi.js';
1237
+ import { logger } from '../utils/logger.js';
1238
+
1239
+ export async function validateCommand(input: string): Promise<void> {
1240
+ try {
1241
+ logger.info(`Validating: ${input}`);
1242
+
1243
+ const detection = await detectFormat(input);
1244
+ logger.success(`Format detected: ${detection.format}`);
1245
+
1246
+ if (detection.format === 'openapi' || detection.format === 'swagger') {
1247
+ const schema = parseOpenAPI(detection.content);
1248
+ logger.success(`Valid API specification: ${schema.name}`);
1249
+ logger.info(`Base URL: ${schema.baseUrl}`);
1250
+ logger.info(`Endpoints: ${schema.endpoints.length}`);
1251
+ logger.info(`Auth type: ${schema.auth.type}`);
1252
+ } else {
1253
+ logger.warn('Format detected but parsing not implemented yet');
1254
+ }
1255
+
1256
+ } catch (error) {
1257
+ if (error instanceof Error) {
1258
+ logger.error(`Validation failed: ${error.message}`);
1259
+ process.exit(1);
1260
+ }
1261
+ throw error;
1262
+ }
1263
+ }
1264
+ ```
1265
+
1266
+ **Step 2: Build to verify**
1267
+
1268
+ Run: `npm run build`
1269
+ Expected: Compiles successfully
1270
+
1271
+ **Step 3: Commit**
1272
+
1273
+ ```bash
1274
+ git add src/commands/validate.ts
1275
+ git commit -m "feat: implement validate command
1276
+
1277
+ Validate API specs without generating code.
1278
+
1279
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1280
+ ```
1281
+
1282
+ ---
1283
+
1284
+ ### Task 14: Implement List Command
1285
+
1286
+ **Files:**
1287
+ - Create: `src/commands/list.ts`
1288
+
1289
+ **Step 1: Create list command**
1290
+
1291
+ ```typescript
1292
+ import { listServers } from '../registry/manager.js';
1293
+ import { logger } from '../utils/logger.js';
1294
+
1295
+ export async function listCommand(): Promise<void> {
1296
+ try {
1297
+ const servers = await listServers();
1298
+
1299
+ if (servers.length === 0) {
1300
+ logger.info('No MCP servers generated yet');
1301
+ return;
1302
+ }
1303
+
1304
+ logger.info(`Generated MCP servers (${servers.length}):\n`);
1305
+
1306
+ for (const server of servers) {
1307
+ console.log(` ${server.name}`);
1308
+ console.log(` Path: ${server.path}`);
1309
+ console.log(` Created: ${new Date(server.createdAt).toLocaleString()}\n`);
1310
+ }
1311
+
1312
+ } catch (error) {
1313
+ if (error instanceof Error) {
1314
+ logger.error(error.message);
1315
+ process.exit(1);
1316
+ }
1317
+ throw error;
1318
+ }
1319
+ }
1320
+ ```
1321
+
1322
+ **Step 2: Build to verify**
1323
+
1324
+ Run: `npm run build`
1325
+ Expected: Compiles successfully
1326
+
1327
+ **Step 3: Commit**
1328
+
1329
+ ```bash
1330
+ git add src/commands/list.ts
1331
+ git commit -m "feat: implement list command
1332
+
1333
+ List all generated MCP servers from registry.
1334
+
1335
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1336
+ ```
1337
+
1338
+ ---
1339
+
1340
+ ### Task 15: Implement Install Command
1341
+
1342
+ **Files:**
1343
+ - Create: `src/commands/install.ts`
1344
+
1345
+ **Step 1: Create install command**
1346
+
1347
+ ```typescript
1348
+ import * as fs from 'fs/promises';
1349
+ import * as path from 'path';
1350
+ import * as os from 'os';
1351
+ import { getServer } from '../registry/manager.js';
1352
+ import { logger } from '../utils/logger.js';
1353
+
1354
+ export async function installCommand(serverName: string): Promise<void> {
1355
+ try {
1356
+ // Get server from registry
1357
+ const server = await getServer(serverName);
1358
+ if (!server) {
1359
+ logger.error(`Server not found: ${serverName}`);
1360
+ logger.info('Run "mcp-factory list" to see available servers');
1361
+ process.exit(1);
1362
+ }
1363
+
1364
+ // Check if server build exists
1365
+ const buildPath = path.join(server.path, 'build', 'index.js');
1366
+ try {
1367
+ await fs.access(buildPath);
1368
+ } catch {
1369
+ logger.error(`Server not built yet. Run:`);
1370
+ logger.info(` cd ${server.path}`);
1371
+ logger.info(` npm install && npm run build`);
1372
+ process.exit(1);
1373
+ }
1374
+
1375
+ // Determine Claude config paths
1376
+ const configPaths = [
1377
+ path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
1378
+ path.join(os.homedir(), '.claude', 'config.json'),
1379
+ ];
1380
+
1381
+ let installed = false;
1382
+
1383
+ for (const configPath of configPaths) {
1384
+ try {
1385
+ await fs.access(configPath);
1386
+ await installToConfig(configPath, serverName, buildPath);
1387
+ installed = true;
1388
+
1389
+ const configName = configPath.includes('claude_desktop_config.json')
1390
+ ? 'Claude Desktop'
1391
+ : 'Claude Code';
1392
+ logger.success(`Installed ${serverName} to ${configName}`);
1393
+
1394
+ } catch {
1395
+ // Config file doesn't exist, skip
1396
+ }
1397
+ }
1398
+
1399
+ if (!installed) {
1400
+ logger.warn('No Claude configuration files found');
1401
+ logger.info('Expected locations:');
1402
+ configPaths.forEach(p => logger.info(` ${p}`));
1403
+ } else {
1404
+ logger.info('\nNext steps:');
1405
+ logger.info(' 1. Edit the config file and add your API credentials');
1406
+ logger.info(' 2. Restart Claude Desktop/Code to load the server');
1407
+ }
1408
+
1409
+ } catch (error) {
1410
+ if (error instanceof Error) {
1411
+ logger.error(error.message);
1412
+ process.exit(1);
1413
+ }
1414
+ throw error;
1415
+ }
1416
+ }
1417
+
1418
+ async function installToConfig(
1419
+ configPath: string,
1420
+ serverName: string,
1421
+ buildPath: string
1422
+ ): Promise<void> {
1423
+ const content = await fs.readFile(configPath, 'utf-8');
1424
+ const config = JSON.parse(content);
1425
+
1426
+ if (!config.mcpServers) {
1427
+ config.mcpServers = {};
1428
+ }
1429
+
1430
+ config.mcpServers[serverName] = {
1431
+ command: 'node',
1432
+ args: [buildPath],
1433
+ env: {
1434
+ API_KEY: 'YOUR_API_KEY_HERE',
1435
+ },
1436
+ };
1437
+
1438
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
1439
+ }
1440
+ ```
1441
+
1442
+ **Step 2: Build to verify**
1443
+
1444
+ Run: `npm run build`
1445
+ Expected: Compiles successfully
1446
+
1447
+ **Step 3: Commit**
1448
+
1449
+ ```bash
1450
+ git add src/commands/install.ts
1451
+ git commit -m "feat: implement install command
1452
+
1453
+ Auto-configure generated servers in Claude Desktop/Code config.
1454
+
1455
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1456
+ ```
1457
+
1458
+ ---
1459
+
1460
+ ### Task 16: Wire Up CLI Commands
1461
+
1462
+ **Files:**
1463
+ - Modify: `src/cli.ts`
1464
+
1465
+ **Step 1: Import commands**
1466
+
1467
+ Add imports at top of file:
1468
+
1469
+ ```typescript
1470
+ import { createCommand } from './commands/create.js';
1471
+ import { validateCommand } from './commands/validate.js';
1472
+ import { listCommand } from './commands/list.js';
1473
+ import { installCommand } from './commands/install.js';
1474
+ ```
1475
+
1476
+ **Step 2: Add command definitions**
1477
+
1478
+ Replace the existing program definition with:
1479
+
1480
+ ```typescript
1481
+ program
1482
+ .name('mcp-factory')
1483
+ .description('Generate production-ready MCP servers from API documentation')
1484
+ .version('0.1.0');
1485
+
1486
+ program
1487
+ .command('create')
1488
+ .description('Generate MCP server from API documentation')
1489
+ .argument('<input>', 'Path to API spec file or URL')
1490
+ .option('--ai-parse', 'Use AI to parse unstructured documentation')
1491
+ .option('-o, --output <dir>', 'Output directory for generated server')
1492
+ .action(createCommand);
1493
+
1494
+ program
1495
+ .command('validate')
1496
+ .description('Validate API specification without generating code')
1497
+ .argument('<input>', 'Path to API spec file')
1498
+ .action(validateCommand);
1499
+
1500
+ program
1501
+ .command('list')
1502
+ .description('List all generated MCP servers')
1503
+ .action(listCommand);
1504
+
1505
+ program
1506
+ .command('install')
1507
+ .description('Install MCP server to Claude Desktop/Code configuration')
1508
+ .argument('<server-name>', 'Name of the server to install')
1509
+ .action(installCommand);
1510
+
1511
+ program.parse();
1512
+ ```
1513
+
1514
+ **Step 3: Build to verify**
1515
+
1516
+ Run: `npm run build`
1517
+ Expected: Compiles successfully
1518
+
1519
+ **Step 4: Test CLI help**
1520
+
1521
+ Run: `node dist/cli.js --help`
1522
+ Expected: Shows all commands
1523
+
1524
+ **Step 5: Commit**
1525
+
1526
+ ```bash
1527
+ git add src/cli.ts
1528
+ git commit -m "feat: wire up all CLI commands
1529
+
1530
+ Connect create, validate, list, install commands to CLI.
1531
+
1532
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1533
+ ```
1534
+
1535
+ ---
1536
+
1537
+ ## Phase 7: Testing and Polish
1538
+
1539
+ ### Task 17: Test with Sample OpenAPI Spec
1540
+
1541
+ **Files:**
1542
+ - Create: `test-fixtures/weather-api.json`
1543
+
1544
+ **Step 1: Create sample OpenAPI spec**
1545
+
1546
+ ```json
1547
+ {
1548
+ "openapi": "3.0.0",
1549
+ "info": {
1550
+ "title": "Weather API",
1551
+ "version": "1.0.0"
1552
+ },
1553
+ "servers": [
1554
+ {
1555
+ "url": "https://api.weather.example.com"
1556
+ }
1557
+ ],
1558
+ "components": {
1559
+ "securitySchemes": {
1560
+ "ApiKeyAuth": {
1561
+ "type": "apiKey",
1562
+ "in": "header",
1563
+ "name": "X-API-Key"
1564
+ }
1565
+ }
1566
+ },
1567
+ "security": [
1568
+ {
1569
+ "ApiKeyAuth": []
1570
+ }
1571
+ ],
1572
+ "paths": {
1573
+ "/weather": {
1574
+ "get": {
1575
+ "operationId": "get_weather",
1576
+ "summary": "Get current weather for a city",
1577
+ "parameters": [
1578
+ {
1579
+ "name": "city",
1580
+ "in": "query",
1581
+ "required": true,
1582
+ "schema": {
1583
+ "type": "string"
1584
+ }
1585
+ }
1586
+ ],
1587
+ "responses": {
1588
+ "200": {
1589
+ "description": "Successful response"
1590
+ }
1591
+ }
1592
+ }
1593
+ }
1594
+ }
1595
+ }
1596
+ ```
1597
+
1598
+ **Step 2: Test create command**
1599
+
1600
+ Run: `node dist/cli.js create test-fixtures/weather-api.json`
1601
+ Expected: Generates weather-api-mcp directory
1602
+
1603
+ **Step 3: Verify generated files**
1604
+
1605
+ Run: `ls weather-api-mcp/`
1606
+ Expected: Shows package.json, tsconfig.json, src/, README.md
1607
+
1608
+ **Step 4: Test generated server builds**
1609
+
1610
+ Run: `cd weather-api-mcp && npm install && npm run build`
1611
+ Expected: Compiles successfully
1612
+
1613
+ **Step 5: Test list command**
1614
+
1615
+ Run: `node dist/cli.js list`
1616
+ Expected: Shows weather-api in registry
1617
+
1618
+ **Step 6: Commit test fixture**
1619
+
1620
+ ```bash
1621
+ git add test-fixtures/
1622
+ git commit -m "test: add sample Weather API OpenAPI spec for testing
1623
+
1624
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1625
+ ```
1626
+
1627
+ ---
1628
+
1629
+ ### Task 18: Add NPM Bin Setup
1630
+
1631
+ **Files:**
1632
+ - Modify: `package.json`
1633
+
1634
+ **Step 1: Update package.json bin section**
1635
+
1636
+ Ensure the bin section is correct and add prepare script:
1637
+
1638
+ ```json
1639
+ {
1640
+ "bin": {
1641
+ "mcp-factory": "./dist/cli.js"
1642
+ },
1643
+ "scripts": {
1644
+ "build": "tsc",
1645
+ "dev": "tsc --watch",
1646
+ "test": "node --test dist/**/*.test.js",
1647
+ "prepare": "npm run build"
1648
+ }
1649
+ }
1650
+ ```
1651
+
1652
+ **Step 2: Make CLI executable**
1653
+
1654
+ Run: `chmod +x dist/cli.js`
1655
+
1656
+ **Step 3: Test global install locally**
1657
+
1658
+ Run: `npm link`
1659
+ Run: `mcp-factory --version`
1660
+ Expected: Shows version number
1661
+
1662
+ **Step 4: Unlink after testing**
1663
+
1664
+ Run: `npm unlink -g @mcp-factory/cli`
1665
+
1666
+ **Step 5: Commit**
1667
+
1668
+ ```bash
1669
+ git add package.json
1670
+ git commit -m "feat: configure npm bin for global CLI installation
1671
+
1672
+ Enables 'mcp-factory' command when installed globally.
1673
+
1674
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1675
+ ```
1676
+
1677
+ ---
1678
+
1679
+ ### Task 19: Create README for CLI Project
1680
+
1681
+ **Files:**
1682
+ - Create: `README.md`
1683
+
1684
+ **Step 1: Create comprehensive README**
1685
+
1686
+ ```markdown
1687
+ # MCP Factory
1688
+
1689
+ Generate production-ready MCP servers from API documentation in one command.
1690
+
1691
+ ## Installation
1692
+
1693
+ ```bash
1694
+ npm install -g @mcp-factory/cli
1695
+ ```
1696
+
1697
+ ## Quick Start
1698
+
1699
+ ```bash
1700
+ # Generate MCP server from OpenAPI spec
1701
+ mcp-factory create openapi.json
1702
+
1703
+ # Install to Claude Desktop/Code
1704
+ cd my-api-mcp
1705
+ npm install && npm run build
1706
+ mcp-factory install my-api
1707
+
1708
+ # List generated servers
1709
+ mcp-factory list
1710
+ ```
1711
+
1712
+ ## Commands
1713
+
1714
+ ### create
1715
+
1716
+ Generate an MCP server from API documentation:
1717
+
1718
+ ```bash
1719
+ mcp-factory create <input> [options]
1720
+
1721
+ Options:
1722
+ --ai-parse Use AI to parse unstructured documentation
1723
+ -o, --output <dir> Output directory for generated server
1724
+ ```
1725
+
1726
+ **Supported input formats:**
1727
+ - OpenAPI 3.x (JSON/YAML)
1728
+ - Swagger 2.0 (JSON/YAML)
1729
+ - Postman collections (coming soon)
1730
+ - Unstructured docs with `--ai-parse` (coming soon)
1731
+
1732
+ ### validate
1733
+
1734
+ Validate API specification without generating code:
1735
+
1736
+ ```bash
1737
+ mcp-factory validate <input>
1738
+ ```
1739
+
1740
+ ### list
1741
+
1742
+ List all generated MCP servers:
1743
+
1744
+ ```bash
1745
+ mcp-factory list
1746
+ ```
1747
+
1748
+ ### install
1749
+
1750
+ Install generated server to Claude Desktop/Code configuration:
1751
+
1752
+ ```bash
1753
+ mcp-factory install <server-name>
1754
+ ```
1755
+
1756
+ ## Generated Server Structure
1757
+
1758
+ ```
1759
+ my-api-mcp/
1760
+ ├── src/
1761
+ │ ├── index.ts # MCP server entry point
1762
+ │ ├── client.ts # HTTP client with auth
1763
+ │ └── tools.ts # Tool implementations
1764
+ ├── package.json
1765
+ ├── tsconfig.json
1766
+ └── README.md
1767
+ ```
1768
+
1769
+ ## Development
1770
+
1771
+ ```bash
1772
+ # Clone and setup
1773
+ git clone <repo>
1774
+ cd mcp-factory
1775
+ npm install
1776
+
1777
+ # Build
1778
+ npm run build
1779
+
1780
+ # Test with sample
1781
+ node dist/cli.js create test-fixtures/weather-api.json
1782
+ ```
1783
+
1784
+ ## License
1785
+
1786
+ MIT
1787
+ ```
1788
+
1789
+ **Step 2: Commit**
1790
+
1791
+ ```bash
1792
+ git add README.md
1793
+ git commit -m "docs: add comprehensive README for MCP Factory CLI
1794
+
1795
+ Usage instructions, commands, and quick start guide.
1796
+
1797
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1798
+ ```
1799
+
1800
+ ---
1801
+
1802
+ ## Phase 8: Final Verification
1803
+
1804
+ ### Task 20: End-to-End Test
1805
+
1806
+ **Step 1: Clean previous test output**
1807
+
1808
+ Run: `rm -rf weather-api-mcp`
1809
+
1810
+ **Step 2: Run full generation pipeline**
1811
+
1812
+ Run: `node dist/cli.js create test-fixtures/weather-api.json`
1813
+ Expected: Success message
1814
+
1815
+ **Step 3: Build generated server**
1816
+
1817
+ Run: `cd weather-api-mcp && npm install && npm run build`
1818
+ Expected: Build succeeds, creates build/index.js
1819
+
1820
+ **Step 4: Verify generated server structure**
1821
+
1822
+ Run: `ls -R`
1823
+ Expected: All expected files present
1824
+
1825
+ **Step 5: Check README has correct config**
1826
+
1827
+ Run: `cat README.md`
1828
+ Expected: Contains Claude config with correct paths
1829
+
1830
+ **Step 6: Return to CLI directory**
1831
+
1832
+ Run: `cd ..`
1833
+
1834
+ **Step 7: Commit final state**
1835
+
1836
+ ```bash
1837
+ git add -A
1838
+ git commit -m "test: verify end-to-end MCP server generation
1839
+
1840
+ Complete pipeline test from OpenAPI spec to built MCP server.
1841
+
1842
+ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
1843
+ ```
1844
+
1845
+ ---
1846
+
1847
+ ## Summary
1848
+
1849
+ This implementation plan delivers a working MCP Factory CLI with:
1850
+
1851
+ ✓ Core architecture (CLI, parsers, generator, registry)
1852
+ ✓ OpenAPI/Swagger parsing
1853
+ ✓ Template-based code generation
1854
+ ✓ Pattern detection (auth, pagination, rate limiting)
1855
+ ✓ Registry management
1856
+ ✓ All four commands (create, validate, list, install)
1857
+ ✓ End-to-end tested with sample API
1858
+
1859
+ **Not included (future work):**
1860
+ - Postman collection parser (stub created)
1861
+ - AI-powered parsing (stub created)
1862
+ - Advanced templates (types, validation, comprehensive tests)
1863
+ - OAuth flow handling
1864
+ - Webhook support
1865
+
1866
+ **Ready to execute:** Use @superpowers:executing-plans or @superpowers:subagent-driven-development