@open-mercato/ai-assistant 0.6.5-develop.5382.1.f542de69af → 0.6.5
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/AGENTS.md +12 -0
- package/dist/modules/ai_assistant/cli.js +14 -6
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities.js +4 -0
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/lib/attachment-parts.js +10 -3
- package/dist/modules/ai_assistant/lib/attachment-parts.js.map +2 -2
- package/dist/modules/ai_assistant/lib/client-factory.js +0 -8
- package/dist/modules/ai_assistant/lib/client-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +26 -0
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +23 -3
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js +2 -4
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js +71 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +1 -30
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +29 -23
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/dist/modules/ai_assistant/setup.js +9 -2
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/package.json +11 -12
- package/src/modules/ai_assistant/__tests__/cli-mcp-serve-key.test.ts +63 -0
- package/src/modules/ai_assistant/__tests__/setup-schedules.test.ts +33 -0
- package/src/modules/ai_assistant/cli.ts +18 -5
- package/src/modules/ai_assistant/data/__tests__/schema-unique-indexes.test.ts +39 -0
- package/src/modules/ai_assistant/data/entities.ts +5 -0
- package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +112 -0
- package/src/modules/ai_assistant/lib/__tests__/client-factory.test.ts +42 -0
- package/src/modules/ai_assistant/lib/__tests__/codemode-tools.test.ts +107 -0
- package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +30 -3
- package/src/modules/ai_assistant/lib/__tests__/mcp-client.test.ts +20 -1
- package/src/modules/ai_assistant/lib/__tests__/mcp-dev-key-resolution.test.ts +92 -0
- package/src/modules/ai_assistant/lib/__tests__/mcp-server-auth-fail-closed.test.ts +133 -0
- package/src/modules/ai_assistant/lib/attachment-parts.ts +26 -5
- package/src/modules/ai_assistant/lib/client-factory.ts +3 -9
- package/src/modules/ai_assistant/lib/codemode-tools.ts +49 -0
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +35 -3
- package/src/modules/ai_assistant/lib/mcp-client.ts +9 -5
- package/src/modules/ai_assistant/lib/mcp-dev-key-resolution.ts +107 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +1 -36
- package/src/modules/ai_assistant/lib/mcp-server.ts +47 -33
- package/src/modules/ai_assistant/lib/types.ts +7 -0
- package/src/modules/ai_assistant/setup.ts +11 -2
|
@@ -17,7 +17,14 @@ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacS
|
|
|
17
17
|
* Create and configure an MCP server instance.
|
|
18
18
|
*/
|
|
19
19
|
export async function createMcpServer(options: McpServerOptions): Promise<Server> {
|
|
20
|
-
const { config, container, context,
|
|
20
|
+
const { config, container, context, allowUnauthenticatedSuperadmin } = options
|
|
21
|
+
|
|
22
|
+
// Treat empty / whitespace-only secrets as missing so a blank api key cannot
|
|
23
|
+
// fall through into an unauthenticated branch.
|
|
24
|
+
const apiKeySecret =
|
|
25
|
+
typeof options.apiKeySecret === 'string' && options.apiKeySecret.trim().length > 0
|
|
26
|
+
? options.apiKeySecret
|
|
27
|
+
: undefined
|
|
21
28
|
|
|
22
29
|
let tenantId: string | null = null
|
|
23
30
|
let organizationId: string | null = null
|
|
@@ -37,41 +44,48 @@ export async function createMcpServer(options: McpServerOptions): Promise<Server
|
|
|
37
44
|
userFeatures = authResult.features
|
|
38
45
|
isSuperAdmin = authResult.isSuperAdmin
|
|
39
46
|
console.error(`[MCP Server] Authenticated via API key: ${authResult.keyName}`)
|
|
40
|
-
} else if (context) {
|
|
41
|
-
// Manual context
|
|
47
|
+
} else if (context && context.userId) {
|
|
48
|
+
// Manual context with a real user — load that user's ACL.
|
|
42
49
|
tenantId = context.tenantId
|
|
43
50
|
organizationId = context.organizationId
|
|
44
51
|
userId = context.userId
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}>
|
|
56
|
-
}
|
|
57
|
-
const acl = await rbacService.loadAcl(userId, {
|
|
58
|
-
tenantId,
|
|
59
|
-
organizationId,
|
|
60
|
-
})
|
|
61
|
-
userFeatures = acl.features
|
|
62
|
-
isSuperAdmin = acl.isSuperAdmin
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.error('[MCP Server] Failed to load user ACL:', error)
|
|
53
|
+
try {
|
|
54
|
+
const rbacService = container.resolve('rbacService') as {
|
|
55
|
+
loadAcl: (
|
|
56
|
+
userId: string,
|
|
57
|
+
scope: { tenantId: string | null; organizationId: string | null }
|
|
58
|
+
) => Promise<{
|
|
59
|
+
isSuperAdmin: boolean
|
|
60
|
+
features: string[]
|
|
61
|
+
}>
|
|
65
62
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
const acl = await rbacService.loadAcl(userId, {
|
|
64
|
+
tenantId,
|
|
65
|
+
organizationId,
|
|
66
|
+
})
|
|
67
|
+
userFeatures = acl.features
|
|
68
|
+
isSuperAdmin = acl.isSuperAdmin
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('[MCP Server] Failed to load user ACL:', error)
|
|
71
|
+
}
|
|
72
|
+
} else if (allowUnauthenticatedSuperadmin) {
|
|
73
|
+
// Explicit, loud dev/testing opt-in. Without a user there is no ACL to load,
|
|
74
|
+
// so the server runs as superadmin with no tenant scoping beyond whatever the
|
|
75
|
+
// caller pinned via `context`. NEVER enable this in production.
|
|
76
|
+
if (context) {
|
|
77
|
+
tenantId = context.tenantId
|
|
78
|
+
organizationId = context.organizationId
|
|
70
79
|
}
|
|
71
|
-
} else {
|
|
72
|
-
// No context and no API key - superadmin for dev/testing
|
|
73
80
|
isSuperAdmin = true
|
|
74
|
-
console.error(
|
|
81
|
+
console.error(
|
|
82
|
+
'[MCP Server] WARNING: allowUnauthenticatedSuperadmin is enabled — running with UNAUTHENTICATED SUPERADMIN access and no per-user ACL. Do not use this outside local development/testing.'
|
|
83
|
+
)
|
|
84
|
+
} else {
|
|
85
|
+
// Fail closed: refuse to start rather than silently escalating to superadmin.
|
|
86
|
+
throw new Error(
|
|
87
|
+
'[internal] MCP server refused to start: no authentication provided. Supply a valid apiKeySecret, a context with a non-empty userId, or explicitly set allowUnauthenticatedSuperadmin: true for local development.'
|
|
88
|
+
)
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
const toolContext: McpToolContext = {
|
|
@@ -200,14 +214,14 @@ export async function runMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
200
214
|
|
|
201
215
|
console.error(`[MCP Server] Starting ${options.config.name} v${options.config.version}`)
|
|
202
216
|
|
|
203
|
-
if (options.apiKeySecret) {
|
|
217
|
+
if (options.apiKeySecret && options.apiKeySecret.trim().length > 0) {
|
|
204
218
|
console.error(`[MCP Server] Authentication: API key`)
|
|
205
|
-
} else if (options.context) {
|
|
219
|
+
} else if (options.context && options.context.userId) {
|
|
206
220
|
console.error(`[MCP Server] Tenant: ${options.context.tenantId ?? '(none)'}`)
|
|
207
221
|
console.error(`[MCP Server] Organization: ${options.context.organizationId ?? '(none)'}`)
|
|
208
|
-
console.error(`[MCP Server] User: ${options.context.userId
|
|
222
|
+
console.error(`[MCP Server] User: ${options.context.userId}`)
|
|
209
223
|
} else {
|
|
210
|
-
console.error(`[MCP Server] Authentication: none (superadmin
|
|
224
|
+
console.error(`[MCP Server] Authentication: none (unauthenticated superadmin opt-in)`)
|
|
211
225
|
}
|
|
212
226
|
|
|
213
227
|
console.error(`[MCP Server] Tools registered: ${toolCount}`)
|
|
@@ -217,6 +217,13 @@ export interface McpServerOptions {
|
|
|
217
217
|
}
|
|
218
218
|
/** API key secret for authentication (alternative to manual context) */
|
|
219
219
|
apiKeySecret?: string
|
|
220
|
+
/**
|
|
221
|
+
* Explicit, loud opt-in for running without authentication as a superadmin
|
|
222
|
+
* (development/testing only). When false/unset and no `apiKeySecret` or
|
|
223
|
+
* `context.userId` is supplied, the server fails closed and refuses to start
|
|
224
|
+
* instead of silently escalating to superadmin. NEVER enable in production.
|
|
225
|
+
*/
|
|
226
|
+
allowUnauthenticatedSuperadmin?: boolean
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
/**
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
1
2
|
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
4
|
+
const PENDING_ACTION_CLEANUP_SCHEDULE_KEY = 'ai_assistant:pending-action-cleanup'
|
|
5
|
+
const TOKEN_USAGE_PRUNE_SCHEDULE_KEY = 'ai_assistant:token-usage-prune'
|
|
6
|
+
|
|
7
|
+
function stableScheduleUuid(stableKey: string): string {
|
|
8
|
+
const hex = createHash('sha256').update(stableKey).digest('hex')
|
|
9
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PENDING_ACTION_CLEANUP_SCHEDULE_ID = stableScheduleUuid(PENDING_ACTION_CLEANUP_SCHEDULE_KEY)
|
|
13
|
+
const TOKEN_USAGE_PRUNE_SCHEDULE_ID = stableScheduleUuid(TOKEN_USAGE_PRUNE_SCHEDULE_KEY)
|
|
5
14
|
|
|
6
15
|
/**
|
|
7
16
|
* System-scoped recurring schedule: every 5 minutes, enqueue a job to the
|