@codebakers/cli 1.1.5 → 1.1.7
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/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +218 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +772 -0
- package/dist/commands/install-hook.d.ts +12 -0
- package/dist/commands/install-hook.js +193 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +81 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +54 -0
- package/dist/commands/mcp-config.d.ts +6 -0
- package/dist/commands/mcp-config.js +209 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +26 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +92 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +49 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +50 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +71 -1075
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +544 -0
- package/package.json +17 -38
- package/src/commands/doctor.ts +231 -0
- package/src/commands/init.ts +827 -0
- package/src/commands/install-hook.ts +207 -0
- package/src/commands/install.ts +94 -0
- package/src/commands/login.ts +56 -0
- package/src/commands/mcp-config.ts +235 -0
- package/src/commands/serve.ts +23 -0
- package/src/commands/setup.ts +104 -0
- package/src/commands/status.ts +48 -0
- package/src/commands/uninstall.ts +49 -0
- package/src/config.ts +34 -0
- package/src/index.ts +87 -0
- package/src/mcp/server.ts +617 -0
- package/tsconfig.json +16 -0
- package/README.md +0 -89
- package/dist/chunk-7CKLRE2H.js +0 -36
- package/dist/config-R2H6JKGW.js +0 -16
|
@@ -0,0 +1,617 @@
|
|
|
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 {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
ErrorCode,
|
|
9
|
+
McpError,
|
|
10
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import { getApiKey, getApiUrl } from '../config.js';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
// Pattern cache to avoid repeated API calls
|
|
16
|
+
const patternCache = new Map<string, { content: string; timestamp: number }>();
|
|
17
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
18
|
+
|
|
19
|
+
// Project context type
|
|
20
|
+
interface ProjectContext {
|
|
21
|
+
projectName: string;
|
|
22
|
+
dependencies: string[];
|
|
23
|
+
devDependencies: string[];
|
|
24
|
+
folderStructure: string[];
|
|
25
|
+
hasAuth: boolean;
|
|
26
|
+
hasDatabase: boolean;
|
|
27
|
+
hasPayments: boolean;
|
|
28
|
+
uiLibrary: string | null;
|
|
29
|
+
schemaPath: string | null;
|
|
30
|
+
componentsPath: string | null;
|
|
31
|
+
existingComponents: string[];
|
|
32
|
+
existingServices: string[];
|
|
33
|
+
existingApiRoutes: string[];
|
|
34
|
+
codebakersState: Record<string, unknown> | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// API response type for optimize-prompt
|
|
38
|
+
interface OptimizeResponse {
|
|
39
|
+
optimizedPrompt: string;
|
|
40
|
+
featureName: string;
|
|
41
|
+
patterns: string[];
|
|
42
|
+
method: string;
|
|
43
|
+
hasContext: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class CodeBakersServer {
|
|
47
|
+
private server: Server;
|
|
48
|
+
private apiKey: string | null;
|
|
49
|
+
private apiUrl: string;
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
this.apiKey = getApiKey();
|
|
53
|
+
this.apiUrl = getApiUrl();
|
|
54
|
+
|
|
55
|
+
this.server = new Server(
|
|
56
|
+
{
|
|
57
|
+
name: 'codebakers',
|
|
58
|
+
version: '1.0.0',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
capabilities: {
|
|
62
|
+
tools: {},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
this.setupHandlers();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private gatherProjectContext(): ProjectContext {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const context: ProjectContext = {
|
|
73
|
+
projectName: 'Unknown',
|
|
74
|
+
dependencies: [],
|
|
75
|
+
devDependencies: [],
|
|
76
|
+
folderStructure: [],
|
|
77
|
+
hasAuth: false,
|
|
78
|
+
hasDatabase: false,
|
|
79
|
+
hasPayments: false,
|
|
80
|
+
uiLibrary: null,
|
|
81
|
+
schemaPath: null,
|
|
82
|
+
componentsPath: null,
|
|
83
|
+
existingComponents: [],
|
|
84
|
+
existingServices: [],
|
|
85
|
+
existingApiRoutes: [],
|
|
86
|
+
codebakersState: null,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Read package.json
|
|
90
|
+
try {
|
|
91
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
92
|
+
if (fs.existsSync(pkgPath)) {
|
|
93
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
94
|
+
context.projectName = pkg.name || 'Unknown';
|
|
95
|
+
context.dependencies = Object.keys(pkg.dependencies || {});
|
|
96
|
+
context.devDependencies = Object.keys(pkg.devDependencies || {});
|
|
97
|
+
|
|
98
|
+
// Detect libraries
|
|
99
|
+
const allDeps = [...context.dependencies, ...context.devDependencies];
|
|
100
|
+
context.hasAuth = allDeps.some(d => d.includes('supabase') || d.includes('next-auth') || d.includes('clerk'));
|
|
101
|
+
context.hasDatabase = allDeps.some(d => d.includes('drizzle') || d.includes('prisma') || d.includes('postgres'));
|
|
102
|
+
context.hasPayments = allDeps.some(d => d.includes('stripe') || d.includes('paypal'));
|
|
103
|
+
|
|
104
|
+
if (allDeps.includes('@radix-ui/react-dialog') || allDeps.some(d => d.includes('shadcn'))) {
|
|
105
|
+
context.uiLibrary = 'shadcn/ui';
|
|
106
|
+
} else if (allDeps.includes('@chakra-ui/react')) {
|
|
107
|
+
context.uiLibrary = 'Chakra UI';
|
|
108
|
+
} else if (allDeps.includes('@mui/material')) {
|
|
109
|
+
context.uiLibrary = 'Material UI';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Ignore package.json errors
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Read .codebakers.json state
|
|
117
|
+
try {
|
|
118
|
+
const statePath = path.join(cwd, '.codebakers.json');
|
|
119
|
+
if (fs.existsSync(statePath)) {
|
|
120
|
+
context.codebakersState = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore state file errors
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Scan folder structure
|
|
127
|
+
const scanDir = (dir: string, prefix = ''): string[] => {
|
|
128
|
+
const results: string[] = [];
|
|
129
|
+
try {
|
|
130
|
+
if (!fs.existsSync(dir)) return results;
|
|
131
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
134
|
+
const fullPath = path.join(prefix, entry.name);
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
results.push(fullPath + '/');
|
|
137
|
+
// Only go 2 levels deep
|
|
138
|
+
if (prefix.split('/').length < 2) {
|
|
139
|
+
results.push(...scanDir(path.join(dir, entry.name), fullPath));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Ignore scan errors
|
|
145
|
+
}
|
|
146
|
+
return results;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
context.folderStructure = scanDir(cwd);
|
|
150
|
+
|
|
151
|
+
// Find schema path
|
|
152
|
+
const schemaPaths = [
|
|
153
|
+
'src/db/schema.ts',
|
|
154
|
+
'src/lib/db/schema.ts',
|
|
155
|
+
'db/schema.ts',
|
|
156
|
+
'prisma/schema.prisma',
|
|
157
|
+
'drizzle/schema.ts',
|
|
158
|
+
];
|
|
159
|
+
for (const schemaPath of schemaPaths) {
|
|
160
|
+
if (fs.existsSync(path.join(cwd, schemaPath))) {
|
|
161
|
+
context.schemaPath = schemaPath;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Find components path and list components
|
|
167
|
+
const componentPaths = ['src/components', 'components', 'app/components'];
|
|
168
|
+
for (const compPath of componentPaths) {
|
|
169
|
+
const fullPath = path.join(cwd, compPath);
|
|
170
|
+
if (fs.existsSync(fullPath)) {
|
|
171
|
+
context.componentsPath = compPath;
|
|
172
|
+
try {
|
|
173
|
+
const scanComponents = (dir: string, prefix = ''): string[] => {
|
|
174
|
+
const comps: string[] = [];
|
|
175
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
if (entry.name.startsWith('.')) continue;
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
comps.push(...scanComponents(path.join(dir, entry.name), entry.name + '/'));
|
|
180
|
+
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.jsx')) {
|
|
181
|
+
comps.push(prefix + entry.name.replace(/\.(tsx|jsx)$/, ''));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return comps;
|
|
185
|
+
};
|
|
186
|
+
context.existingComponents = scanComponents(fullPath).slice(0, 50); // Limit to 50
|
|
187
|
+
} catch {
|
|
188
|
+
// Ignore component scan errors
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Find services
|
|
195
|
+
const servicePaths = ['src/services', 'src/lib/services', 'services'];
|
|
196
|
+
for (const servPath of servicePaths) {
|
|
197
|
+
const fullPath = path.join(cwd, servPath);
|
|
198
|
+
if (fs.existsSync(fullPath)) {
|
|
199
|
+
try {
|
|
200
|
+
const entries = fs.readdirSync(fullPath);
|
|
201
|
+
context.existingServices = entries
|
|
202
|
+
.filter(e => e.endsWith('.ts') || e.endsWith('.js'))
|
|
203
|
+
.map(e => e.replace(/\.(ts|js)$/, ''))
|
|
204
|
+
.slice(0, 20);
|
|
205
|
+
} catch {
|
|
206
|
+
// Ignore service scan errors
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Find API routes
|
|
213
|
+
const apiPaths = ['src/app/api', 'app/api', 'pages/api'];
|
|
214
|
+
for (const apiPath of apiPaths) {
|
|
215
|
+
const fullPath = path.join(cwd, apiPath);
|
|
216
|
+
if (fs.existsSync(fullPath)) {
|
|
217
|
+
try {
|
|
218
|
+
const scanApiRoutes = (dir: string, prefix = ''): string[] => {
|
|
219
|
+
const routes: string[] = [];
|
|
220
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
if (entry.name.startsWith('.')) continue;
|
|
223
|
+
if (entry.isDirectory()) {
|
|
224
|
+
routes.push(...scanApiRoutes(path.join(dir, entry.name), prefix + '/' + entry.name));
|
|
225
|
+
} else if (entry.name === 'route.ts' || entry.name === 'route.js') {
|
|
226
|
+
routes.push(prefix || '/');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return routes;
|
|
230
|
+
};
|
|
231
|
+
context.existingApiRoutes = scanApiRoutes(fullPath).slice(0, 30);
|
|
232
|
+
} catch {
|
|
233
|
+
// Ignore API route scan errors
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return context;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private formatContextForPrompt(context: ProjectContext): string {
|
|
243
|
+
const lines: string[] = [];
|
|
244
|
+
|
|
245
|
+
lines.push(`Project: ${context.projectName}`);
|
|
246
|
+
|
|
247
|
+
if (context.uiLibrary) {
|
|
248
|
+
lines.push(`UI Library: ${context.uiLibrary}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (context.schemaPath) {
|
|
252
|
+
lines.push(`Database Schema: ${context.schemaPath}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (context.componentsPath && context.existingComponents.length > 0) {
|
|
256
|
+
lines.push(`Components Path: ${context.componentsPath}`);
|
|
257
|
+
lines.push(`Existing Components: ${context.existingComponents.slice(0, 20).join(', ')}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (context.existingServices.length > 0) {
|
|
261
|
+
lines.push(`Existing Services: ${context.existingServices.join(', ')}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (context.existingApiRoutes.length > 0) {
|
|
265
|
+
lines.push(`Existing API Routes: ${context.existingApiRoutes.join(', ')}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const features: string[] = [];
|
|
269
|
+
if (context.hasAuth) features.push('auth');
|
|
270
|
+
if (context.hasDatabase) features.push('database');
|
|
271
|
+
if (context.hasPayments) features.push('payments');
|
|
272
|
+
if (features.length > 0) {
|
|
273
|
+
lines.push(`Has: ${features.join(', ')}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const relevantDeps = context.dependencies.filter(d =>
|
|
277
|
+
['next', 'react', 'drizzle-orm', 'stripe', '@supabase/supabase-js', 'zod', 'react-hook-form', 'tailwindcss'].some(rd => d.includes(rd))
|
|
278
|
+
);
|
|
279
|
+
if (relevantDeps.length > 0) {
|
|
280
|
+
lines.push(`Key Dependencies: ${relevantDeps.join(', ')}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return lines.join('\n');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private setupHandlers(): void {
|
|
287
|
+
// List available tools
|
|
288
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
289
|
+
tools: [
|
|
290
|
+
{
|
|
291
|
+
name: 'optimize_and_build',
|
|
292
|
+
description:
|
|
293
|
+
'ALWAYS USE THIS FIRST for any coding request. Takes a simple user request, uses AI to analyze intent and detect relevant patterns, optimizes it into a production-ready prompt, and returns everything needed to build the feature correctly. No keyword matching - AI understands what you actually want to build.',
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: 'object' as const,
|
|
296
|
+
properties: {
|
|
297
|
+
request: {
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'The user\'s original request (e.g., "add login", "create checkout", "zoom animation on image")',
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
required: ['request'],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'get_pattern',
|
|
307
|
+
description:
|
|
308
|
+
'Fetch a single CodeBakers pattern module by name. Use optimize_and_build instead for automatic pattern detection.',
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: 'object' as const,
|
|
311
|
+
properties: {
|
|
312
|
+
pattern: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
description:
|
|
315
|
+
'Pattern name (e.g., "00-core", "01-database", "02-auth", "03-api", "04-frontend")',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
required: ['pattern'],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: 'list_patterns',
|
|
323
|
+
description:
|
|
324
|
+
'List all available CodeBakers pattern modules.',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object' as const,
|
|
327
|
+
properties: {},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'get_patterns',
|
|
332
|
+
description:
|
|
333
|
+
'Fetch multiple CodeBakers patterns at once. Use optimize_and_build instead for automatic pattern detection.',
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: 'object' as const,
|
|
336
|
+
properties: {
|
|
337
|
+
patterns: {
|
|
338
|
+
type: 'array',
|
|
339
|
+
items: { type: 'string' },
|
|
340
|
+
description: 'Array of pattern names to fetch (max 5)',
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
required: ['patterns'],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
}));
|
|
348
|
+
|
|
349
|
+
// Handle tool calls
|
|
350
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
351
|
+
if (!this.apiKey) {
|
|
352
|
+
throw new McpError(
|
|
353
|
+
ErrorCode.InvalidRequest,
|
|
354
|
+
'Not logged in. Run `codebakers login` first.'
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const { name, arguments: args } = request.params;
|
|
359
|
+
|
|
360
|
+
switch (name) {
|
|
361
|
+
case 'optimize_and_build':
|
|
362
|
+
return this.handleOptimizeAndBuild(args as { request: string });
|
|
363
|
+
|
|
364
|
+
case 'get_pattern':
|
|
365
|
+
return this.handleGetPattern(args as { pattern: string });
|
|
366
|
+
|
|
367
|
+
case 'list_patterns':
|
|
368
|
+
return this.handleListPatterns();
|
|
369
|
+
|
|
370
|
+
case 'get_patterns':
|
|
371
|
+
return this.handleGetPatterns(args as { patterns: string[] });
|
|
372
|
+
|
|
373
|
+
default:
|
|
374
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private async handleOptimizeAndBuild(args: { request: string }) {
|
|
380
|
+
const { request: userRequest } = args;
|
|
381
|
+
|
|
382
|
+
// Step 1: Gather project context
|
|
383
|
+
const context = this.gatherProjectContext();
|
|
384
|
+
const contextSummary = this.formatContextForPrompt(context);
|
|
385
|
+
|
|
386
|
+
// Step 2: Call API to optimize the prompt with context
|
|
387
|
+
// The API uses AI to analyze intent and detect patterns (no keyword matching)
|
|
388
|
+
const optimizeResponse = await fetch(`${this.apiUrl}/api/optimize-prompt`, {
|
|
389
|
+
method: 'POST',
|
|
390
|
+
headers: {
|
|
391
|
+
'Content-Type': 'application/json',
|
|
392
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
393
|
+
},
|
|
394
|
+
body: JSON.stringify({
|
|
395
|
+
prompt: userRequest,
|
|
396
|
+
context: {
|
|
397
|
+
summary: contextSummary,
|
|
398
|
+
projectName: context.projectName,
|
|
399
|
+
uiLibrary: context.uiLibrary,
|
|
400
|
+
schemaPath: context.schemaPath,
|
|
401
|
+
componentsPath: context.componentsPath,
|
|
402
|
+
existingComponents: context.existingComponents.slice(0, 30),
|
|
403
|
+
existingServices: context.existingServices,
|
|
404
|
+
existingApiRoutes: context.existingApiRoutes,
|
|
405
|
+
hasAuth: context.hasAuth,
|
|
406
|
+
hasDatabase: context.hasDatabase,
|
|
407
|
+
hasPayments: context.hasPayments,
|
|
408
|
+
dependencies: context.dependencies.slice(0, 30),
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Default values if API fails
|
|
414
|
+
let optimizedPrompt = userRequest;
|
|
415
|
+
let detectedFeature = 'Feature';
|
|
416
|
+
let patterns = ['00-core', '04-frontend']; // Default fallback
|
|
417
|
+
|
|
418
|
+
if (optimizeResponse.ok) {
|
|
419
|
+
const optimizeData: OptimizeResponse = await optimizeResponse.json();
|
|
420
|
+
optimizedPrompt = optimizeData.optimizedPrompt || userRequest;
|
|
421
|
+
detectedFeature = optimizeData.featureName || 'Feature';
|
|
422
|
+
// Use AI-detected patterns from the API (no local keyword matching)
|
|
423
|
+
patterns = optimizeData.patterns || ['00-core', '04-frontend'];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Step 3: Fetch all relevant patterns (as detected by AI)
|
|
427
|
+
const patternResult = await this.fetchPatterns(patterns);
|
|
428
|
+
|
|
429
|
+
// Step 4: Build the response showing the optimization with context
|
|
430
|
+
const patternContent = Object.entries(patternResult.patterns || {})
|
|
431
|
+
.map(([name, text]) => `## ${name}\n\n${text}`)
|
|
432
|
+
.join('\n\n---\n\n');
|
|
433
|
+
|
|
434
|
+
const response = `# 🪄 Prompt Optimizer (AI-Powered Intent Analysis)
|
|
435
|
+
|
|
436
|
+
## Your Request
|
|
437
|
+
"${userRequest}"
|
|
438
|
+
|
|
439
|
+
## Project Context Detected
|
|
440
|
+
${contextSummary}
|
|
441
|
+
|
|
442
|
+
## AI Analysis
|
|
443
|
+
- **Detected Intent:** ${detectedFeature}
|
|
444
|
+
- **Relevant Patterns:** ${patterns.join(', ')}
|
|
445
|
+
|
|
446
|
+
## Optimized Prompt (Production-Ready)
|
|
447
|
+
${optimizedPrompt}
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
# Pattern Documentation
|
|
452
|
+
|
|
453
|
+
${patternContent}
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
**IMPORTANT:** Use the optimized prompt above as your guide. It is tailored to THIS project's structure, existing components, and conventions. The AI analyzed your intent (not just keywords) to select the right patterns. The prompt includes:
|
|
458
|
+
- References to existing components and services you should reuse
|
|
459
|
+
- The correct file paths for this project
|
|
460
|
+
- Production requirements (error handling, loading states, validation, tests)
|
|
461
|
+
|
|
462
|
+
Show the user what their simple request was expanded into, then proceed with the implementation following the patterns above.`;
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
content: [
|
|
466
|
+
{
|
|
467
|
+
type: 'text' as const,
|
|
468
|
+
text: response,
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private async handleGetPattern(args: { pattern: string }) {
|
|
475
|
+
const { pattern } = args;
|
|
476
|
+
|
|
477
|
+
// Check cache first
|
|
478
|
+
const cached = patternCache.get(pattern);
|
|
479
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: 'text' as const,
|
|
484
|
+
text: cached.content,
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Fetch from API
|
|
491
|
+
const result = await this.fetchPatterns([pattern]);
|
|
492
|
+
|
|
493
|
+
if (result.patterns && result.patterns[pattern]) {
|
|
494
|
+
const content = result.patterns[pattern];
|
|
495
|
+
|
|
496
|
+
// Cache the result
|
|
497
|
+
patternCache.set(pattern, { content, timestamp: Date.now() });
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
content: [
|
|
501
|
+
{
|
|
502
|
+
type: 'text' as const,
|
|
503
|
+
text: content,
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
throw new McpError(
|
|
510
|
+
ErrorCode.InvalidRequest,
|
|
511
|
+
`Pattern "${pattern}" not found. Use list_patterns to see available patterns.`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private async handleListPatterns() {
|
|
516
|
+
const response = await fetch(`${this.apiUrl}/api/patterns`, {
|
|
517
|
+
method: 'GET',
|
|
518
|
+
headers: {
|
|
519
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
if (!response.ok) {
|
|
524
|
+
const error = await response.json().catch(() => ({}));
|
|
525
|
+
throw new McpError(
|
|
526
|
+
ErrorCode.InternalError,
|
|
527
|
+
error.error || 'Failed to fetch patterns'
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const data = await response.json();
|
|
532
|
+
const patternList = data.patterns
|
|
533
|
+
.map((p: { name: string }) => `- ${p.name}`)
|
|
534
|
+
.join('\n');
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
content: [
|
|
538
|
+
{
|
|
539
|
+
type: 'text' as const,
|
|
540
|
+
text: `Available CodeBakers Patterns (${data.total} total):\n\n${patternList}\n\nTip: Use optimize_and_build for automatic AI-powered pattern detection.`,
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private async handleGetPatterns(args: { patterns: string[] }) {
|
|
547
|
+
const { patterns } = args;
|
|
548
|
+
|
|
549
|
+
if (patterns.length > 5) {
|
|
550
|
+
throw new McpError(
|
|
551
|
+
ErrorCode.InvalidRequest,
|
|
552
|
+
'Maximum 5 patterns per request'
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const result = await this.fetchPatterns(patterns);
|
|
557
|
+
|
|
558
|
+
const content = Object.entries(result.patterns || {})
|
|
559
|
+
.map(([name, text]) => `## ${name}\n\n${text}`)
|
|
560
|
+
.join('\n\n---\n\n');
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
content: [
|
|
564
|
+
{
|
|
565
|
+
type: 'text' as const,
|
|
566
|
+
text:
|
|
567
|
+
content ||
|
|
568
|
+
'No patterns found. Use list_patterns to see available patterns.',
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private async fetchPatterns(patterns: string[]) {
|
|
575
|
+
const response = await fetch(`${this.apiUrl}/api/patterns`, {
|
|
576
|
+
method: 'POST',
|
|
577
|
+
headers: {
|
|
578
|
+
'Content-Type': 'application/json',
|
|
579
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
580
|
+
},
|
|
581
|
+
body: JSON.stringify({ patterns }),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
if (!response.ok) {
|
|
585
|
+
const error = await response.json().catch(() => ({}));
|
|
586
|
+
throw new McpError(
|
|
587
|
+
ErrorCode.InternalError,
|
|
588
|
+
error.error || 'Failed to fetch patterns'
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return response.json();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async run(): Promise<void> {
|
|
596
|
+
const transport = new StdioServerTransport();
|
|
597
|
+
await this.server.connect(transport);
|
|
598
|
+
|
|
599
|
+
// Log to stderr so it doesn't interfere with stdio protocol
|
|
600
|
+
console.error('CodeBakers MCP server running on stdio');
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Export function for programmatic usage
|
|
605
|
+
export async function runServer(): Promise<void> {
|
|
606
|
+
const server = new CodeBakersServer();
|
|
607
|
+
await server.run();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Run directly if executed as main module
|
|
611
|
+
const isMain = process.argv[1]?.includes('server');
|
|
612
|
+
if (isMain) {
|
|
613
|
+
runServer().catch((error) => {
|
|
614
|
+
console.error('Failed to start MCP server:', error);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
});
|
|
617
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|