Skip to content

Commit fa21b6f

Browse files
✨ feat(mcp): IncreaseAvailableCreatures write slice | add MCP server tool
1 parent d86408d commit fa21b6f

2 files changed

Lines changed: 113 additions & 16 deletions

File tree

src/main/java/com/dddheroes/heroesofddd/creaturerecruitment/write/builddwelling/ModelContextProtocol.java renamed to src/main/java/com/dddheroes/heroesofddd/creaturerecruitment/write/builddwelling/BuildDwellingMcp.java

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.dddheroes.heroesofddd.shared.application.GameMetaData;
44
import org.axonframework.commandhandling.gateway.CommandGateway;
5+
import org.jetbrains.annotations.NotNull;
56
import org.springframework.ai.chat.model.ToolContext;
67
import org.springframework.ai.tool.ToolCallbackProvider;
78
import org.springframework.ai.tool.annotation.Tool;
@@ -14,7 +15,7 @@
1415
import java.util.Map;
1516
import java.util.concurrent.CompletableFuture;
1617

17-
public class ModelContextProtocol {
18+
public class BuildDwellingMcp {
1819

1920
@Component
2021
static class Tools {
@@ -41,20 +42,30 @@ public CompletableFuture<Map<String, Object>> buildDwelling(
4142
var command = BuildDwelling.command(dwellingId, creatureId, costPerTroop);
4243

4344
return commandGateway.send(command, GameMetaData.with(gameId, playerId))
44-
.thenApply(_ -> Map.of(
45-
"success", true,
46-
"dwellingId", dwellingId,
47-
"creatureId", creatureId,
48-
"costPerTroop", costPerTroop,
49-
"playerId", playerId,
50-
"message", "Dwelling built successfully"
51-
))
52-
.exceptionally(throwable -> Map.of(
53-
"success", false,
54-
"error", throwable != null ? throwable.getMessage() : "Unknown error",
55-
"dwellingId", dwellingId,
56-
"message", "Failed to build dwelling: " + (throwable != null ? throwable.getMessage() : "Unknown error")
57-
));
45+
.thenApply(_ -> getSuccess(dwellingId, creatureId, costPerTroop, playerId))
46+
.exceptionally(throwable -> failure(dwellingId, throwable));
47+
}
48+
49+
@NotNull
50+
private static Map<String, Object> failure(String dwellingId, Throwable throwable) {
51+
return Map.of(
52+
"success", false,
53+
"error", throwable != null ? throwable.getMessage() : "Unknown error",
54+
"dwellingId", dwellingId,
55+
"message", "Failed to build dwelling: " + (throwable != null ? throwable.getMessage() : "Unknown error")
56+
);
57+
}
58+
59+
@NotNull
60+
private static Map<String, Object> getSuccess(String dwellingId, String creatureId, Map<String, Integer> costPerTroop, String playerId) {
61+
return Map.of(
62+
"success", true,
63+
"dwellingId", dwellingId,
64+
"creatureId", creatureId,
65+
"costPerTroop", costPerTroop,
66+
"playerId", playerId,
67+
"message", "Dwelling built successfully"
68+
);
5869
}
5970

6071
private String playerId(ToolContext toolContext) {
@@ -67,7 +78,7 @@ private String playerId(ToolContext toolContext) {
6778
static class Config {
6879

6980
@Bean
70-
ToolCallbackProvider buildDwellingTool(ModelContextProtocol.Tools sliceTools) {
81+
ToolCallbackProvider buildDwellingTool(BuildDwellingMcp.Tools sliceTools) {
7182
return MethodToolCallbackProvider.builder().toolObjects(sliceTools).build();
7283
}
7384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures;
2+
3+
import com.dddheroes.heroesofddd.shared.application.GameMetaData;
4+
import org.axonframework.commandhandling.gateway.CommandGateway;
5+
import org.jetbrains.annotations.NotNull;
6+
import org.springframework.ai.chat.model.ToolContext;
7+
import org.springframework.ai.tool.ToolCallbackProvider;
8+
import org.springframework.ai.tool.annotation.Tool;
9+
import org.springframework.ai.tool.annotation.ToolParam;
10+
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
11+
import org.springframework.context.annotation.Bean;
12+
import org.springframework.context.annotation.Configuration;
13+
import org.springframework.stereotype.Component;
14+
15+
import java.util.Map;
16+
import java.util.concurrent.CompletableFuture;
17+
18+
public class IncreaseAvailableCreaturesMcp {
19+
20+
@Component
21+
static class Tools {
22+
23+
private final CommandGateway commandGateway;
24+
25+
public Tools(CommandGateway commandGateway) {
26+
this.commandGateway = commandGateway;
27+
}
28+
29+
@Tool(
30+
name = "increase_available_creatures",
31+
description = "Increase the number of available creatures in a dwelling. This affects how many creatures can be recruited from this dwelling. The playerId should be provided via MCP client context (similar to REST API headers)."
32+
)
33+
public CompletableFuture<Map<String, Object>> increaseAvailableCreatures(
34+
@ToolParam(description = "Unique identifier for the game instance") String gameId,
35+
@ToolParam(description = "Unique identifier for the dwelling to modify") String dwellingId,
36+
@ToolParam(description = "Type of creature to increase availability for (e.g., 'ANGEL', 'DRAGON', 'GRIFFIN')") String creatureId,
37+
@ToolParam(description = "Number of creatures to increase availability by (positive integer)") Integer increaseBy,
38+
ToolContext toolContext
39+
) {
40+
var playerId = playerId(toolContext);
41+
42+
var command = IncreaseAvailableCreatures.command(dwellingId, creatureId, increaseBy);
43+
44+
return commandGateway.send(command, GameMetaData.with(gameId, playerId))
45+
.thenApply(_ -> success(dwellingId, creatureId, increaseBy, playerId))
46+
.exceptionally(throwable -> failure(dwellingId, throwable));
47+
}
48+
49+
@NotNull
50+
private static Map<String, Object> success(String dwellingId, String creatureId, Integer increaseBy, String playerId) {
51+
return Map.of(
52+
"success", true,
53+
"dwellingId", dwellingId,
54+
"creatureId", creatureId,
55+
"increaseBy", increaseBy,
56+
"playerId", playerId,
57+
"message", "Available creatures increased successfully"
58+
);
59+
}
60+
61+
@NotNull
62+
private static Map<String, Object> failure(String dwellingId, Throwable throwable) {
63+
return Map.of(
64+
"success", false,
65+
"error", throwable != null ? throwable.getMessage() : "Unknown error",
66+
"dwellingId", dwellingId,
67+
"message", "Failed to increase available creatures: " + (throwable != null ? throwable.getMessage() : "Unknown error")
68+
);
69+
}
70+
71+
private String playerId(ToolContext toolContext) {
72+
return (String) toolContext.getContext().getOrDefault("playerId", "default-player-id");
73+
}
74+
75+
}
76+
77+
@Configuration
78+
static class Config {
79+
80+
@Bean
81+
ToolCallbackProvider increaseAvailableCreaturesTool(IncreaseAvailableCreaturesMcp.Tools sliceTools) {
82+
return MethodToolCallbackProvider.builder().toolObjects(sliceTools).build();
83+
}
84+
85+
}
86+
}

0 commit comments

Comments
 (0)