Skip to content

Commit ec1472e

Browse files
authored
Add MCP server (#176)
1 parent 598f83c commit ec1472e

File tree

13 files changed

+247
-20
lines changed

13 files changed

+247
-20
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ MERCURE_JWT_SECRET=dev-@1123581321-killer-mercure-secret
5555
###< symfony/mercure-bundle ###
5656

5757
###> symfony/expo-notifier ###
58-
EXPO_DSN=expo://TOKEN@default
58+
EXPO_DSN=expo://TOKEN@default
5959
###< symfony/expo-notifier ###
6060

6161
###> sentry/sentry-symfony ###

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ build
3333
###> phpstan/phpstan ###
3434
phpstan.neon
3535
###< phpstan/phpstan ###
36+
37+
/deploy-instruction.md

compose.override.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ services:
3030
###> doctrine/doctrine-bundle ###
3131
database:
3232
ports:
33-
- "5432"
33+
- "5432:5432"
3434
###< doctrine/doctrine-bundle ###

composer.lock

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/packages/mcp.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
mcp:
2+
app: 'killer-party-app' # Application name to be exposed to clients
3+
version: '1.0.0' # Application version to be exposed to clients
4+
pagination_limit: 50 # Maximum number of items returned per list request (default: 50)
5+
instructions: | # Instructions describing server purpose and usage context (for LLMs)
6+
This server provides information about the Killer Party game data.
7+
8+
Use when being ask about game data like players, rooms created, missions, etc.
9+
This server provides stats and figures about the game.
10+
client_transports:
11+
stdio: true
12+
http: true # Enable HTTP transport via controller
13+
14+
# HTTP transport configuration (optional)
15+
http:
16+
path: /_mcp # HTTP endpoint path (default: /_mcp)
17+
session:
18+
store: file # Session store type: 'file' or 'memory' (default: file)
19+
directory: '%kernel.cache_dir%/mcp-sessions' # Directory for file store (default: cache_dir/mcp-sessions)
20+
ttl: 3600 # Session TTL in seconds (default: 3600)

config/routes.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
controllers:
22
resource: ../src/Api/Controller/
33
type: attribute
4+
5+
_mcp:
6+
resource: .
7+
type: mcp

src/Api/Controller/PlayerController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,12 @@ public function killTarget(Player $player): JsonResponse
215215

216216
$this->persistenceAdapter->flush();
217217

218+
$playerTarget = $player->getTarget();
219+
$this->hub->publish(
220+
sprintf('player/%s', $playerTarget?->getId()),
221+
$this->serializer->serialize((object) $playerTarget, [AbstractNormalizer::GROUPS => 'publish-mercure']),
222+
);
223+
218224
return $this->json(null, Response::HTTP_OK);
219225
}
220226
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\UseCase\Room;
6+
7+
use App\Domain\Player\Enum\PlayerStatus;
8+
use App\Domain\Room\Entity\Room;
9+
use App\Domain\Room\RoomRepository;
10+
11+
final readonly class RoomReportUseCase
12+
{
13+
public function __construct(private RoomRepository $roomRepository)
14+
{
15+
}
16+
17+
/**
18+
* Generate a comprehensive report for a room.
19+
*
20+
* @return array{
21+
* roomId: string,
22+
* roomName: string,
23+
* status: string,
24+
* isInGame: bool,
25+
* totalPlayers: int,
26+
* alivePlayers: int,
27+
* totalMissions: int,
28+
* createdAt: string|null,
29+
* dateEnd: string|null,
30+
* isGameMastered: bool,
31+
* hasWinner: bool,
32+
* winnerName: string|null
33+
* }|array{error: string, roomId: string}
34+
*/
35+
public function execute(string $roomId): array
36+
{
37+
$room = $this->roomRepository->find($roomId);
38+
39+
if (!$room instanceof Room) {
40+
return [
41+
'error' => 'Room not found',
42+
'roomId' => $roomId,
43+
];
44+
}
45+
46+
$players = $room->getPlayers();
47+
$alivePlayers = array_filter(
48+
$players->toArray(),
49+
static fn ($player) => $player->getStatus() === PlayerStatus::ALIVE,
50+
);
51+
52+
return [
53+
'roomId' => $room->getId(),
54+
'roomName' => $room->getName(),
55+
'status' => $room->getStatus(),
56+
'isInGame' => $room->getStatus() === Room::IN_GAME,
57+
'totalPlayers' => $players->count(),
58+
'alivePlayers' => count($alivePlayers),
59+
'totalMissions' => $room->getMissions()->count(),
60+
'createdAt' => $room->getCreatedAt()?->format('Y-m-d H:i:s'),
61+
'dateEnd' => $room->getDateEnd()?->format('Y-m-d H:i:s'),
62+
'isGameMastered' => $room->isGameMastered(),
63+
'hasWinner' => $room->getWinner() !== null,
64+
'winnerName' => $room->getWinner()?->getName(),
65+
];
66+
}
67+
}

src/Domain/Room/RoomRepository.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
/** @extends BaseRepository<Room> */
1111
interface RoomRepository extends BaseRepository
1212
{
13-
public function getRoomByIdOrCode(mixed $identifier): mixed;
14-
1513
public function getEmptyRooms(): iterable;
1614

1715
public function countInGameRooms(): int;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Infrastructure\Mcp\Prompts;
6+
7+
use Mcp\Capability\Attribute\McpPrompt;
8+
9+
#[McpPrompt(
10+
name: 'room_report',
11+
description: 'Generate a comprehensive and nicely formatted report about a game room',
12+
)]
13+
readonly class ReportPrompt
14+
{
15+
/**
16+
* Generate a prompt to build a room report.
17+
*
18+
* @param string $roomId The ID or code of the room to report on
19+
*/
20+
public function __invoke(string $roomId): array
21+
{
22+
return [
23+
'role' => 'user',
24+
// phpcs:ignore Squiz.Arrays.ArrayDeclaration.NoComma
25+
'content' => <<<PROMPT
26+
You are tasked with generating a comprehensive report about a game room in the Killer game application.
27+
28+
**Instructions:**
29+
30+
1. Use the `room_report` tool with the room ID: {$roomId}
31+
2. The tool will return the following information:
32+
- Room ID and name
33+
- Game status (IN_GAME, PENDING, or ENDED)
34+
- Total number of players
35+
- Number of alive players
36+
- Total number of missions
37+
- Creation date and end date
38+
- Whether the game is game-mastered
39+
- Winner information (if any)
40+
41+
3. Present the information in a clear, well-formatted manner following this structure:
42+
43+
**Room Report Format:**
44+
45+
```
46+
# Game Room Report: [Room Name]
47+
48+
## General Information
49+
- **Room ID:** [ID]
50+
- **Room Name:** [Name]
51+
- **Created:** [Date]
52+
- **End Date:** [Date]
53+
- **Game Master Mode:** [Yes/No]
54+
55+
## Game Status
56+
- **Status:** [IN_GAME/PENDING/ENDED]
57+
- **Is Currently Playing:** [Yes/No]
58+
59+
## Players Overview
60+
- **Total Players:** [X]
61+
- **Alive Players:** [X]
62+
- **Eliminated Players:** [X]
63+
64+
## Missions
65+
- **Total Missions:** [X]
66+
67+
## Game Outcome
68+
- **Winner:** [Player name or "No winner yet"]
69+
```
70+
71+
4. Add contextual insights such as:
72+
- If the game is IN_GAME, mention the current state of competition
73+
- If many players have been eliminated, note the intensity of the game
74+
- If the game hasn't started (PENDING), mention it's waiting to begin
75+
- If the game has ENDED, highlight the winner
76+
77+
5. Use clear formatting with headers, bullet points, and emphasis where appropriate
78+
79+
**Now generate the report for room: {$roomId}**
80+
PROMPT,
81+
];
82+
}
83+
}

0 commit comments

Comments
 (0)