Skip to content

Commit 7076c84

Browse files
author
AgentScope Developer
committed
feat(mcp): Add native MCP server implementation with calculator tool example and docs
1 parent c3f302c commit 7076c84

48 files changed

Lines changed: 4046 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agentscope-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@
8484
<artifactId>jackson-datatype-jsr310</artifactId>
8585
</dependency>
8686

87+
<!-- Jackson Java 8 Optional support -->
88+
<dependency>
89+
<groupId>com.fasterxml.jackson.datatype</groupId>
90+
<artifactId>jackson-datatype-jdk8</artifactId>
91+
</dependency>
92+
8793
<!-- SLF4J API -->
8894
<dependency>
8995
<groupId>org.slf4j</groupId>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024-2026 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+
* 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+
17+
package io.agentscope.core.mcp.handler;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import io.agentscope.core.mcp.message.JsonRpcError;
21+
import io.agentscope.core.mcp.message.JsonRpcMessage;
22+
import io.agentscope.core.mcp.message.JsonRpcNotification;
23+
import io.agentscope.core.mcp.message.JsonRpcRequest;
24+
import io.agentscope.core.mcp.message.JsonRpcResponse;
25+
import io.agentscope.core.mcp.transport.TransportException;
26+
27+
/**
28+
* Abstract base class for method handlers.
29+
*
30+
* <p>Provides common logic for handling requests and notifications.
31+
*/
32+
public abstract class AbstractMethodHandler implements MethodHandler {
33+
34+
protected ObjectMapper objectMapper = new ObjectMapper();
35+
36+
@Override
37+
public JsonRpcResponse handleMessage(JsonRpcMessage message) throws TransportException {
38+
try {
39+
if (message instanceof JsonRpcRequest) {
40+
JsonRpcRequest request = (JsonRpcRequest) message;
41+
Object result = handle(request.getParams());
42+
return new JsonRpcResponse(request.getId().orElse(null), result);
43+
} else if (message instanceof JsonRpcNotification) {
44+
JsonRpcNotification notification = (JsonRpcNotification) message;
45+
handle(notification.getParams());
46+
return null; // Notifications don't require responses
47+
}
48+
throw new TransportException("Unknown message type: " + message.getClass());
49+
} catch (Exception e) {
50+
if (message instanceof JsonRpcRequest) {
51+
JsonRpcRequest request = (JsonRpcRequest) message;
52+
JsonRpcError error =
53+
new JsonRpcError(
54+
JsonRpcError.ErrorCode.INTERNAL_ERROR,
55+
"Internal server error: " + e.getMessage(),
56+
e.toString());
57+
return new JsonRpcResponse(request.getId().orElse(null), error);
58+
}
59+
throw new TransportException("Error handling notification", e);
60+
}
61+
}
62+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2024-2026 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+
* 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+
17+
package io.agentscope.core.mcp.handler;
18+
19+
import io.agentscope.core.mcp.schema.CallToolResult;
20+
import io.agentscope.core.mcp.tool.Tool;
21+
import io.agentscope.core.mcp.tool.ToolManager;
22+
import java.util.ArrayList;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Optional;
27+
28+
/**
29+
* Handler for `tools/call` requests. Looks up a registered server-side Tool and executes it.
30+
*/
31+
public class CallToolHandler extends AbstractMethodHandler {
32+
33+
private final ToolManager toolManager;
34+
35+
public CallToolHandler(ToolManager toolManager) {
36+
this.toolManager = toolManager;
37+
}
38+
39+
@Override
40+
public String getMethod() {
41+
return "tools/call";
42+
}
43+
44+
@SuppressWarnings("unchecked")
45+
@Override
46+
public Object handle(Object params) throws Exception {
47+
if (!(params instanceof Map)) {
48+
throw new IllegalArgumentException("Invalid params for tools/call");
49+
}
50+
Map<String, Object> map = (Map<String, Object>) params;
51+
String name = (String) map.get("name");
52+
Object arguments = map.get("arguments");
53+
54+
if (name == null || name.isBlank()) {
55+
throw new IllegalArgumentException("Tool name is required");
56+
}
57+
58+
Optional<Tool> toolOpt = toolManager.get(name);
59+
if (toolOpt.isEmpty()) {
60+
throw new IllegalArgumentException("Tool not found: " + name);
61+
}
62+
63+
Tool tool = toolOpt.get();
64+
Object result = tool.execute(arguments);
65+
66+
// Normalize result into a content block list. Always wrap in a text block.
67+
Map<String, Object> block = new HashMap<>();
68+
block.put("type", "text");
69+
String text;
70+
if (result instanceof String) {
71+
text = (String) result;
72+
} else {
73+
text = objectMapper.writeValueAsString(result == null ? "" : result);
74+
}
75+
block.put("text", text);
76+
List<Object> content = new ArrayList<>();
77+
content.add(block);
78+
79+
return new CallToolResult(content, Optional.empty());
80+
}
81+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2024-2026 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+
* 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+
17+
package io.agentscope.core.mcp.handler;
18+
19+
import java.util.Map;
20+
import java.util.Optional;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
23+
/**
24+
* Registry for MCP method handlers.
25+
*
26+
* <p>Stores and retrieves handlers by method name.
27+
*/
28+
public class HandlerRegistry {
29+
30+
private final Map<String, MethodHandler> handlers = new ConcurrentHashMap<>();
31+
32+
/**
33+
* Register a handler for a method.
34+
*
35+
* @param method the method name
36+
* @param handler the handler
37+
*/
38+
public void register(String method, MethodHandler handler) {
39+
handlers.put(method, handler);
40+
}
41+
42+
/**
43+
* Get a handler for a method.
44+
*
45+
* @param method the method name
46+
* @return the handler, or empty if not found
47+
*/
48+
public Optional<MethodHandler> get(String method) {
49+
return Optional.ofNullable(handlers.get(method));
50+
}
51+
52+
/**
53+
* Check if a handler is registered for a method.
54+
*
55+
* @param method the method name
56+
* @return true if handler exists
57+
*/
58+
public boolean has(String method) {
59+
return handlers.containsKey(method);
60+
}
61+
62+
/**
63+
* Unregister a handler for a method.
64+
*
65+
* @param method the method name
66+
*/
67+
public void unregister(String method) {
68+
handlers.remove(method);
69+
}
70+
71+
/**
72+
* Get all registered method names.
73+
*
74+
* @return set of method names
75+
*/
76+
public Map<String, MethodHandler> getAll() {
77+
return new ConcurrentHashMap<>(handlers);
78+
}
79+
80+
/**
81+
* Clear all handlers.
82+
*/
83+
public void clear() {
84+
handlers.clear();
85+
}
86+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2024-2026 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+
* 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+
17+
package io.agentscope.core.mcp.handler;
18+
19+
import io.agentscope.core.mcp.schema.InitializeResult;
20+
import io.agentscope.core.mcp.tool.ToolManager;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
/**
25+
* Handler for `initialize` requests (handshake).
26+
*/
27+
public class InitializeHandler extends AbstractMethodHandler {
28+
29+
private final ToolManager toolManager;
30+
31+
public InitializeHandler(ToolManager toolManager) {
32+
this.toolManager = toolManager;
33+
}
34+
35+
@Override
36+
public String getMethod() {
37+
return "initialize";
38+
}
39+
40+
@Override
41+
public Object handle(Object params) throws Exception {
42+
Map<String, Object> capabilities = new HashMap<>();
43+
capabilities.put("tools", new HashMap<>());
44+
capabilities.put("protocol", "mcp");
45+
46+
Map<String, Object> serverInfo = new HashMap<>();
47+
serverInfo.put("name", "agentscope-core");
48+
serverInfo.put("version", "0.1.0");
49+
50+
return new InitializeResult(capabilities, "2.0", serverInfo);
51+
}
52+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2024-2026 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+
* 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+
17+
package io.agentscope.core.mcp.handler;
18+
19+
import io.agentscope.core.mcp.schema.ListToolsResult;
20+
import io.agentscope.core.mcp.tool.Tool;
21+
import io.agentscope.core.mcp.tool.ToolManager;
22+
import java.util.ArrayList;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Optional;
27+
28+
/**
29+
* Handler for `tools/list` requests. Returns metadata about registered tools.
30+
*/
31+
public class ListToolsHandler extends AbstractMethodHandler {
32+
33+
private final ToolManager toolManager;
34+
35+
public ListToolsHandler(ToolManager toolManager) {
36+
this.toolManager = toolManager;
37+
}
38+
39+
@Override
40+
public String getMethod() {
41+
return "tools/list";
42+
}
43+
44+
@Override
45+
public Object handle(Object params) throws Exception {
46+
List<Object> out = new ArrayList<>();
47+
for (Tool t : toolManager.list()) {
48+
Map<String, Object> m = new HashMap<>();
49+
m.put("name", t.getName());
50+
m.put("description", t.getDescription());
51+
m.put("inputSchema", t.getInputSchema());
52+
out.add(m);
53+
}
54+
55+
return new ListToolsResult(Optional.empty(), out);
56+
}
57+
}

0 commit comments

Comments
 (0)