增加 SuperAgent MCP 内嵌接口和配置文档
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package cn.nianxx.thhotel.integrations.mcp.superagent.control;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"mcp.enabled=true",
|
||||
"mcp.auth-token=test-mcp-token",
|
||||
"mcp.enable-submit-task-results=false",
|
||||
"mcp.max-body-bytes=12000"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class SuperAgentMcpControllerTest {
|
||||
|
||||
private static final String ENDPOINT = "/mcp";
|
||||
private static final String AUTHORIZATION = "Bearer test-mcp-token";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldRejectMcpRequestWithoutBearerToken() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-auth-001",
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.error.data.code").value("MCP_AUTH_INVALID"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnJsonRpcParseErrorWhenRequestBodyInvalid() throws Exception {
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content("{invalid-json"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.error.code").value(-32700))
|
||||
.andExpect(jsonPath("$.error.data.code").value("MCP_REQUEST_INVALID"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectMcpRequestWhenBodyLargerThanConfiguredLimit() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-body-too-large-001",
|
||||
"method": "tools/list",
|
||||
"params": {
|
||||
"padding": "%s"
|
||||
}
|
||||
}
|
||||
""".formatted("x".repeat(12_100));
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isPayloadTooLarge())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.error.data.code").value("MCP_REQUEST_BODY_TOO_LARGE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAcceptInitializedNotificationWithoutJsonRpcResponse() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized",
|
||||
"params": {}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isAccepted())
|
||||
.andExpect(content().string(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldListFiveSuperAgentMcpTools() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-tools-001",
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.id").value("mcp-tools-001"))
|
||||
.andExpect(jsonPath("$.result.tools.length()").value(5))
|
||||
.andExpect(jsonPath("$.result.tools[0].name").value("th_hotel_query_case_context"))
|
||||
.andExpect(jsonPath("$.result.tools[0].annotations.readOnlyHint").value(true))
|
||||
.andExpect(jsonPath("$.result.tools[3].name").value("th_hotel_list_message_conversation_messages"))
|
||||
.andExpect(jsonPath("$.result.tools[3].annotations.readOnlyHint").value(true))
|
||||
.andExpect(jsonPath("$.result.tools[4].name").value("th_hotel_submit_task_results"))
|
||||
.andExpect(jsonPath("$.result.tools[4].annotations.readOnlyHint").value(false))
|
||||
.andExpect(jsonPath("$.result.tools[4].annotations.destructiveHint").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallCaseContextToolThroughEmbeddedMcpEndpoint() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-call-case-context-001",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "th_hotel_query_case_context",
|
||||
"arguments": {
|
||||
"hotel_id": "HOTEL-TEST",
|
||||
"group_code": "GRP-MCP-NOT-FOUND"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.id").value("mcp-call-case-context-001"))
|
||||
.andExpect(jsonPath("$.result.isError").value(false))
|
||||
.andExpect(jsonPath("$.result.structuredContent.success").value(true))
|
||||
.andExpect(jsonPath("$.result.structuredContent.data.matched_order_records.length()").value(0))
|
||||
.andExpect(jsonPath("$.result.structuredContent.data.target_object_validation.status").value("none"))
|
||||
.andExpect(content().string(containsString("th_hotel_query_case_context")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSubmitTaskResultsToolWhenWriteToolDisabled() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-submit-disabled-001",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "th_hotel_submit_task_results",
|
||||
"arguments": {
|
||||
"hotel_id": "HOTEL-TEST",
|
||||
"source_message_id": "mail-mcp-disabled-001",
|
||||
"ai_task_results": []
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.id").value("mcp-submit-disabled-001"))
|
||||
.andExpect(jsonPath("$.result.isError").value(true))
|
||||
.andExpect(jsonPath("$.result.structuredContent.error.code").value("MCP_TOOL_DISABLED"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package cn.nianxx.thhotel.integrations.mcp.superagent.control;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"mcp.enabled=true",
|
||||
"mcp.auth-token=test-mcp-token",
|
||||
"mcp.enable-submit-task-results=true",
|
||||
"mcp.max-body-bytes=12000"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class SuperAgentMcpSubmitEnabledControllerTest {
|
||||
|
||||
private static final String ENDPOINT = "/mcp";
|
||||
private static final String AUTHORIZATION = "Bearer test-mcp-token";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldDelegateSubmitTaskResultsToolWhenWriteToolEnabled() throws Exception {
|
||||
String body = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "mcp-submit-enabled-001",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "th_hotel_submit_task_results",
|
||||
"arguments": {
|
||||
"hotel_id": "HOTEL-TEST",
|
||||
"source_message_id": "mail-mcp-enabled-missing-001",
|
||||
"ai_task_results": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"catalog_code": "S01",
|
||||
"skill_id": "S01_new_booking_skill",
|
||||
"result_type": "normal_task",
|
||||
"task_type": "NEW_BOOKING"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", AUTHORIZATION)
|
||||
.content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.jsonrpc").value("2.0"))
|
||||
.andExpect(jsonPath("$.id").value("mcp-submit-enabled-001"))
|
||||
.andExpect(jsonPath("$.result.isError").value(true))
|
||||
.andExpect(jsonPath("$.result.structuredContent.error.code").value("SOURCE_MESSAGE_NOT_FOUND"))
|
||||
.andExpect(content().string(not(containsString("MCP_TOOL_DISABLED"))));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user