@open-mercato/ai-assistant 0.6.8-develop.6931.1.4a1e1c786b → 0.6.8-develop.6941.1.3c26284542

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.
Files changed (27) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +9 -33
  3. package/dist/modules/ai_assistant/api/tools/route.js +2 -2
  4. package/dist/modules/ai_assistant/api/tools/route.js.map +2 -2
  5. package/dist/modules/ai_assistant/lib/generated-registry-loader.js +55 -10
  6. package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
  7. package/dist/modules/ai_assistant/lib/in-process-client.js +2 -2
  8. package/dist/modules/ai_assistant/lib/in-process-client.js.map +2 -2
  9. package/dist/modules/ai_assistant/lib/mcp-server.js +2 -3
  10. package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
  11. package/dist/modules/ai_assistant/lib/tool-input-schema.js +13 -0
  12. package/dist/modules/ai_assistant/lib/tool-input-schema.js.map +7 -0
  13. package/dist/modules/ai_assistant/lib/tool-loader.js +16 -2
  14. package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
  15. package/dist/modules/ai_assistant/lib/tool-registry.js +1 -1
  16. package/dist/modules/ai_assistant/lib/tool-registry.js.map +2 -2
  17. package/package.json +6 -6
  18. package/src/modules/ai_assistant/api/tools/route.ts +2 -2
  19. package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +128 -1
  20. package/src/modules/ai_assistant/lib/__tests__/tool-input-schema.test.ts +52 -0
  21. package/src/modules/ai_assistant/lib/__tests__/tool-loader.test.ts +65 -0
  22. package/src/modules/ai_assistant/lib/generated-registry-loader.ts +107 -12
  23. package/src/modules/ai_assistant/lib/in-process-client.ts +2 -2
  24. package/src/modules/ai_assistant/lib/mcp-server.ts +2 -3
  25. package/src/modules/ai_assistant/lib/tool-input-schema.ts +33 -0
  26. package/src/modules/ai_assistant/lib/tool-loader.ts +18 -1
  27. package/src/modules/ai_assistant/lib/tool-registry.ts +1 -1
@@ -1,2 +1,2 @@
1
- [build:ai-assistant] found 231 entry points
1
+ [build:ai-assistant] found 232 entry points
2
2
  [build:ai-assistant] built successfully
package/AGENTS.md CHANGED
@@ -1489,6 +1489,10 @@ Agents that need multi-step tool loops configure the `loop` block on `AiAgentDef
1489
1489
 
1490
1490
  **Backward compatibility**: additive. With `MCP_SERVER_API_KEY_FILE` unset the OpenCode entrypoint behaves byte-for-byte as before; host-MCP mode (`yarn mcp:serve` + `MCP_SERVER_API_KEY`) is unchanged. Spec: `.ai/specs/2026-07-07-windows-one-command-agentic-dev-environment.md`.
1491
1491
 
1492
+ ### 2026-08-05 - @app module entries are compiled, not raw TS
1493
+
1494
+ `compileAndImportGenerated` (`lib/generated-registry-loader.ts`) compiles every `@app` module entry a generated registry references into `<appRoot>/.mercato/generated/app-modules/`, via the new `compileAppSourceFile` in `shared/lib/bootstrap/dynamicLoader` (reuses `loadBootstrapData`'s esbuild bundle and dep cache; packages stay external). The 2026-06-24 fix below only rewrote the specifier to an absolute `.ts` path, which holds only while the target's graph stays inside Node's type stripping — a real module's does not (`./di` is extensionless, `./data/entities` has decorators), so the first app-local `ai-tools.ts` killed the tool registry. Uncompilable modules log and fall back to the raw path. Additive: the artifact map is an optional third `rewriteGeneratedAliasImports` arg.
1495
+
1492
1496
  ### 2026-06-24 - MCP dev server loads ai-tools for @app local modules (#3524)
1493
1497
 
1494
1498
  **What changed** (`lib/generated-registry-loader.ts`):
@@ -1595,20 +1599,9 @@ Note: the guard is intentionally NOT added to `mcp-client.ts` `connectHttp`, whi
1595
1599
  - Removed auto-discovered module AI tools from `ai-tools.generated.ts`
1596
1600
  - Token savings: from ~10+ tool schemas to exactly 2, with fixed footprint regardless of API surface growth
1597
1601
 
1598
- **Files created**:
1599
- - `lib/codemode-tools.ts` — `search` and `execute` tool definitions
1600
- - `lib/sandbox.ts` — `node:vm` sandbox executor with security restrictions
1601
- - `lib/truncate.ts` — Response size limiter (40K chars / ~10K tokens)
1602
+ **Files created**: `lib/codemode-tools.ts` (the two tool definitions), `lib/sandbox.ts` (`node:vm` executor), `lib/truncate.ts` (40K-char response limiter) — all three described under "Rules for Code Mode Internals" above.
1602
1603
 
1603
- **Files modified**:
1604
- - `lib/api-endpoint-index.ts` — Added `getRawOpenApiSpec()` for raw spec caching
1605
- - `lib/tool-loader.ts` — Loads Code Mode tools instead of legacy tools + module tools
1606
- - `lib/http-server.ts` — Pre-caches raw OpenAPI spec at startup
1607
- - `lib/mcp-server.ts` — Generates entity graph and caches spec for stdio mode
1608
-
1609
- **Files kept but unused**:
1610
- - `lib/api-discovery-tools.ts` — Old find_api/call_api (no longer imported, deleted in #1876)
1611
- - `lib/entity-graph-tools.ts` — Old discover_schema (no longer imported, deleted in #1876)
1604
+ **Files modified**: `lib/api-endpoint-index.ts` (raw spec caching), `lib/tool-loader.ts`, `lib/http-server.ts`, `lib/mcp-server.ts`. The superseded `lib/api-discovery-tools.ts` and `lib/entity-graph-tools.ts` were later deleted in #1876.
1612
1605
 
1613
1606
  ### 2026-01-17 - Session Persistence Fix
1614
1607
 
@@ -1623,31 +1616,14 @@ Note: the guard is intentionally NOT added to `mcp-client.ts` `connectHttp`, whi
1623
1616
  2. **React stale closure**: `handleSubmit` callback captured initial `null` sessionId value.
1624
1617
 
1625
1618
  **Fixes applied**:
1626
- - `opencode-handlers.ts`: Removed Promise.race, await only SSE eventPromise
1627
- - `useCommandPalette.ts`: Added `opencodeSessionIdRef` (ref) alongside state to avoid stale closures
1628
-
1629
- **Files modified**:
1630
- - `src/modules/ai_assistant/lib/opencode-handlers.ts` - Fixed Promise.race completion bug
1631
- - `src/frontend/hooks/useCommandPalette.ts` - Added ref pattern for sessionId
1619
+ - `lib/opencode-handlers.ts`: removed `Promise.race`, await only the SSE eventPromise
1620
+ - `src/frontend/hooks/useCommandPalette.ts`: added `opencodeSessionIdRef` alongside state to avoid stale closures
1632
1621
 
1633
1622
  ### 2026-01 - OpenCode Integration
1634
1623
 
1635
1624
  **Lesson learned:** When replacing an AI backend, preserve the session management contract — the frontend depends on `sessionId` in `done` events regardless of the underlying AI engine.
1636
1625
 
1637
- **Major change**: Replaced Vercel AI SDK with OpenCode as the AI backend.
1638
-
1639
- **What changed**:
1640
- - Chat API now routes all requests to OpenCode
1641
- - Added session management for conversation context
1642
- - Added "Agent is working..." indicator
1643
- - OpenCode connects to MCP server for tools
1644
- - Removed direct AI provider integration
1645
-
1646
- **Files modified**:
1647
- - `src/modules/ai_assistant/api/chat/route.ts` - Complete rewrite to use OpenCode
1648
- - `src/frontend/hooks/useCommandPalette.ts` - Added session state, thinking indicator
1649
- - `src/frontend/components/CommandPalette/ToolChatPage.tsx` - Added thinking UI
1650
- - `src/frontend/types.ts` - Added ChatSSEEvent, isThinking
1626
+ Replaced the Vercel AI SDK with OpenCode: the chat API routes to OpenCode, session management carries conversation context, and OpenCode reaches tools through the MCP server. Current shape: "Rules for the Chat Flow" and "Rules for Session Management" above; see git history for per-file detail.
1651
1627
 
1652
1628
  ### 2026-01 - API Discovery Tools / Hybrid Tool Discovery (superseded)
1653
1629
 
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "@open-mercato/shared/lib/logger";
2
2
  import { NextResponse } from "next/server";
3
- import { zodToJsonSchema } from "zod-to-json-schema";
4
3
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
4
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
5
  import { getToolRegistry } from "../../lib/tool-registry.js";
6
+ import { toolInputJsonSchema } from "../../lib/tool-input-schema.js";
7
7
  import { loadAllModuleTools } from "../../lib/tool-loader.js";
8
8
  import { hasRequiredFeatures } from "../../lib/auth.js";
9
9
  const logger = createLogger("ai_assistant");
@@ -40,7 +40,7 @@ async function GET(req) {
40
40
  return {
41
41
  name: tool.name,
42
42
  description: tool.description,
43
- inputSchema: zodToJsonSchema(tool.inputSchema),
43
+ inputSchema: toolInputJsonSchema(tool.inputSchema),
44
44
  module: nameParts[0] || "other"
45
45
  };
46
46
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/ai_assistant/api/tools/route.ts"],
4
- "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getToolRegistry } from '../../lib/tool-registry'\nimport { loadAllModuleTools } from '../../lib/tool-loader'\nimport { hasRequiredFeatures } from '../../lib/auth'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst logger = createLogger('ai_assistant')\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI tools',\n methods: {\n GET: { summary: 'List available MCP tools filtered by user permissions' },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Get tools filtered by ACL\n const registry = getToolRegistry()\n const allTools = Array.from(registry.getTools().values())\n\n const accessibleTools = allTools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService)\n )\n\n const tools = accessibleTools.map((tool) => {\n const nameParts = tool.name.split('.')\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\n module: nameParts[0] || 'other',\n }\n })\n\n return NextResponse.json({ tools })\n } catch (error) {\n logger.error('AI Tools \u2014 Error listing tools', { err: error })\n return NextResponse.json({ error: 'Failed to list tools' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAE/C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAGpC,MAAM,SAAS,aAAa,cAAc;AAEnC,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK,EAAE,SAAS,wDAAwD;AAAA,EAC1E;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AAEzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAGhE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAGD,UAAM,mBAAmB;AAGzB,UAAM,WAAW,gBAAgB;AACjC,UAAM,WAAW,MAAM,KAAK,SAAS,SAAS,EAAE,OAAO,CAAC;AAExD,UAAM,kBAAkB,SAAS;AAAA,MAAO,CAAC,SACvC,oBAAoB,KAAK,kBAAkB,IAAI,UAAU,IAAI,cAAc,WAAW;AAAA,IACxF;AAEA,UAAM,QAAQ,gBAAgB,IAAI,CAAC,SAAS;AAC1C,YAAM,YAAY,KAAK,KAAK,MAAM,GAAG;AACrC,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,aAAa,gBAAgB,KAAK,WAAkB;AAAA,QACpD,QAAQ,UAAU,CAAC,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AAAA,EACpC,SAAS,OAAO;AACd,WAAO,MAAM,uCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,WAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC7E;AACF;",
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getToolRegistry } from '../../lib/tool-registry'\nimport { toolInputJsonSchema } from '../../lib/tool-input-schema'\nimport { loadAllModuleTools } from '../../lib/tool-loader'\nimport { hasRequiredFeatures } from '../../lib/auth'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst logger = createLogger('ai_assistant')\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI tools',\n methods: {\n GET: { summary: 'List available MCP tools filtered by user permissions' },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Get tools filtered by ACL\n const registry = getToolRegistry()\n const allTools = Array.from(registry.getTools().values())\n\n const accessibleTools = allTools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService)\n )\n\n const tools = accessibleTools.map((tool) => {\n const nameParts = tool.name.split('.')\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: toolInputJsonSchema(tool.inputSchema),\n module: nameParts[0] || 'other',\n }\n })\n\n return NextResponse.json({ tools })\n } catch (error) {\n logger.error('AI Tools \u2014 Error listing tools', { err: error })\n return NextResponse.json({ error: 'Failed to list tools' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAE/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAGpC,MAAM,SAAS,aAAa,cAAc;AAEnC,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK,EAAE,SAAS,wDAAwD;AAAA,EAC1E;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AAEzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAGhE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAGD,UAAM,mBAAmB;AAGzB,UAAM,WAAW,gBAAgB;AACjC,UAAM,WAAW,MAAM,KAAK,SAAS,SAAS,EAAE,OAAO,CAAC;AAExD,UAAM,kBAAkB,SAAS;AAAA,MAAO,CAAC,SACvC,oBAAoB,KAAK,kBAAkB,IAAI,UAAU,IAAI,cAAc,WAAW;AAAA,IACxF;AAEA,UAAM,QAAQ,gBAAgB,IAAI,CAAC,SAAS;AAC1C,YAAM,YAAY,KAAK,KAAK,MAAM,GAAG;AACrC,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,aAAa,oBAAoB,KAAK,WAAW;AAAA,QACjD,QAAQ,UAAU,CAAC,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AAAA,EACpC,SAAS,OAAO;AACd,WAAO,MAAM,uCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,WAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC7E;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from "@open-mercato/shared/lib/logger";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs";
4
+ import crypto from "node:crypto";
4
5
  import { createRequire } from "node:module";
5
6
  import { fileURLToPath, pathToFileURL } from "node:url";
6
7
  const logger = createLogger("ai_assistant");
@@ -36,15 +37,18 @@ async function compileAndImportGenerated(tsPath) {
36
37
  if (!fs.existsSync(tsPath)) {
37
38
  throw new Error(`Generated file not found: ${tsPath}`);
38
39
  }
40
+ const runtime = useJestCjsArtifact ? "cjs" : "esm";
41
+ const tsSource = fs.readFileSync(tsPath, "utf-8");
42
+ const appLocalArtifacts = await compileAppLocalModuleEntries(tsSource, appRoot, runtime);
39
43
  const jsExists = fs.existsSync(jsPath);
40
44
  const needsCompile = !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs;
41
45
  if (needsCompile) {
42
46
  const esbuild = await import("esbuild");
43
- const tsSource = fs.readFileSync(tsPath, "utf-8");
44
47
  const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(
45
48
  tsSource,
46
49
  appRoot,
47
- useJestCjsArtifact ? "cjs" : "esm"
50
+ runtime,
51
+ appLocalArtifacts
48
52
  );
49
53
  const result = await esbuild.transform(aliasRewritten, {
50
54
  loader: "ts",
@@ -63,6 +67,40 @@ async function compileAndImportGenerated(tsPath) {
63
67
  function isJestRuntime() {
64
68
  return typeof process.env.JEST_WORKER_ID === "string";
65
69
  }
70
+ function collectAppLocalSpecifiers(source) {
71
+ const specifiers = /* @__PURE__ */ new Set();
72
+ for (const [, specifier] of source.matchAll(APP_LOCAL_STATIC_IMPORT)) specifiers.add(specifier);
73
+ for (const [, specifier] of source.matchAll(APP_LOCAL_DYNAMIC_IMPORT)) specifiers.add(specifier);
74
+ return [...specifiers].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
75
+ }
76
+ function appLocalArtifactName(specifier, runtime) {
77
+ const slug = specifier.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
78
+ const digest = crypto.createHash("sha256").update(specifier).digest("hex").slice(0, 8);
79
+ return `${slug}-${digest}.${runtime === "cjs" ? "cjs" : "mjs"}`;
80
+ }
81
+ async function compileAppLocalModuleEntries(source, appRoot, runtime) {
82
+ const specifiers = collectAppLocalSpecifiers(source);
83
+ const artifacts = /* @__PURE__ */ new Map();
84
+ if (specifiers.length === 0) return artifacts;
85
+ const generatedDir = path.join(appRoot, ".mercato", "generated");
86
+ const { compileAppSourceFile } = await import("@open-mercato/shared/lib/bootstrap/dynamicLoader");
87
+ for (const specifier of specifiers) {
88
+ const target = path.resolve(generatedDir, specifier);
89
+ const tsPath = fs.existsSync(`${target}.ts`) ? `${target}.ts` : fs.existsSync(target) && target.endsWith(".ts") ? target : null;
90
+ if (tsPath === null) continue;
91
+ const outFile = path.join(generatedDir, "app-modules", appLocalArtifactName(specifier, runtime));
92
+ try {
93
+ await compileAppSourceFile(tsPath, { appRoot, outFile, format: runtime });
94
+ artifacts.set(specifier, outFile);
95
+ } catch (error) {
96
+ logger.warn("Could not compile an app-local module entry for the generated registry", {
97
+ specifier,
98
+ err: error
99
+ });
100
+ }
101
+ }
102
+ return artifacts;
103
+ }
66
104
  const UNSAFE_JS_STRING_CHAR_ESCAPES = {
67
105
  60: "\\u003C",
68
106
  // < — HTML/script-tag breakout
@@ -82,25 +120,31 @@ function escapeUnsafeJsStringChars(value) {
82
120
  function toSafeJsStringLiteral(value) {
83
121
  return escapeUnsafeJsStringChars(JSON.stringify(value));
84
122
  }
85
- function rewriteGeneratedAliasImports(source, appRoot) {
86
- return rewriteGeneratedAliasImportsForRuntime(source, appRoot, "esm");
123
+ function rewriteGeneratedAliasImports(source, appRoot, appLocalArtifacts) {
124
+ return rewriteGeneratedAliasImportsForRuntime(source, appRoot, "esm", appLocalArtifacts);
87
125
  }
88
- function rewriteGeneratedAliasImportsForRuntime(source, appRoot, runtime) {
126
+ const APP_LOCAL_STATIC_IMPORT = /from\s+["']((?:\.\.\/)+src\/[^"']+)["']/g;
127
+ const APP_LOCAL_DYNAMIC_IMPORT = /import\s*\(\s*["']((?:\.\.\/)+src\/[^"']+)["']\s*\)/g;
128
+ function rewriteGeneratedAliasImportsForRuntime(source, appRoot, runtime, appLocalArtifacts = /* @__PURE__ */ new Map()) {
89
129
  const generatedDir = path.join(appRoot, ".mercato", "generated");
130
+ const toRuntimeLiteral = (target) => toSafeJsStringLiteral(runtime === "esm" ? pathToFileURL(target).href : target);
90
131
  const toResolvedLiteral = (target) => {
91
132
  const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
92
- const specifier = runtime === "esm" ? pathToFileURL(candidate).href : candidate;
93
- return toSafeJsStringLiteral(specifier);
133
+ return toRuntimeLiteral(candidate);
94
134
  };
95
135
  const resolveAlias = (relativePath) => toResolvedLiteral(path.join(appRoot, relativePath));
96
- const resolveRelative = (specifier) => toResolvedLiteral(path.resolve(generatedDir, specifier));
136
+ const resolveRelative = (specifier) => {
137
+ const artifact = appLocalArtifacts.get(specifier);
138
+ if (artifact !== void 0) return toRuntimeLiteral(artifact);
139
+ return toResolvedLiteral(path.resolve(generatedDir, specifier));
140
+ };
97
141
  return source.replace(/from\s+["']@\/([^"']+)["']/g, (_match, relativePath) => {
98
142
  return `from ${resolveAlias(relativePath)}`;
99
143
  }).replace(/import\s*\(\s*["']@\/([^"']+)["']\s*\)/g, (_match, relativePath) => {
100
144
  return `import(${resolveAlias(relativePath)})`;
101
- }).replace(/from\s+["']((?:\.\.\/)+src\/[^"']+)["']/g, (_match, specifier) => {
145
+ }).replace(APP_LOCAL_STATIC_IMPORT, (_match, specifier) => {
102
146
  return `from ${resolveRelative(specifier)}`;
103
- }).replace(/import\s*\(\s*["']((?:\.\.\/)+src\/[^"']+)["']\s*\)/g, (_match, specifier) => {
147
+ }).replace(APP_LOCAL_DYNAMIC_IMPORT, (_match, specifier) => {
104
148
  return `import(${resolveRelative(specifier)})`;
105
149
  });
106
150
  }
@@ -124,6 +168,7 @@ async function ensureApiRouteManifestsRegistered() {
124
168
  }
125
169
  }
126
170
  export {
171
+ collectAppLocalSpecifiers,
127
172
  compileAndImportGenerated,
128
173
  ensureApiRouteManifestsRegistered,
129
174
  escapeUnsafeJsStringChars,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/generated-registry-loader.ts"],
4
- "sourcesContent": ["/**\n * Runtime loader for `.mercato/generated/*.generated.ts` registry files.\n *\n * The generated registries import their entries through the `@/` path alias\n * (e.g. `@/.mercato/generated/ai-tools.generated`). That alias is only\n * understood by the Next.js bundler \u2014 in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers, the CLI tool-test runner) a raw\n * `import('@/.mercato/...')` throws `ERR_MODULE_NOT_FOUND: Cannot find\n * package '@/.mercato'` because Node treats `@/` as a package specifier.\n *\n * These helpers locate the generated `.ts` file on disk and compile-and-import\n * it with esbuild (transpile-only), rewriting `@/` aliases to absolute paths.\n * This mirrors `loadBootstrapData` in\n * `@open-mercato/shared/lib/bootstrap/dynamicLoader` and works in both the\n * monorepo and standalone apps.\n */\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nconst logger = createLogger('ai_assistant')\n\nconst requireFromHere = createRequire(import.meta.url)\n\n/**\n * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without\n * hardcoding the workspace layout. Searches upward from this module's compiled\n * location for a `apps/mercato/.mercato/generated/<fileName>` (monorepo), then\n * falls back to cwd-relative lookups (standalone apps run from the app dir).\n */\nexport function findGeneratedFile(fileName: string): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n\n if (here) {\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n }\n // Fallbacks: cwd-based lookup (CLI invoked from apps/mercato, or a standalone\n // app whose root holds `.mercato/generated`).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import a generated registry file on the fly. Rewrites the entry\n * specifiers Node can't resolve standalone (`@/...` aliases and the\n * `../../src/...` relative imports the generator emits for `@app` local\n * modules) to absolute file URLs, transpiles TS \u2192 ESM, and emits a sibling\n * `.mjs` we can `import()` from Node. Cached on mtime so repeat calls in the\n * same process don't recompile.\n *\n * Transpile-only (no bundling): the generated registries declare an array\n * literal whose entries are static `import(\"\u2026\")` arrow functions \u2014 we want\n * those `import()` strings to stay as runtime imports so Node resolves them\n * lazily through the workspace's normal module resolution. Eagerly bundling\n * pulls Next.js / route-handler internals into the `.mjs` and breaks at runtime\n * (e.g. `next/server` package-exports map).\n */\nexport async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {\n const useJestCjsArtifact = isJestRuntime()\n const jsPath = tsPath.replace(/\\.ts$/, useJestCjsArtifact ? '.jest.cjs' : '.mjs')\n // appRoot is two directories up from `.mercato/generated/<file>.ts`.\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n if (!fs.existsSync(tsPath)) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n const esbuild = await import('esbuild')\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(\n tsSource,\n appRoot,\n useJestCjsArtifact ? 'cjs' : 'esm',\n )\n const result = await esbuild.transform(aliasRewritten, {\n loader: 'ts',\n format: useJestCjsArtifact ? 'cjs' : 'esm',\n target: 'node18',\n sourcemap: false,\n sourcefile: tsPath,\n })\n fs.writeFileSync(jsPath, result.code)\n }\n\n if (useJestCjsArtifact) {\n return requireFromHere(jsPath) as Record<string, unknown>\n }\n return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>\n}\n\nfunction isJestRuntime(): boolean {\n return typeof process.env.JEST_WORKER_ID === 'string'\n}\n\nconst UNSAFE_JS_STRING_CHAR_ESCAPES: Record<number, string> = {\n 0x3c: '\\\\u003C', // < \u2014 HTML/script-tag breakout\n 0x3e: '\\\\u003E', // > \u2014 HTML/script-tag breakout\n 0x2028: '\\\\u2028', // line separator \u2014 string content but a statement terminator pre-ES2019\n 0x2029: '\\\\u2029', // paragraph separator \u2014 same\n}\n\n/**\n * Escape characters that `JSON.stringify` leaves intact but which can still\n * break out of (or alter the meaning of) the JavaScript string literal that the\n * stringified value is embedded into \u2014 notably `<`/`>` (HTML/script-tag\n * breakout) and the U+2028 / U+2029 line separators (valid string content but\n * statement terminators in pre-ES2019 parsers). Apply this on top of\n * `JSON.stringify` so the emitted import source stays well-formed regardless of\n * the resolved path. Exported for unit testing.\n */\nexport function escapeUnsafeJsStringChars(value: string): string {\n return value.replace(\n /[<>\\u2028\\u2029]/g,\n (char) => UNSAFE_JS_STRING_CHAR_ESCAPES[char.charCodeAt(0)],\n )\n}\n\n/**\n * Stringify a resolved path into a JavaScript string literal that is safe to\n * embed in generated source: `JSON.stringify` handles quoting/standard escapes,\n * and `escapeUnsafeJsStringChars` neutralizes the characters it leaves intact.\n */\nfunction toSafeJsStringLiteral(value: string): string {\n return escapeUnsafeJsStringChars(JSON.stringify(value))\n}\n\n/**\n * Rewrite the two specifier shapes the generator emits for module entries in a\n * generated registry to absolute `file://` URLs Node can resolve in a\n * standalone process:\n *\n * 1. `@/...` path-alias imports (both `from \"@/x\"` and dynamic `import(\"@/x\")`).\n * The `@/` alias is a Next.js bundler convention; outside the bundler Node\n * treats `@/...` as a bare package specifier and throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against `appRoot`.\n * 2. `../../src/...` relative imports the generator emits for `@app` local\n * modules (e.g. `from \"../../src/modules/<id>/ai-tools\"`). esbuild's\n * transform (transpile-only) leaves these untouched, so the compiled\n * `.mjs` keeps an extensionless relative specifier that resolves to a\n * `.ts` file with no compiled `.js`/`.mjs` sibling \u2014 Node ESM then throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against the generated file's directory\n * (`<appRoot>/.mercato/generated`), the location the generator wrote them\n * relative to. Package-backed modules (`@open-mercato/*`) are unaffected \u2014\n * their bare specifiers resolve through `node_modules` to compiled `.js`.\n *\n * Both shapes reuse the same `.ts`-suffix probe so a source-only TypeScript\n * target is loaded directly (Node strips types). Other specifiers (bare\n * packages, sibling `./` imports) are left untouched. Exported for unit testing.\n */\nexport function rewriteGeneratedAliasImports(source: string, appRoot: string): string {\n return rewriteGeneratedAliasImportsForRuntime(source, appRoot, 'esm')\n}\n\nfunction rewriteGeneratedAliasImportsForRuntime(\n source: string,\n appRoot: string,\n runtime: 'esm' | 'cjs',\n): string {\n const generatedDir = path.join(appRoot, '.mercato', 'generated')\n const toResolvedLiteral = (target: string): string => {\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n const specifier = runtime === 'esm' ? pathToFileURL(candidate).href : candidate\n return toSafeJsStringLiteral(specifier)\n }\n const resolveAlias = (relativePath: string): string =>\n toResolvedLiteral(path.join(appRoot, relativePath))\n const resolveRelative = (specifier: string): string =>\n toResolvedLiteral(path.resolve(generatedDir, specifier))\n return source\n .replace(/from\\s+[\"']@\\/([^\"']+)[\"']/g, (_match, relativePath: string) => {\n return `from ${resolveAlias(relativePath)}`\n })\n .replace(/import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g, (_match, relativePath: string) => {\n return `import(${resolveAlias(relativePath)})`\n })\n .replace(/from\\s+[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']/g, (_match, specifier: string) => {\n return `from ${resolveRelative(specifier)}`\n })\n .replace(/import\\s*\\(\\s*[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']\\s*\\)/g, (_match, specifier: string) => {\n return `import(${resolveRelative(specifier)})`\n })\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many module tools are \"API-backed\" \u2014 their handlers\n * delegate to `createAiApiOperationRunner`, which fails closed with\n * \"No API route manifest registered\" unless the manifest is present. In the\n * Next.js app this is wired at bootstrap, but the standalone MCP servers\n * (`mcp:dev` / `mcp:serve`) bootstrap DI without it, so we register it here.\n *\n * Idempotent: `registerApiRouteManifests` replaces the stored manifest, so\n * calling this repeatedly (e.g. per-request HTTP handlers) is safe. Returns the\n * number of registered routes (0 when the generated file is absent).\n */\nexport async function ensureApiRouteManifestsRegistered(): Promise<number> {\n const registry = await import('@open-mercato/shared/modules/registry')\n // Already wired (e.g. the Next.js app bootstrap, or a prior call). Leave the\n // existing manifest untouched so we never interfere with the in-app agents\n // framework, which registers it at bootstrap with its own override pipeline.\n const existing = registry.getApiRouteManifests()\n if (existing.length > 0) return existing.length\n\n const tsPath = findGeneratedFile('api-routes.generated.ts')\n if (!tsPath) return 0\n try {\n const mod = await compileAndImportGenerated(tsPath)\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) return 0\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n return apiRoutes.length\n } catch (error) {\n logger.warn('Could not register api-routes manifest', { err: error })\n return 0\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Runtime loader for `.mercato/generated/*.generated.ts` registry files.\n *\n * The generated registries import their entries through the `@/` path alias\n * (e.g. `@/.mercato/generated/ai-tools.generated`). That alias is only\n * understood by the Next.js bundler \u2014 in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers, the CLI tool-test runner) a raw\n * `import('@/.mercato/...')` throws `ERR_MODULE_NOT_FOUND: Cannot find\n * package '@/.mercato'` because Node treats `@/` as a package specifier.\n *\n * These helpers locate the generated `.ts` file on disk and compile-and-import\n * it with esbuild (transpile-only), rewriting `@/` aliases to absolute paths.\n * This mirrors `loadBootstrapData` in\n * `@open-mercato/shared/lib/bootstrap/dynamicLoader` and works in both the\n * monorepo and standalone apps.\n */\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport crypto from 'node:crypto'\nimport { createRequire } from 'node:module'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\nconst logger = createLogger('ai_assistant')\n\nconst requireFromHere = createRequire(import.meta.url)\n\n/**\n * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without\n * hardcoding the workspace layout. Searches upward from this module's compiled\n * location for a `apps/mercato/.mercato/generated/<fileName>` (monorepo), then\n * falls back to cwd-relative lookups (standalone apps run from the app dir).\n */\nexport function findGeneratedFile(fileName: string): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n\n if (here) {\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n }\n // Fallbacks: cwd-based lookup (CLI invoked from apps/mercato, or a standalone\n // app whose root holds `.mercato/generated`).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import a generated registry file on the fly. Rewrites the entry\n * specifiers Node can't resolve standalone (`@/...` aliases and the\n * `../../src/...` relative imports the generator emits for `@app` local\n * modules) to absolute file URLs, transpiles TS \u2192 ESM, and emits a sibling\n * `.mjs` we can `import()` from Node. Cached on mtime so repeat calls in the\n * same process don't recompile.\n *\n * Transpile-only (no bundling): the generated registries declare an array\n * literal whose entries are static `import(\"\u2026\")` arrow functions \u2014 we want\n * those `import()` strings to stay as runtime imports so Node resolves them\n * lazily through the workspace's normal module resolution. Eagerly bundling\n * pulls Next.js / route-handler internals into the `.mjs` and breaks at runtime\n * (e.g. `next/server` package-exports map).\n *\n * The `@app` local module entries are the one exception: those targets are raw\n * app TypeScript with no compiled sibling, so they are compiled separately\n * (see `compileAppLocalModuleEntries`) and the registry points at the artifact.\n */\nexport async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {\n const useJestCjsArtifact = isJestRuntime()\n const jsPath = tsPath.replace(/\\.ts$/, useJestCjsArtifact ? '.jest.cjs' : '.mjs')\n // appRoot is two directories up from `.mercato/generated/<file>.ts`.\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n if (!fs.existsSync(tsPath)) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n\n const runtime = useJestCjsArtifact ? 'cjs' : 'esm'\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n // Runs on every call, not only when the registry itself is stale: the\n // artifact path is stable, so a registry cache hit would otherwise pin an\n // app module's compiled output to whatever it was when the registry was\n // last regenerated.\n const appLocalArtifacts = await compileAppLocalModuleEntries(tsSource, appRoot, runtime)\n\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n const esbuild = await import('esbuild')\n const aliasRewritten = rewriteGeneratedAliasImportsForRuntime(\n tsSource,\n appRoot,\n runtime,\n appLocalArtifacts,\n )\n const result = await esbuild.transform(aliasRewritten, {\n loader: 'ts',\n format: useJestCjsArtifact ? 'cjs' : 'esm',\n target: 'node18',\n sourcemap: false,\n sourcefile: tsPath,\n })\n fs.writeFileSync(jsPath, result.code)\n }\n\n if (useJestCjsArtifact) {\n return requireFromHere(jsPath) as Record<string, unknown>\n }\n return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>\n}\n\nfunction isJestRuntime(): boolean {\n return typeof process.env.JEST_WORKER_ID === 'string'\n}\n\n/** Every `../../src/...` specifier the generator emitted for `@app` local modules. */\nexport function collectAppLocalSpecifiers(source: string): string[] {\n const specifiers = new Set<string>()\n for (const [, specifier] of source.matchAll(APP_LOCAL_STATIC_IMPORT)) specifiers.add(specifier)\n for (const [, specifier] of source.matchAll(APP_LOCAL_DYNAMIC_IMPORT)) specifiers.add(specifier)\n return [...specifiers].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n}\n\n/** Stable, collision-free artifact name for one app-local specifier. */\nfunction appLocalArtifactName(specifier: string, runtime: 'esm' | 'cjs'): string {\n const slug = specifier.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '')\n const digest = crypto.createHash('sha256').update(specifier).digest('hex').slice(0, 8)\n return `${slug}-${digest}.${runtime === 'cjs' ? 'cjs' : 'mjs'}`\n}\n\n/**\n * Compile every `@app` local module entry the generated registry references,\n * and map each specifier to its artifact.\n *\n * Package-backed modules (`@open-mercato/*`) never reach here \u2014 their bare\n * specifiers resolve through `node_modules` to compiled `.js`. App-local\n * modules have no compiled sibling, and Node cannot load their `.ts` source\n * directly: relative specifiers need explicit extensions under type stripping,\n * and the module's own graph (`./di`, `./data/entities`) carries decorator and\n * enum syntax that type stripping rejects outright. Bundling the entry with\n * every package import left external is what makes the source loadable.\n *\n * A module that fails to compile is logged and left pointing at its raw source,\n * which reproduces the pre-existing resolution error rather than silently\n * dropping the module's tools from the registry.\n */\nasync function compileAppLocalModuleEntries(\n source: string,\n appRoot: string,\n runtime: 'esm' | 'cjs',\n): Promise<Map<string, string>> {\n const specifiers = collectAppLocalSpecifiers(source)\n const artifacts = new Map<string, string>()\n if (specifiers.length === 0) return artifacts\n\n const generatedDir = path.join(appRoot, '.mercato', 'generated')\n const { compileAppSourceFile } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n\n for (const specifier of specifiers) {\n const target = path.resolve(generatedDir, specifier)\n const tsPath = fs.existsSync(`${target}.ts`)\n ? `${target}.ts`\n : fs.existsSync(target) && target.endsWith('.ts')\n ? target\n : null\n if (tsPath === null) continue\n\n const outFile = path.join(generatedDir, 'app-modules', appLocalArtifactName(specifier, runtime))\n try {\n await compileAppSourceFile(tsPath, { appRoot, outFile, format: runtime })\n artifacts.set(specifier, outFile)\n } catch (error) {\n logger.warn('Could not compile an app-local module entry for the generated registry', {\n specifier,\n err: error,\n })\n }\n }\n\n return artifacts\n}\n\nconst UNSAFE_JS_STRING_CHAR_ESCAPES: Record<number, string> = {\n 0x3c: '\\\\u003C', // < \u2014 HTML/script-tag breakout\n 0x3e: '\\\\u003E', // > \u2014 HTML/script-tag breakout\n 0x2028: '\\\\u2028', // line separator \u2014 string content but a statement terminator pre-ES2019\n 0x2029: '\\\\u2029', // paragraph separator \u2014 same\n}\n\n/**\n * Escape characters that `JSON.stringify` leaves intact but which can still\n * break out of (or alter the meaning of) the JavaScript string literal that the\n * stringified value is embedded into \u2014 notably `<`/`>` (HTML/script-tag\n * breakout) and the U+2028 / U+2029 line separators (valid string content but\n * statement terminators in pre-ES2019 parsers). Apply this on top of\n * `JSON.stringify` so the emitted import source stays well-formed regardless of\n * the resolved path. Exported for unit testing.\n */\nexport function escapeUnsafeJsStringChars(value: string): string {\n return value.replace(\n /[<>\\u2028\\u2029]/g,\n (char) => UNSAFE_JS_STRING_CHAR_ESCAPES[char.charCodeAt(0)],\n )\n}\n\n/**\n * Stringify a resolved path into a JavaScript string literal that is safe to\n * embed in generated source: `JSON.stringify` handles quoting/standard escapes,\n * and `escapeUnsafeJsStringChars` neutralizes the characters it leaves intact.\n */\nfunction toSafeJsStringLiteral(value: string): string {\n return escapeUnsafeJsStringChars(JSON.stringify(value))\n}\n\n/**\n * Rewrite the two specifier shapes the generator emits for module entries in a\n * generated registry to absolute `file://` URLs Node can resolve in a\n * standalone process:\n *\n * 1. `@/...` path-alias imports (both `from \"@/x\"` and dynamic `import(\"@/x\")`).\n * The `@/` alias is a Next.js bundler convention; outside the bundler Node\n * treats `@/...` as a bare package specifier and throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against `appRoot`.\n * 2. `../../src/...` relative imports the generator emits for `@app` local\n * modules (e.g. `from \"../../src/modules/<id>/ai-tools\"`). esbuild's\n * transform (transpile-only) leaves these untouched, so the compiled\n * `.mjs` keeps an extensionless relative specifier that resolves to a\n * `.ts` file with no compiled `.js`/`.mjs` sibling \u2014 Node ESM then throws\n * `ERR_MODULE_NOT_FOUND`. Resolved against the generated file's directory\n * (`<appRoot>/.mercato/generated`), the location the generator wrote them\n * relative to. Package-backed modules (`@open-mercato/*`) are unaffected \u2014\n * their bare specifiers resolve through `node_modules` to compiled `.js`.\n *\n * When `appLocalArtifacts` maps a shape-2 specifier to a compiled artifact, that\n * artifact wins: pointing Node at raw app TypeScript only works while the file\n * and its whole graph stay within what type stripping accepts, which app\n * modules do not (see `compileAppLocalModuleEntries`). Without a mapping both\n * shapes fall back to the same `.ts`-suffix probe. Other specifiers (bare\n * packages, sibling `./` imports) are left untouched. Exported for unit testing.\n */\nexport function rewriteGeneratedAliasImports(\n source: string,\n appRoot: string,\n appLocalArtifacts?: Map<string, string>,\n): string {\n return rewriteGeneratedAliasImportsForRuntime(source, appRoot, 'esm', appLocalArtifacts)\n}\n\nconst APP_LOCAL_STATIC_IMPORT = /from\\s+[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']/g\nconst APP_LOCAL_DYNAMIC_IMPORT = /import\\s*\\(\\s*[\"']((?:\\.\\.\\/)+src\\/[^\"']+)[\"']\\s*\\)/g\n\nfunction rewriteGeneratedAliasImportsForRuntime(\n source: string,\n appRoot: string,\n runtime: 'esm' | 'cjs',\n appLocalArtifacts: Map<string, string> = new Map(),\n): string {\n const generatedDir = path.join(appRoot, '.mercato', 'generated')\n const toRuntimeLiteral = (target: string): string =>\n toSafeJsStringLiteral(runtime === 'esm' ? pathToFileURL(target).href : target)\n const toResolvedLiteral = (target: string): string => {\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return toRuntimeLiteral(candidate)\n }\n const resolveAlias = (relativePath: string): string =>\n toResolvedLiteral(path.join(appRoot, relativePath))\n const resolveRelative = (specifier: string): string => {\n const artifact = appLocalArtifacts.get(specifier)\n if (artifact !== undefined) return toRuntimeLiteral(artifact)\n return toResolvedLiteral(path.resolve(generatedDir, specifier))\n }\n return source\n .replace(/from\\s+[\"']@\\/([^\"']+)[\"']/g, (_match, relativePath: string) => {\n return `from ${resolveAlias(relativePath)}`\n })\n .replace(/import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g, (_match, relativePath: string) => {\n return `import(${resolveAlias(relativePath)})`\n })\n .replace(APP_LOCAL_STATIC_IMPORT, (_match, specifier: string) => {\n return `from ${resolveRelative(specifier)}`\n })\n .replace(APP_LOCAL_DYNAMIC_IMPORT, (_match, specifier: string) => {\n return `import(${resolveRelative(specifier)})`\n })\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many module tools are \"API-backed\" \u2014 their handlers\n * delegate to `createAiApiOperationRunner`, which fails closed with\n * \"No API route manifest registered\" unless the manifest is present. In the\n * Next.js app this is wired at bootstrap, but the standalone MCP servers\n * (`mcp:dev` / `mcp:serve`) bootstrap DI without it, so we register it here.\n *\n * Idempotent: `registerApiRouteManifests` replaces the stored manifest, so\n * calling this repeatedly (e.g. per-request HTTP handlers) is safe. Returns the\n * number of registered routes (0 when the generated file is absent).\n */\nexport async function ensureApiRouteManifestsRegistered(): Promise<number> {\n const registry = await import('@open-mercato/shared/modules/registry')\n // Already wired (e.g. the Next.js app bootstrap, or a prior call). Leave the\n // existing manifest untouched so we never interfere with the in-app agents\n // framework, which registers it at bootstrap with its own override pipeline.\n const existing = registry.getApiRouteManifests()\n if (existing.length > 0) return existing.length\n\n const tsPath = findGeneratedFile('api-routes.generated.ts')\n if (!tsPath) return 0\n try {\n const mod = await compileAndImportGenerated(tsPath)\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) return 0\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n return apiRoutes.length\n } catch (error) {\n logger.warn('Could not register api-routes manifest', { err: error })\n return 0\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- import { zodToJsonSchema } from "zod-to-json-schema";
1
+ import { toolInputJsonSchema } from "./tool-input-schema.js";
2
2
  import { getToolRegistry } from "./tool-registry.js";
3
3
  import { executeTool } from "./tool-executor.js";
4
4
  import { loadAllModuleTools } from "./tool-loader.js";
@@ -70,7 +70,7 @@ class InProcessMcpClient {
70
70
  return accessibleTools.map((tool) => ({
71
71
  name: tool.name,
72
72
  description: tool.description,
73
- inputSchema: zodToJsonSchema(tool.inputSchema)
73
+ inputSchema: toolInputJsonSchema(tool.inputSchema)
74
74
  }));
75
75
  }
76
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 { zodToJsonSchema } from 'zod-to-json-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: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\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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",
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
  }
@@ -4,7 +4,7 @@ import {
4
4
  ListToolsRequestSchema,
5
5
  CallToolRequestSchema
6
6
  } from "@modelcontextprotocol/sdk/types.js";
7
- import { zodToJsonSchema } from "zod-to-json-schema";
7
+ import { toolInputJsonSchema } from "./tool-input-schema.js";
8
8
  import { getToolRegistry } from "./tool-registry.js";
9
9
  import { executeTool } from "./tool-executor.js";
10
10
  import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
@@ -91,8 +91,7 @@ async function createMcpServer(options) {
91
91
  tools: accessibleTools.map((tool) => ({
92
92
  name: tool.name,
93
93
  description: tool.description,
94
- // Cast to any for Zod v4 compatibility with zod-to-json-schema
95
- inputSchema: zodToJsonSchema(tool.inputSchema)
94
+ inputSchema: toolInputJsonSchema(tool.inputSchema)
96
95
  }))
97
96
  };
98
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 { zodToJsonSchema } from 'zod-to-json-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 // Cast to any for Zod v4 compatibility with zod-to-json-schema\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\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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",
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
  }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ function toolInputJsonSchema(schema) {
3
+ if (!schema) return { type: "object", properties: {} };
4
+ try {
5
+ return z.toJSONSchema(schema, { io: "input", unrepresentable: "any" });
6
+ } catch {
7
+ return { type: "object", properties: {} };
8
+ }
9
+ }
10
+ export {
11
+ toolInputJsonSchema
12
+ };
13
+ //# sourceMappingURL=tool-input-schema.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/tool-input-schema.ts"],
4
+ "sourcesContent": ["import { z, type ZodTypeAny } from 'zod'\n\n/**\n * Publish a tool's declared input as JSON Schema.\n *\n * Every place a tool is advertised \u2014 the MCP `tools/list` handler, the in-process client and\n * `GET /api/ai_assistant/tools` \u2014 used to call `zod-to-json-schema`, a Zod **3** converter. This\n * repository is on Zod 4, whose internals that package cannot read, so it silently returned\n * `{ \"$schema\": \"\u2026\" }` for every tool: no `properties`, no `required`, nothing. It did not throw\n * and nothing failed a type check, so the tools kept listing and a model was simply never told\n * that `example.get_customer_priority` takes a `customerId`. An argument-free tool looked\n * identical to one with three required fields.\n *\n * Zod 4 ships its own converter, so the fix is to use it. Two deliberate choices:\n *\n * - `io: 'input'` \u2014 a tool schema describes what the CALLER sends. With the default (`'output'`)\n * a field carrying a `.default()` is published as required-on-output, which is the opposite of\n * what a caller needs to know.\n * - `unrepresentable: 'any'` \u2014 the default throws on a type with no JSON Schema form (`z.date()`,\n * `z.bigint()`). Throwing here would take down the whole tool listing because one tool declared\n * a date, so such a field is published as an unconstrained value instead.\n *\n * A schema that still cannot be converted degrades to the empty object schema rather than\n * propagating: listing the remaining tools is worth more than failing the request.\n */\nexport function toolInputJsonSchema(schema: ZodTypeAny | undefined): Record<string, unknown> {\n if (!schema) return { type: 'object', properties: {} }\n try {\n return z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as Record<string, unknown>\n } catch {\n return { type: 'object', properties: {} }\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAA0B;AAyB5B,SAAS,oBAAoB,QAAyD;AAC3F,MAAI,CAAC,OAAQ,QAAO,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AACrD,MAAI;AACF,WAAO,EAAE,aAAa,QAAQ,EAAE,IAAI,SAAS,iBAAiB,MAAM,CAAC;AAAA,EACvE,QAAQ;AACN,WAAO,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,EAC1C;AACF;",
6
+ "names": []
7
+ }
@@ -12,6 +12,7 @@ import {
12
12
  ensureApiRouteManifestsRegistered
13
13
  } from "./generated-registry-loader.js";
14
14
  const logger = createLogger("ai_assistant").child({ component: "tools" });
15
+ let allModuleToolsLoad = null;
15
16
  function isModuleAiTool(value) {
16
17
  if (!value || typeof value !== "object") return false;
17
18
  const candidate = value;
@@ -109,7 +110,7 @@ async function loadGeneratedModuleAiTools() {
109
110
  return 0;
110
111
  }
111
112
  }
112
- async function loadAllModuleTools() {
113
+ async function loadAllModuleToolsUncached() {
113
114
  registerMcpTool(contextWhoamiTool, { moduleId: "context" });
114
115
  logger.debug("Registered built-in context_whoami tool");
115
116
  try {
@@ -134,6 +135,18 @@ async function loadAllModuleTools() {
134
135
  logger.error("Could not register API route manifests", { err: error });
135
136
  }
136
137
  }
138
+ function loadAllModuleTools() {
139
+ if (!allModuleToolsLoad) {
140
+ allModuleToolsLoad = loadAllModuleToolsUncached().catch((error) => {
141
+ allModuleToolsLoad = null;
142
+ throw error;
143
+ });
144
+ }
145
+ return allModuleToolsLoad;
146
+ }
147
+ function resetAllModuleToolsLoadForTests() {
148
+ allModuleToolsLoad = null;
149
+ }
137
150
  async function indexToolsForSearch(searchService, force = false) {
138
151
  const registry = getToolRegistry();
139
152
  const toolSearchService = new ToolSearchService(searchService, registry);
@@ -162,6 +175,7 @@ export {
162
175
  loadGeneratedAiToolOverrides,
163
176
  loadGeneratedModuleAiTools,
164
177
  loadModuleTools,
165
- registerGeneratedAiToolEntries
178
+ registerGeneratedAiToolEntries,
179
+ resetAllModuleToolsLoadForTests
166
180
  };
167
181
  //# sourceMappingURL=tool-loader.js.map