jsc-typescript-ast-mcp 1.0.11 → 1.1.0
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/README.md
CHANGED
|
@@ -96,6 +96,7 @@ Analyze React component structures in your codebase.
|
|
|
96
96
|
- **Input**:
|
|
97
97
|
- `entryFilePath`: Path to the entry file
|
|
98
98
|
- `maxDepth`: Maximum depth of the component tree (default: 3)
|
|
99
|
+
- `data-id` (optional): Attribute name to capture into `TreeNode.dataId` (example: `data-attribute-id`)
|
|
99
100
|
- **Output**: JSON representation of the component tree structure
|
|
100
101
|
|
|
101
102
|
## License
|
|
@@ -11,9 +11,13 @@ export const registerComponentTreeTool = (server) => {
|
|
|
11
11
|
.number()
|
|
12
12
|
.default(3)
|
|
13
13
|
.describe('Maximum depth of the component tree (default: 3)'),
|
|
14
|
+
'data-id': z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Optional attribute name to capture as TreeNode.dataId (example: data-attribute-id)'),
|
|
14
18
|
},
|
|
15
|
-
}, async ({ entryFilePath, maxDepth }) => {
|
|
16
|
-
const tree = getComponentTree(entryFilePath, maxDepth);
|
|
19
|
+
}, async ({ entryFilePath, maxDepth, ['data-id']: dataIdAttribute }) => {
|
|
20
|
+
const tree = getComponentTree(entryFilePath, maxDepth, dataIdAttribute);
|
|
17
21
|
return {
|
|
18
22
|
content: [
|
|
19
23
|
{
|
|
@@ -24,7 +28,7 @@ export const registerComponentTreeTool = (server) => {
|
|
|
24
28
|
};
|
|
25
29
|
});
|
|
26
30
|
};
|
|
27
|
-
const getComponentTree = (entryFilePath, maxDepth) => {
|
|
31
|
+
const getComponentTree = (entryFilePath, maxDepth, dataIdAttribute) => {
|
|
28
32
|
const project = createProject();
|
|
29
33
|
const sourceFile = project.getSourceFile(entryFilePath);
|
|
30
34
|
if (!sourceFile) {
|
|
@@ -33,5 +37,6 @@ const getComponentTree = (entryFilePath, maxDepth) => {
|
|
|
33
37
|
const component = getComponent(sourceFile);
|
|
34
38
|
return analyzeComponent(component, project, {
|
|
35
39
|
maxDepth,
|
|
40
|
+
dataIdAttribute,
|
|
36
41
|
}, 0);
|
|
37
42
|
};
|
|
@@ -2,8 +2,9 @@ import { Node, SyntaxKind, } from 'ts-morph';
|
|
|
2
2
|
import { analyzeComponent } from './analyzeComponent.js';
|
|
3
3
|
import { getComponent } from './component.js';
|
|
4
4
|
import { getTagName } from './nameHelper.js';
|
|
5
|
+
import { extractPropsFromNode } from './props.js';
|
|
5
6
|
import { handleTernary } from './ternary.js';
|
|
6
|
-
const extractFromExpression = (expression, project, options, depth) => {
|
|
7
|
+
export const extractFromExpression = (expression, project, options, depth) => {
|
|
7
8
|
if (Node.isConditionalExpression(expression)) {
|
|
8
9
|
return handleTernary(expression, project, options, depth);
|
|
9
10
|
}
|
|
@@ -15,7 +16,7 @@ const extractFromExpression = (expression, project, options, depth) => {
|
|
|
15
16
|
}
|
|
16
17
|
return extractJSX(expression, project, options, depth);
|
|
17
18
|
};
|
|
18
|
-
const handleLogicalAnd = (expression, project, options, depth) => {
|
|
19
|
+
export const handleLogicalAnd = (expression, project, options, depth) => {
|
|
19
20
|
if (expression.getOperatorToken().getText() !== '&&') {
|
|
20
21
|
return [];
|
|
21
22
|
}
|
|
@@ -103,7 +104,7 @@ const resolveComponentFile = (node) => {
|
|
|
103
104
|
}
|
|
104
105
|
return null;
|
|
105
106
|
};
|
|
106
|
-
|
|
107
|
+
const buildNodeFromJSX = (node, project, options, depth) => {
|
|
107
108
|
const tagName = getTagName(node);
|
|
108
109
|
const isHtml = tagName[0] === tagName[0].toLowerCase();
|
|
109
110
|
const treeNode = {
|
|
@@ -111,6 +112,19 @@ function buildNodeFromJSX(node, project, options, depth) {
|
|
|
111
112
|
type: isHtml ? 'html' : 'component',
|
|
112
113
|
children: [],
|
|
113
114
|
};
|
|
115
|
+
if (!isHtml) {
|
|
116
|
+
treeNode.filePath = node.getSourceFile().getFilePath();
|
|
117
|
+
}
|
|
118
|
+
const props = extractPropsFromNode(node);
|
|
119
|
+
if (props) {
|
|
120
|
+
treeNode.props = props;
|
|
121
|
+
if (options.dataIdAttribute && options.dataIdAttribute in props) {
|
|
122
|
+
const dataIdValue = props[options.dataIdAttribute];
|
|
123
|
+
if (dataIdValue !== undefined && dataIdValue !== null) {
|
|
124
|
+
treeNode.dataId = String(dataIdValue);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
114
128
|
// Recurse into children
|
|
115
129
|
if (Node.isJsxElement(node)) {
|
|
116
130
|
const children = node.getJsxChildren();
|
|
@@ -140,12 +154,13 @@ function buildNodeFromJSX(node, project, options, depth) {
|
|
|
140
154
|
if (!isHtml && depth < options.maxDepth) {
|
|
141
155
|
const resolved = resolveComponentFile(node);
|
|
142
156
|
if (resolved) {
|
|
157
|
+
treeNode.filePath = resolved.getSourceFile().getFilePath();
|
|
143
158
|
const subTree = analyzeComponent(resolved, project, options, depth);
|
|
144
159
|
treeNode.children.push(...subTree.children);
|
|
145
160
|
}
|
|
146
161
|
}
|
|
147
162
|
return treeNode;
|
|
148
|
-
}
|
|
163
|
+
};
|
|
149
164
|
export const findReturnedJSX = (node) => {
|
|
150
165
|
const returnStmt = node.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
|
|
151
166
|
if (returnStmt) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Node, } from 'ts-morph';
|
|
2
|
+
const extractPropValue = (attribute) => {
|
|
3
|
+
const initializer = attribute.getInitializer();
|
|
4
|
+
if (!initializer) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
if (Node.isStringLiteral(initializer)) {
|
|
8
|
+
return initializer.getLiteralText();
|
|
9
|
+
}
|
|
10
|
+
if (Node.isJsxExpression(initializer)) {
|
|
11
|
+
const expression = initializer.getExpression();
|
|
12
|
+
if (!expression) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return expression.getText();
|
|
16
|
+
}
|
|
17
|
+
return initializer.getText();
|
|
18
|
+
};
|
|
19
|
+
const addSpreadProps = (props, spreadAttribute) => {
|
|
20
|
+
const expression = spreadAttribute.getExpression();
|
|
21
|
+
const expressionText = expression.getText();
|
|
22
|
+
// Keep spread attributes as references so callers can resolve them later if needed.
|
|
23
|
+
props[`...${expressionText}`] = expressionText;
|
|
24
|
+
};
|
|
25
|
+
export const extractPropsFromNode = (node) => {
|
|
26
|
+
const attributes = Node.isJsxElement(node)
|
|
27
|
+
? node.getOpeningElement().getAttributes()
|
|
28
|
+
: node.getAttributes();
|
|
29
|
+
if (attributes.length === 0) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const props = {};
|
|
33
|
+
for (const attribute of attributes) {
|
|
34
|
+
if (Node.isJsxAttribute(attribute)) {
|
|
35
|
+
props[attribute.getNameNode().getText()] = extractPropValue(attribute);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (Node.isJsxSpreadAttribute(attribute)) {
|
|
39
|
+
addSpreadProps(props, attribute);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(props).length === 0) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return props;
|
|
46
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsc-typescript-ast-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"mcpName": "io.github.jscoobyced/jsc-typescript-ast-mcp",
|
|
5
5
|
"description": "A Model Context Protocol (MCP) server that provides an abstract syntax tree (AST) representation of TypeScript code using the ts-morph library. It allows clients to analyze and manipulate TypeScript code structures, making it easier for AI models to understand and work with TypeScript projects. You can create a JSON representation of your React components, and use it for various purposes such as documentation, code analysis, or even generating new code based on existing components.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"format": "prettier --write .",
|
|
23
23
|
"build": "tsc",
|
|
24
|
+
"lint": "eslint . --ext .ts",
|
|
24
25
|
"test:mcp": "npx @modelcontextprotocol/inspector node dist/index.js"
|
|
25
26
|
},
|
|
26
27
|
"bin": {
|