@open-mercato/ai-assistant 0.6.8-develop.7063.1.664341cf65 → 0.6.8-develop.7065.1.b7b351a126

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 (29) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/modules/ai_assistant/lib/codemode-tools.js +14 -6
  4. package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
  5. package/dist/modules/ai_assistant/lib/http-server.js +3 -1
  6. package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
  7. package/dist/modules/ai_assistant/lib/in-process-client.js +3 -1
  8. package/dist/modules/ai_assistant/lib/in-process-client.js.map +2 -2
  9. package/dist/modules/ai_assistant/lib/mcp-client.js +2 -1
  10. package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
  11. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +3 -1
  12. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
  13. package/dist/modules/ai_assistant/lib/mcp-server.js +3 -1
  14. package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
  15. package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js +18 -0
  16. package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js.map +7 -0
  17. package/package.json +6 -6
  18. package/src/modules/ai_assistant/lib/__tests__/codemode-tool-annotations.test.ts +58 -0
  19. package/src/modules/ai_assistant/lib/__tests__/mcp-client.test.ts +30 -0
  20. package/src/modules/ai_assistant/lib/__tests__/mcp-server-tool-annotations.test.ts +120 -0
  21. package/src/modules/ai_assistant/lib/__tests__/mcp-tool-annotations.test.ts +57 -0
  22. package/src/modules/ai_assistant/lib/codemode-tools.ts +21 -7
  23. package/src/modules/ai_assistant/lib/http-server.ts +2 -0
  24. package/src/modules/ai_assistant/lib/in-process-client.ts +2 -0
  25. package/src/modules/ai_assistant/lib/mcp-client.ts +1 -0
  26. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +2 -0
  27. package/src/modules/ai_assistant/lib/mcp-server.ts +2 -0
  28. package/src/modules/ai_assistant/lib/mcp-tool-annotations.ts +35 -0
  29. package/src/modules/ai_assistant/lib/types.ts +11 -0
@@ -1,2 +1,2 @@
1
- [build:ai-assistant] found 232 entry points
1
+ [build:ai-assistant] found 233 entry points
2
2
  [build:ai-assistant] built successfully
package/AGENTS.md CHANGED
@@ -166,7 +166,7 @@ const listPeopleTool = defineAiTool({
166
166
  MUST rules:
167
167
  - MUST set `requiredFeatures` for any tool that reads or writes tenant data. The wildcard-aware ACL matcher is applied before the handler runs.
168
168
  - MUST use Zod for `inputSchema` — never raw JSON Schema.
169
- - MUST set `isMutation: true` on write tools. The policy gate strips these from read-only agents and from read-only tenant overrides.
169
+ - MUST set `isMutation: true` on write tools. It gates read-only agents/overrides and drives the MCP `readOnlyHint` in `tools/list`.
170
170
  - MUST route every mutation tool through `prepareMutation(...)` (see the Mutation Approvals guide at `/framework/ai-assistant/mutation-approvals`). Writing directly inside the handler bypasses the approval gate — the runtime fails closed and refuses to return a result to the operator.
171
171
  - Mutation preview resolvers SHOULD return a normalized `after` snapshot and display hints when tool inputs contain dictionary IDs or other opaque values. Use `display.fieldLabels`, `display.before`, and `display.after` so `field-diff-card` shows operator-friendly names while `field`, `before`, and `after` keep the raw execution values.
172
172
  - MUST expose tools to an agent by listing the tool name in the agent's `allowedTools`. Tools not on the whitelist never reach the model.
@@ -378,6 +378,9 @@ function buildEntitySchemas(graph) {
378
378
  }
379
379
  const CODE_MODE_MAX_API_CALLS = 50;
380
380
  const CODE_MODE_MAX_MUTATION_CALLS = 20;
381
+ function registerCodeModeTool(tool) {
382
+ registerMcpTool(tool, { moduleId: "codemode" });
383
+ }
381
384
  async function loadCodeModeTools() {
382
385
  const commonTypes = await generateCommonTypes();
383
386
  registerSearchTool();
@@ -385,9 +388,10 @@ async function loadCodeModeTools() {
385
388
  return 2;
386
389
  }
387
390
  function registerSearchTool() {
388
- registerMcpTool(
391
+ registerCodeModeTool(
389
392
  {
390
393
  name: "search",
394
+ isMutation: false,
391
395
  description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.
392
396
  Globals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.
393
397
  Use BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,
@@ -447,17 +451,22 @@ Use BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common
447
451
  _memoryContext: memoryContext
448
452
  };
449
453
  }
450
- },
451
- { moduleId: "codemode" }
454
+ }
452
455
  );
453
456
  }
454
457
  function registerExecuteTool(commonTypes) {
455
458
  const typesBlock = commonTypes ? `
456
459
 
457
460
  ${commonTypes}` : "";
458
- registerMcpTool(
461
+ registerCodeModeTool(
459
462
  {
460
463
  name: "execute",
464
+ // api.request() reaches every documented endpoint, including POST/PUT/DELETE,
465
+ // so the tool is neither read-only nor guaranteed non-destructive. It is
466
+ // intentionally exempt from prepareMutation: arbitrary sandbox code cannot
467
+ // provide the structured before/after preview that approval flow requires.
468
+ isMutation: true,
469
+ isDestructive: true,
461
470
  description: `Make API calls. Returns JSON.
462
471
  Globals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.
463
472
  RULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,
@@ -526,8 +535,7 @@ RULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collecti
526
535
  _memoryContext: memoryContext
527
536
  };
528
537
  }
529
- },
530
- { moduleId: "codemode" }
538
+ }
531
539
  );
532
540
  }
533
541
  function createApiRequestFn(ctx, onCall) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/codemode-tools.ts"],
4
- "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\n\n/**\n * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { applyContextScopeToQuery, applyContextScopeToBody } from './scope-injection'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst logger = createLogger('ai_assistant').child({ component: 'codemode' })\n\nconst DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveAiApiRequestTimeoutMs(): number {\n const raw = process.env.AI_API_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS)\n}\n\n/**\n * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n logger.debug('Generated common type stubs', { count: typeLines.length - 1 })\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/** Maximum api.request() calls allowed per execute() run, regardless of method. */\nexport const CODE_MODE_MAX_API_CALLS = 50\n/** Maximum mutation (non-GET/HEAD/OPTIONS) api.request() calls allowed per execute() run. */\nexport const CODE_MODE_MAX_MUTATION_CALLS = 20\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerMcpTool(\n {\n name: 'search',\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n logger.debug('search tool invoked', { codeChars: input.code.length })\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n logger.debug('search tool cache hit', { label: cached.label })\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n logger.warn('search tool call limit exceeded', { count })\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n logger.info('search tool errored', { durationMs: result.durationMs, err: result.error })\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n logger.info('search tool succeeded', { durationMs: result.durationMs, resultChars: truncated.length })\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerMcpTool(\n {\n name: 'execute',\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n logger.debug('execute tool invoked', { codeChars: input.code.length, userId: ctx.userId || 'unknown' })\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n logger.warn('execute tool call limit exceeded', { count })\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Cap API calls for safety. The mutation cap is enforced against the\n // actually-observed HTTP method, not a static scan of the source \u2014 so a\n // dynamically-built method (e.g. 'PO' + 'ST') can never escape it.\n const maxApiCalls = CODE_MODE_MAX_API_CALLS\n let apiCallCount = 0\n let mutationCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, (normalizedMethod) => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n if (isUnsafeHttpMethod(normalizedMethod)) {\n mutationCallCount++\n if (mutationCallCount > CODE_MODE_MAX_MUTATION_CALLS) {\n throw new Error(`Mutation API call limit exceeded (max ${CODE_MODE_MAX_MUTATION_CALLS})`)\n }\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n logger.info('execute tool errored', { durationMs: result.durationMs, apiCalls: apiCallCount, err: result.error })\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n logger.info('execute tool succeeded', { durationMs: result.durationMs, apiCalls: apiCallCount, resultChars: truncated.length })\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n */\nexport function createApiRequestFn(\n ctx: McpToolContext,\n onCall: (normalizedMethod: string) => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n return async (params) => {\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = String(method ?? '').toUpperCase()\n onCall(normalizedMethod)\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n logger.warn('api.request blocked by Code Mode RBAC', { method: normalizedMethod, path: apiPath, statusCode: authorization.statusCode, durationMs: callDuration })\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters \u2014 scope is enforced from ctx for every method, not only\n // GET, so AI-supplied tenantId/organizationId can never survive (see scope-injection).\n const queryParams = applyContextScopeToQuery(query, ctx)\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build request body with context-enforced scope\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\n requestBody = applyContextScopeToBody(body, ctx)\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request using host fetch (not sandbox)\n const response = await fetchWithTimeout(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n logger.debug('api.request completed with error status', { method: normalizedMethod, path: apiPath, status: response.status, durationMs: callDuration })\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n logger.debug('api.request completed', { method: normalizedMethod, path: apiPath, status: response.status, durationMs: callDuration, bytes: responseText.length })\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n\n if (isUnsafeApiRequestPath(path)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode rejected unsafe API path: ${normalizedMethod} ${path}`,\n }\n }\n\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nconst SINGLE_DOT_SEGMENTS = new Set(['.', '%2e'])\nconst DOUBLE_DOT_SEGMENTS = new Set(['..', '.%2e', '%2e.', '%2e%2e'])\n\n/**\n * Rejects request paths that the WHATWG URL parser would rewrite before the\n * actual fetch (`..`/`.` path segments \u2014 including their percent-encoded forms\n * \u2014 backslashes, and percent-encoded separators). Code Mode authorizes the\n * literal path it was given, but `new URL()` collapses dot segments and\n * normalizes backslashes for http(s) URLs, so without this guard the wire\n * request can resolve to a different endpoint than the one that was authorized.\n */\nexport function isUnsafeApiRequestPath(path: string): boolean {\n const [rawPath] = String(path ?? '').split('?')\n\n // The WHATWG URL parser strips ASCII tab/newline/carriage-return from the URL\n // before parsing, so a smuggled `.<TAB>.` segment collapses to `..` on the\n // wire even though the literal segment never equals a dot segment here. Raw\n // control characters never appear in legitimate REST paths, so reject them.\n if (/[\\u0000-\\u001f]/.test(rawPath)) {\n return true\n }\n\n // http(s) URLs treat backslashes as path separators, so they can smuggle\n // separators past the segment-based authorizer.\n if (rawPath.includes('\\\\')) {\n return true\n }\n\n // Percent-encoded separators never appear in legitimate REST paths and let\n // the literal-'/' segment split desync from the parsed request URL.\n if (/%2f/i.test(rawPath) || /%5c/i.test(rawPath)) {\n return true\n }\n\n return rawPath.split('/').some((segment) => {\n const lowered = segment.toLowerCase()\n return SINGLE_DOT_SEGMENTS.has(lowered) || DOUBLE_DOT_SEGMENTS.has(lowered)\n })\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nexport function isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\n\n/**\n * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { AiToolDefinition, McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { applyContextScopeToQuery, applyContextScopeToBody } from './scope-injection'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst logger = createLogger('ai_assistant').child({ component: 'codemode' })\n\nconst DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveAiApiRequestTimeoutMs(): number {\n const raw = process.env.AI_API_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS)\n}\n\n/**\n * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n logger.debug('Generated common type stubs', { count: typeLines.length - 1 })\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/** Maximum api.request() calls allowed per execute() run, regardless of method. */\nexport const CODE_MODE_MAX_API_CALLS = 50\n/** Maximum mutation (non-GET/HEAD/OPTIONS) api.request() calls allowed per execute() run. */\nexport const CODE_MODE_MAX_MUTATION_CALLS = 20\n\n/**\n * Register a Code Mode tool through the typed definition so the optional\n * metadata (`isMutation`, `isDestructive`) survives registration \u2014 the MCP\n * `tools/list` annotations are derived from those flags.\n */\nfunction registerCodeModeTool(tool: AiToolDefinition<{ code: string }>): void {\n registerMcpTool(tool, { moduleId: 'codemode' })\n}\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerCodeModeTool(\n {\n name: 'search',\n isMutation: false,\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n logger.debug('search tool invoked', { codeChars: input.code.length })\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n logger.debug('search tool cache hit', { label: cached.label })\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n logger.warn('search tool call limit exceeded', { count })\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n logger.info('search tool errored', { durationMs: result.durationMs, err: result.error })\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n logger.info('search tool succeeded', { durationMs: result.durationMs, resultChars: truncated.length })\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerCodeModeTool(\n {\n name: 'execute',\n // api.request() reaches every documented endpoint, including POST/PUT/DELETE,\n // so the tool is neither read-only nor guaranteed non-destructive. It is\n // intentionally exempt from prepareMutation: arbitrary sandbox code cannot\n // provide the structured before/after preview that approval flow requires.\n isMutation: true,\n isDestructive: true,\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n logger.debug('execute tool invoked', { codeChars: input.code.length, userId: ctx.userId || 'unknown' })\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n logger.warn('execute tool call limit exceeded', { count })\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Cap API calls for safety. The mutation cap is enforced against the\n // actually-observed HTTP method, not a static scan of the source \u2014 so a\n // dynamically-built method (e.g. 'PO' + 'ST') can never escape it.\n const maxApiCalls = CODE_MODE_MAX_API_CALLS\n let apiCallCount = 0\n let mutationCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, (normalizedMethod) => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n if (isUnsafeHttpMethod(normalizedMethod)) {\n mutationCallCount++\n if (mutationCallCount > CODE_MODE_MAX_MUTATION_CALLS) {\n throw new Error(`Mutation API call limit exceeded (max ${CODE_MODE_MAX_MUTATION_CALLS})`)\n }\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n logger.info('execute tool errored', { durationMs: result.durationMs, apiCalls: apiCallCount, err: result.error })\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n logger.info('execute tool succeeded', { durationMs: result.durationMs, apiCalls: apiCallCount, resultChars: truncated.length })\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n */\nexport function createApiRequestFn(\n ctx: McpToolContext,\n onCall: (normalizedMethod: string) => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n return async (params) => {\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = String(method ?? '').toUpperCase()\n onCall(normalizedMethod)\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n logger.warn('api.request blocked by Code Mode RBAC', { method: normalizedMethod, path: apiPath, statusCode: authorization.statusCode, durationMs: callDuration })\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters \u2014 scope is enforced from ctx for every method, not only\n // GET, so AI-supplied tenantId/organizationId can never survive (see scope-injection).\n const queryParams = applyContextScopeToQuery(query, ctx)\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build request body with context-enforced scope\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\n requestBody = applyContextScopeToBody(body, ctx)\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request using host fetch (not sandbox)\n const response = await fetchWithTimeout(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n logger.debug('api.request completed with error status', { method: normalizedMethod, path: apiPath, status: response.status, durationMs: callDuration })\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n logger.debug('api.request completed', { method: normalizedMethod, path: apiPath, status: response.status, durationMs: callDuration, bytes: responseText.length })\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n\n if (isUnsafeApiRequestPath(path)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode rejected unsafe API path: ${normalizedMethod} ${path}`,\n }\n }\n\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nconst SINGLE_DOT_SEGMENTS = new Set(['.', '%2e'])\nconst DOUBLE_DOT_SEGMENTS = new Set(['..', '.%2e', '%2e.', '%2e%2e'])\n\n/**\n * Rejects request paths that the WHATWG URL parser would rewrite before the\n * actual fetch (`..`/`.` path segments \u2014 including their percent-encoded forms\n * \u2014 backslashes, and percent-encoded separators). Code Mode authorizes the\n * literal path it was given, but `new URL()` collapses dot segments and\n * normalizes backslashes for http(s) URLs, so without this guard the wire\n * request can resolve to a different endpoint than the one that was authorized.\n */\nexport function isUnsafeApiRequestPath(path: string): boolean {\n const [rawPath] = String(path ?? '').split('?')\n\n // The WHATWG URL parser strips ASCII tab/newline/carriage-return from the URL\n // before parsing, so a smuggled `.<TAB>.` segment collapses to `..` on the\n // wire even though the literal segment never equals a dot segment here. Raw\n // control characters never appear in legitimate REST paths, so reject them.\n if (/[\\u0000-\\u001f]/.test(rawPath)) {\n return true\n }\n\n // http(s) URLs treat backslashes as path separators, so they can smuggle\n // separators past the segment-based authorizer.\n if (rawPath.includes('\\\\')) {\n return true\n }\n\n // Percent-encoded separators never appear in legitimate REST paths and let\n // the literal-'/' segment split desync from the parsed request URL.\n if (/%2f/i.test(rawPath) || /%5c/i.test(rawPath)) {\n return true\n }\n\n return rawPath.split('/').some((segment) => {\n const lowered = segment.toLowerCase()\n return SINGLE_DOT_SEGMENTS.has(lowered) || DOUBLE_DOT_SEGMENTS.has(lowered)\n })\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nexport function isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["memoryContext", "callDuration"]
7
7
  }
@@ -8,6 +8,7 @@ import { executeTool } from "./tool-executor.js";
8
8
  import { loadAllModuleTools, indexToolsForSearch } from "./tool-loader.js";
9
9
  import { extractApiKeyFromHeaders, hasRequiredFeatures } from "./auth.js";
10
10
  import { jsonSchemaToZod } from "./schema-utils.js";
11
+ import { buildMcpToolAnnotations } from "./mcp-tool-annotations.js";
11
12
  import { redactSecretForLog, deriveApiKeySessionId } from "./log-redaction.js";
12
13
  import { findApiKeyBySecret, findSessionApiKeyWithSecret } from "@open-mercato/core/modules/api_keys/services/apiKeyService";
13
14
  const logger = createLogger("ai_assistant").child({ component: "mcp-http" });
@@ -142,7 +143,8 @@ function createMcpServerForRequest(config, toolContext, apiKeyRecord) {
142
143
  tool.name,
143
144
  {
144
145
  description: tool.description,
145
- inputSchema: safeSchema
146
+ inputSchema: safeSchema,
147
+ annotations: buildMcpToolAnnotations(tool)
146
148
  },
147
149
  async (args) => {
148
150
  const toolArgs = args ?? {};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/http-server.ts"],
4
- "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod, toSafeZodSchema } from './schema-utils'\nimport { redactSecretForLog, deriveApiKeySessionId } from './log-redaction'\nimport type { McpServerConfig, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'\nimport { findApiKeyBySecret, findSessionApiKeyWithSecret } from '@open-mercato/core/modules/api_keys/services/apiKeyService'\n\nconst logger = createLogger('ai_assistant').child({ component: 'mcp-http' })\n\n/**\n * Options for the HTTP MCP server.\n */\nexport type McpHttpServerOptions = {\n config: McpServerConfig\n container: AwilixContainer\n port: number\n}\n\n/**\n * Resolve user context from session token.\n * Returns null if session token is invalid or expired.\n * Includes the decrypted API key secret for making authenticated API calls.\n */\nasync function resolveSessionContext(\n sessionToken: string,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const em = baseContext.container.resolve<EntityManager>('em')\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n\n // Look up ephemeral key by session token with decrypted secret\n const sessionResult = await findSessionApiKeyWithSecret(em, sessionToken)\n if (!sessionResult) {\n if (debug) {\n logger.debug('Session token not found, expired, or secret unavailable', { sessionToken: redactSecretForLog(sessionToken) })\n }\n return null\n }\n\n const { key: sessionKey, secret: sessionSecret } = sessionResult\n\n // Load ACL for the session user\n const userId = sessionKey.sessionUserId || sessionKey.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('Session key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('Session context resolved', {\n userId,\n tenantId: sessionKey.tenantId,\n organizationId: sessionKey.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n hasSessionSecret: !!sessionSecret,\n })\n }\n\n return {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n // Use the decrypted session secret for API calls (not the MCP server key)\n apiKeySecret: sessionSecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving session context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Resolve user context from the server-level API key (header-based auth fallback).\n * Used when no session token is provided \u2014 loads the API key's ACL for RBAC.\n */\nasync function resolveApiKeyContext(\n apiKeyRecord: ApiKey,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n const userId = apiKeyRecord.sessionUserId ?? apiKeyRecord.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('API key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${apiKeyRecord.id}`, {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('API key context resolved', {\n userId,\n tenantId: apiKeyRecord.tenantId,\n organizationId: apiKeyRecord.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n })\n }\n\n return {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n apiKeySecret: baseContext.apiKeySecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving API key context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Create a stateless MCP server instance for a single request.\n * Tools are registered without pre-filtering - permission checks happen at execution time\n * based on the session token provided in each tool call.\n */\nfunction createMcpServerForRequest(\n config: McpServerConfig,\n toolContext: McpToolContext,\n apiKeyRecord: ApiKey\n): McpServer {\n const server = new McpServer(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n if (config.debug) {\n logger.debug('Registering tools (ACL checked per-call via session token)', { toolCount: tools.length })\n }\n\n // Register ALL tools - permission checks happen at execution time via session token\n for (const tool of tools) {\n if (config.debug) {\n logger.debug('Registering tool', { toolName: tool.name })\n }\n\n // Convert Zod schema to a \"safe\" schema without Date types\n // This uses JSON Schema round-trip to avoid issues with MCP SDK's internal conversion\n // Also inject _sessionToken as an optional parameter so the AI knows to pass it\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n // Convert to JSON Schema first\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Inject _sessionToken into the JSON schema properties\n const properties = (jsonSchema.properties ?? {}) as Record<string, unknown>\n properties._sessionToken = {\n type: 'string',\n description: 'Session authorization token. If omitted, the server API key roles are used instead.',\n }\n jsonSchema.properties = properties\n\n // Convert back to Zod with passthrough to allow extra properties\n const converted = jsonSchemaToZod(jsonSchema)\n // Use type assertion since we know it's an object schema (we added properties above)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (config.debug) {\n logger.debug('Skipping tool: schema conversion failed', { toolName: tool.name, err: error })\n }\n continue\n }\n } else {\n // If no schema, create one with just _sessionToken\n safeSchema = z.object({\n _sessionToken: z\n .string()\n .optional()\n .describe('Session authorization token (REQUIRED for all tool calls)'),\n })\n }\n\n // Wrap in try/catch to handle any remaining edge cases\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n // Extract session token from args\n const sessionToken = toolArgs._sessionToken as string | undefined\n delete toolArgs._sessionToken // Remove before passing to tool handler\n\n // Always log tool calls for debugging\n logger.debug('Tool call received', {\n toolName: tool.name,\n hasSessionToken: !!sessionToken,\n argKeys: Object.keys(toolArgs ?? {}).join(','),\n })\n\n // Resolve user context from session token\n let effectiveContext = toolContext\n if (sessionToken) {\n const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug)\n if (sessionContext) {\n // Session context includes the decrypted API key secret + session ID for memory layer\n effectiveContext = { ...sessionContext, sessionId: sessionToken }\n } else {\n // Session token expired - return user-friendly error for AI to relay\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Your chat session has expired. Please close and reopen the chat window to continue.',\n code: 'SESSION_EXPIRED',\n }),\n },\n ],\n isError: true,\n }\n }\n } else {\n // No session token \u2014 fall back to header API key auth\n const apiKeyContext = await resolveApiKeyContext(apiKeyRecord, toolContext, config.debug)\n if (apiKeyContext) {\n effectiveContext = apiKeyContext\n } else if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Authentication failed: provide a session token (_sessionToken) or a valid API key with assigned roles',\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n\n // Derive a fallback sessionId from the API key so all tool calls\n // within the same MCP connection share a session memory cache\n if (!effectiveContext.sessionId && effectiveContext.apiKeySecret) {\n effectiveContext = {\n ...effectiveContext,\n sessionId: deriveApiKeySessionId(effectiveContext.apiKeySecret),\n }\n }\n }\n\n // Check if user has required permissions for this tool\n if (tool.requiredFeatures?.length) {\n const rbacService = effectiveContext.container.resolve<RbacService>('rbacService')\n const hasAccess = hasRequiredFeatures(\n tool.requiredFeatures,\n effectiveContext.userFeatures,\n effectiveContext.isSuperAdmin,\n rbacService\n )\n if (!hasAccess) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: `Insufficient permissions for tool \"${tool.name}\". Required: ${tool.requiredFeatures.join(', ')}`,\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n try {\n const result = await executeTool(tool.name, toolArgs, effectiveContext)\n\n if (!result.success) {\n logger.error('Tool call failed', { toolName: tool.name, err: result.error, code: result.errorCode })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n logger.debug('Tool call succeeded', { toolName: tool.name })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n } catch (err) {\n logger.error('Tool call threw', { toolName: tool.name, err })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: err instanceof Error ? err.message : 'Unknown error', code: 'EXCEPTION' }),\n },\n ],\n isError: true,\n }\n }\n }\n )\n } catch (error) {\n // Skip tools with schemas that can't be registered\n if (config.debug) {\n logger.debug('Skipping tool: registration failed', { toolName: tool.name, err: error })\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Maximum request body size (1MB).\n * Prevents memory exhaustion from oversized payloads.\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Run MCP server with HTTP transport (stateless mode).\n *\n * Each request creates a new MCP server instance and transport.\n * The server authenticates requests using API keys from the x-api-key header.\n */\nexport async function runMcpHttpServer(options: McpHttpServerOptions): Promise<void> {\n const { config, container, port } = options\n\n await loadAllModuleTools()\n\n // Generate and cache entity graph for understand_entity tool\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n logger.info('Entity graph generated', { entities: graph.nodes.length, relationships: graph.edges.length })\n } catch (error) {\n logger.warn('Entity graph generation skipped', { err: error })\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n logger.info('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n logger.warn('OpenAPI spec not available')\n }\n } catch (error) {\n logger.warn('OpenAPI spec caching skipped', { err: error })\n }\n\n // Index tools and entity schemas for hybrid search discovery (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n logger.info('Indexed entity schemas for hybrid search', { count })\n }\n }\n } catch (entityError) {\n logger.warn('Entity schema indexing skipped', { err: entityError })\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n logger.warn('Search indexing skipped (search service not available)', { err: error })\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n tools: getToolRegistry().listToolNames().length,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n logger.debug('Request received', { method: req.method, path: url.pathname })\n\n // Extract headers\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n // Server-level authentication via database lookup\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate API key against database (prefix lookup + bcrypt verify + expiry check)\n const em = container.resolve<EntityManager>('em')\n const apiKeyRecord = await findApiKeyBySecret(em, providedApiKey)\n if (!apiKeyRecord) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid or expired API key' }))\n return\n }\n\n if (config.debug) {\n logger.debug('Server-level auth passed', { method: req.method, keyPrefix: apiKeyRecord.keyPrefix })\n }\n\n // Create base tool context using API key's tenant/org scope\n // Session tokens can override with user-specific permissions\n const toolContext: McpToolContext = {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId: apiKeyRecord.createdBy ?? null,\n container,\n userFeatures: [],\n isSuperAdmin: false,\n apiKeySecret: providedApiKey,\n }\n\n try {\n // Create stateless transport (no session ID generator = stateless)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create new server for this request\n const mcpServer = createMcpServerForRequest(config, toolContext, apiKeyRecord)\n\n if (config.debug) {\n // Check registered tools on the server\n const registeredTools = (mcpServer as any)._registeredTools || {}\n logger.debug('Registered tools in McpServer', { toolNames: Object.keys(registeredTools).join(',') })\n logger.debug('Tool handlers initialized', { initialized: (mcpServer as any)._toolHandlersInitialized })\n }\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (config.debug) {\n logger.debug('Request completed, cleaned up')\n }\n })\n } catch (error) {\n logger.error('Error handling request', { err: error })\n if (!res.headersSent) {\n // Handle payload too large error\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n logger.info('Starting MCP HTTP server', { name: config.name, version: config.version })\n logger.info('Endpoint ready', { endpoint: `http://localhost:${port}/mcp` })\n logger.info('Health endpoint ready', { endpoint: `http://localhost:${port}/health` })\n logger.info('Tools registered', { toolCount })\n logger.info('Mode: stateless (new server per request)')\n logger.info('Server auth: API key validated against database (x-api-key header)')\n logger.info('User auth: session token (_sessionToken) preferred, falls back to API key roles')\n\n // Return a Promise that keeps the process alive until shutdown\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n logger.info('Server listening', { port })\n })\n\n const shutdown = async () => {\n logger.info('Shutting down')\n httpServer.close(() => {\n logger.info('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z, type ZodType } from 'zod'\nimport { getToolRegistry } from './tool-registry'\nimport { executeTool } from './tool-executor'\nimport { loadAllModuleTools, indexToolsForSearch } from './tool-loader'\nimport { authenticateMcpRequest, extractApiKeyFromHeaders, hasRequiredFeatures } from './auth'\nimport { jsonSchemaToZod, toSafeZodSchema } from './schema-utils'\nimport { buildMcpToolAnnotations } from './mcp-tool-annotations'\nimport { redactSecretForLog, deriveApiKeySessionId } from './log-redaction'\nimport type { McpServerConfig, McpToolContext } from './types'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'\nimport { findApiKeyBySecret, findSessionApiKeyWithSecret } from '@open-mercato/core/modules/api_keys/services/apiKeyService'\n\nconst logger = createLogger('ai_assistant').child({ component: 'mcp-http' })\n\n/**\n * Options for the HTTP MCP server.\n */\nexport type McpHttpServerOptions = {\n config: McpServerConfig\n container: AwilixContainer\n port: number\n}\n\n/**\n * Resolve user context from session token.\n * Returns null if session token is invalid or expired.\n * Includes the decrypted API key secret for making authenticated API calls.\n */\nasync function resolveSessionContext(\n sessionToken: string,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const em = baseContext.container.resolve<EntityManager>('em')\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n\n // Look up ephemeral key by session token with decrypted secret\n const sessionResult = await findSessionApiKeyWithSecret(em, sessionToken)\n if (!sessionResult) {\n if (debug) {\n logger.debug('Session token not found, expired, or secret unavailable', { sessionToken: redactSecretForLog(sessionToken) })\n }\n return null\n }\n\n const { key: sessionKey, secret: sessionSecret } = sessionResult\n\n // Load ACL for the session user\n const userId = sessionKey.sessionUserId || sessionKey.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('Session key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('Session context resolved', {\n userId,\n tenantId: sessionKey.tenantId,\n organizationId: sessionKey.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n hasSessionSecret: !!sessionSecret,\n })\n }\n\n return {\n tenantId: sessionKey.tenantId ?? null,\n organizationId: sessionKey.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n // Use the decrypted session secret for API calls (not the MCP server key)\n apiKeySecret: sessionSecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving session context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Resolve user context from the server-level API key (header-based auth fallback).\n * Used when no session token is provided \u2014 loads the API key's ACL for RBAC.\n */\nasync function resolveApiKeyContext(\n apiKeyRecord: ApiKey,\n baseContext: McpToolContext,\n debug?: boolean\n): Promise<McpToolContext | null> {\n try {\n const rbacService = baseContext.container.resolve<RbacService>('rbacService')\n const userId = apiKeyRecord.sessionUserId ?? apiKeyRecord.createdBy\n if (!userId) {\n if (debug) {\n logger.debug('API key has no associated user')\n }\n return null\n }\n\n const acl = await rbacService.loadAcl(`api_key:${apiKeyRecord.id}`, {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n })\n\n if (debug) {\n logger.debug('API key context resolved', {\n userId,\n tenantId: apiKeyRecord.tenantId,\n organizationId: apiKeyRecord.organizationId,\n features: acl.features.length,\n isSuperAdmin: acl.isSuperAdmin,\n })\n }\n\n return {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId,\n container: baseContext.container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n apiKeySecret: baseContext.apiKeySecret,\n }\n } catch (error) {\n if (debug) {\n logger.debug('Error resolving API key context', { err: error })\n }\n return null\n }\n}\n\n/**\n * Create a stateless MCP server instance for a single request.\n * Tools are registered without pre-filtering - permission checks happen at execution time\n * based on the session token provided in each tool call.\n */\nfunction createMcpServerForRequest(\n config: McpServerConfig,\n toolContext: McpToolContext,\n apiKeyRecord: ApiKey\n): McpServer {\n const server = new McpServer(\n { name: config.name, version: config.version },\n { capabilities: { tools: {} } }\n )\n\n const registry = getToolRegistry()\n const tools = Array.from(registry.getTools().values())\n\n if (config.debug) {\n logger.debug('Registering tools (ACL checked per-call via session token)', { toolCount: tools.length })\n }\n\n // Register ALL tools - permission checks happen at execution time via session token\n for (const tool of tools) {\n if (config.debug) {\n logger.debug('Registering tool', { toolName: tool.name })\n }\n\n // Convert Zod schema to a \"safe\" schema without Date types\n // This uses JSON Schema round-trip to avoid issues with MCP SDK's internal conversion\n // Also inject _sessionToken as an optional parameter so the AI knows to pass it\n let safeSchema: ZodType | undefined\n if (tool.inputSchema) {\n try {\n // Convert to JSON Schema first\n const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Inject _sessionToken into the JSON schema properties\n const properties = (jsonSchema.properties ?? {}) as Record<string, unknown>\n properties._sessionToken = {\n type: 'string',\n description: 'Session authorization token. If omitted, the server API key roles are used instead.',\n }\n jsonSchema.properties = properties\n\n // Convert back to Zod with passthrough to allow extra properties\n const converted = jsonSchemaToZod(jsonSchema)\n // Use type assertion since we know it's an object schema (we added properties above)\n safeSchema = (converted as z.ZodObject<any>).passthrough()\n } catch (error) {\n if (config.debug) {\n logger.debug('Skipping tool: schema conversion failed', { toolName: tool.name, err: error })\n }\n continue\n }\n } else {\n // If no schema, create one with just _sessionToken\n safeSchema = z.object({\n _sessionToken: z\n .string()\n .optional()\n .describe('Session authorization token (REQUIRED for all tool calls)'),\n })\n }\n\n // Wrap in try/catch to handle any remaining edge cases\n try {\n server.registerTool(\n tool.name,\n {\n description: tool.description,\n inputSchema: safeSchema,\n annotations: buildMcpToolAnnotations(tool),\n },\n async (args: unknown) => {\n const toolArgs = (args ?? {}) as Record<string, unknown>\n\n // Extract session token from args\n const sessionToken = toolArgs._sessionToken as string | undefined\n delete toolArgs._sessionToken // Remove before passing to tool handler\n\n // Always log tool calls for debugging\n logger.debug('Tool call received', {\n toolName: tool.name,\n hasSessionToken: !!sessionToken,\n argKeys: Object.keys(toolArgs ?? {}).join(','),\n })\n\n // Resolve user context from session token\n let effectiveContext = toolContext\n if (sessionToken) {\n const sessionContext = await resolveSessionContext(sessionToken, toolContext, config.debug)\n if (sessionContext) {\n // Session context includes the decrypted API key secret + session ID for memory layer\n effectiveContext = { ...sessionContext, sessionId: sessionToken }\n } else {\n // Session token expired - return user-friendly error for AI to relay\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Your chat session has expired. Please close and reopen the chat window to continue.',\n code: 'SESSION_EXPIRED',\n }),\n },\n ],\n isError: true,\n }\n }\n } else {\n // No session token \u2014 fall back to header API key auth\n const apiKeyContext = await resolveApiKeyContext(apiKeyRecord, toolContext, config.debug)\n if (apiKeyContext) {\n effectiveContext = apiKeyContext\n } else if (!effectiveContext.userId && effectiveContext.userFeatures.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Authentication failed: provide a session token (_sessionToken) or a valid API key with assigned roles',\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n\n // Derive a fallback sessionId from the API key so all tool calls\n // within the same MCP connection share a session memory cache\n if (!effectiveContext.sessionId && effectiveContext.apiKeySecret) {\n effectiveContext = {\n ...effectiveContext,\n sessionId: deriveApiKeySessionId(effectiveContext.apiKeySecret),\n }\n }\n }\n\n // Check if user has required permissions for this tool\n if (tool.requiredFeatures?.length) {\n const rbacService = effectiveContext.container.resolve<RbacService>('rbacService')\n const hasAccess = hasRequiredFeatures(\n tool.requiredFeatures,\n effectiveContext.userFeatures,\n effectiveContext.isSuperAdmin,\n rbacService\n )\n if (!hasAccess) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: `Insufficient permissions for tool \"${tool.name}\". Required: ${tool.requiredFeatures.join(', ')}`,\n code: 'UNAUTHORIZED',\n }),\n },\n ],\n isError: true,\n }\n }\n }\n\n try {\n const result = await executeTool(tool.name, toolArgs, effectiveContext)\n\n if (!result.success) {\n logger.error('Tool call failed', { toolName: tool.name, err: result.error, code: result.errorCode })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: result.error, code: result.errorCode }),\n },\n ],\n isError: true,\n }\n }\n\n logger.debug('Tool call succeeded', { toolName: tool.name })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n }\n } catch (err) {\n logger.error('Tool call threw', { toolName: tool.name, err })\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: err instanceof Error ? err.message : 'Unknown error', code: 'EXCEPTION' }),\n },\n ],\n isError: true,\n }\n }\n }\n )\n } catch (error) {\n // Skip tools with schemas that can't be registered\n if (config.debug) {\n logger.debug('Skipping tool: registration failed', { toolName: tool.name, err: error })\n }\n continue\n }\n }\n\n return server\n}\n\n/**\n * Maximum request body size (1MB).\n * Prevents memory exhaustion from oversized payloads.\n */\nconst MAX_BODY_SIZE = 1 * 1024 * 1024\n\n/**\n * Parse JSON body from request with size limit.\n */\nasync function parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalSize = 0\n\n req.on('data', (chunk: Buffer) => {\n totalSize += chunk.length\n if (totalSize > MAX_BODY_SIZE) {\n req.destroy()\n reject(new Error('Request payload too large'))\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n const body = Buffer.concat(chunks).toString('utf-8')\n resolve(body ? JSON.parse(body) : undefined)\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\n/**\n * Run MCP server with HTTP transport (stateless mode).\n *\n * Each request creates a new MCP server instance and transport.\n * The server authenticates requests using API keys from the x-api-key header.\n */\nexport async function runMcpHttpServer(options: McpHttpServerOptions): Promise<void> {\n const { config, container, port } = options\n\n await loadAllModuleTools()\n\n // Generate and cache entity graph for understand_entity tool\n try {\n const { extractEntityGraph, cacheEntityGraph } = await import('./entity-graph')\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n cacheEntityGraph(graph)\n logger.info('Entity graph generated', { entities: graph.nodes.length, relationships: graph.edges.length })\n } catch (error) {\n logger.warn('Entity graph generation skipped', { err: error })\n }\n\n // Pre-cache rich OpenAPI spec for Code Mode search tool (prefers runtime module registry over static JSON)\n try {\n const { loadRichOpenApiSpec } = await import('./api-endpoint-index')\n const spec = await loadRichOpenApiSpec()\n if (spec) {\n logger.info('Rich OpenAPI spec cached for Code Mode (with requestBody schemas)')\n } else {\n logger.warn('OpenAPI spec not available')\n }\n } catch (error) {\n logger.warn('OpenAPI spec caching skipped', { err: error })\n }\n\n // Index tools and entity schemas for hybrid search discovery (if search service available)\n try {\n const searchService = container.resolve('searchService') as SearchService\n\n // Index MCP tools\n await indexToolsForSearch(searchService)\n\n // Index entity schemas for hybrid search\n try {\n const { getCachedEntityGraph } = await import('./entity-graph')\n const { indexEntitiesForSearch } = await import('./entity-index')\n const graph = getCachedEntityGraph()\n if (graph) {\n const { count } = await indexEntitiesForSearch(searchService, graph)\n if (count > 0) {\n logger.info('Indexed entity schemas for hybrid search', { count })\n }\n }\n } catch (entityError) {\n logger.warn('Entity schema indexing skipped', { err: entityError })\n }\n } catch (error) {\n // Search service might not be configured - discovery will use fallback\n logger.warn('Search indexing skipped (search service not available)', { err: error })\n }\n\n const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n\n // Health check endpoint\n if (url.pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n status: 'ok',\n tools: getToolRegistry().listToolNames().length,\n timestamp: new Date().toISOString(),\n }))\n return\n }\n\n if (url.pathname !== '/mcp') {\n res.writeHead(404, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Not found' }))\n return\n }\n\n logger.debug('Request received', { method: req.method, path: url.pathname })\n\n // Extract headers\n const headers: Record<string, string | undefined> = {}\n for (const [key, value] of Object.entries(req.headers)) {\n headers[key] = Array.isArray(value) ? value[0] : value\n }\n\n // Server-level authentication via database lookup\n const providedApiKey = extractApiKeyFromHeaders(headers)\n if (!providedApiKey) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'API key required (x-api-key header)' }))\n return\n }\n\n // Validate API key against database (prefix lookup + bcrypt verify + expiry check)\n const em = container.resolve<EntityManager>('em')\n const apiKeyRecord = await findApiKeyBySecret(em, providedApiKey)\n if (!apiKeyRecord) {\n res.writeHead(401, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Invalid or expired API key' }))\n return\n }\n\n if (config.debug) {\n logger.debug('Server-level auth passed', { method: req.method, keyPrefix: apiKeyRecord.keyPrefix })\n }\n\n // Create base tool context using API key's tenant/org scope\n // Session tokens can override with user-specific permissions\n const toolContext: McpToolContext = {\n tenantId: apiKeyRecord.tenantId ?? null,\n organizationId: apiKeyRecord.organizationId ?? null,\n userId: apiKeyRecord.createdBy ?? null,\n container,\n userFeatures: [],\n isSuperAdmin: false,\n apiKeySecret: providedApiKey,\n }\n\n try {\n // Create stateless transport (no session ID generator = stateless)\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: req.method === 'POST',\n })\n\n // Create new server for this request\n const mcpServer = createMcpServerForRequest(config, toolContext, apiKeyRecord)\n\n if (config.debug) {\n // Check registered tools on the server\n const registeredTools = (mcpServer as any)._registeredTools || {}\n logger.debug('Registered tools in McpServer', { toolNames: Object.keys(registeredTools).join(',') })\n logger.debug('Tool handlers initialized', { initialized: (mcpServer as any)._toolHandlersInitialized })\n }\n\n // Connect server to transport\n await mcpServer.connect(transport)\n\n // Handle the request\n if (req.method === 'POST') {\n const body = await parseJsonBody(req)\n await transport.handleRequest(req, res, body)\n } else {\n await transport.handleRequest(req, res)\n }\n\n // Cleanup after response finishes\n res.on('finish', () => {\n transport.close()\n mcpServer.close()\n if (config.debug) {\n logger.debug('Request completed, cleaned up')\n }\n })\n } catch (error) {\n logger.error('Error handling request', { err: error })\n if (!res.headersSent) {\n // Handle payload too large error\n if (error instanceof Error && error.message === 'Request payload too large') {\n res.writeHead(413, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({ error: 'Request payload too large (max 1MB)' }))\n return\n }\n\n res.writeHead(500, { 'Content-Type': 'application/json' })\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: `Internal server error: ${error instanceof Error ? error.message : String(error)}`,\n },\n id: null,\n })\n )\n }\n }\n })\n\n const toolCount = getToolRegistry().listToolNames().length\n\n logger.info('Starting MCP HTTP server', { name: config.name, version: config.version })\n logger.info('Endpoint ready', { endpoint: `http://localhost:${port}/mcp` })\n logger.info('Health endpoint ready', { endpoint: `http://localhost:${port}/health` })\n logger.info('Tools registered', { toolCount })\n logger.info('Mode: stateless (new server per request)')\n logger.info('Server auth: API key validated against database (x-api-key header)')\n logger.info('User auth: session token (_sessionToken) preferred, falls back to API key roles')\n\n // Return a Promise that keeps the process alive until shutdown\n return new Promise<void>((resolve) => {\n httpServer.listen(port, () => {\n logger.info('Server listening', { port })\n })\n\n const shutdown = async () => {\n logger.info('Shutting down')\n httpServer.close(() => {\n logger.info('Server closed')\n resolve()\n })\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { toolInputJsonSchema } from "./tool-input-schema.js";
2
+ import { buildMcpToolAnnotations } from "./mcp-tool-annotations.js";
2
3
  import { getToolRegistry } from "./tool-registry.js";
3
4
  import { executeTool } from "./tool-executor.js";
4
5
  import { loadAllModuleTools } from "./tool-loader.js";
@@ -70,7 +71,8 @@ class InProcessMcpClient {
70
71
  return accessibleTools.map((tool) => ({
71
72
  name: tool.name,
72
73
  description: tool.description,
73
- inputSchema: toolInputJsonSchema(tool.inputSchema)
74
+ inputSchema: toolInputJsonSchema(tool.inputSchema),
75
+ annotations: buildMcpToolAnnotations(tool)
74
76
  }));
75
77
  }
76
78
  /**