@stackkedjohn/mcp-factory-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +100 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +33 -0
  4. package/dist/commands/create.d.ts +4 -0
  5. package/dist/commands/create.js +56 -0
  6. package/dist/commands/install.d.ts +1 -0
  7. package/dist/commands/install.js +79 -0
  8. package/dist/commands/list.d.ts +1 -0
  9. package/dist/commands/list.js +24 -0
  10. package/dist/commands/validate.d.ts +1 -0
  11. package/dist/commands/validate.js +27 -0
  12. package/dist/generator/analyzer.d.ts +2 -0
  13. package/dist/generator/analyzer.js +14 -0
  14. package/dist/generator/engine.d.ts +10 -0
  15. package/dist/generator/engine.js +46 -0
  16. package/dist/parsers/ai-parser.d.ts +2 -0
  17. package/dist/parsers/ai-parser.js +7 -0
  18. package/dist/parsers/detector.d.ts +6 -0
  19. package/dist/parsers/detector.js +38 -0
  20. package/dist/parsers/openapi.d.ts +5 -0
  21. package/dist/parsers/openapi.js +205 -0
  22. package/dist/parsers/postman.d.ts +2 -0
  23. package/dist/parsers/postman.js +4 -0
  24. package/dist/registry/manager.d.ts +13 -0
  25. package/dist/registry/manager.js +43 -0
  26. package/dist/schema/api-schema.d.ts +77 -0
  27. package/dist/schema/api-schema.js +1 -0
  28. package/dist/utils/errors.d.ts +13 -0
  29. package/dist/utils/errors.js +26 -0
  30. package/dist/utils/logger.d.ts +7 -0
  31. package/dist/utils/logger.js +19 -0
  32. package/docs/plans/2026-02-02-mcp-factory-design.md +306 -0
  33. package/docs/plans/2026-02-02-mcp-factory-implementation.md +1866 -0
  34. package/package.json +48 -0
  35. package/src/cli.ts +41 -0
  36. package/src/commands/create.ts +65 -0
  37. package/src/commands/install.ts +92 -0
  38. package/src/commands/list.ts +28 -0
  39. package/src/commands/validate.ts +29 -0
  40. package/src/generator/analyzer.ts +20 -0
  41. package/src/generator/engine.ts +73 -0
  42. package/src/parsers/ai-parser.ts +10 -0
  43. package/src/parsers/detector.ts +49 -0
  44. package/src/parsers/openapi.ts +238 -0
  45. package/src/parsers/postman.ts +6 -0
  46. package/src/registry/manager.ts +62 -0
  47. package/src/schema/api-schema.ts +87 -0
  48. package/src/utils/errors.ts +27 -0
  49. package/src/utils/logger.ts +23 -0
  50. package/templates/README.md.hbs +40 -0
  51. package/templates/client.ts.hbs +45 -0
  52. package/templates/index.ts.hbs +36 -0
  53. package/templates/package.json.hbs +20 -0
  54. package/templates/test.ts.hbs +1 -0
  55. package/templates/tools.ts.hbs +38 -0
  56. package/templates/tsconfig.json.hbs +13 -0
  57. package/templates/types.ts.hbs +1 -0
  58. package/templates/validation.ts.hbs +1 -0
  59. package/test-fixtures/weather-api.json +49 -0
  60. package/tsconfig.json +17 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Parse OpenAPI 3.x or Swagger 2.0 specification into unified APISchema
3
+ */
4
+ export function parseOpenAPI(spec) {
5
+ const isSwagger2 = spec.swagger === '2.0';
6
+ const isOpenAPI3 = spec.openapi?.startsWith('3.');
7
+ if (!isSwagger2 && !isOpenAPI3) {
8
+ throw new Error('Unsupported spec version. Only OpenAPI 3.x and Swagger 2.0 are supported.');
9
+ }
10
+ return {
11
+ name: spec.info.title || 'API',
12
+ baseUrl: getBaseUrl(spec),
13
+ auth: detectAuth(spec),
14
+ endpoints: parseEndpoints(spec),
15
+ };
16
+ }
17
+ /**
18
+ * Extract base URL from spec
19
+ */
20
+ function getBaseUrl(spec) {
21
+ // OpenAPI 3.x
22
+ if (spec.servers && spec.servers.length > 0) {
23
+ return spec.servers[0].url;
24
+ }
25
+ // Swagger 2.0
26
+ if (spec.host) {
27
+ const scheme = spec.schemes?.[0] || 'https';
28
+ const basePath = spec.basePath || '';
29
+ return `${scheme}://${spec.host}${basePath}`;
30
+ }
31
+ return '';
32
+ }
33
+ /**
34
+ * Detect authentication configuration
35
+ */
36
+ function detectAuth(spec) {
37
+ const securitySchemes = spec.components?.securitySchemes || spec.securityDefinitions || {};
38
+ const firstScheme = Object.values(securitySchemes)[0];
39
+ if (!firstScheme) {
40
+ return { type: 'none' };
41
+ }
42
+ switch (firstScheme.type) {
43
+ case 'apiKey':
44
+ return {
45
+ type: 'api-key',
46
+ location: firstScheme.in === 'header' ? 'header' : 'query',
47
+ name: firstScheme.name,
48
+ description: firstScheme.description,
49
+ };
50
+ case 'http':
51
+ if (firstScheme.scheme === 'bearer') {
52
+ return { type: 'bearer', description: firstScheme.description };
53
+ }
54
+ if (firstScheme.scheme === 'basic') {
55
+ return { type: 'basic', description: firstScheme.description };
56
+ }
57
+ return { type: 'none' };
58
+ case 'oauth2':
59
+ return { type: 'oauth', description: firstScheme.description };
60
+ default:
61
+ return { type: 'none' };
62
+ }
63
+ }
64
+ /**
65
+ * Parse all endpoints from spec
66
+ */
67
+ function parseEndpoints(spec) {
68
+ const endpoints = [];
69
+ const paths = spec.paths || {};
70
+ for (const [path, pathItem] of Object.entries(paths)) {
71
+ const methods = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
72
+ for (const method of methods) {
73
+ const operation = pathItem[method];
74
+ if (operation) {
75
+ endpoints.push(parseOperation(path, method, operation, pathItem));
76
+ }
77
+ }
78
+ }
79
+ return endpoints;
80
+ }
81
+ /**
82
+ * Parse a single operation into an Endpoint
83
+ */
84
+ function parseOperation(path, method, operation, pathItem) {
85
+ const parameters = [];
86
+ // Parse path-level parameters
87
+ if (pathItem.parameters) {
88
+ parameters.push(...parseParameters(pathItem.parameters));
89
+ }
90
+ // Parse operation-level parameters
91
+ if (operation.parameters) {
92
+ parameters.push(...parseParameters(operation.parameters));
93
+ }
94
+ // Parse request body (OpenAPI 3.x)
95
+ let requestBody;
96
+ if (operation.requestBody) {
97
+ const content = operation.requestBody.content || {};
98
+ const jsonContent = content['application/json'];
99
+ if (jsonContent?.schema) {
100
+ requestBody = {
101
+ description: operation.requestBody.description || '',
102
+ required: operation.requestBody.required || false,
103
+ contentType: 'application/json',
104
+ schema: parseSchema(jsonContent.schema),
105
+ };
106
+ }
107
+ }
108
+ // Parse response (use first successful response)
109
+ const responses = operation.responses || {};
110
+ const successCode = Object.keys(responses).find(code => code.startsWith('2')) || '200';
111
+ const successResponse = responses[successCode] || {};
112
+ const responseContent = successResponse.content?.['application/json'];
113
+ const response = {
114
+ statusCode: parseInt(successCode),
115
+ description: successResponse.description || '',
116
+ contentType: 'application/json',
117
+ schema: responseContent?.schema ? parseSchema(responseContent.schema) : { type: 'object' },
118
+ };
119
+ // Parse error responses
120
+ const errors = [];
121
+ for (const [code, resp] of Object.entries(responses)) {
122
+ if (!code.startsWith('2') && code !== 'default') {
123
+ const errorResp = resp;
124
+ const errorContent = errorResp.content?.['application/json'];
125
+ errors.push({
126
+ statusCode: parseInt(code),
127
+ description: errorResp.description || `Error ${code}`,
128
+ schema: errorContent?.schema ? parseSchema(errorContent.schema) : undefined,
129
+ });
130
+ }
131
+ }
132
+ return {
133
+ id: operation.operationId || `${method}_${path.replace(/\//g, '_')}`,
134
+ method: method.toUpperCase(),
135
+ path,
136
+ description: operation.description || operation.summary || '',
137
+ parameters,
138
+ requestBody,
139
+ response,
140
+ errors,
141
+ };
142
+ }
143
+ /**
144
+ * Parse parameters array
145
+ */
146
+ function parseParameters(params) {
147
+ return params.map((param) => {
148
+ // Handle $ref
149
+ if (param.$ref) {
150
+ // For simplicity, we'll skip resolving refs
151
+ return null;
152
+ }
153
+ // Only parse path, query, and header parameters (not body)
154
+ if (!['path', 'query', 'header'].includes(param.in)) {
155
+ return null;
156
+ }
157
+ const schema = param.schema || { type: param.type };
158
+ return {
159
+ name: param.name,
160
+ in: param.in,
161
+ required: param.required || false,
162
+ description: param.description || '',
163
+ schema: parseSchema(schema),
164
+ };
165
+ }).filter(Boolean);
166
+ }
167
+ /**
168
+ * Parse JSON Schema into SchemaType
169
+ */
170
+ function parseSchema(schema) {
171
+ if (!schema) {
172
+ return { type: 'string' };
173
+ }
174
+ // Handle basic types
175
+ if (schema.type === 'object' || schema.properties) {
176
+ const properties = {};
177
+ const required = schema.required || [];
178
+ for (const [key, value] of Object.entries(schema.properties || {})) {
179
+ properties[key] = parseSchema(value);
180
+ }
181
+ return {
182
+ type: 'object',
183
+ properties,
184
+ required,
185
+ };
186
+ }
187
+ if (schema.type === 'array') {
188
+ return {
189
+ type: 'array',
190
+ items: parseSchema(schema.items),
191
+ };
192
+ }
193
+ // Enum
194
+ if (schema.enum) {
195
+ return {
196
+ type: schema.type || 'string',
197
+ enum: schema.enum,
198
+ };
199
+ }
200
+ // Basic types with format
201
+ return {
202
+ type: schema.type || 'string',
203
+ format: schema.format,
204
+ };
205
+ }
@@ -0,0 +1,2 @@
1
+ import { APISchema } from '../schema/api-schema.js';
2
+ export declare function parsePostman(collection: any): APISchema;
@@ -0,0 +1,4 @@
1
+ import { ParseError } from '../utils/errors.js';
2
+ export function parsePostman(collection) {
3
+ throw new ParseError('Postman collection parsing not yet implemented');
4
+ }
@@ -0,0 +1,13 @@
1
+ export interface RegistryEntry {
2
+ name: string;
3
+ path: string;
4
+ createdAt: string;
5
+ }
6
+ export interface Registry {
7
+ servers: RegistryEntry[];
8
+ }
9
+ export declare function loadRegistry(): Promise<Registry>;
10
+ export declare function saveRegistry(registry: Registry): Promise<void>;
11
+ export declare function addServer(name: string, serverPath: string): Promise<void>;
12
+ export declare function getServer(name: string): Promise<RegistryEntry | undefined>;
13
+ export declare function listServers(): Promise<RegistryEntry[]>;
@@ -0,0 +1,43 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ const REGISTRY_DIR = path.join(os.homedir(), '.mcp-factory');
5
+ const REGISTRY_FILE = path.join(REGISTRY_DIR, 'registry.json');
6
+ async function ensureRegistry() {
7
+ try {
8
+ await fs.access(REGISTRY_FILE);
9
+ }
10
+ catch {
11
+ await fs.mkdir(REGISTRY_DIR, { recursive: true });
12
+ await fs.writeFile(REGISTRY_FILE, JSON.stringify({ servers: [] }, null, 2));
13
+ }
14
+ }
15
+ export async function loadRegistry() {
16
+ await ensureRegistry();
17
+ const content = await fs.readFile(REGISTRY_FILE, 'utf-8');
18
+ return JSON.parse(content);
19
+ }
20
+ export async function saveRegistry(registry) {
21
+ await ensureRegistry();
22
+ await fs.writeFile(REGISTRY_FILE, JSON.stringify(registry, null, 2));
23
+ }
24
+ export async function addServer(name, serverPath) {
25
+ const registry = await loadRegistry();
26
+ // Remove existing entry if present
27
+ registry.servers = registry.servers.filter(s => s.name !== name);
28
+ // Add new entry
29
+ registry.servers.push({
30
+ name,
31
+ path: path.resolve(serverPath),
32
+ createdAt: new Date().toISOString(),
33
+ });
34
+ await saveRegistry(registry);
35
+ }
36
+ export async function getServer(name) {
37
+ const registry = await loadRegistry();
38
+ return registry.servers.find(s => s.name === name);
39
+ }
40
+ export async function listServers() {
41
+ const registry = await loadRegistry();
42
+ return registry.servers;
43
+ }
@@ -0,0 +1,77 @@
1
+ export interface APISchema {
2
+ name: string;
3
+ baseUrl: string;
4
+ auth: AuthConfig;
5
+ endpoints: Endpoint[];
6
+ commonHeaders?: Record<string, string>;
7
+ rateLimit?: RateLimitConfig;
8
+ pagination?: PaginationConfig;
9
+ }
10
+ export interface AuthConfig {
11
+ type: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
12
+ location?: 'header' | 'query';
13
+ name?: string;
14
+ description?: string;
15
+ }
16
+ export interface Endpoint {
17
+ id: string;
18
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
19
+ path: string;
20
+ description: string;
21
+ parameters: Parameter[];
22
+ requestBody?: RequestBody;
23
+ response: ResponseSchema;
24
+ errors: ErrorSchema[];
25
+ }
26
+ export interface Parameter {
27
+ name: string;
28
+ in: 'path' | 'query' | 'header';
29
+ description?: string;
30
+ required: boolean;
31
+ schema: SchemaType;
32
+ }
33
+ export interface RequestBody {
34
+ description?: string;
35
+ required: boolean;
36
+ contentType: string;
37
+ schema: SchemaType;
38
+ }
39
+ export interface ResponseSchema {
40
+ statusCode: number;
41
+ description?: string;
42
+ contentType: string;
43
+ schema: SchemaType;
44
+ }
45
+ export interface ErrorSchema {
46
+ statusCode: number;
47
+ description: string;
48
+ schema?: SchemaType;
49
+ }
50
+ export interface SchemaType {
51
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
52
+ properties?: Record<string, SchemaType>;
53
+ items?: SchemaType;
54
+ required?: string[];
55
+ enum?: string[];
56
+ format?: string;
57
+ }
58
+ export interface RateLimitConfig {
59
+ strategy: 'header-based' | 'retry-after' | 'none';
60
+ headerName?: string;
61
+ requestsPerWindow?: number;
62
+ windowSeconds?: number;
63
+ }
64
+ export interface PaginationConfig {
65
+ style: 'cursor' | 'offset' | 'page' | 'link-header';
66
+ cursorParam?: string;
67
+ limitParam?: string;
68
+ offsetParam?: string;
69
+ pageParam?: string;
70
+ }
71
+ export interface DetectedPatterns {
72
+ authPattern: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
73
+ paginationStyle?: 'cursor' | 'offset' | 'page' | 'link-header';
74
+ rateLimitStrategy?: 'header-based' | 'retry-after' | 'none';
75
+ errorFormat: 'standard' | 'custom';
76
+ hasWebhooks: boolean;
77
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export declare class MCPFactoryError extends Error {
2
+ code: string;
3
+ constructor(message: string, code: string);
4
+ }
5
+ export declare class ParseError extends MCPFactoryError {
6
+ constructor(message: string);
7
+ }
8
+ export declare class ValidationError extends MCPFactoryError {
9
+ constructor(message: string);
10
+ }
11
+ export declare class GenerationError extends MCPFactoryError {
12
+ constructor(message: string);
13
+ }
@@ -0,0 +1,26 @@
1
+ export class MCPFactoryError extends Error {
2
+ code;
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = 'MCPFactoryError';
7
+ }
8
+ }
9
+ export class ParseError extends MCPFactoryError {
10
+ constructor(message) {
11
+ super(message, 'PARSE_ERROR');
12
+ this.name = 'ParseError';
13
+ }
14
+ }
15
+ export class ValidationError extends MCPFactoryError {
16
+ constructor(message) {
17
+ super(message, 'VALIDATION_ERROR');
18
+ this.name = 'ValidationError';
19
+ }
20
+ }
21
+ export class GenerationError extends MCPFactoryError {
22
+ constructor(message) {
23
+ super(message, 'GENERATION_ERROR');
24
+ this.name = 'GenerationError';
25
+ }
26
+ }
@@ -0,0 +1,7 @@
1
+ export declare const logger: {
2
+ info: (message: string) => void;
3
+ success: (message: string) => void;
4
+ error: (message: string) => void;
5
+ warn: (message: string) => void;
6
+ debug: (message: string) => void;
7
+ };
@@ -0,0 +1,19 @@
1
+ export const logger = {
2
+ info: (message) => {
3
+ console.log(`ℹ ${message}`);
4
+ },
5
+ success: (message) => {
6
+ console.log(`✓ ${message}`);
7
+ },
8
+ error: (message) => {
9
+ console.error(`✗ ${message}`);
10
+ },
11
+ warn: (message) => {
12
+ console.warn(`⚠ ${message}`);
13
+ },
14
+ debug: (message) => {
15
+ if (process.env.DEBUG) {
16
+ console.log(`[DEBUG] ${message}`);
17
+ }
18
+ }
19
+ };