Skip to content

Commit d160536

Browse files
committed
add request mapping and bean type and injection information to embedded mcp server
1 parent 7198389 commit d160536

File tree

6 files changed

+712
-2
lines changed

6 files changed

+712
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.mcp;
12+
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
import java.util.Optional;
17+
import java.util.stream.Collectors;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
import org.springframework.ai.tool.annotation.Tool;
22+
import org.springframework.ai.tool.annotation.ToolParam;
23+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
24+
import org.springframework.ide.vscode.commons.java.IJavaProject;
25+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
26+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
27+
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
28+
import org.springframework.stereotype.Component;
29+
30+
/**
31+
* MCP tools for analyzing Spring components and dependency injection
32+
*
33+
* @author Martin Lippert
34+
*/
35+
@Component
36+
public class ComponentAnalysisMcpTools {
37+
38+
private static final Logger logger = LoggerFactory.getLogger(ComponentAnalysisMcpTools.class);
39+
40+
private final JavaProjectFinder projectFinder;
41+
private final SpringMetamodelIndex springIndex;
42+
43+
public ComponentAnalysisMcpTools(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
44+
this.projectFinder = projectFinder;
45+
this.springIndex = springIndex;
46+
}
47+
48+
/**
49+
* Record representing component information for MCP clients
50+
*/
51+
public static record ComponentInfo(
52+
String name,
53+
String type,
54+
List<String> annotations,
55+
String sourceFile,
56+
int startLine,
57+
int startColumn,
58+
int endLine,
59+
int endColumn
60+
) {}
61+
62+
/**
63+
* Record representing bean usage information including where it's defined and injected
64+
*/
65+
public static record BeanUsageInfo(
66+
String beanName,
67+
String beanType,
68+
ComponentInfo definition,
69+
List<InjectionPointInfo> injectionPoints
70+
) {}
71+
72+
/**
73+
* Record representing an injection point
74+
*/
75+
public static record InjectionPointInfo(
76+
String injectedIntoBean,
77+
String injectedIntoBeanType,
78+
String injectionName,
79+
String injectionType,
80+
String sourceFile,
81+
int startLine,
82+
int startColumn,
83+
int endLine,
84+
int endColumn
85+
) {}
86+
87+
@Tool(description = """
88+
Get detailed information about a specific bean, including where it's defined and all places where it's injected.
89+
This helps understand bean usage and dependencies across the application.
90+
Returns information for all beans with the given name (there may be multiple beans with the same name in different contexts).
91+
""")
92+
public List<BeanUsageInfo> getBeanUsageInfo(
93+
@ToolParam(description = "the name of the project in the workspace of the user") String projectName,
94+
@ToolParam(description = "the name of the bean to analyze") String beanName) throws Exception {
95+
96+
logger.info("get bean usage info for: {} in project: {}", beanName, projectName);
97+
98+
IJavaProject project = getProject(projectName);
99+
100+
// Find all beans with this name (there may be multiple)
101+
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), beanName);
102+
103+
if (beans.length == 0) {
104+
throw new Exception("bean with name '" + beanName + "' not found in project: " + projectName);
105+
}
106+
107+
List<BeanUsageInfo> usageInfos = new ArrayList<>();
108+
109+
// Create usage info for each bean found
110+
for (Bean bean : beans) {
111+
ComponentInfo definition = createComponentInfo(bean);
112+
113+
// Find all injection points for this bean
114+
List<InjectionPointInfo> injectionPoints = findInjectionPoints(project.getElementName(), bean);
115+
116+
BeanUsageInfo usageInfo = new BeanUsageInfo(
117+
bean.getName(),
118+
bean.getType(),
119+
definition,
120+
injectionPoints
121+
);
122+
123+
usageInfos.add(usageInfo);
124+
}
125+
126+
logger.info("found {} beans with name {} in project: {}", beans.length, beanName, projectName);
127+
128+
return usageInfos;
129+
}
130+
131+
@Tool(description = """
132+
Find all beans that match a specific type.
133+
This is useful for finding all implementations of an interface or all beans of a certain class.
134+
""")
135+
public List<ComponentInfo> findBeansByType(
136+
@ToolParam(description = "the name of the project in the workspace of the user") String projectName,
137+
@ToolParam(description = "the fully qualified type name to search for") String typeName) throws Exception {
138+
139+
logger.info("find beans by type: {} for project: {}", typeName, projectName);
140+
141+
IJavaProject project = getProject(projectName);
142+
143+
// Find beans with matching type
144+
Bean[] beans = springIndex.getBeansWithType(project.getElementName(), typeName);
145+
146+
List<ComponentInfo> components = Arrays.stream(beans)
147+
.map(this::createComponentInfo)
148+
.collect(Collectors.toList());
149+
150+
logger.info("found {} beans of type {} for project: {}", components.size(), typeName, projectName);
151+
152+
return components;
153+
}
154+
155+
//
156+
// Helper methods
157+
//
158+
159+
private ComponentInfo createComponentInfo(Bean bean) {
160+
// Convert AnnotationMetadata[] to List<String> of annotation types
161+
List<String> annotationTypes = bean.getAnnotations() != null
162+
? Arrays.stream(bean.getAnnotations())
163+
.map(annotation -> annotation.getAnnotationType())
164+
.collect(Collectors.toList())
165+
: List.of();
166+
167+
return new ComponentInfo(
168+
bean.getName(),
169+
bean.getType(),
170+
annotationTypes,
171+
bean.getLocation() != null ? bean.getLocation().getUri() : null,
172+
bean.getLocation() != null ? bean.getLocation().getRange().getStart().getLine() : 0,
173+
bean.getLocation() != null ? bean.getLocation().getRange().getStart().getCharacter() : 0,
174+
bean.getLocation() != null ? bean.getLocation().getRange().getEnd().getLine() : 0,
175+
bean.getLocation() != null ? bean.getLocation().getRange().getEnd().getCharacter() : 0
176+
);
177+
}
178+
179+
private List<InjectionPointInfo> findInjectionPoints(String projectName, Bean targetBean) {
180+
List<InjectionPointInfo> injectionPoints = new ArrayList<>();
181+
182+
// Get all beans in the project
183+
Bean[] allBeans = springIndex.getBeansOfProject(projectName);
184+
185+
// Look through all beans to find injection points where this bean could be injected
186+
for (Bean bean : allBeans) {
187+
if (bean.getInjectionPoints() != null) {
188+
for (InjectionPoint injectionPoint : bean.getInjectionPoints()) {
189+
// Check if the target bean is type-compatible with the injection point
190+
if (targetBean.isTypeCompatibleWith(injectionPoint.getType())) {
191+
InjectionPointInfo info = new InjectionPointInfo(
192+
bean.getName(),
193+
bean.getType(),
194+
injectionPoint.getName(),
195+
injectionPoint.getType(),
196+
injectionPoint.getLocation() != null ? injectionPoint.getLocation().getUri() : null,
197+
injectionPoint.getLocation() != null ? injectionPoint.getLocation().getRange().getStart().getLine() : 0,
198+
injectionPoint.getLocation() != null ? injectionPoint.getLocation().getRange().getStart().getCharacter() : 0,
199+
injectionPoint.getLocation() != null ? injectionPoint.getLocation().getRange().getEnd().getLine() : 0,
200+
injectionPoint.getLocation() != null ? injectionPoint.getLocation().getRange().getEnd().getCharacter() : 0
201+
);
202+
injectionPoints.add(info);
203+
}
204+
}
205+
}
206+
}
207+
208+
return injectionPoints;
209+
}
210+
211+
private IJavaProject getProject(String projectName) throws Exception {
212+
Optional<? extends IJavaProject> found = projectFinder.all().stream()
213+
.filter(project -> project.getElementName().toLowerCase().equals(projectName.toLowerCase()))
214+
.findFirst();
215+
216+
if (found.isEmpty()) {
217+
throw new Exception("project with name " + projectName + " not found");
218+
} else {
219+
return found.get();
220+
}
221+
}
222+
223+
}
224+

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/mcp/McpConfig.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ public List<ToolCallback> registerTools(
2828
SpringVersionsAndGenerations springVersionsAndGenerations,
2929
SpringIndexAccess springIndexAccess,
3030
ProjectInformation projectInformation,
31-
StereotypeInformation stereotypeInformation) {
31+
StereotypeInformation stereotypeInformation,
32+
RequestMappingMcpTools requestMappingMcpTools,
33+
ComponentAnalysisMcpTools componentAnalysisMcpTools) {
3234

3335
return List.of(ToolCallbacks.from(
3436
springVersionsAndGenerations,
3537
springIndexAccess,
3638
projectInformation,
37-
stereotypeInformation));
39+
stereotypeInformation,
40+
requestMappingMcpTools,
41+
componentAnalysisMcpTools));
3842
}
3943

4044
// @Bean

0 commit comments

Comments
 (0)