Skip to main content
rule official · GitHub

php-mcp-server — GitHub Copilot rule: what it does, how to install

Instructions for GitHub Copilot on developing Model Context Protocol servers in PHP 8.2+. Guides the assistant to use official SDK attributes and clean project structures.

Install into GitHub Copilot · Manually: put the files in place All methods and platforms ↓
.github/instructions/php-mcp-server.instructions.md
Open on GitHub

What php-mcp-server does

This instruction file guides GitHub Copilot in generating Model Context Protocol servers using the official PHP SDK developed with The PHP Foundation. It teaches the AI agent the expected project layout, separating tools, resources, and prompts under src/.

Copilot learns to build capabilities using native PHP attributes such as #[McpTool], #[McpResource], and #[Schema]. It constructs server entry points through Server::builder(), wires up StdioTransport, and configures PSR-16 caching to speed up discovery.

Your environment must run PHP 8.2 or later with the mcp/sdk Composer package installed. Without that package, generated classes cannot execute. The rule triggers automatically on any **/*.php file.

What it helps with

  • structure a PHP MCP server project with Composer
  • declare MCP tools using #[McpTool] and parameter schemas
  • set up Server::builder() with PSR-16 attribute discovery caching
  • expose URI-based resources using #[McpResourceTemplate]

Example prompts for the agent

  • Create an MCP tool class that processes image uploads using ImageContent
  • Write a server.php entry point using StdioTransport and automatic attribute discovery
  • Add a configuration resource accessible at config://app/settings

How to install php-mcp-server

Manually: put the files in place

.github/instructions/php-mcp-server.instructions.md

The applyTo field sets which files it applies to

Step by step, with commands for each platform: AGENTS.md vs CLAUDE.md: what they are and which one to use

Questions about php-mcp-server

How do I install php-mcp-server in GitHub Copilot?

Save the instruction file to .github/instructions/php-mcp-server.instructions.md in your project root. GitHub Copilot will immediately apply it across all **/*.php files.

What does php-mcp-server do?

It directs GitHub Copilot to write MCP servers according to the official PHP SDK standards. The assistant uses attributes like #[McpTool] instead of manual boilerplate.

What are the prerequisites for this rule?

You need PHP 8.2 or higher and the mcp/sdk package installed via composer require mcp/sdk. A PSR-16 cache implementation is optional for discovery caching.

Source text

php-mcp-server.instructions.md · MIT
---
description: 'Best practices for building Model Context Protocol servers in PHP using the official PHP SDK with attribute-based discovery and multiple transport options'
applyTo: '**/*.php'
---

# PHP MCP Server Development Best Practices

This guide provides best practices for building Model Context Protocol (MCP) servers using the official PHP SDK maintained in collaboration with The PHP Foundation.

## Installation and Setup

### Install via Composer

```bash
composer require mcp/sdk
```

### Project Structure

Organize your PHP MCP server project:

```
my-mcp-server/
├── composer.json
├── src/
│   ├── Tools/
│   │   ├── Calculator.php
│   │   └── FileManager.php
│   ├── Resources/
│   │   ├── ConfigProvider.php
│   │   └── DataProvider.php
│   ├── Prompts/
│   │   └── PromptGenerator.php
│   └── Server.php
├── server.php           # Server entry point
└── tests/
    └── ToolsTest.php
```

### Composer Configuration

```json
{
    "name": "your-org/mcp-server",
    "description": "MCP Server for...",
    "type": "project",
    "require": {
        "php": "^8.2",
        "mcp/sdk": "^0.1"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}
```

## Server Implementation

### Basic Server with Attribute Discovery

Create your server entry point:

```php
#!/usr/bin/env php
<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;

$server = Server::builder()
    ->setServerInfo('My MCP Server', '1.0.0')
    ->setDiscovery(__DIR__, ['.'])
    ->build();

$transport = new StdioTransport();

$server->run($transport);
```

### Server with Caching

Use PSR-16 cache for better performance:

```php
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;

$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery'));

$server = Server::builder()
    ->setServerInfo('My MCP Server', '1.0.0')
    ->setDiscovery(
        basePath: __DIR__,
        scanDirs: ['.', 'src'],
        excludeDirs: ['vendor', 'tests'],
        cache: $cache
    )
    ->build();
```

### Manual Registration

Register capabilities programmatically:

```php
use App\Tools\Calculator;
use App\Resources\Config;

$server = Server::builder()
    ->setServerInfo('My MCP Server', '1.0.0')
    ->addTool([Calculator::class, 'add'], 'add')
    ->addTool([Calculator::class, 'multiply'], 'multiply')
    ->addResource([Config::class, 'getSettings'], 'config://app/settings')
    ->build();
```

## Tool Development

### Simple Tool with Attribute

```php
<?php

namespace App\Tools;

use Mcp\Capability\Attribute\McpTool;

class Calculator
{
    /**
     * Adds two numbers together.
     * 
     * @param int $a The first number
     * @param int $b The second number
     * @return int The sum of the two numbers
     */
    #[McpTool]
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
}
```

### Tool with Custom Name

```php
use Mcp\Capability\Attribute\McpTool;

class FileManager
{
    /**
     * Reads file content from the filesystem.
     */
    #[McpTool(name: 'read_file')]
    public function readFileContent(string $path): string
    {
        if (!file_exists($path)) {
            throw new \InvalidArgumentException("File not found: {$path}");
        }
        
        return file_get_contents($path);
    }
}
```

### Tool with Validation and Schema

```php
use Mcp\Capability\Attribute\{McpTool, Schema};

class UserManager
{
    #[McpTool(name: 'create_user')]
    public function createUser(
        #[Schema(format: 'email')]
        string $email,
        
        #[Schema(minimum: 18, maximum: 120)]
        int $age,
        
        #[Schema(
            pattern: '^[A-Z][a-z]+$',
            description: 'Capitalized first name'
        )]
        string $firstName
    ): array
    {
        return [
            'id' => uniqid(),
            'email' => $email,
            'age' => $age,
            'firstName' => $firstName
        ];
    }
}
```

### Tool with Complex Return Types

```php
use Mcp\Schema\Content\{TextContent, ImageContent};

class ReportGenerator
{
    #[McpTool]
    public function generateReport(string $type): array
    {
        return [
            new TextContent('Report generated:'),
            TextContent::code($this->generateCode($type), 'php'),
            new TextContent('Summary: All checks passed.')
        ];
    }
    
    #[McpTool]
    public function getChart(string $chartType): ImageContent
    {
        $imageData = $this->generateChartImage($chartType);
        
        return new ImageContent(
            data: base64_encode($imageData),
            mimeType: 'image/png'
        );
    }
}
```

### Tool with Match Expression

```php
#[McpTool(name: 'calculate')]
public function performCalculation(float $a, float $b, string $operation): float
{
    return match($operation) {
        'add' => $a + $b,
        'subtract' => $a - $b,
        'multiply' => $a * $b,
        'divide' => $b != 0 ? $a / $b : 
            throw new \InvalidArgumentException('Division by zero'),
        default => throw new \InvalidArgumentException('Invalid operation')
    };
}
```

## Resource Implementation

### Static Resource

```php
<?php

namespace App\Resources;

use Mcp\Capability\Attribute\McpResource;

class ConfigProvider
{
    /**
     * Provides the current application configuration.
     */
    #[McpResource(
        uri: 'config://app/settings',
        name: 'app_settings',
        mimeType: 'application/json'
    )]
    public function getSettings(): array
    {
        return [
            'version' => '1.0.0',
            'debug' => false,
            'features' => ['auth', 'logging']
        ];
    }
}
```

### Resource Template with Variables

```php
use Mcp\Capability\Attribute\McpResourceTemplate;

class UserProvider
{
    /**
     * Retrieves user profile information by ID and section.
     */
    #[McpResourceTemplate(
        uriTemplate: 'user://{userId}/profile/{section}',
        name: 'user_profile',
        description: 'User profile data by section',
        mimeType: 'application/json'
    )]
    public function getUserProfile(string $userId, string $section): array
    {
        // Variable order must match URI template order
        return $this->users[$userId][$section] ?? 
            throw new \InvalidArgumentException("Profile section not found");
    }
}
```

### Resource with File Content

```php
use Mcp\Schema\Content\{TextResourceContents, BlobResourceContents};

class FileProvider
{
    #[McpResource(uri: 'file://readme.txt', mimeType: 'text/plain')]
    public function getReadme(): TextResourceContents
    {
        return new TextResourceContents(
            uri: 'file://readme.txt',
            mimeType: 'text/plain',
            text: file_get_contents(__DIR__ . '/README.txt')
        );
    }
    
    #[McpResource(uri: 'file://image.png', mimeType: 'image/png')]
    public function getImage(): BlobResourceContents
    {
        $imageData = file_get_contents(__DIR__ . '/image.png');
        
        return new BlobResourceContents(
            uri: 'file://image.png',
            mimeType: 'image/png',
            blob: base64_encode($imageData)
        );
    }
}
```

## Prompt Implementation

### Basic Prompt

```php
<?php

namespace App\Prompts;

use Mcp\Capability\Attribute\McpPrompt;

class PromptGenerator
{
    /**
     * Generates a code review request prompt.
     */
    #[McpPrompt(name: 'code_review')]
    public function reviewCode(string $language, string $code, string $focus = 'general'): array
    {
        return [
            ['role' => 'assistant', 'content' => 'You are an expert code reviewer.'],
            ['role' => 'user', 'content' => "Review this {$language} code focusing on {$focus}:\n\n```{$language}\n{$code}\n```"]
        ];
    }
}
```

### Prompt with Mixed Content

```php
use Mcp\Schema\Content\{TextContent, ImageContent};
use Mcp\Schema\PromptMessage;
use Mcp\Schema\Enum\Role;

#[McpPrompt]
public function analyzeImage(string $imageUrl, string $question): array
{
    $imageData = file_get_contents($imageUrl);
    
    return [
        new PromptMessage(Role::Assistant, [
            new TextContent('You are an image analysis expert.')
        ]),
        new PromptMessage(Role::User, [
            new TextContent($question),
            new ImageContent(
                data: base64_encode($imageData),
                mimeType: 'image/jpeg'
            )
        ])
    ];
}
```

## Completion Providers

### Value List Completion

```php
use Mcp\Capability\Attribute\{McpPrompt, CompletionProvider};

#[McpPrompt]
public function generateContent(
    #[CompletionProvider(values: ['blog', 'article', 'tutorial', 'guide'])]
    string $contentType,
    
    #[CompletionProvider(values: ['beginner', 'intermediate', 'advanced'])]
    string $difficulty
): array
{
    return [
        ['role' => 'user', 'content' => "Create a {$difficulty} level {$contentType}"]
    ];
}
```

### Enum Completion

```php
enum Priority: string
{
    case LOW = 'low';
    case MEDIUM = 'medium';
    case HIGH = 'high';
}

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
}

#[McpResourceTemplate(uriTemplate: 'tasks/{taskId}')]
public function getTask(
    string $taskId,
    
    #[CompletionProvider(enum: Priority::class)]
    string $priority,
    
    #[CompletionProvider(enum: Status::class)]
    string $status
): array
{
    return $this->tasks[$taskId] ?? [];
}
```

### Custom Completion Provider

```php
use Mcp\Capability\Prompt\Completion\ProviderInterface;

class UserIdCompletionProvider implements ProviderInterface
{
    public function __construct(
        private DatabaseService $db
    ) {}

    public function getCompletions(string $currentValue): array
    {
        return $this->db->searchUserIds($currentValue);
    }
}

#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile')]
public function getUserProfile(
    #[CompletionProvider(provider: UserIdCompletionProvider::class)]
    string $userId
): array
{
    return $this->users[$userId] ?? 
        throw new \InvalidArgumentException('User not found');
}
```

## Transport Options

### Stdio Transport

For command-line integration (default):

```php
use Mcp\Server\Transport\StdioTransport;

$transport = new StdioTransport();
$server->run($transport);
```

### HTTP Transport

For web-based integration:

```php
use Mcp\Server\Transport\StreamableHttpTransport;
use Nyholm\Psr7\Factory\Psr17Factory;

$psr17Factory = new Psr17Factory();

$request = $psr17Factory->createServerRequestFromGlobals();

$transport = new StreamableHttpTransport(
    $request,
    $psr17Factory,  // Response factory
    $psr17Factory   // Stream factory
);

$response = $server->run($transport);

// Send response in your web framework
foreach ($response->getHeaders() as $name => $values) {
    foreach ($values as $value) {
        header("$name: $value", false);
    }
}

http_response_code($response->getStatusCode());
echo $response->getBody();
```

## Session Management

### In-Memory Sessions (Default)

```php
$server = Server::builder()
    ->setServerInfo('My Server', '1.0.0')
    ->setSession(ttl: 7200) // 2 hours
    ->build();
```

### File-Based Sessions

```php
use Mcp\Server\Session\FileSessionStore;

$server = Server::builder()
    ->setServerInfo('My Server', '1.0.0')
    ->setSession(new FileSessionStore(__DIR__ . '/sessions'))
    ->build();
```

### Custom Session Store

```php
use Mcp\Server\Session\InMemorySessionStore;

$server = Server::builder()
    ->setServerInfo('My Server', '1.0.0')
    ->setSession(new InMemorySessionStore(3600))
    ->build();
```

## Error Handling

### Exception Handling in Tools

```php
#[McpTool]
public function divideNumbers(float $a, float $b): float
{
    if ($b === 0.0) {
        throw new \InvalidArgumentException('Division by zero is not allowed');
    }
    
    return $a / $b;
}

#[McpTool]
public function processFile(string $filename): string
{
    if (!file_exists($filename)) {
        throw new \InvalidArgumentException("File not found: {$filename}");
    }
    
    if (!is_readable($filename)) {
        throw new \RuntimeException("File not readable: {$filename}");
    }
    
    return file_get_contents($filename);
}
```

### Custom Error Responses

The SDK automatically converts exceptions into JSON-RPC error responses that MCP clients understand.

## Testing

### PHPUnit Tests for Tools

```php
<?php

namespace Tests;

use PHPUnit\Framework\TestCase;
use App\Tools\Calculator;

class CalculatorTest extends TestCase
{
    private Calculator $calculator;
    
    protected function setUp(): void
    {
        $this->calculator = new Calculator();
    }
    
    public function testAdd(): void
    {
        $result = $this->calculator->add(5, 3);
        $this->assertSame(8, $result);
    }
    
    public function testDivideByZero(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Division by zero');
        
        $this->calculator->divide(10, 0);
    }
}
```

### Testing Server Discovery

```php
public function testServerDiscoversTools(): void
{
    $server = Server::builder()
        ->setServerInfo('Test Server', '1.0.0')
        ->setDiscovery(__DIR__ . '/../src', ['.'])
        ->build();
    
    $capabilities = $server->getCapabilities();
    
    $this->assertArrayHasKey('tools', $capabilities);
    $this->assertNotEmpty($capabilities['tools']);
}
```

## Performance Best Practices

### Use Discovery Caching

Always use caching in production:

```php
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Psr16Cache;

$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);

$cache = new Psr16Cache(new RedisAdapter($redis));

$server = Server::builder()
    ->setServerInfo('My Server', '1.0.0')
    ->setDiscovery(
        basePath: __DIR__,
        scanDirs: ['src'],
        excludeDirs: ['vendor', 'tests', 'var'],
        cache: $cache
    )
    ->build();
```

### Optimize Scan Directories

Only scan necessary directories:

```php
$server = Server::builder()
    ->setDiscovery(
        basePath: __DIR__,
        scanDirs: ['src/Tools', 'src/Resources'],  // Specific dirs
        excludeDirs: ['vendor', 'tests', 'var', 'cache']
    )
    ->build();
```

### Use OPcache

Enable OPcache in production for better PHP performance:

```ini
; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
```

## Framework Integration

### Laravel Integration

```php
// app/Console/Commands/McpServer.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;

class McpServer extends Command
{
    protected $signature = 'mcp:serve';
    protected $description = 'Start MCP server';
    
    public function handle()
    {
        $server = Server::builder()
            ->setServerInfo('Laravel MCP Server', '1.0.0')
            ->setDiscovery(app_path(), ['Tools', 'Resources'])
            ->build();
        
        $transport = new StdioTransport();
        $server->run($transport);
    }
}
```

### Symfony Integration

```php
// Use symfony/mcp-bundle for native integration
composer require symfony/mcp-bundle
```

## Deployment

### Docker Deployment

```dockerfile
FROM php:8.2-cli

# Install extensions
RUN docker-php-ext-install pdo pdo_mysql

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /app

# Copy application
COPY . /app

# Install dependencies
RUN composer install --no-dev --optimize-autoloader

# Make server executable
RUN chmod +x /app/server.php

CMD ["php", "/app/server.php"]
```

### Systemd Service

```ini
[Unit]
Description=MCP PHP Server
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/mcp-server
ExecStart=/usr/bin/php /var/www/mcp-server/server.php
Restart=always

[Install]
WantedBy=multi-user.target
```

## Configuration for MCP Clients

### Claude Desktop Configuration

```json
{
  "mcpServers": {
    "php-server": {
      "command": "php",
      "args": ["/absolute/path/to/server.php"]
    }
  }
}
```

### MCP Inspector Testing

```bash
npx @modelcontextprotocol/inspector php /path/to/server.php
```

## Additional Resources

- [Official PHP SDK Repository](https://github.com/modelcontextprotocol/php-sdk)
- [MCP Elements Documentation](https://github.com/modelcontextprotocol/php-sdk/blob/main/docs/mcp-elements.md)
- [Server Builder Documentation](https://github.com/modelcontextprotocol/php-sdk/blob/main/docs/server-builder.md)
- [Transport Documentation](https://github.com/modelcontextprotocol/php-sdk/blob/main/docs/transports.md)
- [Examples](https://github.com/modelcontextprotocol/php-sdk/blob/main/docs/examples.md)
- [MCP Specification](https://spec.modelcontextprotocol.io/)
- [Model Context Protocol](https://modelcontextprotocol.io/)

© github · MIT · github/awesome-copilot

Similar IDE rules

All IDE rules for Agent setup: memory, context, orchestration →

From the same set861

The whole set →
playwright-typescript This rule tells Copilot how to write Playwright tests: role-based locators, auto-retrying assertions, one file per feature. Useful when an agent writes the tests. ★ 40,418repo stars playwright-python Guides GitHub Copilot to write Python browser tests with Pytest. Enforces role-based locators, auto-retrying assertions, and clean test structure. ★ 40,418repo stars playwright-dotnet This rule guides GitHub Copilot when it creates or edits Playwright tests in C#. Use it to shape test structure, locators, and assertions. ★ 40,418repo stars php-symfony This rule steers Copilot toward official Symfony practices: project layout, dependency injection, Doctrine, Twig and testing. Useful when you keep re-explaining conventions in chat. ★ 40,418repo stars performance-optimization This rule helps spot web performance issues tied to Core Web Vitals and review possible fixes. Use it when working on page loading, rendering, or layout stability. ★ 40,418repo stars pcf-tooling A rule that tells Copilot to use Microsoft Power Platform CLI for building, debugging and deploying Power Apps code components. ★ 40,418repo stars pcf-sample-components Guides GitHub Copilot through preparing, building, and bundling Power Apps Component Framework samples into deployable solution archives. ★ 40,418repo stars pcf-react-platform-libraries This rule guides GitHub Copilot through building React-based PCF controls with Power Apps platform libraries. Use it when setting up virtual controls for canvas or model-driven apps. ★ 40,418repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor