Skip to content

Commit e429ce3

Browse files
committed
Handle default value for MCP parameter
1 parent 3656625 commit e429ce3

File tree

16 files changed

+175
-101
lines changed

16 files changed

+175
-101
lines changed

joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/jsonrpc/JsonRpcRequest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@
2424
*/
2525
public class JsonRpcRequest implements Serializable {
2626

27+
private static final long serialVersionUID = 1L;
28+
2729
public static final String JSON_PATH_ID = "$.id";
2830

29-
private static final long serialVersionUID = 1L;
31+
public static final String DEFAULT_VERSION = "2.0";
3032

3133
/**
3234
* A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
3335
*/
34-
private String jsonrpc = "2.0";
36+
private String jsonrpc = DEFAULT_VERSION;
3537

3638
/**
3739
* A String containing the name of the method to be invoked.
@@ -105,6 +107,10 @@ public boolean notification() {
105107
return id == null;
106108
}
107109

110+
public boolean validate() {
111+
return DEFAULT_VERSION.equals(jsonrpc) && method != null && !method.isEmpty();
112+
}
113+
108114
@Override
109115
public String toString() {
110116
return "JsonRpcRequest{" +

joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/mcp/DefaultMcpParameterParser.java

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@
2121
import com.jd.live.agent.governance.jsonrpc.JsonRpcException.NotEnoughParameter;
2222

2323
import java.lang.reflect.Array;
24-
import java.lang.reflect.Type;
2524
import java.util.*;
2625
import java.util.concurrent.atomic.AtomicInteger;
2726
import java.util.function.BiFunction;
28-
import java.util.function.Function;
2927

3028
/**
3129
* Default implementation of McpParameterConverter that handles parameter conversion for JSON-RPC calls.
@@ -161,59 +159,16 @@ private Object[] parse(McpToolParameter[] parameters,
161159
BiFunction<McpToolParameter, Integer, Object> paramFunc) throws Exception {
162160
Object[] args = new Object[parameters.length];
163161
if (parameters.length == 1) {
164-
args[0] = parse(parameters[0], converter, ctx, p -> params);
162+
args[0] = parameters[0].parse(ctx, p -> params);
165163
} else {
166164
AtomicInteger counter = new AtomicInteger(0);
167165
for (int i = 0; i < parameters.length; i++) {
168-
args[i] = parse(parameters[i], converter, ctx, p -> paramFunc.apply(p, counter.getAndIncrement()));
166+
args[i] = parameters[i].parse(ctx, p -> paramFunc.apply(p, counter.getAndIncrement()));
169167
if (args[i] == null && parameters[i].isRequired()) {
170168
throw new JsonRpcException("Required parameter at position " + i + " is missing", JsonRpcError.INVALID_PARAMS);
171169
}
172170
}
173171
}
174172
return args;
175173
}
176-
177-
/**
178-
* Parses and converts a single parameter value.
179-
*
180-
* @param parameter target parameter definition
181-
* @param converter object converter
182-
* @param ctx parsing context
183-
* @param valueFunc function to get parameter value
184-
* @return parsed and converted parameter value
185-
*/
186-
private Object parse(McpToolParameter parameter,
187-
ObjectConverter converter,
188-
RequestContext ctx,
189-
Function<McpToolParameter, Object> valueFunc) throws Exception {
190-
if (parameter.isFramework()) {
191-
// system parser
192-
return parameter.parse(ctx, converter);
193-
}
194-
return parameter.convert(valueFunc.apply(parameter), converter);
195-
}
196-
197-
/**
198-
* Converts a single value to target type using converter.
199-
*
200-
* @param value Value to convert
201-
* @param targetClass Target class type
202-
* @param targetType Target generic type
203-
* @param converter Object converter to use
204-
* @return Converted value
205-
*/
206-
private Object convert(Object value, Class<?> targetClass, Type targetType, ObjectConverter converter) {
207-
if (value == null) {
208-
return null;
209-
}
210-
try {
211-
if (targetClass.isInstance(value) && (SIMPLE_TYPES.contains(targetClass) || targetClass.isEnum())) {
212-
return value;
213-
}
214-
return converter.convert(value, targetType);
215-
} catch (Exception e) {
216-
throw new JsonRpcException("Failed to convert value to " + targetClass.getName(), e, JsonRpcError.INVALID_PARAMS);
217-
}
218-
}
219174
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.jd.live.agent.plugin.application.springboot.v2.mcp;
16+
package com.jd.live.agent.governance.mcp;
1717

18-
import com.jd.live.agent.governance.mcp.RequestContext;
19-
20-
public abstract class AbstractParserContext implements RequestContext {
18+
/**
19+
* Expression that can be evaluated to a value.
20+
*/
21+
public interface Expression {
2122

23+
/**
24+
* Returns true if this is a static/literal value that doesn't need resolution.
25+
*/
26+
boolean isLiteral();
2227
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright © ${year} ${owner} (${email})
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.jd.live.agent.governance.mcp;
17+
18+
/**
19+
* Factory for parsing and evaluating expressions.
20+
*/
21+
public interface ExpressionFactory {
22+
23+
/**
24+
* Parses a string into an Expression object.
25+
*
26+
* @param expression the expression string to parse
27+
* @return the parsed Expression
28+
*/
29+
Expression parse(String expression);
30+
31+
/**
32+
* Evaluates an Expression to its result value.
33+
*
34+
* @param expression the Expression to evaluate
35+
* @return the evaluation result
36+
*/
37+
Object evaluate(Expression expression);
38+
39+
}

joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/mcp/McpToolParameter.java

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
*/
1616
package com.jd.live.agent.governance.mcp;
1717

18-
import com.jd.live.agent.core.parser.ObjectConverter;
1918
import com.jd.live.agent.core.util.converter.Converter;
2019
import lombok.Getter;
2120

2221
import java.lang.reflect.Parameter;
2322
import java.lang.reflect.Type;
23+
import java.util.function.Function;
2424

2525
import static com.jd.live.agent.core.util.type.ClassUtils.SIMPLE_TYPES;
2626

@@ -54,6 +54,8 @@ public class McpToolParameter {
5454

5555
private final RequestParser parser;
5656

57+
private final RequestParser defaultValueParser;
58+
5759
public McpToolParameter(Parameter parameter,
5860
int index,
5961
Type actualType,
@@ -62,7 +64,8 @@ public McpToolParameter(Parameter parameter,
6264
boolean optional,
6365
boolean convertable,
6466
Converter<Object, ?> converter,
65-
RequestParser parser) {
67+
RequestParser parser,
68+
RequestParser defaultValueParser) {
6669
this.parameter = parameter;
6770
this.name = parameter.getName();
6871
this.arg = arg;
@@ -75,6 +78,7 @@ public McpToolParameter(Parameter parameter,
7578
this.convertable = convertable;
7679
this.converter = converter;
7780
this.parser = parser;
81+
this.defaultValueParser = defaultValueParser;
7882
}
7983

8084
public String getArg() {
@@ -92,34 +96,36 @@ public boolean isFramework() {
9296
return parser != null;
9397
}
9498

95-
public Object parse(RequestContext ctx, ObjectConverter converter) throws Exception {
96-
if (parser == null) {
97-
return convert(null);
98-
}
99-
Object result = parser.parse(ctx);
100-
if (result != null && convertable) {
101-
return convert(result, converter);
99+
public Object parse(RequestContext ctx, Function<McpToolParameter, Object> valueFunc) throws Exception {
100+
Object result;
101+
if (isFramework()) {
102+
// system parser
103+
result = parser.parse(ctx);
104+
result = result == null && defaultValueParser != null ? defaultValueParser.parse(ctx) : result;
105+
result = convertable ? convert(ctx, result) : result;
106+
} else {
107+
result = valueFunc.apply(this);
108+
result = result == null && defaultValueParser != null ? defaultValueParser.parse(ctx) : result;
109+
result = convert(ctx, result);
102110
}
103-
return convert(result);
111+
return wrap(result);
104112
}
105113

106-
public Object convert(Object result, ObjectConverter converter) {
107-
Object converted = result;
108-
if (result != null) {
109-
Class<?> actualClass = getActualClass();
110-
Class<?> resultClass = result.getClass();
111-
if (actualClass.isInstance(result)) {
112-
if (SIMPLE_TYPES.contains(resultClass) || resultClass.isEnum()) {
113-
return convert(result);
114-
}
114+
private Object convert(RequestContext ctx, Object target) {
115+
if (target == null) {
116+
return null;
117+
}
118+
Class<?> resultClass = getActualClass();
119+
Class<?> targetClass = target.getClass();
120+
if (resultClass.isInstance(target)) {
121+
if (SIMPLE_TYPES.contains(targetClass) || targetClass.isEnum()) {
122+
return wrap(target);
115123
}
116-
converted = converter.convert(result, actualType);
117124
}
118-
119-
return convert(converted);
125+
return ctx.getConverter().convert(target, actualType);
120126
}
121127

122-
public Object convert(Object value) {
128+
private Object wrap(Object value) {
123129
return value == null || converter == null ? value : converter.convert(value);
124130
}
125131

@@ -140,6 +146,7 @@ public static class McpToolParameterBuilder {
140146
private boolean convertable;
141147
private Converter<Object, ?> converter;
142148
private RequestParser parser;
149+
private RequestParser defaultValueParser;
143150

144151
public McpToolParameterBuilder parameter(Parameter parameter) {
145152
this.parameter = parameter;
@@ -190,6 +197,11 @@ public McpToolParameterBuilder parser(RequestParser parser) {
190197
return this;
191198
}
192199

200+
public McpToolParameterBuilder defaultValueParser(RequestParser defaultValueParser) {
201+
this.defaultValueParser = defaultValueParser;
202+
return this;
203+
}
204+
193205
public Parameter parameter() {
194206
return this.parameter;
195207
}
@@ -238,6 +250,10 @@ public RequestParser parser() {
238250
return this.parser;
239251
}
240252

253+
public RequestParser defaultValueParser() {
254+
return this.defaultValueParser;
255+
}
256+
241257
public Class<?> actualClass() {
242258
return actualType instanceof Class ? (Class<?>) actualType : type;
243259
}
@@ -251,7 +267,7 @@ public boolean isAssignableTo(Class<?> targetClass) {
251267
}
252268

253269
public McpToolParameter build() {
254-
return new McpToolParameter(parameter, index, actualType, arg, required, optional, convertable, converter, parser);
270+
return new McpToolParameter(parameter, index, actualType, arg, required, optional, convertable, converter, parser, defaultValueParser);
255271
}
256272
}
257273

joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/mcp/RequestContext.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,34 @@
1515
*/
1616
package com.jd.live.agent.governance.mcp;
1717

18+
import com.jd.live.agent.core.parser.ObjectConverter;
19+
import lombok.Getter;
20+
1821
/**
19-
* Context for parameter parsing.
22+
* Context interface for MCP request parameter conversion.
23+
*
24+
* <p>Provides access to an ObjectConverter that transforms MCP request
25+
* parameters into method argument types.
26+
*
27+
* @see ObjectConverter
2028
*/
2129
public interface RequestContext {
2230

31+
/**
32+
* Gets the converter for transforming request parameters.
33+
*
34+
* @return the object converter instance
35+
*/
36+
ObjectConverter getConverter();
37+
38+
@Getter
39+
abstract class AbstractRequestContext implements RequestContext {
40+
41+
private ObjectConverter converter;
42+
43+
public AbstractRequestContext(ObjectConverter converter) {
44+
this.converter = converter;
45+
}
46+
47+
}
2348
}

joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/mcp/RequestParser.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,30 @@ public interface RequestParser {
3030
*/
3131
Object parse(RequestContext ctx) throws Exception;
3232

33+
/**
34+
* Combines multiple request parsers and executes them in sequence until a non-null result is found.
35+
*/
36+
class CompositeRequestParser implements RequestParser {
37+
38+
private RequestParser[] parsers;
39+
40+
public CompositeRequestParser(RequestParser... parsers) {
41+
this.parsers = parsers;
42+
}
43+
44+
@Override
45+
public Object parse(RequestContext ctx) throws Exception {
46+
Object result = null;
47+
if (parsers != null) {
48+
for (RequestParser parser : parsers) {
49+
result = parser.parse(ctx);
50+
if (result != null) {
51+
break;
52+
}
53+
}
54+
}
55+
return result;
56+
}
57+
}
58+
3359
}

joylive-plugin/joylive-application/joylive-application-springboot2/src/main/java/com/jd/live/agent/plugin/application/springboot/v2/mcp/reactive/ReactiveMcpController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public Mono<JsonRpcResponse> handle(@RequestBody Mono<JsonRpcRequest> request, S
6060
return Mono.just(JsonRpcResponse.createMethodNotFoundResponse(req.getId()));
6161
}
6262
try {
63-
Object[] args = parameterParser.parse(method, req.getParams(), objectConverter, new ReactiveParserContext(exchange));
63+
Object[] args = parameterParser.parse(method, req.getParams(), objectConverter, new ReactiveRequestContext(objectConverter, exchange));
6464
Object result = method.invoke(args);
6565
// Result may be a Mono object
6666
return MonoConverter.INSTANCE.convert(result)

joylive-plugin/joylive-application/joylive-application-springboot2/src/main/java/com/jd/live/agent/plugin/application/springboot/v2/mcp/reactive/ReactiveMcpToolScanner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private boolean support(Class<?> type) {
145145
}
146146

147147
private ServerWebExchange getWebExchange(RequestContext ctx) {
148-
return ((ReactiveParserContext) ctx).getExchange();
148+
return ((ReactiveRequestContext) ctx).getExchange();
149149
}
150150

151151
private ServerHttpRequest getRequest(RequestContext ctx) {

0 commit comments

Comments
 (0)