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