@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
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # MCP Factory
2
+
3
+ Generate production-ready MCP servers from API documentation in one command.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @mcp-factory/cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Generate MCP server from OpenAPI spec
15
+ mcp-factory create openapi.json
16
+
17
+ # Install to Claude Desktop/Code
18
+ cd my-api-mcp
19
+ npm install && npm run build
20
+ mcp-factory install my-api
21
+
22
+ # List generated servers
23
+ mcp-factory list
24
+ ```
25
+
26
+ ## Commands
27
+
28
+ ### create
29
+
30
+ Generate an MCP server from API documentation:
31
+
32
+ ```bash
33
+ mcp-factory create <input> [options]
34
+
35
+ Options:
36
+ --ai-parse Use AI to parse unstructured documentation
37
+ -o, --output <dir> Output directory for generated server
38
+ ```
39
+
40
+ **Supported input formats:**
41
+ - OpenAPI 3.x (JSON/YAML)
42
+ - Swagger 2.0 (JSON/YAML)
43
+ - Postman collections (coming soon)
44
+ - Unstructured docs with `--ai-parse` (coming soon)
45
+
46
+ ### validate
47
+
48
+ Validate API specification without generating code:
49
+
50
+ ```bash
51
+ mcp-factory validate <input>
52
+ ```
53
+
54
+ ### list
55
+
56
+ List all generated MCP servers:
57
+
58
+ ```bash
59
+ mcp-factory list
60
+ ```
61
+
62
+ ### install
63
+
64
+ Install generated server to Claude Desktop/Code configuration:
65
+
66
+ ```bash
67
+ mcp-factory install <server-name>
68
+ ```
69
+
70
+ ## Generated Server Structure
71
+
72
+ ```
73
+ my-api-mcp/
74
+ ├── src/
75
+ │ ├── index.ts # MCP server entry point
76
+ │ ├── client.ts # HTTP client with auth
77
+ │ └── tools.ts # Tool implementations
78
+ ├── package.json
79
+ ├── tsconfig.json
80
+ └── README.md
81
+ ```
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ # Clone and setup
87
+ git clone <repo>
88
+ cd mcp-factory
89
+ npm install
90
+
91
+ # Build
92
+ npm run build
93
+
94
+ # Test with sample
95
+ node dist/cli.js create test-fixtures/weather-api.json
96
+ ```
97
+
98
+ ## License
99
+
100
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { createCommand } from './commands/create.js';
4
+ import { validateCommand } from './commands/validate.js';
5
+ import { listCommand } from './commands/list.js';
6
+ import { installCommand } from './commands/install.js';
7
+ const program = new Command();
8
+ program
9
+ .name('mcp-factory')
10
+ .description('Generate production-ready MCP servers from API documentation')
11
+ .version('0.1.0');
12
+ program
13
+ .command('create')
14
+ .description('Generate MCP server from API documentation')
15
+ .argument('<input>', 'Path to API spec file or URL')
16
+ .option('--ai-parse', 'Use AI to parse unstructured documentation')
17
+ .option('-o, --output <dir>', 'Output directory for generated server')
18
+ .action(createCommand);
19
+ program
20
+ .command('validate')
21
+ .description('Validate API specification without generating code')
22
+ .argument('<input>', 'Path to API spec file')
23
+ .action(validateCommand);
24
+ program
25
+ .command('list')
26
+ .description('List all generated MCP servers')
27
+ .action(listCommand);
28
+ program
29
+ .command('install')
30
+ .description('Install MCP server to Claude Desktop/Code configuration')
31
+ .argument('<server-name>', 'Name of the server to install')
32
+ .action(installCommand);
33
+ program.parse();
@@ -0,0 +1,4 @@
1
+ export declare function createCommand(input: string, options: {
2
+ aiParse?: boolean;
3
+ output?: string;
4
+ }): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import * as path from 'path';
2
+ import { detectFormat } from '../parsers/detector.js';
3
+ import { parseOpenAPI } from '../parsers/openapi.js';
4
+ import { parsePostman } from '../parsers/postman.js';
5
+ import { parseWithAI } from '../parsers/ai-parser.js';
6
+ import { analyzePatterns } from '../generator/analyzer.js';
7
+ import { generateServer } from '../generator/engine.js';
8
+ import { addServer } from '../registry/manager.js';
9
+ import { logger } from '../utils/logger.js';
10
+ import { ParseError } from '../utils/errors.js';
11
+ export async function createCommand(input, options) {
12
+ try {
13
+ logger.info(`Detecting format for: ${input}`);
14
+ // Detect format
15
+ const detection = await detectFormat(input);
16
+ logger.info(`Detected format: ${detection.format}`);
17
+ // Parse to APISchema
18
+ let schema;
19
+ if (detection.format === 'openapi' || detection.format === 'swagger') {
20
+ schema = parseOpenAPI(detection.content);
21
+ }
22
+ else if (detection.format === 'postman') {
23
+ schema = parsePostman(detection.content);
24
+ }
25
+ else if (options.aiParse) {
26
+ logger.info('Using AI parser for unstructured docs...');
27
+ schema = await parseWithAI(JSON.stringify(detection.content));
28
+ }
29
+ else {
30
+ throw new ParseError('Could not detect format. Use --ai-parse for unstructured docs.');
31
+ }
32
+ logger.success(`Parsed API: ${schema.name}`);
33
+ // Analyze patterns
34
+ const patterns = analyzePatterns(schema);
35
+ logger.info(`Detected patterns: auth=${patterns.authPattern}, pagination=${patterns.paginationStyle || 'none'}`);
36
+ // Generate server
37
+ const outputDir = options.output || path.join(process.cwd(), `${schema.name}-mcp`);
38
+ logger.info(`Generating server in: ${outputDir}`);
39
+ await generateServer(schema, patterns, outputDir);
40
+ // Add to registry
41
+ await addServer(schema.name, outputDir);
42
+ logger.success(`Generated MCP server: ${schema.name}`);
43
+ logger.info(`Next steps:`);
44
+ logger.info(` cd ${outputDir}`);
45
+ logger.info(` npm install`);
46
+ logger.info(` npm run build`);
47
+ logger.info(` npm test`);
48
+ }
49
+ catch (error) {
50
+ if (error instanceof Error) {
51
+ logger.error(error.message);
52
+ process.exit(1);
53
+ }
54
+ throw error;
55
+ }
56
+ }
@@ -0,0 +1 @@
1
+ export declare function installCommand(serverName: string): Promise<void>;
@@ -0,0 +1,79 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { getServer } from '../registry/manager.js';
5
+ import { logger } from '../utils/logger.js';
6
+ export async function installCommand(serverName) {
7
+ try {
8
+ // Get server from registry
9
+ const server = await getServer(serverName);
10
+ if (!server) {
11
+ logger.error(`Server not found: ${serverName}`);
12
+ logger.info('Run "mcp-factory list" to see available servers');
13
+ process.exit(1);
14
+ }
15
+ // Check if server build exists
16
+ const buildPath = path.join(server.path, 'build', 'index.js');
17
+ try {
18
+ await fs.access(buildPath);
19
+ }
20
+ catch {
21
+ logger.error(`Server not built yet. Run:`);
22
+ logger.info(` cd ${server.path}`);
23
+ logger.info(` npm install && npm run build`);
24
+ process.exit(1);
25
+ }
26
+ // Determine Claude config paths
27
+ const configPaths = [
28
+ path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
29
+ path.join(os.homedir(), '.claude', 'config.json'),
30
+ ];
31
+ let installed = false;
32
+ for (const configPath of configPaths) {
33
+ try {
34
+ await fs.access(configPath);
35
+ await installToConfig(configPath, serverName, buildPath);
36
+ installed = true;
37
+ const configName = configPath.includes('claude_desktop_config.json')
38
+ ? 'Claude Desktop'
39
+ : 'Claude Code';
40
+ logger.success(`Installed ${serverName} to ${configName}`);
41
+ }
42
+ catch {
43
+ // Config file doesn't exist, skip
44
+ }
45
+ }
46
+ if (!installed) {
47
+ logger.warn('No Claude configuration files found');
48
+ logger.info('Expected locations:');
49
+ configPaths.forEach(p => logger.info(` ${p}`));
50
+ }
51
+ else {
52
+ logger.info('\nNext steps:');
53
+ logger.info(' 1. Edit the config file and add your API credentials');
54
+ logger.info(' 2. Restart Claude Desktop/Code to load the server');
55
+ }
56
+ }
57
+ catch (error) {
58
+ if (error instanceof Error) {
59
+ logger.error(error.message);
60
+ process.exit(1);
61
+ }
62
+ throw error;
63
+ }
64
+ }
65
+ async function installToConfig(configPath, serverName, buildPath) {
66
+ const content = await fs.readFile(configPath, 'utf-8');
67
+ const config = JSON.parse(content);
68
+ if (!config.mcpServers) {
69
+ config.mcpServers = {};
70
+ }
71
+ config.mcpServers[serverName] = {
72
+ command: 'node',
73
+ args: [buildPath],
74
+ env: {
75
+ API_KEY: 'YOUR_API_KEY_HERE',
76
+ },
77
+ };
78
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
79
+ }
@@ -0,0 +1 @@
1
+ export declare function listCommand(): Promise<void>;
@@ -0,0 +1,24 @@
1
+ import { listServers } from '../registry/manager.js';
2
+ import { logger } from '../utils/logger.js';
3
+ export async function listCommand() {
4
+ try {
5
+ const servers = await listServers();
6
+ if (servers.length === 0) {
7
+ logger.info('No MCP servers generated yet');
8
+ return;
9
+ }
10
+ logger.info(`Generated MCP servers (${servers.length}):\n`);
11
+ for (const server of servers) {
12
+ console.log(` ${server.name}`);
13
+ console.log(` Path: ${server.path}`);
14
+ console.log(` Created: ${new Date(server.createdAt).toLocaleString()}\n`);
15
+ }
16
+ }
17
+ catch (error) {
18
+ if (error instanceof Error) {
19
+ logger.error(error.message);
20
+ process.exit(1);
21
+ }
22
+ throw error;
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export declare function validateCommand(input: string): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { detectFormat } from '../parsers/detector.js';
2
+ import { parseOpenAPI } from '../parsers/openapi.js';
3
+ import { logger } from '../utils/logger.js';
4
+ export async function validateCommand(input) {
5
+ try {
6
+ logger.info(`Validating: ${input}`);
7
+ const detection = await detectFormat(input);
8
+ logger.success(`Format detected: ${detection.format}`);
9
+ if (detection.format === 'openapi' || detection.format === 'swagger') {
10
+ const schema = parseOpenAPI(detection.content);
11
+ logger.success(`Valid API specification: ${schema.name}`);
12
+ logger.info(`Base URL: ${schema.baseUrl}`);
13
+ logger.info(`Endpoints: ${schema.endpoints.length}`);
14
+ logger.info(`Auth type: ${schema.auth.type}`);
15
+ }
16
+ else {
17
+ logger.warn('Format detected but parsing not implemented yet');
18
+ }
19
+ }
20
+ catch (error) {
21
+ if (error instanceof Error) {
22
+ logger.error(`Validation failed: ${error.message}`);
23
+ process.exit(1);
24
+ }
25
+ throw error;
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ import { APISchema, DetectedPatterns } from '../schema/api-schema.js';
2
+ export declare function analyzePatterns(schema: APISchema): DetectedPatterns;
@@ -0,0 +1,14 @@
1
+ export function analyzePatterns(schema) {
2
+ return {
3
+ authPattern: schema.auth.type,
4
+ paginationStyle: schema.pagination?.style,
5
+ rateLimitStrategy: schema.rateLimit?.strategy || 'none',
6
+ errorFormat: detectErrorFormat(schema),
7
+ hasWebhooks: false,
8
+ };
9
+ }
10
+ function detectErrorFormat(schema) {
11
+ // Check if any endpoint has custom error schemas
12
+ const hasCustomErrors = schema.endpoints.some(endpoint => endpoint.errors.length > 0 && endpoint.errors.some(e => e.schema));
13
+ return hasCustomErrors ? 'custom' : 'standard';
14
+ }
@@ -0,0 +1,10 @@
1
+ import { APISchema, DetectedPatterns } from '../schema/api-schema.js';
2
+ export interface GenerationContext {
3
+ name: string;
4
+ baseUrl: string;
5
+ auth: any;
6
+ endpoints: any[];
7
+ patterns: DetectedPatterns;
8
+ absolutePath?: string;
9
+ }
10
+ export declare function generateServer(schema: APISchema, patterns: DetectedPatterns, outputDir: string): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import Handlebars from 'handlebars';
4
+ import { GenerationError } from '../utils/errors.js';
5
+ import { fileURLToPath } from 'url';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ // Register Handlebars helper for equality check
9
+ Handlebars.registerHelper('eq', (a, b) => a === b);
10
+ export async function generateServer(schema, patterns, outputDir) {
11
+ const context = {
12
+ name: schema.name,
13
+ baseUrl: schema.baseUrl,
14
+ auth: schema.auth,
15
+ endpoints: schema.endpoints,
16
+ patterns,
17
+ absolutePath: path.resolve(outputDir),
18
+ };
19
+ // Create output directory structure
20
+ await fs.mkdir(path.join(outputDir, 'src'), { recursive: true });
21
+ // Get template directory
22
+ const templateDir = path.join(__dirname, '..', '..', 'templates');
23
+ // Generate files from templates
24
+ await generateFile(templateDir, outputDir, 'package.json.hbs', 'package.json', context);
25
+ await generateFile(templateDir, outputDir, 'tsconfig.json.hbs', 'tsconfig.json', context);
26
+ await generateFile(templateDir, outputDir, 'README.md.hbs', 'README.md', context);
27
+ await generateFile(templateDir, outputDir, 'index.ts.hbs', 'src/index.ts', context);
28
+ await generateFile(templateDir, outputDir, 'client.ts.hbs', 'src/client.ts', context);
29
+ await generateFile(templateDir, outputDir, 'tools.ts.hbs', 'src/tools.ts', context);
30
+ await generateFile(templateDir, outputDir, 'types.ts.hbs', 'src/types.ts', context);
31
+ await generateFile(templateDir, outputDir, 'validation.ts.hbs', 'src/validation.ts', context);
32
+ await generateFile(templateDir, outputDir, 'test.ts.hbs', 'test.ts', context);
33
+ }
34
+ async function generateFile(templateDir, outputDir, templateFile, outputFile, context) {
35
+ try {
36
+ const templatePath = path.join(templateDir, templateFile);
37
+ const templateContent = await fs.readFile(templatePath, 'utf-8');
38
+ const template = Handlebars.compile(templateContent);
39
+ const output = template(context);
40
+ const outputPath = path.join(outputDir, outputFile);
41
+ await fs.writeFile(outputPath, output, 'utf-8');
42
+ }
43
+ catch (error) {
44
+ throw new GenerationError(`Failed to generate ${outputFile}: ${error}`);
45
+ }
46
+ }
@@ -0,0 +1,2 @@
1
+ import { APISchema } from '../schema/api-schema.js';
2
+ export declare function parseWithAI(content: string): Promise<APISchema>;
@@ -0,0 +1,7 @@
1
+ import { ParseError } from '../utils/errors.js';
2
+ export async function parseWithAI(content) {
3
+ if (!process.env.ANTHROPIC_API_KEY) {
4
+ throw new ParseError('ANTHROPIC_API_KEY environment variable required for AI parsing');
5
+ }
6
+ throw new ParseError('AI-powered parsing not yet implemented');
7
+ }
@@ -0,0 +1,6 @@
1
+ export type InputFormat = 'openapi' | 'swagger' | 'postman' | 'unknown';
2
+ export interface DetectionResult {
3
+ format: InputFormat;
4
+ content: any;
5
+ }
6
+ export declare function detectFormat(input: string): Promise<DetectionResult>;
@@ -0,0 +1,38 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as yaml from 'yaml';
3
+ import { ParseError } from '../utils/errors.js';
4
+ export async function detectFormat(input) {
5
+ let content;
6
+ // Check if input is a file path
7
+ try {
8
+ content = await fs.readFile(input, 'utf-8');
9
+ }
10
+ catch {
11
+ throw new ParseError(`Could not read file: ${input}`);
12
+ }
13
+ // Try parsing as JSON
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(content);
17
+ }
18
+ catch {
19
+ // Try parsing as YAML
20
+ try {
21
+ parsed = yaml.parse(content);
22
+ }
23
+ catch {
24
+ throw new ParseError('Could not parse input as JSON or YAML');
25
+ }
26
+ }
27
+ // Detect format from parsed content
28
+ if (parsed.openapi) {
29
+ return { format: 'openapi', content: parsed };
30
+ }
31
+ if (parsed.swagger) {
32
+ return { format: 'swagger', content: parsed };
33
+ }
34
+ if (parsed.info?.schema?.includes('postman')) {
35
+ return { format: 'postman', content: parsed };
36
+ }
37
+ return { format: 'unknown', content: parsed };
38
+ }
@@ -0,0 +1,5 @@
1
+ import { APISchema } from '../schema/api-schema.js';
2
+ /**
3
+ * Parse OpenAPI 3.x or Swagger 2.0 specification into unified APISchema
4
+ */
5
+ export declare function parseOpenAPI(spec: any): APISchema;