Skip to content

Commit 31c3d11

Browse files
author
klapaudius
committed
Add optional Docker setup for development environment
Introduced a Docker setup including configurations for Nginx, PHP-FPM, and Redis to streamline development. Updated the README and added detailed setup instructions in Development Guidelines. Included relevant Docker configuration files and examples.
1 parent 7aca12e commit 31c3d11

File tree

6 files changed

+354
-2
lines changed

6 files changed

+354
-2
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ klp_mcp_server:
9494
- **Streaming Endpoint for MCP Clients**: `GET /{default_path}/sse`
9595
- **Request Submission Endpoint**: `POST /{default_path}/messages`
9696

97+
### Docker Setup (Optional)
98+
99+
The project includes a Docker setup that can be used for development. The Docker setup includes Nginx, PHP-FPM with Redis extension, and Redis server.
100+
101+
For detailed instructions on how to set up and use the Docker containers, please refer to the [Development Guidelines](docs/guidelines.md#docker-setup).
102+
97103
## Strongly Recommended
98104
Enhance your application's security by implementing OAuth2 Authentication. You can use the [klapaudius/oauth-server-bundle](https://github.com/klapaudius/FOSOAuthServerBundle) or any other compatible OAuth2 solution.
99105

@@ -160,15 +166,15 @@ npx @modelcontextprotocol/inspector node build/index.js
160166
This will typically open a web interface at `localhost:6274`. To test your MCP server:
161167

162168
1. **Warning**: `symfony server:start` CANNOT be used with this package because it cannot handle multiple PHP connections simultaneously. Since MCP SSE requires processing multiple connections concurrently, you must use one of these alternatives:
163-
169+
164170
- Nginx + PHP-FPM
165171
- Apache + PHP-FPM
166172
- Custom Docker setup
167173
- Any web server that properly supports SSE streaming
168174
2. In the Inspector interface, enter your Symfony server's MCP SSE URL (e.g., `http://localhost:8000/mcp/sse`)
169175
3. Connect and explore available tools visually
170176

171-
The SSE URL follows the pattern: `http://[your-web-server]/[default_path]/sse` where `default_path` is defined in your `config/packages/klp_mcp_server.yaml` file.
177+
The SSE URL follows the pattern: `http(s)://[your-web-server]/[default_path]/sse` where `default_path` is defined in your `config/packages/klp_mcp_server.yaml` file.
172178

173179
## Advanced Features
174180

docker/.env

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
COMPOSE_PROJECT_NAME=mcp
2+
3+
NGINX_IP=xxx.xxx.xxx.xxx
4+
NGINX_EXPOSED_PORT=xxxx
5+
6+
PHP_IP=xxx.xxx.xxx.xxx
7+
8+
REDIS_IP=xxx.xxx.xxx.xxx
9+
10+
NETWORK_SUBNET=xxx.xxx.xxx.xxx/24

docker/docker-compose.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
services:
2+
web:
3+
image: nginx:latest
4+
ports:
5+
- '${NGINX_EXPOSED_PORT}:80'
6+
networks:
7+
mcp_net:
8+
ipv4_address: ${NGINX_IP}
9+
10+
volumes:
11+
- ..:/var/www
12+
- ./webserver/default.conf:/etc/nginx/conf.d/default.conf
13+
links:
14+
- php-fpm
15+
16+
php-fpm:
17+
build:
18+
context: ./php-fpm
19+
dockerfile: Dockerfile
20+
networks:
21+
mcp_net:
22+
ipv4_address: ${PHP_IP}
23+
extra_hosts:
24+
- "host.docker.internal:host-gateway"
25+
links:
26+
- redis
27+
volumes:
28+
- ..:/var/www
29+
30+
redis:
31+
image: "redis:alpine"
32+
command: redis-server
33+
volumes:
34+
- ./redis/data:/var/lib/redis
35+
networks:
36+
mcp_net:
37+
ipv4_address: ${REDIS_IP}
38+
39+
networks:
40+
mcp_net:
41+
driver: bridge
42+
ipam:
43+
driver: default
44+
config:
45+
- subnet: ${NETWORK_SUBNET}

docker/php-fpm/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM php:8-fpm
2+
3+
RUN pecl install redis \
4+
&& docker-php-ext-enable redis

docker/webserver/default.conf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
server {
2+
index index.php index.html;
3+
server_name phpfpm.local;
4+
error_log /var/log/error.log;
5+
access_log /var/log/access.log;
6+
root /var/www/public;
7+
proxy_connect_timeout 5s;
8+
proxy_read_timeout 5s;
9+
proxy_send_timeout 5s;
10+
11+
location / {
12+
try_files $uri $uri/ /index.php?$query_string;
13+
}
14+
15+
location ~ \.php$ {
16+
try_files $uri =404;
17+
fastcgi_split_path_info ^(.+\.php)(/.+)$;
18+
fastcgi_pass php-fpm:9000;
19+
fastcgi_index index.php;
20+
include fastcgi_params;
21+
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
22+
fastcgi_param PATH_INFO $fastcgi_path_info;
23+
}
24+
}

docs/guidelines.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Symfony MCP Server Development Guidelines
2+
3+
This document provides essential information for developers working on the Symfony MCP Server project.
4+
5+
## Build/Configuration Instructions
6+
7+
### Prerequisites
8+
- PHP 8.2 or higher
9+
- Symfony 7.0
10+
- Composer
11+
12+
### Installation
13+
14+
1. Clone the repository:
15+
```bash
16+
git clone https://github.com/klapaudius/symfony-mcp-server.git
17+
```
18+
19+
2. Create a new Symfony project (if you don't have one already):
20+
```bash
21+
composer create-project symfony/skeleton my-symfony-project
22+
cd my-symfony-project
23+
```
24+
25+
3. Add the cloned repository as a local repository in your Symfony project's composer.json:
26+
```json
27+
{
28+
"repositories": [
29+
{
30+
"type": "path",
31+
"url": "../symfony-mcp-server"
32+
}
33+
]
34+
}
35+
```
36+
Note: Adjust the path "../symfony-mcp-server" to match the relative path from your Symfony project to the cloned repository.
37+
38+
4. Require the package from the local repository:
39+
```bash
40+
composer require klapaudius/symfony-mcp-server:@dev
41+
```
42+
43+
5. Create the configuration file `config/packages/klp_mcp_server.yaml` with the following content:
44+
```yaml
45+
klp_mcp_server:
46+
enabled: true
47+
server:
48+
name: 'My MCP Server'
49+
version: '1.0.0'
50+
default_path: 'mcp'
51+
ping:
52+
enabled: true
53+
interval: 30
54+
server_provider: 'sse'
55+
sse_adapter: 'redis'
56+
adapters:
57+
redis:
58+
prefix: 'mcp_sse_'
59+
host: 'localhost' # Change as needed
60+
ttl: 100
61+
tools:
62+
- KLP\KlpMcpServer\Services\ToolService\Examples\HelloWorldTool
63+
- KLP\KlpMcpServer\Services\ToolService\Examples\VersionCheckTool
64+
```
65+
66+
6. Add routes in your `config/routes.yaml`:
67+
```yaml
68+
klp_mcp_server:
69+
resource: '@KlpMcpServerBundle/Resources/config/routes.php'
70+
type: php
71+
```
72+
73+
### Important Configuration Notes
74+
75+
- **Web Server**: This package cannot be used with `symfony server:start` as it requires processing multiple connections concurrently. Use Nginx + PHP-FPM, Apache + PHP-FPM, or a custom Docker setup instead.
76+
- **Redis Configuration**: Ensure your Redis server is properly configured and accessible at the host specified in the configuration.
77+
- **Security**: It's strongly recommended to implement OAuth2 Authentication for production use.
78+
79+
### Docker Setup
80+
81+
The project includes a Docker setup that can be used for development. The Docker setup includes:
82+
- Nginx web server
83+
- PHP-FPM with Redis extension
84+
- Redis server
85+
86+
To set up and use the Docker containers:
87+
88+
1. Navigate to the docker directory:
89+
```bash
90+
cd docker
91+
```
92+
93+
2. Configure the environment variables by creating a `.env.local` file:
94+
```bash
95+
# Example configuration
96+
COMPOSE_PROJECT_NAME=mcp
97+
98+
NGINX_IP=172.20.0.2
99+
NGINX_EXPOSED_PORT=8080
100+
101+
PHP_IP=172.20.0.3
102+
103+
REDIS_IP=172.20.0.4
104+
105+
NETWORK_SUBNET=172.20.0.0/24
106+
```
107+
Note: Adjust the IP addresses and port as needed for your environment.
108+
109+
3. Build and start the Docker containers:
110+
```bash
111+
docker-compose up -d
112+
```
113+
114+
4. Access the application in your browser at `http://localhost:8080` (or the port you specified in NGINX_EXPOSED_PORT).
115+
116+
5. To stop the containers:
117+
```bash
118+
docker-compose down
119+
```
120+
121+
6. Update your Symfony configuration to use the Redis server in the Docker container:
122+
```yaml
123+
# config/packages/klp_mcp_server.yaml
124+
klp_mcp_server:
125+
# ...
126+
adapters:
127+
redis:
128+
prefix: 'mcp_sse_'
129+
host: 'redis' # Use the service name from docker-compose.yml
130+
ttl: 100
131+
```
132+
133+
## Testing Information
134+
135+
### Running Tests
136+
137+
The project uses PHPUnit for testing. To run all tests:
138+
139+
```bash
140+
vendor/bin/phpunit
141+
```
142+
143+
To run a specific test file:
144+
145+
```bash
146+
vendor/bin/phpunit tests/path/to/TestFile.php
147+
```
148+
149+
To generate code coverage reports:
150+
151+
```bash
152+
vendor/bin/phpunit --coverage-html build/coverage
153+
```
154+
155+
### Adding New Tests
156+
157+
1. **Test Location**: Place tests in the `tests/` directory, mirroring the structure of the `src/` directory.
158+
2. **Naming Convention**: Name test classes with the suffix `Test` (e.g., `DataUtilTest.php`).
159+
3. **Test Size**: Use PHPUnit attributes to indicate test size:
160+
```php
161+
#[Small] // For unit tests
162+
#[Medium] // For integration tests
163+
#[Large] // For system tests
164+
```
165+
4. **Mocking**: Use PHPUnit's mocking capabilities for dependencies:
166+
```php
167+
$mockTransport = $this->createMock(SseTransportInterface::class);
168+
```
169+
170+
### Example Test
171+
172+
Here's a simple test for the `DataUtil` class:
173+
174+
```php
175+
<?php
176+
177+
namespace KLP\KlpMcpServer\Tests\Utils;
178+
179+
use KLP\KlpMcpServer\Data\Requests\NotificationData;
180+
use KLP\KlpMcpServer\Data\Requests\RequestData;
181+
use KLP\KlpMcpServer\Utils\DataUtil;
182+
use PHPUnit\Framework\Attributes\Small;
183+
use PHPUnit\Framework\TestCase;
184+
185+
#[Small]
186+
class DataUtilTest extends TestCase
187+
{
188+
public function test_make_request_data_creates_request_data_when_message_has_method_and_id(): void
189+
{
190+
$clientId = 'test_client';
191+
$message = [
192+
'jsonrpc' => '2.0',
193+
'method' => 'test.method',
194+
'id' => 1,
195+
'params' => ['param1' => 'value1']
196+
];
197+
198+
$result = DataUtil::makeRequestData($clientId, $message);
199+
200+
$this->assertInstanceOf(RequestData::class, $result);
201+
$this->assertEquals('test.method', $result->method);
202+
$this->assertEquals('2.0', $result->jsonRpc);
203+
$this->assertEquals(1, $result->id);
204+
$this->assertEquals(['param1' => 'value1'], $result->params);
205+
}
206+
}
207+
```
208+
209+
## Additional Development Information
210+
211+
### Code Style
212+
213+
- The project follows PSR-12 coding standards.
214+
- Use type hints for method parameters and return types.
215+
- Document classes and methods with PHPDoc comments.
216+
217+
### Creating MCP Tools
218+
219+
1. Use the provided command to generate a new tool:
220+
```bash
221+
php bin/console make:mcp-tool MyCustomTool
222+
```
223+
224+
2. Implement the `ToolInterface` in your custom tool class:
225+
```php
226+
use KLP\KlpMcpServer\Services\ToolService\ToolInterface;
227+
228+
class MyCustomTool implements ToolInterface
229+
{
230+
// Implementation
231+
}
232+
```
233+
234+
3. Register your tool in `config/packages/klp_mcp_server.yaml`.
235+
236+
### Testing MCP Tools
237+
238+
Use the provided command to test your MCP tools:
239+
240+
```bash
241+
# Test a specific tool interactively
242+
php bin/console mcp:test-tool MyCustomTool
243+
244+
# List all available tools
245+
php bin/console mcp:test-tool --list
246+
247+
# Test with specific JSON input
248+
php bin/console mcp:test-tool MyCustomTool --input='{"param":"value"}'
249+
```
250+
251+
### Debugging
252+
253+
- Enable Symfony's debug mode for detailed error messages.
254+
- Use the MCP Inspector for visualizing and testing your MCP tools:
255+
```bash
256+
npx @modelcontextprotocol/inspector node build/index.js
257+
```
258+
259+
### Architecture Notes
260+
261+
- The package implements a publish/subscribe (pub/sub) messaging pattern through its adapter system.
262+
- The default Redis adapter maintains message queues for each client, identified by unique client IDs.
263+
- Long-lived SSE connections subscribe to messages for their respective clients and deliver them in real-time.

0 commit comments

Comments
 (0)