@open-mercato/ai-assistant 0.8.1-develop.7207.1.901ef7e47a → 0.8.1-develop.7218.1.9f22882799

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.
@@ -1,2 +1,2 @@
1
- [build:ai-assistant] found 235 entry points
1
+ [build:ai-assistant] found 237 entry points
2
2
  [build:ai-assistant] built successfully
@@ -1,6 +1,8 @@
1
1
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
2
  import { resetServerLoggerCache } from "@open-mercato/shared/lib/logger";
3
+ import { installNextSubpathResolveHook } from "./lib/next-subpath-resolve-hook.js";
3
4
  async function ensureBootstrap() {
5
+ installNextSubpathResolveHook();
4
6
  try {
5
7
  const { getDiRegistrars } = await import("@open-mercato/shared/lib/di/container");
6
8
  getDiRegistrars();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/ai_assistant/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resetServerLoggerCache } from '@open-mercato/shared/lib/logger'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n // stdout carries the MCP JSON-RPC protocol (StdioServerTransport) \u2014 route all\n // facade logs to stderr and rebuild any pino root created earlier in this process.\n process.env.OM_LOG_DESTINATION = 'stderr'\n resetServerLoggerCache()\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst mcpEnsureApiKey: ModuleCli = {\n command: 'mcp:ensure-api-key',\n async run(rest) {\n const args = parseArgs(rest)\n // A bare `--file` (no value) parses to boolean true \u2014 treat it as missing\n // rather than provisioning into a file literally named \"true\".\n const fileArg = typeof args.file === 'string' ? args.file.trim() : ''\n const filePath =\n fileArg || process.env.MCP_SERVER_API_KEY_FILE?.trim() || process.env.MCP_API_KEY_FILE?.trim() || ''\n if (!filePath) {\n console.error('Usage: mercato ai_assistant mcp:ensure-api-key --file <path> [options]')\n console.error('')\n console.error('Ensures a valid MCP server API key exists in the database and that its')\n console.error('plaintext secret is stored in <path> (the idempotency anchor). The secret')\n console.error('is never printed; consumers such as the OpenCode container read the file.')\n console.error('')\n console.error('Options:')\n console.error(' --file <path> Secret file path (or MCP_SERVER_API_KEY_FILE env)')\n console.error(' --name <name> API key name (default __mcp_server__)')\n console.error(' --email <email> Owner user email (default OM_INIT_SUPERADMIN_EMAIL, then superadmin@acme.com)')\n console.error(' --rotate Force rotation even when the file secret is still valid')\n process.exitCode = 1\n return\n }\n\n const keyName = typeof args.name === 'string' && args.name.length > 0 ? args.name : undefined\n const ownerEmail = typeof args.email === 'string' && args.email.length > 0 ? args.email : undefined\n const rotate = args.rotate === true || args.rotate === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const rbac = container.resolve('rbacService') as import('@open-mercato/core/modules/auth/services/rbacService').RbacService\n\n const { ensureMcpApiKey } = await import('./lib/mcp-ensure-api-key')\n const result = await ensureMcpApiKey({ em, filePath, keyName, ownerEmail, rotate, rbac })\n const message = result.status === 'valid' ? 'Existing key is valid' : 'New key created'\n console.log(`[mcp:ensure-api-key] ${message} (id=${result.keyId}, prefix=${result.keyPrefix})`)\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n mcpEnsureApiKey,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resetServerLoggerCache } from '@open-mercato/shared/lib/logger'\nimport { installNextSubpathResolveHook } from './lib/next-subpath-resolve-hook'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // These commands run outside Next.js, but the API route modules the\n // operation runner imports on a tool call say `from 'next/server'`, which\n // plain Node cannot resolve (#6118). Install the retry-with-`.js` hook before\n // anything below can pull a route module in.\n installNextSubpathResolveHook()\n\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n // stdout carries the MCP JSON-RPC protocol (StdioServerTransport) \u2014 route all\n // facade logs to stderr and rebuild any pino root created earlier in this process.\n process.env.OM_LOG_DESTINATION = 'stderr'\n resetServerLoggerCache()\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst mcpEnsureApiKey: ModuleCli = {\n command: 'mcp:ensure-api-key',\n async run(rest) {\n const args = parseArgs(rest)\n // A bare `--file` (no value) parses to boolean true \u2014 treat it as missing\n // rather than provisioning into a file literally named \"true\".\n const fileArg = typeof args.file === 'string' ? args.file.trim() : ''\n const filePath =\n fileArg || process.env.MCP_SERVER_API_KEY_FILE?.trim() || process.env.MCP_API_KEY_FILE?.trim() || ''\n if (!filePath) {\n console.error('Usage: mercato ai_assistant mcp:ensure-api-key --file <path> [options]')\n console.error('')\n console.error('Ensures a valid MCP server API key exists in the database and that its')\n console.error('plaintext secret is stored in <path> (the idempotency anchor). The secret')\n console.error('is never printed; consumers such as the OpenCode container read the file.')\n console.error('')\n console.error('Options:')\n console.error(' --file <path> Secret file path (or MCP_SERVER_API_KEY_FILE env)')\n console.error(' --name <name> API key name (default __mcp_server__)')\n console.error(' --email <email> Owner user email (default OM_INIT_SUPERADMIN_EMAIL, then superadmin@acme.com)')\n console.error(' --rotate Force rotation even when the file secret is still valid')\n process.exitCode = 1\n return\n }\n\n const keyName = typeof args.name === 'string' && args.name.length > 0 ? args.name : undefined\n const ownerEmail = typeof args.email === 'string' && args.email.length > 0 ? args.email : undefined\n const rotate = args.rotate === true || args.rotate === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const rbac = container.resolve('rbacService') as import('@open-mercato/core/modules/auth/services/rbacService').RbacService\n\n const { ensureMcpApiKey } = await import('./lib/mcp-ensure-api-key')\n const result = await ensureMcpApiKey({ em, filePath, keyName, ownerEmail, rotate, rbac })\n const message = result.status === 'valid' ? 'Existing key is valid' : 'New key created'\n console.log(`[mcp:ensure-api-key] ${message} (id=${result.keyId}, prefix=${result.keyPrefix})`)\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n mcpEnsureApiKey,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
5
+ "mappings": "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",
6
6
  "names": ["filteredGraph"]
7
7
  }
@@ -0,0 +1,6 @@
1
+ import { resolveNextSubpathAsync } from "./next-subpath-resolve-hook.js";
2
+ const resolve = resolveNextSubpathAsync;
3
+ export {
4
+ resolve
5
+ };
6
+ //# sourceMappingURL=next-subpath-resolve-hook-worker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/next-subpath-resolve-hook-worker.ts"],
4
+ "sourcesContent": ["import { resolveNextSubpathAsync } from './next-subpath-resolve-hook'\n\nexport const resolve = resolveNextSubpathAsync\n"],
5
+ "mappings": "AAAA,SAAS,+BAA+B;AAEjC,MAAM,UAAU;",
6
+ "names": []
7
+ }
@@ -0,0 +1,62 @@
1
+ import * as nodeModule from "node:module";
2
+ const NEXT_BARE_SUBPATH = /^next\/[A-Za-z0-9_-]+$/;
3
+ let installed = false;
4
+ function isModuleNotFound(error) {
5
+ return Boolean(error) && typeof error === "object" && error.code === "ERR_MODULE_NOT_FOUND";
6
+ }
7
+ function resolveNextSubpath(specifier, context, nextResolve) {
8
+ if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context);
9
+ try {
10
+ return nextResolve(specifier, context);
11
+ } catch (error) {
12
+ if (!isModuleNotFound(error)) throw error;
13
+ try {
14
+ return nextResolve(`${specifier}.js`, context);
15
+ } catch {
16
+ throw error;
17
+ }
18
+ }
19
+ }
20
+ async function resolveNextSubpathAsync(specifier, context, nextResolve) {
21
+ if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context);
22
+ try {
23
+ return await nextResolve(specifier, context);
24
+ } catch (error) {
25
+ if (!isModuleNotFound(error)) throw error;
26
+ try {
27
+ return await nextResolve(`${specifier}.js`, context);
28
+ } catch {
29
+ throw error;
30
+ }
31
+ }
32
+ }
33
+ function runtimeRegisterHooks() {
34
+ const candidate = nodeModule.registerHooks;
35
+ return typeof candidate === "function" ? candidate : void 0;
36
+ }
37
+ function runtimeRegisterHookModule() {
38
+ const candidate = nodeModule.register;
39
+ return typeof candidate === "function" ? candidate : void 0;
40
+ }
41
+ function installNextSubpathResolveHook(registerHooks = runtimeRegisterHooks(), registerHookModule = runtimeRegisterHookModule()) {
42
+ if (installed) return false;
43
+ if (typeof registerHooks === "function") {
44
+ registerHooks({ resolve: resolveNextSubpath });
45
+ } else if (typeof registerHookModule === "function") {
46
+ registerHookModule(new URL("./next-subpath-resolve-hook-worker.js", import.meta.url), import.meta.url);
47
+ } else {
48
+ return false;
49
+ }
50
+ installed = true;
51
+ return true;
52
+ }
53
+ function resetNextSubpathResolveHookForTests() {
54
+ installed = false;
55
+ }
56
+ export {
57
+ installNextSubpathResolveHook,
58
+ resetNextSubpathResolveHookForTests,
59
+ resolveNextSubpath,
60
+ resolveNextSubpathAsync
61
+ };
62
+ //# sourceMappingURL=next-subpath-resolve-hook.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/next-subpath-resolve-hook.ts"],
4
+ "sourcesContent": ["/**\n * Node ESM resolution for `next/<subpath>` in the standalone MCP processes (#6118).\n *\n * `mercato ai_assistant mcp:serve-http` (and `mcp:serve`, `mcp:dev`,\n * `mcp:list-tools`) run in plain Node, and the AI API operation runner loads\n * API route modules with a runtime `import()`. Those modules import\n * `NextResponse` from `'next/server'`: a bare package subpath that only a\n * bundler resolves, because `next` ships no `exports` map and Node's ESM\n * resolver does no extension guessing. Every route with that import (287 of\n * them on the 0.7.0 packages) therefore fails to load with\n * `ERR_MODULE_NOT_FOUND \u2026 node_modules/next/server`, and every tool backed by\n * such a route returns \"Failed to load route module\".\n *\n * `next/server.js`, `next/headers.js`, `next/navigation.js` \u2026 do exist as files,\n * which is why `@open-mercato/shared` imports `'next/headers.js'`. Rather than\n * rewriting several hundred imports across every package (and every\n * third-party module), this hook retries a failed bare `next/<subpath>` with\n * the `.js` suffix. It only runs in the processes that install it, only for\n * `next/*` specifiers, and only after the normal resolution failed, so it\n * becomes a no-op the day `next` publishes an `exports` map.\n */\nimport * as nodeModule from 'node:module'\n\ntype ResolveContext = Record<string, unknown>\ntype ResolveResult = { url: string; format?: string | null | undefined; shortCircuit?: boolean }\ntype NextResolve = (specifier: string, context?: ResolveContext) => ResolveResult\ntype AsyncNextResolve = (specifier: string, context?: ResolveContext) => ResolveResult | Promise<ResolveResult>\n\n/** Bare `next/<one segment>` specifiers, e.g. `next/server`, `next/headers`. */\nconst NEXT_BARE_SUBPATH = /^next\\/[A-Za-z0-9_-]+$/\n\nlet installed = false\n\nfunction isModuleNotFound(error: unknown): boolean {\n return Boolean(error) && typeof error === 'object' && (error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND'\n}\n\n/**\n * The resolve hook itself. Exported so it can be tested without touching the\n * process-global hook chain.\n */\nexport function resolveNextSubpath(\n specifier: string,\n context: ResolveContext,\n nextResolve: NextResolve,\n): ResolveResult {\n if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context)\n try {\n return nextResolve(specifier, context)\n } catch (error) {\n if (!isModuleNotFound(error)) throw error\n try {\n return nextResolve(`${specifier}.js`, context)\n } catch {\n // Neither form resolves (Next is not installed at all): surface the\n // original error, which names the specifier the caller wrote.\n throw error\n }\n }\n}\n\nexport async function resolveNextSubpathAsync(\n specifier: string,\n context: ResolveContext,\n nextResolve: AsyncNextResolve,\n): Promise<ResolveResult> {\n if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context)\n try {\n return await nextResolve(specifier, context)\n } catch (error) {\n if (!isModuleNotFound(error)) throw error\n try {\n return await nextResolve(`${specifier}.js`, context)\n } catch {\n throw error\n }\n }\n}\n\ntype RegisterHooks = (hooks: { resolve: typeof resolveNextSubpath }) => unknown\ntype RegisterHookModule = (specifier: string | URL, parentURL?: string | URL) => unknown\n\n/**\n * `module.registerHooks` exists from Node 22.15. It is read off the namespace\n * rather than imported by name: in the published ESM build a named import of an\n * export the running Node lacks fails while the module links, before the check\n * in `installNextSubpathResolveHook` can run, and the package still declares\n * `node >= 22.0.0`.\n */\nfunction runtimeRegisterHooks(): RegisterHooks | undefined {\n const candidate = (nodeModule as unknown as { registerHooks?: unknown }).registerHooks\n return typeof candidate === 'function' ? (candidate as RegisterHooks) : undefined\n}\n\nfunction runtimeRegisterHookModule(): RegisterHookModule | undefined {\n const candidate = (nodeModule as unknown as { register?: unknown }).register\n return typeof candidate === 'function' ? (candidate as RegisterHookModule) : undefined\n}\n\n/**\n * Install the hook once per process. Returns `true` when it was installed by\n * this call, `false` when it already was or when the runtime has neither\n * registration API. Node 22.15+ uses the synchronous hook API; earlier\n * supported Node 22 releases use the asynchronous hook API.\n */\nexport function installNextSubpathResolveHook(\n registerHooks: RegisterHooks | null | undefined = runtimeRegisterHooks(),\n registerHookModule: RegisterHookModule | null | undefined = runtimeRegisterHookModule(),\n): boolean {\n if (installed) return false\n if (typeof registerHooks === 'function') {\n registerHooks({ resolve: resolveNextSubpath })\n } else if (typeof registerHookModule === 'function') {\n registerHookModule(new URL('./next-subpath-resolve-hook-worker.js', import.meta.url), import.meta.url)\n } else {\n return false\n }\n installed = true\n return true\n}\n\n/** Test seam: forget that the hook was installed. */\nexport function resetNextSubpathResolveHookForTests(): void {\n installed = false\n}\n"],
5
+ "mappings": "AAqBA,YAAY,gBAAgB;AAQ5B,MAAM,oBAAoB;AAE1B,IAAI,YAAY;AAEhB,SAAS,iBAAiB,OAAyB;AACjD,SAAO,QAAQ,KAAK,KAAK,OAAO,UAAU,YAAa,MAA6B,SAAS;AAC/F;AAMO,SAAS,mBACd,WACA,SACA,aACe;AACf,MAAI,CAAC,kBAAkB,KAAK,SAAS,EAAG,QAAO,YAAY,WAAW,OAAO;AAC7E,MAAI;AACF,WAAO,YAAY,WAAW,OAAO;AAAA,EACvC,SAAS,OAAO;AACd,QAAI,CAAC,iBAAiB,KAAK,EAAG,OAAM;AACpC,QAAI;AACF,aAAO,YAAY,GAAG,SAAS,OAAO,OAAO;AAAA,IAC/C,QAAQ;AAGN,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,eAAsB,wBACpB,WACA,SACA,aACwB;AACxB,MAAI,CAAC,kBAAkB,KAAK,SAAS,EAAG,QAAO,YAAY,WAAW,OAAO;AAC7E,MAAI;AACF,WAAO,MAAM,YAAY,WAAW,OAAO;AAAA,EAC7C,SAAS,OAAO;AACd,QAAI,CAAC,iBAAiB,KAAK,EAAG,OAAM;AACpC,QAAI;AACF,aAAO,MAAM,YAAY,GAAG,SAAS,OAAO,OAAO;AAAA,IACrD,QAAQ;AACN,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAYA,SAAS,uBAAkD;AACzD,QAAM,YAAa,WAAsD;AACzE,SAAO,OAAO,cAAc,aAAc,YAA8B;AAC1E;AAEA,SAAS,4BAA4D;AACnE,QAAM,YAAa,WAAiD;AACpE,SAAO,OAAO,cAAc,aAAc,YAAmC;AAC/E;AAQO,SAAS,8BACd,gBAAkD,qBAAqB,GACvE,qBAA4D,0BAA0B,GAC7E;AACT,MAAI,UAAW,QAAO;AACtB,MAAI,OAAO,kBAAkB,YAAY;AACvC,kBAAc,EAAE,SAAS,mBAAmB,CAAC;AAAA,EAC/C,WAAW,OAAO,uBAAuB,YAAY;AACnD,uBAAmB,IAAI,IAAI,yCAAyC,YAAY,GAAG,GAAG,YAAY,GAAG;AAAA,EACvG,OAAO;AACL,WAAO;AAAA,EACT;AACA,cAAY;AACZ,SAAO;AACT;AAGO,SAAS,sCAA4C;AAC1D,cAAY;AACd;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ai-assistant",
3
- "version": "0.8.1-develop.7207.1.901ef7e47a",
3
+ "version": "0.8.1-develop.7218.1.9f22882799",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -100,16 +100,16 @@
100
100
  "zod-to-json-schema": "^3.25.2"
101
101
  },
102
102
  "peerDependencies": {
103
- "@open-mercato/shared": "0.8.1-develop.7207.1.901ef7e47a",
104
- "@open-mercato/ui": "0.8.1-develop.7207.1.901ef7e47a",
103
+ "@open-mercato/shared": "0.8.1-develop.7218.1.9f22882799",
104
+ "@open-mercato/ui": "0.8.1-develop.7218.1.9f22882799",
105
105
  "react": "^19.0.0",
106
106
  "react-dom": "^19.0.0",
107
107
  "zod": ">=3.23.0"
108
108
  },
109
109
  "devDependencies": {
110
- "@open-mercato/cli": "0.8.1-develop.7207.1.901ef7e47a",
111
- "@open-mercato/shared": "0.8.1-develop.7207.1.901ef7e47a",
112
- "@open-mercato/ui": "0.8.1-develop.7207.1.901ef7e47a",
110
+ "@open-mercato/cli": "0.8.1-develop.7218.1.9f22882799",
111
+ "@open-mercato/shared": "0.8.1-develop.7218.1.9f22882799",
112
+ "@open-mercato/ui": "0.8.1-develop.7218.1.9f22882799",
113
113
  "@types/react": "^19.2.18",
114
114
  "@types/react-dom": "^19.2.5",
115
115
  "react": "19.2.8",
@@ -0,0 +1,42 @@
1
+ import type { ModuleCli } from '@open-mercato/shared/modules/registry'
2
+ import cliCommands from '../cli'
3
+
4
+ const installHookMock = jest.fn(() => true)
5
+ const runMcpHttpServerMock = jest.fn(async () => undefined)
6
+
7
+ jest.mock('@open-mercato/shared/lib/di/container', () => ({
8
+ createRequestContainer: jest.fn(async () => ({})),
9
+ getDiRegistrars: jest.fn(() => []),
10
+ }))
11
+
12
+ jest.mock('../lib/next-subpath-resolve-hook', () => ({
13
+ installNextSubpathResolveHook: (...args: unknown[]) => installHookMock(...args),
14
+ }))
15
+
16
+ jest.mock('../lib/http-server', () => ({
17
+ runMcpHttpServer: (...args: unknown[]) => runMcpHttpServerMock(...args),
18
+ }))
19
+
20
+ function getCommand(name: string): ModuleCli {
21
+ const cmd = (cliCommands as ModuleCli[]).find((c) => c.command === name)
22
+ if (!cmd) throw new Error(`${name} command not found`)
23
+ return cmd
24
+ }
25
+
26
+ describe('standalone MCP commands install the next/<subpath> resolve hook (#6118)', () => {
27
+ beforeEach(() => {
28
+ jest.clearAllMocks()
29
+ })
30
+
31
+ it('installs the hook before serving over HTTP', async () => {
32
+ await getCommand('mcp:serve-http').run(['--port', '3999'])
33
+
34
+ expect(installHookMock).toHaveBeenCalledTimes(1)
35
+ expect(runMcpHttpServerMock).toHaveBeenCalledTimes(1)
36
+ // The hook must be in place before the server (and the first tool call)
37
+ // can import a route module.
38
+ expect(installHookMock.mock.invocationCallOrder[0]).toBeLessThan(
39
+ runMcpHttpServerMock.mock.invocationCallOrder[0],
40
+ )
41
+ })
42
+ })
@@ -1,12 +1,19 @@
1
1
  import type { ModuleCli } from '@open-mercato/shared/modules/registry'
2
2
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
3
3
  import { resetServerLoggerCache } from '@open-mercato/shared/lib/logger'
4
+ import { installNextSubpathResolveHook } from './lib/next-subpath-resolve-hook'
4
5
  /**
5
6
  * Ensure app bootstrap is called before creating DI container.
6
7
  * Uses the shared generated-bootstrap loader so the command works both from
7
8
  * the monorepo app and from standalone apps installed through npm packages.
8
9
  */
9
10
  async function ensureBootstrap(): Promise<void> {
11
+ // These commands run outside Next.js, but the API route modules the
12
+ // operation runner imports on a tool call say `from 'next/server'`, which
13
+ // plain Node cannot resolve (#6118). Install the retry-with-`.js` hook before
14
+ // anything below can pull a route module in.
15
+ installNextSubpathResolveHook()
16
+
10
17
  // First check if DI is already available
11
18
  try {
12
19
  const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')
@@ -0,0 +1,174 @@
1
+ import {
2
+ installNextSubpathResolveHook,
3
+ resetNextSubpathResolveHookForTests,
4
+ resolveNextSubpath,
5
+ resolveNextSubpathAsync,
6
+ } from '../next-subpath-resolve-hook'
7
+
8
+ function notFound(specifier: string): Error & { code: string } {
9
+ return Object.assign(new Error(`Cannot find module '${specifier}'`), { code: 'ERR_MODULE_NOT_FOUND' })
10
+ }
11
+
12
+ describe('resolveNextSubpath (#6118)', () => {
13
+ const context = { parentURL: 'file:///app/route.js' }
14
+
15
+ it('leaves specifiers other than next/<subpath> to the default resolver', () => {
16
+ const nextResolve = jest.fn(() => ({ url: 'file:///app/node_modules/zod/index.js' }))
17
+
18
+ const result = resolveNextSubpath('zod', context, nextResolve)
19
+
20
+ expect(result.url).toBe('file:///app/node_modules/zod/index.js')
21
+ expect(nextResolve).toHaveBeenCalledTimes(1)
22
+ expect(nextResolve).toHaveBeenCalledWith('zod', context)
23
+ })
24
+
25
+ it('does not touch a next/<subpath> that resolves on its own', () => {
26
+ // The day `next` ships an exports map the hook must step aside.
27
+ const nextResolve = jest.fn(() => ({ url: 'file:///app/node_modules/next/server.js' }))
28
+
29
+ resolveNextSubpath('next/server', context, nextResolve)
30
+
31
+ expect(nextResolve).toHaveBeenCalledTimes(1)
32
+ })
33
+
34
+ it('retries a bare next/<subpath> with the .js suffix when Node cannot resolve it', () => {
35
+ // `next` has no exports map and Node ESM does no extension guessing, so
36
+ // `next/server` fails while `next/server.js` is a real file.
37
+ const nextResolve = jest
38
+ .fn()
39
+ .mockImplementationOnce(() => { throw notFound('/app/node_modules/next/server') })
40
+ .mockImplementationOnce(() => ({ url: 'file:///app/node_modules/next/server.js' }))
41
+
42
+ const result = resolveNextSubpath('next/server', context, nextResolve)
43
+
44
+ expect(result.url).toBe('file:///app/node_modules/next/server.js')
45
+ expect(nextResolve).toHaveBeenNthCalledWith(1, 'next/server', context)
46
+ expect(nextResolve).toHaveBeenNthCalledWith(2, 'next/server.js', context)
47
+ })
48
+
49
+ it('covers the other subpaths route modules import', () => {
50
+ for (const subpath of ['next/headers', 'next/navigation', 'next/cache']) {
51
+ const nextResolve = jest
52
+ .fn()
53
+ .mockImplementationOnce(() => { throw notFound(subpath) })
54
+ .mockImplementationOnce((resolvedSpecifier: string) => ({
55
+ url: `file:///app/node_modules/${resolvedSpecifier}`,
56
+ }))
57
+ expect(resolveNextSubpath(subpath, context, nextResolve).url).toBe(`file:///app/node_modules/${subpath}.js`)
58
+ }
59
+ })
60
+
61
+ it('does not retry deeper or already-suffixed specifiers', () => {
62
+ for (const specifier of ['next/server.js', 'next/dist/server/web/spec-extension/response', 'next']) {
63
+ const nextResolve = jest.fn(() => { throw notFound(specifier) })
64
+ expect(() => resolveNextSubpath(specifier, context, nextResolve)).toThrow(/Cannot find module/)
65
+ expect(nextResolve).toHaveBeenCalledTimes(1)
66
+ }
67
+ })
68
+
69
+ it('rethrows the original error when the .js form is missing too', () => {
70
+ const original = notFound('/app/node_modules/next/server')
71
+ const nextResolve = jest
72
+ .fn()
73
+ .mockImplementationOnce(() => { throw original })
74
+ .mockImplementationOnce(() => { throw notFound('/app/node_modules/next/server.js') })
75
+
76
+ expect(() => resolveNextSubpath('next/server', context, nextResolve)).toThrow(original)
77
+ })
78
+
79
+ it('rethrows errors that are not ERR_MODULE_NOT_FOUND without retrying', () => {
80
+ const syntaxError = Object.assign(new Error('bad package.json'), { code: 'ERR_INVALID_PACKAGE_CONFIG' })
81
+ const nextResolve = jest.fn(() => { throw syntaxError })
82
+
83
+ expect(() => resolveNextSubpath('next/server', context, nextResolve)).toThrow(syntaxError)
84
+ expect(nextResolve).toHaveBeenCalledTimes(1)
85
+ })
86
+ })
87
+
88
+ describe('installNextSubpathResolveHook', () => {
89
+ beforeEach(() => {
90
+ resetNextSubpathResolveHookForTests()
91
+ })
92
+
93
+ it('registers the resolve hook once per process', () => {
94
+ const register = jest.fn()
95
+
96
+ expect(installNextSubpathResolveHook(register, null)).toBe(true)
97
+ expect(installNextSubpathResolveHook(register, null)).toBe(false)
98
+
99
+ expect(register).toHaveBeenCalledTimes(1)
100
+ expect(register).toHaveBeenCalledWith({ resolve: resolveNextSubpath })
101
+ })
102
+
103
+ it('registers an asynchronous hook on Node 22.0-22.14', () => {
104
+ const registerHookModule = jest.fn()
105
+
106
+ expect(installNextSubpathResolveHook(null, registerHookModule)).toBe(true)
107
+ expect(registerHookModule).toHaveBeenCalledWith(
108
+ expect.objectContaining({ href: expect.stringMatching(/next-subpath-resolve-hook-worker\.js$/) }),
109
+ expect.stringMatching(/^file:/),
110
+ )
111
+ })
112
+
113
+ it('is a no-op on a runtime without either registration API', () => {
114
+ expect(installNextSubpathResolveHook(null, null)).toBe(false)
115
+ })
116
+ })
117
+
118
+ describe('resolveNextSubpathAsync', () => {
119
+ const context = { parentURL: 'file:///app/route.js' }
120
+
121
+ it('retries a bare next/<subpath> with the .js suffix', async () => {
122
+ const nextResolve = jest
123
+ .fn()
124
+ .mockRejectedValueOnce(notFound('/app/node_modules/next/server'))
125
+ .mockResolvedValueOnce({ url: 'file:///app/node_modules/next/server.js' })
126
+
127
+ const result = await resolveNextSubpathAsync('next/server', context, nextResolve)
128
+
129
+ expect(result.url).toBe('file:///app/node_modules/next/server.js')
130
+ expect(nextResolve).toHaveBeenNthCalledWith(1, 'next/server', context)
131
+ expect(nextResolve).toHaveBeenNthCalledWith(2, 'next/server.js', context)
132
+ })
133
+ })
134
+
135
+ describe('installNextSubpathResolveHook against the runtime node:module', () => {
136
+ afterEach(() => {
137
+ jest.dontMock('node:module')
138
+ jest.resetModules()
139
+ })
140
+
141
+ it('installs through node:module.register on Node 22.0-22.14', () => {
142
+ // A named import of `registerHooks` would throw while the module loads on
143
+ // such a runtime; the helper must load and use the older registration API.
144
+ jest.resetModules()
145
+ const register = jest.fn()
146
+ jest.doMock('node:module', () => ({ register }))
147
+ let installed: boolean | undefined
148
+ jest.isolateModules(() => {
149
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
150
+ const hookModule = require('../next-subpath-resolve-hook') as typeof import('../next-subpath-resolve-hook')
151
+ installed = hookModule.installNextSubpathResolveHook()
152
+ })
153
+ expect(installed).toBe(true)
154
+ expect(register).toHaveBeenCalledTimes(1)
155
+ expect(register.mock.calls[0][0]).toEqual(
156
+ expect.objectContaining({ href: expect.stringMatching(/next-subpath-resolve-hook-worker\.js$/) }),
157
+ )
158
+ })
159
+
160
+ it('installs through node:module.registerHooks when the runtime has it', () => {
161
+ jest.resetModules()
162
+ const register = jest.fn()
163
+ jest.doMock('node:module', () => ({ registerHooks: register }))
164
+ let installed: boolean | undefined
165
+ jest.isolateModules(() => {
166
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
167
+ const hookModule = require('../next-subpath-resolve-hook') as typeof import('../next-subpath-resolve-hook')
168
+ installed = hookModule.installNextSubpathResolveHook()
169
+ })
170
+ expect(installed).toBe(true)
171
+ expect(register).toHaveBeenCalledTimes(1)
172
+ expect(register.mock.calls[0][0]).toEqual({ resolve: expect.any(Function) })
173
+ })
174
+ })
@@ -0,0 +1,3 @@
1
+ import { resolveNextSubpathAsync } from './next-subpath-resolve-hook'
2
+
3
+ export const resolve = resolveNextSubpathAsync
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Node ESM resolution for `next/<subpath>` in the standalone MCP processes (#6118).
3
+ *
4
+ * `mercato ai_assistant mcp:serve-http` (and `mcp:serve`, `mcp:dev`,
5
+ * `mcp:list-tools`) run in plain Node, and the AI API operation runner loads
6
+ * API route modules with a runtime `import()`. Those modules import
7
+ * `NextResponse` from `'next/server'`: a bare package subpath that only a
8
+ * bundler resolves, because `next` ships no `exports` map and Node's ESM
9
+ * resolver does no extension guessing. Every route with that import (287 of
10
+ * them on the 0.7.0 packages) therefore fails to load with
11
+ * `ERR_MODULE_NOT_FOUND … node_modules/next/server`, and every tool backed by
12
+ * such a route returns "Failed to load route module".
13
+ *
14
+ * `next/server.js`, `next/headers.js`, `next/navigation.js` … do exist as files,
15
+ * which is why `@open-mercato/shared` imports `'next/headers.js'`. Rather than
16
+ * rewriting several hundred imports across every package (and every
17
+ * third-party module), this hook retries a failed bare `next/<subpath>` with
18
+ * the `.js` suffix. It only runs in the processes that install it, only for
19
+ * `next/*` specifiers, and only after the normal resolution failed, so it
20
+ * becomes a no-op the day `next` publishes an `exports` map.
21
+ */
22
+ import * as nodeModule from 'node:module'
23
+
24
+ type ResolveContext = Record<string, unknown>
25
+ type ResolveResult = { url: string; format?: string | null | undefined; shortCircuit?: boolean }
26
+ type NextResolve = (specifier: string, context?: ResolveContext) => ResolveResult
27
+ type AsyncNextResolve = (specifier: string, context?: ResolveContext) => ResolveResult | Promise<ResolveResult>
28
+
29
+ /** Bare `next/<one segment>` specifiers, e.g. `next/server`, `next/headers`. */
30
+ const NEXT_BARE_SUBPATH = /^next\/[A-Za-z0-9_-]+$/
31
+
32
+ let installed = false
33
+
34
+ function isModuleNotFound(error: unknown): boolean {
35
+ return Boolean(error) && typeof error === 'object' && (error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND'
36
+ }
37
+
38
+ /**
39
+ * The resolve hook itself. Exported so it can be tested without touching the
40
+ * process-global hook chain.
41
+ */
42
+ export function resolveNextSubpath(
43
+ specifier: string,
44
+ context: ResolveContext,
45
+ nextResolve: NextResolve,
46
+ ): ResolveResult {
47
+ if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context)
48
+ try {
49
+ return nextResolve(specifier, context)
50
+ } catch (error) {
51
+ if (!isModuleNotFound(error)) throw error
52
+ try {
53
+ return nextResolve(`${specifier}.js`, context)
54
+ } catch {
55
+ // Neither form resolves (Next is not installed at all): surface the
56
+ // original error, which names the specifier the caller wrote.
57
+ throw error
58
+ }
59
+ }
60
+ }
61
+
62
+ export async function resolveNextSubpathAsync(
63
+ specifier: string,
64
+ context: ResolveContext,
65
+ nextResolve: AsyncNextResolve,
66
+ ): Promise<ResolveResult> {
67
+ if (!NEXT_BARE_SUBPATH.test(specifier)) return nextResolve(specifier, context)
68
+ try {
69
+ return await nextResolve(specifier, context)
70
+ } catch (error) {
71
+ if (!isModuleNotFound(error)) throw error
72
+ try {
73
+ return await nextResolve(`${specifier}.js`, context)
74
+ } catch {
75
+ throw error
76
+ }
77
+ }
78
+ }
79
+
80
+ type RegisterHooks = (hooks: { resolve: typeof resolveNextSubpath }) => unknown
81
+ type RegisterHookModule = (specifier: string | URL, parentURL?: string | URL) => unknown
82
+
83
+ /**
84
+ * `module.registerHooks` exists from Node 22.15. It is read off the namespace
85
+ * rather than imported by name: in the published ESM build a named import of an
86
+ * export the running Node lacks fails while the module links, before the check
87
+ * in `installNextSubpathResolveHook` can run, and the package still declares
88
+ * `node >= 22.0.0`.
89
+ */
90
+ function runtimeRegisterHooks(): RegisterHooks | undefined {
91
+ const candidate = (nodeModule as unknown as { registerHooks?: unknown }).registerHooks
92
+ return typeof candidate === 'function' ? (candidate as RegisterHooks) : undefined
93
+ }
94
+
95
+ function runtimeRegisterHookModule(): RegisterHookModule | undefined {
96
+ const candidate = (nodeModule as unknown as { register?: unknown }).register
97
+ return typeof candidate === 'function' ? (candidate as RegisterHookModule) : undefined
98
+ }
99
+
100
+ /**
101
+ * Install the hook once per process. Returns `true` when it was installed by
102
+ * this call, `false` when it already was or when the runtime has neither
103
+ * registration API. Node 22.15+ uses the synchronous hook API; earlier
104
+ * supported Node 22 releases use the asynchronous hook API.
105
+ */
106
+ export function installNextSubpathResolveHook(
107
+ registerHooks: RegisterHooks | null | undefined = runtimeRegisterHooks(),
108
+ registerHookModule: RegisterHookModule | null | undefined = runtimeRegisterHookModule(),
109
+ ): boolean {
110
+ if (installed) return false
111
+ if (typeof registerHooks === 'function') {
112
+ registerHooks({ resolve: resolveNextSubpath })
113
+ } else if (typeof registerHookModule === 'function') {
114
+ registerHookModule(new URL('./next-subpath-resolve-hook-worker.js', import.meta.url), import.meta.url)
115
+ } else {
116
+ return false
117
+ }
118
+ installed = true
119
+ return true
120
+ }
121
+
122
+ /** Test seam: forget that the hook was installed. */
123
+ export function resetNextSubpathResolveHookForTests(): void {
124
+ installed = false
125
+ }