webmcp-nexus-core 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  extractProperties: () => extractProperties,
34
34
  extractToolsFromFile: () => extractToolsFromFile,
35
+ generateFieldSchemasInjectionCode: () => generateFieldSchemasInjectionCode,
35
36
  generateSchema: () => generateSchema,
36
37
  generateSchemaInjectionCode: () => generateSchemaInjectionCode,
37
38
  mapType: () => mapType,
@@ -44,7 +45,7 @@ module.exports = __toCommonJS(index_exports);
44
45
  // src/ts-extractor.ts
45
46
  var import_node_path = __toESM(require("path"), 1);
46
47
  var import_ts_morph = require("ts-morph");
47
- var REGISTRATION_FUNCTIONS = ["registerGlobalTools", "useWebMcpTools"];
48
+ var REGISTRATION_FUNCTIONS = ["registerGlobalTools", "useWebMcpTools", "withWebMcpTools"];
48
49
  var isDebug = () => (process.env.DEBUG ?? "").toLowerCase().includes("webmcp");
49
50
  function resolveWithAlias(spec, alias) {
50
51
  if (!alias) return null;
@@ -158,6 +159,21 @@ function extractFunctionMetadata(node, _sourceFile) {
158
159
  }
159
160
  return { description, readOnly, properties };
160
161
  }
162
+ if (node.getKind() === import_ts_morph.SyntaxKind.MethodDeclaration) {
163
+ const methodDecl = node;
164
+ const jsDocs = methodDecl.getJsDocs();
165
+ if (jsDocs.length > 0) {
166
+ description = jsDocs[0].getDescription()?.trim() ?? "";
167
+ const tags = jsDocs[0].getTags();
168
+ readOnly = tags.some((tag) => tag.getTagName() === "readonly");
169
+ }
170
+ const params = methodDecl.getParameters();
171
+ if (params.length > 0) {
172
+ const paramType = params[0].getType();
173
+ properties = extractProperties(paramType);
174
+ }
175
+ return { description, readOnly, properties };
176
+ }
161
177
  if (node.getKind() === import_ts_morph.SyntaxKind.ArrowFunction || node.getKind() === import_ts_morph.SyntaxKind.FunctionExpression) {
162
178
  const funcNode = node;
163
179
  const parent = funcNode.getParent();
@@ -424,6 +440,119 @@ function resolveNamespaceImportArg(arg, sourceFile, project, projectRoot, alias)
424
440
  }
425
441
  return tools;
426
442
  }
443
+ var REACT_LIFECYCLE_METHODS = /* @__PURE__ */ new Set([
444
+ "constructor",
445
+ "render",
446
+ "componentDidMount",
447
+ "componentDidUpdate",
448
+ "componentWillUnmount",
449
+ "shouldComponentUpdate",
450
+ "getSnapshotBeforeUpdate",
451
+ "componentDidCatch",
452
+ "getDerivedStateFromProps",
453
+ "getDerivedStateFromError",
454
+ "UNSAFE_componentWillMount",
455
+ "UNSAFE_componentWillReceiveProps",
456
+ "UNSAFE_componentWillUpdate"
457
+ ]);
458
+ function resolveClassComponentArg(args, sourceFile, _project, projectRoot) {
459
+ const tools = [];
460
+ if (args.length === 0) return tools;
461
+ const firstArg = args[0];
462
+ if (firstArg.getKind() !== import_ts_morph.SyntaxKind.Identifier) return tools;
463
+ const symbol = firstArg.getSymbol();
464
+ if (!symbol) return tools;
465
+ const declarations = symbol.getDeclarations();
466
+ if (declarations.length === 0) return tools;
467
+ let classDecl;
468
+ for (const decl of declarations) {
469
+ if (decl.getKind() === import_ts_morph.SyntaxKind.ClassDeclaration) {
470
+ classDecl = decl;
471
+ break;
472
+ }
473
+ if (decl.getKind() === import_ts_morph.SyntaxKind.VariableDeclaration) {
474
+ const init = decl.getInitializer?.();
475
+ if (init && init.getKind() === import_ts_morph.SyntaxKind.ClassExpression) {
476
+ classDecl = init;
477
+ break;
478
+ }
479
+ }
480
+ }
481
+ if (!classDecl) return tools;
482
+ const className = firstArg.getText();
483
+ if (!className) {
484
+ console.warn(`[webmcp] withWebMcpTools: anonymous class is not supported, skipping.`);
485
+ return tools;
486
+ }
487
+ let explicitMethods = null;
488
+ if (args.length > 1 && args[1].getKind() === import_ts_morph.SyntaxKind.ArrayLiteralExpression) {
489
+ explicitMethods = /* @__PURE__ */ new Set();
490
+ const arrayLiteral = args[1];
491
+ for (const element of arrayLiteral.getElements()) {
492
+ if (element.getKind() === import_ts_morph.SyntaxKind.StringLiteral) {
493
+ explicitMethods.add(element.getLiteralValue());
494
+ }
495
+ }
496
+ }
497
+ const filePath = sourceFile.getFilePath();
498
+ const baseDir = projectRoot ?? process.cwd();
499
+ const relPath = "./" + import_node_path.default.relative(baseDir, filePath).replace(/\.tsx?$/, "");
500
+ const members = classDecl.getMembers?.() ?? [];
501
+ for (const member of members) {
502
+ const memberKind = member.getKind();
503
+ if (memberKind === import_ts_morph.SyntaxKind.MethodDeclaration) {
504
+ const name = member.getName?.();
505
+ if (!name || REACT_LIFECYCLE_METHODS.has(name)) continue;
506
+ if (explicitMethods && !explicitMethods.has(name)) continue;
507
+ const metadata = extractFunctionMetadata(member, sourceFile);
508
+ if (!explicitMethods && !metadata.description) continue;
509
+ tools.push({
510
+ name,
511
+ description: metadata.description,
512
+ properties: metadata.properties,
513
+ readOnly: metadata.readOnly,
514
+ sourceFile: relPath,
515
+ injectionTarget: `${className}.prototype.${name}`,
516
+ memberType: "prototype"
517
+ });
518
+ }
519
+ if (memberKind === import_ts_morph.SyntaxKind.PropertyDeclaration) {
520
+ const name = member.getName?.();
521
+ if (!name || REACT_LIFECYCLE_METHODS.has(name)) continue;
522
+ if (explicitMethods && !explicitMethods.has(name)) continue;
523
+ const initializer = member.getInitializer?.();
524
+ if (!initializer) continue;
525
+ const initKind = initializer.getKind();
526
+ if (initKind !== import_ts_morph.SyntaxKind.ArrowFunction && initKind !== import_ts_morph.SyntaxKind.FunctionExpression) continue;
527
+ let description = "";
528
+ let readOnly = false;
529
+ const jsDocs = member.getJsDocs?.();
530
+ if (jsDocs && jsDocs.length > 0) {
531
+ description = jsDocs[0].getDescription?.()?.trim() ?? "";
532
+ const tags = jsDocs[0].getTags?.() ?? [];
533
+ readOnly = tags.some((tag) => tag.getTagName() === "readonly");
534
+ }
535
+ if (!explicitMethods && !description) continue;
536
+ let properties = [];
537
+ const funcNode = initializer;
538
+ const params = funcNode.getParameters?.();
539
+ if (params && params.length > 0) {
540
+ const paramType = params[0].getType();
541
+ properties = extractProperties(paramType);
542
+ }
543
+ tools.push({
544
+ name,
545
+ description,
546
+ properties,
547
+ readOnly,
548
+ sourceFile: relPath,
549
+ injectionTarget: `${className}.__webmcpFieldSchemas`,
550
+ memberType: "field"
551
+ });
552
+ }
553
+ }
554
+ return tools;
555
+ }
427
556
  function extractToolsFromFile(fileContent, filePath, projectRoot, alias) {
428
557
  const hasRegistration = REGISTRATION_FUNCTIONS.some((fn) => fileContent.includes(fn));
429
558
  if (!hasRegistration) return null;
@@ -467,6 +596,11 @@ function extractToolsFromFile(fileContent, filePath, projectRoot, alias) {
467
596
  start: callExpr.getStart()
468
597
  });
469
598
  const args = callExpr.getArguments();
599
+ if (exprText === "withWebMcpTools") {
600
+ const tools = resolveClassComponentArg(args, sourceFile, project, projectRoot);
601
+ result.tools.push(...tools);
602
+ return;
603
+ }
470
604
  for (const arg of args) {
471
605
  if (arg.getKind() === import_ts_morph.SyntaxKind.ObjectLiteralExpression) {
472
606
  const tools = resolveObjectLiteralArg(arg, sourceFile, project, projectRoot);
@@ -528,6 +662,19 @@ function generateSchemaInjectionCode(injectionTarget, description, properties, r
528
662
  };
529
663
  return `${injectionTarget}.__webmcpSchema = ${JSON.stringify(schemaObj, null, 2)};`;
530
664
  }
665
+ function generateFieldSchemasInjectionCode(className, fieldTools) {
666
+ const schemas = {};
667
+ for (const tool of fieldTools) {
668
+ const inputSchema = generateSchema(tool.properties);
669
+ delete inputSchema.description;
670
+ schemas[tool.name] = {
671
+ description: tool.description,
672
+ inputSchema,
673
+ readOnly: tool.readOnly
674
+ };
675
+ }
676
+ return `${className}.__webmcpFieldSchemas = ${JSON.stringify(schemas, null, 2)};`;
677
+ }
531
678
  function mapTypeToSchema(prop) {
532
679
  const schema = { type: "string" };
533
680
  if (prop.description) {
@@ -575,7 +722,7 @@ function mapTypeToSchema(prop) {
575
722
  // src/transform.ts
576
723
  var isDebug2 = () => (process.env.DEBUG ?? "").toLowerCase().includes("webmcp");
577
724
  function transformCode(code, filePath, options = {}) {
578
- if (!code.includes("registerGlobalTools") && !code.includes("useWebMcpTools")) {
725
+ if (!code.includes("registerGlobalTools") && !code.includes("useWebMcpTools") && !code.includes("withWebMcpTools")) {
579
726
  return { code, transformed: false };
580
727
  }
581
728
  if (isDebug2()) {
@@ -589,6 +736,7 @@ function transformCode(code, filePath, options = {}) {
589
736
  }
590
737
  const injections = [];
591
738
  for (const tool of result.tools) {
739
+ if (tool.memberType === "field") continue;
592
740
  const injectionCode = generateSchemaInjectionCode(
593
741
  tool.injectionTarget,
594
742
  tool.description,
@@ -597,18 +745,42 @@ function transformCode(code, filePath, options = {}) {
597
745
  );
598
746
  injections.push(injectionCode);
599
747
  }
748
+ const fieldToolsByClass = /* @__PURE__ */ new Map();
749
+ for (const tool of result.tools) {
750
+ if (tool.memberType !== "field") continue;
751
+ const className = tool.injectionTarget.replace(".__webmcpFieldSchemas", "");
752
+ if (!fieldToolsByClass.has(className)) {
753
+ fieldToolsByClass.set(className, []);
754
+ }
755
+ fieldToolsByClass.get(className).push({
756
+ name: tool.name,
757
+ description: tool.description,
758
+ properties: tool.properties,
759
+ readOnly: tool.readOnly
760
+ });
761
+ }
762
+ for (const [className, fieldTools] of fieldToolsByClass) {
763
+ injections.push(generateFieldSchemasInjectionCode(className, fieldTools));
764
+ }
600
765
  if (injections.length === 0) {
601
766
  return { code, transformed: false };
602
767
  }
603
768
  const firstCall = result.registrationCalls.sort((a, b) => a.start - b.start)[0];
769
+ let insertPos = firstCall.start;
770
+ const textBefore = code.slice(0, firstCall.start);
771
+ const exportDefaultMatch = textBefore.match(/(export\s+default\s+)$/);
772
+ if (exportDefaultMatch) {
773
+ insertPos = firstCall.start - exportDefaultMatch[1].length;
774
+ }
604
775
  const injectionBlock = "\n// [webmcp-nexus-core] \u6784\u5EFA\u65F6\u6CE8\u5165\u7684 schema \u5143\u6570\u636E\n" + injections.join("\n") + "\n";
605
- const newCode = code.slice(0, firstCall.start) + injectionBlock + code.slice(firstCall.start);
776
+ const newCode = code.slice(0, insertPos) + injectionBlock + code.slice(insertPos);
606
777
  return { code: newCode, transformed: true };
607
778
  }
608
779
  // Annotate the CommonJS export names for ESM import in node:
609
780
  0 && (module.exports = {
610
781
  extractProperties,
611
782
  extractToolsFromFile,
783
+ generateFieldSchemasInjectionCode,
612
784
  generateSchema,
613
785
  generateSchemaInjectionCode,
614
786
  mapType,
package/dist/index.d.cts CHANGED
@@ -40,6 +40,24 @@ declare function generateSchema(properties: PropertyInfo[], description?: string
40
40
  * @returns 注入代码字符串
41
41
  */
42
42
  declare function generateSchemaInjectionCode(injectionTarget: string, description: string, properties: PropertyInfo[], readOnly: boolean): string;
43
+ /**
44
+ * 为 class field 箭头函数生成聚合的静态属性注入代码。
45
+ *
46
+ * 输出形如:
47
+ * ```
48
+ * ClassName.__webmcpFieldSchemas = { "method1": {...}, "method2": {...} };
49
+ * ```
50
+ *
51
+ * @param className - class 名称
52
+ * @param fieldTools - class field 工具元数据列表
53
+ * @returns 注入代码字符串
54
+ */
55
+ declare function generateFieldSchemasInjectionCode(className: string, fieldTools: Array<{
56
+ name: string;
57
+ description: string;
58
+ properties: PropertyInfo[];
59
+ readOnly: boolean;
60
+ }>): string;
43
61
  declare function mapTypeToSchema(prop: PropertyInfo): JsonSchema;
44
62
 
45
63
  /**
@@ -69,8 +87,15 @@ interface ExtractedTool {
69
87
  * 注入目标表达式。
70
88
  * 对于对象字面量参数中的本地变量:变量名,如 "searchInPanel"
71
89
  * 对于 namespace import:namespace.exportName,如 "userApi.getUser"
90
+ * 对于 class 原型方法:"ClassName.prototype.methodName"
72
91
  */
73
92
  injectionTarget: string;
93
+ /**
94
+ * class 成员类型。仅在 withWebMcpTools 场景下有值。
95
+ * - 'prototype':原型方法(MethodDeclaration)
96
+ * - 'field':class field 箭头函数(PropertyDeclaration)
97
+ */
98
+ memberType?: 'prototype' | 'field';
74
99
  }
75
100
  /** 注册调用的提取结果 */
76
101
  interface ExtractionResult {
@@ -79,7 +104,7 @@ interface ExtractionResult {
79
104
  /** 注册调用的位置信息(用于在调用前注入代码) */
80
105
  registrationCalls: {
81
106
  /** 调用类型 */
82
- type: 'registerGlobalTools' | 'useWebMcpTools';
107
+ type: 'registerGlobalTools' | 'useWebMcpTools' | 'withWebMcpTools';
83
108
  /** 调用在源码中的起始位置(字符偏移量) */
84
109
  start: number;
85
110
  }[];
@@ -104,7 +129,7 @@ declare function mapType(type: Type): string;
104
129
  /** 从一个 Type 提取所有属性信息 */
105
130
  declare function extractProperties(type: Type, depth?: number): PropertyInfo[];
106
131
  /**
107
- * 从源文件中提取所有 registerGlobalTools / useWebMcpTools 调用引用的工具
132
+ * 从源文件中提取所有 registerGlobalTools / useWebMcpTools / withWebMcpTools 调用引用的工具
108
133
  *
109
134
  * @param fileContent - 源文件内容
110
135
  * @param filePath - 源文件绝对路径
@@ -126,4 +151,4 @@ interface TransformResult {
126
151
  }
127
152
  declare function transformCode(code: string, filePath: string, options?: TransformOptions): TransformResult;
128
153
 
129
- export { type AliasMap, type ExtractedTool, type ExtractionResult, type JsonSchema, type PropertyInfo, type TransformOptions, type TransformResult, extractProperties, extractToolsFromFile, generateSchema, generateSchemaInjectionCode, mapType, mapTypeToSchema, resolveWithAlias, transformCode };
154
+ export { type AliasMap, type ExtractedTool, type ExtractionResult, type JsonSchema, type PropertyInfo, type TransformOptions, type TransformResult, extractProperties, extractToolsFromFile, generateFieldSchemasInjectionCode, generateSchema, generateSchemaInjectionCode, mapType, mapTypeToSchema, resolveWithAlias, transformCode };
package/dist/index.d.ts CHANGED
@@ -40,6 +40,24 @@ declare function generateSchema(properties: PropertyInfo[], description?: string
40
40
  * @returns 注入代码字符串
41
41
  */
42
42
  declare function generateSchemaInjectionCode(injectionTarget: string, description: string, properties: PropertyInfo[], readOnly: boolean): string;
43
+ /**
44
+ * 为 class field 箭头函数生成聚合的静态属性注入代码。
45
+ *
46
+ * 输出形如:
47
+ * ```
48
+ * ClassName.__webmcpFieldSchemas = { "method1": {...}, "method2": {...} };
49
+ * ```
50
+ *
51
+ * @param className - class 名称
52
+ * @param fieldTools - class field 工具元数据列表
53
+ * @returns 注入代码字符串
54
+ */
55
+ declare function generateFieldSchemasInjectionCode(className: string, fieldTools: Array<{
56
+ name: string;
57
+ description: string;
58
+ properties: PropertyInfo[];
59
+ readOnly: boolean;
60
+ }>): string;
43
61
  declare function mapTypeToSchema(prop: PropertyInfo): JsonSchema;
44
62
 
45
63
  /**
@@ -69,8 +87,15 @@ interface ExtractedTool {
69
87
  * 注入目标表达式。
70
88
  * 对于对象字面量参数中的本地变量:变量名,如 "searchInPanel"
71
89
  * 对于 namespace import:namespace.exportName,如 "userApi.getUser"
90
+ * 对于 class 原型方法:"ClassName.prototype.methodName"
72
91
  */
73
92
  injectionTarget: string;
93
+ /**
94
+ * class 成员类型。仅在 withWebMcpTools 场景下有值。
95
+ * - 'prototype':原型方法(MethodDeclaration)
96
+ * - 'field':class field 箭头函数(PropertyDeclaration)
97
+ */
98
+ memberType?: 'prototype' | 'field';
74
99
  }
75
100
  /** 注册调用的提取结果 */
76
101
  interface ExtractionResult {
@@ -79,7 +104,7 @@ interface ExtractionResult {
79
104
  /** 注册调用的位置信息(用于在调用前注入代码) */
80
105
  registrationCalls: {
81
106
  /** 调用类型 */
82
- type: 'registerGlobalTools' | 'useWebMcpTools';
107
+ type: 'registerGlobalTools' | 'useWebMcpTools' | 'withWebMcpTools';
83
108
  /** 调用在源码中的起始位置(字符偏移量) */
84
109
  start: number;
85
110
  }[];
@@ -104,7 +129,7 @@ declare function mapType(type: Type): string;
104
129
  /** 从一个 Type 提取所有属性信息 */
105
130
  declare function extractProperties(type: Type, depth?: number): PropertyInfo[];
106
131
  /**
107
- * 从源文件中提取所有 registerGlobalTools / useWebMcpTools 调用引用的工具
132
+ * 从源文件中提取所有 registerGlobalTools / useWebMcpTools / withWebMcpTools 调用引用的工具
108
133
  *
109
134
  * @param fileContent - 源文件内容
110
135
  * @param filePath - 源文件绝对路径
@@ -126,4 +151,4 @@ interface TransformResult {
126
151
  }
127
152
  declare function transformCode(code: string, filePath: string, options?: TransformOptions): TransformResult;
128
153
 
129
- export { type AliasMap, type ExtractedTool, type ExtractionResult, type JsonSchema, type PropertyInfo, type TransformOptions, type TransformResult, extractProperties, extractToolsFromFile, generateSchema, generateSchemaInjectionCode, mapType, mapTypeToSchema, resolveWithAlias, transformCode };
154
+ export { type AliasMap, type ExtractedTool, type ExtractionResult, type JsonSchema, type PropertyInfo, type TransformOptions, type TransformResult, extractProperties, extractToolsFromFile, generateFieldSchemasInjectionCode, generateSchema, generateSchemaInjectionCode, mapType, mapTypeToSchema, resolveWithAlias, transformCode };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  Project,
5
5
  SyntaxKind
6
6
  } from "ts-morph";
7
- var REGISTRATION_FUNCTIONS = ["registerGlobalTools", "useWebMcpTools"];
7
+ var REGISTRATION_FUNCTIONS = ["registerGlobalTools", "useWebMcpTools", "withWebMcpTools"];
8
8
  var isDebug = () => (process.env.DEBUG ?? "").toLowerCase().includes("webmcp");
9
9
  function resolveWithAlias(spec, alias) {
10
10
  if (!alias) return null;
@@ -118,6 +118,21 @@ function extractFunctionMetadata(node, _sourceFile) {
118
118
  }
119
119
  return { description, readOnly, properties };
120
120
  }
121
+ if (node.getKind() === SyntaxKind.MethodDeclaration) {
122
+ const methodDecl = node;
123
+ const jsDocs = methodDecl.getJsDocs();
124
+ if (jsDocs.length > 0) {
125
+ description = jsDocs[0].getDescription()?.trim() ?? "";
126
+ const tags = jsDocs[0].getTags();
127
+ readOnly = tags.some((tag) => tag.getTagName() === "readonly");
128
+ }
129
+ const params = methodDecl.getParameters();
130
+ if (params.length > 0) {
131
+ const paramType = params[0].getType();
132
+ properties = extractProperties(paramType);
133
+ }
134
+ return { description, readOnly, properties };
135
+ }
121
136
  if (node.getKind() === SyntaxKind.ArrowFunction || node.getKind() === SyntaxKind.FunctionExpression) {
122
137
  const funcNode = node;
123
138
  const parent = funcNode.getParent();
@@ -384,6 +399,119 @@ function resolveNamespaceImportArg(arg, sourceFile, project, projectRoot, alias)
384
399
  }
385
400
  return tools;
386
401
  }
402
+ var REACT_LIFECYCLE_METHODS = /* @__PURE__ */ new Set([
403
+ "constructor",
404
+ "render",
405
+ "componentDidMount",
406
+ "componentDidUpdate",
407
+ "componentWillUnmount",
408
+ "shouldComponentUpdate",
409
+ "getSnapshotBeforeUpdate",
410
+ "componentDidCatch",
411
+ "getDerivedStateFromProps",
412
+ "getDerivedStateFromError",
413
+ "UNSAFE_componentWillMount",
414
+ "UNSAFE_componentWillReceiveProps",
415
+ "UNSAFE_componentWillUpdate"
416
+ ]);
417
+ function resolveClassComponentArg(args, sourceFile, _project, projectRoot) {
418
+ const tools = [];
419
+ if (args.length === 0) return tools;
420
+ const firstArg = args[0];
421
+ if (firstArg.getKind() !== SyntaxKind.Identifier) return tools;
422
+ const symbol = firstArg.getSymbol();
423
+ if (!symbol) return tools;
424
+ const declarations = symbol.getDeclarations();
425
+ if (declarations.length === 0) return tools;
426
+ let classDecl;
427
+ for (const decl of declarations) {
428
+ if (decl.getKind() === SyntaxKind.ClassDeclaration) {
429
+ classDecl = decl;
430
+ break;
431
+ }
432
+ if (decl.getKind() === SyntaxKind.VariableDeclaration) {
433
+ const init = decl.getInitializer?.();
434
+ if (init && init.getKind() === SyntaxKind.ClassExpression) {
435
+ classDecl = init;
436
+ break;
437
+ }
438
+ }
439
+ }
440
+ if (!classDecl) return tools;
441
+ const className = firstArg.getText();
442
+ if (!className) {
443
+ console.warn(`[webmcp] withWebMcpTools: anonymous class is not supported, skipping.`);
444
+ return tools;
445
+ }
446
+ let explicitMethods = null;
447
+ if (args.length > 1 && args[1].getKind() === SyntaxKind.ArrayLiteralExpression) {
448
+ explicitMethods = /* @__PURE__ */ new Set();
449
+ const arrayLiteral = args[1];
450
+ for (const element of arrayLiteral.getElements()) {
451
+ if (element.getKind() === SyntaxKind.StringLiteral) {
452
+ explicitMethods.add(element.getLiteralValue());
453
+ }
454
+ }
455
+ }
456
+ const filePath = sourceFile.getFilePath();
457
+ const baseDir = projectRoot ?? process.cwd();
458
+ const relPath = "./" + nodePath.relative(baseDir, filePath).replace(/\.tsx?$/, "");
459
+ const members = classDecl.getMembers?.() ?? [];
460
+ for (const member of members) {
461
+ const memberKind = member.getKind();
462
+ if (memberKind === SyntaxKind.MethodDeclaration) {
463
+ const name = member.getName?.();
464
+ if (!name || REACT_LIFECYCLE_METHODS.has(name)) continue;
465
+ if (explicitMethods && !explicitMethods.has(name)) continue;
466
+ const metadata = extractFunctionMetadata(member, sourceFile);
467
+ if (!explicitMethods && !metadata.description) continue;
468
+ tools.push({
469
+ name,
470
+ description: metadata.description,
471
+ properties: metadata.properties,
472
+ readOnly: metadata.readOnly,
473
+ sourceFile: relPath,
474
+ injectionTarget: `${className}.prototype.${name}`,
475
+ memberType: "prototype"
476
+ });
477
+ }
478
+ if (memberKind === SyntaxKind.PropertyDeclaration) {
479
+ const name = member.getName?.();
480
+ if (!name || REACT_LIFECYCLE_METHODS.has(name)) continue;
481
+ if (explicitMethods && !explicitMethods.has(name)) continue;
482
+ const initializer = member.getInitializer?.();
483
+ if (!initializer) continue;
484
+ const initKind = initializer.getKind();
485
+ if (initKind !== SyntaxKind.ArrowFunction && initKind !== SyntaxKind.FunctionExpression) continue;
486
+ let description = "";
487
+ let readOnly = false;
488
+ const jsDocs = member.getJsDocs?.();
489
+ if (jsDocs && jsDocs.length > 0) {
490
+ description = jsDocs[0].getDescription?.()?.trim() ?? "";
491
+ const tags = jsDocs[0].getTags?.() ?? [];
492
+ readOnly = tags.some((tag) => tag.getTagName() === "readonly");
493
+ }
494
+ if (!explicitMethods && !description) continue;
495
+ let properties = [];
496
+ const funcNode = initializer;
497
+ const params = funcNode.getParameters?.();
498
+ if (params && params.length > 0) {
499
+ const paramType = params[0].getType();
500
+ properties = extractProperties(paramType);
501
+ }
502
+ tools.push({
503
+ name,
504
+ description,
505
+ properties,
506
+ readOnly,
507
+ sourceFile: relPath,
508
+ injectionTarget: `${className}.__webmcpFieldSchemas`,
509
+ memberType: "field"
510
+ });
511
+ }
512
+ }
513
+ return tools;
514
+ }
387
515
  function extractToolsFromFile(fileContent, filePath, projectRoot, alias) {
388
516
  const hasRegistration = REGISTRATION_FUNCTIONS.some((fn) => fileContent.includes(fn));
389
517
  if (!hasRegistration) return null;
@@ -427,6 +555,11 @@ function extractToolsFromFile(fileContent, filePath, projectRoot, alias) {
427
555
  start: callExpr.getStart()
428
556
  });
429
557
  const args = callExpr.getArguments();
558
+ if (exprText === "withWebMcpTools") {
559
+ const tools = resolveClassComponentArg(args, sourceFile, project, projectRoot);
560
+ result.tools.push(...tools);
561
+ return;
562
+ }
430
563
  for (const arg of args) {
431
564
  if (arg.getKind() === SyntaxKind.ObjectLiteralExpression) {
432
565
  const tools = resolveObjectLiteralArg(arg, sourceFile, project, projectRoot);
@@ -488,6 +621,19 @@ function generateSchemaInjectionCode(injectionTarget, description, properties, r
488
621
  };
489
622
  return `${injectionTarget}.__webmcpSchema = ${JSON.stringify(schemaObj, null, 2)};`;
490
623
  }
624
+ function generateFieldSchemasInjectionCode(className, fieldTools) {
625
+ const schemas = {};
626
+ for (const tool of fieldTools) {
627
+ const inputSchema = generateSchema(tool.properties);
628
+ delete inputSchema.description;
629
+ schemas[tool.name] = {
630
+ description: tool.description,
631
+ inputSchema,
632
+ readOnly: tool.readOnly
633
+ };
634
+ }
635
+ return `${className}.__webmcpFieldSchemas = ${JSON.stringify(schemas, null, 2)};`;
636
+ }
491
637
  function mapTypeToSchema(prop) {
492
638
  const schema = { type: "string" };
493
639
  if (prop.description) {
@@ -535,7 +681,7 @@ function mapTypeToSchema(prop) {
535
681
  // src/transform.ts
536
682
  var isDebug2 = () => (process.env.DEBUG ?? "").toLowerCase().includes("webmcp");
537
683
  function transformCode(code, filePath, options = {}) {
538
- if (!code.includes("registerGlobalTools") && !code.includes("useWebMcpTools")) {
684
+ if (!code.includes("registerGlobalTools") && !code.includes("useWebMcpTools") && !code.includes("withWebMcpTools")) {
539
685
  return { code, transformed: false };
540
686
  }
541
687
  if (isDebug2()) {
@@ -549,6 +695,7 @@ function transformCode(code, filePath, options = {}) {
549
695
  }
550
696
  const injections = [];
551
697
  for (const tool of result.tools) {
698
+ if (tool.memberType === "field") continue;
552
699
  const injectionCode = generateSchemaInjectionCode(
553
700
  tool.injectionTarget,
554
701
  tool.description,
@@ -557,17 +704,41 @@ function transformCode(code, filePath, options = {}) {
557
704
  );
558
705
  injections.push(injectionCode);
559
706
  }
707
+ const fieldToolsByClass = /* @__PURE__ */ new Map();
708
+ for (const tool of result.tools) {
709
+ if (tool.memberType !== "field") continue;
710
+ const className = tool.injectionTarget.replace(".__webmcpFieldSchemas", "");
711
+ if (!fieldToolsByClass.has(className)) {
712
+ fieldToolsByClass.set(className, []);
713
+ }
714
+ fieldToolsByClass.get(className).push({
715
+ name: tool.name,
716
+ description: tool.description,
717
+ properties: tool.properties,
718
+ readOnly: tool.readOnly
719
+ });
720
+ }
721
+ for (const [className, fieldTools] of fieldToolsByClass) {
722
+ injections.push(generateFieldSchemasInjectionCode(className, fieldTools));
723
+ }
560
724
  if (injections.length === 0) {
561
725
  return { code, transformed: false };
562
726
  }
563
727
  const firstCall = result.registrationCalls.sort((a, b) => a.start - b.start)[0];
728
+ let insertPos = firstCall.start;
729
+ const textBefore = code.slice(0, firstCall.start);
730
+ const exportDefaultMatch = textBefore.match(/(export\s+default\s+)$/);
731
+ if (exportDefaultMatch) {
732
+ insertPos = firstCall.start - exportDefaultMatch[1].length;
733
+ }
564
734
  const injectionBlock = "\n// [webmcp-nexus-core] \u6784\u5EFA\u65F6\u6CE8\u5165\u7684 schema \u5143\u6570\u636E\n" + injections.join("\n") + "\n";
565
- const newCode = code.slice(0, firstCall.start) + injectionBlock + code.slice(firstCall.start);
735
+ const newCode = code.slice(0, insertPos) + injectionBlock + code.slice(insertPos);
566
736
  return { code: newCode, transformed: true };
567
737
  }
568
738
  export {
569
739
  extractProperties,
570
740
  extractToolsFromFile,
741
+ generateFieldSchemasInjectionCode,
571
742
  generateSchema,
572
743
  generateSchemaInjectionCode,
573
744
  mapType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmcp-nexus-core",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Core logic for WebMCP build plugins - TypeScript type extraction and JSON Schema generation",
5
5
  "license": "MIT",
6
6
  "keywords": [