@happyvertical/smrt-core 0.40.62 → 0.40.64
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/AGENTS.md +29 -3
- package/README.md +50 -2
- package/agents/generators.md +22 -0
- package/dist/__typechecks__/collection-read-plan.d.ts +50 -0
- package/dist/__typechecks__/collection-read-plan.d.ts.map +1 -0
- package/dist/collection-read-plan.d.ts +77 -0
- package/dist/collection-read-plan.d.ts.map +1 -0
- package/dist/collection-read-plan.js +55 -0
- package/dist/collection-read-plan.js.map +1 -0
- package/dist/collection.d.ts +12 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +37 -5
- package/dist/collection.js.map +1 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +44 -5
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/decorators/index.d.ts +8 -2
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/embeddings/types.d.ts +4 -1
- package/dist/embeddings/types.d.ts.map +1 -1
- package/dist/generators/mcp-emit.d.ts +72 -0
- package/dist/generators/mcp-emit.d.ts.map +1 -0
- package/dist/generators/mcp-emit.js +104 -0
- package/dist/generators/mcp-emit.js.map +1 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +7 -1
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +17 -3
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +43 -21
- package/dist/generators/mcp.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/manifest/generator.d.ts +5 -0
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +10 -7
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/object.d.ts +11 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +12 -2
- package/dist/object.js.map +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +53 -3
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/types.d.ts +7 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -4
- package/dist/registry.js.map +1 -1
- package/dist/smrt-knowledge.json +7 -7
- package/dist/system/types.d.ts +4 -1
- package/dist/system/types.d.ts.map +1 -1
- package/dist/utils/scanner-module.d.ts +7 -0
- package/dist/utils/scanner-module.d.ts.map +1 -1
- package/dist/vite-plugin/index.d.ts +7 -0
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +3 -2
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +92 -26
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/package.json +11 -13
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-emit.js","names":[],"sources":["../../src/generators/mcp-emit.ts"],"sourcesContent":["/**\n * Emit helpers for generated MCP server sources.\n *\n * The MCP generators build their output as TypeScript source: the runtime\n * template carries type-only imports, type annotations, and generics. Whatever\n * the caller asked for as `outputPath` decides how that source has to be\n * written (#2279):\n *\n * - `.ts` / `.mts` — write the TypeScript verbatim. The consumer runs it\n * through `tsx` or Node's type stripping, which is what the repository's own\n * MCP conformance fixture does. The generated source therefore has to stay\n * erasable-syntax-only.\n * - `.cjs` / `.cts` — rejected. Generated servers are ES modules, so a\n * CommonJS target cannot run in any language.\n * - anything else (`.js` by default) — transpile to JavaScript first, so\n * `node .smrt/mcp-server/index.js` runs instead of dying on\n * `SyntaxError: Unexpected identifier 'CallToolRequest'`.\n *\n * Transpilation uses the `typescript` package that `@happyvertical/smrt-core`\n * already depends on, imported lazily so that merely importing the generators\n * does not pull the compiler into memory.\n */\n\n/** Language a generated file is written in, derived from its extension. */\nexport type GeneratedSourceLanguage = 'typescript' | 'javascript';\n\n/** Extension a generated module may be written with. */\nexport type GeneratedSourceExtension = '.ts' | '.mts' | '.js' | '.mjs';\n\nconst TYPESCRIPT_EXTENSIONS = ['.ts', '.mts'];\n\n/**\n * Generated servers are ES modules — they use `import` and `import.meta.url` —\n * so a CommonJS target can never run whatever language it is written in.\n */\nconst COMMONJS_EXTENSIONS = ['.cjs', '.cts'];\n\nfunction assertModuleTarget(outputPath: string, lowerCased: string): void {\n const commonJs = COMMONJS_EXTENSIONS.find((extension) =>\n lowerCased.endsWith(extension),\n );\n if (commonJs) {\n throw new Error(\n `Cannot generate an MCP server at '${outputPath}': the generated server is an ES module, so a ${commonJs} target cannot run. Use .js/.mjs for JavaScript or .ts/.mts for TypeScript.`,\n );\n }\n}\n\n/**\n * Decide how a generated file must be written from the path the caller asked\n * for. Unknown extensions emit JavaScript: `node` is the documented way to run\n * a generated server, so JavaScript is the safe default.\n *\n * @param outputPath - Path the generated file will be written to\n * @returns The language the file contents must be written in\n * @throws When the path asks for a CommonJS target\n */\nexport function resolveGeneratedSourceLanguage(\n outputPath: string,\n): GeneratedSourceLanguage {\n const lowerCased = outputPath.toLowerCase();\n assertModuleTarget(outputPath, lowerCased);\n return TYPESCRIPT_EXTENSIONS.some((extension) =>\n lowerCased.endsWith(extension),\n )\n ? 'typescript'\n : 'javascript';\n}\n\n/**\n * Extension for modules emitted alongside a generated entry point, so the\n * modular server's relative imports resolve to files that exist on disk *and*\n * are loaded with the entry's own module semantics. An `.mjs` entry point in a\n * CommonJS package needs `.mjs` siblings, not `.js` ones that Node would then\n * parse as CommonJS.\n *\n * @param outputPath - Path of the generated entry point\n * @returns The extension every sibling module is written with\n * @throws When the path asks for a CommonJS target\n */\nexport function generatedSiblingExtension(\n outputPath: string,\n): GeneratedSourceExtension {\n const lowerCased = outputPath.toLowerCase();\n assertModuleTarget(outputPath, lowerCased);\n if (lowerCased.endsWith('.mts')) return '.mts';\n if (lowerCased.endsWith('.ts')) return '.ts';\n if (lowerCased.endsWith('.mjs')) return '.mjs';\n return '.js';\n}\n\ntype TypeScriptApi = typeof import('typescript');\n\n/**\n * Load the TypeScript compiler API lazily.\n *\n * `typescript` is CommonJS: Node's ESM interop exposes the whole API on\n * `default`, while bundlers hand back the namespace itself.\n */\nasync function loadTypeScript(): Promise<TypeScriptApi> {\n const imported = (await import('typescript')) as TypeScriptApi & {\n default?: TypeScriptApi;\n };\n return imported.default ?? imported;\n}\n\n/**\n * Transpile generated TypeScript to runnable ESM JavaScript.\n *\n * `verbatimModuleSyntax` keeps every value import the template emits (an\n * unused-looking import must not be elided: the generated server relies on\n * side effects and on imports whose only use is inside emitted switch cases),\n * while type-only import specifiers are dropped.\n *\n * @param source - Generated TypeScript source\n * @param label - Target path or name reported in transpile diagnostics\n * @returns Equivalent JavaScript source\n * @throws When the generated source is not valid TypeScript\n */\nexport async function transpileGeneratedSource(\n source: string,\n label = 'generated MCP source',\n): Promise<string> {\n const ts = await loadTypeScript();\n\n const { outputText, diagnostics } = ts.transpileModule(source, {\n // The input is always TypeScript regardless of where it will be written;\n // a `.js` file name here would make the compiler parse it as JavaScript.\n fileName: 'smrt-generated-mcp-source.ts',\n reportDiagnostics: true,\n compilerOptions: {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n verbatimModuleSyntax: true,\n removeComments: false,\n newLine: ts.NewLineKind.LineFeed,\n },\n });\n\n const errors = (diagnostics ?? []).filter(\n (diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error,\n );\n if (errors.length > 0) {\n const details = errors\n .map((diagnostic) =>\n ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '),\n )\n .join('; ');\n throw new Error(\n `Failed to transpile generated MCP source (${label}): ${details}`,\n );\n }\n\n return outputText;\n}\n\n/**\n * Render generated TypeScript for the requested output language.\n *\n * @param source - Generated TypeScript source\n * @param language - Language the file must be written in\n * @param label - Target path or name reported in transpile diagnostics\n * @returns Source ready to be written to disk\n */\nexport async function renderGeneratedSource(\n source: string,\n language: GeneratedSourceLanguage,\n label = 'generated MCP source',\n): Promise<string> {\n if (language === 'typescript') {\n return source;\n }\n return transpileGeneratedSource(source, label);\n}\n"],"mappings":";AA6BA,IAAM,wBAAwB,CAAC,OAAO,MAAM;;;;;AAM5C,IAAM,sBAAsB,CAAC,QAAQ,MAAM;AAE3C,SAAS,mBAAmB,YAAoB,YAA0B;CACxE,MAAM,WAAW,oBAAoB,MAAM,cACzC,WAAW,SAAS,SAAS,CAC/B;CACA,IAAI,UACF,MAAM,IAAI,MACR,qCAAqC,WAAW,gDAAgD,SAAS,4EAC3G;AAEJ;;;;;;;;;;AAWA,SAAgB,+BACd,YACyB;CACzB,MAAM,aAAa,WAAW,YAAY;CAC1C,mBAAmB,YAAY,UAAU;CACzC,OAAO,sBAAsB,MAAM,cACjC,WAAW,SAAS,SAAS,CAC/B,IACI,eACA;AACN;;;;;;;;;;;;AAaA,SAAgB,0BACd,YAC0B;CAC1B,MAAM,aAAa,WAAW,YAAY;CAC1C,mBAAmB,YAAY,UAAU;CACzC,IAAI,WAAW,SAAS,MAAM,GAAG,OAAO;CACxC,IAAI,WAAW,SAAS,KAAK,GAAG,OAAO;CACvC,IAAI,WAAW,SAAS,MAAM,GAAG,OAAO;CACxC,OAAO;AACT;;;;;;;AAUA,eAAe,iBAAyC;CACtD,MAAM,WAAY,MAAM,OAAO;CAG/B,OAAO,SAAS,WAAW;AAC7B;;;;;;;;;;;;;;AAeA,eAAsB,yBACpB,QACA,QAAQ,wBACS;CACjB,MAAM,KAAK,MAAM,eAAe;CAEhC,MAAM,EAAE,YAAY,gBAAgB,GAAG,gBAAgB,QAAQ;EAG7D,UAAU;EACV,mBAAmB;EACnB,iBAAiB;GACf,QAAQ,GAAG,aAAa;GACxB,QAAQ,GAAG,WAAW;GACtB,kBAAkB,GAAG,qBAAqB;GAC1C,sBAAsB;GACtB,gBAAgB;GAChB,SAAS,GAAG,YAAY;EAC1B;CACF,CAAC;CAED,MAAM,UAAU,eAAe,CAAC,EAAA,CAAG,QAChC,eAAe,WAAW,aAAa,GAAG,mBAAmB,KAChE;CACA,IAAI,OAAO,SAAS,GAAG;EACrB,MAAM,UAAU,OACb,KAAK,eACJ,GAAG,6BAA6B,WAAW,aAAa,GAAG,CAC7D,CAAC,CACA,KAAK,IAAI;EACZ,MAAM,IAAI,MACR,6CAA6C,MAAM,KAAK,SAC1D;CACF;CAEA,OAAO;AACT;;;;;;;;;AAUA,eAAsB,sBACpB,QACA,UACA,QAAQ,wBACS;CACjB,IAAI,aAAa,cACf,OAAO;CAET,OAAO,yBAAyB,QAAQ,KAAK;AAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAStD,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC,CAAC,CAAC;IACH,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;KAClC,CAAC;IACF,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN;QACE,KAAK,EAAE,iBAAiB,CAAC;QACzB,QAAQ,EAAE,OAAO,CAAC;QAClB,sEAAsE;QACtE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,OAAO,CAAC;KACxB,CACF,CAAC;IACF,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAClB,MAAM,EACN;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CACF,CAAC;IAEF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAStD,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACxC,CAAC,CAAC;IACH,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;KAClC,CAAC;IACF,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN;QACE,KAAK,EAAE,iBAAiB,CAAC;QACzB,QAAQ,EAAE,OAAO,CAAC;QAClB,sEAAsE;QACtE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,OAAO,CAAC;KACxB,CACF,CAAC;IACF,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAClB,MAAM,EACN;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CACF,CAAC;IAEF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CAitB7E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,GAAE,MAA6B,GACxC,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CASR;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CA0GR"}
|
|
@@ -498,7 +498,13 @@ class McpTaskExtensionTransport {
|
|
|
498
498
|
onerror?: (error: Error) => void;
|
|
499
499
|
onmessage?: (message: any) => void;
|
|
500
500
|
|
|
501
|
-
|
|
501
|
+
// An explicit field, not a parameter property: a \`.ts\` target has to stay
|
|
502
|
+
// erasable-syntax-only so Node's type stripping can run it (#2279).
|
|
503
|
+
wire: StdioServerTransport;
|
|
504
|
+
|
|
505
|
+
constructor(wire: StdioServerTransport) {
|
|
506
|
+
this.wire = wire;
|
|
507
|
+
}
|
|
502
508
|
|
|
503
509
|
async start() {
|
|
504
510
|
this.wire.onclose = () => this.onclose?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-runtime-template.js","names":[],"sources":["../../src/generators/mcp-runtime-template.ts"],"sourcesContent":["/**\n * Runtime bootstrap template for generated MCP servers\n *\n * This template provides stdio transport integration for SMRT-generated MCP servers.\n * It handles:\n * - Server initialization with @modelcontextprotocol/server v2\n * - Tool registration from MCPGenerator\n * - Stdio transport connection\n * - Error handling and logging\n * - Graceful shutdown\n */\n\nimport type { CustomActionScope } from './custom-action.js';\nimport type { MCPConfig, MCPContext } from './mcp.js';\n\n/**\n * Helper function to capitalize first letter\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport interface RuntimeOptions {\n /** Server name (defaults to package name) */\n name?: string;\n\n /** Server version (defaults to package version) */\n version?: string;\n\n /** Server description */\n description?: string;\n\n /** MCP generator configuration */\n config?: MCPConfig;\n\n /** MCP context (database, AI client, etc.) */\n context?: MCPContext;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Static tool definitions (generated at build time) */\n tools?: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n }>;\n /** Cache hint emitted for deploy-static tools/list results. */\n toolListCacheHint?: {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n };\n /** Internal invocation metadata; never exposed by the MCP tools/list result. */\n customActions?: Record<\n string,\n {\n scope: CustomActionScope;\n isStatic: boolean;\n /** Declared method name; tool IDs are lowercased protocol aliases. */\n methodName?: string;\n parameterNames?: string[];\n optionsParameter?: boolean;\n legacyOptions: boolean;\n }\n >;\n /** Task-enabled item custom actions. Never emitted in tools/list. */\n taskActions?: Record<\n string,\n {\n objectName: string;\n objectType: string;\n }\n >;\n\n /**\n * Lowercased simple names of objects that are `@TenantScoped` (#1554). When\n * non-empty, the generated server imports the tenancy fail-closed gate and\n * wraps tenant-scoped tool calls so a stdio invocation cannot read across all\n * tenants. The tenant is sourced from `SMRT_MCP_TENANT_ID` /\n * `SMRT_MCP_ALLOW_CROSS_TENANT` env vars (this server has no auth principal).\n */\n tenantScopedObjects?: string[];\n\n /**\n * Build-time approved STI discriminators, keyed by the lowercased MCP object\n * prefix. The generated runtime uses this instead of searching an initially\n * empty registry, then loads the approved qualified type through the public\n * collection API.\n */\n stiTargets?: Record<string, Record<string, string>>;\n}\n\n/**\n * Generate runtime bootstrap code for MCP server\n *\n * @param options - Runtime configuration options\n * @returns TypeScript code for server entry point\n */\nexport function generateRuntimeBootstrap(options: RuntimeOptions = {}): string {\n const {\n name = 'smrt-mcp-server',\n version = '1.0.0',\n description = 'Auto-generated MCP server from SMRT objects',\n debug = false,\n tools = [],\n customActions = {},\n taskActions = {},\n tenantScopedObjects = [],\n stiTargets = {},\n toolListCacheHint = { ttlMs: 86_400_000, cacheScope: 'private' },\n } = options;\n\n // Generate static tool array as TypeScript code\n const toolsCode = tools.length > 0 ? JSON.stringify(tools, null, 2) : '[]';\n const hasTaskActions = Object.keys(taskActions).length > 0;\n\n // Fail-closed tenant context (#1554): only wire the tenancy gate when at\n // least one exposed object is tenant-scoped, so apps without tenancy never\n // get a dangling import.\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n // Generate static switch cases using shared helper\n const generateSwitchCases = (indent: string) => {\n return tools\n .map((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: process.env.DATABASE_TYPE || 'sqlite', 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} const structuredContent = {\n${indent} data: itemsPublic,\n${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },\n${indent} };\n${indent} return successResult(structuredContent, 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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult(item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget('${objectName}', args, aiConfig);\n\n${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));\n${indent} await newItem.save();\n\n${indent} return successResult(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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult(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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult({ success: true, message: 'Object deleted successfully' });\n${indent}}`;\n\n default:\n // Custom action. Its descriptor is deliberately kept separate\n // from TOOLS so MCP clients receive only protocol-defined fields.\n return `${indent}case '${tool.name}': {\n${indent} const actionMeta = CUSTOM_ACTIONS['${tool.name}'] || { scope: 'item', isStatic: false, legacyOptions: true };\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (actionMeta.scope === 'item' && !id) {\n${indent} throw new Error('ID is required for custom action ${action}');\n${indent} }\n${indent} if (actionMeta.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('${capitalize(objectName)}', {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = actionMeta.scope === 'item'\n${indent} ? await collection.get(id)\n${indent} : actionMeta.isStatic\n${indent} ? ObjectRegistry.getClass('${capitalize(objectName)}')?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(actionMeta.scope === 'item' ? 'Object not found' : 'Custom action target not found');\n${indent} }\n${indent} const actionMethod = target[actionMeta.methodName || '${action}'];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error('Method ${action} not found on custom action target');\n${indent} }\n\n${indent} const methodArgs = actionMeta.legacyOptions\n${indent} ? [Object.keys(options ?? {}).length > 0 ? options : directArgs]\n${indent} : actionMeta.optionsParameter\n${indent} ? [options]\n${indent} : (actionMeta.parameterNames || []).map((parameterName) => args[\n${indent} parameterName === 'id'\n${indent} ? 'actionId'\n${indent} : parameterName\n${indent} ]);\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return errorResult(\n${indent} { error: failure },\n${indent} JSON.stringify({ error: failure }),\n${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} );\n${indent} }\n\n${indent} const publicResult = toPublicResult(result);\n${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));\n${indent}}`;\n }\n })\n .join('\\n\\n');\n };\n\n const switchCases = generateSwitchCases(' ');\n\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @smrt/core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n *\n * SECURITY (#1540): tool responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This stdio server has NO\n * per-call authentication principal — its trust boundary is the host process /\n * MCP client that launches it. Run it only in a trusted context, or front it\n * with an authenticated gateway. Do not expose it directly to untrusted callers.\n */\n\nimport {\n type CallToolRequest,\n type ListToolsRequest,\n Server,\n ${hasTaskActions ? 'specTypeSchemas,' : ''}\n} from '@modelcontextprotocol/server';\nimport { ${hasTaskActions ? 'StdioServerTransport, ' : ''}serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\nimport { loadConfig } from '@happyvertical/smrt-config';\n${hasTaskActions ? \"import { McpTaskStore, TaskRunner } from '@happyvertical/smrt-jobs';\\n\" : ''}\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}\n// Server configuration\nconst SERVER_NAME = ${JSON.stringify(name)};\nconst SERVER_VERSION = ${JSON.stringify(version)};\nconst SERVER_DESCRIPTION = ${JSON.stringify(description)};\nconst DEBUG = ${debug};\n\n// Static tool definitions (generated at build time)\nconst TOOLS = ${toolsCode};\nconst TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};\nconst CUSTOM_ACTIONS = ${JSON.stringify(customActions)};\nconst TASK_ACTIONS = ${JSON.stringify(taskActions)};\nconst STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};\n${\n hasTenantScoped\n ? `\n// Fail-closed tenant context (#1554): tenant-scoped objects must run inside a\n// tenant. This stdio server has no auth principal, so the tenant is taken from\n// the environment; without it (and with tenancy enabled) tenant-scoped tools\n// throw rather than reading across all tenants.\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([\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)) {\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/** Resolve an advertised STI discriminator to its registered subtype collection. */\nasync function resolveCreateTarget(baseObjectName: string, args: Record<string, any>, aiConfig: any) {\n let objectName = baseObjectName;\n const discriminator = args._meta_type;\n const targets = STI_TARGETS[baseObjectName];\n if (typeof discriminator === 'string' && targets) {\n const target = targets[discriminator];\n if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);\n objectName = target;\n }\n const collection = await ObjectRegistry.getCollection(objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n ai: aiConfig,\n });\n return { collection, objectName };\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)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\nfunction successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {\n return {\n content: [{ type: 'text', text }],\n structuredContent,\n };\n}\n\nfunction errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {\n return {\n content: [{ type: 'text', text }],\n isError: true,\n structuredContent,\n ...(_meta ? { _meta } : {}),\n };\n}\n\n${\n hasTaskActions\n ? `\n// The bundled SDK validates tools/call responses against its older result\n// codec, which does not yet know CreateTaskResult. Intercept the extension at\n// the transport boundary, then pass every non-task message untouched to the\n// SDK server. This keeps ordinary MCP behaviour and version negotiation owned\n// by the SDK while making the extension available today.\nconst MCP_TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';\nlet taskRuntime: Promise<{ store: McpTaskStore; runner: TaskRunner }> | undefined;\n\nfunction clientSupportsTasks(message: any): boolean {\n return message?.params?._meta?.['io.modelcontextprotocol/clientCapabilities']\n ?.extensions?.[MCP_TASKS_EXTENSION] !== undefined;\n}\n\n/** Validate the 2026 request envelope before task dispatch mutates a job. */\nfunction taskEnvelopeError(message: any): { code: number; message: string; data?: any } | undefined {\n const meta = message?.params?._meta;\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {\n return {\n code: -32602,\n message: 'Request is missing the required _meta envelope for protocol revision 2026-07-28',\n };\n }\n const protocolVersion = meta['io.modelcontextprotocol/protocolVersion'];\n if (protocolVersion !== '2026-07-28') {\n return typeof protocolVersion === 'string'\n ? {\n code: -32022,\n message: 'Unsupported protocol version: ' + protocolVersion,\n data: { supported: ['2026-07-28'], requested: protocolVersion },\n }\n : {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/protocolVersion must be a string',\n };\n }\n if (!specTypeSchemas.ClientCapabilities.safeParse(\n meta['io.modelcontextprotocol/clientCapabilities'],\n ).success) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientCapabilities is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/clientInfo'] !== undefined &&\n !specTypeSchemas.Implementation.safeParse(\n meta['io.modelcontextprotocol/clientInfo'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientInfo is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/logLevel'] !== undefined &&\n !specTypeSchemas.LoggingLevel.safeParse(\n meta['io.modelcontextprotocol/logLevel'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/logLevel is invalid',\n };\n }\n if (\n meta.progressToken !== undefined &&\n !specTypeSchemas.ProgressToken.safeParse(meta.progressToken).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: progressToken is invalid',\n };\n }\n return undefined;\n}\n\nfunction jsonRpcError(id: any, code: number, message: string, data?: any) {\n return { jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } };\n}\n\nasync function getTaskRuntime() {\n if (!taskRuntime) {\n taskRuntime = (async () => {\n const firstAction = Object.values(TASK_ACTIONS)[0] as { objectName: string } | undefined;\n if (!firstAction) throw new Error('No task-enabled MCP action is configured');\n const collection = await ObjectRegistry.getCollection(firstAction.objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n });\n const db = collection.db;\n const store = await McpTaskStore.create(db, { ownerId: process.env.SMRT_MCP_TENANT_ID || null });\n const runner = new TaskRunner({ queues: ['mcp-tasks'] });\n await runner.initialize(db);\n await runner.start();\n return { store, runner };\n })();\n }\n return taskRuntime;\n}\n\nfunction taskInvocationArgs(actionMeta: any, args: Record<string, any>): any[] {\n const { id: _id, options, ...directArgs } = args;\n if (actionMeta.legacyOptions) {\n return [Object.keys(options ?? {}).length > 0 ? options : directArgs];\n }\n if (actionMeta.optionsParameter) return [options];\n const parameterNames = actionMeta.parameterNames || [];\n const invocationParameterNames = parameterNames.at(-1) === 'context'\n ? parameterNames.slice(0, -1)\n : parameterNames;\n return invocationParameterNames.map((parameterName: string) =>\n args[parameterName === 'id' ? 'actionId' : parameterName],\n );\n}\n\nasync function handleTaskExtensionMessage(message: any): Promise<any | null> {\n if (!message || typeof message !== 'object' || message.id === undefined) return null;\n const method = message.method;\n const params = message.params ?? {};\n const action = method === 'tools/call' ? TASK_ACTIONS[params.name] : undefined;\n const isTaskMethod = action || ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(method);\n if (!isTaskMethod) return null;\n // A task-enabled tool retains its normal synchronous behaviour until its\n // client explicitly opts into Tasks. Do not impose the task envelope on\n // legacy calls that will be passed untouched to the SDK.\n if (!clientSupportsTasks(message) && method === 'tools/call') return null;\n const envelopeError = taskEnvelopeError(message);\n if (envelopeError) {\n return jsonRpcError(message.id, envelopeError.code, envelopeError.message, envelopeError.data);\n }\n if (!clientSupportsTasks(message)) {\n // A task-only method cannot fall back to a legacy response shape. A task\n // tool can still run normally when the client did not opt in, so let the\n // SDK handle that direct tools/call path.\n if (method === 'tools/call') return null;\n return jsonRpcError(message.id, -32021, 'Missing required client capability', {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n });\n }\n try {\n const { store } = await getTaskRuntime();\n if (action) {\n if (typeof params.arguments?.id !== 'string') {\n return jsonRpcError(message.id, -32602, 'Task-enabled custom actions require an id');\n }\n const actionMeta = CUSTOM_ACTIONS[params.name];\n const task = await store.createTask({\n objectType: action.objectType,\n objectId: params.arguments.id,\n method: actionMeta.methodName || params.name.slice(params.name.indexOf('_') + 1),\n invocationArgs: taskInvocationArgs(actionMeta, params.arguments),\n tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'},\n });\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'task', content: [], structuredContent: {}, ...task } };\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcError(message.id, -32602, 'taskId is required');\n }\n if (method === 'tasks/get') {\n const task = await store.getTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete', ...task } };\n }\n if (method === 'tasks/update') {\n await store.updateTask(\n params.taskId,\n params.inputResponses && typeof params.inputResponses === 'object'\n ? params.inputResponses\n : {},\n );\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n }\n await store.cancelTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n } catch (error) {\n const messageText = error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcError(message.id, -32602, messageText);\n }\n}\n\nclass McpTaskExtensionTransport {\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: any) => void;\n\n constructor(private readonly wire: StdioServerTransport) {}\n\n async start() {\n this.wire.onclose = () => this.onclose?.();\n this.wire.onerror = (error) => this.onerror?.(error);\n this.wire.onmessage = (message) => {\n void (async () => {\n const response = await handleTaskExtensionMessage(message);\n if (response) await this.wire.send(response);\n else this.onmessage?.(message);\n })().catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));\n };\n await this.wire.start();\n }\n\n close() { return this.wire.close(); }\n send(message: any) { return this.wire.send(message); }\n}\n`\n : ''\n}\n\n/**\n * Main server startup function\n */\nexport async function createServer(): Promise<Server> {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n }\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context (#1554): install the tenancy interceptor so\n // tenant-scoped tools are actually filtered, and so the entry-point gate\n // throws (rather than passing through) when no tenant is supplied. Without\n // this, a tenant set via SMRT_MCP_TENANT_ID would only set async context\n // with no interceptor to enforce it.\n enableTenancy();\n`\n : ''\n}\n // Register the application package manifest before resolving generated\n // object names. Generated servers are commonly run from the application\n // package itself, which is not a node_modules dependency of its process.\n const localManifestPaths = [\n resolve(process.cwd(), 'dist', 'manifest.json'),\n resolve(process.cwd(), '.smrt', 'manifest.json'),\n ].filter(existsSync);\n if (localManifestPaths.length > 0) {\n ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });\n }\n\n // A manifest supplies schemas and tool metadata, while an executable\n // custom action also needs the application's actual class constructor.\n // Consumer builds generate this registration module; load it after the\n // manifest so decorators enrich the already-known metadata.\n const localRegisterPath = process.env.SMRT_MCP_REGISTER_PATH\n || resolve(process.cwd(), '.smrt', 'register.js');\n if (existsSync(localRegisterPath)) {\n await import(pathToFileURL(localRegisterPath).href);\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await loadConfig();\n const aiConfig = appConfig?.ai || {};\n\n if (DEBUG) {\n console.error(\\`[MCP] Loaded \\${TOOLS.length} static tools\\`);\n console.error(\\`[MCP] Available tools:\\`, TOOLS.map(t => t.name).join(', '));\n }\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 ${hasTaskActions ? \"extensions: { 'io.modelcontextprotocol/tasks': {} },\" : ''}\n },\n cacheHints: {\n 'tools/list': TOOL_LIST_CACHE_HINT,\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler('tools/list', async (_request: ListToolsRequest) => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: [...TOOLS].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler('tools/call', async (request: CallToolRequest) => {\n const { name: toolName, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${toolName}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n try {\n // Static switch statement for tool execution\n const runToolBody = async () => {\n switch (toolName) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${toolName}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const [toolObject] = toolName.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 if (DEBUG) {\n console.error(\\`[MCP] Tool executed successfully: \\${toolName}\\`);\n }\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error(\\`[MCP] Tool execution failed: \\${toolName}\\`, error);\n\n return errorResult(\n { error: { message: errorMessage } },\n \\`Error executing tool \\${toolName}: \\${errorMessage}\\`,\n );\n }\n });\n\n return server;\n}\n\nasync function main() {\n try {\n // Initialize the registry before accepting an intercepted task call. The\n // SDK would normally invoke this factory for the first regular message,\n // but task calls can be the first message on a stdio connection.\n const server = await createServer();\n const transport = ${hasTaskActions ? 'new McpTaskExtensionTransport(new StdioServerTransport())' : 'undefined'};\n const handle = serveStdio(() => server, {\n ...(transport ? { transport } : {}),\n onerror: (error) => console.error('[MCP] Protocol error:', error),\n });\n const shutdown = async () => {\n if (DEBUG) console.error('[MCP] Shutting down gracefully');\n ${hasTaskActions ? 'if (taskRuntime) {\\n const { runner } = await taskRuntime;\\n await runner.stop();\\n }' : ''}\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\n// Start only when executed, so adapters and tests may import the factory.\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/**\n * Generate package.json script for running MCP server\n *\n * @param serverPath - Path to generated server file (relative to package root)\n * @returns Script command for package.json\n */\nexport function generateMCPScript(\n serverPath: string = 'dist/mcp-server.js',\n): string {\n return `node ${serverPath}`;\n}\n\n/**\n * Generate Claude Desktop configuration example\n *\n * @param serverName - Name for the MCP server\n * @param serverPath - Absolute path to server file\n * @returns Configuration object for claude_desktop_config.json\n */\nexport function generateClaudeConfig(\n serverName: string,\n serverPath: string,\n): object {\n return {\n mcpServers: {\n [serverName]: {\n command: 'node',\n args: [serverPath],\n },\n },\n };\n}\n\n/**\n * Generate README documentation for MCP server setup\n *\n * @param serverName - Name of the MCP server\n * @param serverPath - Path to the server file\n * @returns Markdown documentation\n */\nexport function generateMCPDocumentation(\n serverName: string,\n serverPath: string,\n): string {\n return `# MCP Server Setup\n\nThis project includes an auto-generated MCP (Model Context Protocol) server that exposes SMRT objects as tools for AI integration.\n\n## Quick Start\n\n### 1. Build the MCP Server\n\n\\`\\`\\`bash\nnpm run build\n\\`\\`\\`\n\nThis generates the MCP server at: \\`${serverPath}\\`\n\n### 2. Configure Claude Desktop\n\nAdd the following to your Claude Desktop configuration file:\n\n**macOS**: \\`~/.config/Claude/claude_desktop_config.json\\`\n**Windows**: \\`%APPDATA%\\\\Claude\\\\claude_desktop_config.json\\`\n\n\\`\\`\\`json\n{\n \"mcpServers\": {\n \"${serverName}\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/${serverPath}\"]\n }\n }\n}\n\\`\\`\\`\n\nReplace \\`/absolute/path/to/\\` with the actual absolute path to your project directory.\n\n### 3. Restart Claude Desktop\n\nClose and reopen Claude Desktop to load the new MCP server.\n\n### 4. Test the Integration\n\nIn Claude Code, you can now use the auto-generated tools. For example:\n\n- \\`list_products\\` - List all products\n- \\`get_product\\` - Get a specific product by ID\n- \\`create_product\\` - Create a new product\n- And more...\n\n## Environment Variables\n\nThe MCP server supports optional environment variables:\n\n- \\`DATABASE_URL\\` - Database connection string\n\n**AI Provider Configuration (in priority order):**\n1. **Generic configuration** (supports any provider):\n - \\`SMRT_AI_PROVIDER\\` - Provider name (e.g., 'openai', 'anthropic', 'claude-cli', 'gemini')\n - \\`SMRT_AI_API_KEY\\` - API key for the provider\n - \\`SMRT_AI_MODEL\\` - Model to use (optional)\n\n2. **Provider-specific fallbacks**:\n - \\`OPENAI_API_KEY\\` - OpenAI API key (auto-detects provider as 'openai')\n - \\`ANTHROPIC_API_KEY\\` - Anthropic API key (auto-detects provider as 'anthropic')\n - \\`CLAUDE_API_KEY\\` + \\`CLAUDE_MODEL\\` - Claude CLI provider (defaults to 'sonnet')\n\n**Examples:**\n\\`\\`\\`bash\n# Using generic configuration (recommended)\nexport SMRT_AI_PROVIDER=claude-cli\nexport SMRT_AI_MODEL=sonnet\n\n# Using provider-specific configuration\nexport CLAUDE_API_KEY=your-key\nexport CLAUDE_MODEL=sonnet\n\n# Using OpenAI\nexport OPENAI_API_KEY=your-openai-key\n\\`\\`\\`\n\n## Troubleshooting\n\n### Server Not Appearing in Claude\n\n1. Check that the path in \\`claude_desktop_config.json\\` is absolute\n2. Verify the server file exists at the specified path\n3. Check Claude Desktop logs for errors\n\n### Tools Not Working\n\n1. Ensure your database is accessible (if using one)\n2. Check that SMRT objects are properly decorated with \\`@smrt()\\`\n3. Look for errors in the MCP server output\n\n### Debug Mode\n\nTo enable debug logging, set the \\`DEBUG\\` constant to \\`true\\` in the generated server file.\n\n## Generated Tools\n\nThe following tools are automatically generated from your SMRT objects:\n\n- **CRUD Operations**: \\`list_\\`, \\`get_\\`, \\`create_\\`, \\`update_\\`, \\`delete_\\` for each object type\n- **Custom Actions**: Any custom methods included in the \\`@smrt()\\` decorator configuration\n\nSee the SMRT object definitions for the complete list of available tools and their parameters.\n`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-runtime-template.js","names":[],"sources":["../../src/generators/mcp-runtime-template.ts"],"sourcesContent":["/**\n * Runtime bootstrap template for generated MCP servers\n *\n * This template provides stdio transport integration for SMRT-generated MCP servers.\n * It handles:\n * - Server initialization with @modelcontextprotocol/server v2\n * - Tool registration from MCPGenerator\n * - Stdio transport connection\n * - Error handling and logging\n * - Graceful shutdown\n */\n\nimport type { CustomActionScope } from './custom-action.js';\nimport type { MCPConfig, MCPContext } from './mcp.js';\n\n/**\n * Helper function to capitalize first letter\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport interface RuntimeOptions {\n /** Server name (defaults to package name) */\n name?: string;\n\n /** Server version (defaults to package version) */\n version?: string;\n\n /** Server description */\n description?: string;\n\n /** MCP generator configuration */\n config?: MCPConfig;\n\n /** MCP context (database, AI client, etc.) */\n context?: MCPContext;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Static tool definitions (generated at build time) */\n tools?: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n }>;\n /** Cache hint emitted for deploy-static tools/list results. */\n toolListCacheHint?: {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n };\n /** Internal invocation metadata; never exposed by the MCP tools/list result. */\n customActions?: Record<\n string,\n {\n scope: CustomActionScope;\n isStatic: boolean;\n /** Declared method name; tool IDs are lowercased protocol aliases. */\n methodName?: string;\n parameterNames?: string[];\n optionsParameter?: boolean;\n legacyOptions: boolean;\n }\n >;\n /** Task-enabled item custom actions. Never emitted in tools/list. */\n taskActions?: Record<\n string,\n {\n objectName: string;\n objectType: string;\n }\n >;\n\n /**\n * Lowercased simple names of objects that are `@TenantScoped` (#1554). When\n * non-empty, the generated server imports the tenancy fail-closed gate and\n * wraps tenant-scoped tool calls so a stdio invocation cannot read across all\n * tenants. The tenant is sourced from `SMRT_MCP_TENANT_ID` /\n * `SMRT_MCP_ALLOW_CROSS_TENANT` env vars (this server has no auth principal).\n */\n tenantScopedObjects?: string[];\n\n /**\n * Build-time approved STI discriminators, keyed by the lowercased MCP object\n * prefix. The generated runtime uses this instead of searching an initially\n * empty registry, then loads the approved qualified type through the public\n * collection API.\n */\n stiTargets?: Record<string, Record<string, string>>;\n}\n\n/**\n * Generate runtime bootstrap code for MCP server\n *\n * @param options - Runtime configuration options\n * @returns TypeScript code for server entry point\n */\nexport function generateRuntimeBootstrap(options: RuntimeOptions = {}): string {\n const {\n name = 'smrt-mcp-server',\n version = '1.0.0',\n description = 'Auto-generated MCP server from SMRT objects',\n debug = false,\n tools = [],\n customActions = {},\n taskActions = {},\n tenantScopedObjects = [],\n stiTargets = {},\n toolListCacheHint = { ttlMs: 86_400_000, cacheScope: 'private' },\n } = options;\n\n // Generate static tool array as TypeScript code\n const toolsCode = tools.length > 0 ? JSON.stringify(tools, null, 2) : '[]';\n const hasTaskActions = Object.keys(taskActions).length > 0;\n\n // Fail-closed tenant context (#1554): only wire the tenancy gate when at\n // least one exposed object is tenant-scoped, so apps without tenancy never\n // get a dangling import.\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n // Generate static switch cases using shared helper\n const generateSwitchCases = (indent: string) => {\n return tools\n .map((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: process.env.DATABASE_TYPE || 'sqlite', 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} const structuredContent = {\n${indent} data: itemsPublic,\n${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },\n${indent} };\n${indent} return successResult(structuredContent, 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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult(item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget('${objectName}', args, aiConfig);\n\n${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));\n${indent} await newItem.save();\n\n${indent} return successResult(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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult(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: process.env.DATABASE_TYPE || 'sqlite', 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 successResult({ success: true, message: 'Object deleted successfully' });\n${indent}}`;\n\n default:\n // Custom action. Its descriptor is deliberately kept separate\n // from TOOLS so MCP clients receive only protocol-defined fields.\n return `${indent}case '${tool.name}': {\n${indent} const actionMeta = CUSTOM_ACTIONS['${tool.name}'] || { scope: 'item', isStatic: false, legacyOptions: true };\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (actionMeta.scope === 'item' && !id) {\n${indent} throw new Error('ID is required for custom action ${action}');\n${indent} }\n${indent} if (actionMeta.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('${capitalize(objectName)}', {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = actionMeta.scope === 'item'\n${indent} ? await collection.get(id)\n${indent} : actionMeta.isStatic\n${indent} ? ObjectRegistry.getClass('${capitalize(objectName)}')?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(actionMeta.scope === 'item' ? 'Object not found' : 'Custom action target not found');\n${indent} }\n${indent} const actionMethod = target[actionMeta.methodName || '${action}'];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error('Method ${action} not found on custom action target');\n${indent} }\n\n${indent} const methodArgs = actionMeta.legacyOptions\n${indent} ? [Object.keys(options ?? {}).length > 0 ? options : directArgs]\n${indent} : actionMeta.optionsParameter\n${indent} ? [options]\n${indent} : (actionMeta.parameterNames || []).map((parameterName) => args[\n${indent} parameterName === 'id'\n${indent} ? 'actionId'\n${indent} : parameterName\n${indent} ]);\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return errorResult(\n${indent} { error: failure },\n${indent} JSON.stringify({ error: failure }),\n${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} );\n${indent} }\n\n${indent} const publicResult = toPublicResult(result);\n${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));\n${indent}}`;\n }\n })\n .join('\\n\\n');\n };\n\n const switchCases = generateSwitchCases(' ');\n\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @smrt/core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n *\n * SECURITY (#1540): tool responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This stdio server has NO\n * per-call authentication principal — its trust boundary is the host process /\n * MCP client that launches it. Run it only in a trusted context, or front it\n * with an authenticated gateway. Do not expose it directly to untrusted callers.\n */\n\nimport {\n type CallToolRequest,\n type ListToolsRequest,\n Server,\n ${hasTaskActions ? 'specTypeSchemas,' : ''}\n} from '@modelcontextprotocol/server';\nimport { ${hasTaskActions ? 'StdioServerTransport, ' : ''}serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\nimport { loadConfig } from '@happyvertical/smrt-config';\n${hasTaskActions ? \"import { McpTaskStore, TaskRunner } from '@happyvertical/smrt-jobs';\\n\" : ''}\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}\n// Server configuration\nconst SERVER_NAME = ${JSON.stringify(name)};\nconst SERVER_VERSION = ${JSON.stringify(version)};\nconst SERVER_DESCRIPTION = ${JSON.stringify(description)};\nconst DEBUG = ${debug};\n\n// Static tool definitions (generated at build time)\nconst TOOLS = ${toolsCode};\nconst TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};\nconst CUSTOM_ACTIONS = ${JSON.stringify(customActions)};\nconst TASK_ACTIONS = ${JSON.stringify(taskActions)};\nconst STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};\n${\n hasTenantScoped\n ? `\n// Fail-closed tenant context (#1554): tenant-scoped objects must run inside a\n// tenant. This stdio server has no auth principal, so the tenant is taken from\n// the environment; without it (and with tenancy enabled) tenant-scoped tools\n// throw rather than reading across all tenants.\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([\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)) {\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/** Resolve an advertised STI discriminator to its registered subtype collection. */\nasync function resolveCreateTarget(baseObjectName: string, args: Record<string, any>, aiConfig: any) {\n let objectName = baseObjectName;\n const discriminator = args._meta_type;\n const targets = STI_TARGETS[baseObjectName];\n if (typeof discriminator === 'string' && targets) {\n const target = targets[discriminator];\n if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);\n objectName = target;\n }\n const collection = await ObjectRegistry.getCollection(objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n ai: aiConfig,\n });\n return { collection, objectName };\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)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\nfunction successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {\n return {\n content: [{ type: 'text', text }],\n structuredContent,\n };\n}\n\nfunction errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {\n return {\n content: [{ type: 'text', text }],\n isError: true,\n structuredContent,\n ...(_meta ? { _meta } : {}),\n };\n}\n\n${\n hasTaskActions\n ? `\n// The bundled SDK validates tools/call responses against its older result\n// codec, which does not yet know CreateTaskResult. Intercept the extension at\n// the transport boundary, then pass every non-task message untouched to the\n// SDK server. This keeps ordinary MCP behaviour and version negotiation owned\n// by the SDK while making the extension available today.\nconst MCP_TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';\nlet taskRuntime: Promise<{ store: McpTaskStore; runner: TaskRunner }> | undefined;\n\nfunction clientSupportsTasks(message: any): boolean {\n return message?.params?._meta?.['io.modelcontextprotocol/clientCapabilities']\n ?.extensions?.[MCP_TASKS_EXTENSION] !== undefined;\n}\n\n/** Validate the 2026 request envelope before task dispatch mutates a job. */\nfunction taskEnvelopeError(message: any): { code: number; message: string; data?: any } | undefined {\n const meta = message?.params?._meta;\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {\n return {\n code: -32602,\n message: 'Request is missing the required _meta envelope for protocol revision 2026-07-28',\n };\n }\n const protocolVersion = meta['io.modelcontextprotocol/protocolVersion'];\n if (protocolVersion !== '2026-07-28') {\n return typeof protocolVersion === 'string'\n ? {\n code: -32022,\n message: 'Unsupported protocol version: ' + protocolVersion,\n data: { supported: ['2026-07-28'], requested: protocolVersion },\n }\n : {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/protocolVersion must be a string',\n };\n }\n if (!specTypeSchemas.ClientCapabilities.safeParse(\n meta['io.modelcontextprotocol/clientCapabilities'],\n ).success) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientCapabilities is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/clientInfo'] !== undefined &&\n !specTypeSchemas.Implementation.safeParse(\n meta['io.modelcontextprotocol/clientInfo'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientInfo is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/logLevel'] !== undefined &&\n !specTypeSchemas.LoggingLevel.safeParse(\n meta['io.modelcontextprotocol/logLevel'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/logLevel is invalid',\n };\n }\n if (\n meta.progressToken !== undefined &&\n !specTypeSchemas.ProgressToken.safeParse(meta.progressToken).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: progressToken is invalid',\n };\n }\n return undefined;\n}\n\nfunction jsonRpcError(id: any, code: number, message: string, data?: any) {\n return { jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } };\n}\n\nasync function getTaskRuntime() {\n if (!taskRuntime) {\n taskRuntime = (async () => {\n const firstAction = Object.values(TASK_ACTIONS)[0] as { objectName: string } | undefined;\n if (!firstAction) throw new Error('No task-enabled MCP action is configured');\n const collection = await ObjectRegistry.getCollection(firstAction.objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n });\n const db = collection.db;\n const store = await McpTaskStore.create(db, { ownerId: process.env.SMRT_MCP_TENANT_ID || null });\n const runner = new TaskRunner({ queues: ['mcp-tasks'] });\n await runner.initialize(db);\n await runner.start();\n return { store, runner };\n })();\n }\n return taskRuntime;\n}\n\nfunction taskInvocationArgs(actionMeta: any, args: Record<string, any>): any[] {\n const { id: _id, options, ...directArgs } = args;\n if (actionMeta.legacyOptions) {\n return [Object.keys(options ?? {}).length > 0 ? options : directArgs];\n }\n if (actionMeta.optionsParameter) return [options];\n const parameterNames = actionMeta.parameterNames || [];\n const invocationParameterNames = parameterNames.at(-1) === 'context'\n ? parameterNames.slice(0, -1)\n : parameterNames;\n return invocationParameterNames.map((parameterName: string) =>\n args[parameterName === 'id' ? 'actionId' : parameterName],\n );\n}\n\nasync function handleTaskExtensionMessage(message: any): Promise<any | null> {\n if (!message || typeof message !== 'object' || message.id === undefined) return null;\n const method = message.method;\n const params = message.params ?? {};\n const action = method === 'tools/call' ? TASK_ACTIONS[params.name] : undefined;\n const isTaskMethod = action || ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(method);\n if (!isTaskMethod) return null;\n // A task-enabled tool retains its normal synchronous behaviour until its\n // client explicitly opts into Tasks. Do not impose the task envelope on\n // legacy calls that will be passed untouched to the SDK.\n if (!clientSupportsTasks(message) && method === 'tools/call') return null;\n const envelopeError = taskEnvelopeError(message);\n if (envelopeError) {\n return jsonRpcError(message.id, envelopeError.code, envelopeError.message, envelopeError.data);\n }\n if (!clientSupportsTasks(message)) {\n // A task-only method cannot fall back to a legacy response shape. A task\n // tool can still run normally when the client did not opt in, so let the\n // SDK handle that direct tools/call path.\n if (method === 'tools/call') return null;\n return jsonRpcError(message.id, -32021, 'Missing required client capability', {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n });\n }\n try {\n const { store } = await getTaskRuntime();\n if (action) {\n if (typeof params.arguments?.id !== 'string') {\n return jsonRpcError(message.id, -32602, 'Task-enabled custom actions require an id');\n }\n const actionMeta = CUSTOM_ACTIONS[params.name];\n const task = await store.createTask({\n objectType: action.objectType,\n objectId: params.arguments.id,\n method: actionMeta.methodName || params.name.slice(params.name.indexOf('_') + 1),\n invocationArgs: taskInvocationArgs(actionMeta, params.arguments),\n tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'},\n });\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'task', content: [], structuredContent: {}, ...task } };\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcError(message.id, -32602, 'taskId is required');\n }\n if (method === 'tasks/get') {\n const task = await store.getTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete', ...task } };\n }\n if (method === 'tasks/update') {\n await store.updateTask(\n params.taskId,\n params.inputResponses && typeof params.inputResponses === 'object'\n ? params.inputResponses\n : {},\n );\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n }\n await store.cancelTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n } catch (error) {\n const messageText = error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcError(message.id, -32602, messageText);\n }\n}\n\nclass McpTaskExtensionTransport {\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: any) => void;\n\n // An explicit field, not a parameter property: a \\`.ts\\` target has to stay\n // erasable-syntax-only so Node's type stripping can run it (#2279).\n wire: StdioServerTransport;\n\n constructor(wire: StdioServerTransport) {\n this.wire = wire;\n }\n\n async start() {\n this.wire.onclose = () => this.onclose?.();\n this.wire.onerror = (error) => this.onerror?.(error);\n this.wire.onmessage = (message) => {\n void (async () => {\n const response = await handleTaskExtensionMessage(message);\n if (response) await this.wire.send(response);\n else this.onmessage?.(message);\n })().catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));\n };\n await this.wire.start();\n }\n\n close() { return this.wire.close(); }\n send(message: any) { return this.wire.send(message); }\n}\n`\n : ''\n}\n\n/**\n * Main server startup function\n */\nexport async function createServer(): Promise<Server> {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n }\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context (#1554): install the tenancy interceptor so\n // tenant-scoped tools are actually filtered, and so the entry-point gate\n // throws (rather than passing through) when no tenant is supplied. Without\n // this, a tenant set via SMRT_MCP_TENANT_ID would only set async context\n // with no interceptor to enforce it.\n enableTenancy();\n`\n : ''\n}\n // Register the application package manifest before resolving generated\n // object names. Generated servers are commonly run from the application\n // package itself, which is not a node_modules dependency of its process.\n const localManifestPaths = [\n resolve(process.cwd(), 'dist', 'manifest.json'),\n resolve(process.cwd(), '.smrt', 'manifest.json'),\n ].filter(existsSync);\n if (localManifestPaths.length > 0) {\n ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });\n }\n\n // A manifest supplies schemas and tool metadata, while an executable\n // custom action also needs the application's actual class constructor.\n // Consumer builds generate this registration module; load it after the\n // manifest so decorators enrich the already-known metadata.\n const localRegisterPath = process.env.SMRT_MCP_REGISTER_PATH\n || resolve(process.cwd(), '.smrt', 'register.js');\n if (existsSync(localRegisterPath)) {\n await import(pathToFileURL(localRegisterPath).href);\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await loadConfig();\n const aiConfig = appConfig?.ai || {};\n\n if (DEBUG) {\n console.error(\\`[MCP] Loaded \\${TOOLS.length} static tools\\`);\n console.error(\\`[MCP] Available tools:\\`, TOOLS.map(t => t.name).join(', '));\n }\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 ${hasTaskActions ? \"extensions: { 'io.modelcontextprotocol/tasks': {} },\" : ''}\n },\n cacheHints: {\n 'tools/list': TOOL_LIST_CACHE_HINT,\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler('tools/list', async (_request: ListToolsRequest) => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: [...TOOLS].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler('tools/call', async (request: CallToolRequest) => {\n const { name: toolName, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${toolName}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n try {\n // Static switch statement for tool execution\n const runToolBody = async () => {\n switch (toolName) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${toolName}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const [toolObject] = toolName.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 if (DEBUG) {\n console.error(\\`[MCP] Tool executed successfully: \\${toolName}\\`);\n }\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error(\\`[MCP] Tool execution failed: \\${toolName}\\`, error);\n\n return errorResult(\n { error: { message: errorMessage } },\n \\`Error executing tool \\${toolName}: \\${errorMessage}\\`,\n );\n }\n });\n\n return server;\n}\n\nasync function main() {\n try {\n // Initialize the registry before accepting an intercepted task call. The\n // SDK would normally invoke this factory for the first regular message,\n // but task calls can be the first message on a stdio connection.\n const server = await createServer();\n const transport = ${hasTaskActions ? 'new McpTaskExtensionTransport(new StdioServerTransport())' : 'undefined'};\n const handle = serveStdio(() => server, {\n ...(transport ? { transport } : {}),\n onerror: (error) => console.error('[MCP] Protocol error:', error),\n });\n const shutdown = async () => {\n if (DEBUG) console.error('[MCP] Shutting down gracefully');\n ${hasTaskActions ? 'if (taskRuntime) {\\n const { runner } = await taskRuntime;\\n await runner.stop();\\n }' : ''}\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\n// Start only when executed, so adapters and tests may import the factory.\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/**\n * Generate package.json script for running MCP server\n *\n * @param serverPath - Path to generated server file (relative to package root)\n * @returns Script command for package.json\n */\nexport function generateMCPScript(\n serverPath: string = 'dist/mcp-server.js',\n): string {\n return `node ${serverPath}`;\n}\n\n/**\n * Generate Claude Desktop configuration example\n *\n * @param serverName - Name for the MCP server\n * @param serverPath - Absolute path to server file\n * @returns Configuration object for claude_desktop_config.json\n */\nexport function generateClaudeConfig(\n serverName: string,\n serverPath: string,\n): object {\n return {\n mcpServers: {\n [serverName]: {\n command: 'node',\n args: [serverPath],\n },\n },\n };\n}\n\n/**\n * Generate README documentation for MCP server setup\n *\n * @param serverName - Name of the MCP server\n * @param serverPath - Path to the server file\n * @returns Markdown documentation\n */\nexport function generateMCPDocumentation(\n serverName: string,\n serverPath: string,\n): string {\n return `# MCP Server Setup\n\nThis project includes an auto-generated MCP (Model Context Protocol) server that exposes SMRT objects as tools for AI integration.\n\n## Quick Start\n\n### 1. Build the MCP Server\n\n\\`\\`\\`bash\nnpm run build\n\\`\\`\\`\n\nThis generates the MCP server at: \\`${serverPath}\\`\n\n### 2. Configure Claude Desktop\n\nAdd the following to your Claude Desktop configuration file:\n\n**macOS**: \\`~/.config/Claude/claude_desktop_config.json\\`\n**Windows**: \\`%APPDATA%\\\\Claude\\\\claude_desktop_config.json\\`\n\n\\`\\`\\`json\n{\n \"mcpServers\": {\n \"${serverName}\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/${serverPath}\"]\n }\n }\n}\n\\`\\`\\`\n\nReplace \\`/absolute/path/to/\\` with the actual absolute path to your project directory.\n\n### 3. Restart Claude Desktop\n\nClose and reopen Claude Desktop to load the new MCP server.\n\n### 4. Test the Integration\n\nIn Claude Code, you can now use the auto-generated tools. For example:\n\n- \\`list_products\\` - List all products\n- \\`get_product\\` - Get a specific product by ID\n- \\`create_product\\` - Create a new product\n- And more...\n\n## Environment Variables\n\nThe MCP server supports optional environment variables:\n\n- \\`DATABASE_URL\\` - Database connection string\n\n**AI Provider Configuration (in priority order):**\n1. **Generic configuration** (supports any provider):\n - \\`SMRT_AI_PROVIDER\\` - Provider name (e.g., 'openai', 'anthropic', 'claude-cli', 'gemini')\n - \\`SMRT_AI_API_KEY\\` - API key for the provider\n - \\`SMRT_AI_MODEL\\` - Model to use (optional)\n\n2. **Provider-specific fallbacks**:\n - \\`OPENAI_API_KEY\\` - OpenAI API key (auto-detects provider as 'openai')\n - \\`ANTHROPIC_API_KEY\\` - Anthropic API key (auto-detects provider as 'anthropic')\n - \\`CLAUDE_API_KEY\\` + \\`CLAUDE_MODEL\\` - Claude CLI provider (defaults to 'sonnet')\n\n**Examples:**\n\\`\\`\\`bash\n# Using generic configuration (recommended)\nexport SMRT_AI_PROVIDER=claude-cli\nexport SMRT_AI_MODEL=sonnet\n\n# Using provider-specific configuration\nexport CLAUDE_API_KEY=your-key\nexport CLAUDE_MODEL=sonnet\n\n# Using OpenAI\nexport OPENAI_API_KEY=your-openai-key\n\\`\\`\\`\n\n## Troubleshooting\n\n### Server Not Appearing in Claude\n\n1. Check that the path in \\`claude_desktop_config.json\\` is absolute\n2. Verify the server file exists at the specified path\n3. Check Claude Desktop logs for errors\n\n### Tools Not Working\n\n1. Ensure your database is accessible (if using one)\n2. Check that SMRT objects are properly decorated with \\`@smrt()\\`\n3. Look for errors in the MCP server output\n\n### Debug Mode\n\nTo enable debug logging, set the \\`DEBUG\\` constant to \\`true\\` in the generated server file.\n\n## Generated Tools\n\nThe following tools are automatically generated from your SMRT objects:\n\n- **CRUD Operations**: \\`list_\\`, \\`get_\\`, \\`create_\\`, \\`update_\\`, \\`delete_\\` for each object type\n- **Custom Actions**: Any custom methods included in the \\`@smrt()\\` decorator configuration\n\nSee the SMRT object definitions for the complete list of available tools and their parameters.\n`;\n}\n"],"mappings":"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"}
|
package/dist/generators/mcp.d.ts
CHANGED
|
@@ -297,7 +297,13 @@ export declare class MCPGenerator {
|
|
|
297
297
|
* ```
|
|
298
298
|
*/
|
|
299
299
|
generateServer(options?: {
|
|
300
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* Path to output server file (relative or absolute).
|
|
302
|
+
*
|
|
303
|
+
* The extension decides the emitted language: `.ts`/`.mts`/`.cts` keep
|
|
304
|
+
* the generated TypeScript, anything else is transpiled to JavaScript so
|
|
305
|
+
* plain `node <path>` runs it (#2279).
|
|
306
|
+
*/
|
|
301
307
|
outputPath?: string;
|
|
302
308
|
/** Server name for configuration */
|
|
303
309
|
serverName?: string;
|
|
@@ -309,7 +315,7 @@ export declare class MCPGenerator {
|
|
|
309
315
|
generateClaudeConfigFile?: boolean;
|
|
310
316
|
/** Generate README documentation */
|
|
311
317
|
generateReadme?: boolean;
|
|
312
|
-
/** Generate modular directory structure (tools/, handlers/, config
|
|
318
|
+
/** Generate modular directory structure (tools/, handlers/, config) */
|
|
313
319
|
modular?: boolean;
|
|
314
320
|
}): Promise<void>;
|
|
315
321
|
private runtimeCustomActions;
|
|
@@ -328,10 +334,15 @@ export declare class MCPGenerator {
|
|
|
328
334
|
* Creates separate files for tools, handlers, configuration, and main entry point.
|
|
329
335
|
* This makes the generated server easier to customize and extend.
|
|
330
336
|
*
|
|
331
|
-
*
|
|
337
|
+
* The sibling modules use the entry point's own extension and the entry
|
|
338
|
+
* emits matching relative specifiers, so its imports resolve to files that
|
|
339
|
+
* exist and load with the same module semantics (#2279).
|
|
340
|
+
*
|
|
341
|
+
* @param indexPath - Absolute path of the entry point to generate
|
|
332
342
|
* @param serverName - Server name
|
|
333
343
|
* @param serverVersion - Server version
|
|
334
344
|
* @param debug - Enable debug logging
|
|
345
|
+
* @param language - Language the generated files are written in
|
|
335
346
|
*/
|
|
336
347
|
private generateModularServer;
|
|
337
348
|
/**
|
|
@@ -352,6 +363,9 @@ export declare class MCPGenerator {
|
|
|
352
363
|
private generateHandlersFile;
|
|
353
364
|
/**
|
|
354
365
|
* Generate modular index file (main entry point)
|
|
366
|
+
*
|
|
367
|
+
* @param toolListCacheHint - Cache hint emitted for `tools/list` results
|
|
368
|
+
* @param extension - Extension of the sibling modules this entry imports
|
|
355
369
|
*/
|
|
356
370
|
private generateModularIndex;
|
|
357
371
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/generators/mcp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/generators/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/generators/mcp.js
CHANGED
|
@@ -2,6 +2,7 @@ import { SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInvocationArgs,
|
|
|
2
2
|
import { ObjectRegistry } from "../registry.js";
|
|
3
3
|
import { SmrtCollection } from "../collection.js";
|
|
4
4
|
import { runWithTenantGate } from "./tenant-gate.js";
|
|
5
|
+
import { generatedSiblingExtension, renderGeneratedSource, resolveGeneratedSourceLanguage } from "./mcp-emit.js";
|
|
5
6
|
import { generateClaudeConfig, generateMCPDocumentation, generateMCPScript, generateRuntimeBootstrap } from "./mcp-runtime-template.js";
|
|
6
7
|
import { buildToolInputSchema, fieldTypeToJsonSchema, finalizeMcpJsonSchema } from "./tool-schema.js";
|
|
7
8
|
import { dirname, resolve } from "node:path";
|
|
@@ -12,6 +13,19 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
12
13
|
*
|
|
13
14
|
* Exposes smrt objects as AI tools for Claude, GPT, and other AI models
|
|
14
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Write one generated module, rendering it for the requested output language.
|
|
18
|
+
*
|
|
19
|
+
* Every generator in this file produces TypeScript source; a JavaScript target
|
|
20
|
+
* is transpiled on the way out so the written file is runnable as-is (#2279).
|
|
21
|
+
*
|
|
22
|
+
* @param targetPath - Absolute path of the file to write
|
|
23
|
+
* @param source - Generated TypeScript source
|
|
24
|
+
* @param language - Language the file must be written in
|
|
25
|
+
*/
|
|
26
|
+
async function writeGeneratedFile(targetPath, source, language) {
|
|
27
|
+
await writeFile(targetPath, await renderGeneratedSource(source, language, targetPath), "utf-8");
|
|
28
|
+
}
|
|
15
29
|
var MCP_STABLE_CATALOG_TTL_MS = 864e5;
|
|
16
30
|
/**
|
|
17
31
|
* Resolve the generated tools/list cache policy at generation time.
|
|
@@ -885,13 +899,14 @@ var MCPGenerator = class {
|
|
|
885
899
|
const { outputPath = ".smrt/mcp-server/index.js", serverName = this.config.name || "smrt-mcp-server", serverVersion = this.config.version || "1.0.0", debug = false, generateClaudeConfigFile = false, generateReadme = false, modular = false } = options;
|
|
886
900
|
const resolvedPath = resolve(process.cwd(), outputPath);
|
|
887
901
|
const outputDir = dirname(resolvedPath);
|
|
902
|
+
const language = resolveGeneratedSourceLanguage(resolvedPath);
|
|
888
903
|
await mkdir(outputDir, { recursive: true });
|
|
889
|
-
if (modular) await this.generateModularServer(
|
|
904
|
+
if (modular) await this.generateModularServer(resolvedPath, serverName, serverVersion, debug, language);
|
|
890
905
|
else {
|
|
891
906
|
const tools = await this.generateTools();
|
|
892
907
|
const tenantScopedObjects = await this.tenantScopedObjectNames(tools);
|
|
893
908
|
const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(tools);
|
|
894
|
-
await
|
|
909
|
+
await writeGeneratedFile(resolvedPath, generateRuntimeBootstrap({
|
|
895
910
|
name: serverName,
|
|
896
911
|
version: serverVersion,
|
|
897
912
|
description: this.config.description,
|
|
@@ -904,7 +919,7 @@ var MCPGenerator = class {
|
|
|
904
919
|
tenantScopedObjects,
|
|
905
920
|
stiTargets: this.runtimeStiTargets(tools),
|
|
906
921
|
toolListCacheHint: resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools)
|
|
907
|
-
}),
|
|
922
|
+
}), language);
|
|
908
923
|
console.log(`✅ Generated MCP server: ${resolvedPath}`);
|
|
909
924
|
}
|
|
910
925
|
if (generateClaudeConfigFile) {
|
|
@@ -1006,30 +1021,36 @@ var MCPGenerator = class {
|
|
|
1006
1021
|
* Creates separate files for tools, handlers, configuration, and main entry point.
|
|
1007
1022
|
* This makes the generated server easier to customize and extend.
|
|
1008
1023
|
*
|
|
1009
|
-
*
|
|
1024
|
+
* The sibling modules use the entry point's own extension and the entry
|
|
1025
|
+
* emits matching relative specifiers, so its imports resolve to files that
|
|
1026
|
+
* exist and load with the same module semantics (#2279).
|
|
1027
|
+
*
|
|
1028
|
+
* @param indexPath - Absolute path of the entry point to generate
|
|
1010
1029
|
* @param serverName - Server name
|
|
1011
1030
|
* @param serverVersion - Server version
|
|
1012
1031
|
* @param debug - Enable debug logging
|
|
1032
|
+
* @param language - Language the generated files are written in
|
|
1013
1033
|
*/
|
|
1014
|
-
async generateModularServer(
|
|
1034
|
+
async generateModularServer(indexPath, serverName, serverVersion, debug, language) {
|
|
1035
|
+
const outputDir = dirname(indexPath);
|
|
1036
|
+
const extension = generatedSiblingExtension(indexPath);
|
|
1015
1037
|
const toolsDir = resolve(outputDir, "tools");
|
|
1016
1038
|
const handlersDir = resolve(outputDir, "handlers");
|
|
1017
1039
|
await mkdir(toolsDir, { recursive: true });
|
|
1018
1040
|
await mkdir(handlersDir, { recursive: true });
|
|
1019
|
-
const configPath = resolve(outputDir,
|
|
1020
|
-
await
|
|
1041
|
+
const configPath = resolve(outputDir, `config${extension}`);
|
|
1042
|
+
await writeGeneratedFile(configPath, this.generateConfigFile(serverName, serverVersion, debug), language);
|
|
1021
1043
|
console.log(`✅ Generated config: ${configPath}`);
|
|
1022
1044
|
const generatedTools = await this.generateTools();
|
|
1023
|
-
const toolsPath = resolve(toolsDir,
|
|
1024
|
-
await
|
|
1045
|
+
const toolsPath = resolve(toolsDir, `index${extension}`);
|
|
1046
|
+
await writeGeneratedFile(toolsPath, this.generateToolsFile(generatedTools), language);
|
|
1025
1047
|
console.log(`✅ Generated tools: ${toolsPath}`);
|
|
1026
|
-
const handlersPath = resolve(handlersDir,
|
|
1048
|
+
const handlersPath = resolve(handlersDir, `index${extension}`);
|
|
1027
1049
|
const tenantScopedObjects = await this.tenantScopedObjectNames(generatedTools);
|
|
1028
1050
|
const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(generatedTools);
|
|
1029
|
-
await
|
|
1051
|
+
await writeGeneratedFile(handlersPath, await this.generateHandlersFile(tenantScopedObjects), language);
|
|
1030
1052
|
console.log(`✅ Generated handlers: ${handlersPath}`);
|
|
1031
|
-
|
|
1032
|
-
await writeFile(indexPath, this.generateModularIndex(resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools)), "utf-8");
|
|
1053
|
+
await writeGeneratedFile(indexPath, this.generateModularIndex(resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools), extension), language);
|
|
1033
1054
|
console.log(`✅ Generated MCP server: ${indexPath}`);
|
|
1034
1055
|
}
|
|
1035
1056
|
/**
|
|
@@ -1352,11 +1373,11 @@ function errorResult(structuredContent: any, text: string, _meta?: Record<string
|
|
|
1352
1373
|
*/
|
|
1353
1374
|
export async function handleToolCall(
|
|
1354
1375
|
name: string,
|
|
1355
|
-
|
|
1376
|
+
toolArguments: any = {},
|
|
1356
1377
|
aiConfig: any = {}
|
|
1357
1378
|
) {
|
|
1358
1379
|
try {
|
|
1359
|
-
const args =
|
|
1380
|
+
const args = toolArguments;
|
|
1360
1381
|
|
|
1361
1382
|
const runToolBody = async () => {
|
|
1362
1383
|
switch (name) {
|
|
@@ -1392,8 +1413,11 @@ ${hasTenantScoped ? `
|
|
|
1392
1413
|
}
|
|
1393
1414
|
/**
|
|
1394
1415
|
* Generate modular index file (main entry point)
|
|
1416
|
+
*
|
|
1417
|
+
* @param toolListCacheHint - Cache hint emitted for `tools/list` results
|
|
1418
|
+
* @param extension - Extension of the sibling modules this entry imports
|
|
1395
1419
|
*/
|
|
1396
|
-
generateModularIndex(toolListCacheHint) {
|
|
1420
|
+
generateModularIndex(toolListCacheHint, extension = ".js") {
|
|
1397
1421
|
return `#!/usr/bin/env node
|
|
1398
1422
|
/**
|
|
1399
1423
|
* Auto-generated MCP Server
|
|
@@ -1409,12 +1433,10 @@ import { resolve } from 'node:path';
|
|
|
1409
1433
|
import { pathToFileURL } from 'node:url';
|
|
1410
1434
|
import { ObjectRegistry } from '@happyvertical/smrt-core';
|
|
1411
1435
|
import { loadConfig } from '@happyvertical/smrt-config';
|
|
1412
|
-
import { getDatabase } from '@happyvertical/sql';
|
|
1413
|
-
import { getAI } from '@happyvertical/ai';
|
|
1414
1436
|
|
|
1415
|
-
import { SERVER_NAME, SERVER_VERSION, DEBUG } from './config
|
|
1416
|
-
import { tools } from './tools/index
|
|
1417
|
-
import { handleToolCall } from './handlers/index
|
|
1437
|
+
import { SERVER_NAME, SERVER_VERSION, DEBUG } from './config${extension}';
|
|
1438
|
+
import { tools } from './tools/index${extension}';
|
|
1439
|
+
import { handleToolCall } from './handlers/index${extension}';
|
|
1418
1440
|
|
|
1419
1441
|
const TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};
|
|
1420
1442
|
|