Quick Start Guide โ
Get up and running with the PHP MCP SDK in minutes! This guide covers the essentials for building your first MCP server and client.
Prerequisites โ
- PHP 8.1+ with
json
,mbstring
extensions - Composer installed
- Basic understanding of PHP async programming
If you haven't installed the SDK yet, see the Installation Guide.
๐ฏ Your First MCP Server โ
Let's create a simple weather server that demonstrates the core MCP concepts.
Step 1: Create the Server โ
Create weather-server.php
:
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MCP\Server\McpServer;
use MCP\Server\Transport\StdioServerTransport;
use MCP\Types\Implementation;
use function Amp\async;
// Create server with implementation info
$server = new McpServer(
new Implementation(
'weather-server', // Server name
'1.0.0', // Version
'Simple Weather Server' // Description
)
);
// Register a tool that clients can call
$server->tool(
'get-weather', // Tool name
'Get current weather for a location',
[
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'City name or coordinates'
],
'units' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
'default' => 'celsius'
]
],
'required' => ['location']
],
function (array $params): array {
// Simulate weather API call
$weather = [
'location' => $params['location'],
'temperature' => rand(15, 30),
'condition' => ['sunny', 'cloudy', 'rainy', 'snowy'][rand(0, 3)],
'humidity' => rand(40, 80),
'wind_speed' => rand(5, 25)
];
// Convert temperature if requested
if (isset($params['units']) && $params['units'] === 'fahrenheit') {
$weather['temperature'] = round($weather['temperature'] * 9/5 + 32);
$units = 'ยฐF';
} else {
$units = 'ยฐC';
}
return [
'content' => [[
'type' => 'text',
'text' => json_encode([
'location' => $weather['location'],
'temperature' => $weather['temperature'] . $units,
'condition' => $weather['condition'],
'humidity' => $weather['humidity'] . '%',
'wind_speed' => $weather['wind_speed'] . ' km/h'
], JSON_PRETTY_PRINT)
]]
];
}
);
// Register a resource that provides weather data
$server->resource(
'weather://current/{location}',
'Current weather conditions for a location',
'application/json',
function (string $uri): array {
// Extract location from URI
preg_match('/weather:\/\/current\/(.+)/', $uri, $matches);
$location = urldecode($matches[1] ?? 'Unknown');
$weather = [
'location' => $location,
'temperature' => rand(15, 30),
'condition' => ['sunny', 'cloudy', 'rainy'][rand(0, 2)],
'timestamp' => date('c')
];
return [
'contents' => [[
'uri' => $uri,
'mimeType' => 'application/json',
'text' => json_encode($weather, JSON_PRETTY_PRINT)
]]
];
}
);
// Start the server
async(function () use ($server) {
echo "๐ค๏ธ Weather server starting...\n";
$transport = new StdioServerTransport();
$server->connect($transport)->await();
})->await();
Step 2: Make It Executable โ
chmod +x weather-server.php
Step 3: Test with MCP Inspector โ
The MCP Inspector is a great tool for testing your server:
# Install MCP Inspector (requires Node.js)
npm install -g @modelcontextprotocol/inspector
# Test your server
mcp-inspector ./weather-server.php
This opens a web interface where you can:
- View available tools and resources
- Test tool calls with different parameters
- Inspect the JSON-RPC messages
- Debug any issues
๐ Your First MCP Client โ
Now let's create a client to interact with our weather server.
Create weather-client.php
:
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MCP\Client\Client;
use MCP\Client\Transport\StdioClientTransport;
use MCP\Types\Implementation;
use function Amp\async;
// Create client instance
$client = new Client(
new Implementation('weather-client', '1.0.0')
);
// Configure transport to connect to our weather server
$transport = new StdioClientTransport([
'command' => 'php',
'args' => [__DIR__ . '/weather-server.php']
]);
async(function() use ($client, $transport) {
try {
// Connect to the server
echo "๐ Connecting to weather server...\n";
$client->connect($transport)->await();
echo "โ
Connected successfully!\n\n";
// Get server information
$serverInfo = $client->initialize()->await();
echo "๐ Server Info:\n";
echo " Name: {$serverInfo['serverInfo']['name']}\n";
echo " Version: {$serverInfo['serverInfo']['version']}\n\n";
// List available tools
echo "๐ง Available Tools:\n";
$toolsResult = $client->listTools()->await();
foreach ($toolsResult['tools'] as $tool) {
echo " - {$tool['name']}: {$tool['description']}\n";
}
echo "\n";
// List available resources
echo "๐ฆ Available Resources:\n";
$resourcesResult = $client->listResources()->await();
foreach ($resourcesResult['resources'] as $resource) {
echo " - {$resource['uri']}: {$resource['name']}\n";
}
echo "\n";
// Call the weather tool
echo "๐ค๏ธ Getting weather for London...\n";
$weatherResult = $client->callTool('get-weather', [
'location' => 'London, UK',
'units' => 'celsius'
])->await();
echo "Weather Result:\n";
echo $weatherResult['content'][0]['text'] . "\n\n";
// Read a weather resource
echo "๐ Reading weather resource for Paris...\n";
$resourceResult = $client->readResource('weather://current/Paris%2C%20France')->await();
echo "Resource Content:\n";
echo $resourceResult['contents'][0]['text'] . "\n\n";
// Test error handling
echo "๐งช Testing error handling...\n";
try {
$client->callTool('nonexistent-tool')->await();
} catch (\MCP\Types\McpError $e) {
echo "Expected error caught: {$e->getMessage()}\n";
}
// Clean shutdown
echo "\n๐ Disconnecting...\n";
$client->close()->await();
echo "โ
Disconnected successfully!\n";
} catch (\Exception $error) {
echo "โ Client error: " . $error->getMessage() . "\n";
error_log($error->getTraceAsString());
}
})->await();
Step 4: Run the Client โ
chmod +x weather-client.php
php weather-client.php
You should see output showing the client connecting, discovering tools/resources, making calls, and handling responses.
๐๏ธ Understanding the Code โ
Server Components โ
- Implementation: Describes your server (name, version, description)
- Tools: Callable functions that clients can invoke
- Resources: Data sources that clients can read
- Transport: Communication layer (STDIO, HTTP, WebSocket)
Tool Registration โ
$server->tool(
$name, // Unique tool identifier
$description, // Human-readable description
$schema, // JSON schema for parameters
$handler // Function that processes calls
);
Resource Registration โ
$server->resource(
$uriTemplate, // URI pattern with placeholders
$description, // Resource description
$mimeType, // Content type
$handler // Function that provides content
);
Client Operations โ
// Connect to server
$client->connect($transport)->await();
// Discover capabilities
$tools = $client->listTools()->await();
$resources = $client->listResources()->await();
// Use capabilities
$result = $client->callTool($name, $params)->await();
$content = $client->readResource($uri)->await();
๐ Advanced Features โ
Adding Prompts โ
Prompts are templates that help LLMs understand how to use your server:
$server->prompt(
'weather-analysis',
'Analyze weather data for insights',
[
[
'name' => 'location',
'description' => 'Location to analyze',
'required' => true
],
[
'name' => 'days',
'description' => 'Number of days to analyze',
'required' => false
]
],
function (array $arguments): array {
$location = $arguments['location'];
$days = $arguments['days'] ?? 7;
return [
'description' => "Weather analysis for {$location}",
'messages' => [
[
'role' => 'system',
'content' => [
'type' => 'text',
'text' => "Analyze weather patterns for {$location} over {$days} days. Provide insights about trends, recommendations for activities, and any notable conditions."
]
]
]
];
}
);
Error Handling โ
use MCP\Types\McpError;
use MCP\Types\ErrorCode;
$server->tool(
'validated-tool',
'A tool with validation',
$schema,
function (array $params): array {
// Validate required parameters
if (!isset($params['location'])) {
throw new McpError(
ErrorCode::InvalidParams,
'Missing required parameter: location'
);
}
// Your tool logic here...
return $result;
}
);
Logging โ
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('weather-server');
$logger->pushHandler(new StreamHandler('php://stderr', Logger::INFO));
$server->setLogger($logger);
๐งช Testing Your Implementation โ
Unit Tests โ
Create tests/WeatherServerTest.php
:
<?php
use PHPUnit\Framework\TestCase;
use MCP\Server\McpServer;
use MCP\Types\Implementation;
class WeatherServerTest extends TestCase
{
public function testServerCreation(): void
{
$server = new McpServer(
new Implementation('test-server', '1.0.0')
);
$this->assertInstanceOf(McpServer::class, $server);
}
public function testToolRegistration(): void
{
$server = new McpServer(
new Implementation('test-server', '1.0.0')
);
$server->tool(
'test-tool',
'Test tool',
['type' => 'object'],
fn($params) => ['content' => [['type' => 'text', 'text' => 'test']]]
);
$tools = $server->listTools();
$this->assertCount(1, $tools['tools']);
$this->assertEquals('test-tool', $tools['tools'][0]['name']);
}
}
Run tests:
vendor/bin/phpunit tests/
Integration Tests โ
Test with other MCP clients:
# Test with Python MCP client
pip install mcp
python -c "
import asyncio
from mcp import StdioClientTransport, Client
async def test():
transport = StdioClientTransport('php', ['./weather-server.php'])
client = Client()
await client.connect(transport)
result = await client.call_tool('get-weather', {'location': 'Tokyo'})
print(result)
asyncio.run(test())
"
๐ Next Steps โ
Now that you have a working MCP server and client:
- ๐ Learn Core Concepts - Deeper understanding of MCP
- ๐ฅ๏ธ Advanced Server Guide - Production-ready servers
- ๐ฑ Advanced Client Guide - Robust client implementation
- ๐ Authentication Guide - Secure your MCP services
- ๐๏ธ Laravel Integration - Framework integration
๐ Troubleshooting โ
Common Issues โ
Server doesn't start: Check PHP version and extension requirements Client connection fails: Verify server is executable and paths are correct
JSON parsing errors: Ensure proper UTF-8 encoding in your tool responses Memory issues: Increase PHP memory limit for development
Debugging Tips โ
Enable verbose logging:
bashMCP_LOG_LEVEL=debug php weather-server.php
Use MCP Inspector to see raw JSON-RPC messages
Add debug output:
phperror_log("Debug: " . json_encode($data));
Test with simple clients before complex integration
Getting Help โ
Happy coding with MCP! ๐