@open-mercato/ai-assistant 0.6.8-develop.7100.1.fbf66fca35 → 0.7.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/AGENTS.md +1 -1
- package/dist/modules/ai_assistant/ai-tools/search-pack.js +3 -93
- package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +3 -3
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +0 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +6 -14
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +2 -10
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/http-server.js +1 -3
- package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/in-process-client.js +1 -3
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js +1 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +1 -3
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +1 -3
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/package.json +7 -8
- package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +0 -5
- package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +5 -211
- package/src/modules/ai_assistant/ai-tools/search-pack.ts +4 -110
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +0 -3
- package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +0 -16
- package/src/modules/ai_assistant/lib/__tests__/mcp-client.test.ts +0 -30
- package/src/modules/ai_assistant/lib/codemode-tools.ts +7 -21
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +2 -10
- package/src/modules/ai_assistant/lib/http-server.ts +0 -2
- package/src/modules/ai_assistant/lib/in-process-client.ts +0 -2
- package/src/modules/ai_assistant/lib/mcp-client.ts +0 -1
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +0 -2
- package/src/modules/ai_assistant/lib/mcp-server.ts +0 -2
- package/src/modules/ai_assistant/lib/types.ts +0 -11
- package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js +0 -18
- package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js.map +0 -7
- package/src/modules/ai_assistant/lib/__tests__/codemode-tool-annotations.test.ts +0 -58
- package/src/modules/ai_assistant/lib/__tests__/mcp-server-tool-annotations.test.ts +0 -120
- package/src/modules/ai_assistant/lib/__tests__/mcp-tool-annotations.test.ts +0 -57
- package/src/modules/ai_assistant/lib/mcp-tool-annotations.ts +0 -35
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/http-server.ts"],
|
|
4
|
-
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod, toSafeZodSchema } from './schema-utils'\nimport { buildMcpToolAnnotations } from './mcp-tool-annotations'\nimport { redactSecretForLog, deriveApiKeySessionId } from './log-redaction'\nimport type { McpServerConfig, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'\nimport { findApiKeyBySecret, findSessionApiKeyWithSecret } from '@open-mercato/core/modules/api_keys/services/apiKeyService'\n\nconst logger = createLogger('ai_assistant').child({ component: 'mcp-http' })\n\n/**\n * Options for the HTTP MCP server.\n */\nexport type McpHttpServerOptions = {\n config: McpServerConfig\n container: AwilixContainer\n port: number\n}\n\n/**\n * Resolve user context from session token.\n * Returns null if session token is invalid or expired.\n * Includes the decrypted API key secret for making authenticated API calls.\n */\nasync function resolveSessionContext(\n sessionToken: string,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const em = baseContext.container.resolve<EntityManager>('em')\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n\n // Look up ephemeral key by session token with decrypted secret\n const sessionResult = await findSessionApiKeyWithSecret(em, sessionToken)\n if (!sessionResult) {\n if (debug) {\n logger.debug('Session token not found, expired, or secret unavailable', { sessionToken: redactSecretForLog(sessionToken) })\n }\n return null\n }\n\n const { key: sessionKey, secret: sessionSecret } = sessionResult\n\n // Load ACL for the session user\n const userId = sessionKey.sessionUserId || sessionKey.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('Session key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('Session context resolved', {\n userId,\n tenantId: sessionKey.tenantId,\n organizationId: sessionKey.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n hasSessionSecret: !!sessionSecret,\n })\n }\n\n return {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n // Use the decrypted session secret for API calls (not the MCP server key)\n apiKeySecret: sessionSecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving session context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Resolve user context from the server-level API key (header-based auth fallback).\n * Used when no session token is provided \u2014 loads the API key's ACL for RBAC.\n */\nasync function resolveApiKeyContext(\n apiKeyRecord: ApiKey,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n const userId = apiKeyRecord.sessionUserId ?? apiKeyRecord.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('API key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${apiKeyRecord.id}`, {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('API key context resolved', {\n userId,\n tenantId: apiKeyRecord.tenantId,\n organizationId: apiKeyRecord.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n })\n }\n\n return {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n apiKeySecret: baseContext.apiKeySecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving API key context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Create a stateless MCP server instance for a single request.\n * Tools are registered without pre-filtering - permission checks happen at execution time\n * based on the session token provided in each tool call.\n */\nfunction createMcpServerForRequest(\n config: McpServerConfig,\n toolContext: McpToolContext,\n apiKeyRecord: ApiKey\n): McpServer {\n const server = new McpServer(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n if (config.debug) {\n logger.debug('Registering tools (ACL checked per-call via session token)', { toolCount: tools.length })\n }\n\n // Register ALL tools - permission checks happen at execution time via session token\n for (const tool of tools) {\n if (config.debug) {\n logger.debug('Registering tool', { toolName: tool.name })\n }\n\n // Convert Zod schema to a \"safe\" schema without Date types\n // This uses JSON Schema round-trip to avoid issues with MCP SDK's internal conversion\n // Also inject _sessionToken as an optional parameter so the AI knows to pass it\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n // Convert to JSON Schema first\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Inject _sessionToken into the JSON schema properties\n const properties = (jsonSchema.properties ?? {}) as Record<string, unknown>\n properties._sessionToken = {\n type: 'string',\n description: 'Session authorization token. If omitted, the server API key roles are used instead.',\n }\n jsonSchema.properties = properties\n\n // Convert back to Zod with passthrough to allow extra properties\n const converted = jsonSchemaToZod(jsonSchema)\n // Use type assertion since we know it's an object schema (we added properties above)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (config.debug) {\n logger.debug('Skipping tool: schema conversion failed', { toolName: tool.name, err: error })\n }\n continue\n }\n } else {\n // If no schema, create one with just _sessionToken\n safeSchema = z.object({\n _sessionToken: z\n .string()\n .optional()\n .describe('Session authorization token (REQUIRED for all tool calls)'),\n })\n }\n\n // Wrap in try/catch to handle any remaining edge cases\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n annotations: buildMcpToolAnnotations(tool),\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n // Extract session token from args\n const sessionToken = toolArgs._sessionToken as string | undefined\n delete toolArgs._sessionToken // Remove before passing to tool handler\n\n // Always log tool calls for debugging\n logger.debug('Tool call received', {\n toolName: tool.name,\n hasSessionToken: !!sessionToken,\n argKeys: Object.keys(toolArgs ?? {}).join(','),\n })\n\n // Resolve user context from session token\n let effectiveContext = toolContext\n if (sessionToken) {\n const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug)\n if (sessionContext) {\n // Session context includes the decrypted API key secret + session ID for memory layer\n effectiveContext = { ...sessionContext, sessionId: sessionToken }\n } else {\n // Session token expired - return user-friendly error for AI to relay\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Your chat session has expired. Please close and reopen the chat window to continue.',\n code: 'SESSION_EXPIRED',\n }),\n },\n ],\n isError: true,\n }\n }\n } else {\n // No session token \u2014 fall back to header API key auth\n const apiKeyContext = await resolveApiKeyContext(apiKeyRecord, toolContext, config.debug)\n if (apiKeyContext) {\n effectiveContext = apiKeyContext\n } else if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Authentication failed: provide a session token (_sessionToken) or a valid API key with assigned roles',\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n\n // Derive a fallback sessionId from the API key so all tool calls\n // within the same MCP connection share a session memory cache\n if (!effectiveContext.sessionId && effectiveContext.apiKeySecret) {\n effectiveContext = {\n ...effectiveContext,\n sessionId: deriveApiKeySessionId(effectiveContext.apiKeySecret),\n }\n }\n }\n\n // Check if user has required permissions for this tool\n if (tool.requiredFeatures?.length) {\n const rbacService = effectiveContext.container.resolve<RbacService>('rbacService')\n const hasAccess = hasRequiredFeatures(\n tool.requiredFeatures,\n effectiveContext.userFeatures,\n effectiveContext.isSuperAdmin,\n rbacService\n )\n if (!hasAccess) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: `Insufficient permissions for tool \"${tool.name}\". Required: ${tool.requiredFeatures.join(', ')}`,\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n try {\n const result = await executeTool(tool.name, toolArgs, effectiveContext)\n\n if (!result.success) {\n logger.error('Tool call failed', { toolName: tool.name, err: result.error, code: result.errorCode })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n logger.debug('Tool call succeeded', { toolName: tool.name })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n } catch (err) {\n logger.error('Tool call threw', { toolName: tool.name, err })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: err instanceof Error ? err.message : 'Unknown error', code: 'EXCEPTION' }),\n },\n ],\n isError: true,\n }\n }\n }\n )\n } catch (error) {\n // Skip tools with schemas that can't be registered\n if (config.debug) {\n logger.debug('Skipping tool: registration failed', { toolName: tool.name, err: error })\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Maximum request body size (1MB).\n * Prevents memory exhaustion from oversized payloads.\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Run MCP server with HTTP transport (stateless mode).\n *\n * Each request creates a new MCP server instance and transport.\n * The server authenticates requests using API keys from the x-api-key header.\n */\nexport async function runMcpHttpServer(options: McpHttpServerOptions): Promise<void> {\n const { config, container, port } = options\n\n await loadAllModuleTools()\n\n // Generate and cache entity graph for understand_entity tool\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n logger.info('Entity graph generated', { entities: graph.nodes.length, relationships: graph.edges.length })\n } catch (error) {\n logger.warn('Entity graph generation skipped', { err: error })\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n logger.info('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n logger.warn('OpenAPI spec not available')\n }\n } catch (error) {\n logger.warn('OpenAPI spec caching skipped', { err: error })\n }\n\n // Index tools and entity schemas for hybrid search discovery (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n logger.info('Indexed entity schemas for hybrid search', { count })\n }\n }\n } catch (entityError) {\n logger.warn('Entity schema indexing skipped', { err: entityError })\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n logger.warn('Search indexing skipped (search service not available)', { err: error })\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n tools: getToolRegistry().listToolNames().length,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n logger.debug('Request received', { method: req.method, path: url.pathname })\n\n // Extract headers\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n // Server-level authentication via database lookup\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate API key against database (prefix lookup + bcrypt verify + expiry check)\n const em = container.resolve<EntityManager>('em')\n const apiKeyRecord = await findApiKeyBySecret(em, providedApiKey)\n if (!apiKeyRecord) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid or expired API key' }))\n return\n }\n\n if (config.debug) {\n logger.debug('Server-level auth passed', { method: req.method, keyPrefix: apiKeyRecord.keyPrefix })\n }\n\n // Create base tool context using API key's tenant/org scope\n // Session tokens can override with user-specific permissions\n const toolContext: McpToolContext = {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId: apiKeyRecord.createdBy ?? null,\n container,\n userFeatures: [],\n isSuperAdmin: false,\n apiKeySecret: providedApiKey,\n }\n\n try {\n // Create stateless transport (no session ID generator = stateless)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create new server for this request\n const mcpServer = createMcpServerForRequest(config, toolContext, apiKeyRecord)\n\n if (config.debug) {\n // Check registered tools on the server\n const registeredTools = (mcpServer as any)._registeredTools || {}\n logger.debug('Registered tools in McpServer', { toolNames: Object.keys(registeredTools).join(',') })\n logger.debug('Tool handlers initialized', { initialized: (mcpServer as any)._toolHandlersInitialized })\n }\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (config.debug) {\n logger.debug('Request completed, cleaned up')\n }\n })\n } catch (error) {\n logger.error('Error handling request', { err: error })\n if (!res.headersSent) {\n // Handle payload too large error\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n logger.info('Starting MCP HTTP server', { name: config.name, version: config.version })\n logger.info('Endpoint ready', { endpoint: `http://localhost:${port}/mcp` })\n logger.info('Health endpoint ready', { endpoint: `http://localhost:${port}/health` })\n logger.info('Tools registered', { toolCount })\n logger.info('Mode: stateless (new server per request)')\n logger.info('Server auth: API key validated against database (x-api-key header)')\n logger.info('User auth: session token (_sessionToken) preferred, falls back to API key roles')\n\n // Return a Promise that keeps the process alive until shutdown\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n logger.info('Server listening', { port })\n })\n\n const shutdown = async () => {\n logger.info('Shutting down')\n httpServer.close(() => {\n logger.info('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod, toSafeZodSchema } from './schema-utils'\nimport { redactSecretForLog, deriveApiKeySessionId } from './log-redaction'\nimport type { McpServerConfig, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'\nimport { findApiKeyBySecret, findSessionApiKeyWithSecret } from '@open-mercato/core/modules/api_keys/services/apiKeyService'\n\nconst logger = createLogger('ai_assistant').child({ component: 'mcp-http' })\n\n/**\n * Options for the HTTP MCP server.\n */\nexport type McpHttpServerOptions = {\n config: McpServerConfig\n container: AwilixContainer\n port: number\n}\n\n/**\n * Resolve user context from session token.\n * Returns null if session token is invalid or expired.\n * Includes the decrypted API key secret for making authenticated API calls.\n */\nasync function resolveSessionContext(\n sessionToken: string,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const em = baseContext.container.resolve<EntityManager>('em')\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n\n // Look up ephemeral key by session token with decrypted secret\n const sessionResult = await findSessionApiKeyWithSecret(em, sessionToken)\n if (!sessionResult) {\n if (debug) {\n logger.debug('Session token not found, expired, or secret unavailable', { sessionToken: redactSecretForLog(sessionToken) })\n }\n return null\n }\n\n const { key: sessionKey, secret: sessionSecret } = sessionResult\n\n // Load ACL for the session user\n const userId = sessionKey.sessionUserId || sessionKey.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('Session key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('Session context resolved', {\n userId,\n tenantId: sessionKey.tenantId,\n organizationId: sessionKey.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n hasSessionSecret: !!sessionSecret,\n })\n }\n\n return {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n // Use the decrypted session secret for API calls (not the MCP server key)\n apiKeySecret: sessionSecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving session context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Resolve user context from the server-level API key (header-based auth fallback).\n * Used when no session token is provided \u2014 loads the API key's ACL for RBAC.\n */\nasync function resolveApiKeyContext(\n apiKeyRecord: ApiKey,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n const userId = apiKeyRecord.sessionUserId ?? apiKeyRecord.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('API key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${apiKeyRecord.id}`, {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('API key context resolved', {\n userId,\n tenantId: apiKeyRecord.tenantId,\n organizationId: apiKeyRecord.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n })\n }\n\n return {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n apiKeySecret: baseContext.apiKeySecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving API key context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Create a stateless MCP server instance for a single request.\n * Tools are registered without pre-filtering - permission checks happen at execution time\n * based on the session token provided in each tool call.\n */\nfunction createMcpServerForRequest(\n config: McpServerConfig,\n toolContext: McpToolContext,\n apiKeyRecord: ApiKey\n): McpServer {\n const server = new McpServer(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n if (config.debug) {\n logger.debug('Registering tools (ACL checked per-call via session token)', { toolCount: tools.length })\n }\n\n // Register ALL tools - permission checks happen at execution time via session token\n for (const tool of tools) {\n if (config.debug) {\n logger.debug('Registering tool', { toolName: tool.name })\n }\n\n // Convert Zod schema to a \"safe\" schema without Date types\n // This uses JSON Schema round-trip to avoid issues with MCP SDK's internal conversion\n // Also inject _sessionToken as an optional parameter so the AI knows to pass it\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n // Convert to JSON Schema first\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Inject _sessionToken into the JSON schema properties\n const properties = (jsonSchema.properties ?? {}) as Record<string, unknown>\n properties._sessionToken = {\n type: 'string',\n description: 'Session authorization token. If omitted, the server API key roles are used instead.',\n }\n jsonSchema.properties = properties\n\n // Convert back to Zod with passthrough to allow extra properties\n const converted = jsonSchemaToZod(jsonSchema)\n // Use type assertion since we know it's an object schema (we added properties above)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (config.debug) {\n logger.debug('Skipping tool: schema conversion failed', { toolName: tool.name, err: error })\n }\n continue\n }\n } else {\n // If no schema, create one with just _sessionToken\n safeSchema = z.object({\n _sessionToken: z\n .string()\n .optional()\n .describe('Session authorization token (REQUIRED for all tool calls)'),\n })\n }\n\n // Wrap in try/catch to handle any remaining edge cases\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n // Extract session token from args\n const sessionToken = toolArgs._sessionToken as string | undefined\n delete toolArgs._sessionToken // Remove before passing to tool handler\n\n // Always log tool calls for debugging\n logger.debug('Tool call received', {\n toolName: tool.name,\n hasSessionToken: !!sessionToken,\n argKeys: Object.keys(toolArgs ?? {}).join(','),\n })\n\n // Resolve user context from session token\n let effectiveContext = toolContext\n if (sessionToken) {\n const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug)\n if (sessionContext) {\n // Session context includes the decrypted API key secret + session ID for memory layer\n effectiveContext = { ...sessionContext, sessionId: sessionToken }\n } else {\n // Session token expired - return user-friendly error for AI to relay\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Your chat session has expired. Please close and reopen the chat window to continue.',\n code: 'SESSION_EXPIRED',\n }),\n },\n ],\n isError: true,\n }\n }\n } else {\n // No session token \u2014 fall back to header API key auth\n const apiKeyContext = await resolveApiKeyContext(apiKeyRecord, toolContext, config.debug)\n if (apiKeyContext) {\n effectiveContext = apiKeyContext\n } else if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Authentication failed: provide a session token (_sessionToken) or a valid API key with assigned roles',\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n\n // Derive a fallback sessionId from the API key so all tool calls\n // within the same MCP connection share a session memory cache\n if (!effectiveContext.sessionId && effectiveContext.apiKeySecret) {\n effectiveContext = {\n ...effectiveContext,\n sessionId: deriveApiKeySessionId(effectiveContext.apiKeySecret),\n }\n }\n }\n\n // Check if user has required permissions for this tool\n if (tool.requiredFeatures?.length) {\n const rbacService = effectiveContext.container.resolve<RbacService>('rbacService')\n const hasAccess = hasRequiredFeatures(\n tool.requiredFeatures,\n effectiveContext.userFeatures,\n effectiveContext.isSuperAdmin,\n rbacService\n )\n if (!hasAccess) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: `Insufficient permissions for tool \"${tool.name}\". Required: ${tool.requiredFeatures.join(', ')}`,\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n try {\n const result = await executeTool(tool.name, toolArgs, effectiveContext)\n\n if (!result.success) {\n logger.error('Tool call failed', { toolName: tool.name, err: result.error, code: result.errorCode })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n logger.debug('Tool call succeeded', { toolName: tool.name })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n } catch (err) {\n logger.error('Tool call threw', { toolName: tool.name, err })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: err instanceof Error ? err.message : 'Unknown error', code: 'EXCEPTION' }),\n },\n ],\n isError: true,\n }\n }\n }\n )\n } catch (error) {\n // Skip tools with schemas that can't be registered\n if (config.debug) {\n logger.debug('Skipping tool: registration failed', { toolName: tool.name, err: error })\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Maximum request body size (1MB).\n * Prevents memory exhaustion from oversized payloads.\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Run MCP server with HTTP transport (stateless mode).\n *\n * Each request creates a new MCP server instance and transport.\n * The server authenticates requests using API keys from the x-api-key header.\n */\nexport async function runMcpHttpServer(options: McpHttpServerOptions): Promise<void> {\n const { config, container, port } = options\n\n await loadAllModuleTools()\n\n // Generate and cache entity graph for understand_entity tool\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n logger.info('Entity graph generated', { entities: graph.nodes.length, relationships: graph.edges.length })\n } catch (error) {\n logger.warn('Entity graph generation skipped', { err: error })\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n logger.info('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n logger.warn('OpenAPI spec not available')\n }\n } catch (error) {\n logger.warn('OpenAPI spec caching skipped', { err: error })\n }\n\n // Index tools and entity schemas for hybrid search discovery (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n logger.info('Indexed entity schemas for hybrid search', { count })\n }\n }\n } catch (entityError) {\n logger.warn('Entity schema indexing skipped', { err: entityError })\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n logger.warn('Search indexing skipped (search service not available)', { err: error })\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n tools: getToolRegistry().listToolNames().length,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n logger.debug('Request received', { method: req.method, path: url.pathname })\n\n // Extract headers\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n // Server-level authentication via database lookup\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate API key against database (prefix lookup + bcrypt verify + expiry check)\n const em = container.resolve<EntityManager>('em')\n const apiKeyRecord = await findApiKeyBySecret(em, providedApiKey)\n if (!apiKeyRecord) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid or expired API key' }))\n return\n }\n\n if (config.debug) {\n logger.debug('Server-level auth passed', { method: req.method, keyPrefix: apiKeyRecord.keyPrefix })\n }\n\n // Create base tool context using API key's tenant/org scope\n // Session tokens can override with user-specific permissions\n const toolContext: McpToolContext = {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId: apiKeyRecord.createdBy ?? null,\n container,\n userFeatures: [],\n isSuperAdmin: false,\n apiKeySecret: providedApiKey,\n }\n\n try {\n // Create stateless transport (no session ID generator = stateless)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create new server for this request\n const mcpServer = createMcpServerForRequest(config, toolContext, apiKeyRecord)\n\n if (config.debug) {\n // Check registered tools on the server\n const registeredTools = (mcpServer as any)._registeredTools || {}\n logger.debug('Registered tools in McpServer', { toolNames: Object.keys(registeredTools).join(',') })\n logger.debug('Tool handlers initialized', { initialized: (mcpServer as any)._toolHandlersInitialized })\n }\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (config.debug) {\n logger.debug('Request completed, cleaned up')\n }\n })\n } catch (error) {\n logger.error('Error handling request', { err: error })\n if (!res.headersSent) {\n // Handle payload too large error\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n logger.info('Starting MCP HTTP server', { name: config.name, version: config.version })\n logger.info('Endpoint ready', { endpoint: `http://localhost:${port}/mcp` })\n logger.info('Health endpoint ready', { endpoint: `http://localhost:${port}/health` })\n logger.info('Tools registered', { toolCount })\n logger.info('Mode: stateless (new server per request)')\n logger.info('Server auth: API key validated against database (x-api-key header)')\n logger.info('User auth: session token (_sessionToken) preferred, falls back to API key roles')\n\n // Return a Promise that keeps the process alive until shutdown\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n logger.info('Server listening', { port })\n })\n\n const shutdown = async () => {\n logger.info('Shutting down')\n httpServer.close(() => {\n logger.info('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { toolInputJsonSchema } from "./tool-input-schema.js";
|
|
2
|
-
import { buildMcpToolAnnotations } from "./mcp-tool-annotations.js";
|
|
3
2
|
import { getToolRegistry } from "./tool-registry.js";
|
|
4
3
|
import { executeTool } from "./tool-executor.js";
|
|
5
4
|
import { loadAllModuleTools } from "./tool-loader.js";
|
|
@@ -71,8 +70,7 @@ class InProcessMcpClient {
|
|
|
71
70
|
return accessibleTools.map((tool) => ({
|
|
72
71
|
name: tool.name,
|
|
73
72
|
description: tool.description,
|
|
74
|
-
inputSchema: toolInputJsonSchema(tool.inputSchema)
|
|
75
|
-
annotations: buildMcpToolAnnotations(tool)
|
|
73
|
+
inputSchema: toolInputJsonSchema(tool.inputSchema)
|
|
76
74
|
}));
|
|
77
75
|
}
|
|
78
76
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/in-process-client.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { z } from 'zod'\nimport { toolInputJsonSchema } from './tool-input-schema'\nimport {
|
|
5
|
-
"mappings": "AAEA,SAAS,2BAA2B;AACpC,SAAS
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { z } from 'zod'\nimport { toolInputJsonSchema } from './tool-input-schema'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools } from './tool-loader'\nimport { authenticateMcpRequest, hasRequiredFeatures, type McpAuthSuccess } from './auth'\nimport type { McpToolContext, McpClientInterface, ToolInfo, ToolResult, McpToolDefinition } from './types'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\n/**\n * Options for creating an in-process MCP client.\n */\nexport type InProcessClientOptions = {\n /** API key secret for authentication */\n apiKeySecret: string\n /** DI container */\n container: AwilixContainer\n}\n\n/**\n * Options for creating an in-process MCP client with direct auth context.\n * Used when the caller already has authenticated user context (e.g., from session).\n */\nexport type AuthContextOptions = {\n /** DI container */\n container: AwilixContainer\n /** Pre-authenticated user context */\n authContext: {\n tenantId: string | null\n organizationId: string | null\n userId: string\n userFeatures: string[]\n isSuperAdmin: boolean\n }\n}\n\n/**\n * Tool info with raw Zod schema for AI SDK integration.\n */\nexport type ToolInfoWithSchema = {\n name: string\n description: string\n inputSchema: z.ZodType<unknown>\n}\n\n/**\n * In-process MCP client for direct tool execution.\n *\n * This client executes tools directly without MCP protocol overhead,\n * making it the fastest option when running in the same process as\n * the LLM service.\n *\n * Authentication is still performed via API key to ensure proper\n * ACL filtering of available tools.\n */\nexport class InProcessMcpClient implements McpClientInterface {\n private auth: McpAuthSuccess\n private container: AwilixContainer\n private toolContext: McpToolContext\n private toolsLoaded = false\n\n private constructor(auth: McpAuthSuccess, container: AwilixContainer) {\n this.auth = auth\n this.container = container\n this.toolContext = {\n tenantId: auth.tenantId,\n organizationId: auth.organizationId,\n userId: auth.userId,\n container,\n userFeatures: auth.features,\n isSuperAdmin: auth.isSuperAdmin,\n }\n }\n\n /**\n * Create and authenticate an in-process client using API key.\n */\n static async create(options: InProcessClientOptions): Promise<InProcessMcpClient> {\n const { apiKeySecret, container } = options\n\n const authResult = await authenticateMcpRequest(apiKeySecret, container)\n if (!authResult.success) {\n throw new Error(`Authentication failed: ${authResult.error}`)\n }\n\n return new InProcessMcpClient(authResult, container)\n }\n\n /**\n * Create an in-process client with pre-authenticated context.\n * Use this when you already have user auth context (e.g., from session auth).\n */\n static async createWithAuthContext(options: AuthContextOptions): Promise<InProcessMcpClient> {\n const { container, authContext } = options\n\n // Create a synthetic auth result (no API key lookup needed)\n const syntheticAuth: McpAuthSuccess = {\n success: true,\n keyId: 'session-auth',\n keyName: 'Session Authentication',\n tenantId: authContext.tenantId,\n organizationId: authContext.organizationId,\n userId: authContext.userId,\n features: authContext.userFeatures,\n isSuperAdmin: authContext.isSuperAdmin,\n }\n\n return new InProcessMcpClient(syntheticAuth, container)\n }\n\n /**\n * Ensure tools are loaded from all modules.\n */\n private async ensureToolsLoaded(): Promise<void> {\n if (!this.toolsLoaded) {\n await loadAllModuleTools()\n this.toolsLoaded = true\n }\n }\n\n /**\n * List available tools filtered by API key's permissions.\n * Returns JSON Schema format (for MCP protocol compatibility).\n */\n async listTools(): Promise<ToolInfo[]> {\n await this.ensureToolsLoaded()\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n const rbacService = this.container.resolve<RbacService>('rbacService')\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin, rbacService)\n )\n\n return accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: toolInputJsonSchema(tool.inputSchema),\n }))\n }\n\n /**\n * List available tools with raw Zod schemas.\n * Use this for AI SDK integration which requires Zod schemas.\n */\n async listToolsWithSchemas(): Promise<ToolInfoWithSchema[]> {\n await this.ensureToolsLoaded()\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n const rbacService = this.container.resolve<RbacService>('rbacService')\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, this.auth.features, this.auth.isSuperAdmin, rbacService)\n )\n\n return accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n }))\n }\n\n /**\n * Execute a tool directly.\n */\n async callTool(name: string, args: unknown): Promise<ToolResult> {\n await this.ensureToolsLoaded()\n\n const result = await executeTool(name, args ?? {}, this.toolContext)\n\n return {\n success: result.success,\n result: result.result,\n error: result.error,\n }\n }\n\n /**\n * Close the client (no-op for in-process).\n */\n async close(): Promise<void> {\n // No resources to clean up for in-process client\n }\n\n /**\n * Get the authenticated context info.\n */\n getAuthInfo(): {\n keyId: string\n keyName: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n isSuperAdmin: boolean\n } {\n return {\n keyId: this.auth.keyId,\n keyName: this.auth.keyName,\n tenantId: this.auth.tenantId,\n organizationId: this.auth.organizationId,\n userId: this.auth.userId,\n isSuperAdmin: this.auth.isSuperAdmin,\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-client.ts"],
|
|
4
|
-
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { McpClientInterface, ToolInfo, ToolResult } from './types'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * Options for stdio transport.\n */\nexport type StdioClientOptions = {\n transport: 'stdio'\n /**\n * API key secret. Delivered to the spawned server via the\n * `OPEN_MERCATO_API_KEY` environment variable, never as a command-line\n * argument (argv is world-readable via `ps`/`/proc/<pid>/cmdline`).\n */\n apiKeySecret: string\n /** Command to run (default: 'yarn') */\n command?: string\n /** Arguments for the command (default: mercato ai_assistant mcp:serve, no secret on argv) */\n args?: string[]\n /** Working directory (default: process.cwd()) */\n cwd?: string\n}\n\n/**\n * Options for HTTP transport.\n */\nexport type HttpClientOptions = {\n transport: 'http'\n /** API key secret (sent via x-api-key header) */\n apiKeySecret: string\n /** MCP server URL (e.g., 'http://localhost:3001/mcp') */\n url: string\n}\n\n/**\n * Combined options for McpClient.\n */\nexport type McpClientOptions = StdioClientOptions | HttpClientOptions\n\n/**\n * MCP protocol client for connecting to MCP servers.\n *\n * Supports two transport modes:\n * - stdio: Spawns server as subprocess\n * - http: Connects to HTTP server\n */\nexport class McpClient implements McpClientInterface {\n private client: Client\n private transport: StdioClientTransport | StreamableHTTPClientTransport\n private apiKeySecret: string\n\n private constructor(\n client: Client,\n transport: StdioClientTransport | StreamableHTTPClientTransport,\n apiKeySecret: string\n ) {\n this.client = client\n this.transport = transport\n this.apiKeySecret = apiKeySecret\n }\n\n /**\n * Connect to an MCP server via the specified transport.\n */\n static async connect(options: McpClientOptions): Promise<McpClient> {\n if (options.transport === 'stdio') {\n return McpClient.connectStdio(options)\n } else {\n return McpClient.connectHttp(options)\n }\n }\n\n /**\n * Connect via stdio transport (spawn subprocess).\n */\n private static async connectStdio(options: StdioClientOptions): Promise<McpClient> {\n const command = options.command ?? 'yarn'\n // The API key is passed via OPEN_MERCATO_API_KEY in the child env (below),\n // never on argv \u2014 command-line arguments are readable by any local user.\n const args = options.args ?? [\n 'mercato',\n 'ai_assistant',\n 'mcp:serve',\n ]\n const cwd = options.cwd ?? process.cwd()\n\n const transport = new StdioClientTransport({\n command,\n args,\n cwd,\n env: { ...process.env, OPEN_MERCATO_API_KEY: options.apiKeySecret } as Record<string, string>,\n stderr: 'pipe',\n })\n transport.stderr?.on('data', (data) => {\n const message = data.toString().trim()\n if (message) {\n logger.info('MCP server stderr output', { output: message })\n }\n })\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret)\n }\n\n /**\n * Connect via HTTP transport.\n */\n private static async connectHttp(options: HttpClientOptions): Promise<McpClient> {\n const transport = new StreamableHTTPClientTransport(\n new URL(options.url),\n {\n requestInit: {\n headers: {\n 'x-api-key': options.apiKeySecret,\n },\n },\n }\n )\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret)\n }\n\n /**\n * List available tools from the server.\n */\n async listTools(): Promise<ToolInfo[]> {\n const response = await this.client.listTools()\n\n return response.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? '',\n inputSchema: (tool.inputSchema ?? {}) as Record<string, unknown>,\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC,SAAS,qCAAqC;AAG9C,MAAM,SAAS,aAAa,cAAc;AA4CnC,MAAM,UAAwC;AAAA,EAK3C,YACN,QACA,WACA,cACA;AACA,SAAK,SAAS;AACd,SAAK,YAAY;AACjB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAAQ,SAA+C;AAClE,QAAI,QAAQ,cAAc,SAAS;AACjC,aAAO,UAAU,aAAa,OAAO;AAAA,IACvC,OAAO;AACL,aAAO,UAAU,YAAY,OAAO;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB,aAAa,SAAiD;AACjF,UAAM,UAAU,QAAQ,WAAW;AAGnC,UAAM,OAAO,QAAQ,QAAQ;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AAEvC,UAAM,YAAY,IAAI,qBAAqB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,EAAE,GAAG,QAAQ,KAAK,sBAAsB,QAAQ,aAAa;AAAA,MAClE,QAAQ;AAAA,IACV,CAAC;AACD,cAAU,QAAQ,GAAG,QAAQ,CAAC,SAAS;AACrC,YAAM,UAAU,KAAK,SAAS,EAAE,KAAK;AACrC,UAAI,SAAS;AACX,eAAO,KAAK,4BAA4B,EAAE,QAAQ,QAAQ,CAAC;AAAA,MAC7D;AAAA,IACF,CAAC;AAED,UAAM,SAAS,IAAI;AAAA,MACjB,EAAE,MAAM,uBAAuB,SAAS,QAAQ;AAAA,MAChD,EAAE,cAAc,CAAC,EAAE;AAAA,IACrB;AAEA,UAAM,OAAO,QAAQ,SAAS;AAE9B,WAAO,IAAI,UAAU,QAAQ,WAAW,QAAQ,YAAY;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB,YAAY,SAAgD;AAC/E,UAAM,YAAY,IAAI;AAAA,MACpB,IAAI,IAAI,QAAQ,GAAG;AAAA,MACnB;AAAA,QACE,aAAa;AAAA,UACX,SAAS;AAAA,YACP,aAAa,QAAQ;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,IAAI;AAAA,MACjB,EAAE,MAAM,uBAAuB,SAAS,QAAQ;AAAA,MAChD,EAAE,cAAc,CAAC,EAAE;AAAA,IACrB;AAEA,UAAM,OAAO,QAAQ,SAAS;AAE9B,WAAO,IAAI,UAAU,QAAQ,WAAW,QAAQ,YAAY;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAiC;AACrC,UAAM,WAAW,MAAM,KAAK,OAAO,UAAU;AAE7C,WAAO,SAAS,MAAM,IAAI,CAAC,UAAU;AAAA,MACnC,MAAM,KAAK;AAAA,MACX,aAAa,KAAK,eAAe;AAAA,MACjC,aAAc,KAAK,eAAe,CAAC;AAAA,
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { McpClientInterface, ToolInfo, ToolResult } from './types'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * Options for stdio transport.\n */\nexport type StdioClientOptions = {\n transport: 'stdio'\n /**\n * API key secret. Delivered to the spawned server via the\n * `OPEN_MERCATO_API_KEY` environment variable, never as a command-line\n * argument (argv is world-readable via `ps`/`/proc/<pid>/cmdline`).\n */\n apiKeySecret: string\n /** Command to run (default: 'yarn') */\n command?: string\n /** Arguments for the command (default: mercato ai_assistant mcp:serve, no secret on argv) */\n args?: string[]\n /** Working directory (default: process.cwd()) */\n cwd?: string\n}\n\n/**\n * Options for HTTP transport.\n */\nexport type HttpClientOptions = {\n transport: 'http'\n /** API key secret (sent via x-api-key header) */\n apiKeySecret: string\n /** MCP server URL (e.g., 'http://localhost:3001/mcp') */\n url: string\n}\n\n/**\n * Combined options for McpClient.\n */\nexport type McpClientOptions = StdioClientOptions | HttpClientOptions\n\n/**\n * MCP protocol client for connecting to MCP servers.\n *\n * Supports two transport modes:\n * - stdio: Spawns server as subprocess\n * - http: Connects to HTTP server\n */\nexport class McpClient implements McpClientInterface {\n private client: Client\n private transport: StdioClientTransport | StreamableHTTPClientTransport\n private apiKeySecret: string\n\n private constructor(\n client: Client,\n transport: StdioClientTransport | StreamableHTTPClientTransport,\n apiKeySecret: string\n ) {\n this.client = client\n this.transport = transport\n this.apiKeySecret = apiKeySecret\n }\n\n /**\n * Connect to an MCP server via the specified transport.\n */\n static async connect(options: McpClientOptions): Promise<McpClient> {\n if (options.transport === 'stdio') {\n return McpClient.connectStdio(options)\n } else {\n return McpClient.connectHttp(options)\n }\n }\n\n /**\n * Connect via stdio transport (spawn subprocess).\n */\n private static async connectStdio(options: StdioClientOptions): Promise<McpClient> {\n const command = options.command ?? 'yarn'\n // The API key is passed via OPEN_MERCATO_API_KEY in the child env (below),\n // never on argv \u2014 command-line arguments are readable by any local user.\n const args = options.args ?? [\n 'mercato',\n 'ai_assistant',\n 'mcp:serve',\n ]\n const cwd = options.cwd ?? process.cwd()\n\n const transport = new StdioClientTransport({\n command,\n args,\n cwd,\n env: { ...process.env, OPEN_MERCATO_API_KEY: options.apiKeySecret } as Record<string, string>,\n stderr: 'pipe',\n })\n transport.stderr?.on('data', (data) => {\n const message = data.toString().trim()\n if (message) {\n logger.info('MCP server stderr output', { output: message })\n }\n })\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret)\n }\n\n /**\n * Connect via HTTP transport.\n */\n private static async connectHttp(options: HttpClientOptions): Promise<McpClient> {\n const transport = new StreamableHTTPClientTransport(\n new URL(options.url),\n {\n requestInit: {\n headers: {\n 'x-api-key': options.apiKeySecret,\n },\n },\n }\n )\n\n const client = new Client(\n { name: 'open-mercato-client', version: '0.1.0' },\n { capabilities: {} }\n )\n\n await client.connect(transport)\n\n return new McpClient(client, transport, options.apiKeySecret)\n }\n\n /**\n * List available tools from the server.\n */\n async listTools(): Promise<ToolInfo[]> {\n const response = await this.client.listTools()\n\n return response.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? '',\n inputSchema: (tool.inputSchema ?? {}) as Record<string, unknown>,\n }))\n }\n\n /**\n * Call a tool on the server.\n */\n async callTool(name: string, args: unknown): Promise<ToolResult> {\n try {\n const response = await this.client.callTool({\n name,\n arguments: args as Record<string, unknown>,\n })\n\n // Parse content from response\n const content = response.content\n if (!Array.isArray(content) || content.length === 0) {\n return { success: true, result: null }\n }\n\n const firstContent = content[0]\n if (firstContent.type === 'text') {\n try {\n const parsed = JSON.parse(firstContent.text)\n\n // Check if it's an error response\n if (response.isError || parsed.error) {\n return {\n success: false,\n error: parsed.error ?? 'Unknown error',\n }\n }\n\n return { success: true, result: parsed }\n } catch {\n // Not JSON, return as-is\n return { success: true, result: firstContent.text }\n }\n }\n\n return { success: true, result: content }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return { success: false, error: message }\n }\n }\n\n /**\n * Close the client and release resources.\n */\n async close(): Promise<void> {\n try {\n await this.client.close()\n } catch {\n // Ignore close errors\n }\n\n try {\n await this.transport.close()\n } catch {\n // Ignore close errors\n }\n\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,7 +8,6 @@ import { executeTool } from "./tool-executor.js";
|
|
|
8
8
|
import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
|
|
9
9
|
import { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
|
|
10
10
|
import { jsonSchemaToZod } from "./schema-utils.js";
|
|
11
|
-
import { buildMcpToolAnnotations } from "./mcp-tool-annotations.js";
|
|
12
11
|
import { getApiKeyFromMcpJson } from "./mcp-dev-key-resolution.js";
|
|
13
12
|
const logger = createLogger("ai_assistant");
|
|
14
13
|
const DEFAULT_PORT = 3001;
|
|
@@ -78,8 +77,7 @@ function createDevMcpServer(toolContext, authFeatures, isSuperAdmin, debug) {
|
|
|
78
77
|
tool.name,
|
|
79
78
|
{
|
|
80
79
|
description: tool.description,
|
|
81
|
-
inputSchema: safeSchema
|
|
82
|
-
annotations: buildMcpToolAnnotations(tool)
|
|
80
|
+
inputSchema: safeSchema
|
|
83
81
|
},
|
|
84
82
|
async (args) => {
|
|
85
83
|
const toolArgs = args ?? {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-dev-server.ts"],
|
|
4
|
-
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod } from './schema-utils'\nimport { buildMcpToolAnnotations } from './mcp-tool-annotations'\nimport { getApiKeyFromMcpJson } from './mcp-dev-key-resolution'\nimport type { McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst logger = createLogger('ai_assistant')\n\nconst DEFAULT_PORT = 3001\n\nconst log = (message: string, ...args: unknown[]) => {\n logger.info(message, args.length > 0 ? { details: args.map((arg) => String(arg)).join(' ') } : undefined)\n}\n\n/**\n * Maximum request body size (1MB).\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Create MCP server with tools pre-authenticated for dev use.\n * No session tokens required - uses API key authentication directly.\n */\nfunction createDevMcpServer(\n toolContext: McpToolContext,\n authFeatures: string[],\n isSuperAdmin: boolean,\n debug: boolean\n): McpServer {\n const server = new McpServer(\n { name: 'open-mercato-mcp-dev', version: '0.1.0' },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n // Filter tools based on API key permissions\n const rbacService = toolContext.container.resolve<RbacService>('rbacService')\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, authFeatures, isSuperAdmin, rbacService)\n )\n\n if (debug) {\n log(`Registering ${accessibleTools.length}/${tools.length} tools (filtered by API key permissions)`)\n }\n\n for (const tool of accessibleTools) {\n if (debug) {\n log(`Registering tool: ${tool.name}`)\n }\n\n // Convert Zod schema to safe schema without Date types\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n const converted = jsonSchemaToZod(jsonSchema)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - schema conversion failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n } else {\n safeSchema = z.object({}).passthrough()\n }\n\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n annotations: buildMcpToolAnnotations(tool),\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n if (debug) {\n log(`Calling tool: ${tool.name}`, JSON.stringify(toolArgs))\n }\n\n const result = await executeTool(tool.name, toolArgs, toolContext)\n\n if (!result.success) {\n log(`Tool error: ${result.error}`)\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n }\n )\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - registration failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Development MCP server for Claude Code integration.\n *\n * This server uses HTTP transport and authenticates via the\n * x-api-key header configured in .mcp.json file.\n *\n * Usage:\n * yarn mcp:dev\n *\n * Configure in .mcp.json for Claude Code with HTTP transport.\n */\nexport async function runMcpDevServer(): Promise<void> {\n const apiKey = await getApiKeyFromMcpJson()\n const port = parseInt(process.env.MCP_DEV_PORT ?? '', 10) || DEFAULT_PORT\n const debug = process.env.MCP_DEBUG === 'true'\n\n if (!apiKey) {\n log('Error: API key not found in .mcp.json')\n log('')\n log('To get an API key:')\n log(' 1. Log into Open Mercato as an admin')\n log(' 2. Go to Settings > API Keys')\n log(' 3. Create a new key with the required permissions')\n log('')\n log('Then configure in .mcp.json:')\n log(' {')\n log(' \"mcpServers\": {')\n log(' \"open-mercato\": {')\n log(' \"type\": \"http\",')\n log(' \"url\": \"http://localhost:3001/mcp\",')\n log(' \"headers\": {')\n log(' \"x-api-key\": \"omk_your_api_key_here\"')\n log(' }')\n log(' }')\n log(' }')\n log(' }')\n process.exit(1)\n }\n\n log('Starting development MCP HTTP server...')\n\n // Create DI container\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n\n // Authenticate the API key upfront\n log('Authenticating API key...')\n const authResult = await authenticateMcpRequest(apiKey, container)\n\n if (!authResult.success) {\n log(`Authentication failed: ${authResult.error}`)\n process.exit(1)\n }\n\n log(`Authenticated as: ${authResult.keyName}`)\n log(`Tenant: ${authResult.tenantId ?? '(global)'}`)\n log(`Organization: ${authResult.organizationId ?? '(none)'}`)\n log(`Super admin: ${authResult.isSuperAdmin}`)\n log(`Features: ${authResult.features.length > 0 ? authResult.features.join(', ') : '(none)'}`)\n\n // Load tools\n log('Loading tools...')\n await loadAllModuleTools()\n\n // Generate and cache entity graph\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n log('Generating entity relationship graph...')\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n log(`Entity graph: ${graph.nodes.length} entities, ${graph.edges.length} relationships`)\n } catch (error) {\n log('Entity graph generation skipped:', error instanceof Error ? error.message : error)\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n log('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n log('OpenAPI spec not available')\n }\n } catch (error) {\n log('OpenAPI spec caching skipped:', error instanceof Error ? error.message : error)\n }\n\n // Index tools and entity schemas for search (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n log(`Indexed ${count} entity schemas for discovery`)\n }\n }\n } catch (entityError) {\n log('Entity schema indexing skipped:', entityError instanceof Error ? entityError.message : entityError)\n }\n } catch {\n log('Search indexing skipped (search service not available)')\n }\n\n // Generate a stable session ID for dev mode (enables session memory / caching)\n const { randomBytes } = await import('node:crypto')\n const devSessionId = 'dev_' + randomBytes(8).toString('hex')\n log(`Session ID: ${devSessionId} (stable for this server instance)`)\n\n // Create tool context from auth result\n const toolContext: McpToolContext = {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n userId: authResult.userId,\n container,\n userFeatures: authResult.features,\n isSuperAdmin: authResult.isSuperAdmin,\n apiKeySecret: apiKey,\n sessionId: devSessionId,\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n mode: 'development',\n tools: getToolRegistry().listToolNames().length,\n tenant: authResult.tenantId,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n // Extract and validate API key from header\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate against the configured API key\n if (providedApiKey !== apiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid API key' }))\n return\n }\n\n if (debug) {\n log(`Authenticated request (${req.method})`)\n }\n\n try {\n // Create stateless transport\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create server with pre-authenticated context (no session tokens needed)\n const mcpServer = createDevMcpServer(toolContext, authResult.features, authResult.isSuperAdmin, debug)\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (debug) {\n log(`Request completed, cleaned up`)\n }\n })\n } catch (error) {\n log('Error handling request:', error)\n if (!res.headersSent) {\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n log(`Tools registered: ${toolCount}`)\n log(`Endpoint: http://localhost:${port}/mcp`)\n log(`Health: http://localhost:${port}/health`)\n log(`Mode: Development (API key auth, no session tokens)`)\n\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n log(`Server listening on port ${port}`)\n log('Ready for Claude Code connections')\n })\n\n const shutdown = async () => {\n log('Shutting down...')\n httpServer.close(() => {\n log('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAA+D;AACxE,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAC9C,SAAS,SAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB,2BAA2B;AACxD,SAAS,wBAAwB,0BAA0B,2BAA2B;AACtF,SAAS,uBAAuB;AAChC,SAAS
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod } from './schema-utils'\nimport { getApiKeyFromMcpJson } from './mcp-dev-key-resolution'\nimport type { McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst logger = createLogger('ai_assistant')\n\nconst DEFAULT_PORT = 3001\n\nconst log = (message: string, ...args: unknown[]) => {\n logger.info(message, args.length > 0 ? { details: args.map((arg) => String(arg)).join(' ') } : undefined)\n}\n\n/**\n * Maximum request body size (1MB).\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Create MCP server with tools pre-authenticated for dev use.\n * No session tokens required - uses API key authentication directly.\n */\nfunction createDevMcpServer(\n toolContext: McpToolContext,\n authFeatures: string[],\n isSuperAdmin: boolean,\n debug: boolean\n): McpServer {\n const server = new McpServer(\n { name: 'open-mercato-mcp-dev', version: '0.1.0' },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n // Filter tools based on API key permissions\n const rbacService = toolContext.container.resolve<RbacService>('rbacService')\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, authFeatures, isSuperAdmin, rbacService)\n )\n\n if (debug) {\n log(`Registering ${accessibleTools.length}/${tools.length} tools (filtered by API key permissions)`)\n }\n\n for (const tool of accessibleTools) {\n if (debug) {\n log(`Registering tool: ${tool.name}`)\n }\n\n // Convert Zod schema to safe schema without Date types\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n const converted = jsonSchemaToZod(jsonSchema)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - schema conversion failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n } else {\n safeSchema = z.object({}).passthrough()\n }\n\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n if (debug) {\n log(`Calling tool: ${tool.name}`, JSON.stringify(toolArgs))\n }\n\n const result = await executeTool(tool.name, toolArgs, toolContext)\n\n if (!result.success) {\n log(`Tool error: ${result.error}`)\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n }\n )\n } catch (error) {\n if (debug) {\n log(`Skipping tool ${tool.name} - registration failed:`, error instanceof Error ? error.message : error)\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Development MCP server for Claude Code integration.\n *\n * This server uses HTTP transport and authenticates via the\n * x-api-key header configured in .mcp.json file.\n *\n * Usage:\n * yarn mcp:dev\n *\n * Configure in .mcp.json for Claude Code with HTTP transport.\n */\nexport async function runMcpDevServer(): Promise<void> {\n const apiKey = await getApiKeyFromMcpJson()\n const port = parseInt(process.env.MCP_DEV_PORT ?? '', 10) || DEFAULT_PORT\n const debug = process.env.MCP_DEBUG === 'true'\n\n if (!apiKey) {\n log('Error: API key not found in .mcp.json')\n log('')\n log('To get an API key:')\n log(' 1. Log into Open Mercato as an admin')\n log(' 2. Go to Settings > API Keys')\n log(' 3. Create a new key with the required permissions')\n log('')\n log('Then configure in .mcp.json:')\n log(' {')\n log(' \"mcpServers\": {')\n log(' \"open-mercato\": {')\n log(' \"type\": \"http\",')\n log(' \"url\": \"http://localhost:3001/mcp\",')\n log(' \"headers\": {')\n log(' \"x-api-key\": \"omk_your_api_key_here\"')\n log(' }')\n log(' }')\n log(' }')\n log(' }')\n process.exit(1)\n }\n\n log('Starting development MCP HTTP server...')\n\n // Create DI container\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n\n // Authenticate the API key upfront\n log('Authenticating API key...')\n const authResult = await authenticateMcpRequest(apiKey, container)\n\n if (!authResult.success) {\n log(`Authentication failed: ${authResult.error}`)\n process.exit(1)\n }\n\n log(`Authenticated as: ${authResult.keyName}`)\n log(`Tenant: ${authResult.tenantId ?? '(global)'}`)\n log(`Organization: ${authResult.organizationId ?? '(none)'}`)\n log(`Super admin: ${authResult.isSuperAdmin}`)\n log(`Features: ${authResult.features.length > 0 ? authResult.features.join(', ') : '(none)'}`)\n\n // Load tools\n log('Loading tools...')\n await loadAllModuleTools()\n\n // Generate and cache entity graph\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n log('Generating entity relationship graph...')\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n log(`Entity graph: ${graph.nodes.length} entities, ${graph.edges.length} relationships`)\n } catch (error) {\n log('Entity graph generation skipped:', error instanceof Error ? error.message : error)\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n log('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n log('OpenAPI spec not available')\n }\n } catch (error) {\n log('OpenAPI spec caching skipped:', error instanceof Error ? error.message : error)\n }\n\n // Index tools and entity schemas for search (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n log(`Indexed ${count} entity schemas for discovery`)\n }\n }\n } catch (entityError) {\n log('Entity schema indexing skipped:', entityError instanceof Error ? entityError.message : entityError)\n }\n } catch {\n log('Search indexing skipped (search service not available)')\n }\n\n // Generate a stable session ID for dev mode (enables session memory / caching)\n const { randomBytes } = await import('node:crypto')\n const devSessionId = 'dev_' + randomBytes(8).toString('hex')\n log(`Session ID: ${devSessionId} (stable for this server instance)`)\n\n // Create tool context from auth result\n const toolContext: McpToolContext = {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n userId: authResult.userId,\n container,\n userFeatures: authResult.features,\n isSuperAdmin: authResult.isSuperAdmin,\n apiKeySecret: apiKey,\n sessionId: devSessionId,\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n mode: 'development',\n tools: getToolRegistry().listToolNames().length,\n tenant: authResult.tenantId,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n // Extract and validate API key from header\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate against the configured API key\n if (providedApiKey !== apiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid API key' }))\n return\n }\n\n if (debug) {\n log(`Authenticated request (${req.method})`)\n }\n\n try {\n // Create stateless transport\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create server with pre-authenticated context (no session tokens needed)\n const mcpServer = createDevMcpServer(toolContext, authResult.features, authResult.isSuperAdmin, debug)\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (debug) {\n log(`Request completed, cleaned up`)\n }\n })\n } catch (error) {\n log('Error handling request:', error)\n if (!res.headersSent) {\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n log(`Tools registered: ${toolCount}`)\n log(`Endpoint: http://localhost:${port}/mcp`)\n log(`Health: http://localhost:${port}/health`)\n log(`Mode: Development (API key auth, no session tokens)`)\n\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n log(`Server listening on port ${port}`)\n log('Ready for Claude Code connections')\n })\n\n const shutdown = async () => {\n log('Shutting down...')\n httpServer.close(() => {\n log('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
CallToolRequestSchema
|
|
6
6
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import { toolInputJsonSchema } from "./tool-input-schema.js";
|
|
8
|
-
import { buildMcpToolAnnotations } from "./mcp-tool-annotations.js";
|
|
9
8
|
import { getToolRegistry } from "./tool-registry.js";
|
|
10
9
|
import { executeTool } from "./tool-executor.js";
|
|
11
10
|
import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
|
|
@@ -92,8 +91,7 @@ async function createMcpServer(options) {
|
|
|
92
91
|
tools: accessibleTools.map((tool) => ({
|
|
93
92
|
name: tool.name,
|
|
94
93
|
description: tool.description,
|
|
95
|
-
inputSchema: toolInputJsonSchema(tool.inputSchema)
|
|
96
|
-
annotations: buildMcpToolAnnotations(tool)
|
|
94
|
+
inputSchema: toolInputJsonSchema(tool.inputSchema)
|
|
97
95
|
}))
|
|
98
96
|
};
|
|
99
97
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/mcp-server.ts"],
|
|
4
|
-
"sourcesContent": ["import { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js'\nimport { toolInputJsonSchema } from './tool-input-schema'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,SAAS
|
|
4
|
+
"sourcesContent": ["import { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js'\nimport { toolInputJsonSchema } from './tool-input-schema'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, hasRequiredFeatures } from './auth'\nimport type { McpServerOptions, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\n\nconst formatStderrError = (error: unknown): string =>\n error instanceof Error ? error.stack ?? error.message : String(error)\n\nconst writeStderrLine = (line: string): void => {\n process.stderr.write(`${line}\\n`)\n}\n\n/**\n * Create and configure an MCP server instance.\n */\nexport async function createMcpServer(options: McpServerOptions): Promise<Server> {\n const { config, container, context, allowUnauthenticatedSuperadmin } = options\n\n // Treat empty / whitespace-only secrets as missing so a blank api key cannot\n // fall through into an unauthenticated branch.\n const apiKeySecret =\n typeof options.apiKeySecret === 'string' && options.apiKeySecret.trim().length > 0\n ? options.apiKeySecret\n : undefined\n\n let tenantId: string | null = null\n let organizationId: string | null = null\n let userId: string | null = null\n let userFeatures: string[] = []\n let isSuperAdmin = false\n\n // API key authentication takes precedence\n if (apiKeySecret) {\n const authResult = await authenticateMcpRequest(apiKeySecret, container)\n if (!authResult.success) {\n throw new Error(`API key authentication failed: ${authResult.error}`)\n }\n tenantId = authResult.tenantId\n organizationId = authResult.organizationId\n userId = authResult.userId\n userFeatures = authResult.features\n isSuperAdmin = authResult.isSuperAdmin\n writeStderrLine(`[MCP Server] Authenticated via API key: ${authResult.keyName}`)\n } else if (context && context.userId) {\n // Manual context with a real user \u2014 load that user's ACL.\n tenantId = context.tenantId\n organizationId = context.organizationId\n userId = context.userId\n\n try {\n const rbacService = container.resolve('rbacService') as {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n }>\n }\n const acl = await rbacService.loadAcl(userId, {\n tenantId,\n organizationId,\n })\n userFeatures = acl.features\n isSuperAdmin = acl.isSuperAdmin\n } catch (error) {\n writeStderrLine(`[MCP Server] Failed to load user ACL: ${formatStderrError(error)}`)\n }\n } else if (allowUnauthenticatedSuperadmin) {\n // Explicit, loud dev/testing opt-in. Without a user there is no ACL to load,\n // so the server runs as superadmin with no tenant scoping beyond whatever the\n // caller pinned via `context`. NEVER enable this in production.\n if (context) {\n tenantId = context.tenantId\n organizationId = context.organizationId\n }\n isSuperAdmin = true\n writeStderrLine(\n '[MCP Server] WARNING: allowUnauthenticatedSuperadmin is enabled \u2014 running with UNAUTHENTICATED SUPERADMIN access and no per-user ACL. Do not use this outside local development/testing.'\n )\n } else {\n // Fail closed: refuse to start rather than silently escalating to superadmin.\n throw new Error(\n '[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.'\n )\n }\n\n const toolContext: McpToolContext = {\n tenantId,\n organizationId,\n userId,\n container,\n userFeatures,\n isSuperAdmin,\n apiKeySecret,\n }\n\n const server = new Server(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n // List tools handler\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n // Filter tools based on user permissions\n const rbacService = container.resolve<RbacService>('rbacService')\n const accessibleTools = tools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, userFeatures, isSuperAdmin, rbacService)\n )\n\n if (config.debug) {\n writeStderrLine(\n `[MCP Server] Listing ${accessibleTools.length}/${tools.length} tools (filtered by ACL)`\n )\n }\n\n return {\n tools: accessibleTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: toolInputJsonSchema(tool.inputSchema),\n })),\n }\n })\n\n // Call tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params\n\n if (config.debug) {\n writeStderrLine(`[MCP Server] Calling tool: ${name} argKeys=${Object.keys(args ?? {}).join(',')}`)\n }\n\n const result = await executeTool(name, args ?? {}, toolContext)\n\n if (!result.success) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n })\n\n return server\n}\n\n/**\n * Run MCP server with stdio transport.\n * This keeps the process running until terminated.\n *\n * Supports two authentication modes:\n * 1. API key: Provide `apiKeySecret` option\n * 2. Manual context: Provide `context` with tenant/org/user\n */\nexport async function runMcpServer(options: McpServerOptions): Promise<void> {\n // Generate entity graph for Code Mode search tool\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n writeStderrLine(`[MCP Server] Entity graph: ${graph.nodes.length} entities`)\n } catch (error) {\n writeStderrLine(`[MCP Server] Entity graph skipped: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n // Pre-cache raw OpenAPI spec for Code Mode search tool\n try {\n const { getRawOpenApiSpec } = await import('./api-endpoint-index')\n await getRawOpenApiSpec()\n writeStderrLine('[MCP Server] Raw OpenAPI spec cached for Code Mode')\n } catch (error) {\n writeStderrLine(`[MCP Server] Raw OpenAPI spec caching skipped: ${error instanceof Error ? error.message : String(error)}`)\n }\n\n // Load tools from all modules before starting\n await loadAllModuleTools()\n\n // Index tools for hybrid search discovery (if search service available)\n try {\n const searchService = options.container.resolve('searchService') as SearchService\n await indexToolsForSearch(searchService)\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n writeStderrLine('[MCP Server] Search indexing skipped (search service not available)')\n }\n\n const server = await createMcpServer(options)\n const transport = new StdioServerTransport()\n\n const toolCount = getToolRegistry().listToolNames().length\n\n writeStderrLine(`[MCP Server] Starting ${options.config.name} v${options.config.version}`)\n\n if (options.apiKeySecret && options.apiKeySecret.trim().length > 0) {\n writeStderrLine(`[MCP Server] Authentication: API key`)\n } else if (options.context && options.context.userId) {\n writeStderrLine(`[MCP Server] Tenant: ${options.context.tenantId ?? '(none)'}`)\n writeStderrLine(`[MCP Server] Organization: ${options.context.organizationId ?? '(none)'}`)\n writeStderrLine(`[MCP Server] User: ${options.context.userId}`)\n } else {\n writeStderrLine(`[MCP Server] Authentication: none (unauthenticated superadmin opt-in)`)\n }\n\n writeStderrLine(`[MCP Server] Tools registered: ${toolCount}`)\n\n await server.connect(transport)\n\n writeStderrLine('[MCP Server] Connected and ready for requests')\n\n // Handle shutdown gracefully\n const shutdown = async () => {\n writeStderrLine('[MCP Server] Shutting down...')\n await server.close()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ai-assistant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -99,16 +99,16 @@
|
|
|
99
99
|
"zod-to-json-schema": "^3.25.2"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
|
-
"@open-mercato/shared": "0.
|
|
103
|
-
"@open-mercato/ui": "0.
|
|
102
|
+
"@open-mercato/shared": "0.7.0",
|
|
103
|
+
"@open-mercato/ui": "0.7.0",
|
|
104
104
|
"react": "^19.0.0",
|
|
105
105
|
"react-dom": "^19.0.0",
|
|
106
106
|
"zod": ">=3.23.0"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@open-mercato/cli": "0.
|
|
110
|
-
"@open-mercato/shared": "0.
|
|
111
|
-
"@open-mercato/ui": "0.
|
|
109
|
+
"@open-mercato/cli": "0.7.0",
|
|
110
|
+
"@open-mercato/shared": "0.7.0",
|
|
111
|
+
"@open-mercato/ui": "0.7.0",
|
|
112
112
|
"@types/react": "^19.2.17",
|
|
113
113
|
"@types/react-dom": "^19.2.3",
|
|
114
114
|
"react": "19.2.8",
|
|
@@ -123,6 +123,5 @@
|
|
|
123
123
|
"type": "git",
|
|
124
124
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
125
125
|
"directory": "packages/ai-assistant"
|
|
126
|
-
}
|
|
127
|
-
"stableVersion": "0.6.7"
|
|
126
|
+
}
|
|
128
127
|
}
|
|
@@ -156,15 +156,10 @@ describe('WS-C integration — tool-pack coverage', () => {
|
|
|
156
156
|
|
|
157
157
|
it('propagates tenantId + organizationId to the search service call', async () => {
|
|
158
158
|
const searchMock = jest.fn().mockResolvedValue([])
|
|
159
|
-
const searchIndexerMock = {
|
|
160
|
-
getEntityConfig: (entityId: string) => ({ entityId, aclFeatures: ['ai_assistant.view'], enabled: true } as any),
|
|
161
|
-
getAllEntityConfigs: () => [{ entityId: 'test:entity', aclFeatures: ['ai_assistant.view'], enabled: true } as any],
|
|
162
|
-
}
|
|
163
159
|
const ctx = makeCtx({
|
|
164
160
|
container: {
|
|
165
161
|
resolve: (name: string) => {
|
|
166
162
|
if (name === 'searchService') return { search: searchMock }
|
|
167
|
-
if (name === 'searchIndexer') return searchIndexerMock
|
|
168
163
|
throw new Error(`Unknown registration: ${name}`)
|
|
169
164
|
},
|
|
170
165
|
},
|