Build Your First MCP Server in 10 Minutes
Welcome to MCP! In this tutorial, you'll build a working MCP server from scratch in just 10 minutes. By the end, you'll have a server that provides useful tools and understand the core concepts.
🎯 What You'll Build
A Personal Assistant Server that provides:
- ✅ Calculator tool - Perform mathematical calculations
- ✅ Note-taking tool - Save and retrieve notes
- ✅ System info resource - Get system information
- ✅ Help prompt - Guide users on available features
📋 Prerequisites
- PHP 8.1+ installed
- Composer installed
- 10 minutes of your time
- Basic PHP knowledge (arrays, functions, classes)
🚀 Let's Build!
Step 1: Create Project Structure (1 minute)
# Create a new directory for your server
mkdir my-first-mcp-server
cd my-first-mcp-server
# Initialize composer project
composer init --name="my-company/mcp-server" --no-interaction
# Install PHP MCP SDK
composer require dalehurley/php-mcp-sdkStep 2: Create the Server File (3 minutes)
Create personal-assistant-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 MCP\Types\McpError;
use MCP\Types\ErrorCode;
use function Amp\async;
// Create server with implementation info
$server = new McpServer(
new Implementation(
'personal-assistant', // Server name
'1.0.0', // Version
'Personal Assistant MCP Server' // Description
)
);
// In-memory storage for notes (in production, use a database)
$notes = [];
// 🧮 TOOL 1: Calculator
$server->tool(
'calculate',
'Perform mathematical calculations',
[
'type' => 'object',
'properties' => [
'expression' => [
'type' => 'string',
'description' => 'Mathematical expression to evaluate (e.g., "2 + 2", "10 * 5")'
]
],
'required' => ['expression']
],
function (array $params): array {
$expression = $params['expression'];
// Basic security: only allow safe mathematical operations
if (!preg_match('/^[0-9+\-*\/\(\)\.\s]+$/', $expression)) {
throw new McpError(
ErrorCode::InvalidParams,
'Expression contains invalid characters. Only numbers and basic operators (+, -, *, /, parentheses) are allowed.'
);
}
try {
// Evaluate the expression safely
$result = eval("return {$expression};");
return [
'content' => [[
'type' => 'text',
'text' => "Calculation: {$expression} = {$result}"
]]
];
} catch (\ParseError $e) {
throw new McpError(
ErrorCode::InvalidParams,
"Invalid mathematical expression: {$expression}"
);
}
}
);
// 📝 TOOL 2: Note Management
$server->tool(
'save-note',
'Save a note with a title and content',
[
'type' => 'object',
'properties' => [
'title' => [
'type' => 'string',
'description' => 'Title of the note'
],
'content' => [
'type' => 'string',
'description' => 'Content of the note'
]
],
'required' => ['title', 'content']
],
function (array $params) use (&$notes): array {
$title = $params['title'];
$content = $params['content'];
$timestamp = date('Y-m-d H:i:s');
$notes[$title] = [
'content' => $content,
'created' => $timestamp,
'updated' => $timestamp
];
return [
'content' => [[
'type' => 'text',
'text' => "Note '{$title}' saved successfully at {$timestamp}"
]]
];
}
);
$server->tool(
'get-note',
'Retrieve a saved note by title',
[
'type' => 'object',
'properties' => [
'title' => [
'type' => 'string',
'description' => 'Title of the note to retrieve'
]
],
'required' => ['title']
],
function (array $params) use (&$notes): array {
$title = $params['title'];
if (!isset($notes[$title])) {
throw new McpError(
ErrorCode::InvalidParams,
"Note '{$title}' not found"
);
}
$note = $notes[$title];
return [
'content' => [[
'type' => 'text',
'text' => "Note: {$title}\nCreated: {$note['created']}\nUpdated: {$note['updated']}\n\nContent:\n{$note['content']}"
]]
];
}
);
$server->tool(
'list-notes',
'List all saved notes',
[
'type' => 'object',
'properties' => []
],
function (array $params) use (&$notes): array {
if (empty($notes)) {
return [
'content' => [[
'type' => 'text',
'text' => 'No notes saved yet. Use save-note to create your first note!'
]]
];
}
$notesList = "Saved Notes:\n\n";
foreach ($notes as $title => $note) {
$notesList .= "📝 {$title}\n";
$notesList .= " Created: {$note['created']}\n";
$notesList .= " Preview: " . substr($note['content'], 0, 50) . "...\n\n";
}
return [
'content' => [[
'type' => 'text',
'text' => $notesList
]]
];
}
);
// 💻 RESOURCE: System Information
$server->resource(
'system-info',
'system://info',
'application/json',
function (string $uri): array {
$systemInfo = [
'server_name' => 'Personal Assistant MCP Server',
'version' => '1.0.0',
'php_version' => PHP_VERSION,
'memory_usage' => memory_get_usage(true),
'peak_memory' => memory_get_peak_usage(true),
'uptime' => time() - $_SERVER['REQUEST_TIME'],
'timestamp' => date('c'),
'tools_count' => 4, // We have 4 tools
'resources_count' => 1 // We have 1 resource
];
return [
'contents' => [[
'uri' => $uri,
'mimeType' => 'application/json',
'text' => json_encode($systemInfo, JSON_PRETTY_PRINT)
]]
];
}
);
// 💡 PROMPT: Help and Usage Guide
$server->prompt(
'help',
'Get help on how to use the personal assistant',
[
[
'name' => 'topic',
'description' => 'Specific topic to get help with (optional)',
'required' => false
]
],
function (array $arguments): array {
$topic = $arguments['topic'] ?? 'general';
$helpContent = match ($topic) {
'calculator' => "🧮 Calculator Help:\n\nUse the 'calculate' tool to perform mathematical calculations.\n\nExamples:\n- calculate('2 + 2')\n- calculate('10 * 5 + 3')\n- calculate('(100 - 20) / 4')\n\nSupported operations: +, -, *, /, parentheses",
'notes' => "📝 Notes Help:\n\nManage your notes with these tools:\n\n- save-note(title, content) - Save a new note\n- get-note(title) - Retrieve a note by title\n- list-notes() - See all your notes\n\nNotes are stored in memory and will be lost when the server restarts.",
'system' => "💻 System Help:\n\nAccess system information:\n\n- Read resource 'system://info' to get server status and system information\n\nThis includes PHP version, memory usage, uptime, and more.",
default => "🤖 Personal Assistant Help:\n\nWelcome! I'm your personal assistant MCP server. Here's what I can help you with:\n\n🧮 **Calculator**: Perform mathematical calculations\n📝 **Notes**: Save, retrieve, and list personal notes\n💻 **System Info**: Get server and system information\n\n**Available Tools:**\n- calculate - Mathematical calculations\n- save-note - Save a note\n- get-note - Retrieve a note\n- list-notes - List all notes\n\n**Available Resources:**\n- system://info - System information\n\n**Getting Help:**\nUse this prompt with different topics:\n- help(topic='calculator') - Calculator help\n- help(topic='notes') - Notes help\n- help(topic='system') - System help\n\nHappy assisting! 🚀"
};
return [
'description' => "Help for {$topic}",
'messages' => [[
'role' => 'assistant',
'content' => [[
'type' => 'text',
'text' => $helpContent
]]
]]
];
}
);
// 🚀 Start the server
echo "🤖 Starting Personal Assistant MCP Server...\n";
echo "Available capabilities:\n";
echo " 🧮 Calculator tool\n";
echo " 📝 Note management tools\n";
echo " 💻 System information resource\n";
echo " 💡 Interactive help prompt\n";
echo "\n✅ Server ready for connections!\n";
async(function() use ($server) {
$transport = new StdioServerTransport();
$server->connect($transport)->await();
})->await();Step 3: Make It Executable (30 seconds)
chmod +x personal-assistant-server.phpStep 4: Test Your Server (2 minutes)
Option A: Test with MCP Inspector (Recommended)
# Install MCP Inspector (requires Node.js)
npm install -g @modelcontextprotocol/inspector
# Test your server
mcp-inspector ./personal-assistant-server.phpThis opens a web interface where you can:
- ✅ View all available tools and resources
- ✅ Test tool calls with different parameters
- ✅ Inspect JSON-RPC messages
- ✅ Debug any issues
Option B: Test with a Simple Client
Create test-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;
echo "🧪 Testing Personal Assistant Server\n";
echo "===================================\n\n";
$client = new Client(new Implementation('test-client', '1.0.0'));
$transport = new StdioClientTransport([
'command' => 'php',
'args' => [__DIR__ . '/personal-assistant-server.php']
]);
async(function() use ($client, $transport) {
try {
// Connect to server
echo "🔌 Connecting to server...\n";
$client->connect($transport)->await();
echo "✅ Connected successfully!\n\n";
// Test calculator tool
echo "🧮 Testing calculator...\n";
$calcResult = $client->callTool('calculate', ['expression' => '15 + 27'])->await();
echo "Result: {$calcResult['content'][0]['text']}\n\n";
// Test note saving
echo "📝 Testing note saving...\n";
$saveResult = $client->callTool('save-note', [
'title' => 'My First Note',
'content' => 'This is my first note created with MCP!'
])->await();
echo "Result: {$saveResult['content'][0]['text']}\n\n";
// Test note retrieval
echo "📖 Testing note retrieval...\n";
$getResult = $client->callTool('get-note', ['title' => 'My First Note'])->await();
echo "Result:\n{$getResult['content'][0]['text']}\n\n";
// Test system resource
echo "💻 Testing system resource...\n";
$sysResult = $client->readResource('system://info')->await();
echo "System Info:\n{$sysResult['contents'][0]['text']}\n\n";
// Test help prompt
echo "💡 Testing help prompt...\n";
$helpResult = $client->getPrompt('help')->await();
echo "Help Content:\n{$helpResult['messages'][0]['content'][0]['text']}\n\n";
// Clean shutdown
echo "🔌 Disconnecting...\n";
$client->close()->await();
echo "✅ Test completed successfully!\n";
} catch (\Exception $e) {
echo "❌ Test failed: {$e->getMessage()}\n";
}
})->await();chmod +x test-client.php
php test-client.phpStep 5: Understanding Your Server (3 minutes)
Let's break down what you just built:
🏗️ Server Structure
// 1. Server Creation
$server = new McpServer(
new Implementation('name', 'version', 'description')
);
// 2. Tool Registration
$server->tool($name, $description, $schema, $handler);
// 3. Resource Registration
$server->resource($name, $uri, $mimeType, $handler);
// 4. Prompt Registration
$server->prompt($name, $description, $arguments, $handler);
// 5. Start Server
$server->connect($transport)->await();🔧 Tool Components
Every tool has four parts:
- Name - Unique identifier (
'calculate') - Description - Human-readable description
- Schema - JSON Schema for input validation
- Handler - Function that does the work
$server->tool(
'tool-name', // 1. Name
'What the tool does', // 2. Description
[ // 3. Schema
'type' => 'object',
'properties' => [/* ... */]
],
function (array $params): array { // 4. Handler
// Your logic here
return ['content' => [/* results */]];
}
);📦 Resource Components
Resources provide data through URI templates:
$server->resource(
'resource-name', // Resource name
'scheme://path', // URI template
'application/json', // MIME type
function (string $uri): array { // Content provider
return ['contents' => [/* data */]];
}
);💭 Prompt Components
Prompts help LLMs understand how to use your server:
$server->prompt(
'prompt-name', // Prompt name
'What the prompt does', // Description
[ // Arguments
[
'name' => 'arg1',
'description' => 'First argument',
'required' => true
]
],
function (array $args): array { // Content generator
return [
'description' => 'Prompt description',
'messages' => [/* chat messages */]
];
}
);🎉 Congratulations!
You've just built your first MCP server! Here's what you accomplished:
✅ Created a working MCP server with multiple capabilities
✅ Implemented tools that perform useful functions
✅ Added resources that provide system information
✅ Created prompts that guide user interaction
✅ Tested everything with a client
🚀 Next Steps
Now that you have a working server, here are some ideas to explore:
Immediate Enhancements (10 minutes each)
Add More Tools:
php// Weather tool $server->tool('get-weather', 'Get weather information', $schema, $weatherHandler); // File operations $server->tool('read-file', 'Read file contents', $schema, $fileHandler); // Database queries $server->tool('query-db', 'Query database', $schema, $dbHandler);Add More Resources:
php// Configuration files $server->resource('config', 'config://{env}', 'application/json', $configHandler); // Log files $server->resource('logs', 'logs://{date}', 'text/plain', $logHandler);Persist Notes (use SQLite or files):
php// Replace in-memory storage with SQLite $pdo = new PDO('sqlite:notes.db'); $pdo->exec('CREATE TABLE IF NOT EXISTS notes (title TEXT PRIMARY KEY, content TEXT, created TEXT)');
Learning Path
- 📖 Learn Core Concepts: Understanding MCP
- 🔧 Advanced Tools: Creating Servers
- 🔐 Add Security: Authentication
- 🌐 Web Deployment: Transports
- 🏗️ Framework Integration: Laravel Integration
Real-World Examples
- 📊 Data Pipeline - Data analysis tools
- 📝 Blog CMS - Content management
- 🤖 AI Assistant - AI-powered tools
- 📱 API Gateway - Service orchestration
🆘 Need Help?
Common Issues
Server won't start:
# Check PHP version
php --version # Should be 8.1+
# Check syntax
php -l personal-assistant-server.php
# Run with debug
DEBUG=1 php personal-assistant-server.phpClient connection fails:
# Check if server is executable
ls -la personal-assistant-server.php
# Test server directly
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | php personal-assistant-server.phpTool errors:
- Check parameter names match the schema
- Verify required parameters are provided
- Look at error messages for clues
Getting Help
🎯 What's Next?
You're now ready to build more sophisticated MCP servers! Here are some paths to explore:
- 🔧 Advanced Server Development: Build production-ready servers with databases, APIs, and complex logic
- 📱 Client Development: Create clients that connect to multiple servers
- 🌐 Web Integration: Deploy your servers with HTTP transport for web access
- 🤖 AI Integration: Connect your servers to OpenAI, Claude, or other LLMs
- 🏗️ Framework Integration: Integrate with Laravel, Symfony, or other PHP frameworks
Ready for the next challenge? → Build Your First Client
🎉 You've taken your first step into the MCP ecosystem. Welcome aboard!