diff --git a/mateclaw-server/src/main/java/vip/mate/tool/mcp/controller/McpServerController.java b/mateclaw-server/src/main/java/vip/mate/tool/mcp/controller/McpServerController.java index 08d47558..8ac4db2e 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/mcp/controller/McpServerController.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/mcp/controller/McpServerController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import vip.mate.common.result.R; import vip.mate.tool.mcp.model.McpServerEntity; +import vip.mate.tool.mcp.model.McpToolDescriptor; import vip.mate.tool.mcp.runtime.McpClientManager.ConnectionResult; import vip.mate.tool.mcp.service.McpServerService; @@ -77,6 +78,21 @@ public class McpServerController { return R.ok(result); } + /** + * RFC-03 Lane A3 — list the tools surfaced by an MCP server (QwenPaw #2495). + * + *
Reads from the in-memory cache populated on connect/refresh, so the
+ * call is non-blocking and safe to poll from the admin UI. Returns an
+ * empty list when the server is configured but disconnected, in error
+ * state, or has no tools — never an error response in that case.
+ * 404 is reserved for "the server id doesn't exist".
+ */
+ @Operation(summary = "列出 MCP Server 已发现的工具")
+ @GetMapping("/{id}/tools")
+ public R Mirrors the discovered subset of {@code io.modelcontextprotocol.spec.McpSchema.Tool}
+ * that the UI actually needs (name, description, params schema). Stays in
+ * {@code vip.mate.tool.mcp.model} so the public API doesn't leak the
+ * mcp-sdk transport types — those have evolved across SDK releases and
+ * we don't want every UI bump to track them.
+ *
+ * {@code inputSchema} is left as a raw {@link Object} so Jackson
+ * serializes whatever JSON-Schema shape the server reported; consumers
+ * (UI, third-party clients) pass it straight through to JSON Schema
+ * renderers / validators.
+ *
+ * {@link JsonInclude}{@code .NON_NULL} on the type keeps the wire
+ * payload tight when servers omit optional fields (description is
+ * optional in the MCP spec).
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record McpToolDescriptor(
+ String name,
+ String description,
+ Object inputSchema
+) {
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java b/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java
index 416f541f..01e2934e 100644
--- a/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java
+++ b/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java
@@ -161,6 +161,31 @@ public class McpServerService {
return testConnection(entity);
}
+ /**
+ * RFC-03 Lane A3 — list the tools the given MCP server has surfaced
+ * to the runtime (fixes QwenPaw #2495).
+ *
+ * Reads from {@link McpClientManager#getServerTools(Long)} which
+ * already caches the {@code listTools()} response on connect/refresh,
+ * so this is a constant-time lookup with no network roundtrip. The
+ * returned list is empty when the server is disconnected, in error
+ * state, or simply has no tools — never throws on those paths so the
+ * UI can render "no tools yet" rather than an error.
+ *
+ * {@link #getById} is invoked first so a stale id (deleted server)
+ * still returns a 404 from the controller layer rather than silently
+ * "no tools".
+ */
+ public List> listTools(@PathVariable Long id) {
+ return R.ok(mcpServerService.listToolsByServer(id));
+ }
+
@Operation(summary = "刷新所有 MCP Server 连接")
@PostMapping("/refresh")
public R