44 * Licensed under the MIT License. See License.txt in the project root for license information.
55 *--------------------------------------------------------------------------------------------*/
66
7- package modelengine .fel .tool .mcp .server ;
7+ package modelengine .fel .tool .mcp .server . support ;
88
99import io .modelcontextprotocol .server .McpServerFeatures ;
1010import io .modelcontextprotocol .server .McpSyncServer ;
1111import io .modelcontextprotocol .spec .McpSchema ;
1212import modelengine .fel .tool .mcp .entity .ServerSchema ;
1313import modelengine .fel .tool .mcp .entity .Tool ;
14+ import modelengine .fel .tool .mcp .server .McpServer ;
1415import modelengine .fel .tool .service .ToolChangedObserver ;
1516import modelengine .fel .tool .service .ToolExecuteService ;
1617import modelengine .fitframework .annotation .Component ;
1920import modelengine .fitframework .util .StringUtils ;
2021
2122import java .util .ArrayList ;
23+ import java .util .HashMap ;
2224import java .util .List ;
2325import java .util .Map ;
24- import java .util .concurrent . ConcurrentHashMap ;
26+ import java .util .stream . Collectors ;
2527
2628import static modelengine .fel .tool .info .schema .PluginSchema .TYPE ;
2729import static modelengine .fel .tool .info .schema .ToolsSchema .PROPERTIES ;
3335 * with MCP Server Bean {@link McpSyncServer}.
3436 *
3537 * @author 季聿阶
38+ * @author 黄可欣
3639 * @since 2025-05-15
3740 */
3841@ Component
@@ -41,7 +44,6 @@ public class DefaultMcpStreamableServer implements McpServer, ToolChangedObserve
4144 private final McpSyncServer mcpSyncServer ;
4245
4346 private final ToolExecuteService toolExecuteService ;
44- private final Map <String , Tool > tools = new ConcurrentHashMap <>();
4547 private final List <ToolsChangedObserver > toolsChangedObservers = new ArrayList <>();
4648
4749 /**
@@ -66,7 +68,9 @@ public ServerSchema getSchema() {
6668
6769 @ Override
6870 public List <Tool > getTools () {
69- return List .copyOf (this .tools .values ());
71+ return this .mcpSyncServer .listTools ().stream ()
72+ .map (this ::convertToFelTool )
73+ .collect (Collectors .toList ());
7074 }
7175
7276 @ Override
@@ -90,13 +94,7 @@ public void onToolAdded(String name, String description, Map<String, Object> par
9094 log .warn ("Tool addition is ignored: tool schema is null or empty. [toolName={}]" , name );
9195 return ;
9296 }
93- Object props = parameters .get (PROPERTIES );
94- Object reqs = parameters .get (REQUIRED );
95- if (!(parameters .get (TYPE ) instanceof String )
96- || (props != null && (!(props instanceof Map <?, ?>)
97- || ((Map <?, ?>) props ).keySet ().stream ().anyMatch (k -> !(k instanceof String ))))
98- || (reqs != null && (!(reqs instanceof List <?>)
99- || ((List <?>) reqs ).stream ().anyMatch (v -> !(v instanceof String ))))) {
97+ if (!isValidParameterSchema (parameters )) {
10098 log .warn ("Invalid parameter schema. [toolName={}]" , name );
10199 return ;
102100 }
@@ -111,26 +109,27 @@ public void onToolAdded(String name, String description, Map<String, Object> par
111109 .inputSchema (inputSchema )
112110 .build ())
113111 .callHandler ((exchange , request ) -> {
114- Map <String , Object > args = request .arguments ();
115- String result = this .toolExecuteService .execute (name , args );
116- return new McpSchema .CallToolResult (result , false );
112+ try {
113+ Map <String , Object > args = request .arguments ();
114+ String result = this .toolExecuteService .execute (name , args );
115+ return new McpSchema .CallToolResult (result , false );
116+ } catch (IllegalArgumentException e ) {
117+ log .warn ("Invalid arguments for tool execution. [toolName={}, error={}]" , name , e .getMessage ());
118+ return new McpSchema .CallToolResult ("Error: Invalid arguments - " + e .getMessage (), true );
119+ } catch (Exception e ) {
120+ log .error ("Failed to execute tool. [toolName={}]" , name , e );
121+ return new McpSchema .CallToolResult ("Error: Tool execution failed - " + e .getMessage (), true );
122+ }
117123 })
118124 .build ();
119- Tool tool = new Tool ();
120- tool .setName (name );
121- tool .setDescription (description );
122- tool .setInputSchema (parameters );
123125
124126 try {
125127 this .mcpSyncServer .addTool (toolSpecification );
126- this .tools .put (name , tool );
128+ log .info ("Tool added to MCP server. [toolName={}, description={}, schema={}]" , name , description , parameters );
129+ this .toolsChangedObservers .forEach (ToolsChangedObserver ::onToolsChanged );
127130 } catch (Exception e ) {
128- log .error ("Failed to add tool: {}" , name , e );
129- this .tools .remove (name );
130- return ;
131+ log .error ("Failed to add tool to MCP server. [toolName={}]" , name , e );
131132 }
132- log .info ("Tool added to MCP server. [toolName={}, description={}, schema={}]" , name , description , parameters );
133- this .toolsChangedObservers .forEach (ToolsChangedObserver ::onToolsChanged );
134133 }
135134
136135 @ Override
@@ -140,8 +139,64 @@ public void onToolRemoved(String name) {
140139 return ;
141140 }
142141 this .mcpSyncServer .removeTool (name );
143- this .tools .remove (name );
144142 log .info ("Tool removed from MCP server. [toolName={}]" , name );
145143 this .toolsChangedObservers .forEach (ToolsChangedObserver ::onToolsChanged );
146144 }
145+
146+ /**
147+ * Converts an MCP SDK Tool to a FEL Tool entity.
148+ *
149+ * @param mcpTool The MCP SDK tool to convert.
150+ * @return A FEL Tool entity with the corresponding name, description, and input schema.
151+ */
152+ private Tool convertToFelTool (McpSchema .Tool mcpTool ) {
153+ Tool tool = new Tool ();
154+ tool .setName (mcpTool .name ());
155+ tool .setDescription (mcpTool .description ());
156+
157+ // Convert JsonSchema to Map<String, Object>
158+ McpSchema .JsonSchema inputSchema = mcpTool .inputSchema ();
159+ Map <String , Object > schemaMap = new HashMap <>();
160+ schemaMap .put (TYPE , inputSchema .type ());
161+ if (inputSchema .properties () != null ) {
162+ schemaMap .put (PROPERTIES , inputSchema .properties ());
163+ }
164+ if (inputSchema .required () != null ) {
165+ schemaMap .put (REQUIRED , inputSchema .required ());
166+ }
167+ tool .setInputSchema (schemaMap );
168+
169+ return tool ;
170+ }
171+
172+ /**
173+ * Validates the structure of the parameter schema to ensure it conforms to the expected format.
174+ *
175+ * @param parameters The parameter schema to validate, represented as a Map with String keys and Object values.
176+ * @return {@code true} if the parameter schema is valid; {@code false} otherwise.
177+ */
178+ private boolean isValidParameterSchema (Map <String , Object > parameters ) {
179+ Object type = parameters .get (TYPE );
180+ if (!(type instanceof String )) {
181+ return false ;
182+ }
183+
184+ Object props = parameters .get (PROPERTIES );
185+ if (!(props instanceof Map <?, ?> propsMap )) {
186+ return false ;
187+ }
188+ if (propsMap .keySet ().stream ().anyMatch (k -> !(k instanceof String ))) {
189+ return false ;
190+ }
191+
192+ Object reqs = parameters .get (REQUIRED );
193+ if (!(reqs instanceof List <?> reqsList )) {
194+ return false ;
195+ }
196+ if (reqsList .stream ().anyMatch (v -> !(v instanceof String ))) {
197+ return false ;
198+ }
199+
200+ return true ;
201+ }
147202}
0 commit comments