import { createLogger } from '@open-mercato/shared/lib/logger' import { NextResponse, type NextRequest } from 'next/server' import { z } from 'zod' import type { EntityManager } from '@mikro-orm/postgresql' import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server' import { createRequestContainer } from '@open-mercato/shared/lib/di/container' import { createApiKey } from '@open-mercato/core/modules/api_keys/services/apiKeyService' import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi' import { getUserRoleIds } from '../../lib/user-role-ids' const logger = createLogger('ai_assistant') export const metadata = { POST: { requireAuth: true, requireFeatures: ['api_keys.create'] }, } const bodySchema = z.object({ name: z.string().min(1).max(120).optional(), description: z.string().max(1000).optional().nullable(), }) /** * POST /api/ai_assistant/mcp-key * * Creates a persistent MCP API key (`omk_...`) for use in `.mcp.json`. Unlike a * raw `POST /api/api_keys/keys` call — which assigns no roles unless the client * passes them — this endpoint resolves the **caller's own roles** server-side * and attaches them to the key, so the key inherits the current user's ACL. * The user cannot escalate: they only ever grant their own roles to their own * key. */ export async function POST(req: NextRequest) { const auth = await getAuthFromRequest(req) if (!auth?.sub) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } try { const raw = await req.json().catch(() => ({})) const body = bodySchema.parse(raw ?? {}) const container = await createRequestContainer() const em = container.resolve('em') const roleIds = await getUserRoleIds(em, auth.sub, auth.tenantId) const { record, secret } = await createApiKey(em, { name: body.name ?? `MCP Config - ${new Date().toISOString().slice(0, 10)}`, description: body.description ?? 'Generated from AI Assistant settings for MCP client', tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null, roles: roleIds, createdBy: auth.sub, }) return NextResponse.json({ id: record.id, name: record.name, keyPrefix: record.keyPrefix, secret, tenantId: record.tenantId ?? null, organizationId: record.organizationId ?? null, roles: roleIds, }) } catch (error) { logger.error('MCP Key — Error', { err: error }) return NextResponse.json( { error: 'Failed to create MCP API key' }, { status: 500 }, ) } } const responseSchema = z.object({ id: z.string(), name: z.string(), keyPrefix: z.string(), secret: z.string().describe('Full API key value (omk_...). Shown once.'), tenantId: z.string().uuid().nullable(), organizationId: z.string().uuid().nullable(), roles: z.array(z.string()).describe('Role ids inherited from the calling user.'), }) const errorSchema = z.object({ error: z.string() }) export const openApi: OpenApiRouteDoc = { summary: 'Create MCP API key', description: 'Creates a persistent MCP API key that inherits the calling user\'s roles, for use in an MCP client `.mcp.json`.', methods: { POST: { summary: 'Generate MCP API key', description: 'Generates a persistent `omk_` API key scoped to the calling user\'s tenant/organization and carrying the caller\'s own roles, so the key has the same ACL as the user.', responses: [ { status: 200, description: 'API key created', schema: responseSchema }, ], errors: [ { status: 401, description: 'Unauthorized', schema: errorSchema }, { status: 500, description: 'Failed to create MCP API key', schema: errorSchema }, ], }, }, }