webmcp-nexus-core 0.1.11 → 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 +175 -3
- package/dist/index.d.cts +28 -3
- package/dist/index.d.ts +28 -3
- package/dist/index.js +174 -3
- package/package.json +1 -1
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,
|
|
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,
|
|
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,
|