Aias00 commented on code in PR #7424:
URL: https://github.com/apache/shenyu/pull/7424#discussion_r4214004147


##########
shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-agent-gateway/src/main/java/org/apache/shenyu/springboot/starter/plugin/agent/gateway/AgentGatewayPluginConfiguration.java:
##########
@@ -35,11 +42,15 @@ public class AgentGatewayPluginConfiguration {
     /**
      * Create the agent gateway plugin.
      *
+     * @param providers explicitly registered server-side tools
+     * @param resolvers trusted security adapter, missing adapters deny access
      * @return the plugin
      */
     @Bean
-    public ShenyuPlugin agentGatewayPlugin() {
-        return new AgentGatewayPlugin();
+    public ShenyuPlugin agentGatewayPlugin(final 
ObjectProvider<AgentToolProvider> providers, final 
ObjectProvider<AgentMcpSecurityResolver> resolvers) {
+        AgentToolRegistry registry = new 
AgentToolRegistry(providers.orderedStream().toList());
+        AgentMcpSecurityResolver resolver = resolvers.getIfAvailable(() -> 
exchange -> Mono.empty());
+        return new AgentGatewayPlugin(new AgentMcpHttpHandler(new 
AgentMcpDispatcher(registry, "shenyu-agent-gateway", "2.7.2-SNAPSHOT"), 
resolver));

Review Comment:
   [NIT] The server version "2.7.2-SNAPSHOT" is hardcoded here, and again in 
AgentGatewayPlugin.java:54 and AgentMcpDispatcherTest.java:3812. This value is 
advertised as serverInfo.version in server/discover and will silently drift 
from the real release version after a cut. Consider deriving it from the 
project version (a build-time constant or ProjectVersion.getVersion() if 
available) or a single named constant, so discovery does not lie post-release.



##########
shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/protocol/AgentMcpDispatcher.java:
##########
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.agent.gateway.protocol;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.shenyu.plugin.agent.gateway.tool.AgentToolArgumentException;
+import 
org.apache.shenyu.plugin.agent.gateway.tool.AgentToolCapabilityException;
+import org.apache.shenyu.plugin.agent.gateway.tool.AgentToolDefinition;
+import org.apache.shenyu.plugin.agent.gateway.tool.AgentToolExecutionException;
+import org.apache.shenyu.plugin.agent.gateway.tool.AgentToolInvocation;
+import org.apache.shenyu.plugin.agent.gateway.tool.AgentToolRegistry;
+import reactor.core.publisher.Mono;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * Request-local discover/list/call dispatch, without HTTP or security-source 
adaptation.
+ */
+public final class AgentMcpDispatcher {
+
+    private final AgentToolRegistry registry;
+
+    private final String serverName;
+
+    private final String serverVersion;
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    public AgentMcpDispatcher(final AgentToolRegistry registry, final String 
serverName, final String serverVersion) {
+        this.registry = Objects.requireNonNull(registry, "registry");
+        if (Objects.requireNonNull(serverName, "serverName").isBlank() || 
Objects.requireNonNull(serverVersion, "serverVersion").isBlank()) {
+            throw new IllegalArgumentException("Server identity must not be 
blank");
+        }
+        this.serverName = serverName;
+        this.serverVersion = serverVersion;
+    }
+
+    /**
+     * Dispatch lazily with fresh trusted context for each subscription, 
without retry.
+     * The factory must obtain identity and grants from server-side security, 
not request metadata.
+     *
+     * @param request a parsed, validated request
+     * @param contextFactory request-local trusted context factory
+     * @return one JSON-RPC response or a typed protocol failure, linked to 
provider cancellation
+     */
+    public Mono<ObjectNode> dispatch(final AgentMcpRequest request, final 
Supplier<AgentMcpExecutionContext> contextFactory) {
+        Objects.requireNonNull(request, "request");
+        Objects.requireNonNull(contextFactory, "contextFactory");
+        return Mono.defer(() -> {
+            AgentMcpExecutionContext context = 
Objects.requireNonNull(contextFactory.get(), "execution context");
+            if 
(!registry.listDefinitions(context.getRuleTools()).keySet().containsAll(context.getRuleTools()))
 {
+                return Mono.error(failure(request, 500, -32603, "Configured 
tool is not registered"));
+            }
+            return route(request, context).contextWrite(reactorContext -> 
reactorContext.put(AgentMcpExecutionContext.class, context));
+        }).onErrorMap(error -> {
+            if (error instanceof AgentMcpProtocolException) {
+                return error;
+            }
+            AgentMcpProtocolException failure = failure(request, 500, -32603, 
"Internal error");
+            failure.initCause(error);
+            return failure;
+        });
+    }
+
+    private Mono<ObjectNode> route(final AgentMcpRequest request, final 
AgentMcpExecutionContext context) {
+        switch (request.getMethod()) {
+            case "server/discover":
+                ObjectNode discovery = complete();
+                cacheMetadata(discovery);
+                
discovery.putArray("supportedVersions").add(AgentMcpRequestParser.VERSION);
+                discovery.putObject("capabilities").putObject("tools");
+                return Mono.fromSupplier(() -> response(request, 
discovery.deepCopy()));
+            case "tools/list":
+                return Mono.fromSupplier(() -> list(request, context));
+            case "tools/call":
+                return call(request, context);
+            default:
+                return Mono.error(failure(request, 404, -32601, "Method not 
found"));
+        }
+    }
+
+    private ObjectNode list(final AgentMcpRequest request, final 
AgentMcpExecutionContext context) {
+        if (request.getParams().has("cursor")) {
+            throw failure(request, 400, -32602, "Cursor is not supported by 
this unpaginated tool list");
+        }
+        ObjectNode result = complete();
+        cacheMetadata(result);
+        ArrayNode tools = result.putArray("tools");
+        registry.listDefinitions(context.getAllowedTools()).forEach((name, 
definition) -> {
+            ObjectNode tool = tools.addObject().put("name", 
name).put("description", definition.getDescription());
+            tool.set("inputSchema", toTree(definition.getInputSchema()));
+        });
+        return response(request, result);
+    }
+
+    private Mono<ObjectNode> call(final AgentMcpRequest request, final 
AgentMcpExecutionContext context) {
+        ObjectNode params = request.getParams();
+        String name = params.get("name").textValue();
+        AgentToolDefinition definition = 
registry.listDefinitions(context.getAllowedTools()).get(name);
+        if (Objects.isNull(definition)) {
+            return Mono.error(failure(request, 403, -32602, "Tool is not 
available"));
+        }
+        JsonObject arguments = params.has("arguments") ? 
JsonParser.parseString(params.get("arguments").toString()).getAsJsonObject() : 
new JsonObject();
+        JsonObject capabilities = 
JsonParser.parseString(params.path("_meta").path("io.modelcontextprotocol/clientCapabilities").toString()).getAsJsonObject();

Review Comment:
   [NIT] 
params.path("_meta").path("io.modelcontextprotocol/clientCapabilities") was 
already validated as an object by AgentMcpRequestParser (non-object 
clientCapabilities is rejected with -32602). Re-serializing with .toString() 
and re-parsing here is redundant on the hot path. You can reuse 
request.getParams().getAsJsonObject("_meta").getAsJsonObject("io.modelcontextprotocol/clientCapabilities")
 directly, which is already an independent JsonObject copy from AgentMcpRequest.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to