@stackkedjohn/mcp-factory-cli 0.1.2 → 0.2.1
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 +5 -0
- package/dist/commands/create.js +4 -0
- package/dist/parsers/apib-parser.d.ts +2 -0
- package/dist/parsers/apib-parser.js +354 -0
- package/dist/parsers/apib-parser.test.d.ts +1 -0
- package/dist/parsers/apib-parser.test.js +59 -0
- package/dist/parsers/detector.d.ts +1 -1
- package/dist/parsers/detector.js +8 -0
- package/dist/parsers/detector.test.d.ts +1 -0
- package/dist/parsers/detector.test.js +20 -0
- package/docs/plans/2026-02-02-api-blueprint-support.md +781 -0
- package/package.json +2 -2
- package/src/commands/create.ts +3 -0
- package/src/parsers/apib-parser.test.ts +64 -0
- package/src/parsers/apib-parser.ts +421 -0
- package/src/parsers/detector.test.ts +21 -0
- package/src/parsers/detector.ts +11 -1
- package/test-fixtures/sample.apib +31 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
# API Blueprint (.apib) Support Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add support for parsing API Blueprint (.apib) format files to generate MCP servers, enabling users to use .apib documentation alongside OpenAPI/Swagger specs.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Extend the existing parser pipeline with a new API Blueprint parser that transforms .apib AST into our unified APISchema format. Use the `protagonist` library for .apib parsing, following the same pattern as existing OpenAPI/Postman parsers.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, protagonist (API Blueprint parser), existing MCP Factory architecture
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Add protagonist dependency and type definitions
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `package.json`
|
|
17
|
+
- Modify: `package-lock.json` (auto-updated)
|
|
18
|
+
|
|
19
|
+
**Step 1: Add protagonist npm package**
|
|
20
|
+
|
|
21
|
+
Run:
|
|
22
|
+
```bash
|
|
23
|
+
npm install protagonist
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Expected: Package added to dependencies, package-lock.json updated
|
|
27
|
+
|
|
28
|
+
**Step 2: Add TypeScript types for protagonist**
|
|
29
|
+
|
|
30
|
+
Run:
|
|
31
|
+
```bash
|
|
32
|
+
npm install --save-dev @types/protagonist
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Expected: Types added to devDependencies
|
|
36
|
+
|
|
37
|
+
Note: If @types/protagonist doesn't exist, we'll declare types inline in the parser file.
|
|
38
|
+
|
|
39
|
+
**Step 3: Commit dependency changes**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git add package.json package-lock.json
|
|
43
|
+
git commit -m "chore: add protagonist for API Blueprint parsing"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Task 2: Update format detector to recognize .apib files
|
|
49
|
+
|
|
50
|
+
**Files:**
|
|
51
|
+
- Modify: `src/parsers/detector.ts`
|
|
52
|
+
|
|
53
|
+
**Step 1: Write failing test for .apib detection**
|
|
54
|
+
|
|
55
|
+
Create: `src/parsers/detector.test.ts`
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { describe, it } from 'node:test';
|
|
59
|
+
import assert from 'node:assert';
|
|
60
|
+
import { detectFormat } from './detector.js';
|
|
61
|
+
import * as fs from 'fs/promises';
|
|
62
|
+
import * as path from 'path';
|
|
63
|
+
|
|
64
|
+
describe('Format Detector', () => {
|
|
65
|
+
it('should detect API Blueprint format from .apib file', async () => {
|
|
66
|
+
// Create temp .apib file
|
|
67
|
+
const tempFile = path.join(process.cwd(), 'test.apib');
|
|
68
|
+
await fs.writeFile(tempFile, 'FORMAT: 1A\n# My API\n## GET /users', 'utf-8');
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const result = await detectFormat(tempFile);
|
|
72
|
+
assert.strictEqual(result.format, 'apib');
|
|
73
|
+
assert.ok(result.content.includes('FORMAT: 1A'));
|
|
74
|
+
} finally {
|
|
75
|
+
await fs.unlink(tempFile);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Step 2: Run test to verify it fails**
|
|
82
|
+
|
|
83
|
+
Run:
|
|
84
|
+
```bash
|
|
85
|
+
npm run build && node --test dist/parsers/detector.test.js
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Expected: FAIL - format should be 'apib' but got 'unknown'
|
|
89
|
+
|
|
90
|
+
**Step 3: Update detector.ts to add apib format type**
|
|
91
|
+
|
|
92
|
+
Modify: `src/parsers/detector.ts`
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
export type InputFormat = 'openapi' | 'swagger' | 'postman' | 'apib' | 'unknown';
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Step 4: Update detector.ts to check for .apib extension**
|
|
99
|
+
|
|
100
|
+
Modify: `src/parsers/detector.ts` in the `detectFormat` function, add before JSON/YAML parsing:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export async function detectFormat(input: string): Promise<DetectionResult> {
|
|
104
|
+
let content: string;
|
|
105
|
+
|
|
106
|
+
// Check if input is a file path
|
|
107
|
+
try {
|
|
108
|
+
content = await fs.readFile(input, 'utf-8');
|
|
109
|
+
} catch {
|
|
110
|
+
throw new ParseError(`Could not read file: ${input}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check file extension for API Blueprint
|
|
114
|
+
if (input.endsWith('.apib') || input.endsWith('.apiblueprint')) {
|
|
115
|
+
return { format: 'apib', content };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Alternatively, check content for API Blueprint markers
|
|
119
|
+
if (content.trim().startsWith('FORMAT: 1A')) {
|
|
120
|
+
return { format: 'apib', content };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Rest of existing JSON/YAML parsing...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Step 5: Run test to verify it passes**
|
|
127
|
+
|
|
128
|
+
Run:
|
|
129
|
+
```bash
|
|
130
|
+
npm run build && node --test dist/parsers/detector.test.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Expected: PASS - .apib files detected correctly
|
|
134
|
+
|
|
135
|
+
**Step 6: Commit detector changes**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git add src/parsers/detector.ts src/parsers/detector.test.ts
|
|
139
|
+
git commit -m "feat: detect API Blueprint (.apib) format"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Task 3: Create API Blueprint parser
|
|
145
|
+
|
|
146
|
+
**Files:**
|
|
147
|
+
- Create: `src/parsers/apib-parser.ts`
|
|
148
|
+
- Create: `src/parsers/apib-parser.test.ts`
|
|
149
|
+
|
|
150
|
+
**Step 1: Write failing test for API Blueprint parsing**
|
|
151
|
+
|
|
152
|
+
Create: `src/parsers/apib-parser.test.ts`
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { describe, it } from 'node:test';
|
|
156
|
+
import assert from 'node:assert';
|
|
157
|
+
import { parseAPIBlueprint } from './apib-parser.js';
|
|
158
|
+
|
|
159
|
+
describe('API Blueprint Parser', () => {
|
|
160
|
+
it('should parse a basic API Blueprint document', async () => {
|
|
161
|
+
const apibContent = `
|
|
162
|
+
FORMAT: 1A
|
|
163
|
+
HOST: https://api.example.com
|
|
164
|
+
|
|
165
|
+
# My API
|
|
166
|
+
This is my API description.
|
|
167
|
+
|
|
168
|
+
## GET /users
|
|
169
|
+
Get a list of users
|
|
170
|
+
|
|
171
|
+
+ Response 200 (application/json)
|
|
172
|
+
+ Body
|
|
173
|
+
|
|
174
|
+
[
|
|
175
|
+
{
|
|
176
|
+
"id": 1,
|
|
177
|
+
"name": "John Doe"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
## POST /users
|
|
182
|
+
Create a new user
|
|
183
|
+
|
|
184
|
+
+ Request (application/json)
|
|
185
|
+
+ Body
|
|
186
|
+
|
|
187
|
+
{
|
|
188
|
+
"name": "John Doe"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
+ Response 201 (application/json)
|
|
192
|
+
+ Body
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
"id": 1,
|
|
196
|
+
"name": "John Doe"
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
const schema = await parseAPIBlueprint(apibContent);
|
|
201
|
+
|
|
202
|
+
assert.strictEqual(schema.name, 'My API');
|
|
203
|
+
assert.strictEqual(schema.baseUrl, 'https://api.example.com');
|
|
204
|
+
assert.strictEqual(schema.endpoints.length, 2);
|
|
205
|
+
|
|
206
|
+
// Check GET endpoint
|
|
207
|
+
const getEndpoint = schema.endpoints.find(e => e.method === 'GET');
|
|
208
|
+
assert.ok(getEndpoint);
|
|
209
|
+
assert.strictEqual(getEndpoint.path, '/users');
|
|
210
|
+
assert.strictEqual(getEndpoint.description, 'Get a list of users');
|
|
211
|
+
|
|
212
|
+
// Check POST endpoint
|
|
213
|
+
const postEndpoint = schema.endpoints.find(e => e.method === 'POST');
|
|
214
|
+
assert.ok(postEndpoint);
|
|
215
|
+
assert.strictEqual(postEndpoint.path, '/users');
|
|
216
|
+
assert.strictEqual(postEndpoint.description, 'Create a new user');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Step 2: Run test to verify it fails**
|
|
222
|
+
|
|
223
|
+
Run:
|
|
224
|
+
```bash
|
|
225
|
+
npm run build && node --test dist/parsers/apib-parser.test.js
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Expected: FAIL - parseAPIBlueprint is not defined
|
|
229
|
+
|
|
230
|
+
**Step 3: Create minimal API Blueprint parser implementation**
|
|
231
|
+
|
|
232
|
+
Create: `src/parsers/apib-parser.ts`
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import protagonist from 'protagonist';
|
|
236
|
+
import { APISchema, Endpoint, AuthConfig, Parameter, ResponseSchema, ErrorSchema, SchemaType, RequestBody } from '../schema/api-schema.js';
|
|
237
|
+
|
|
238
|
+
export async function parseAPIBlueprint(content: string): Promise<APISchema> {
|
|
239
|
+
// Parse the API Blueprint content
|
|
240
|
+
const result = await protagonist.parse(content);
|
|
241
|
+
|
|
242
|
+
if (result.error) {
|
|
243
|
+
throw new Error(`API Blueprint parse error: ${result.error.message}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const ast = result.ast;
|
|
247
|
+
|
|
248
|
+
// Extract API name and description
|
|
249
|
+
const name = ast.name || 'Untitled API';
|
|
250
|
+
const baseUrl = extractBaseUrl(ast);
|
|
251
|
+
|
|
252
|
+
// Extract endpoints from resource groups
|
|
253
|
+
const endpoints: Endpoint[] = [];
|
|
254
|
+
|
|
255
|
+
for (const resourceGroup of ast.resourceGroups || []) {
|
|
256
|
+
for (const resource of resourceGroup.resources || []) {
|
|
257
|
+
for (const action of resource.actions || []) {
|
|
258
|
+
const endpoint = parseAction(resource, action);
|
|
259
|
+
endpoints.push(endpoint);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Detect authentication (basic heuristic)
|
|
265
|
+
const auth: AuthConfig = detectAuth(ast);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
name,
|
|
269
|
+
baseUrl,
|
|
270
|
+
auth,
|
|
271
|
+
endpoints,
|
|
272
|
+
commonHeaders: {},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function extractBaseUrl(ast: any): string {
|
|
277
|
+
// Check metadata for HOST
|
|
278
|
+
if (ast.metadata) {
|
|
279
|
+
for (const meta of ast.metadata) {
|
|
280
|
+
if (meta.name === 'HOST') {
|
|
281
|
+
return meta.value;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function parseAction(resource: any, action: any): Endpoint {
|
|
289
|
+
const method = action.method.toUpperCase() as Endpoint['method'];
|
|
290
|
+
const path = resource.uriTemplate;
|
|
291
|
+
const description = action.description || action.name || '';
|
|
292
|
+
|
|
293
|
+
// Extract parameters from URI template and action parameters
|
|
294
|
+
const parameters: Parameter[] = parseParameters(resource, action);
|
|
295
|
+
|
|
296
|
+
// Extract request body if present
|
|
297
|
+
const requestBody = parseRequestBody(action);
|
|
298
|
+
|
|
299
|
+
// Extract response schema
|
|
300
|
+
const response = parseResponse(action);
|
|
301
|
+
|
|
302
|
+
// Extract error responses
|
|
303
|
+
const errors = parseErrors(action);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
id: `${method.toLowerCase()}${path.replace(/[^a-zA-Z0-9]/g, '_')}`,
|
|
307
|
+
method,
|
|
308
|
+
path,
|
|
309
|
+
description,
|
|
310
|
+
parameters,
|
|
311
|
+
requestBody,
|
|
312
|
+
response,
|
|
313
|
+
errors,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseParameters(resource: any, action: any): Parameter[] {
|
|
318
|
+
const parameters: Parameter[] = [];
|
|
319
|
+
|
|
320
|
+
// Parse URI parameters
|
|
321
|
+
if (resource.parameters) {
|
|
322
|
+
for (const param of resource.parameters) {
|
|
323
|
+
parameters.push({
|
|
324
|
+
name: param.name,
|
|
325
|
+
in: 'path',
|
|
326
|
+
description: param.description || '',
|
|
327
|
+
required: param.required || false,
|
|
328
|
+
schema: {
|
|
329
|
+
type: mapTypeToSchemaType(param.type),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Parse action parameters (usually query/header)
|
|
336
|
+
if (action.parameters) {
|
|
337
|
+
for (const param of action.parameters) {
|
|
338
|
+
parameters.push({
|
|
339
|
+
name: param.name,
|
|
340
|
+
in: 'query', // Default to query, could be enhanced
|
|
341
|
+
description: param.description || '',
|
|
342
|
+
required: param.required || false,
|
|
343
|
+
schema: {
|
|
344
|
+
type: mapTypeToSchemaType(param.type),
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return parameters;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function parseRequestBody(action: any): RequestBody | undefined {
|
|
354
|
+
// Find request with a body
|
|
355
|
+
const request = action.examples?.[0]?.requests?.[0];
|
|
356
|
+
if (!request || !request.body) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
description: request.description || '',
|
|
362
|
+
required: true,
|
|
363
|
+
contentType: request.headers?.['Content-Type']?.[0]?.value || 'application/json',
|
|
364
|
+
schema: {
|
|
365
|
+
type: 'object', // Simplified - could parse JSON schema from body
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function parseResponse(action: any): ResponseSchema {
|
|
371
|
+
// Get first successful response (200-299)
|
|
372
|
+
const response = action.examples?.[0]?.responses?.[0];
|
|
373
|
+
|
|
374
|
+
if (!response) {
|
|
375
|
+
return {
|
|
376
|
+
statusCode: 200,
|
|
377
|
+
description: '',
|
|
378
|
+
contentType: 'application/json',
|
|
379
|
+
schema: { type: 'object' },
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
statusCode: parseInt(response.name) || 200,
|
|
385
|
+
description: response.description || '',
|
|
386
|
+
contentType: response.headers?.['Content-Type']?.[0]?.value || 'application/json',
|
|
387
|
+
schema: {
|
|
388
|
+
type: 'object', // Simplified - could parse JSON schema from body
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function parseErrors(action: any): ErrorSchema[] {
|
|
394
|
+
const errors: ErrorSchema[] = [];
|
|
395
|
+
|
|
396
|
+
const responses = action.examples?.[0]?.responses || [];
|
|
397
|
+
for (const response of responses) {
|
|
398
|
+
const statusCode = parseInt(response.name);
|
|
399
|
+
if (statusCode >= 400) {
|
|
400
|
+
errors.push({
|
|
401
|
+
statusCode,
|
|
402
|
+
description: response.description || `Error ${statusCode}`,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return errors;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function detectAuth(ast: any): AuthConfig {
|
|
411
|
+
// Simple heuristic: check if any examples mention authentication
|
|
412
|
+
const astString = JSON.stringify(ast).toLowerCase();
|
|
413
|
+
|
|
414
|
+
if (astString.includes('bearer') || astString.includes('jwt')) {
|
|
415
|
+
return {
|
|
416
|
+
type: 'bearer',
|
|
417
|
+
location: 'header',
|
|
418
|
+
name: 'Authorization',
|
|
419
|
+
description: 'Bearer token authentication',
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (astString.includes('api-key') || astString.includes('apikey')) {
|
|
424
|
+
return {
|
|
425
|
+
type: 'api-key',
|
|
426
|
+
location: 'header',
|
|
427
|
+
name: 'X-API-Key',
|
|
428
|
+
description: 'API key authentication',
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
type: 'none',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mapTypeToSchemaType(type?: string): SchemaType['type'] {
|
|
438
|
+
if (!type) return 'string';
|
|
439
|
+
|
|
440
|
+
const lowerType = type.toLowerCase();
|
|
441
|
+
if (lowerType.includes('number') || lowerType.includes('integer')) return 'number';
|
|
442
|
+
if (lowerType.includes('bool')) return 'boolean';
|
|
443
|
+
if (lowerType.includes('array')) return 'array';
|
|
444
|
+
if (lowerType.includes('object')) return 'object';
|
|
445
|
+
|
|
446
|
+
return 'string';
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Step 4: Run test to verify it passes**
|
|
451
|
+
|
|
452
|
+
Run:
|
|
453
|
+
```bash
|
|
454
|
+
npm run build && node --test dist/parsers/apib-parser.test.js
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Expected: PASS - API Blueprint parsed correctly into APISchema
|
|
458
|
+
|
|
459
|
+
**Step 5: Commit API Blueprint parser**
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
git add src/parsers/apib-parser.ts src/parsers/apib-parser.test.ts
|
|
463
|
+
git commit -m "feat: implement API Blueprint parser"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Task 4: Wire up API Blueprint parser in create command
|
|
469
|
+
|
|
470
|
+
**Files:**
|
|
471
|
+
- Modify: `src/commands/create.ts`
|
|
472
|
+
|
|
473
|
+
**Step 1: Write integration test**
|
|
474
|
+
|
|
475
|
+
Create: `test-fixtures/sample.apib`
|
|
476
|
+
|
|
477
|
+
```apib
|
|
478
|
+
FORMAT: 1A
|
|
479
|
+
HOST: https://api.example.com
|
|
480
|
+
|
|
481
|
+
# Sample API
|
|
482
|
+
A simple API for testing
|
|
483
|
+
|
|
484
|
+
## Users Collection [/users]
|
|
485
|
+
|
|
486
|
+
### List Users [GET]
|
|
487
|
+
Get a list of all users
|
|
488
|
+
|
|
489
|
+
+ Response 200 (application/json)
|
|
490
|
+
+ Body
|
|
491
|
+
|
|
492
|
+
[
|
|
493
|
+
{"id": 1, "name": "Alice"},
|
|
494
|
+
{"id": 2, "name": "Bob"}
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
### Create User [POST]
|
|
498
|
+
Create a new user
|
|
499
|
+
|
|
500
|
+
+ Request (application/json)
|
|
501
|
+
+ Body
|
|
502
|
+
|
|
503
|
+
{"name": "Charlie"}
|
|
504
|
+
|
|
505
|
+
+ Response 201 (application/json)
|
|
506
|
+
+ Body
|
|
507
|
+
|
|
508
|
+
{"id": 3, "name": "Charlie"}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Step 2: Test manually to verify it fails**
|
|
512
|
+
|
|
513
|
+
Run:
|
|
514
|
+
```bash
|
|
515
|
+
npm run build
|
|
516
|
+
node dist/cli.js create test-fixtures/sample.apib
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Expected: Error - "Could not detect format" or similar
|
|
520
|
+
|
|
521
|
+
**Step 3: Import API Blueprint parser in create command**
|
|
522
|
+
|
|
523
|
+
Modify: `src/commands/create.ts`
|
|
524
|
+
|
|
525
|
+
Add import at top:
|
|
526
|
+
```typescript
|
|
527
|
+
import { parseAPIBlueprint } from '../parsers/apib-parser.js';
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Step 4: Add API Blueprint case to parser switch**
|
|
531
|
+
|
|
532
|
+
Modify: `src/commands/create.ts` in the `createCommand` function:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// Parse to APISchema
|
|
536
|
+
let schema;
|
|
537
|
+
if (detection.format === 'openapi' || detection.format === 'swagger') {
|
|
538
|
+
schema = parseOpenAPI(detection.content);
|
|
539
|
+
} else if (detection.format === 'postman') {
|
|
540
|
+
schema = parsePostman(detection.content);
|
|
541
|
+
} else if (detection.format === 'apib') {
|
|
542
|
+
schema = await parseAPIBlueprint(detection.content);
|
|
543
|
+
} else if (options.aiParse) {
|
|
544
|
+
logger.info('Using AI parser for unstructured docs...');
|
|
545
|
+
schema = await parseWithAI(JSON.stringify(detection.content));
|
|
546
|
+
} else {
|
|
547
|
+
throw new ParseError('Could not detect format. Use --ai-parse for unstructured docs.');
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Step 5: Test integration manually**
|
|
552
|
+
|
|
553
|
+
Run:
|
|
554
|
+
```bash
|
|
555
|
+
npm run build
|
|
556
|
+
node dist/cli.js create test-fixtures/sample.apib
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Expected: Success - MCP server generated from .apib file
|
|
560
|
+
|
|
561
|
+
**Step 6: Verify generated server builds and runs**
|
|
562
|
+
|
|
563
|
+
Run:
|
|
564
|
+
```bash
|
|
565
|
+
cd "Sample API-mcp"
|
|
566
|
+
npm install
|
|
567
|
+
npm run build
|
|
568
|
+
npm test
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Expected: All commands succeed
|
|
572
|
+
|
|
573
|
+
**Step 7: Commit integration**
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
git add src/commands/create.ts test-fixtures/sample.apib
|
|
577
|
+
git commit -m "feat: integrate API Blueprint parser into create command"
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Task 5: Update documentation
|
|
583
|
+
|
|
584
|
+
**Files:**
|
|
585
|
+
- Modify: `README.md`
|
|
586
|
+
|
|
587
|
+
**Step 1: Update supported formats section**
|
|
588
|
+
|
|
589
|
+
Modify: `README.md`
|
|
590
|
+
|
|
591
|
+
Find the "Supported Formats" section and update:
|
|
592
|
+
|
|
593
|
+
```markdown
|
|
594
|
+
**Supported Formats:**
|
|
595
|
+
- OpenAPI 3.x (JSON/YAML)
|
|
596
|
+
- Swagger 2.0 (JSON/YAML)
|
|
597
|
+
- API Blueprint (.apib)
|
|
598
|
+
- Postman Collections (coming soon)
|
|
599
|
+
- Unstructured docs with `--ai-parse` (coming soon)
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Step 2: Add API Blueprint example**
|
|
603
|
+
|
|
604
|
+
Add to examples section:
|
|
605
|
+
|
|
606
|
+
```markdown
|
|
607
|
+
### Example: API Blueprint
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
# Generate from API Blueprint file
|
|
611
|
+
mcp-factory create ./api-documentation.apib
|
|
612
|
+
|
|
613
|
+
cd "My API-mcp"
|
|
614
|
+
npm install && npm run build
|
|
615
|
+
mcp-factory install "My API"
|
|
616
|
+
```
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Step 3: Update quick start if needed**
|
|
620
|
+
|
|
621
|
+
Ensure quick start mentions .apib support:
|
|
622
|
+
|
|
623
|
+
```markdown
|
|
624
|
+
## Quick Start
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
# Generate MCP server from API documentation
|
|
628
|
+
# Supports: OpenAPI, Swagger, API Blueprint (.apib)
|
|
629
|
+
mcp-factory create ./api-docs.yaml
|
|
630
|
+
|
|
631
|
+
# Build the generated server
|
|
632
|
+
cd "My API-mcp"
|
|
633
|
+
npm install && npm run build
|
|
634
|
+
|
|
635
|
+
# Install to Claude Desktop
|
|
636
|
+
mcp-factory install "My API"
|
|
637
|
+
```
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Step 4: Commit documentation updates**
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
git add README.md
|
|
644
|
+
git commit -m "docs: add API Blueprint format support to README"
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Task 6: Version bump and publish preparation
|
|
650
|
+
|
|
651
|
+
**Files:**
|
|
652
|
+
- Modify: `package.json`
|
|
653
|
+
|
|
654
|
+
**Step 1: Run all tests to verify everything works**
|
|
655
|
+
|
|
656
|
+
Run:
|
|
657
|
+
```bash
|
|
658
|
+
npm run build
|
|
659
|
+
npm test
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Expected: All tests pass
|
|
663
|
+
|
|
664
|
+
**Step 2: Test with real .apib file if available**
|
|
665
|
+
|
|
666
|
+
If user has a real .apib file:
|
|
667
|
+
```bash
|
|
668
|
+
node dist/cli.js create /path/to/real-api.apib
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Verify it generates correctly.
|
|
672
|
+
|
|
673
|
+
**Step 3: Bump version**
|
|
674
|
+
|
|
675
|
+
Run:
|
|
676
|
+
```bash
|
|
677
|
+
npm version minor -m "feat: add API Blueprint (.apib) format support"
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Expected: Version bumped from 0.1.2 to 0.2.0 (minor version for new feature)
|
|
681
|
+
|
|
682
|
+
**Step 4: Push changes and tag**
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
git push
|
|
686
|
+
git push --tags
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**Step 5: Publish to npm**
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
npm publish --access public
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Expected: Package published successfully
|
|
696
|
+
|
|
697
|
+
**Step 6: Verify installation**
|
|
698
|
+
|
|
699
|
+
```bash
|
|
700
|
+
npm install -g @stackkedjohn/mcp-factory-cli
|
|
701
|
+
mcp-factory --version
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
Expected: Shows new version 0.2.0
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## Testing Checklist
|
|
709
|
+
|
|
710
|
+
After implementation, verify:
|
|
711
|
+
|
|
712
|
+
- [ ] .apib files are detected correctly by format detector
|
|
713
|
+
- [ ] API Blueprint parser extracts API name, base URL, and endpoints
|
|
714
|
+
- [ ] Generated MCP server from .apib builds without errors
|
|
715
|
+
- [ ] Generated server includes all endpoints from .apib
|
|
716
|
+
- [ ] Authentication is detected (at least basic heuristics)
|
|
717
|
+
- [ ] Parameters (path/query) are extracted correctly
|
|
718
|
+
- [ ] Request/response bodies are handled
|
|
719
|
+
- [ ] Error responses (4xx/5xx) are captured
|
|
720
|
+
- [ ] Generated server can be installed to Claude Desktop
|
|
721
|
+
- [ ] README accurately documents .apib support
|
|
722
|
+
- [ ] npm package published with new version
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Notes for Implementation
|
|
727
|
+
|
|
728
|
+
**Key Libraries:**
|
|
729
|
+
- `protagonist` - Official API Blueprint parser (Node.js wrapper around Drafter C++ library)
|
|
730
|
+
- AST structure follows API Blueprint spec
|
|
731
|
+
|
|
732
|
+
**Known Limitations:**
|
|
733
|
+
- Schema inference from example bodies is simplified (uses generic 'object' type)
|
|
734
|
+
- Authentication detection uses heuristics (not explicit .apib auth declarations)
|
|
735
|
+
- Could enhance with JSON Schema parsing from MSON attributes
|
|
736
|
+
|
|
737
|
+
**Future Enhancements:**
|
|
738
|
+
- Parse MSON (Markdown Syntax for Object Notation) for detailed schemas
|
|
739
|
+
- Better authentication parsing from Headers section
|
|
740
|
+
- Support for Data Structures section
|
|
741
|
+
- Validation against API Blueprint spec
|
|
742
|
+
|
|
743
|
+
**Protagonist AST Structure:**
|
|
744
|
+
```typescript
|
|
745
|
+
{
|
|
746
|
+
ast: {
|
|
747
|
+
name: string;
|
|
748
|
+
description: string;
|
|
749
|
+
metadata: Array<{name: string, value: string}>;
|
|
750
|
+
resourceGroups: Array<{
|
|
751
|
+
name: string;
|
|
752
|
+
resources: Array<{
|
|
753
|
+
name: string;
|
|
754
|
+
uriTemplate: string;
|
|
755
|
+
parameters: Array<Parameter>;
|
|
756
|
+
actions: Array<{
|
|
757
|
+
name: string;
|
|
758
|
+
method: string;
|
|
759
|
+
description: string;
|
|
760
|
+
examples: Array<{
|
|
761
|
+
requests: Array<{body: string, headers: any}>;
|
|
762
|
+
responses: Array<{name: string, body: string, headers: any}>;
|
|
763
|
+
}>;
|
|
764
|
+
}>;
|
|
765
|
+
}>;
|
|
766
|
+
}>;
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## Execution Complete
|
|
774
|
+
|
|
775
|
+
After all tasks completed:
|
|
776
|
+
|
|
777
|
+
1. Merge feature branch to main
|
|
778
|
+
2. Verify npm package published
|
|
779
|
+
3. Test global installation: `npm i -g @stackkedjohn/mcp-factory-cli`
|
|
780
|
+
4. Test with real .apib file from user
|
|
781
|
+
5. Update project documentation with new feature announcement
|