@happyvertical/smrt-core 0.40.61 → 0.40.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENTS.md +16 -3
  2. package/README.md +32 -1
  3. package/agents/generators.md +22 -0
  4. package/dist/collection.d.ts +12 -1
  5. package/dist/collection.d.ts.map +1 -1
  6. package/dist/collection.js +37 -5
  7. package/dist/collection.js.map +1 -1
  8. package/dist/decorators/index.d.ts +8 -2
  9. package/dist/decorators/index.d.ts.map +1 -1
  10. package/dist/decorators/index.js.map +1 -1
  11. package/dist/embeddings/types.d.ts +4 -1
  12. package/dist/embeddings/types.d.ts.map +1 -1
  13. package/dist/generators/mcp-emit.d.ts +72 -0
  14. package/dist/generators/mcp-emit.d.ts.map +1 -0
  15. package/dist/generators/mcp-emit.js +104 -0
  16. package/dist/generators/mcp-emit.js.map +1 -0
  17. package/dist/generators/mcp-runtime-template.d.ts +5 -0
  18. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  19. package/dist/generators/mcp-runtime-template.js +236 -3
  20. package/dist/generators/mcp-runtime-template.js.map +1 -1
  21. package/dist/generators/mcp.d.ts +63 -3
  22. package/dist/generators/mcp.d.ts.map +1 -1
  23. package/dist/generators/mcp.js +145 -21
  24. package/dist/generators/mcp.js.map +1 -1
  25. package/dist/manifest/generator.d.ts +5 -0
  26. package/dist/manifest/generator.d.ts.map +1 -1
  27. package/dist/manifest/generator.js +10 -7
  28. package/dist/manifest/generator.js.map +1 -1
  29. package/dist/manifest/static-manifest.js +1 -1
  30. package/dist/manifest/static-manifest.js.map +1 -1
  31. package/dist/manifest/store.js +1 -1
  32. package/dist/manifest.json +1 -1
  33. package/dist/object.d.ts +11 -1
  34. package/dist/object.d.ts.map +1 -1
  35. package/dist/object.js +11 -1
  36. package/dist/object.js.map +1 -1
  37. package/dist/registry/types.d.ts +8 -0
  38. package/dist/registry/types.d.ts.map +1 -1
  39. package/dist/smrt-knowledge.json +7 -7
  40. package/dist/system/compatibility.d.ts.map +1 -1
  41. package/dist/system/compatibility.js +6 -0
  42. package/dist/system/compatibility.js.map +1 -1
  43. package/dist/system/types.d.ts +4 -1
  44. package/dist/system/types.d.ts.map +1 -1
  45. package/dist/utils/scanner-module.d.ts +7 -0
  46. package/dist/utils/scanner-module.d.ts.map +1 -1
  47. package/dist/vite-plugin/index.d.ts +7 -0
  48. package/dist/vite-plugin/index.d.ts.map +1 -1
  49. package/dist/vite-plugin/index.js +3 -2
  50. package/dist/vite-plugin/index.js.map +1 -1
  51. package/package.json +10 -12
@@ -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.
@@ -53,6 +67,29 @@ function resolveCustomActionMethod(methods, toolAction) {
53
67
  for (const [methodName, method] of methods) if (methodName.toLowerCase() === toolAction.toLowerCase()) return [methodName, method];
54
68
  return [toolAction, void 0];
55
69
  }
70
+ /** Preserve a declared method's case when a runtime-only class has no manifest. */
71
+ function resolveRuntimeMethodName(classConstructor, action) {
72
+ let prototype = classConstructor?.prototype;
73
+ while (prototype && prototype !== Object.prototype) {
74
+ const match = Object.getOwnPropertyNames(prototype).find((name) => name.toLowerCase() === action.toLowerCase());
75
+ if (match) return match;
76
+ prototype = Object.getPrototypeOf(prototype);
77
+ }
78
+ return action;
79
+ }
80
+ /**
81
+ * Background task methods receive `JobExecutionContext` from TaskRunner, not
82
+ * from untrusted MCP arguments. Keep that conventional trailing parameter out
83
+ * of the persisted positional call so the runner can append its live context.
84
+ */
85
+ function buildTaskActionInvocationArgs(metadata, args) {
86
+ const parameters = metadata.parameters;
87
+ if (parameters?.at(-1)?.name === "context") return buildCustomActionInvocationArgs({
88
+ ...metadata,
89
+ parameters: parameters.slice(0, -1)
90
+ }, args);
91
+ return buildCustomActionInvocationArgs(metadata, args);
92
+ }
56
93
  /**
57
94
  * Generate MCP server from smrt objects
58
95
  */
@@ -459,6 +496,63 @@ var MCPGenerator = class {
459
496
  };
460
497
  }
461
498
  }
499
+ /** Whether a visible tool has explicitly opted into durable task execution. */
500
+ async supportsTaskTool(name) {
501
+ return await this.resolveTaskAction(name, { id: "__mcp_task_probe__" }) !== null;
502
+ }
503
+ /**
504
+ * Create a durable MCP task for an explicitly enabled item custom action.
505
+ * The caller is responsible for checking the client's extension capability
506
+ * before exposing this result on the wire.
507
+ */
508
+ async createTask(request) {
509
+ if (!this.context.taskStore) throw new Error("MCP Tasks is enabled but no durable task store is configured");
510
+ const resolved = await this.resolveTaskAction(request.params.name, request.params.arguments);
511
+ if (!resolved) throw new Error(`MCP task execution is not enabled for tool: ${request.params.name}`);
512
+ return {
513
+ content: [],
514
+ structuredContent: {},
515
+ resultType: "task",
516
+ ...await this.context.taskStore.createTask({
517
+ objectType: resolved.objectType,
518
+ objectId: resolved.objectId,
519
+ method: resolved.methodName,
520
+ invocationArgs: resolved.invocationArgs,
521
+ tenantId: this.context.tenantId ?? null
522
+ })
523
+ };
524
+ }
525
+ async resolveTaskAction(toolName, args) {
526
+ const separator = toolName.indexOf("_");
527
+ if (separator <= 0) return null;
528
+ const objectPrefix = toolName.slice(0, separator);
529
+ const action = toolName.slice(separator + 1);
530
+ if ([
531
+ "list",
532
+ "get",
533
+ "create",
534
+ "update",
535
+ "delete"
536
+ ].includes(action)) return null;
537
+ const classEntry = Array.from(ObjectRegistry.getAllClasses().entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectPrefix.toLowerCase());
538
+ if (!classEntry) return null;
539
+ const [key, classInfo] = classEntry;
540
+ const objectName = classInfo.name || key;
541
+ const mcpConfig = ObjectRegistry.getConfig(objectName).mcp;
542
+ const configuredTasks = typeof mcpConfig === "object" ? mcpConfig.tasks : void 0;
543
+ if (configuredTasks !== true && (!Array.isArray(configuredTasks) || !configuredTasks.some((method) => method.toLowerCase() === action.toLowerCase()))) return null;
544
+ if (!(await this.generateTools()).some((tool) => tool.name === toolName)) return null;
545
+ const [resolvedMethodName, method] = resolveCustomActionMethod(await ObjectRegistry.getAllMethods(objectName), action);
546
+ const methodName = method ? resolvedMethodName : resolveRuntimeMethodName(classInfo.constructor, action);
547
+ const metadata = this.resolveCustomActionMetadata(objectName, methodName, method, this.hasCollectionReceiver(classInfo));
548
+ if (!metadata.idRequired || metadata.isStatic || typeof args.id !== "string") return null;
549
+ return {
550
+ objectType: classInfo.qualifiedName || objectName,
551
+ objectId: args.id,
552
+ methodName,
553
+ invocationArgs: buildTaskActionInvocationArgs(metadata, args)
554
+ };
555
+ }
462
556
  /** Convert runtime values to the JSON values MCP structuredContent permits. */
463
557
  toJsonValue(value) {
464
558
  const serialized = JSON.stringify(value);
@@ -805,13 +899,14 @@ var MCPGenerator = class {
805
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;
806
900
  const resolvedPath = resolve(process.cwd(), outputPath);
807
901
  const outputDir = dirname(resolvedPath);
902
+ const language = resolveGeneratedSourceLanguage(resolvedPath);
808
903
  await mkdir(outputDir, { recursive: true });
809
- if (modular) await this.generateModularServer(outputDir, serverName, serverVersion, debug);
904
+ if (modular) await this.generateModularServer(resolvedPath, serverName, serverVersion, debug, language);
810
905
  else {
811
906
  const tools = await this.generateTools();
812
907
  const tenantScopedObjects = await this.tenantScopedObjectNames(tools);
813
908
  const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(tools);
814
- await writeFile(resolvedPath, generateRuntimeBootstrap({
909
+ await writeGeneratedFile(resolvedPath, generateRuntimeBootstrap({
815
910
  name: serverName,
816
911
  version: serverVersion,
817
912
  description: this.config.description,
@@ -820,10 +915,11 @@ var MCPGenerator = class {
820
915
  debug,
821
916
  tools,
822
917
  customActions: await this.runtimeCustomActions(tools),
918
+ taskActions: await this.runtimeTaskActions(tools),
823
919
  tenantScopedObjects,
824
920
  stiTargets: this.runtimeStiTargets(tools),
825
921
  toolListCacheHint: resolveMCPToolListCacheHint(this.config.cache?.toolsList, hasTenantScopedTools)
826
- }), "utf-8");
922
+ }), language);
827
923
  console.log(`✅ Generated MCP server: ${resolvedPath}`);
828
924
  }
829
925
  if (generateClaudeConfigFile) {
@@ -877,6 +973,26 @@ var MCPGenerator = class {
877
973
  }
878
974
  return metadata;
879
975
  }
976
+ /** Emit only task-enabled item custom actions for the generated runtime. */
977
+ async runtimeTaskActions(tools) {
978
+ const actions = {};
979
+ const classes = ObjectRegistry.getAllClasses();
980
+ for (const tool of tools) {
981
+ if (!await this.supportsTaskTool(tool.name)) continue;
982
+ const separator = tool.name.indexOf("_");
983
+ if (separator <= 0) continue;
984
+ const objectPrefix = tool.name.slice(0, separator).toLowerCase();
985
+ const matched = Array.from(classes.entries()).find(([key, info]) => (info.name || key).toLowerCase() === objectPrefix);
986
+ if (!matched) continue;
987
+ const [key, classInfo] = matched;
988
+ const objectName = classInfo.name || key;
989
+ actions[tool.name] = {
990
+ objectName,
991
+ objectType: classInfo.qualifiedName || objectName
992
+ };
993
+ }
994
+ return actions;
995
+ }
880
996
  /**
881
997
  * Emit only the STI discriminator targets advertised by create-tool schemas.
882
998
  * Generated processes start with an empty registry, so resolving the
@@ -905,30 +1021,36 @@ var MCPGenerator = class {
905
1021
  * Creates separate files for tools, handlers, configuration, and main entry point.
906
1022
  * This makes the generated server easier to customize and extend.
907
1023
  *
908
- * @param outputDir - Directory to generate files in
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
909
1029
  * @param serverName - Server name
910
1030
  * @param serverVersion - Server version
911
1031
  * @param debug - Enable debug logging
1032
+ * @param language - Language the generated files are written in
912
1033
  */
913
- async generateModularServer(outputDir, serverName, serverVersion, debug) {
1034
+ async generateModularServer(indexPath, serverName, serverVersion, debug, language) {
1035
+ const outputDir = dirname(indexPath);
1036
+ const extension = generatedSiblingExtension(indexPath);
914
1037
  const toolsDir = resolve(outputDir, "tools");
915
1038
  const handlersDir = resolve(outputDir, "handlers");
916
1039
  await mkdir(toolsDir, { recursive: true });
917
1040
  await mkdir(handlersDir, { recursive: true });
918
- const configPath = resolve(outputDir, "config.ts");
919
- await writeFile(configPath, this.generateConfigFile(serverName, serverVersion, debug), "utf-8");
1041
+ const configPath = resolve(outputDir, `config${extension}`);
1042
+ await writeGeneratedFile(configPath, this.generateConfigFile(serverName, serverVersion, debug), language);
920
1043
  console.log(`✅ Generated config: ${configPath}`);
921
1044
  const generatedTools = await this.generateTools();
922
- const toolsPath = resolve(toolsDir, "index.ts");
923
- await writeFile(toolsPath, this.generateToolsFile(generatedTools), "utf-8");
1045
+ const toolsPath = resolve(toolsDir, `index${extension}`);
1046
+ await writeGeneratedFile(toolsPath, this.generateToolsFile(generatedTools), language);
924
1047
  console.log(`✅ Generated tools: ${toolsPath}`);
925
- const handlersPath = resolve(handlersDir, "index.ts");
1048
+ const handlersPath = resolve(handlersDir, `index${extension}`);
926
1049
  const tenantScopedObjects = await this.tenantScopedObjectNames(generatedTools);
927
1050
  const hasTenantScopedTools = tenantScopedObjects.length > 0 || await this.hasTenantScopedTools(generatedTools);
928
- await writeFile(handlersPath, await this.generateHandlersFile(tenantScopedObjects), "utf-8");
1051
+ await writeGeneratedFile(handlersPath, await this.generateHandlersFile(tenantScopedObjects), language);
929
1052
  console.log(`✅ Generated handlers: ${handlersPath}`);
930
- const indexPath = resolve(outputDir, "index.js");
931
- 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);
932
1054
  console.log(`✅ Generated MCP server: ${indexPath}`);
933
1055
  }
934
1056
  /**
@@ -1147,6 +1269,7 @@ const TENANT_SCOPED = new Set(${JSON.stringify(tenantScopedSet)});
1147
1269
  const MCP_TENANT_ID = process.env.SMRT_MCP_TENANT_ID || undefined;
1148
1270
  const MCP_ALLOW_CROSS_TENANT = process.env.SMRT_MCP_ALLOW_CROSS_TENANT === 'true';
1149
1271
  ` : ""}
1272
+
1150
1273
  const PUBLIC_JSON_OPTIONS = {
1151
1274
  permissions: (process.env.SMRT_MCP_PERMISSIONS || '')
1152
1275
  .split(',')
@@ -1250,11 +1373,11 @@ function errorResult(structuredContent: any, text: string, _meta?: Record<string
1250
1373
  */
1251
1374
  export async function handleToolCall(
1252
1375
  name: string,
1253
- arguments: any = {},
1376
+ toolArguments: any = {},
1254
1377
  aiConfig: any = {}
1255
1378
  ) {
1256
1379
  try {
1257
- const args = arguments;
1380
+ const args = toolArguments;
1258
1381
 
1259
1382
  const runToolBody = async () => {
1260
1383
  switch (name) {
@@ -1290,8 +1413,11 @@ ${hasTenantScoped ? `
1290
1413
  }
1291
1414
  /**
1292
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
1293
1419
  */
1294
- generateModularIndex(toolListCacheHint) {
1420
+ generateModularIndex(toolListCacheHint, extension = ".js") {
1295
1421
  return `#!/usr/bin/env node
1296
1422
  /**
1297
1423
  * Auto-generated MCP Server
@@ -1307,12 +1433,10 @@ import { resolve } from 'node:path';
1307
1433
  import { pathToFileURL } from 'node:url';
1308
1434
  import { ObjectRegistry } from '@happyvertical/smrt-core';
1309
1435
  import { loadConfig } from '@happyvertical/smrt-config';
1310
- import { getDatabase } from '@happyvertical/sql';
1311
- import { getAI } from '@happyvertical/ai';
1312
1436
 
1313
- import { SERVER_NAME, SERVER_VERSION, DEBUG } from './config.js';
1314
- import { tools } from './tools/index.js';
1315
- import { handleToolCall } from './handlers/index.js';
1437
+ import { SERVER_NAME, SERVER_VERSION, DEBUG } from './config${extension}';
1438
+ import { tools } from './tools/index${extension}';
1439
+ import { handleToolCall } from './handlers/index${extension}';
1316
1440
 
1317
1441
  const TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};
1318
1442