@opensaas/stack-cli 0.1.6 → 0.3.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +215 -0
- package/CLAUDE.md +60 -12
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +10 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +116 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +21 -3
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +3 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +3 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/lists.d.ts +31 -0
- package/dist/generator/lists.d.ts.map +1 -0
- package/dist/generator/lists.js +91 -0
- package/dist/generator/lists.js.map +1 -0
- package/dist/generator/plugin-types.d.ts +10 -0
- package/dist/generator/plugin-types.d.ts.map +1 -0
- package/dist/generator/plugin-types.js +122 -0
- package/dist/generator/plugin-types.js.map +1 -0
- package/dist/generator/prisma-config.d.ts +17 -0
- package/dist/generator/prisma-config.d.ts.map +1 -0
- package/dist/generator/prisma-config.js +40 -0
- package/dist/generator/prisma-config.js.map +1 -0
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/generator/prisma.js +1 -2
- package/dist/generator/prisma.js.map +1 -1
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +53 -1
- package/dist/generator/types.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/lib/documentation-provider.d.ts +43 -0
- package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
- package/dist/mcp/lib/documentation-provider.js +163 -0
- package/dist/mcp/lib/documentation-provider.js.map +1 -0
- package/dist/mcp/lib/features/catalog.d.ts +26 -0
- package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
- package/dist/mcp/lib/features/catalog.js +291 -0
- package/dist/mcp/lib/features/catalog.js.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.js +546 -0
- package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
- package/dist/mcp/lib/types.d.ts +80 -0
- package/dist/mcp/lib/types.d.ts.map +1 -0
- package/dist/mcp/lib/types.js +5 -0
- package/dist/mcp/lib/types.js.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
- package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
- package/dist/mcp/server/index.d.ts +8 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js +202 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
- package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
- package/dist/mcp/server/stack-mcp-server.js +265 -0
- package/dist/mcp/server/stack-mcp-server.js.map +1 -0
- package/package.json +9 -7
- package/src/commands/__snapshots__/generate.test.ts.snap +61 -21
- package/src/commands/dev.test.ts +0 -1
- package/src/commands/generate.test.ts +18 -8
- package/src/commands/generate.ts +12 -0
- package/src/commands/mcp.ts +135 -0
- package/src/generator/__snapshots__/context.test.ts.snap +8 -8
- package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
- package/src/generator/__snapshots__/types.test.ts.snap +605 -9
- package/src/generator/context.test.ts +13 -8
- package/src/generator/context.ts +21 -3
- package/src/generator/index.ts +3 -0
- package/src/generator/lists.test.ts +335 -0
- package/src/generator/lists.ts +102 -0
- package/src/generator/plugin-types.ts +147 -0
- package/src/generator/prisma-config.ts +46 -0
- package/src/generator/prisma.test.ts +0 -10
- package/src/generator/prisma.ts +1 -2
- package/src/generator/types.test.ts +0 -12
- package/src/generator/types.ts +56 -1
- package/src/index.ts +4 -0
- package/src/mcp/lib/documentation-provider.ts +203 -0
- package/src/mcp/lib/features/catalog.ts +301 -0
- package/src/mcp/lib/generators/feature-generator.ts +598 -0
- package/src/mcp/lib/types.ts +89 -0
- package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
- package/src/mcp/server/index.ts +240 -0
- package/src/mcp/server/stack-mcp-server.ts +301 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenSaaS Stack MCP Server - Business logic for MCP tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WizardEngine } from '../lib/wizards/wizard-engine.js'
|
|
6
|
+
import { OpenSaasDocumentationProvider } from '../lib/documentation-provider.js'
|
|
7
|
+
import { getAllFeatures, getFeature } from '../lib/features/catalog.js'
|
|
8
|
+
|
|
9
|
+
export class StackMCPServer {
|
|
10
|
+
private wizardEngine: WizardEngine
|
|
11
|
+
private docsProvider: OpenSaasDocumentationProvider
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.wizardEngine = new WizardEngine()
|
|
15
|
+
this.docsProvider = new OpenSaasDocumentationProvider()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Implement a feature - starts the wizard flow
|
|
20
|
+
*/
|
|
21
|
+
async implementFeature({ feature, description }: { feature: string; description?: string }) {
|
|
22
|
+
if (feature === 'custom' && !description) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text' as const,
|
|
27
|
+
text: '❌ For custom features, please provide a description of what you want to build.',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (feature === 'custom') {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: 'text' as const,
|
|
38
|
+
text: `🔧 **Custom Feature**: ${description}
|
|
39
|
+
|
|
40
|
+
I'll help you build this custom feature. Let me search the docs for relevant patterns...
|
|
41
|
+
|
|
42
|
+
${await this.searchFeatureDocs({ topic: description! })}
|
|
43
|
+
|
|
44
|
+
Based on your description, consider using these OpenSaaS patterns:
|
|
45
|
+
- Define your data model with \`list()\` and field types
|
|
46
|
+
- Add access control with \`access.operation\` and \`access.field\`
|
|
47
|
+
- Use hooks for data transformation and side effects
|
|
48
|
+
- Consider if any plugins would help (auth, storage, RAG)
|
|
49
|
+
|
|
50
|
+
Would you like me to help you design the config for this feature?`,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return this.wizardEngine.startFeature(feature)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Answer a wizard question
|
|
61
|
+
*/
|
|
62
|
+
async answerFeatureQuestion({
|
|
63
|
+
sessionId,
|
|
64
|
+
answer,
|
|
65
|
+
}: {
|
|
66
|
+
sessionId: string
|
|
67
|
+
answer: string | boolean | string[]
|
|
68
|
+
}) {
|
|
69
|
+
return this.wizardEngine.answerQuestion(sessionId, answer)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Answer a follow-up question
|
|
74
|
+
*/
|
|
75
|
+
async answerFollowUpQuestion({ sessionId, answer }: { sessionId: string; answer: string }) {
|
|
76
|
+
return this.wizardEngine.answerFollowUp(sessionId, answer)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Search documentation
|
|
81
|
+
*/
|
|
82
|
+
async searchFeatureDocs({ topic }: { topic: string }) {
|
|
83
|
+
const docs = await this.docsProvider.getTopicDocs(topic)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text' as const,
|
|
89
|
+
text: `# ${docs.topic}
|
|
90
|
+
|
|
91
|
+
${docs.content}
|
|
92
|
+
|
|
93
|
+
${docs.codeExamples.length > 0 ? `\n## Code Examples\n\n${docs.codeExamples.join('\n\n')}` : ''}
|
|
94
|
+
|
|
95
|
+
${docs.relatedTopics.length > 0 ? `\n## Related Topics\n\n${docs.relatedTopics.map((t) => `- ${t}`).join('\n')}` : ''}
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
📚 **Full documentation**: ${docs.url}`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List all available features
|
|
107
|
+
*/
|
|
108
|
+
async listFeatures() {
|
|
109
|
+
const features = getAllFeatures()
|
|
110
|
+
const categories = {
|
|
111
|
+
authentication: [] as typeof features,
|
|
112
|
+
content: [] as typeof features,
|
|
113
|
+
storage: [] as typeof features,
|
|
114
|
+
search: [] as typeof features,
|
|
115
|
+
custom: [] as typeof features,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
features.forEach((feature) => {
|
|
119
|
+
categories[feature.category].push(feature)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const formatFeatureList = (featureList: typeof features) =>
|
|
123
|
+
featureList
|
|
124
|
+
.map(
|
|
125
|
+
(f) =>
|
|
126
|
+
`### ${f.name}\n**ID**: \`${f.id}\`\n${f.description}\n\n**Includes**:\n${f.includes.map((i) => `- ${i}`).join('\n')}\n${f.dependsOn && f.dependsOn.length > 0 ? `\n**Requires**: ${f.dependsOn.join(', ')}` : ''}`,
|
|
127
|
+
)
|
|
128
|
+
.join('\n\n')
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: 'text' as const,
|
|
134
|
+
text: `# Available OpenSaaS Stack Features
|
|
135
|
+
|
|
136
|
+
Use \`opensaas_implement_feature\` to start implementing any of these:
|
|
137
|
+
|
|
138
|
+
## 🔐 Authentication
|
|
139
|
+
|
|
140
|
+
${formatFeatureList(categories.authentication)}
|
|
141
|
+
|
|
142
|
+
## 📝 Content Management
|
|
143
|
+
|
|
144
|
+
${formatFeatureList(categories.content)}
|
|
145
|
+
|
|
146
|
+
## 📦 Storage
|
|
147
|
+
|
|
148
|
+
${formatFeatureList(categories.storage)}
|
|
149
|
+
|
|
150
|
+
## 🔍 Search
|
|
151
|
+
|
|
152
|
+
${formatFeatureList(categories.search)}
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
**To implement a feature**, call:
|
|
157
|
+
\`\`\`
|
|
158
|
+
opensaas_implement_feature({ feature: "authentication" })
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
**For custom features**, call:
|
|
162
|
+
\`\`\`
|
|
163
|
+
opensaas_implement_feature({
|
|
164
|
+
feature: "custom",
|
|
165
|
+
description: "what you want to build"
|
|
166
|
+
})
|
|
167
|
+
\`\`\``,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Suggest complementary features based on described features
|
|
175
|
+
*/
|
|
176
|
+
async suggestFeatures({ currentFeatures }: { currentFeatures?: string[] }) {
|
|
177
|
+
const allFeatures = getAllFeatures()
|
|
178
|
+
const implemented = new Set(currentFeatures || [])
|
|
179
|
+
|
|
180
|
+
const suggestions = allFeatures
|
|
181
|
+
.filter((f) => !implemented.has(f.id))
|
|
182
|
+
.map((f) => {
|
|
183
|
+
let reasoning = ''
|
|
184
|
+
|
|
185
|
+
// Add context-aware suggestions
|
|
186
|
+
if (f.id === 'authentication' && !implemented.has('authentication')) {
|
|
187
|
+
reasoning = 'Essential for user management and access control'
|
|
188
|
+
} else if (f.id === 'blog' && implemented.has('authentication')) {
|
|
189
|
+
reasoning = 'You have auth - add content creation capabilities'
|
|
190
|
+
} else if (
|
|
191
|
+
f.id === 'comments' &&
|
|
192
|
+
(implemented.has('blog') || implemented.has('authentication'))
|
|
193
|
+
) {
|
|
194
|
+
reasoning = 'Enhance engagement with user comments'
|
|
195
|
+
} else if (
|
|
196
|
+
f.id === 'file-upload' &&
|
|
197
|
+
(implemented.has('blog') || implemented.has('authentication'))
|
|
198
|
+
) {
|
|
199
|
+
reasoning = 'Add media support for richer content'
|
|
200
|
+
} else if (f.id === 'semantic-search' && implemented.has('blog')) {
|
|
201
|
+
reasoning = 'Make your content more discoverable'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { feature: f, reasoning }
|
|
205
|
+
})
|
|
206
|
+
.filter((s) => s.reasoning)
|
|
207
|
+
|
|
208
|
+
if (suggestions.length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: 'text' as const,
|
|
213
|
+
text: "🎉 You've implemented all our built-in features!\n\nConsider building custom features tailored to your specific needs using `opensaas_implement_feature({ feature: 'custom', description: '...' })`",
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
content: [
|
|
221
|
+
{
|
|
222
|
+
type: 'text' as const,
|
|
223
|
+
text: `# Feature Suggestions
|
|
224
|
+
|
|
225
|
+
Based on ${currentFeatures && currentFeatures.length > 0 ? `your current features (${currentFeatures.join(', ')})` : 'common patterns'}, consider adding:
|
|
226
|
+
|
|
227
|
+
${suggestions
|
|
228
|
+
.map(
|
|
229
|
+
(s, i) => `## ${i + 1}. ${s.feature.name}
|
|
230
|
+
|
|
231
|
+
${s.reasoning}
|
|
232
|
+
|
|
233
|
+
**What you'll get**:
|
|
234
|
+
${s.feature.includes.map((inc) => `- ${inc}`).join('\n')}
|
|
235
|
+
|
|
236
|
+
**To implement**: \`opensaas_implement_feature({ feature: "${s.feature.id}" })\`
|
|
237
|
+
`,
|
|
238
|
+
)
|
|
239
|
+
.join('\n---\n\n')}`,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Validate a feature implementation
|
|
247
|
+
*/
|
|
248
|
+
async validateFeature({ feature }: { feature: string; configPath?: string }) {
|
|
249
|
+
const featureDefinition = getFeature(feature)
|
|
250
|
+
|
|
251
|
+
if (!featureDefinition) {
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: 'text' as const,
|
|
256
|
+
text: `❌ Unknown feature: ${feature}\n\nUse \`opensaas_list_features\` to see available features.`,
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// TODO: Implement actual validation by reading the config file
|
|
263
|
+
// For now, return validation checklist
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: 'text' as const,
|
|
269
|
+
text: `# ${featureDefinition.name} Validation
|
|
270
|
+
|
|
271
|
+
Checking your implementation...
|
|
272
|
+
|
|
273
|
+
## Checklist
|
|
274
|
+
|
|
275
|
+
${featureDefinition.includes.map((item) => `- [ ] ${item}`).join('\n')}
|
|
276
|
+
|
|
277
|
+
${featureDefinition.dependsOn && featureDefinition.dependsOn.length > 0 ? `\n## Dependencies\n\nEnsure these features are implemented:\n${featureDefinition.dependsOn.map((dep) => `- [ ] ${dep}`).join('\n')}\n` : ''}
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
**Manual validation steps**:
|
|
282
|
+
|
|
283
|
+
1. Check that all required lists are defined in your config
|
|
284
|
+
2. Verify access control is properly configured
|
|
285
|
+
3. Test the feature in your development environment
|
|
286
|
+
4. Run \`pnpm generate\` and \`pnpm db:push\` successfully
|
|
287
|
+
|
|
288
|
+
**Note**: Full automatic validation coming soon! For now, use this checklist to verify your implementation.`,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Cleanup - clear wizard sessions and caches
|
|
296
|
+
*/
|
|
297
|
+
cleanup() {
|
|
298
|
+
this.wizardEngine.clearCompletedSessions()
|
|
299
|
+
this.docsProvider.clearExpiredCache()
|
|
300
|
+
}
|
|
301
|
+
}
|