Skip to content

Commit 3720c8b

Browse files
committed
feat(mcp): fix(mcp): fix response template with root placeholder not returning actual HTTP response
1 parent d598ae9 commit 3720c8b

File tree

2 files changed

+99
-5
lines changed

2 files changed

+99
-5
lines changed

mcp/spring-ai-alibaba-mcp-gateway/src/main/java/com/alibaba/cloud/ai/mcp/gateway/nacos/callback/NacosMcpGatewayToolCallback.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
public class NacosMcpGatewayToolCallback implements ToolCallback {
7373

7474
private static final Logger logger = LoggerFactory.getLogger(NacosMcpGatewayToolCallback.class);
75-
76-
private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{\\{\\s*(\\.[\\w]+(?:\\.[\\w]+)*)\\s*\\}\\}");
77-
75+
76+
private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{\\{\\s*(\\.(?:[\\w]+(?:\\.[\\w]+)*)?)\\s*\\}\\}");
77+
7878
// 匹配 {{ ${nacos.dataId/group} }} 或 {{ ${nacos.dataId/group}.key1.key2 }}
7979
private static final Pattern NACOS_TEMPLATE_PATTERN = Pattern
8080
.compile("\\{\\{\\s*\\$\\{nacos\\.([^}]+)\\}(\\.[\\w]+(?:\\.[\\w]+)*)?\\s*}}");
@@ -518,13 +518,16 @@ private String processTemplateString(String template, Map<String, Object> params
518518
* @return 解析后的值
519519
*/
520520
private String resolvePathValue(String fullPath, Map<String, Object> args, String extendedData) {
521-
if (StringUtils.isBlank(fullPath)) {
522-
return "";
521+
if (fullPath == null) {
522+
return extendedData != null ? extendedData : "";
523523
}
524524
// 移除开头的点号
525525
if (fullPath.startsWith(".")) {
526526
fullPath = fullPath.substring(1);
527527
}
528+
if (StringUtils.isBlank(fullPath)) {
529+
return extendedData != null ? extendedData : "";
530+
}
528531

529532
String[] pathParts = fullPath.split("\\.");
530533
if (pathParts.length == 0) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
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+
* https://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+
17+
package com.alibaba.cloud.ai.mcp.gateway.nacos.callback;
18+
19+
import com.alibaba.cloud.ai.mcp.gateway.core.utils.SpringBeanUtils;
20+
import com.alibaba.cloud.ai.mcp.gateway.nacos.definition.NacosMcpGatewayToolDefinition;
21+
import com.alibaba.cloud.ai.mcp.nacos.service.NacosMcpOperationService;
22+
import com.alibaba.nacos.api.ai.model.mcp.McpServerRemoteServiceConfig;
23+
import com.fasterxml.jackson.databind.JsonNode;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.fasterxml.jackson.databind.node.ObjectNode;
26+
import org.junit.jupiter.api.AfterEach;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
import org.mockito.Mockito;
30+
import org.springframework.context.support.GenericApplicationContext;
31+
import org.springframework.web.reactive.function.client.WebClient;
32+
33+
import java.lang.reflect.Method;
34+
import java.util.Collections;
35+
import java.util.Map;
36+
37+
import static org.junit.jupiter.api.Assertions.assertEquals;
38+
39+
/**
40+
* Tests for NacosMcpGatewayToolCallback response template processing
41+
* @author saladday
42+
*/
43+
class NacosMcpGatewayToolCallbackTest {
44+
45+
private GenericApplicationContext applicationContext;
46+
47+
private final ObjectMapper objectMapper = new ObjectMapper();
48+
49+
@BeforeEach
50+
void setUp() {
51+
applicationContext = new GenericApplicationContext();
52+
applicationContext.registerBean(WebClient.Builder.class, WebClient::builder);
53+
applicationContext.registerBean(NacosMcpOperationService.class, () -> Mockito.mock(NacosMcpOperationService.class));
54+
applicationContext.refresh();
55+
SpringBeanUtils.getInstance().setApplicationContext(applicationContext);
56+
}
57+
58+
@AfterEach
59+
void tearDown() {
60+
if (applicationContext != null) {
61+
applicationContext.close();
62+
}
63+
SpringBeanUtils.getInstance().setApplicationContext(null);
64+
}
65+
66+
@Test
67+
void bodyTemplateWithRootPlaceholderReturnsRawResponse() throws Exception {
68+
NacosMcpGatewayToolDefinition definition = new NacosMcpGatewayToolDefinition();
69+
definition.setName("test-tool");
70+
definition.setDescription("test tool");
71+
definition.setProtocol("http");
72+
definition.setRemoteServerConfig(new McpServerRemoteServiceConfig());
73+
74+
NacosMcpGatewayToolCallback callback = new NacosMcpGatewayToolCallback(definition);
75+
76+
ObjectNode responseTemplate = objectMapper.createObjectNode();
77+
responseTemplate.put("body", "{{.}}");
78+
79+
JsonNode templateNode = responseTemplate;
80+
81+
Method processResponse = NacosMcpGatewayToolCallback.class
82+
.getDeclaredMethod("processResponse", String.class, JsonNode.class, Map.class);
83+
processResponse.setAccessible(true);
84+
85+
String response = "20.5";
86+
String result = (String) processResponse.invoke(callback, response, templateNode, Collections.emptyMap());
87+
88+
assertEquals(response, result);
89+
}
90+
91+
}

0 commit comments

Comments
 (0)