@opensaas/stack-cli 0.1.0 → 0.1.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +249 -0
- package/LICENSE +21 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +6 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +78 -3
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +1 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +1 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/mcp.d.ts +14 -0
- package/dist/generator/mcp.d.ts.map +1 -0
- package/dist/generator/mcp.js +193 -0
- package/dist/generator/mcp.js.map +1 -0
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +11 -33
- package/dist/generator/types.js.map +1 -1
- package/package.json +9 -3
- package/src/commands/__snapshots__/generate.test.ts.snap +265 -0
- package/src/commands/dev.test.ts +216 -0
- package/src/commands/generate.test.ts +272 -0
- package/src/commands/generate.ts +7 -0
- package/src/commands/init.test.ts +308 -0
- package/src/commands/init.ts +2 -2
- package/src/generator/__snapshots__/context.test.ts.snap +137 -0
- package/src/generator/__snapshots__/prisma.test.ts.snap +182 -0
- package/src/generator/__snapshots__/types.test.ts.snap +512 -0
- package/src/generator/context.test.ts +145 -0
- package/src/generator/context.ts +80 -3
- package/src/generator/index.ts +1 -0
- package/src/generator/mcp.test.ts +393 -0
- package/src/generator/mcp.ts +221 -0
- package/src/generator/prisma.test.ts +221 -0
- package/src/generator/types.test.ts +280 -0
- package/src/generator/types.ts +14 -36
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +26 -0
package/src/generator/context.ts
CHANGED
|
@@ -12,11 +12,81 @@ export function generateContext(config: OpenSaasConfig): string {
|
|
|
12
12
|
// Check if custom Prisma client constructor is provided
|
|
13
13
|
const hasCustomConstructor = !!config.db.prismaClientConstructor
|
|
14
14
|
|
|
15
|
+
// Check if storage is configured
|
|
16
|
+
const hasStorage = !!config.storage && Object.keys(config.storage).length > 0
|
|
17
|
+
|
|
15
18
|
// Generate the Prisma client instantiation code
|
|
16
19
|
const prismaInstantiation = hasCustomConstructor
|
|
17
20
|
? `config.db.prismaClientConstructor!(PrismaClient)`
|
|
18
21
|
: `new PrismaClient()`
|
|
19
22
|
|
|
23
|
+
// Generate storage utilities if storage is configured
|
|
24
|
+
const storageUtilities = hasStorage
|
|
25
|
+
? `
|
|
26
|
+
/**
|
|
27
|
+
* Lazy-loaded storage runtime functions
|
|
28
|
+
* Prevents sharp and other storage dependencies from being bundled in client code
|
|
29
|
+
*/
|
|
30
|
+
let storageRuntime: typeof import('@opensaas/stack-storage/runtime') | null = null
|
|
31
|
+
|
|
32
|
+
async function getStorageRuntime() {
|
|
33
|
+
if (!storageRuntime) {
|
|
34
|
+
try {
|
|
35
|
+
storageRuntime = await import('@opensaas/stack-storage/runtime')
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Failed to load @opensaas/stack-storage/runtime. Make sure @opensaas/stack-storage is installed.'
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return storageRuntime
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Storage utilities for file/image uploads
|
|
47
|
+
*/
|
|
48
|
+
const storage = {
|
|
49
|
+
uploadFile: async (providerName: string, file: File, buffer: Buffer, options?: unknown) => {
|
|
50
|
+
const runtime = await getStorageRuntime()
|
|
51
|
+
return runtime.uploadFile(config, providerName, { file, buffer }, options as any)
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
uploadImage: async (providerName: string, file: File, buffer: Buffer, options?: unknown) => {
|
|
55
|
+
const runtime = await getStorageRuntime()
|
|
56
|
+
return runtime.uploadImage(config, providerName, { file, buffer }, options as any)
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
deleteFile: async (providerName: string, filename: string) => {
|
|
60
|
+
const runtime = await getStorageRuntime()
|
|
61
|
+
return runtime.deleteFile(config, providerName, filename)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
deleteImage: async (metadata: unknown) => {
|
|
65
|
+
const runtime = await getStorageRuntime()
|
|
66
|
+
return runtime.deleteImage(config, metadata as any)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
`
|
|
70
|
+
: `
|
|
71
|
+
/**
|
|
72
|
+
* Storage utilities (not configured)
|
|
73
|
+
*/
|
|
74
|
+
const storage = {
|
|
75
|
+
uploadFile: async () => {
|
|
76
|
+
throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
|
|
77
|
+
},
|
|
78
|
+
uploadImage: async () => {
|
|
79
|
+
throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
|
|
80
|
+
},
|
|
81
|
+
deleteFile: async () => {
|
|
82
|
+
throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
|
|
83
|
+
},
|
|
84
|
+
deleteImage: async () => {
|
|
85
|
+
throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
20
90
|
return `/**
|
|
21
91
|
* Auto-generated context factory
|
|
22
92
|
*
|
|
@@ -27,14 +97,16 @@ export function generateContext(config: OpenSaasConfig): string {
|
|
|
27
97
|
*/
|
|
28
98
|
|
|
29
99
|
import { getContext as getOpensaasContext } from '@opensaas/stack-core'
|
|
100
|
+
import type { Session as OpensaasSession } from '@opensaas/stack-core'
|
|
30
101
|
import { PrismaClient } from './prisma-client'
|
|
102
|
+
import type { Context } from './types'
|
|
31
103
|
import config from '../opensaas.config'
|
|
32
104
|
|
|
33
105
|
// Internal Prisma singleton - managed automatically
|
|
34
106
|
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
|
|
35
107
|
const prisma = globalForPrisma.prisma ?? ${prismaInstantiation}
|
|
36
108
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
37
|
-
|
|
109
|
+
${storageUtilities}
|
|
38
110
|
/**
|
|
39
111
|
* Get OpenSaas context with optional session
|
|
40
112
|
*
|
|
@@ -49,10 +121,15 @@ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
|
49
121
|
* // Authenticated access
|
|
50
122
|
* const context = getContext({ userId: 'user-123' })
|
|
51
123
|
* const myPosts = await context.db.post.findMany()
|
|
124
|
+
*
|
|
125
|
+
* // With custom session type
|
|
126
|
+
* type CustomSession = { userId: string; email: string; role: string } | null
|
|
127
|
+
* const context = getContext<CustomSession>({ userId: '123', email: 'user@example.com', role: 'admin' })
|
|
128
|
+
* // context.session is now typed as CustomSession
|
|
52
129
|
* \`\`\`
|
|
53
130
|
*/
|
|
54
|
-
export function getContext
|
|
55
|
-
return getOpensaasContext(config, prisma, session ?? null)
|
|
131
|
+
export function getContext<TSession extends OpensaasSession = OpensaasSession>(session?: TSession): Context<TSession> {
|
|
132
|
+
return getOpensaasContext(config, prisma, session ?? null, storage) as Context<TSession>
|
|
56
133
|
}
|
|
57
134
|
|
|
58
135
|
export const rawOpensaasContext = getContext()
|
package/src/generator/index.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { generatePrismaSchema, writePrismaSchema } from './prisma.js'
|
|
|
2
2
|
export { generateTypes, writeTypes } from './types.js'
|
|
3
3
|
export { patchPrismaTypes } from './type-patcher.js'
|
|
4
4
|
export { generateContext, writeContext } from './context.js'
|
|
5
|
+
export { generateMcp, writeMcp } from './mcp.js'
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { generateMcp } from './mcp.js'
|
|
3
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
|
+
import { text } from '@opensaas/stack-core/fields'
|
|
5
|
+
import * as fs from 'fs'
|
|
6
|
+
import * as path from 'path'
|
|
7
|
+
import * as os from 'os'
|
|
8
|
+
|
|
9
|
+
describe('MCP Generator', () => {
|
|
10
|
+
let tempDir: string
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Create a temporary directory for each test
|
|
14
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
// Clean up temporary directory
|
|
19
|
+
if (fs.existsSync(tempDir)) {
|
|
20
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('generateMcp', () => {
|
|
25
|
+
it('should return false when MCP is not enabled', () => {
|
|
26
|
+
const config: OpenSaasConfig = {
|
|
27
|
+
db: {
|
|
28
|
+
provider: 'sqlite',
|
|
29
|
+
url: 'file:./dev.db',
|
|
30
|
+
},
|
|
31
|
+
lists: {
|
|
32
|
+
User: {
|
|
33
|
+
fields: {
|
|
34
|
+
name: text(),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const generated = generateMcp(config, tempDir)
|
|
41
|
+
|
|
42
|
+
expect(generated).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should return true when MCP is enabled', () => {
|
|
46
|
+
const config: OpenSaasConfig = {
|
|
47
|
+
db: {
|
|
48
|
+
provider: 'sqlite',
|
|
49
|
+
url: 'file:./dev.db',
|
|
50
|
+
},
|
|
51
|
+
mcp: {
|
|
52
|
+
enabled: true,
|
|
53
|
+
},
|
|
54
|
+
lists: {
|
|
55
|
+
User: {
|
|
56
|
+
fields: {
|
|
57
|
+
name: text(),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const generated = generateMcp(config, tempDir)
|
|
64
|
+
|
|
65
|
+
expect(generated).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should create MCP directory when enabled', () => {
|
|
69
|
+
const config: OpenSaasConfig = {
|
|
70
|
+
db: {
|
|
71
|
+
provider: 'sqlite',
|
|
72
|
+
url: 'file:./dev.db',
|
|
73
|
+
},
|
|
74
|
+
mcp: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
},
|
|
77
|
+
lists: {},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
generateMcp(config, tempDir)
|
|
81
|
+
|
|
82
|
+
const mcpDir = path.join(tempDir, '.opensaas', 'mcp')
|
|
83
|
+
expect(fs.existsSync(mcpDir)).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should generate tools.json with default CRUD tools', () => {
|
|
87
|
+
const config: OpenSaasConfig = {
|
|
88
|
+
db: {
|
|
89
|
+
provider: 'sqlite',
|
|
90
|
+
url: 'file:./dev.db',
|
|
91
|
+
},
|
|
92
|
+
mcp: {
|
|
93
|
+
enabled: true,
|
|
94
|
+
},
|
|
95
|
+
lists: {
|
|
96
|
+
User: {
|
|
97
|
+
fields: {
|
|
98
|
+
name: text(),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
generateMcp(config, tempDir)
|
|
105
|
+
|
|
106
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
107
|
+
expect(fs.existsSync(toolsPath)).toBe(true)
|
|
108
|
+
|
|
109
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
110
|
+
expect(tools).toHaveLength(4) // query, create, update, delete
|
|
111
|
+
|
|
112
|
+
const toolNames = tools.map((t: { name: string }) => t.name)
|
|
113
|
+
expect(toolNames).toContain('list_user_query')
|
|
114
|
+
expect(toolNames).toContain('list_user_create')
|
|
115
|
+
expect(toolNames).toContain('list_user_update')
|
|
116
|
+
expect(toolNames).toContain('list_user_delete')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should respect list-level MCP enabled flag', () => {
|
|
120
|
+
const config: OpenSaasConfig = {
|
|
121
|
+
db: {
|
|
122
|
+
provider: 'sqlite',
|
|
123
|
+
url: 'file:./dev.db',
|
|
124
|
+
},
|
|
125
|
+
mcp: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
},
|
|
128
|
+
lists: {
|
|
129
|
+
User: {
|
|
130
|
+
fields: {
|
|
131
|
+
name: text(),
|
|
132
|
+
},
|
|
133
|
+
mcp: {
|
|
134
|
+
enabled: false,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
Post: {
|
|
138
|
+
fields: {
|
|
139
|
+
title: text(),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
generateMcp(config, tempDir)
|
|
146
|
+
|
|
147
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
148
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
149
|
+
|
|
150
|
+
// Only Post tools should be generated
|
|
151
|
+
const userTools = tools.filter((t: { listKey: string }) => t.listKey === 'User')
|
|
152
|
+
const postTools = tools.filter((t: { listKey: string }) => t.listKey === 'Post')
|
|
153
|
+
|
|
154
|
+
expect(userTools).toHaveLength(0)
|
|
155
|
+
expect(postTools).toHaveLength(4)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should respect custom tool configuration', () => {
|
|
159
|
+
const config: OpenSaasConfig = {
|
|
160
|
+
db: {
|
|
161
|
+
provider: 'sqlite',
|
|
162
|
+
url: 'file:./dev.db',
|
|
163
|
+
},
|
|
164
|
+
mcp: {
|
|
165
|
+
enabled: true,
|
|
166
|
+
},
|
|
167
|
+
lists: {
|
|
168
|
+
User: {
|
|
169
|
+
fields: {
|
|
170
|
+
name: text(),
|
|
171
|
+
},
|
|
172
|
+
mcp: {
|
|
173
|
+
tools: {
|
|
174
|
+
read: true,
|
|
175
|
+
create: false,
|
|
176
|
+
update: false,
|
|
177
|
+
delete: false,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
generateMcp(config, tempDir)
|
|
185
|
+
|
|
186
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
187
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
188
|
+
|
|
189
|
+
expect(tools).toHaveLength(1)
|
|
190
|
+
expect(tools[0].name).toBe('list_user_query')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should include custom tools', () => {
|
|
194
|
+
const config: OpenSaasConfig = {
|
|
195
|
+
db: {
|
|
196
|
+
provider: 'sqlite',
|
|
197
|
+
url: 'file:./dev.db',
|
|
198
|
+
},
|
|
199
|
+
mcp: {
|
|
200
|
+
enabled: true,
|
|
201
|
+
},
|
|
202
|
+
lists: {
|
|
203
|
+
User: {
|
|
204
|
+
fields: {
|
|
205
|
+
name: text(),
|
|
206
|
+
},
|
|
207
|
+
mcp: {
|
|
208
|
+
tools: {
|
|
209
|
+
read: false,
|
|
210
|
+
create: false,
|
|
211
|
+
update: false,
|
|
212
|
+
delete: false,
|
|
213
|
+
},
|
|
214
|
+
customTools: [
|
|
215
|
+
{
|
|
216
|
+
name: 'user_verify_email',
|
|
217
|
+
description: 'Verify a user email address',
|
|
218
|
+
inputSchema: {},
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
+
handler: async () => ({}) as any,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
generateMcp(config, tempDir)
|
|
229
|
+
|
|
230
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
231
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
232
|
+
|
|
233
|
+
expect(tools).toHaveLength(1)
|
|
234
|
+
expect(tools[0].name).toBe('user_verify_email')
|
|
235
|
+
expect(tools[0].operation).toBe('custom')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should generate README.md with usage instructions', () => {
|
|
239
|
+
const config: OpenSaasConfig = {
|
|
240
|
+
db: {
|
|
241
|
+
provider: 'sqlite',
|
|
242
|
+
url: 'file:./dev.db',
|
|
243
|
+
},
|
|
244
|
+
mcp: {
|
|
245
|
+
enabled: true,
|
|
246
|
+
},
|
|
247
|
+
lists: {
|
|
248
|
+
User: {
|
|
249
|
+
fields: {
|
|
250
|
+
name: text(),
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
generateMcp(config, tempDir)
|
|
257
|
+
|
|
258
|
+
const readmePath = path.join(tempDir, '.opensaas', 'mcp', 'README.md')
|
|
259
|
+
expect(fs.existsSync(readmePath)).toBe(true)
|
|
260
|
+
|
|
261
|
+
const readme = fs.readFileSync(readmePath, 'utf-8')
|
|
262
|
+
expect(readme).toContain('# MCP Tools Reference')
|
|
263
|
+
expect(readme).toContain('Available Tools')
|
|
264
|
+
expect(readme).toContain('Usage')
|
|
265
|
+
expect(readme).toContain('createMcpHandlers')
|
|
266
|
+
expect(readme).toContain('Connecting to Claude Desktop')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('should list tools in README', () => {
|
|
270
|
+
const config: OpenSaasConfig = {
|
|
271
|
+
db: {
|
|
272
|
+
provider: 'sqlite',
|
|
273
|
+
url: 'file:./dev.db',
|
|
274
|
+
},
|
|
275
|
+
mcp: {
|
|
276
|
+
enabled: true,
|
|
277
|
+
},
|
|
278
|
+
lists: {
|
|
279
|
+
User: {
|
|
280
|
+
fields: {
|
|
281
|
+
name: text(),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
generateMcp(config, tempDir)
|
|
288
|
+
|
|
289
|
+
const readmePath = path.join(tempDir, '.opensaas', 'mcp', 'README.md')
|
|
290
|
+
const readme = fs.readFileSync(readmePath, 'utf-8')
|
|
291
|
+
|
|
292
|
+
expect(readme).toContain('list_user_query')
|
|
293
|
+
expect(readme).toContain('list_user_create')
|
|
294
|
+
expect(readme).toContain('list_user_update')
|
|
295
|
+
expect(readme).toContain('list_user_delete')
|
|
296
|
+
expect(readme).toContain('4 tool(s) available')
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should handle multiple lists', () => {
|
|
300
|
+
const config: OpenSaasConfig = {
|
|
301
|
+
db: {
|
|
302
|
+
provider: 'sqlite',
|
|
303
|
+
url: 'file:./dev.db',
|
|
304
|
+
},
|
|
305
|
+
mcp: {
|
|
306
|
+
enabled: true,
|
|
307
|
+
},
|
|
308
|
+
lists: {
|
|
309
|
+
User: {
|
|
310
|
+
fields: {
|
|
311
|
+
name: text(),
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
Post: {
|
|
315
|
+
fields: {
|
|
316
|
+
title: text(),
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
generateMcp(config, tempDir)
|
|
323
|
+
|
|
324
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
325
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
326
|
+
|
|
327
|
+
expect(tools).toHaveLength(8) // 4 tools per list
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('should respect global defaultTools config', () => {
|
|
331
|
+
const config: OpenSaasConfig = {
|
|
332
|
+
db: {
|
|
333
|
+
provider: 'sqlite',
|
|
334
|
+
url: 'file:./dev.db',
|
|
335
|
+
},
|
|
336
|
+
mcp: {
|
|
337
|
+
enabled: true,
|
|
338
|
+
defaultTools: {
|
|
339
|
+
read: true,
|
|
340
|
+
create: true,
|
|
341
|
+
update: false,
|
|
342
|
+
delete: false,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
lists: {
|
|
346
|
+
User: {
|
|
347
|
+
fields: {
|
|
348
|
+
name: text(),
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
generateMcp(config, tempDir)
|
|
355
|
+
|
|
356
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
357
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
358
|
+
|
|
359
|
+
expect(tools).toHaveLength(2)
|
|
360
|
+
const toolNames = tools.map((t: { name: string }) => t.name)
|
|
361
|
+
expect(toolNames).toContain('list_user_query')
|
|
362
|
+
expect(toolNames).toContain('list_user_create')
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should use correct dbKey for tool names', () => {
|
|
366
|
+
const config: OpenSaasConfig = {
|
|
367
|
+
db: {
|
|
368
|
+
provider: 'sqlite',
|
|
369
|
+
url: 'file:./dev.db',
|
|
370
|
+
},
|
|
371
|
+
mcp: {
|
|
372
|
+
enabled: true,
|
|
373
|
+
},
|
|
374
|
+
lists: {
|
|
375
|
+
BlogPost: {
|
|
376
|
+
fields: {
|
|
377
|
+
title: text(),
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
generateMcp(config, tempDir)
|
|
384
|
+
|
|
385
|
+
const toolsPath = path.join(tempDir, '.opensaas', 'mcp', 'tools.json')
|
|
386
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, 'utf-8'))
|
|
387
|
+
|
|
388
|
+
const toolNames = tools.map((t: { name: string }) => t.name)
|
|
389
|
+
expect(toolNames).toContain('list_blogPost_query')
|
|
390
|
+
expect(toolNames).toContain('list_blogPost_create')
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
})
|