Skip to content

Commit c537280

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

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature;
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 RecruitCreatureMcp {
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 = "recruit_creature",
31+
description = "Recruit creatures from a dwelling to an army. This will move creatures from the dwelling's available pool to the specified army, consuming resources in the process. The playerId should be provided via MCP client context (similar to REST API headers)."
32+
)
33+
public CompletableFuture<Map<String, Object>> recruitCreature(
34+
@ToolParam(description = "Unique identifier for the game instance") String gameId,
35+
@ToolParam(description = "Unique identifier for the dwelling to recruit from") String dwellingId,
36+
@ToolParam(description = "Type of creature to recruit (e.g., 'ANGEL', 'DRAGON', 'GRIFFIN')") String creatureId,
37+
@ToolParam(description = "Unique identifier for the army to recruit creatures to") String armyId,
38+
@ToolParam(description = "Number of creatures to recruit (positive integer)") Integer quantity,
39+
@ToolParam(description = "Expected cost of recruitment as resource map (e.g., {'gold': 1000, 'wood': 10})") Map<String, Integer> expectedCost,
40+
ToolContext toolContext
41+
) {
42+
var playerId = playerId(toolContext);
43+
44+
var command = RecruitCreature.command(dwellingId, creatureId, armyId, quantity, expectedCost);
45+
46+
return commandGateway.send(command, GameMetaData.with(gameId, playerId))
47+
.thenApply(_ -> success(dwellingId, creatureId, armyId, quantity, playerId))
48+
.exceptionally(throwable -> failure(dwellingId, throwable));
49+
}
50+
51+
@NotNull
52+
private static Map<String, Object> success(String dwellingId, String creatureId, String armyId, Integer quantity, String playerId) {
53+
return Map.of(
54+
"success", true,
55+
"dwellingId", dwellingId,
56+
"creatureId", creatureId,
57+
"armyId", armyId,
58+
"quantity", quantity,
59+
"playerId", playerId,
60+
"message", "Creatures recruited successfully"
61+
);
62+
}
63+
64+
@NotNull
65+
private static Map<String, Object> failure(String dwellingId, Throwable throwable) {
66+
return Map.of(
67+
"success", false,
68+
"error", throwable != null ? throwable.getMessage() : "Unknown error",
69+
"dwellingId", dwellingId,
70+
"message", "Failed to recruit creatures: " + (throwable != null ? throwable.getMessage() : "Unknown error")
71+
);
72+
}
73+
74+
private String playerId(ToolContext toolContext) {
75+
return (String) toolContext.getContext().getOrDefault("playerId", "default-player-id");
76+
}
77+
78+
}
79+
80+
@Configuration
81+
static class Config {
82+
83+
@Bean
84+
ToolCallbackProvider recruitCreatureTool(RecruitCreatureMcp.Tools sliceTools) {
85+
return MethodToolCallbackProvider.builder().toolObjects(sliceTools).build();
86+
}
87+
88+
}
89+
}

0 commit comments

Comments
 (0)