@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.
- package/README.md +100 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/commands/create.d.ts +4 -0
- package/dist/commands/create.js +56 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +79 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +24 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +27 -0
- package/dist/generator/analyzer.d.ts +2 -0
- package/dist/generator/analyzer.js +14 -0
- package/dist/generator/engine.d.ts +10 -0
- package/dist/generator/engine.js +46 -0
- package/dist/parsers/ai-parser.d.ts +2 -0
- package/dist/parsers/ai-parser.js +7 -0
- package/dist/parsers/detector.d.ts +6 -0
- package/dist/parsers/detector.js +38 -0
- package/dist/parsers/openapi.d.ts +5 -0
- package/dist/parsers/openapi.js +205 -0
- package/dist/parsers/postman.d.ts +2 -0
- package/dist/parsers/postman.js +4 -0
- package/dist/registry/manager.d.ts +13 -0
- package/dist/registry/manager.js +43 -0
- package/dist/schema/api-schema.d.ts +77 -0
- package/dist/schema/api-schema.js +1 -0
- package/dist/utils/errors.d.ts +13 -0
- package/dist/utils/errors.js +26 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +19 -0
- package/docs/plans/2026-02-02-mcp-factory-design.md +306 -0
- package/docs/plans/2026-02-02-mcp-factory-implementation.md +1866 -0
- package/package.json +48 -0
- package/src/cli.ts +41 -0
- package/src/commands/create.ts +65 -0
- package/src/commands/install.ts +92 -0
- package/src/commands/list.ts +28 -0
- package/src/commands/validate.ts +29 -0
- package/src/generator/analyzer.ts +20 -0
- package/src/generator/engine.ts +73 -0
- package/src/parsers/ai-parser.ts +10 -0
- package/src/parsers/detector.ts +49 -0
- package/src/parsers/openapi.ts +238 -0
- package/src/parsers/postman.ts +6 -0
- package/src/registry/manager.ts +62 -0
- package/src/schema/api-schema.ts +87 -0
- package/src/utils/errors.ts +27 -0
- package/src/utils/logger.ts +23 -0
- package/templates/README.md.hbs +40 -0
- package/templates/client.ts.hbs +45 -0
- package/templates/index.ts.hbs +36 -0
- package/templates/package.json.hbs +20 -0
- package/templates/test.ts.hbs +1 -0
- package/templates/tools.ts.hbs +38 -0
- package/templates/tsconfig.json.hbs +13 -0
- package/templates/types.ts.hbs +1 -0
- package/templates/validation.ts.hbs +1 -0
- package/test-fixtures/weather-api.json +49 -0
- 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
|