seolinkmap / waasup
Website as a Server unleashing Power (WaaSuP) - Production-ready SaaS Model Context Protocol (MCP) server for PHP
Requires
- php: ^8.1
- psr/http-factory: ^1.0
- psr/http-message: ^1.0|^2.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0|^2.0|^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- monolog/monolog: ^3.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^10.0
- slim/psr7: ^1.0
- slim/slim: ^4.0
- symfony/var-dumper: ^6.0
Suggests
- illuminate/support: For Laravel integration
- monolog/monolog: For PSR-3 logging support
- nyholm/psr7: PSR-17 factory implementation
- slim/psr7: For PSR-7/PSR-17 factories with Slim
- slim/slim: For Slim Framework integration
- symfony/psr-http-message-bridge: For Laravel PSR-7 conversion
This package is not auto-updated.
Last update: 2025-07-12 04:32:07 UTC
README
WaaSuP (Website as a Server unleashing Power) - A production-ready, SaaS-oriented Model Context Protocol (MCP) server implementation for PHP. Built with enterprise-grade features including OAuth 2.1 authentication, real-time Server-Sent Events (SSE), and comprehensive tool management.
🚀 Try It Live
Want to see WaaSuP in action? Try our live demo at seolinkmap.com/mcp-repo with your favorite LLM or agentic tool! This demo showcases the server's capabilities with a public Website-as-a-Server (MCP authless).
Built by SEOLinkMap - The public production implementation powering chat and agentic tools to use with our existing SEO intelligence platform.
✨ Features
- 🔐 OAuth 2.1 Authentication - Complete OAuth flow with token validation and scope management
- ⚡ Real-time SSE Transport - Server-Sent Events for instant message delivery
- 🛠️ Flexible Tool System - Easy tool registration with both class-based and callable approaches
- 🏢 Multi-tenant Architecture - Agency/user context isolation for SaaS applications
- 📊 Production Ready - Comprehensive logging, error handling, and session management
- 🔌 Framework Agnostic - PSR-compliant with Slim Framework integration included
- 💾 Database & Memory Storage - Multiple storage backends for different deployment scenarios
- 🌐 CORS Support - Full cross-origin resource sharing configuration
Requirements
- PHP 8.1 or higher
- Composer
- Database (MySQL/PostgreSQL recommended for production)
Installation
composer require seolinkmap/waasup # For PSR-3 logging support composer require monolog/monolog # For Slim Framework integration composer require slim/slim slim/psr7
Database Setup
- Import the database schema:
mysql -u your_user -p your_database < vendor/seolinkmap/waasup/examples/database/database-schema.sql
- Create your first agency (or configure your existing database):
INSERT INTO mcp_agencies (uuid, name, active) VALUES ('550e8400-e29b-41d4-a716-446655440000', 'My Company', 1);
- Create an OAuth token:
INSERT INTO mcp_oauth_tokens ( access_token, scope, expires_at, agency_id ) VALUES ( 'your-secret-token-here', 'mcp:read mcp:write', DATE_ADD(NOW(), INTERVAL 1 YEAR), 1 );
Quick Start
Basic Server Setup
<?php require_once __DIR__ . '/vendor/autoload.php'; use Slim\Factory\AppFactory; use Slim\Psr7\Factory\{ResponseFactory, StreamFactory}; use Seolinkmap\Waasup\Storage\DatabaseStorage; use Seolinkmap\Waasup\Tools\Registry\ToolRegistry; use Seolinkmap\Waasup\Prompts\Registry\PromptRegistry; use Seolinkmap\Waasup\Resources\Registry\ResourceRegistry; use Seolinkmap\Waasup\Integration\Slim\SlimMCPProvider; // Database connection $pdo = new PDO('mysql:host=localhost;dbname=mcp_server', $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); // Initialize components $storage = new DatabaseStorage($pdo, ['table_prefix' => 'mcp_']); $toolRegistry = new ToolRegistry(); $promptRegistry = new PromptRegistry(); $resourceRegistry = new ResourceRegistry(); $responseFactory = new ResponseFactory(); $streamFactory = new StreamFactory(); // Configuration $config = [ 'server_info' => [ 'name' => 'My MCP Server', 'version' => '1.0.0' ], 'auth' => [ 'context_types' => ['agency'], 'base_url' => 'https://your-domain.com' ] ]; // Create MCP provider $mcpProvider = new SlimMCPProvider( $storage, $toolRegistry, $promptRegistry, $resourceRegistry, $responseFactory, $streamFactory, $config ); // Setup Slim app $app = AppFactory::create(); $app->addErrorMiddleware(true, true, true); // OAuth discovery endpoints $app->get('/.well-known/oauth-authorization-server', [$mcpProvider, 'handleAuthDiscovery']); // Main MCP endpoint $app->map(['GET', 'POST', 'OPTIONS'], '/mcp/{agencyUuid}[/{sessID}]', [$mcpProvider, 'handleMCP']) ->add($mcpProvider->getAuthMiddleware()); $app->run();
Adding Tools
Built-in Tools
The server includes several built-in tools that you can register:
use Seolinkmap\Waasup\Tools\Built\{PingTool, ServerInfoTool}; // Register built-in tools $toolRegistry->registerTool(new PingTool()); $toolRegistry->registerTool(new ServerInfoTool($config));
ping
- Test connectivity and response timesserver_info
- Get server information and capabilities
Callable Tool (Simple)
$toolRegistry->register('get_weather', function($params, $context) { $location = $params['location'] ?? 'Unknown'; // Your weather API logic here return [ 'location' => $location, 'temperature' => '22°C', 'condition' => 'Sunny', 'timestamp' => date('c') ]; }, [ 'description' => 'Get weather information for a location', 'inputSchema' => [ 'type' => 'object', 'properties' => [ 'location' => [ 'type' => 'string', 'description' => 'Location to get weather for' ] ], 'required' => ['location'] ] ]);
Class-based Tool (Advanced)
use Seolinkmap\Waasup\Tools\AbstractTool; class DatabaseQueryTool extends AbstractTool { private PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; parent::__construct( 'db_query', 'Execute safe database queries', [ 'properties' => [ 'query' => ['type' => 'string'], 'params' => ['type' => 'array'] ], 'required' => ['query'] ] ); } public function execute(array $parameters, array $context = []): array { $this->validateParameters($parameters); // Implement your database query logic // with proper security checks return ['result' => 'Query executed successfully']; } } // Register the tool $toolRegistry->registerTool(new DatabaseQueryTool($pdo));
Adding Prompts
$promptRegistry->register('greeting', function($arguments, $context) { $name = $arguments['name'] ?? 'there'; return [ 'description' => 'A friendly greeting prompt', 'messages' => [ [ 'role' => 'user', 'content' => [ 'type' => 'text', 'text' => "Please greet {$name} in a friendly way." ] ] ] ]; }, [ 'description' => 'Generate a friendly greeting prompt', 'inputSchema' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'type' => 'string', 'description' => 'Name of the person to greet' ] ] ] ]);
Adding Resources
$resourceRegistry->register('server://status', function($uri, $context) { return [ 'contents' => [ [ 'uri' => $uri, 'mimeType' => 'application/json', 'text' => json_encode([ 'status' => 'healthy', 'timestamp' => date('c'), 'uptime' => time() - $_SERVER['REQUEST_TIME'] ]) ] ] ]; }, [ 'name' => 'Server Status', 'description' => 'Current server status and health information', 'mimeType' => 'application/json' ]);
OAuth 2.1 Flow
Discovery
- Client discovers endpoints:
GET /.well-known/oauth-authorization-server
- Client requests authorization:
GET /oauth/authorize?response_type=code&client_id=YOUR_CLIENT&redirect_uri=YOUR_CALLBACK
- Client exchanges code for token:
POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=AUTH_CODE&client_id=YOUR_CLIENT
MCP Protocol Usage
SSE Connection & Requests
# Establish SSE connection GET /mcp/550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer your-access-token # Send MCP requests POST /mcp/550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer your-access-token Content-Type: application/json { "jsonrpc": "2.0", "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "My MCP Client", "version": "1.0.0" } }, "id": 1 }
Configuration
Server Configuration
$config = [ 'supported_versions' => ['2025-03-18', '2024-11-05'], 'server_info' => [ 'name' => 'Your MCP Server', 'version' => '1.0.0' ], 'auth' => [ 'context_types' => ['agency', 'user'], 'validate_scope' => true, 'required_scopes' => ['mcp:read'], 'base_url' => 'https://your-domain.com' ], 'sse' => [ 'keepalive_interval' => 1, // seconds 'max_connection_time' => 1800, // 30 minutes 'switch_interval_after' => 60 // switch to longer intervals ] ];
Database Storage
$storage = new DatabaseStorage($pdo, [ 'table_prefix' => 'mcp_', 'cleanup_interval' => 3600 // Clean expired data every hour ]);
Memory Storage (Development/Testing)
use Seolinkmap\Waasup\Storage\MemoryStorage; $storage = new MemoryStorage(); // Add test data $storage->addContext('550e8400-e29b-41d4-a716-446655440000', 'agency', [ 'id' => 1, 'name' => 'Test Agency', 'active' => true ]); $storage->addToken('test-token', [ 'agency_id' => 1, 'scope' => 'mcp:read mcp:write', 'expires_at' => time() + 3600, 'revoked' => false ]);
Logging
With Monolog
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('mcp-server'); $logger->pushHandler(new StreamHandler('/var/log/mcp-server.log', Logger::INFO)); $mcpProvider = new SlimMCPProvider( $storage, $toolRegistry, $promptRegistry, $resourceRegistry, $responseFactory, $streamFactory, $config, $logger // Pass logger here );
Custom Logger
class CustomLogger implements Psr\Log\LoggerInterface { public function info($message, array $context = []): void { // Your custom logging logic file_put_contents('/tmp/mcp.log', date('Y-m-d H:i:s') . " [INFO] $message " . json_encode($context) . "\n", FILE_APPEND ); } // Implement other LoggerInterface methods... }
Framework Integration
Laravel
Service Provider Registration
Add the service provider to your Laravel application:
// Add to config/app.php providers array 'providers' => [ // ... Seolinkmap\Waasup\Integration\Laravel\LaravelServiceProvider::class, ],
Creating an MCP Controller
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\DB; use Seolinkmap\Waasup\MCPSaaSServer; use Seolinkmap\Waasup\Storage\DatabaseStorage; use Seolinkmap\Waasup\Tools\Registry\ToolRegistry; use Seolinkmap\Waasup\Prompts\Registry\PromptRegistry; use Seolinkmap\Waasup\Resources\Registry\ResourceRegistry; use Seolinkmap\Waasup\Integration\Laravel\LaravelMCPProvider; class MCPController extends Controller { public function handleMCP(Request $request, string $agencyUuid, ?string $sessID = null): Response { // Initialize storage with Laravel's database connection $storage = new DatabaseStorage(DB::connection()->getPdo(), ['table_prefix' => 'mcp_']); // Initialize registries $toolRegistry = new ToolRegistry(); $promptRegistry = new PromptRegistry(); $resourceRegistry = new ResourceRegistry(); // Register your tools $toolRegistry->register('get_user_count', function($params, $context) { return ['user_count' => \App\Models\User::count()]; }, [ 'description' => 'Get total user count', 'inputSchema' => ['type' => 'object'] ]); // Configuration $config = [ 'server_info' => [ 'name' => config('app.name') . ' MCP Server', 'version' => '1.0.0' ], 'auth' => [ 'context_types' => ['agency'], 'base_url' => config('app.url') ] ]; // Create and use the Laravel MCP provider $mcpProvider = app(LaravelMCPProvider::class); return $mcpProvider->handleMCP($request); } public function handleAuthDiscovery(Request $request): Response { $mcpProvider = app(LaravelMCPProvider::class); return $mcpProvider->handleAuthDiscovery($request); } }
Route Registration
// In routes/web.php or routes/api.php use App\Http\Controllers\MCPController; // OAuth discovery endpoints Route::get('/.well-known/oauth-authorization-server', [MCPController::class, 'handleAuthDiscovery']); // MCP endpoints with authentication middleware Route::group(['middleware' => ['mcp.auth']], function () { Route::match(['GET', 'POST'], '/mcp/{agencyUuid}/{sessID?}', [MCPController::class, 'handleMCP']); });
Advanced Laravel Integration
// Create a dedicated MCP service <?php namespace App\Services; use Seolinkmap\Waasup\MCPSaaSServer; use Seolinkmap\Waasup\Storage\DatabaseStorage; use Seolinkmap\Waasup\Tools\Registry\ToolRegistry; class MCPService { private MCPSaaSServer $server; public function __construct() { $storage = new DatabaseStorage(\DB::connection()->getPdo()); $toolRegistry = new ToolRegistry(); // Register Laravel-specific tools $this->registerLaravelTools($toolRegistry); $config = [ 'server_info' => [ 'name' => config('app.name') . ' MCP API', 'version' => config('app.version', '1.0.0') ], 'auth' => [ 'base_url' => config('app.url') ] ]; $this->server = new MCPSaaSServer($storage, $toolRegistry, /*...*/, $config); } private function registerLaravelTools(ToolRegistry $registry): void { // Register tools that use Laravel features $registry->register('get_models', function($params, $context) { return [ 'users' => \App\Models\User::count(), 'posts' => \App\Models\Post::count(), ]; }); $registry->register('send_notification', function($params, $context) { // Use Laravel's notification system \Notification::send($user, new \App\Notifications\MCPNotification($params)); return ['status' => 'sent']; }); } public function getServer(): MCPSaaSServer { return $this->server; } }
Standalone (PSR-7)
use Seolinkmap\Waasup\MCPSaaSServer; $server = new MCPSaaSServer($storage, $toolRegistry, $promptRegistry, $resourceRegistry, $config, $logger); // Handle PSR-7 request $response = $server->handle($request, $response);
Advanced Usage
Custom Authentication
class CustomAuthMiddleware extends AuthMiddleware { protected function extractContextId(Request $request): ?string { // Custom logic to extract context from request $customHeader = $request->getHeaderLine('X-Custom-Context'); return $customHeader ?: parent::extractContextId($request); } }
Tool Annotations
$toolRegistry->register('dangerous_operation', $handler, [ 'description' => 'Performs a dangerous operation', 'annotations' => [ 'readOnlyHint' => false, 'destructiveHint' => true, 'idempotentHint' => false ] ]);
Session Management
// Custom session handling $server->addTool('get_session', function($params, $context) { $sessionData = $context['session_data'] ?? []; return ['session' => $sessionData]; });
Built-in Tools
The server includes several built-in tools for testing and basic functionality that you can register:
Ping Tool
use Seolinkmap\Waasup\Tools\Built\PingTool; // Register the ping tool $toolRegistry->registerTool(new PingTool()); // Tests connectivity and returns server timestamp
Server Info Tool
use Seolinkmap\Waasup\Tools\Built\ServerInfoTool; // Register the server info tool $toolRegistry->registerTool(new ServerInfoTool($config)); // Returns server configuration and capabilities
API Reference
MCP Methods
Method | Description |
---|---|
tools/list |
List all available tools |
tools/call |
Execute a specific tool |
prompts/list |
List all available prompts |
prompts/get |
Get a specific prompt |
resources/list |
List all available resources |
resources/read |
Read a specific resource |
resources/templates/list |
List resource templates |
initialize |
Initialize MCP session |
ping |
Health check endpoint |
Error Codes
Code | Description |
---|---|
-32000 |
Authentication required |
-32001 |
Session required |
-32600 |
Invalid Request |
-32601 |
Method not found |
-32602 |
Invalid params |
-32603 |
Internal error |
-32700 |
Parse error |
Testing
# Run tests composer test # Static analysis composer analyse # Code formatting composer format
Example Test
use PHPUnit\Framework\TestCase; use Seolinkmap\Waasup\Storage\MemoryStorage; class MCPServerTest extends TestCase { public function testToolExecution() { $storage = new MemoryStorage(); $toolRegistry = new ToolRegistry(); $toolRegistry->register('test_tool', function($params) { return ['result' => 'success']; }); $result = $toolRegistry->execute('test_tool', []); $this->assertEquals(['result' => 'success'], $result); } }
Security
- Token Validation: All requests require valid OAuth tokens
- Scope Checking: Configurable scope validation for fine-grained access
- SQL Injection: All database queries use prepared statements
- Session Security: Cryptographically secure session ID generation
- CORS: Configurable CORS policies for cross-origin requests
Deployment
Docker
FROM php:8.1-fpm-alpine RUN docker-php-ext-install pdo pdo_mysql COPY . /var/www/html WORKDIR /var/www/html RUN composer install --no-dev --optimize-autoloader EXPOSE 9000 CMD ["php-fpm"]
Nginx Configuration
server { listen 80; server_name your-mcp-server.com; root /var/www/html/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # SSE connection timeout location /mcp/ { proxy_read_timeout 1800s; proxy_send_timeout 1800s; } }
Contributing
We welcome contributions! This server is actively used in production at SEOLinkMap where it powers our SEO intelligence platform.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
composer test
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Development
git clone https://github.com/seolinkmap/waasup.git cd waasup composer install cp .env.example .env # Configure your .env file php -S localhost:8000 -t examples/slim-framework/
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Anthropic for the Model Context Protocol specification
- PHP-FIG for the PSR standards
- Slim Framework for the excellent HTTP foundation
Support & Community
- 🌐 Live Demo: https://seolinkmap.com/mcp-repo (use WaaSuP server to chat with WaaSuP repository... help, install, tool building)
- 📖 Documentation: GitHub Wiki
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
Built with ❤️ by SEOLinkMap for the MCP community.