@happyvertical/smrt-core 0.40.54 → 0.40.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +4 -3
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +28 -40
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +23 -29
- package/dist/generators/mcp.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +2 -1
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +2 -1
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/static-manifest.js +2 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.d.ts.map +1 -1
- package/dist/manifest/store.js +3 -2
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest/test-manifest-stub.js +2 -2
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +2 -1
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +33 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js +37 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/schema/code-generator.d.ts.map +1 -1
- package/dist/schema/code-generator.js +2 -1
- package/dist/schema/code-generator.js.map +1 -1
- package/dist/scripts/generate-manifest.js +14 -2
- package/dist/smrt-knowledge.json +4 -4
- package/dist/vite-plugin/index.d.ts +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +18 -4
- package/dist/vite-plugin/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","names":[],"sources":["../../src/generators/mcp.ts"],"sourcesContent":["/**\n * MCP (Model Context Protocol) server generator for smrt objects\n *\n * Exposes smrt objects as AI tools for Claude, GPT, and other AI models\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { SmrtCollection } from '../collection';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass } from '../registry/types.js';\nimport type { FieldDefinition, MethodDefinition } from '../scanner/types.js';\nimport {\n buildCustomActionInputSchema,\n buildCustomActionInvocationArgs,\n type CustomActionFailure,\n type CustomActionMetadata,\n customActionParameterInputName,\n normalizeCustomActionFailure,\n resolveCustomActionMetadata,\n SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY,\n} from './custom-action.js';\nimport {\n generateClaudeConfig,\n generateMCPDocumentation,\n generateMCPScript,\n generateRuntimeBootstrap,\n type RuntimeOptions,\n} from './mcp-runtime-template.js';\nimport { runWithTenantGate } from './tenant-gate.js';\n\n/**\n * A JSON Schema fragment for an MCP tool input property. The shape varies by\n * field kind (string/number/object/…), so values are `unknown`; this is only\n * ever serialized into the generated tool definitions, never read back.\n */\ntype McpJsonSchema = Record<string, unknown>;\n\n/**\n * Runtime tool-call arguments. They arrive as untyped JSON from the MCP client,\n * so individual keys are narrowed (`as`) at each action's boundary.\n */\ntype ToolArgs = Record<string, unknown>;\n\n/**\n * A method resolved dynamically (by action name) from an object/collection\n * instance and invoked with the parsed tool-call arguments. Narrowed to this\n * type at the call boundary after a `typeof === 'function'` guard.\n */\ntype InstanceCallable = (...args: unknown[]) => unknown;\n\nexport interface MCPConfig {\n name?: string;\n version?: string;\n description?: string;\n server?: {\n name: string;\n version: string;\n };\n}\n\nexport interface MCPContext {\n db?: unknown;\n ai?: unknown;\n user?: {\n id: string;\n roles?: string[];\n };\n /** Resolved permission slugs held by the caller. */\n permissions?: Iterable<string>;\n /**\n * Tenant the calling principal is scoped to (#1554). When set, tenant-scoped\n * tools run inside this tenant's context. Hosts that authenticate a principal\n * (e.g. `@happyvertical/smrt-app-mcp`) should derive it from the principal —\n * the MCP analogue of the SvelteKit auth hook setting `locals.tenantId`.\n */\n tenantId?: string;\n /**\n * Explicit operator opt-in to cross-tenant access for tenant-scoped tools\n * (#1554). Only set for trusted operator/admin callers. Without a `tenantId`\n * or this flag, tenant-scoped tool calls fail closed when tenancy is enabled.\n */\n allowCrossTenant?: boolean;\n}\n\nexport interface MCPTool {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, McpJsonSchema>;\n required?: string[];\n };\n}\n\nexport interface MCPRequest {\n method: string;\n params: {\n name: string;\n arguments: ToolArgs;\n };\n}\n\nexport interface MCPResponse {\n content: Array<{\n type: 'text';\n text: string;\n }>;\n isError?: boolean;\n _meta?: Record<string, unknown>;\n}\n\nclass CustomActionFailureError extends Error {\n constructor(readonly failure: CustomActionFailure) {\n super(failure.message);\n this.name = 'CustomActionFailureError';\n }\n}\n\n/**\n * MCP tool identifiers are lowercase for stable protocol vocabulary, while\n * JavaScript method names retain their declared casing. Resolve a tool suffix\n * back to the registry's canonical method name before inspecting metadata or\n * invoking it.\n */\nfunction resolveCustomActionMethod(\n methods: Map<string, MethodDefinition>,\n toolAction: string,\n): [methodName: string, method: MethodDefinition | undefined] {\n const direct = methods.get(toolAction);\n if (direct) return [toolAction, direct];\n for (const [methodName, method] of methods) {\n if (methodName.toLowerCase() === toolAction.toLowerCase()) {\n return [methodName, method];\n }\n }\n return [toolAction, undefined];\n}\n\n/**\n * Generate MCP server from smrt objects\n */\nexport class MCPGenerator {\n private config: MCPConfig;\n private context: MCPContext;\n private collections = new Map<string, SmrtCollection<SmrtObject>>();\n\n constructor(config: MCPConfig = {}, context: MCPContext = {}) {\n this.config = {\n name: 'smrt-mcp-server',\n version: '1.0.0',\n description: 'Auto-generated MCP server from smrt objects',\n server: {\n name: 'smrt-mcp',\n version: '1.0.0',\n },\n ...config,\n };\n this.context = context;\n }\n\n /**\n * Get server name\n */\n get name(): string | undefined {\n return this.config.name;\n }\n\n /**\n * Get server version\n */\n get version(): string | undefined {\n return this.config.version;\n }\n\n /**\n * Generate all available tools from registered objects\n */\n async generateTools(): Promise<MCPTool[]> {\n const tools: MCPTool[] = [];\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name for tool naming, map key for registry lookups\n const simpleName = classInfo.name || key;\n const config = ObjectRegistry.getConfig(simpleName);\n const mcpConfig = config.mcp;\n\n // `mcp: false` disables MCP generation entirely for the class. Without\n // this gate an `include` list still leaked custom-method tools (the\n // custom-method branch historically only honored `include` when it\n // listed custom methods), so `false` is the only fully fail-closed\n // switch. Mirrors rest.ts's `apiConfig === false` short-circuit\n // (#1540 / #1546).\n if (mcpConfig === false) {\n continue;\n }\n\n // Handle boolean vs object config\n const excluded: string[] =\n typeof mcpConfig === 'object' && mcpConfig?.exclude\n ? mcpConfig.exclude\n : [];\n const included: string[] | undefined =\n typeof mcpConfig === 'object' ? mcpConfig?.include : undefined;\n\n const shouldInclude = (endpoint: string) => {\n if (included && !included.includes(endpoint)) return false;\n if (excluded.includes(endpoint)) return false;\n return true;\n };\n\n const objectTools = await this.generateObjectTools(\n simpleName,\n shouldInclude,\n );\n tools.push(...objectTools);\n }\n\n return tools;\n }\n\n /**\n * Generate tools for a specific object\n */\n private async generateObjectTools(\n objectName: string,\n shouldInclude: (endpoint: string) => boolean,\n ): Promise<MCPTool[]> {\n const tools: MCPTool[] = [];\n const fields = ObjectRegistry.getFields(objectName);\n const lowerName = objectName.toLowerCase();\n const classInfo = ObjectRegistry.getClass(objectName);\n\n // LIST tool\n if (shouldInclude('list')) {\n tools.push({\n name: `${lowerName}_list`,\n description: `List ${objectName} objects with optional filtering`,\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'integer',\n description: 'Maximum number of items to return',\n default: 50,\n minimum: 1,\n maximum: 1000,\n },\n offset: {\n type: 'integer',\n description: 'Number of items to skip',\n default: 0,\n minimum: 0,\n },\n orderBy: {\n type: 'string',\n description: 'Field to order by (e.g., \"created_at DESC\")',\n },\n where: {\n type: 'object',\n description: 'Filter conditions as key-value pairs',\n additionalProperties: true,\n },\n },\n },\n });\n }\n\n // GET tool\n if (shouldInclude('get')) {\n tools.push({\n name: `${lowerName}_get`,\n description: `Get a specific ${objectName} by ID or slug`,\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the object',\n },\n slug: {\n type: 'string',\n description: 'URL-friendly identifier of the object',\n },\n },\n required: ['id'],\n },\n });\n }\n\n // CREATE tool\n if (shouldInclude('create')) {\n const properties: Record<string, McpJsonSchema> = {};\n const required: string[] = [];\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = this.fieldToMCPSchema(field);\n if (field._meta?.required) {\n required.push(fieldName);\n }\n }\n\n tools.push({\n name: `${lowerName}_create`,\n description: `Create a new ${objectName}`,\n inputSchema: {\n type: 'object',\n properties,\n required,\n },\n });\n }\n\n // UPDATE tool\n if (shouldInclude('update')) {\n const properties: Record<string, McpJsonSchema> = {\n id: {\n type: 'string',\n description: 'ID of the object to update',\n },\n };\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = this.fieldToMCPSchema(field);\n }\n\n tools.push({\n name: `${lowerName}_update`,\n description: `Update an existing ${objectName}`,\n inputSchema: {\n type: 'object',\n properties,\n required: ['id'],\n },\n });\n }\n\n // DELETE tool\n if (shouldInclude('delete')) {\n tools.push({\n name: `${lowerName}_delete`,\n description: `Delete a ${objectName} by ID`,\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'ID of the object to delete',\n },\n },\n required: ['id'],\n },\n });\n }\n\n // CUSTOM METHODS - discover from manifest and show by default\n if (classInfo) {\n const config = ObjectRegistry.getConfig(objectName);\n const mcpConfig = config.mcp;\n const included: string[] | undefined =\n typeof mcpConfig === 'object' ? mcpConfig?.include : undefined;\n const excluded: string[] =\n typeof mcpConfig === 'object' && mcpConfig?.exclude\n ? mcpConfig.exclude\n : [];\n\n // When an `include` list is present it is the COMPLETE allowlist for\n // this surface: a custom (non-CRUD) method is exposed ONLY if its name\n // appears in `include`. Without an include list we keep the historical\n // default of auto-exposing every public method. This closes the leak\n // where `mcp: { include: ['list', 'get'] }` still emitted custom-method\n // tools like `payment_recordpayment` because `include` only gated CRUD\n // verbs (#1540 / #1390).\n const crudOperations = ['list', 'get', 'create', 'update', 'delete'];\n const customMethodsInInclude =\n included?.filter((item) => !crudOperations.includes(item)) || [];\n // An include list (even one naming only CRUD verbs) switches custom\n // methods into strict allowlist mode.\n const hasIncludeList = included !== undefined;\n\n // Try to discover methods from manifest (including inherited methods)\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const methodNames = new Set(Array.from(methods.keys()));\n\n // Strict mode: an include list is present, so only the custom methods it\n // names are generated (may be none, e.g. include: ['list', 'get']).\n if (hasIncludeList) {\n for (const methodName of customMethodsInInclude) {\n // Skip if explicitly excluded\n if (excluded.includes(methodName)) continue;\n\n // Check if method exists (in manifest or on class prototype)\n const existsInManifest = methodNames.has(methodName);\n const existsOnClass = this.validateCustomMethod(\n classInfo.constructor,\n methodName,\n );\n\n if (!existsInManifest && !existsOnClass) {\n // Warn about missing methods\n console.warn(\n `Warning: Custom action '${methodName}' specified in MCP config for ${objectName}, but method ${methodName}() not found on class`,\n );\n continue;\n }\n\n // A non-public method must never be exposed as a tool, even when it\n // is explicitly named in `include`. This keeps strict-include mode\n // consistent with the non-strict path below, which gates on\n // `methodDef.isPublic`. Listing a private method in `include` is a\n // config mistake, not an override of method visibility (#1540).\n //\n // The scanner strips private/protected methods from the manifest, so\n // when a non-public method is named in `include` it is absent from\n // `methods` and is only resolvable via validateCustomMethod() on the\n // runtime prototype (TS access modifiers are erased at runtime). Such\n // a method must NOT be emitted. We still allow methods that are\n // present on the class but legitimately absent from the manifest\n // (e.g. inline/dynamically registered classes), so the guard fires\n // only when there is a public manifest entry to anchor on OR the\n // method exists solely as a stripped (non-public) manifest method.\n const methodDef = methods.get(methodName);\n if (methodDef && !methodDef.isPublic) continue;\n\n tools.push(\n this.buildCustomActionTool(\n objectName,\n lowerName,\n methodName,\n methodDef,\n this.hasCollectionReceiver(classInfo),\n ),\n );\n }\n } else {\n // No custom methods in include = show all discovered methods by default\n for (const [methodName, methodDef] of methods) {\n // Skip if not public (private/protected methods shouldn't be in MCP)\n if (!methodDef.isPublic) continue;\n\n // Always respect exclude list\n if (excluded.includes(methodName)) continue;\n\n tools.push(\n this.buildCustomActionTool(\n objectName,\n lowerName,\n methodName,\n methodDef,\n this.hasCollectionReceiver(classInfo),\n ),\n );\n }\n }\n }\n\n return tools;\n }\n\n private buildCustomActionTool(\n objectName: string,\n lowerName: string,\n methodName: string,\n methodDef?: MethodDefinition,\n collectionReceiver = false,\n ): MCPTool {\n const metadata = this.resolveCustomActionMetadata(\n objectName,\n methodName,\n methodDef,\n collectionReceiver,\n );\n return {\n name: `${lowerName}_${methodName}`.toLowerCase(),\n description: `Execute ${methodName} action on ${objectName}`,\n inputSchema: buildCustomActionInputSchema(\n metadata,\n ) as MCPTool['inputSchema'],\n };\n }\n\n private resolveCustomActionMetadata(\n objectName: string,\n action: string,\n method?: MethodDefinition,\n collectionReceiver = false,\n ): CustomActionMetadata {\n return resolveCustomActionMetadata({\n actionName: action,\n method,\n apiConfig: ObjectRegistry.getConfig(objectName).api,\n ...(collectionReceiver ? { defaultScope: 'collection' } : {}),\n });\n }\n\n private hasCollectionReceiver(classInfo?: RegisteredClass): boolean {\n return (\n !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection\n );\n }\n\n /**\n * Validate that a custom method exists on a class\n */\n private validateCustomMethod(\n classConstructor: typeof SmrtObject,\n methodName: string,\n ): boolean {\n try {\n // Check if method exists on the prototype\n const prototype = classConstructor.prototype;\n\n // Check if the method exists and is a function. Dynamic name lookup, so\n // index through a record view of the prototype/constructor.\n if (\n typeof (prototype as unknown as Record<string, unknown>)[methodName] ===\n 'function'\n ) {\n return true;\n }\n\n // Also check static methods\n if (\n typeof (classConstructor as unknown as Record<string, unknown>)[\n methodName\n ] === 'function'\n ) {\n return true;\n }\n\n return false;\n } catch (error) {\n console.warn(\n `Error validating method ${methodName} on class ${classConstructor.name}:`,\n error,\n );\n return false;\n }\n }\n\n /**\n * Convert field definition to MCP schema\n */\n private fieldToMCPSchema(field: FieldDefinition): McpJsonSchema {\n const schema: McpJsonSchema = {\n description: field._meta?.description || `${field.type} field`,\n };\n\n switch (field.type) {\n case 'text':\n schema.type = 'string';\n if (field._meta?.maxLength) schema.maxLength = field._meta.maxLength;\n if (field._meta?.minLength) schema.minLength = field._meta.minLength;\n break;\n case 'integer':\n schema.type = 'integer';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'decimal':\n schema.type = 'number';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'boolean':\n schema.type = 'boolean';\n break;\n case 'datetime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'json':\n schema.type = 'object';\n break;\n case 'foreignKey':\n schema.type = 'string';\n // Lockstep with tool-schema.ts fieldTypeToJsonSchema: the generic\n // relation hint is a fallback — an authored description wins (#2046).\n if (!field._meta?.description) {\n schema.description = `ID of related ${field.related || 'object'}`;\n }\n break;\n default:\n schema.type = 'string';\n }\n\n if (field._meta?.default !== undefined) {\n schema.default = field._meta.default;\n }\n\n return schema;\n }\n\n /**\n * Handle MCP tool calls\n */\n async handleToolCall(request: MCPRequest): Promise<MCPResponse> {\n const { name, arguments: args } = request.params;\n\n try {\n // Check if tool exists\n const availableTools = await this.generateTools();\n const toolExists = availableTools.some((t) => t.name === name);\n\n if (!toolExists) {\n throw new Error(`Unknown tool: ${name}`);\n }\n\n // Parse tool name: `objectname_action`. Split on the FIRST underscore\n // only — a custom method name can itself contain underscores (e.g.\n // `record_payment` → tool `invoice_record_payment`). A naive\n // `name.split('_')` would take `action` as just `record` and mis-route\n // the call. The emitted stdio servers switch on the full tool name, so\n // splitting greedily here also kept the in-process path divergent (#1378).\n const firstUnderscore = name.indexOf('_');\n const objectName =\n firstUnderscore === -1 ? '' : name.slice(0, firstUnderscore);\n const action =\n firstUnderscore === -1 ? '' : name.slice(firstUnderscore + 1);\n\n if (!objectName || !action) {\n throw new Error(`Invalid tool name format: ${name}`);\n }\n\n // Find the registered class (case-insensitive)\n const registeredClasses = ObjectRegistry.getAllClasses();\n let classInfo = null;\n let actualObjectName = '';\n\n for (const [_key, info] of registeredClasses) {\n // Issue #951: Match by simple name, not the qualified map key\n const simpleName = info.name || _key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n classInfo = info;\n actualObjectName = simpleName;\n break;\n }\n }\n\n if (!classInfo) {\n throw new Error(`Object type '${objectName}' not found`);\n }\n\n // Get or create collection\n const collection = await this.getCollection(actualObjectName, classInfo);\n\n // Execute the action\n const result = await this.executeAction(\n collection,\n action,\n args,\n actualObjectName,\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (error) {\n if (error instanceof CustomActionFailureError) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: error.failure }),\n },\n ],\n isError: true,\n _meta: {\n [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: error.failure,\n },\n };\n }\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n };\n }\n }\n\n /**\n * Get or create collection for an object\n */\n private async getCollection(\n objectName: string,\n classInfo: RegisteredClass,\n ): Promise<SmrtCollection<SmrtObject>> {\n if (!this.collections.has(objectName)) {\n // Ensure we have a valid collection constructor\n if (\n !classInfo.collectionConstructor ||\n typeof classInfo.collectionConstructor !== 'function'\n ) {\n throw new Error(\n `No valid collection constructor found for ${objectName}`,\n );\n }\n\n const collection = new classInfo.collectionConstructor({\n ai: this.context.ai,\n db: this.context.db,\n });\n\n // Verify the collection is actually a SmrtCollection instance\n if (!(collection instanceof SmrtCollection)) {\n throw new Error(\n `Collection for ${objectName} must extend SmrtCollection`,\n );\n }\n\n // Initialize the collection (database setup, etc.)\n await collection.initialize();\n\n this.collections.set(objectName, collection);\n }\n const collection = this.collections.get(objectName);\n if (!collection) {\n throw new Error(`Collection for ${objectName} not found`);\n }\n return collection;\n }\n\n /**\n * Serialize a tool-response payload, excluding sensitive fields (#1540).\n * Recurses through arrays and plain objects so a SmrtObject nested inside a\n * custom-action result (e.g. `{ item }`) is also stripped — `JSON.stringify`\n * would otherwise call its `toJSON()`. Non-plain instances (Date, etc.) and\n * primitives pass through unchanged; a cycle guard prevents infinite loops.\n */\n private toPublicData(\n value: unknown,\n seen: WeakSet<object> = new WeakSet(),\n options: PublicJsonOptions = this.getPublicJsonOptions(),\n ): unknown {\n if (value === null || typeof value !== 'object') return value;\n const publicSource = value as {\n toPublicJSON?: (options?: PublicJsonOptions) => unknown;\n };\n if (typeof publicSource.toPublicJSON === 'function') {\n return publicSource.toPublicJSON(options);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry) => this.toPublicData(entry, seen, options));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = this.toPublicData(entry, seen, options);\n }\n return out;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * `@field({ readonly: true })` fields from a create/update body, and — when an\n * `@smrt({ api: { writable: [...] } })` allowlist is set — intersect with it.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') {\n return {};\n }\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Execute action on collection\n */\n /**\n * Fail-closed authorization for tool calls (#1540). Mutating tools\n * (create/update/delete + custom actions) require an authenticated principal\n * (`context.user`) unless the object opts out via `@smrt({ api: { public } })`.\n * Reads are allowed when `public` is `true` or `'read'`.\n */\n private requireToolAuth(\n objectName: string | undefined,\n mutating: boolean,\n ): void {\n const apiConfig = objectName\n ? ObjectRegistry.getConfig(objectName)?.api\n : undefined;\n const publicAccess =\n apiConfig && typeof apiConfig === 'object'\n ? (apiConfig as { public?: boolean | 'read' }).public\n : undefined;\n\n if (publicAccess === true) return;\n if (publicAccess === 'read' && !mutating) return;\n if (!this.context.user) {\n throw new Error('Authentication required');\n }\n }\n\n private async executeAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n const mutating = action !== 'list' && action !== 'get';\n this.requireToolAuth(objectName, mutating);\n\n // Fail-closed tenant context (#1554). For tenant-scoped objects, establish\n // the context from the principal's tenant (or an explicit cross-tenant\n // opt-in); without either, this throws when tenancy is enabled rather than\n // letting an optional-scoped read range across all tenants. Tenant-scoping\n // is resolved inside tenancy by class name so it matches the interceptor.\n return runWithTenantGate(\n {\n className: objectName,\n tenantId: this.context.tenantId,\n allowCrossTenant: this.context.allowCrossTenant,\n surface: 'MCP',\n },\n () => this.runAction(collection, action, args, objectName),\n );\n }\n\n /**\n * Derive the set of tenant-scoped object names (lowercased simple names) from\n * a generated tool list, for the emitted runtime template's tenant gate\n * (#1554). Tool names are `objectname_action`.\n *\n * Detection uses ONLY tenancy's `isTenantScopedClass` (the authoritative\n * source the interceptor uses; covers `@TenantScoped`), consulted via an\n * optional dynamic import. We deliberately do NOT fall back to core's\n * `ObjectRegistry.isTenantScoped`: a `@smrt({ tenantScoped })` model can exist\n * in an app that has NOT installed `@happyvertical/smrt-tenancy`, and emitting\n * the static `import { runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy'`\n * gate for it would crash the generated server at import time. When tenancy is\n * absent there is also nothing to enforce, so emitting no gate is correct.\n */\n private async tenantScopedObjectNames(tools: MCPTool[]): Promise<string[]> {\n let isTenantScopedClass: ((name: string) => boolean) | undefined;\n try {\n // Held in a variable so neither TypeScript's declaration emit (TS2307 —\n // core deliberately does not depend on tenancy) nor the bundler tries to\n // statically resolve this optional sibling package. Same pattern as\n // `tenant-gate.ts` / `embeddings/provider.ts`.\n const tenancySpecifier = '@happyvertical/smrt-tenancy';\n const tenancy = (await import(/* @vite-ignore */ tenancySpecifier)) as {\n isTenantScopedClass?: (name: string) => boolean;\n };\n isTenantScopedClass = tenancy.isTenantScopedClass;\n } catch {\n isTenantScopedClass = undefined;\n }\n\n // No tenancy package installed → no gate can run, emit none.\n if (typeof isTenantScopedClass !== 'function') return [];\n\n const scoped = new Set<string>();\n for (const tool of tools) {\n const [objectName] = tool.name.split('_');\n if (!objectName) continue;\n // Resolve the registered simple name (case-insensitive) and test scoping.\n for (const [key, info] of ObjectRegistry.getAllClasses()) {\n const simpleName = info.name || key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n if (isTenantScopedClass(simpleName)) {\n scoped.add(simpleName.toLowerCase());\n }\n break;\n }\n }\n }\n return Array.from(scoped);\n }\n\n /**\n * Execute a resolved MCP action (CRUD or custom) against a collection. Always\n * invoked inside the tenant gate established by {@link executeAction}.\n */\n private async runAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n switch (action) {\n case 'list': {\n // Args arrive as untyped JSON; narrow each query field at this\n // boundary. `where`/`orderBy` are passed through to the collection's\n // typed query API.\n const listOptions: Parameters<typeof collection.list>[0] = {\n limit: Math.min((args.limit as number | undefined) || 50, 1000),\n offset: (args.offset as number | undefined) || 0,\n };\n\n if (args.where) {\n listOptions.where = args.where as (typeof listOptions)['where'];\n }\n\n if (args.orderBy) {\n listOptions.orderBy = args.orderBy as string | string[];\n }\n\n const results = await collection.list(listOptions);\n const total = await collection.count({\n where: (args.where as (typeof listOptions)['where']) || {},\n });\n\n return {\n data: results.map((result) => this.toPublicData(result)),\n meta: {\n total,\n limit: listOptions.limit,\n offset: listOptions.offset,\n count: results.length,\n },\n };\n }\n\n case 'get': {\n if (!args.id && !args.slug) {\n throw new Error('Either id or slug is required');\n }\n\n const filter = (args.id ? args.id : args.slug) as string;\n const item = await collection.get(filter);\n\n if (!item) {\n throw new Error('Object not found');\n }\n\n return this.toPublicData(item);\n }\n\n case 'create': {\n // Mass-assignment guard (#1540): only writable fields from the caller.\n const createData: Record<string, unknown> = this.applyWritablePolicy(\n objectName,\n args,\n );\n // Server-set ownership context (not caller-controlled).\n if (this.context.user) {\n createData.created_by = this.context.user.id;\n createData.owner_id = this.context.user.id;\n }\n\n // The writable-policy output is a dynamically-shaped record of caller\n // data; cast to the collection's create input at this boundary.\n const newItem = await collection.create(\n createData as Parameters<typeof collection.create>[0],\n );\n await newItem.save();\n\n return this.toPublicData(newItem);\n }\n\n case 'update': {\n const id = args.id as string | undefined;\n if (!id) {\n throw new Error('ID is required for update');\n }\n\n const existing = await collection.get(id);\n if (!existing) {\n throw new Error('Object not found');\n }\n\n // Mass-assignment guard (#1540): strip server-managed/read-only keys\n // (incl. `id`) before applying caller-supplied updates.\n const updateData = this.applyWritablePolicy(objectName, args);\n Object.assign(existing, updateData);\n\n // Add user context. `updated_by` is a server-set audit column, not a\n // declared model field, so assign it through a record view.\n if (this.context.user) {\n (existing as unknown as Record<string, unknown>).updated_by =\n this.context.user.id;\n }\n\n await existing.save();\n\n return this.toPublicData(existing);\n }\n\n case 'delete': {\n if (!args.id) {\n throw new Error('ID is required for delete');\n }\n\n const toDelete = await collection.get(args.id as string);\n if (!toDelete) {\n throw new Error('Object not found');\n }\n\n await toDelete.delete();\n\n return { success: true, message: 'Object deleted successfully' };\n }\n\n default: {\n // Handle custom actions. The method may return a SmrtObject (or array),\n // so serialize through toPublicData to strip sensitive fields (#1540).\n const result = await this.executeCustomAction(\n collection,\n action,\n args,\n objectName,\n );\n return this.toPublicData(result);\n }\n }\n }\n\n /**\n * Execute a custom action on a collection/object\n */\n private async executeCustomAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n const id = args.id;\n const [methodName, methodDef] = objectName\n ? resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(objectName),\n action,\n )\n : [action, undefined];\n const metadata = this.resolveCustomActionMetadata(\n objectName ?? '',\n methodName,\n methodDef,\n objectName\n ? this.hasCollectionReceiver(ObjectRegistry.getClass(objectName))\n : false,\n );\n const methodArgs = buildCustomActionInvocationArgs(metadata, args);\n\n try {\n if (methodDef && metadata.idRequired && !id) {\n throw new Error(`ID is required for custom action '${action}'`);\n }\n if (!metadata.idRequired && id && methodDef) {\n throw new Error(\n `Custom action '${action}' is collection-scoped and does not accept an ID`,\n );\n }\n\n // If an ID is provided, get the specific object and call the method on it\n if (id) {\n const object = await collection.get(id as string);\n if (!object) {\n throw new Error('Object not found');\n }\n\n // Custom action names are resolved dynamically, so index the instance\n // through a record view and narrow the value to a callable after the\n // `typeof === 'function'` guard.\n const objectWithMethods = object as unknown as Record<string, unknown>;\n const objectMethod = objectWithMethods[methodName];\n if (typeof objectMethod === 'function') {\n // `.call(object, …)` preserves the receiver binding of the original\n // member call (`object[action](…)`) — the method relies on `this`.\n const result = await (objectMethod as InstanceCallable).call(\n object,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n throw new Error(\n `Method '${methodName}' not found on object instance`,\n );\n }\n } else if (metadata.isStatic && objectName) {\n const classInfo = ObjectRegistry.getClass(objectName);\n const classMethod = (\n classInfo?.constructor as unknown as\n | Record<string, unknown>\n | undefined\n )?.[methodName];\n if (typeof classMethod !== 'function') {\n throw new Error(\n `Static method '${methodName}' not found on ${objectName}`,\n );\n }\n const result = await (classMethod as InstanceCallable).call(\n classInfo?.constructor,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n // No ID provided, try to call the method on the collection\n const collectionMethod = (\n collection as unknown as Record<string, unknown>\n )[methodName];\n if (typeof collectionMethod === 'function') {\n // `.call(collection, …)` preserves the receiver binding of the\n // original member call (`collection[action](…)`).\n const result = await (collectionMethod as InstanceCallable).call(\n collection,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n throw new Error(\n `Method '${methodName}' not found on collection. For object-specific actions, provide an 'id' parameter.`,\n );\n }\n }\n } catch (error) {\n if (error instanceof CustomActionFailureError) throw error;\n throw new Error(\n `Failed to execute custom action '${action}': ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Generate MCP server info\n */\n getServerInfo() {\n return {\n name: this.config.server?.name,\n version: this.config.server?.version,\n description: this.config.description,\n };\n }\n\n /**\n * Generate complete MCP server with stdio transport\n *\n * Creates a runnable Node.js script that exposes SMRT objects as MCP tools.\n * The generated server includes:\n * - Stdio transport integration\n * - Tool registration from ObjectRegistry\n * - Error handling and logging\n * - Graceful shutdown\n *\n * @param options - Server generation options\n * @returns Promise that resolves when all files are written\n *\n * @example\n * ```typescript\n * const generator = new MCPGenerator({\n * name: 'my-app',\n * version: '1.0.0'\n * });\n *\n * await generator.generateServer({\n * outputPath: '.smrt/mcp-server/index.js',\n * serverName: 'my-app-mcp',\n * debug: true\n * });\n * ```\n */\n async generateServer(\n options: {\n /** Path to output server file (relative or absolute) */\n outputPath?: string;\n\n /** Server name for configuration */\n serverName?: string;\n\n /** Server version */\n serverVersion?: string;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Generate Claude Desktop configuration example */\n generateClaudeConfigFile?: boolean;\n\n /** Generate README documentation */\n generateReadme?: boolean;\n\n /** Generate modular directory structure (tools/, handlers/, config.ts) */\n modular?: boolean;\n } = {},\n ): Promise<void> {\n const {\n outputPath = '.smrt/mcp-server/index.js',\n serverName = this.config.name || 'smrt-mcp-server',\n serverVersion = this.config.version || '1.0.0',\n debug = false,\n generateClaudeConfigFile = false,\n generateReadme = false,\n modular = false,\n } = options;\n\n // Resolve output path\n const resolvedPath = resolve(process.cwd(), outputPath);\n const outputDir = dirname(resolvedPath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n if (modular) {\n // Generate modular structure: tools/, handlers/, config.ts, index.js\n await this.generateModularServer(\n outputDir,\n serverName,\n serverVersion,\n debug,\n );\n } else {\n // Generate single-file server with static tools\n const tools = await this.generateTools();\n\n const runtimeOptions: RuntimeOptions = {\n name: serverName,\n version: serverVersion,\n description: this.config.description,\n config: this.config,\n context: this.context,\n debug,\n tools,\n customActions: await this.runtimeCustomActions(tools),\n tenantScopedObjects: await this.tenantScopedObjectNames(tools),\n };\n\n const serverCode = generateRuntimeBootstrap(runtimeOptions);\n\n // Write server file\n await writeFile(resolvedPath, serverCode, 'utf-8');\n console.log(`✅ Generated MCP server: ${resolvedPath}`);\n }\n\n // Generate Claude Desktop configuration example\n if (generateClaudeConfigFile) {\n const claudeConfig = generateClaudeConfig(serverName, resolvedPath);\n const claudeConfigPath = resolve(outputDir, 'claude-config.example.json');\n await writeFile(\n claudeConfigPath,\n JSON.stringify(claudeConfig, null, 2),\n 'utf-8',\n );\n console.log(`✅ Generated Claude config example: ${claudeConfigPath}`);\n }\n\n // Generate README documentation\n if (generateReadme) {\n const readme = generateMCPDocumentation(serverName, outputPath);\n const readmePath = resolve(outputDir, 'MCP-README.md');\n await writeFile(readmePath, readme, 'utf-8');\n console.log(`✅ Generated MCP documentation: ${readmePath}`);\n }\n\n // Generate npm script suggestion\n const mcpScript = generateMCPScript(outputPath);\n console.log(`\\n📝 Add this to your package.json scripts:`);\n console.log(` \"mcp\": \"${mcpScript}\"\\n`);\n }\n\n private async runtimeCustomActions(\n tools: MCPTool[],\n ): Promise<NonNullable<RuntimeOptions['customActions']>> {\n const metadata: NonNullable<RuntimeOptions['customActions']> = {};\n const crudActions = new Set(['list', 'get', 'create', 'update', 'delete']);\n const classes = ObjectRegistry.getAllClasses();\n\n for (const tool of tools) {\n const separator = tool.name.indexOf('_');\n if (separator === -1) continue;\n const objectPrefix = tool.name.slice(0, separator);\n const action = tool.name.slice(separator + 1);\n if (crudActions.has(action)) continue;\n const matched = Array.from(classes.entries()).find(\n ([key, info]) => (info.name || key).toLowerCase() === objectPrefix,\n );\n if (!matched) continue;\n const [key, classInfo] = matched;\n const objectName = classInfo.name || key;\n const [methodName, method] = resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(objectName),\n action,\n );\n const resolved = this.resolveCustomActionMetadata(\n objectName,\n methodName,\n method,\n this.hasCollectionReceiver(classInfo),\n );\n metadata[tool.name] = {\n scope: resolved.scope,\n isStatic: resolved.isStatic,\n methodName,\n ...(resolved.parameters\n ? {\n parameterNames: resolved.parameters.map(\n (parameter) => parameter.name,\n ),\n optionsParameter:\n resolved.parameters.length === 1 &&\n resolved.parameters[0]?.name === 'options',\n }\n : {}),\n legacyOptions: !resolved.parameters,\n };\n }\n return metadata;\n }\n\n /**\n * Generate modular MCP server structure\n *\n * Creates separate files for tools, handlers, configuration, and main entry point.\n * This makes the generated server easier to customize and extend.\n *\n * @param outputDir - Directory to generate files in\n * @param serverName - Server name\n * @param serverVersion - Server version\n * @param debug - Enable debug logging\n */\n private async generateModularServer(\n outputDir: string,\n serverName: string,\n serverVersion: string,\n debug: boolean,\n ): Promise<void> {\n // Create subdirectories\n const toolsDir = resolve(outputDir, 'tools');\n const handlersDir = resolve(outputDir, 'handlers');\n\n await mkdir(toolsDir, { recursive: true });\n await mkdir(handlersDir, { recursive: true });\n\n // Generate config.ts\n const configPath = resolve(outputDir, 'config.ts');\n const configCode = this.generateConfigFile(\n serverName,\n serverVersion,\n debug,\n );\n await writeFile(configPath, configCode, 'utf-8');\n console.log(`✅ Generated config: ${configPath}`);\n\n // Generate tools/index.ts with tool definitions\n const toolsPath = resolve(toolsDir, 'index.ts');\n const toolsCode = await this.generateToolsFile();\n await writeFile(toolsPath, toolsCode, 'utf-8');\n console.log(`✅ Generated tools: ${toolsPath}`);\n\n // Generate handlers/index.ts with tool call handlers\n const handlersPath = resolve(handlersDir, 'index.ts');\n const tenantScopedObjects = await this.tenantScopedObjectNames(\n await this.generateTools(),\n );\n const handlersCode = await this.generateHandlersFile(tenantScopedObjects);\n await writeFile(handlersPath, handlersCode, 'utf-8');\n console.log(`✅ Generated handlers: ${handlersPath}`);\n\n // Generate main index.js entry point\n const indexPath = resolve(outputDir, 'index.js');\n const indexCode = this.generateModularIndex();\n await writeFile(indexPath, indexCode, 'utf-8');\n console.log(`✅ Generated MCP server: ${indexPath}`);\n }\n\n /**\n * Generate configuration file for modular server\n */\n private generateConfigFile(\n serverName: string,\n serverVersion: string,\n debug: boolean,\n ): string {\n return `/**\n * MCP Server Configuration\n * Auto-generated by @happyvertical/smrt-core\n */\n\nexport const SERVER_NAME = ${JSON.stringify(serverName)};\nexport const SERVER_VERSION = ${JSON.stringify(serverVersion)};\nexport const SERVER_DESCRIPTION = ${JSON.stringify(this.config.description)};\nexport const DEBUG = ${debug};\n`;\n }\n\n /**\n * Generate tools definitions file for modular server\n */\n private async generateToolsFile(): Promise<string> {\n const tools = await this.generateTools();\n\n return `/**\n * MCP Tools Definitions\n * Auto-generated from SMRT objects\n */\n\nexport const tools: Array<{\n name: string;\n description: string;\n inputSchema: any;\n}> = ${JSON.stringify(tools, null, 2)};\n`;\n }\n\n /**\n * Generate switch cases for tool execution\n */\n private async generateToolSwitchCases(\n indent: string = ' ',\n ): Promise<string> {\n const tools = await this.generateTools();\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n\n const switchCases = (\n await Promise.all(\n tools.map(async (tool) => {\n const separator = tool.name.indexOf('_');\n const objectName = tool.name.slice(0, separator);\n const action = tool.name.slice(separator + 1);\n\n switch (action) {\n case 'list':\n return `${indent}case '${tool.name}': {\n${indent} const limit = args.limit ?? 50;\n${indent} const offset = args.offset ?? 0;\n${indent} const where = args.where ?? {};\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const items = await collection.list({ where, limit, offset });\n${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent} return { content: [{ type: 'text', text: JSON.stringify(itemsPublic) }] };\n${indent}}`;\n\n case 'get':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id && !args.slug) {\n${indent} throw new Error('Either id or slug is required');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const filter = args.id || args.slug;\n${indent} const item = await collection.get(filter);\n\n${indent} if (!item) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(item.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const newItem = await collection.create(applyWritablePolicy('${capitalize(objectName)}', args));\n${indent} await newItem.save();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'update':\n return `${indent}case '${tool.name}': {\n${indent} const { id, ...updateData } = args;\n${indent} if (!id) {\n${indent} throw new Error('ID is required for update');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const existing = await collection.get(id);\n${indent} if (!existing) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} Object.assign(existing, applyWritablePolicy('${capitalize(objectName)}', updateData));\n${indent} await existing.save();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(existing.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'delete':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id) {\n${indent} throw new Error('ID is required for delete');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const toDelete = await collection.get(args.id);\n${indent} if (!toDelete) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await toDelete.delete();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: 'Object deleted successfully' }) }] };\n${indent}}`;\n\n default: {\n // Custom actions use the same canonical receiver/argument contract\n // as the in-process and standalone MCP runtimes. In particular,\n // a route config cannot turn an instance method into a static one.\n const matched = Array.from(\n ObjectRegistry.getAllClasses().entries(),\n ).find(\n ([key, info]) =>\n (info.name || key).toLowerCase() === objectName.toLowerCase(),\n );\n if (!matched) {\n throw new Error(\n `Unable to resolve custom-action target for tool '${tool.name}'`,\n );\n }\n const [classKey, classInfo] = matched;\n const registeredName = classInfo.name || classKey;\n const [methodName, method] = resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(registeredName),\n action,\n );\n const metadata = this.resolveCustomActionMetadata(\n registeredName,\n methodName,\n method,\n this.hasCollectionReceiver(classInfo),\n );\n const methodArgs = !metadata.parameters\n ? 'Object.keys(options ?? {}).length > 0 ? options : directArgs'\n : metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ? 'options'\n : `[${metadata.parameters\n .map(\n (parameter) =>\n `args[${JSON.stringify(\n customActionParameterInputName(\n metadata,\n parameter.name,\n ),\n )}]`,\n )\n .join(', ')}]`;\n return `${indent}case '${tool.name}': {\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (${JSON.stringify(metadata.scope)} === 'item' && !id) {\n${indent} throw new Error('ID is required for custom action ${action}');\n${indent} }\n${indent} if (${JSON.stringify(metadata.scope)} === 'collection' && id) {\n${indent} throw new Error('Custom action ${action} is collection-scoped and does not accept an ID');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(registeredName)}, {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = ${JSON.stringify(metadata.scope)} === 'item'\n${indent} ? await collection.get(id)\n${indent} : ${metadata.isStatic}\n${indent} ? ObjectRegistry.getClass(${JSON.stringify(registeredName)})?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(${JSON.stringify(\n metadata.scope === 'item'\n ? 'Object not found'\n : 'Custom action target not found',\n )});\n${indent} }\n\n${indent} const actionMethod = target[${JSON.stringify(methodName)}];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error('Method ${methodName} not found on custom action target');\n${indent} }\n\n${indent} const methodArgs = ${methodArgs.startsWith('[') ? methodArgs : `[${methodArgs}]`};\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return {\n${indent} content: [{ type: 'text', text: JSON.stringify({ error: failure }) }],\n${indent} isError: true,\n${indent} _meta: { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} };\n${indent} }\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(toPublicResult(result)) }] };\n${indent}}`;\n }\n }\n }),\n )\n ).join('\\n\\n');\n\n return switchCases;\n }\n\n /**\n * Generate handlers file for modular server\n */\n private async generateHandlersFile(\n tenantScopedObjects: string[] = [],\n ): Promise<string> {\n const switchCases = await this.generateToolSwitchCases(' ');\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n return `/**\n * MCP Tool Call Handlers\n * Auto-generated from SMRT objects\n *\n * SECURITY (#1540): responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This handler has no\n * per-call authentication principal — the generated stdio MCP server's trust\n * boundary is the host process / MCP client. Run it only in a trusted context\n * or behind an authenticated gateway.\n *\n * SECURITY (#1554): tenant-scoped tools run inside a fail-closed tenant gate;\n * the tenant is taken from SMRT_MCP_TENANT_ID (this server has no auth\n * principal) and tenancy is enabled so the interceptor enforces filtering.\n */\n\nimport { ObjectRegistry, normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}${\n hasTenantScoped\n ? `\n// Install the tenancy interceptor so tenant-scoped tools are filtered and the\n// gate fail-closes when no tenant is supplied (#1554).\nenableTenancy();\nconst TENANT_SCOPED = new Set(${JSON.stringify(tenantScopedSet)});\nconst MCP_TENANT_ID = process.env.SMRT_MCP_TENANT_ID || undefined;\nconst MCP_ALLOW_CROSS_TENANT = process.env.SMRT_MCP_ALLOW_CROSS_TENANT === 'true';\n`\n : ''\n}\nconst PUBLIC_JSON_OPTIONS = {\n permissions: (process.env.SMRT_MCP_PERMISSIONS || '')\n .split(',')\n .map((permission) => permission.trim())\n .filter(Boolean),\n};\n\n/**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * \\`@field({ readonly: true })\\` fields from create/update bodies, intersecting\n * with the optional \\`@smrt({ api: { writable: [...] } })\\` allowlist.\n */\nfunction applyWritablePolicy(objectName: string, data: any): Record<string, any> {\n if (!data || typeof data !== 'object') return {};\n const serverManaged = new Set<string>([\n 'id', 'tenantId', 'tenant_id',\n 'createdAt', 'created_at', 'updatedAt', 'updated_at',\n ]);\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api as any;\n if (apiConfig && typeof apiConfig === 'object' && Array.isArray(apiConfig.writable)) {\n writable = apiConfig.writable;\n }\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && ((def as any).readonly === true || (def as any)._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data as Record<string, any>)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Sensitive-field-safe serialization for custom-action results (#1540).\n * Recurses through arrays and plain objects so nested SmrtObjects are stripped\n * too; non-plain instances (Date, etc.) and primitives pass through. Cycle-safe.\n */\nfunction toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any {\n if (value === null || typeof value !== 'object') return value;\n if (typeof value.toPublicJSON === 'function') return value.toPublicJSON(PUBLIC_JSON_OPTIONS);\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry: any) => toPublicResult(entry, seen));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, any> = {};\n for (const [key, entry] of Object.entries(value as Record<string, any>)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\n/**\n * Handle tool call request\n */\nexport async function handleToolCall(\n name: string,\n arguments: any = {},\n aiConfig: any = {}\n) {\n try {\n const args = arguments;\n\n const runToolBody = async () => {\n switch (name) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${name}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const [toolObject] = name.split('_');\n const result =\n toolObject && TENANT_SCOPED.has(toolObject.toLowerCase())\n ? await runTenantScopedEntryPoint(\n { tenantScoped: true, tenantId: MCP_TENANT_ID, allowCrossTenant: MCP_ALLOW_CROSS_TENANT, surface: 'MCP' },\n runToolBody,\n )\n : await runToolBody();`\n : `\n const result = await runToolBody();`\n}\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n return {\n content: [\n {\n type: 'text',\n text: \\`Error executing tool \\${name}: \\${errorMessage}\\`,\n },\n ],\n isError: true,\n };\n }\n}\n`;\n }\n\n /**\n * Generate modular index file (main entry point)\n */\n private generateModularIndex(): string {\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @happyvertical/smrt-core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { config } from '@happyvertical/smrt-config';\nimport { getDatabase } from '@happyvertical/sql';\nimport { getAI } from '@happyvertical/ai';\n\nimport { SERVER_NAME, SERVER_VERSION, DEBUG } from './config.js';\nimport { tools } from './tools/index.js';\nimport { handleToolCall } from './handlers/index.js';\n\n/**\n * Main server startup function\n */\nasync function main() {\n try {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n console.error(\\`[MCP] Available tools:\\`, tools.map(t => t.name).join(', '));\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await config.load();\n const aiConfig = appConfig?.ai || {};\n\n // Create MCP server\n const server = new Server(\n {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${name}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n return await handleToolCall(name, args, aiConfig);\n });\n\n // Setup stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n if (DEBUG) {\n console.error(\\`[MCP] Server connected and ready\\`);\n }\n\n // Handle graceful shutdown\n const shutdown = async () => {\n if (DEBUG) {\n console.error(\\`[MCP] Shutting down gracefully\\`);\n }\n await server.close();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n } catch (error) {\n console.error('[MCP] Fatal error during server startup:', error);\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error('[MCP] Unhandled error:', error);\n process.exit(1);\n});\n`;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp.js","names":[],"sources":["../../src/generators/mcp.ts"],"sourcesContent":["/**\n * MCP (Model Context Protocol) server generator for smrt objects\n *\n * Exposes smrt objects as AI tools for Claude, GPT, and other AI models\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { SmrtCollection } from '../collection';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass } from '../registry/types.js';\nimport type { FieldDefinition, MethodDefinition } from '../scanner/types.js';\nimport {\n buildCustomActionInputSchema,\n buildCustomActionInvocationArgs,\n type CustomActionFailure,\n type CustomActionMetadata,\n customActionParameterInputName,\n normalizeCustomActionFailure,\n resolveCustomActionMetadata,\n SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY,\n} from './custom-action.js';\nimport {\n generateClaudeConfig,\n generateMCPDocumentation,\n generateMCPScript,\n generateRuntimeBootstrap,\n type RuntimeOptions,\n} from './mcp-runtime-template.js';\nimport { runWithTenantGate } from './tenant-gate.js';\n\n/**\n * A JSON Schema fragment for an MCP tool input property. The shape varies by\n * field kind (string/number/object/…), so values are `unknown`; this is only\n * ever serialized into the generated tool definitions, never read back.\n */\ntype McpJsonSchema = Record<string, unknown>;\n\n/**\n * Runtime tool-call arguments. They arrive as untyped JSON from the MCP client,\n * so individual keys are narrowed (`as`) at each action's boundary.\n */\ntype ToolArgs = Record<string, unknown>;\n\n/**\n * A method resolved dynamically (by action name) from an object/collection\n * instance and invoked with the parsed tool-call arguments. Narrowed to this\n * type at the call boundary after a `typeof === 'function'` guard.\n */\ntype InstanceCallable = (...args: unknown[]) => unknown;\n\nexport interface MCPConfig {\n name?: string;\n version?: string;\n description?: string;\n server?: {\n name: string;\n version: string;\n };\n}\n\nexport interface MCPContext {\n db?: unknown;\n ai?: unknown;\n user?: {\n id: string;\n roles?: string[];\n };\n /** Resolved permission slugs held by the caller. */\n permissions?: Iterable<string>;\n /**\n * Tenant the calling principal is scoped to (#1554). When set, tenant-scoped\n * tools run inside this tenant's context. Hosts that authenticate a principal\n * (e.g. `@happyvertical/smrt-app-mcp`) should derive it from the principal —\n * the MCP analogue of the SvelteKit auth hook setting `locals.tenantId`.\n */\n tenantId?: string;\n /**\n * Explicit operator opt-in to cross-tenant access for tenant-scoped tools\n * (#1554). Only set for trusted operator/admin callers. Without a `tenantId`\n * or this flag, tenant-scoped tool calls fail closed when tenancy is enabled.\n */\n allowCrossTenant?: boolean;\n}\n\nexport interface MCPTool {\n name: string;\n description: string;\n inputSchema: {\n type: string;\n properties: Record<string, McpJsonSchema>;\n required?: string[];\n };\n}\n\nexport interface MCPRequest {\n method: string;\n params: {\n name: string;\n arguments: ToolArgs;\n };\n}\n\nexport interface MCPResponse {\n content: Array<{\n type: 'text';\n text: string;\n }>;\n isError?: boolean;\n _meta?: Record<string, unknown>;\n}\n\nclass CustomActionFailureError extends Error {\n constructor(readonly failure: CustomActionFailure) {\n super(failure.message);\n this.name = 'CustomActionFailureError';\n }\n}\n\n/**\n * MCP tool identifiers are lowercase for stable protocol vocabulary, while\n * JavaScript method names retain their declared casing. Resolve a tool suffix\n * back to the registry's canonical method name before inspecting metadata or\n * invoking it.\n */\nfunction resolveCustomActionMethod(\n methods: Map<string, MethodDefinition>,\n toolAction: string,\n): [methodName: string, method: MethodDefinition | undefined] {\n const direct = methods.get(toolAction);\n if (direct) return [toolAction, direct];\n for (const [methodName, method] of methods) {\n if (methodName.toLowerCase() === toolAction.toLowerCase()) {\n return [methodName, method];\n }\n }\n return [toolAction, undefined];\n}\n\n/**\n * Generate MCP server from smrt objects\n */\nexport class MCPGenerator {\n private config: MCPConfig;\n private context: MCPContext;\n private collections = new Map<string, SmrtCollection<SmrtObject>>();\n\n constructor(config: MCPConfig = {}, context: MCPContext = {}) {\n this.config = {\n name: 'smrt-mcp-server',\n version: '1.0.0',\n description: 'Auto-generated MCP server from smrt objects',\n server: {\n name: 'smrt-mcp',\n version: '1.0.0',\n },\n ...config,\n };\n this.context = context;\n }\n\n /**\n * Get server name\n */\n get name(): string | undefined {\n return this.config.name;\n }\n\n /**\n * Get server version\n */\n get version(): string | undefined {\n return this.config.version;\n }\n\n /**\n * Generate all available tools from registered objects\n */\n async generateTools(): Promise<MCPTool[]> {\n const tools: MCPTool[] = [];\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name for tool naming, map key for registry lookups\n const simpleName = classInfo.name || key;\n const config = ObjectRegistry.getConfig(simpleName);\n const mcpConfig = config.mcp;\n\n // `mcp: false` disables MCP generation entirely for the class. Without\n // this gate an `include` list still leaked custom-method tools (the\n // custom-method branch historically only honored `include` when it\n // listed custom methods), so `false` is the only fully fail-closed\n // switch. Mirrors rest.ts's `apiConfig === false` short-circuit\n // (#1540 / #1546).\n if (mcpConfig === false) {\n continue;\n }\n\n // Handle boolean vs object config\n const excluded: string[] =\n typeof mcpConfig === 'object' && mcpConfig?.exclude\n ? mcpConfig.exclude\n : [];\n const included: string[] | undefined =\n typeof mcpConfig === 'object' ? mcpConfig?.include : undefined;\n\n const shouldInclude = (endpoint: string) => {\n if (included && !included.includes(endpoint)) return false;\n if (excluded.includes(endpoint)) return false;\n return true;\n };\n\n const objectTools = await this.generateObjectTools(\n simpleName,\n shouldInclude,\n );\n tools.push(...objectTools);\n }\n\n return tools;\n }\n\n /**\n * Generate tools for a specific object\n */\n private async generateObjectTools(\n objectName: string,\n shouldInclude: (endpoint: string) => boolean,\n ): Promise<MCPTool[]> {\n const tools: MCPTool[] = [];\n const fields = ObjectRegistry.getFields(objectName);\n const lowerName = objectName.toLowerCase();\n const classInfo = ObjectRegistry.getClass(objectName);\n\n // LIST tool\n if (shouldInclude('list')) {\n tools.push({\n name: `${lowerName}_list`,\n description: `List ${objectName} objects with optional filtering`,\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'integer',\n description: 'Maximum number of items to return',\n default: 50,\n minimum: 1,\n maximum: 1000,\n },\n offset: {\n type: 'integer',\n description: 'Number of items to skip',\n default: 0,\n minimum: 0,\n },\n orderBy: {\n type: 'string',\n description: 'Field to order by (e.g., \"created_at DESC\")',\n },\n where: {\n type: 'object',\n description: 'Filter conditions as key-value pairs',\n additionalProperties: true,\n },\n },\n },\n });\n }\n\n // GET tool\n if (shouldInclude('get')) {\n tools.push({\n name: `${lowerName}_get`,\n description: `Get a specific ${objectName} by ID or slug`,\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the object',\n },\n slug: {\n type: 'string',\n description: 'URL-friendly identifier of the object',\n },\n },\n required: ['id'],\n },\n });\n }\n\n // CREATE tool\n if (shouldInclude('create')) {\n const properties: Record<string, McpJsonSchema> = {};\n const required: string[] = [];\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = this.fieldToMCPSchema(field);\n if (field._meta?.required) {\n required.push(fieldName);\n }\n }\n\n tools.push({\n name: `${lowerName}_create`,\n description: `Create a new ${objectName}`,\n inputSchema: {\n type: 'object',\n properties,\n required,\n },\n });\n }\n\n // UPDATE tool\n if (shouldInclude('update')) {\n const properties: Record<string, McpJsonSchema> = {\n id: {\n type: 'string',\n description: 'ID of the object to update',\n },\n };\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = this.fieldToMCPSchema(field);\n }\n\n tools.push({\n name: `${lowerName}_update`,\n description: `Update an existing ${objectName}`,\n inputSchema: {\n type: 'object',\n properties,\n required: ['id'],\n },\n });\n }\n\n // DELETE tool\n if (shouldInclude('delete')) {\n tools.push({\n name: `${lowerName}_delete`,\n description: `Delete a ${objectName} by ID`,\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'ID of the object to delete',\n },\n },\n required: ['id'],\n },\n });\n }\n\n // CUSTOM METHODS - discover from manifest and show by default\n if (classInfo) {\n const config = ObjectRegistry.getConfig(objectName);\n const mcpConfig = config.mcp;\n const included: string[] | undefined =\n typeof mcpConfig === 'object' ? mcpConfig?.include : undefined;\n const excluded: string[] =\n typeof mcpConfig === 'object' && mcpConfig?.exclude\n ? mcpConfig.exclude\n : [];\n\n // When an `include` list is present it is the COMPLETE allowlist for\n // this surface: a custom (non-CRUD) method is exposed ONLY if its name\n // appears in `include`. Without an include list we keep the historical\n // default of auto-exposing every public method. This closes the leak\n // where `mcp: { include: ['list', 'get'] }` still emitted custom-method\n // tools like `payment_recordpayment` because `include` only gated CRUD\n // verbs (#1540 / #1390).\n const crudOperations = ['list', 'get', 'create', 'update', 'delete'];\n const customMethodsInInclude =\n included?.filter((item) => !crudOperations.includes(item)) || [];\n // An include list (even one naming only CRUD verbs) switches custom\n // methods into strict allowlist mode.\n const hasIncludeList = included !== undefined;\n\n // Try to discover methods from manifest (including inherited methods)\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const methodNames = new Set(Array.from(methods.keys()));\n\n // Strict mode: an include list is present, so only the custom methods it\n // names are generated (may be none, e.g. include: ['list', 'get']).\n if (hasIncludeList) {\n for (const methodName of customMethodsInInclude) {\n // Skip if explicitly excluded\n if (excluded.includes(methodName)) continue;\n\n // Check if method exists (in manifest or on class prototype)\n const existsInManifest = methodNames.has(methodName);\n const existsOnClass = this.validateCustomMethod(\n classInfo.constructor,\n methodName,\n );\n\n if (!existsInManifest && !existsOnClass) {\n // Warn about missing methods\n console.warn(\n `Warning: Custom action '${methodName}' specified in MCP config for ${objectName}, but method ${methodName}() not found on class`,\n );\n continue;\n }\n\n // A non-public method must never be exposed as a tool, even when it\n // is explicitly named in `include`. This keeps strict-include mode\n // consistent with the non-strict path below, which gates on\n // `methodDef.isPublic`. Listing a private method in `include` is a\n // config mistake, not an override of method visibility (#1540).\n //\n // The scanner strips private/protected methods from the manifest, so\n // when a non-public method is named in `include` it is absent from\n // `methods` and is only resolvable via validateCustomMethod() on the\n // runtime prototype (TS access modifiers are erased at runtime). Such\n // a method must NOT be emitted. We still allow methods that are\n // present on the class but legitimately absent from the manifest\n // (e.g. inline/dynamically registered classes), so the guard fires\n // only when there is a public manifest entry to anchor on OR the\n // method exists solely as a stripped (non-public) manifest method.\n const methodDef = methods.get(methodName);\n if (methodDef && !methodDef.isPublic) continue;\n\n tools.push(\n this.buildCustomActionTool(\n objectName,\n lowerName,\n methodName,\n methodDef,\n this.hasCollectionReceiver(classInfo),\n ),\n );\n }\n } else {\n // No custom methods in include = show all discovered methods by default\n for (const [methodName, methodDef] of methods) {\n // Skip if not public (private/protected methods shouldn't be in MCP)\n if (!methodDef.isPublic) continue;\n\n // Always respect exclude list\n if (excluded.includes(methodName)) continue;\n\n tools.push(\n this.buildCustomActionTool(\n objectName,\n lowerName,\n methodName,\n methodDef,\n this.hasCollectionReceiver(classInfo),\n ),\n );\n }\n }\n }\n\n return tools;\n }\n\n private buildCustomActionTool(\n objectName: string,\n lowerName: string,\n methodName: string,\n methodDef?: MethodDefinition,\n collectionReceiver = false,\n ): MCPTool {\n const metadata = this.resolveCustomActionMetadata(\n objectName,\n methodName,\n methodDef,\n collectionReceiver,\n );\n return {\n name: `${lowerName}_${methodName}`.toLowerCase(),\n description: `Execute ${methodName} action on ${objectName}`,\n inputSchema: buildCustomActionInputSchema(\n metadata,\n ) as MCPTool['inputSchema'],\n };\n }\n\n private resolveCustomActionMetadata(\n objectName: string,\n action: string,\n method?: MethodDefinition,\n collectionReceiver = false,\n ): CustomActionMetadata {\n return resolveCustomActionMetadata({\n actionName: action,\n method,\n apiConfig: ObjectRegistry.getConfig(objectName).api,\n ...(collectionReceiver ? { defaultScope: 'collection' } : {}),\n });\n }\n\n private hasCollectionReceiver(classInfo?: RegisteredClass): boolean {\n return (\n !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection\n );\n }\n\n /**\n * Validate that a custom method exists on a class\n */\n private validateCustomMethod(\n classConstructor: typeof SmrtObject,\n methodName: string,\n ): boolean {\n try {\n // Check if method exists on the prototype\n const prototype = classConstructor.prototype;\n\n // Check if the method exists and is a function. Dynamic name lookup, so\n // index through a record view of the prototype/constructor.\n if (\n typeof (prototype as unknown as Record<string, unknown>)[methodName] ===\n 'function'\n ) {\n return true;\n }\n\n // Also check static methods\n if (\n typeof (classConstructor as unknown as Record<string, unknown>)[\n methodName\n ] === 'function'\n ) {\n return true;\n }\n\n return false;\n } catch (error) {\n console.warn(\n `Error validating method ${methodName} on class ${classConstructor.name}:`,\n error,\n );\n return false;\n }\n }\n\n /**\n * Convert field definition to MCP schema\n */\n private fieldToMCPSchema(field: FieldDefinition): McpJsonSchema {\n const schema: McpJsonSchema = {\n description: field._meta?.description || `${field.type} field`,\n };\n\n switch (field.type) {\n case 'text':\n schema.type = 'string';\n if (field._meta?.maxLength) schema.maxLength = field._meta.maxLength;\n if (field._meta?.minLength) schema.minLength = field._meta.minLength;\n break;\n case 'integer':\n schema.type = 'integer';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'decimal':\n schema.type = 'number';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'boolean':\n schema.type = 'boolean';\n break;\n case 'datetime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'json':\n schema.type = 'object';\n break;\n case 'foreignKey':\n schema.type = 'string';\n // Lockstep with tool-schema.ts fieldTypeToJsonSchema: the generic\n // relation hint is a fallback — an authored description wins (#2046).\n if (!field._meta?.description) {\n schema.description = `ID of related ${field.related || 'object'}`;\n }\n break;\n default:\n schema.type = 'string';\n }\n\n if (field._meta?.default !== undefined) {\n schema.default = field._meta.default;\n }\n\n return schema;\n }\n\n /**\n * Handle MCP tool calls\n */\n async handleToolCall(request: MCPRequest): Promise<MCPResponse> {\n const { name, arguments: args } = request.params;\n\n try {\n // Check if tool exists\n const availableTools = await this.generateTools();\n const toolExists = availableTools.some((t) => t.name === name);\n\n if (!toolExists) {\n throw new Error(`Unknown tool: ${name}`);\n }\n\n // Parse tool name: `objectname_action`. Split on the FIRST underscore\n // only — a custom method name can itself contain underscores (e.g.\n // `record_payment` → tool `invoice_record_payment`). A naive\n // `name.split('_')` would take `action` as just `record` and mis-route\n // the call. The emitted stdio servers switch on the full tool name, so\n // splitting greedily here also kept the in-process path divergent (#1378).\n const firstUnderscore = name.indexOf('_');\n const objectName =\n firstUnderscore === -1 ? '' : name.slice(0, firstUnderscore);\n const action =\n firstUnderscore === -1 ? '' : name.slice(firstUnderscore + 1);\n\n if (!objectName || !action) {\n throw new Error(`Invalid tool name format: ${name}`);\n }\n\n // Find the registered class (case-insensitive)\n const registeredClasses = ObjectRegistry.getAllClasses();\n let classInfo = null;\n let actualObjectName = '';\n\n for (const [_key, info] of registeredClasses) {\n // Issue #951: Match by simple name, not the qualified map key\n const simpleName = info.name || _key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n classInfo = info;\n actualObjectName = simpleName;\n break;\n }\n }\n\n if (!classInfo) {\n throw new Error(`Object type '${objectName}' not found`);\n }\n\n // Get or create collection\n const collection = await this.getCollection(actualObjectName, classInfo);\n\n // Execute the action\n const result = await this.executeAction(\n collection,\n action,\n args,\n actualObjectName,\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (error) {\n if (error instanceof CustomActionFailureError) {\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ error: error.failure }),\n },\n ],\n isError: true,\n _meta: {\n [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: error.failure,\n },\n };\n }\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n };\n }\n }\n\n /**\n * Get or create collection for an object\n */\n private async getCollection(\n objectName: string,\n classInfo: RegisteredClass,\n ): Promise<SmrtCollection<SmrtObject>> {\n if (!this.collections.has(objectName)) {\n // Ensure we have a valid collection constructor\n if (\n !classInfo.collectionConstructor ||\n typeof classInfo.collectionConstructor !== 'function'\n ) {\n throw new Error(\n `No valid collection constructor found for ${objectName}`,\n );\n }\n\n const collection = new classInfo.collectionConstructor({\n ai: this.context.ai,\n db: this.context.db,\n });\n\n // Verify the collection is actually a SmrtCollection instance\n if (!(collection instanceof SmrtCollection)) {\n throw new Error(\n `Collection for ${objectName} must extend SmrtCollection`,\n );\n }\n\n // Initialize the collection (database setup, etc.)\n await collection.initialize();\n\n this.collections.set(objectName, collection);\n }\n const collection = this.collections.get(objectName);\n if (!collection) {\n throw new Error(`Collection for ${objectName} not found`);\n }\n return collection;\n }\n\n /**\n * Serialize a tool-response payload, excluding sensitive fields (#1540).\n * Recurses through arrays and plain objects so a SmrtObject nested inside a\n * custom-action result (e.g. `{ item }`) is also stripped — `JSON.stringify`\n * would otherwise call its `toJSON()`. Non-plain instances (Date, etc.) and\n * primitives pass through unchanged; a cycle guard prevents infinite loops.\n */\n private toPublicData(\n value: unknown,\n seen: WeakSet<object> = new WeakSet(),\n options: PublicJsonOptions = this.getPublicJsonOptions(),\n ): unknown {\n if (value === null || typeof value !== 'object') return value;\n const publicSource = value as {\n toPublicJSON?: (options?: PublicJsonOptions) => unknown;\n };\n if (typeof publicSource.toPublicJSON === 'function') {\n return publicSource.toPublicJSON(options);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry) => this.toPublicData(entry, seen, options));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = this.toPublicData(entry, seen, options);\n }\n return out;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * `@field({ readonly: true })` fields from a create/update body, and — when an\n * `@smrt({ api: { writable: [...] } })` allowlist is set — intersect with it.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') {\n return {};\n }\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Execute action on collection\n */\n /**\n * Fail-closed authorization for tool calls (#1540). Mutating tools\n * (create/update/delete + custom actions) require an authenticated principal\n * (`context.user`) unless the object opts out via `@smrt({ api: { public } })`.\n * Reads are allowed when `public` is `true` or `'read'`.\n */\n private requireToolAuth(\n objectName: string | undefined,\n mutating: boolean,\n ): void {\n const apiConfig = objectName\n ? ObjectRegistry.getConfig(objectName)?.api\n : undefined;\n const publicAccess =\n apiConfig && typeof apiConfig === 'object'\n ? (apiConfig as { public?: boolean | 'read' }).public\n : undefined;\n\n if (publicAccess === true) return;\n if (publicAccess === 'read' && !mutating) return;\n if (!this.context.user) {\n throw new Error('Authentication required');\n }\n }\n\n private async executeAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n const mutating = action !== 'list' && action !== 'get';\n this.requireToolAuth(objectName, mutating);\n\n // Fail-closed tenant context (#1554). For tenant-scoped objects, establish\n // the context from the principal's tenant (or an explicit cross-tenant\n // opt-in); without either, this throws when tenancy is enabled rather than\n // letting an optional-scoped read range across all tenants. Tenant-scoping\n // is resolved inside tenancy by class name so it matches the interceptor.\n return runWithTenantGate(\n {\n className: objectName,\n tenantId: this.context.tenantId,\n allowCrossTenant: this.context.allowCrossTenant,\n surface: 'MCP',\n },\n () => this.runAction(collection, action, args, objectName),\n );\n }\n\n /**\n * Derive the set of tenant-scoped object names (lowercased simple names) from\n * a generated tool list, for the emitted runtime template's tenant gate\n * (#1554). Tool names are `objectname_action`.\n *\n * Detection uses ONLY tenancy's `isTenantScopedClass` (the authoritative\n * source the interceptor uses; covers `@TenantScoped`), consulted via an\n * optional dynamic import. We deliberately do NOT fall back to core's\n * `ObjectRegistry.isTenantScoped`: a `@smrt({ tenantScoped })` model can exist\n * in an app that has NOT installed `@happyvertical/smrt-tenancy`, and emitting\n * the static `import { runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy'`\n * gate for it would crash the generated server at import time. When tenancy is\n * absent there is also nothing to enforce, so emitting no gate is correct.\n */\n private async tenantScopedObjectNames(tools: MCPTool[]): Promise<string[]> {\n let isTenantScopedClass: ((name: string) => boolean) | undefined;\n try {\n // Held in a variable so neither TypeScript's declaration emit (TS2307 —\n // core deliberately does not depend on tenancy) nor the bundler tries to\n // statically resolve this optional sibling package. Same pattern as\n // `tenant-gate.ts` / `embeddings/provider.ts`.\n const tenancySpecifier = '@happyvertical/smrt-tenancy';\n const tenancy = (await import(/* @vite-ignore */ tenancySpecifier)) as {\n isTenantScopedClass?: (name: string) => boolean;\n };\n isTenantScopedClass = tenancy.isTenantScopedClass;\n } catch {\n isTenantScopedClass = undefined;\n }\n\n // No tenancy package installed → no gate can run, emit none.\n if (typeof isTenantScopedClass !== 'function') return [];\n\n const scoped = new Set<string>();\n for (const tool of tools) {\n const [objectName] = tool.name.split('_');\n if (!objectName) continue;\n // Resolve the registered simple name (case-insensitive) and test scoping.\n for (const [key, info] of ObjectRegistry.getAllClasses()) {\n const simpleName = info.name || key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n if (isTenantScopedClass(simpleName)) {\n scoped.add(simpleName.toLowerCase());\n }\n break;\n }\n }\n }\n return Array.from(scoped);\n }\n\n /**\n * Execute a resolved MCP action (CRUD or custom) against a collection. Always\n * invoked inside the tenant gate established by {@link executeAction}.\n */\n private async runAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n switch (action) {\n case 'list': {\n // Args arrive as untyped JSON; narrow each query field at this\n // boundary. `where`/`orderBy` are passed through to the collection's\n // typed query API.\n const listOptions: Parameters<typeof collection.list>[0] = {\n limit: Math.min((args.limit as number | undefined) || 50, 1000),\n offset: (args.offset as number | undefined) || 0,\n };\n\n if (args.where) {\n listOptions.where = args.where as (typeof listOptions)['where'];\n }\n\n if (args.orderBy) {\n listOptions.orderBy = args.orderBy as string | string[];\n }\n\n const results = await collection.list(listOptions);\n const total = await collection.count({\n where: (args.where as (typeof listOptions)['where']) || {},\n });\n\n return {\n data: results.map((result) => this.toPublicData(result)),\n meta: {\n total,\n limit: listOptions.limit,\n offset: listOptions.offset,\n count: results.length,\n },\n };\n }\n\n case 'get': {\n if (!args.id && !args.slug) {\n throw new Error('Either id or slug is required');\n }\n\n const filter = (args.id ? args.id : args.slug) as string;\n const item = await collection.get(filter);\n\n if (!item) {\n throw new Error('Object not found');\n }\n\n return this.toPublicData(item);\n }\n\n case 'create': {\n // Mass-assignment guard (#1540): only writable fields from the caller.\n const createData: Record<string, unknown> = this.applyWritablePolicy(\n objectName,\n args,\n );\n // Server-set ownership context (not caller-controlled).\n if (this.context.user) {\n createData.created_by = this.context.user.id;\n createData.owner_id = this.context.user.id;\n }\n\n // The writable-policy output is a dynamically-shaped record of caller\n // data; cast to the collection's create input at this boundary.\n const newItem = await collection.create(\n createData as Parameters<typeof collection.create>[0],\n );\n await newItem.save();\n\n return this.toPublicData(newItem);\n }\n\n case 'update': {\n const id = args.id as string | undefined;\n if (!id) {\n throw new Error('ID is required for update');\n }\n\n const existing = await collection.get(id);\n if (!existing) {\n throw new Error('Object not found');\n }\n\n // Mass-assignment guard (#1540): strip server-managed/read-only keys\n // (incl. `id`) before applying caller-supplied updates.\n const updateData = this.applyWritablePolicy(objectName, args);\n Object.assign(existing, updateData);\n\n // Add user context. `updated_by` is a server-set audit column, not a\n // declared model field, so assign it through a record view.\n if (this.context.user) {\n (existing as unknown as Record<string, unknown>).updated_by =\n this.context.user.id;\n }\n\n await existing.save();\n\n return this.toPublicData(existing);\n }\n\n case 'delete': {\n if (!args.id) {\n throw new Error('ID is required for delete');\n }\n\n const toDelete = await collection.get(args.id as string);\n if (!toDelete) {\n throw new Error('Object not found');\n }\n\n await toDelete.delete();\n\n return { success: true, message: 'Object deleted successfully' };\n }\n\n default: {\n // Handle custom actions. The method may return a SmrtObject (or array),\n // so serialize through toPublicData to strip sensitive fields (#1540).\n const result = await this.executeCustomAction(\n collection,\n action,\n args,\n objectName,\n );\n return this.toPublicData(result);\n }\n }\n }\n\n /**\n * Execute a custom action on a collection/object\n */\n private async executeCustomAction(\n collection: SmrtCollection<SmrtObject>,\n action: string,\n args: ToolArgs,\n objectName?: string,\n ): Promise<unknown> {\n const id = args.id;\n const [methodName, methodDef] = objectName\n ? resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(objectName),\n action,\n )\n : [action, undefined];\n const metadata = this.resolveCustomActionMetadata(\n objectName ?? '',\n methodName,\n methodDef,\n objectName\n ? this.hasCollectionReceiver(ObjectRegistry.getClass(objectName))\n : false,\n );\n const methodArgs = buildCustomActionInvocationArgs(metadata, args);\n\n try {\n if (methodDef && metadata.idRequired && !id) {\n throw new Error(`ID is required for custom action '${action}'`);\n }\n if (!metadata.idRequired && id && methodDef) {\n throw new Error(\n `Custom action '${action}' is collection-scoped and does not accept an ID`,\n );\n }\n\n // If an ID is provided, get the specific object and call the method on it\n if (id) {\n const object = await collection.get(id as string);\n if (!object) {\n throw new Error('Object not found');\n }\n\n // Custom action names are resolved dynamically, so index the instance\n // through a record view and narrow the value to a callable after the\n // `typeof === 'function'` guard.\n const objectWithMethods = object as unknown as Record<string, unknown>;\n const objectMethod = objectWithMethods[methodName];\n if (typeof objectMethod === 'function') {\n // `.call(object, …)` preserves the receiver binding of the original\n // member call (`object[action](…)`) — the method relies on `this`.\n const result = await (objectMethod as InstanceCallable).call(\n object,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n throw new Error(\n `Method '${methodName}' not found on object instance`,\n );\n }\n } else if (metadata.isStatic && objectName) {\n const classInfo = ObjectRegistry.getClass(objectName);\n const classMethod = (\n classInfo?.constructor as unknown as\n | Record<string, unknown>\n | undefined\n )?.[methodName];\n if (typeof classMethod !== 'function') {\n throw new Error(\n `Static method '${methodName}' not found on ${objectName}`,\n );\n }\n const result = await (classMethod as InstanceCallable).call(\n classInfo?.constructor,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n // No ID provided, try to call the method on the collection\n const collectionMethod = (\n collection as unknown as Record<string, unknown>\n )[methodName];\n if (typeof collectionMethod === 'function') {\n // `.call(collection, …)` preserves the receiver binding of the\n // original member call (`collection[action](…)`).\n const result = await (collectionMethod as InstanceCallable).call(\n collection,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new CustomActionFailureError(failure);\n return result;\n } else {\n throw new Error(\n `Method '${methodName}' not found on collection. For object-specific actions, provide an 'id' parameter.`,\n );\n }\n }\n } catch (error) {\n if (error instanceof CustomActionFailureError) throw error;\n throw new Error(\n `Failed to execute custom action '${action}': ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n /**\n * Generate MCP server info\n */\n getServerInfo() {\n return {\n name: this.config.server?.name,\n version: this.config.server?.version,\n description: this.config.description,\n };\n }\n\n /**\n * Generate complete MCP server with stdio transport\n *\n * Creates a runnable Node.js script that exposes SMRT objects as MCP tools.\n * The generated server includes:\n * - Stdio transport integration\n * - Tool registration from ObjectRegistry\n * - Error handling and logging\n * - Graceful shutdown\n *\n * @param options - Server generation options\n * @returns Promise that resolves when all files are written\n *\n * @example\n * ```typescript\n * const generator = new MCPGenerator({\n * name: 'my-app',\n * version: '1.0.0'\n * });\n *\n * await generator.generateServer({\n * outputPath: '.smrt/mcp-server/index.js',\n * serverName: 'my-app-mcp',\n * debug: true\n * });\n * ```\n */\n async generateServer(\n options: {\n /** Path to output server file (relative or absolute) */\n outputPath?: string;\n\n /** Server name for configuration */\n serverName?: string;\n\n /** Server version */\n serverVersion?: string;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Generate Claude Desktop configuration example */\n generateClaudeConfigFile?: boolean;\n\n /** Generate README documentation */\n generateReadme?: boolean;\n\n /** Generate modular directory structure (tools/, handlers/, config.ts) */\n modular?: boolean;\n } = {},\n ): Promise<void> {\n const {\n outputPath = '.smrt/mcp-server/index.js',\n serverName = this.config.name || 'smrt-mcp-server',\n serverVersion = this.config.version || '1.0.0',\n debug = false,\n generateClaudeConfigFile = false,\n generateReadme = false,\n modular = false,\n } = options;\n\n // Resolve output path\n const resolvedPath = resolve(process.cwd(), outputPath);\n const outputDir = dirname(resolvedPath);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n if (modular) {\n // Generate modular structure: tools/, handlers/, config.ts, index.js\n await this.generateModularServer(\n outputDir,\n serverName,\n serverVersion,\n debug,\n );\n } else {\n // Generate single-file server with static tools\n const tools = await this.generateTools();\n\n const runtimeOptions: RuntimeOptions = {\n name: serverName,\n version: serverVersion,\n description: this.config.description,\n config: this.config,\n context: this.context,\n debug,\n tools,\n customActions: await this.runtimeCustomActions(tools),\n tenantScopedObjects: await this.tenantScopedObjectNames(tools),\n };\n\n const serverCode = generateRuntimeBootstrap(runtimeOptions);\n\n // Write server file\n await writeFile(resolvedPath, serverCode, 'utf-8');\n console.log(`✅ Generated MCP server: ${resolvedPath}`);\n }\n\n // Generate Claude Desktop configuration example\n if (generateClaudeConfigFile) {\n const claudeConfig = generateClaudeConfig(serverName, resolvedPath);\n const claudeConfigPath = resolve(outputDir, 'claude-config.example.json');\n await writeFile(\n claudeConfigPath,\n JSON.stringify(claudeConfig, null, 2),\n 'utf-8',\n );\n console.log(`✅ Generated Claude config example: ${claudeConfigPath}`);\n }\n\n // Generate README documentation\n if (generateReadme) {\n const readme = generateMCPDocumentation(serverName, outputPath);\n const readmePath = resolve(outputDir, 'MCP-README.md');\n await writeFile(readmePath, readme, 'utf-8');\n console.log(`✅ Generated MCP documentation: ${readmePath}`);\n }\n\n // Generate npm script suggestion\n const mcpScript = generateMCPScript(outputPath);\n console.log(`\\n📝 Add this to your package.json scripts:`);\n console.log(` \"mcp\": \"${mcpScript}\"\\n`);\n }\n\n private async runtimeCustomActions(\n tools: MCPTool[],\n ): Promise<NonNullable<RuntimeOptions['customActions']>> {\n const metadata: NonNullable<RuntimeOptions['customActions']> = {};\n const crudActions = new Set(['list', 'get', 'create', 'update', 'delete']);\n const classes = ObjectRegistry.getAllClasses();\n\n for (const tool of tools) {\n const separator = tool.name.indexOf('_');\n if (separator === -1) continue;\n const objectPrefix = tool.name.slice(0, separator);\n const action = tool.name.slice(separator + 1);\n if (crudActions.has(action)) continue;\n const matched = Array.from(classes.entries()).find(\n ([key, info]) => (info.name || key).toLowerCase() === objectPrefix,\n );\n if (!matched) continue;\n const [key, classInfo] = matched;\n const objectName = classInfo.name || key;\n const [methodName, method] = resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(objectName),\n action,\n );\n const resolved = this.resolveCustomActionMetadata(\n objectName,\n methodName,\n method,\n this.hasCollectionReceiver(classInfo),\n );\n metadata[tool.name] = {\n scope: resolved.scope,\n isStatic: resolved.isStatic,\n methodName,\n ...(resolved.parameters\n ? {\n parameterNames: resolved.parameters.map(\n (parameter) => parameter.name,\n ),\n optionsParameter:\n resolved.parameters.length === 1 &&\n resolved.parameters[0]?.name === 'options',\n }\n : {}),\n legacyOptions: !resolved.parameters,\n };\n }\n return metadata;\n }\n\n /**\n * Generate modular MCP server structure\n *\n * Creates separate files for tools, handlers, configuration, and main entry point.\n * This makes the generated server easier to customize and extend.\n *\n * @param outputDir - Directory to generate files in\n * @param serverName - Server name\n * @param serverVersion - Server version\n * @param debug - Enable debug logging\n */\n private async generateModularServer(\n outputDir: string,\n serverName: string,\n serverVersion: string,\n debug: boolean,\n ): Promise<void> {\n // Create subdirectories\n const toolsDir = resolve(outputDir, 'tools');\n const handlersDir = resolve(outputDir, 'handlers');\n\n await mkdir(toolsDir, { recursive: true });\n await mkdir(handlersDir, { recursive: true });\n\n // Generate config.ts\n const configPath = resolve(outputDir, 'config.ts');\n const configCode = this.generateConfigFile(\n serverName,\n serverVersion,\n debug,\n );\n await writeFile(configPath, configCode, 'utf-8');\n console.log(`✅ Generated config: ${configPath}`);\n\n // Generate tools/index.ts with tool definitions\n const toolsPath = resolve(toolsDir, 'index.ts');\n const toolsCode = await this.generateToolsFile();\n await writeFile(toolsPath, toolsCode, 'utf-8');\n console.log(`✅ Generated tools: ${toolsPath}`);\n\n // Generate handlers/index.ts with tool call handlers\n const handlersPath = resolve(handlersDir, 'index.ts');\n const tenantScopedObjects = await this.tenantScopedObjectNames(\n await this.generateTools(),\n );\n const handlersCode = await this.generateHandlersFile(tenantScopedObjects);\n await writeFile(handlersPath, handlersCode, 'utf-8');\n console.log(`✅ Generated handlers: ${handlersPath}`);\n\n // Generate main index.js entry point\n const indexPath = resolve(outputDir, 'index.js');\n const indexCode = this.generateModularIndex();\n await writeFile(indexPath, indexCode, 'utf-8');\n console.log(`✅ Generated MCP server: ${indexPath}`);\n }\n\n /**\n * Generate configuration file for modular server\n */\n private generateConfigFile(\n serverName: string,\n serverVersion: string,\n debug: boolean,\n ): string {\n return `/**\n * MCP Server Configuration\n * Auto-generated by @happyvertical/smrt-core\n */\n\nexport const SERVER_NAME = ${JSON.stringify(serverName)};\nexport const SERVER_VERSION = ${JSON.stringify(serverVersion)};\nexport const SERVER_DESCRIPTION = ${JSON.stringify(this.config.description)};\nexport const DEBUG = ${debug};\n`;\n }\n\n /**\n * Generate tools definitions file for modular server\n */\n private async generateToolsFile(): Promise<string> {\n const tools = await this.generateTools();\n\n return `/**\n * MCP Tools Definitions\n * Auto-generated from SMRT objects\n */\n\nexport const tools: Array<{\n name: string;\n description: string;\n inputSchema: any;\n}> = ${JSON.stringify(tools, null, 2)};\n`;\n }\n\n /**\n * Generate switch cases for tool execution\n */\n private async generateToolSwitchCases(\n indent: string = ' ',\n ): Promise<string> {\n const tools = await this.generateTools();\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n\n const switchCases = (\n await Promise.all(\n tools.map(async (tool) => {\n const separator = tool.name.indexOf('_');\n const objectName = tool.name.slice(0, separator);\n const action = tool.name.slice(separator + 1);\n\n switch (action) {\n case 'list':\n return `${indent}case '${tool.name}': {\n${indent} const limit = args.limit ?? 50;\n${indent} const offset = args.offset ?? 0;\n${indent} const where = args.where ?? {};\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const items = await collection.list({ where, limit, offset });\n${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent} return { content: [{ type: 'text', text: JSON.stringify(itemsPublic) }] };\n${indent}}`;\n\n case 'get':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id && !args.slug) {\n${indent} throw new Error('Either id or slug is required');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const filter = args.id || args.slug;\n${indent} const item = await collection.get(filter);\n\n${indent} if (!item) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(item.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const newItem = await collection.create(applyWritablePolicy('${capitalize(objectName)}', args));\n${indent} await newItem.save();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'update':\n return `${indent}case '${tool.name}': {\n${indent} const { id, ...updateData } = args;\n${indent} if (!id) {\n${indent} throw new Error('ID is required for update');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const existing = await collection.get(id);\n${indent} if (!existing) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} Object.assign(existing, applyWritablePolicy('${capitalize(objectName)}', updateData));\n${indent} await existing.save();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(existing.toPublicJSON(PUBLIC_JSON_OPTIONS)) }] };\n${indent}}`;\n\n case 'delete':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id) {\n${indent} throw new Error('ID is required for delete');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection('${capitalize(objectName)}', {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const toDelete = await collection.get(args.id);\n${indent} if (!toDelete) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await toDelete.delete();\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: 'Object deleted successfully' }) }] };\n${indent}}`;\n\n default: {\n // Custom actions use the same canonical receiver/argument contract\n // as the in-process and standalone MCP runtimes. In particular,\n // a route config cannot turn an instance method into a static one.\n const matched = Array.from(\n ObjectRegistry.getAllClasses().entries(),\n ).find(\n ([key, info]) =>\n (info.name || key).toLowerCase() === objectName.toLowerCase(),\n );\n if (!matched) {\n throw new Error(\n `Unable to resolve custom-action target for tool '${tool.name}'`,\n );\n }\n const [classKey, classInfo] = matched;\n const registeredName = classInfo.name || classKey;\n const [methodName, method] = resolveCustomActionMethod(\n await ObjectRegistry.getAllMethods(registeredName),\n action,\n );\n const metadata = this.resolveCustomActionMetadata(\n registeredName,\n methodName,\n method,\n this.hasCollectionReceiver(classInfo),\n );\n const methodArgs = !metadata.parameters\n ? 'Object.keys(options ?? {}).length > 0 ? options : directArgs'\n : metadata.parameters.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ? 'options'\n : `[${metadata.parameters\n .map(\n (parameter) =>\n `args[${JSON.stringify(\n customActionParameterInputName(\n metadata,\n parameter.name,\n ),\n )}]`,\n )\n .join(', ')}]`;\n return `${indent}case '${tool.name}': {\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (${JSON.stringify(metadata.scope)} === 'item' && !id) {\n${indent} throw new Error('ID is required for custom action ${action}');\n${indent} }\n${indent} if (${JSON.stringify(metadata.scope)} === 'collection' && id) {\n${indent} throw new Error('Custom action ${action} is collection-scoped and does not accept an ID');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(registeredName)}, {\n${indent} persistence: { type: 'sql', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = ${JSON.stringify(metadata.scope)} === 'item'\n${indent} ? await collection.get(id)\n${indent} : ${metadata.isStatic}\n${indent} ? ObjectRegistry.getClass(${JSON.stringify(registeredName)})?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(${JSON.stringify(\n metadata.scope === 'item'\n ? 'Object not found'\n : 'Custom action target not found',\n )});\n${indent} }\n\n${indent} const actionMethod = target[${JSON.stringify(methodName)}];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error('Method ${methodName} not found on custom action target');\n${indent} }\n\n${indent} const methodArgs = ${methodArgs.startsWith('[') ? methodArgs : `[${methodArgs}]`};\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return {\n${indent} content: [{ type: 'text', text: JSON.stringify({ error: failure }) }],\n${indent} isError: true,\n${indent} _meta: { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} };\n${indent} }\n\n${indent} return { content: [{ type: 'text', text: JSON.stringify(toPublicResult(result)) }] };\n${indent}}`;\n }\n }\n }),\n )\n ).join('\\n\\n');\n\n return switchCases;\n }\n\n /**\n * Generate handlers file for modular server\n */\n private async generateHandlersFile(\n tenantScopedObjects: string[] = [],\n ): Promise<string> {\n const switchCases = await this.generateToolSwitchCases(' ');\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n return `/**\n * MCP Tool Call Handlers\n * Auto-generated from SMRT objects\n *\n * SECURITY (#1540): responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This handler has no\n * per-call authentication principal — the generated stdio MCP server's trust\n * boundary is the host process / MCP client. Run it only in a trusted context\n * or behind an authenticated gateway.\n *\n * SECURITY (#1554): tenant-scoped tools run inside a fail-closed tenant gate;\n * the tenant is taken from SMRT_MCP_TENANT_ID (this server has no auth\n * principal) and tenancy is enabled so the interceptor enforces filtering.\n */\n\nimport { ObjectRegistry, normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}${\n hasTenantScoped\n ? `\n// Install the tenancy interceptor so tenant-scoped tools are filtered and the\n// gate fail-closes when no tenant is supplied (#1554).\nenableTenancy();\nconst TENANT_SCOPED = new Set(${JSON.stringify(tenantScopedSet)});\nconst MCP_TENANT_ID = process.env.SMRT_MCP_TENANT_ID || undefined;\nconst MCP_ALLOW_CROSS_TENANT = process.env.SMRT_MCP_ALLOW_CROSS_TENANT === 'true';\n`\n : ''\n}\nconst PUBLIC_JSON_OPTIONS = {\n permissions: (process.env.SMRT_MCP_PERMISSIONS || '')\n .split(',')\n .map((permission) => permission.trim())\n .filter(Boolean),\n};\n\n/**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * \\`@field({ readonly: true })\\` fields from create/update bodies, intersecting\n * with the optional \\`@smrt({ api: { writable: [...] } })\\` allowlist.\n */\nfunction applyWritablePolicy(objectName: string, data: any): Record<string, any> {\n if (!data || typeof data !== 'object') return {};\n const serverManaged = new Set<string>([\n 'id', 'tenantId', 'tenant_id',\n 'createdAt', 'created_at', 'updatedAt', 'updated_at',\n ]);\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api as any;\n if (apiConfig && typeof apiConfig === 'object' && Array.isArray(apiConfig.writable)) {\n writable = apiConfig.writable;\n }\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && ((def as any).readonly === true || (def as any)._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data as Record<string, any>)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n}\n\n/**\n * Sensitive-field-safe serialization for custom-action results (#1540).\n * Recurses through arrays and plain objects so nested SmrtObjects are stripped\n * too; non-plain instances (Date, etc.) and primitives pass through. Cycle-safe.\n */\nfunction toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any {\n if (value === null || typeof value !== 'object') return value;\n if (typeof value.toPublicJSON === 'function') return value.toPublicJSON(PUBLIC_JSON_OPTIONS);\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry: any) => toPublicResult(entry, seen));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, any> = {};\n for (const [key, entry] of Object.entries(value as Record<string, any>)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\n/**\n * Handle tool call request\n */\nexport async function handleToolCall(\n name: string,\n arguments: any = {},\n aiConfig: any = {}\n) {\n try {\n const args = arguments;\n\n const runToolBody = async () => {\n switch (name) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${name}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const [toolObject] = name.split('_');\n const result =\n toolObject && TENANT_SCOPED.has(toolObject.toLowerCase())\n ? await runTenantScopedEntryPoint(\n { tenantScoped: true, tenantId: MCP_TENANT_ID, allowCrossTenant: MCP_ALLOW_CROSS_TENANT, surface: 'MCP' },\n runToolBody,\n )\n : await runToolBody();`\n : `\n const result = await runToolBody();`\n}\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n return {\n content: [\n {\n type: 'text',\n text: \\`Error executing tool \\${name}: \\${errorMessage}\\`,\n },\n ],\n isError: true,\n };\n }\n}\n`;\n }\n\n /**\n * Generate modular index file (main entry point)\n */\n private generateModularIndex(): string {\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @happyvertical/smrt-core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n */\n\nimport { Server } from '@modelcontextprotocol/server';\nimport { serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { pathToFileURL } from 'node:url';\nimport { loadConfig } from '@happyvertical/smrt-config';\nimport { getDatabase } from '@happyvertical/sql';\nimport { getAI } from '@happyvertical/ai';\n\nimport { SERVER_NAME, SERVER_VERSION, DEBUG } from './config.js';\nimport { tools } from './tools/index.js';\nimport { handleToolCall } from './handlers/index.js';\n\n/**\n * Main server startup function\n */\nexport async function createServer() {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n console.error(\\`[MCP] Available tools:\\`, tools.map(t => t.name).join(', '));\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await loadConfig();\n const aiConfig = appConfig?.ai || {};\n\n // Create MCP server\n const server = new Server(\n {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler('tools/list', async () => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler('tools/call', async (request) => {\n const { name, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${name}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n return await handleToolCall(name, args, aiConfig);\n });\n\n return server;\n}\n\nasync function main() {\n try {\n const handle = serveStdio(() => createServer(), {\n onerror: (error) => console.error('[MCP] Protocol error:', error),\n });\n const shutdown = async () => {\n if (DEBUG) console.error('[MCP] Shutting down gracefully');\n await handle.close();\n process.exit(0);\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n } catch (error) {\n console.error('[MCP] Fatal error during server startup:', error);\n process.exit(1);\n }\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().catch((error) => {\n console.error('[MCP] Unhandled error:', error);\n process.exit(1);\n });\n}\n`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAwB/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAuCD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACG,QAAQ,CACZ,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;YACW,gBAAgB;IAoD9B;;;;;OAKG;YACW,oBAAoB;IAyElC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAiCtB;;OAEG;YACW,WAAW;IAyCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;YACW,yBAAyB;IA6EvC;;OAEG;YACW,uBAAuB;IAwCrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAuB5B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../scanner/types.js";
|
|
1
2
|
import { ManifestGenerator } from "../scanner/manifest-generator.js";
|
|
2
3
|
import { ManifestManager } from "./manager.js";
|
|
3
4
|
import { importWorkspaceModule } from "../utils/import-workspace-module.js";
|
|
@@ -319,7 +320,7 @@ export default ${exportName};
|
|
|
319
320
|
createEmptyManifest(options) {
|
|
320
321
|
const manifest = {
|
|
321
322
|
version: "1.0.0",
|
|
322
|
-
timestamp:
|
|
323
|
+
timestamp: 0,
|
|
323
324
|
objects: {},
|
|
324
325
|
moduleType: options.moduleType || "smrt"
|
|
325
326
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport fg from 'fast-glob';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes.\nconst logger = createLogger({ level: 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.info('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.info(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(process.cwd());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private discoverFiles(options: ManifestBuilderOptions): string[] {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n\n return fg.sync(include, {\n absolute: true,\n ignore: exclude,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.info(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.info('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages();\n logger.info(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.info(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.info(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: process.cwd(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run manifest generation passes (same as Vite plugin path)\n const manifestGen = new ManifestGenerator();\n manifestGen.injectTenantScopedFields(manifest);\n manifestGen.mergeInheritedFields(manifest);\n manifestGen.generateValidationRules(manifest);\n manifestGen.generateSchemas(manifest);\n manifestGen.assertTenantScopedSchemaContract(manifest);\n manifestGen.generateAgentManifests(manifest, packageName, packageJson);\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.info(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(process.cwd());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = isTest ? 'dev' : 'build';\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = options.outputDir || 'src/manifest';\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.info(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(process.cwd(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.info('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.info('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await import('vite');\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.info('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.info('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.info(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.info('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.info('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.info('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.info('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.info('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, process.cwd());\n if (!manifestPath) {\n logger.info(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.info(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.info(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(process.cwd(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n objects: {},\n moduleType: options.moduleType || 'smrt',\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages();\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport fg from 'fast-glob';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes.\nconst logger = createLogger({ level: 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.info('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.info(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(process.cwd());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private discoverFiles(options: ManifestBuilderOptions): string[] {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n\n return fg.sync(include, {\n absolute: true,\n ignore: exclude,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.info(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.info('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages();\n logger.info(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.info(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.info(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: process.cwd(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run manifest generation passes (same as Vite plugin path)\n const manifestGen = new ManifestGenerator();\n manifestGen.injectTenantScopedFields(manifest);\n manifestGen.mergeInheritedFields(manifest);\n manifestGen.generateValidationRules(manifest);\n manifestGen.generateSchemas(manifest);\n manifestGen.assertTenantScopedSchemaContract(manifest);\n manifestGen.generateAgentManifests(manifest, packageName, packageJson);\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.info(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(process.cwd());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = isTest ? 'dev' : 'build';\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = options.outputDir || 'src/manifest';\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.info(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(process.cwd(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.info('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.info('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await import('vite');\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.info('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.info('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.info(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.info('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.info('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.info('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.info('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.info('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, process.cwd());\n if (!manifestPath) {\n logger.info(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.info(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.info(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(process.cwd(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n moduleType: options.moduleType || 'smrt',\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages();\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../../src/manifest/manifest-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../../src/manifest/manifest-loader.ts"],"names":[],"mappings":"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"}
|