@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,238 @@
|
|
|
1
|
+
import { APISchema, Endpoint, Parameter, SchemaType, AuthConfig, RequestBody, ResponseSchema, ErrorSchema } from '../schema/api-schema.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse OpenAPI 3.x or Swagger 2.0 specification into unified APISchema
|
|
5
|
+
*/
|
|
6
|
+
export function parseOpenAPI(spec: any): APISchema {
|
|
7
|
+
const isSwagger2 = spec.swagger === '2.0';
|
|
8
|
+
const isOpenAPI3 = spec.openapi?.startsWith('3.');
|
|
9
|
+
|
|
10
|
+
if (!isSwagger2 && !isOpenAPI3) {
|
|
11
|
+
throw new Error('Unsupported spec version. Only OpenAPI 3.x and Swagger 2.0 are supported.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
name: spec.info.title || 'API',
|
|
16
|
+
baseUrl: getBaseUrl(spec),
|
|
17
|
+
auth: detectAuth(spec),
|
|
18
|
+
endpoints: parseEndpoints(spec),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract base URL from spec
|
|
24
|
+
*/
|
|
25
|
+
function getBaseUrl(spec: any): string {
|
|
26
|
+
// OpenAPI 3.x
|
|
27
|
+
if (spec.servers && spec.servers.length > 0) {
|
|
28
|
+
return spec.servers[0].url;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Swagger 2.0
|
|
32
|
+
if (spec.host) {
|
|
33
|
+
const scheme = spec.schemes?.[0] || 'https';
|
|
34
|
+
const basePath = spec.basePath || '';
|
|
35
|
+
return `${scheme}://${spec.host}${basePath}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detect authentication configuration
|
|
43
|
+
*/
|
|
44
|
+
function detectAuth(spec: any): AuthConfig {
|
|
45
|
+
const securitySchemes = spec.components?.securitySchemes || spec.securityDefinitions || {};
|
|
46
|
+
const firstScheme = Object.values(securitySchemes)[0] as any;
|
|
47
|
+
|
|
48
|
+
if (!firstScheme) {
|
|
49
|
+
return { type: 'none' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
switch (firstScheme.type) {
|
|
53
|
+
case 'apiKey':
|
|
54
|
+
return {
|
|
55
|
+
type: 'api-key',
|
|
56
|
+
location: firstScheme.in === 'header' ? 'header' : 'query',
|
|
57
|
+
name: firstScheme.name,
|
|
58
|
+
description: firstScheme.description,
|
|
59
|
+
};
|
|
60
|
+
case 'http':
|
|
61
|
+
if (firstScheme.scheme === 'bearer') {
|
|
62
|
+
return { type: 'bearer', description: firstScheme.description };
|
|
63
|
+
}
|
|
64
|
+
if (firstScheme.scheme === 'basic') {
|
|
65
|
+
return { type: 'basic', description: firstScheme.description };
|
|
66
|
+
}
|
|
67
|
+
return { type: 'none' };
|
|
68
|
+
case 'oauth2':
|
|
69
|
+
return { type: 'oauth', description: firstScheme.description };
|
|
70
|
+
default:
|
|
71
|
+
return { type: 'none' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse all endpoints from spec
|
|
77
|
+
*/
|
|
78
|
+
function parseEndpoints(spec: any): Endpoint[] {
|
|
79
|
+
const endpoints: Endpoint[] = [];
|
|
80
|
+
const paths = spec.paths || {};
|
|
81
|
+
|
|
82
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
83
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
|
|
84
|
+
|
|
85
|
+
for (const method of methods) {
|
|
86
|
+
const operation = (pathItem as any)[method];
|
|
87
|
+
if (operation) {
|
|
88
|
+
endpoints.push(parseOperation(path, method, operation, pathItem as any));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return endpoints;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse a single operation into an Endpoint
|
|
98
|
+
*/
|
|
99
|
+
function parseOperation(path: string, method: string, operation: any, pathItem: any): Endpoint {
|
|
100
|
+
const parameters: Parameter[] = [];
|
|
101
|
+
|
|
102
|
+
// Parse path-level parameters
|
|
103
|
+
if (pathItem.parameters) {
|
|
104
|
+
parameters.push(...parseParameters(pathItem.parameters));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Parse operation-level parameters
|
|
108
|
+
if (operation.parameters) {
|
|
109
|
+
parameters.push(...parseParameters(operation.parameters));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Parse request body (OpenAPI 3.x)
|
|
113
|
+
let requestBody: RequestBody | undefined;
|
|
114
|
+
if (operation.requestBody) {
|
|
115
|
+
const content = operation.requestBody.content || {};
|
|
116
|
+
const jsonContent = content['application/json'];
|
|
117
|
+
if (jsonContent?.schema) {
|
|
118
|
+
requestBody = {
|
|
119
|
+
description: operation.requestBody.description || '',
|
|
120
|
+
required: operation.requestBody.required || false,
|
|
121
|
+
contentType: 'application/json',
|
|
122
|
+
schema: parseSchema(jsonContent.schema),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Parse response (use first successful response)
|
|
128
|
+
const responses = operation.responses || {};
|
|
129
|
+
const successCode = Object.keys(responses).find(code => code.startsWith('2')) || '200';
|
|
130
|
+
const successResponse = responses[successCode] || {};
|
|
131
|
+
const responseContent = successResponse.content?.['application/json'];
|
|
132
|
+
|
|
133
|
+
const response: ResponseSchema = {
|
|
134
|
+
statusCode: parseInt(successCode),
|
|
135
|
+
description: successResponse.description || '',
|
|
136
|
+
contentType: 'application/json',
|
|
137
|
+
schema: responseContent?.schema ? parseSchema(responseContent.schema) : { type: 'object' },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Parse error responses
|
|
141
|
+
const errors: ErrorSchema[] = [];
|
|
142
|
+
for (const [code, resp] of Object.entries(responses)) {
|
|
143
|
+
if (!code.startsWith('2') && code !== 'default') {
|
|
144
|
+
const errorResp = resp as any;
|
|
145
|
+
const errorContent = errorResp.content?.['application/json'];
|
|
146
|
+
errors.push({
|
|
147
|
+
statusCode: parseInt(code),
|
|
148
|
+
description: errorResp.description || `Error ${code}`,
|
|
149
|
+
schema: errorContent?.schema ? parseSchema(errorContent.schema) : undefined,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
id: operation.operationId || `${method}_${path.replace(/\//g, '_')}`,
|
|
156
|
+
method: method.toUpperCase() as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
157
|
+
path,
|
|
158
|
+
description: operation.description || operation.summary || '',
|
|
159
|
+
parameters,
|
|
160
|
+
requestBody,
|
|
161
|
+
response,
|
|
162
|
+
errors,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parse parameters array
|
|
168
|
+
*/
|
|
169
|
+
function parseParameters(params: any[]): Parameter[] {
|
|
170
|
+
return params.map((param) => {
|
|
171
|
+
// Handle $ref
|
|
172
|
+
if (param.$ref) {
|
|
173
|
+
// For simplicity, we'll skip resolving refs
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Only parse path, query, and header parameters (not body)
|
|
178
|
+
if (!['path', 'query', 'header'].includes(param.in)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const schema = param.schema || { type: param.type };
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
name: param.name,
|
|
186
|
+
in: param.in as 'path' | 'query' | 'header',
|
|
187
|
+
required: param.required || false,
|
|
188
|
+
description: param.description || '',
|
|
189
|
+
schema: parseSchema(schema),
|
|
190
|
+
};
|
|
191
|
+
}).filter(Boolean) as Parameter[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parse JSON Schema into SchemaType
|
|
196
|
+
*/
|
|
197
|
+
function parseSchema(schema: any): SchemaType {
|
|
198
|
+
if (!schema) {
|
|
199
|
+
return { type: 'string' };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle basic types
|
|
203
|
+
if (schema.type === 'object' || schema.properties) {
|
|
204
|
+
const properties: Record<string, SchemaType> = {};
|
|
205
|
+
const required = schema.required || [];
|
|
206
|
+
|
|
207
|
+
for (const [key, value] of Object.entries(schema.properties || {})) {
|
|
208
|
+
properties[key] = parseSchema(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties,
|
|
214
|
+
required,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (schema.type === 'array') {
|
|
219
|
+
return {
|
|
220
|
+
type: 'array',
|
|
221
|
+
items: parseSchema(schema.items),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Enum
|
|
226
|
+
if (schema.enum) {
|
|
227
|
+
return {
|
|
228
|
+
type: schema.type || 'string',
|
|
229
|
+
enum: schema.enum,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Basic types with format
|
|
234
|
+
return {
|
|
235
|
+
type: schema.type || 'string',
|
|
236
|
+
format: schema.format,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
|
|
5
|
+
export interface RegistryEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Registry {
|
|
12
|
+
servers: RegistryEntry[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const REGISTRY_DIR = path.join(os.homedir(), '.mcp-factory');
|
|
16
|
+
const REGISTRY_FILE = path.join(REGISTRY_DIR, 'registry.json');
|
|
17
|
+
|
|
18
|
+
async function ensureRegistry(): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(REGISTRY_FILE);
|
|
21
|
+
} catch {
|
|
22
|
+
await fs.mkdir(REGISTRY_DIR, { recursive: true });
|
|
23
|
+
await fs.writeFile(REGISTRY_FILE, JSON.stringify({ servers: [] }, null, 2));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function loadRegistry(): Promise<Registry> {
|
|
28
|
+
await ensureRegistry();
|
|
29
|
+
const content = await fs.readFile(REGISTRY_FILE, 'utf-8');
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function saveRegistry(registry: Registry): Promise<void> {
|
|
34
|
+
await ensureRegistry();
|
|
35
|
+
await fs.writeFile(REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function addServer(name: string, serverPath: string): Promise<void> {
|
|
39
|
+
const registry = await loadRegistry();
|
|
40
|
+
|
|
41
|
+
// Remove existing entry if present
|
|
42
|
+
registry.servers = registry.servers.filter(s => s.name !== name);
|
|
43
|
+
|
|
44
|
+
// Add new entry
|
|
45
|
+
registry.servers.push({
|
|
46
|
+
name,
|
|
47
|
+
path: path.resolve(serverPath),
|
|
48
|
+
createdAt: new Date().toISOString(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await saveRegistry(registry);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getServer(name: string): Promise<RegistryEntry | undefined> {
|
|
55
|
+
const registry = await loadRegistry();
|
|
56
|
+
return registry.servers.find(s => s.name === name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function listServers(): Promise<RegistryEntry[]> {
|
|
60
|
+
const registry = await loadRegistry();
|
|
61
|
+
return registry.servers;
|
|
62
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
|
|
11
|
+
export interface AuthConfig {
|
|
12
|
+
type: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
|
|
13
|
+
location?: 'header' | 'query';
|
|
14
|
+
name?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Endpoint {
|
|
19
|
+
id: string;
|
|
20
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
21
|
+
path: string;
|
|
22
|
+
description: string;
|
|
23
|
+
parameters: Parameter[];
|
|
24
|
+
requestBody?: RequestBody;
|
|
25
|
+
response: ResponseSchema;
|
|
26
|
+
errors: ErrorSchema[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Parameter {
|
|
30
|
+
name: string;
|
|
31
|
+
in: 'path' | 'query' | 'header';
|
|
32
|
+
description?: string;
|
|
33
|
+
required: boolean;
|
|
34
|
+
schema: SchemaType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RequestBody {
|
|
38
|
+
description?: string;
|
|
39
|
+
required: boolean;
|
|
40
|
+
contentType: string;
|
|
41
|
+
schema: SchemaType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ResponseSchema {
|
|
45
|
+
statusCode: number;
|
|
46
|
+
description?: string;
|
|
47
|
+
contentType: string;
|
|
48
|
+
schema: SchemaType;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ErrorSchema {
|
|
52
|
+
statusCode: number;
|
|
53
|
+
description: string;
|
|
54
|
+
schema?: SchemaType;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SchemaType {
|
|
58
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
59
|
+
properties?: Record<string, SchemaType>;
|
|
60
|
+
items?: SchemaType;
|
|
61
|
+
required?: string[];
|
|
62
|
+
enum?: string[];
|
|
63
|
+
format?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RateLimitConfig {
|
|
67
|
+
strategy: 'header-based' | 'retry-after' | 'none';
|
|
68
|
+
headerName?: string;
|
|
69
|
+
requestsPerWindow?: number;
|
|
70
|
+
windowSeconds?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface PaginationConfig {
|
|
74
|
+
style: 'cursor' | 'offset' | 'page' | 'link-header';
|
|
75
|
+
cursorParam?: string;
|
|
76
|
+
limitParam?: string;
|
|
77
|
+
offsetParam?: string;
|
|
78
|
+
pageParam?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface DetectedPatterns {
|
|
82
|
+
authPattern: 'api-key' | 'bearer' | 'oauth' | 'basic' | 'none';
|
|
83
|
+
paginationStyle?: 'cursor' | 'offset' | 'page' | 'link-header';
|
|
84
|
+
rateLimitStrategy?: 'header-based' | 'retry-after' | 'none';
|
|
85
|
+
errorFormat: 'standard' | 'custom';
|
|
86
|
+
hasWebhooks: boolean;
|
|
87
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class MCPFactoryError extends Error {
|
|
2
|
+
constructor(message: string, public code: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'MCPFactoryError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class ParseError extends MCPFactoryError {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message, 'PARSE_ERROR');
|
|
11
|
+
this.name = 'ParseError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ValidationError extends MCPFactoryError {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message, 'VALIDATION_ERROR');
|
|
18
|
+
this.name = 'ValidationError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class GenerationError extends MCPFactoryError {
|
|
23
|
+
constructor(message: string) {
|
|
24
|
+
super(message, 'GENERATION_ERROR');
|
|
25
|
+
this.name = 'GenerationError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const logger = {
|
|
2
|
+
info: (message: string) => {
|
|
3
|
+
console.log(`ℹ ${message}`);
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
success: (message: string) => {
|
|
7
|
+
console.log(`✓ ${message}`);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
error: (message: string) => {
|
|
11
|
+
console.error(`✗ ${message}`);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
warn: (message: string) => {
|
|
15
|
+
console.warn(`⚠ ${message}`);
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
debug: (message: string) => {
|
|
19
|
+
if (process.env.DEBUG) {
|
|
20
|
+
console.log(`[DEBUG] ${message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# {{name}} MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server for the {{name}} API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"{{name}}": {
|
|
20
|
+
"command": "node",
|
|
21
|
+
"args": ["{{absolutePath}}/build/index.js"],
|
|
22
|
+
"env": {
|
|
23
|
+
{{#if (eq patterns.authPattern 'api-key')}}
|
|
24
|
+
"API_KEY": "your-api-key-here"
|
|
25
|
+
{{/if}}
|
|
26
|
+
{{#if (eq patterns.authPattern 'bearer')}}
|
|
27
|
+
"BEARER_TOKEN": "your-bearer-token-here"
|
|
28
|
+
{{/if}}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Available tools:
|
|
38
|
+
{{#each endpoints}}
|
|
39
|
+
- `{{id}}`: {{description}}
|
|
40
|
+
{{/each}}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
export interface APIClient {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
request: (method: string, path: string, params?: any) => Promise<any>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createClient(baseUrl: string): APIClient {
|
|
9
|
+
return {
|
|
10
|
+
baseUrl,
|
|
11
|
+
async request(method: string, path: string, params?: any) {
|
|
12
|
+
const headers: Record<string, string> = {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
{{#if (eq patterns.authPattern 'api-key')}}
|
|
17
|
+
if (process.env.API_KEY) {
|
|
18
|
+
{{#if (eq auth.location 'header')}}
|
|
19
|
+
headers['{{auth.name}}'] = process.env.API_KEY;
|
|
20
|
+
{{/if}}
|
|
21
|
+
}
|
|
22
|
+
{{/if}}
|
|
23
|
+
|
|
24
|
+
{{#if (eq patterns.authPattern 'bearer')}}
|
|
25
|
+
if (process.env.BEARER_TOKEN) {
|
|
26
|
+
headers['Authorization'] = `Bearer ${process.env.BEARER_TOKEN}`;
|
|
27
|
+
}
|
|
28
|
+
{{/if}}
|
|
29
|
+
|
|
30
|
+
const url = new URL(path, baseUrl);
|
|
31
|
+
|
|
32
|
+
const response = await fetch(url.toString(), {
|
|
33
|
+
method,
|
|
34
|
+
headers,
|
|
35
|
+
body: params ? JSON.stringify(params) : undefined,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return response.json();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { createClient } from './client.js';
|
|
7
|
+
import { tools, handleToolCall } from './tools.js';
|
|
8
|
+
|
|
9
|
+
const server = new Server(
|
|
10
|
+
{
|
|
11
|
+
name: '{{name}}-mcp',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
capabilities: {
|
|
16
|
+
tools: {},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const client = createClient('{{baseUrl}}');
|
|
22
|
+
|
|
23
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
24
|
+
tools,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) =>
|
|
28
|
+
handleToolCall(request, client)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const transport = new StdioServerTransport();
|
|
33
|
+
await server.connect(transport);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for {{name}} API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "node --test test.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
13
|
+
"zod": "^3.22.0",
|
|
14
|
+
"node-fetch": "^3.3.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.3.0",
|
|
18
|
+
"@types/node": "^20.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log('Tests not yet implemented');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { APIClient } from './client.js';
|
|
3
|
+
|
|
4
|
+
export const tools = [
|
|
5
|
+
{{#each endpoints}}
|
|
6
|
+
{
|
|
7
|
+
name: '{{id}}',
|
|
8
|
+
description: '{{description}}',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
{{#each parameters}}
|
|
13
|
+
{{name}}: {
|
|
14
|
+
type: '{{schema.type}}',
|
|
15
|
+
{{#if description}}description: '{{description}}',{{/if}}
|
|
16
|
+
},
|
|
17
|
+
{{/each}}
|
|
18
|
+
},
|
|
19
|
+
required: [{{#each parameters}}{{#if required}}'{{name}}',{{/if}}{{/each}}],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{{/each}}
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export async function handleToolCall(request: CallToolRequest, client: APIClient) {
|
|
26
|
+
const { name, arguments: args } = request.params;
|
|
27
|
+
|
|
28
|
+
{{#each endpoints}}
|
|
29
|
+
if (name === '{{id}}') {
|
|
30
|
+
const result = await client.request('{{method}}', '{{path}}', args);
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
{{/each}}
|
|
36
|
+
|
|
37
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./build",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}; // Types generated from API schema
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}; // Validation schemas
|