diff --git a/README.md b/README.md index 121d8dd4..3369243f 100644 --- a/README.md +++ b/README.md @@ -22,29 +22,16 @@ Until the first major release, this SDK is considered Features - [x] Bring back PHP-MCP examples -- [ ] Glue handler, registry and reference handlers -- [ ] Revive `ServerBuilder` -- [ ] Revive transports - - [ ] Streamable Transport https://github.com/modelcontextprotocol/php-sdk/issues/7 - - [ ] Http/SSE-based Transport https://github.com/modelcontextprotocol/php-sdk/issues/8 +- [x] Glue handler, registry and reference handlers +- [x] Revive `ServerBuilder` +- [x] Revive transports + - [x] Streamable Transport https://github.com/modelcontextprotocol/php-sdk/issues/7 + - [ ] ~~Http/SSE-based Transport https://github.com/modelcontextprotocol/php-sdk/issues/8~~ - [ ] Support pagination - [ ] Support Schema validation - [ ] Support multiple versions of the MCP specification https://github.com/modelcontextprotocol/php-sdk/issues/14 - [ ] (Re-)Implement missing Notification & Request Handlers https://github.com/modelcontextprotocol/php-sdk/issues/9 ---- - -Examples working -- [x] 01-discovery-stdio-calculator -- [ ] 02-discovery-http-userprofile -- [x] 03-manual-registration-stdio -- [ ] 04-combined-registration-http -- [ ] 05-stdio-env-variables -- [ ] 06-custom-dependencies-stdio -- [ ] 07-complex-tool-schema-http -- [ ] 08-schema-showcase-streamable -- [ ] 09-standalone-cli - ## Installation ```bash diff --git a/examples/01-discovery-stdio-calculator/McpElements.php b/examples/01-discovery-stdio-calculator/McpElements.php index 8f05b96c..cde2ecc4 100644 --- a/examples/01-discovery-stdio-calculator/McpElements.php +++ b/examples/01-discovery-stdio-calculator/McpElements.php @@ -19,7 +19,7 @@ /** * @phpstan-type Config array{precision: int, allow_negative: bool} */ -class McpElements +final class McpElements { /** * @var Config diff --git a/examples/02-discovery-http-userprofile/McpElements.php b/examples/02-discovery-http-userprofile/McpElements.php index 600f6ecc..815d3d54 100644 --- a/examples/02-discovery-http-userprofile/McpElements.php +++ b/examples/02-discovery-http-userprofile/McpElements.php @@ -18,9 +18,16 @@ use Mcp\Capability\Attribute\McpTool; use Psr\Log\LoggerInterface; -class McpElements +/** + * @phpstan-type User array{name: string, email: string, role: string} + */ +final class McpElements { - // Simulate a simple user database + /** + * Simulate a simple user database. + * + * @var array + */ private array $users = [ '101' => ['name' => 'Alice', 'email' => 'alice@example.com', 'role' => 'admin'], '102' => ['name' => 'Bob', 'email' => 'bob@example.com', 'role' => 'user'], @@ -28,7 +35,7 @@ class McpElements ]; public function __construct( - private LoggerInterface $logger, + private readonly LoggerInterface $logger, ) { $this->logger->debug('HttpUserProfileExample McpElements instantiated.'); } @@ -38,7 +45,7 @@ public function __construct( * * @param string $userId the ID of the user (from URI) * - * @return array user profile data + * @return User user profile data * * @throws McpServerException if the user is not found */ @@ -64,7 +71,7 @@ public function getUserProfile( /** * Retrieves a list of all known user IDs. * - * @return array list of user IDs + * @return int[] list of user IDs */ #[McpResource( uri: 'user://list/ids', @@ -86,7 +93,7 @@ public function listUserIds(): array * @param string $userId the ID of the user to message * @param string|null $customMessage an optional custom message part * - * @return array status of the operation + * @return array status of the operation */ #[McpTool(name: 'send_welcome')] public function sendWelcomeMessage(string $userId, ?string $customMessage = null): array @@ -106,6 +113,9 @@ public function sendWelcomeMessage(string $userId, ?string $customMessage = null return ['success' => true, 'message_sent' => $message]; } + /** + * @return array + */ #[McpTool(name: 'test_tool_without_params')] public function testToolWithoutParams(): array { @@ -118,7 +128,7 @@ public function testToolWithoutParams(): array * @param string $userId the user ID to generate the bio for * @param string $tone Desired tone (e.g., 'formal', 'casual'). * - * @return array prompt messages + * @return array[] prompt messages * * @throws McpServerException if user not found */ diff --git a/examples/02-discovery-http-userprofile/UserIdCompletionProvider.php b/examples/02-discovery-http-userprofile/UserIdCompletionProvider.php index c11fb609..2b1d0555 100644 --- a/examples/02-discovery-http-userprofile/UserIdCompletionProvider.php +++ b/examples/02-discovery-http-userprofile/UserIdCompletionProvider.php @@ -13,12 +13,12 @@ use Mcp\Capability\Prompt\Completion\ProviderInterface; -class UserIdCompletionProvider implements ProviderInterface +final class UserIdCompletionProvider implements ProviderInterface { + private const AVAILABLE_USER_IDS = ['101', '102', '103']; + public function getCompletions(string $currentValue): array { - $availableUserIds = ['101', '102', '103']; - - return array_filter($availableUserIds, fn (string $userId) => str_contains($userId, $currentValue)); + return array_filter(self::AVAILABLE_USER_IDS, fn (string $userId) => str_contains($userId, $currentValue)); } } diff --git a/examples/02-discovery-http-userprofile/server.php b/examples/02-discovery-http-userprofile/server.php index 163d495c..ce3ac347 100644 --- a/examples/02-discovery-http-userprofile/server.php +++ b/examples/02-discovery-http-userprofile/server.php @@ -54,7 +54,7 @@ function (float $a, float $b, string $operation = 'add'): array { function (): array { $memoryUsage = memory_get_usage(true); $memoryPeak = memory_get_peak_usage(true); - $uptime = time() - $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); + $uptime = time() - ($_SERVER['REQUEST_TIME_FLOAT'] ?? time()); $serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? 'CLI'; return [ diff --git a/examples/03-manual-registration-stdio/SimpleHandlers.php b/examples/03-manual-registration-stdio/SimpleHandlers.php index d646486f..e581c406 100644 --- a/examples/03-manual-registration-stdio/SimpleHandlers.php +++ b/examples/03-manual-registration-stdio/SimpleHandlers.php @@ -13,7 +13,7 @@ use Psr\Log\LoggerInterface; -class SimpleHandlers +final class SimpleHandlers { private string $appVersion = '1.0-manual'; @@ -54,7 +54,7 @@ public function getAppVersion(): string * * @param string $userName the name of the user * - * @return array the prompt messages + * @return array[] the prompt messages */ public function greetingPrompt(string $userName): array { @@ -70,7 +70,7 @@ public function greetingPrompt(string $userName): array * * @param string $itemId the ID of the item * - * @return array item details + * @return array item details */ public function getItemDetails(string $itemId): array { diff --git a/examples/04-combined-registration-http/DiscoveredElements.php b/examples/04-combined-registration-http/DiscoveredElements.php index aacaf2a9..3c3edf81 100644 --- a/examples/04-combined-registration-http/DiscoveredElements.php +++ b/examples/04-combined-registration-http/DiscoveredElements.php @@ -14,7 +14,7 @@ use Mcp\Capability\Attribute\McpResource; use Mcp\Capability\Attribute\McpTool; -class DiscoveredElements +final class DiscoveredElements { /** * A tool discovered via attributes. diff --git a/examples/04-combined-registration-http/ManualHandlers.php b/examples/04-combined-registration-http/ManualHandlers.php index ebac86e0..fef7216a 100644 --- a/examples/04-combined-registration-http/ManualHandlers.php +++ b/examples/04-combined-registration-http/ManualHandlers.php @@ -13,10 +13,10 @@ use Psr\Log\LoggerInterface; -class ManualHandlers +final class ManualHandlers { public function __construct( - private LoggerInterface $logger, + private readonly LoggerInterface $logger, ) { } diff --git a/examples/04-combined-registration-http/server.php b/examples/04-combined-registration-http/server.php index e69c5aa5..69225020 100644 --- a/examples/04-combined-registration-http/server.php +++ b/examples/04-combined-registration-http/server.php @@ -14,7 +14,7 @@ chdir(__DIR__); use Laminas\HttpHandlerRunner\Emitter\SapiEmitter; -use Mcp\CombinedHttpExample\Manual\ManualHandlers; +use Mcp\Example\CombinedHttpExample\ManualHandlers; use Mcp\Server; use Mcp\Server\Session\FileSessionStore; use Mcp\Server\Transport\StreamableHttpTransport; diff --git a/examples/05-stdio-env-variables/EnvToolHandler.php b/examples/05-stdio-env-variables/EnvToolHandler.php index 002f7cf0..49c914d5 100644 --- a/examples/05-stdio-env-variables/EnvToolHandler.php +++ b/examples/05-stdio-env-variables/EnvToolHandler.php @@ -13,7 +13,7 @@ use Mcp\Capability\Attribute\McpTool; -class EnvToolHandler +final class EnvToolHandler { /** * Performs an action that can be modified by an environment variable. @@ -21,7 +21,7 @@ class EnvToolHandler * * @param string $input some input data * - * @return array the result, varying by APP_MODE + * @return array the result, varying by APP_MODE */ #[McpTool(name: 'process_data_by_mode')] public function processData(string $input): array diff --git a/examples/06-custom-dependencies-stdio/McpTaskHandlers.php b/examples/06-custom-dependencies-stdio/McpTaskHandlers.php index ea32adb6..bf5ce556 100644 --- a/examples/06-custom-dependencies-stdio/McpTaskHandlers.php +++ b/examples/06-custom-dependencies-stdio/McpTaskHandlers.php @@ -13,16 +13,19 @@ use Mcp\Capability\Attribute\McpResource; use Mcp\Capability\Attribute\McpTool; -use Mcp\DependenciesStdioExample\Services\StatsServiceInterface; -use Mcp\DependenciesStdioExample\Services\TaskRepositoryInterface; +use Mcp\Example\DependenciesStdioExample\Service\StatsServiceInterface; +use Mcp\Example\DependenciesStdioExample\Service\TaskRepositoryInterface; use Psr\Log\LoggerInterface; -class McpTaskHandlers +/** + * @phpstan-import-type Task from TaskRepositoryInterface + */ +final class McpTaskHandlers { public function __construct( - private TaskRepositoryInterface $taskRepo, - private StatsServiceInterface $statsService, - private LoggerInterface $logger, + private readonly TaskRepositoryInterface $taskRepo, + private readonly StatsServiceInterface $statsService, + private readonly LoggerInterface $logger, ) { $this->logger->info('McpTaskHandlers instantiated with dependencies.'); } @@ -33,7 +36,7 @@ public function __construct( * @param string $userId the ID of the user * @param string $description the task description * - * @return array the created task details + * @return Task the created task details */ #[McpTool(name: 'add_task')] public function addTask(string $userId, string $description): array @@ -48,7 +51,7 @@ public function addTask(string $userId, string $description): array * * @param string $userId the ID of the user * - * @return array a list of tasks + * @return Task[] a list of tasks */ #[McpTool(name: 'list_user_tasks')] public function listUserTasks(string $userId): array @@ -63,7 +66,7 @@ public function listUserTasks(string $userId): array * * @param int $taskId the ID of the task to complete * - * @return array status of the operation + * @return array status of the operation */ #[McpTool(name: 'complete_task')] public function completeTask(int $taskId): array @@ -77,7 +80,7 @@ public function completeTask(int $taskId): array /** * Provides current system statistics. * - * @return array system statistics + * @return array system statistics */ #[McpResource(uri: 'stats://system/overview', name: 'system_stats', mimeType: 'application/json')] public function getSystemStatistics(): array diff --git a/examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php b/examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php index a6c5315c..47526c4c 100644 --- a/examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php +++ b/examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php @@ -13,17 +13,20 @@ use Psr\Log\LoggerInterface; -class InMemoryTaskRepository implements TaskRepositoryInterface +/** + * @phpstan-import-type Task from TaskRepositoryInterface + */ +final class InMemoryTaskRepository implements TaskRepositoryInterface { + /** + * @var array + */ private array $tasks = []; - private int $nextTaskId = 1; - private LoggerInterface $logger; - - public function __construct(LoggerInterface $logger) - { - $this->logger = $logger; + public function __construct( + private readonly LoggerInterface $logger, + ) { // Add some initial tasks $this->addTask('user1', 'Buy groceries'); $this->addTask('user1', 'Write MCP example'); diff --git a/examples/06-custom-dependencies-stdio/Service/StatsServiceInterface.php b/examples/06-custom-dependencies-stdio/Service/StatsServiceInterface.php index 2c94c002..b8485d2a 100644 --- a/examples/06-custom-dependencies-stdio/Service/StatsServiceInterface.php +++ b/examples/06-custom-dependencies-stdio/Service/StatsServiceInterface.php @@ -13,5 +13,8 @@ interface StatsServiceInterface { + /** + * @return array + */ public function getSystemStats(): array; } diff --git a/examples/06-custom-dependencies-stdio/Service/SystemStatsService.php b/examples/06-custom-dependencies-stdio/Service/SystemStatsService.php index 845e3486..075dd8a0 100644 --- a/examples/06-custom-dependencies-stdio/Service/SystemStatsService.php +++ b/examples/06-custom-dependencies-stdio/Service/SystemStatsService.php @@ -11,13 +11,11 @@ namespace Mcp\Example\DependenciesStdioExample\Service; -class SystemStatsService implements StatsServiceInterface +final class SystemStatsService implements StatsServiceInterface { - private TaskRepositoryInterface $taskRepository; - - public function __construct(TaskRepositoryInterface $taskRepository) - { - $this->taskRepository = $taskRepository; + public function __construct( + private readonly TaskRepositoryInterface $taskRepository, + ) { } public function getSystemStats(): array diff --git a/examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php b/examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php index 7ae1c8a4..975cc711 100644 --- a/examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php +++ b/examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php @@ -11,12 +11,24 @@ namespace Mcp\Example\DependenciesStdioExample\Service; +/** + * @phpstan-type Task array{id: int, userId: string, description: string, completed: bool, createdAt: string} + */ interface TaskRepositoryInterface { + /** + * @return Task + */ public function addTask(string $userId, string $description): array; + /** + * @return Task[] + */ public function getTasksForUser(string $userId): array; + /** + * @return Task[] + */ public function getAllTasks(): array; public function completeTask(int $taskId): bool; diff --git a/examples/06-custom-dependencies-stdio/server.php b/examples/06-custom-dependencies-stdio/server.php index 3a6d86ff..ac85d0ac 100644 --- a/examples/06-custom-dependencies-stdio/server.php +++ b/examples/06-custom-dependencies-stdio/server.php @@ -13,7 +13,10 @@ require_once dirname(__DIR__).'/bootstrap.php'; chdir(__DIR__); -use Mcp\DependenciesStdioExample\Services; +use Mcp\Example\DependenciesStdioExample\Service\InMemoryTaskRepository; +use Mcp\Example\DependenciesStdioExample\Service\StatsServiceInterface; +use Mcp\Example\DependenciesStdioExample\Service\SystemStatsService; +use Mcp\Example\DependenciesStdioExample\Service\TaskRepositoryInterface; use Mcp\Server; use Mcp\Server\Transport\StdioTransport; @@ -21,11 +24,11 @@ $container = container(); -$taskRepo = new Services\InMemoryTaskRepository(logger()); -$container->set(Services\TaskRepositoryInterface::class, $taskRepo); +$taskRepo = new InMemoryTaskRepository(logger()); +$container->set(TaskRepositoryInterface::class, $taskRepo); -$statsService = new Services\SystemStatsService($taskRepo); -$container->set(Services\StatsServiceInterface::class, $statsService); +$statsService = new SystemStatsService($taskRepo); +$container->set(StatsServiceInterface::class, $statsService); $server = Server::make() ->setServerInfo('Task Manager Server', '1.0.0') diff --git a/examples/07-complex-tool-schema-http/McpEventScheduler.php b/examples/07-complex-tool-schema-http/McpEventScheduler.php index 0b29cdeb..a6d9db92 100644 --- a/examples/07-complex-tool-schema-http/McpEventScheduler.php +++ b/examples/07-complex-tool-schema-http/McpEventScheduler.php @@ -12,14 +12,14 @@ namespace Mcp\Example\ComplexSchemaHttpExample; use Mcp\Capability\Attribute\McpTool; -use Mcp\ComplexSchemaHttpExample\Model\EventPriority; -use Mcp\ComplexSchemaHttpExample\Model\EventType; +use Mcp\Example\ComplexSchemaHttpExample\Model\EventPriority; +use Mcp\Example\ComplexSchemaHttpExample\Model\EventType; use Psr\Log\LoggerInterface; -class McpEventScheduler +final class McpEventScheduler { public function __construct( - private LoggerInterface $logger, + private readonly LoggerInterface $logger, ) { } @@ -35,17 +35,17 @@ public function __construct( * @param string[]|null $attendees an optional list of attendee email addresses * @param bool $sendInvites send calendar invites to attendees? Defaults to true if attendees are provided * - * @return array confirmation of the scheduled event + * @return array confirmation of the scheduled event */ #[McpTool(name: 'schedule_event')] public function scheduleEvent( string $title, string $date, EventType $type, - ?string $time = null, // Optional, nullable - EventPriority $priority = EventPriority::Normal, // Optional with enum default - ?array $attendees = null, // Optional array of strings, nullable - bool $sendInvites = true, // Optional with default + ?string $time = null, + EventPriority $priority = EventPriority::Normal, + ?array $attendees = null, + bool $sendInvites = true, ): array { $this->logger->info("Tool 'schedule_event' called", compact('title', 'date', 'type', 'time', 'priority', 'attendees', 'sendInvites')); @@ -65,7 +65,7 @@ public function scheduleEvent( return [ 'success' => true, - 'message' => "Event '{$title}' scheduled successfully for {$date}.", + 'message' => \sprintf('Event "%s" scheduled successfully for "%s".', $title, $date), 'event_details' => $eventDetails, ]; } diff --git a/examples/08-schema-showcase-streamable/SchemaShowcaseElements.php b/examples/08-schema-showcase-streamable/SchemaShowcaseElements.php index 3521e2e3..ea11b687 100644 --- a/examples/08-schema-showcase-streamable/SchemaShowcaseElements.php +++ b/examples/08-schema-showcase-streamable/SchemaShowcaseElements.php @@ -13,12 +13,20 @@ use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\Schema; +use Psr\Log\LoggerInterface; -class SchemaShowcaseElements +final class SchemaShowcaseElements { + public function __construct( + private readonly LoggerInterface $logger, + ) { + } + /** * Validates and formats text with string constraints. * Demonstrates: minLength, maxLength, pattern validation. + * + * @return array */ #[McpTool( name: 'format_text', @@ -41,7 +49,7 @@ enum: ['uppercase', 'lowercase', 'title', 'sentence'] )] string $format = 'sentence', ): array { - fwrite(\STDERR, "Format text tool called: text='$text', format='$format'\n"); + $this->logger->info(\sprintf('Tool format_text called with text: %s and format: %s', $text, $format)); $formatted = match ($format) { 'uppercase' => strtoupper($text), @@ -63,6 +71,8 @@ enum: ['uppercase', 'lowercase', 'title', 'sentence'] * Performs mathematical operations with numeric constraints. * * Demonstrates: METHOD-LEVEL Schema + * + * @return array */ #[McpTool(name: 'calculate_range')] #[Schema( @@ -97,7 +107,7 @@ enum: ['uppercase', 'lowercase', 'title', 'sentence'] )] public function calculateRange(float $first, float $second, string $operation, int $precision = 2): array { - fwrite(\STDERR, "Calculate range tool called: $first $operation $second (precision: $precision)\n"); + $this->logger->info(\sprintf('Tool calculate_range called with: %f %s %f (precision: %d)', $first, $operation, $second, $precision)); $result = match ($operation) { 'add' => $first + $second, @@ -126,6 +136,10 @@ public function calculateRange(float $first, float $second, string $operation, i /** * Processes user profile data with object schema validation. * Demonstrates: object properties, required fields, additionalProperties. + * + * @param array $profile + * + * @return array */ #[McpTool( name: 'validate_profile', @@ -172,7 +186,7 @@ public function validateProfile( )] array $profile, ): array { - fwrite(\STDERR, 'Validate profile tool called with: '.json_encode($profile)."\n"); + $this->logger->info(\sprintf('Tool validate_profile called: %s', json_encode($profile))); $errors = []; $warnings = []; @@ -203,6 +217,10 @@ public function validateProfile( /** * Manages a list of items with array constraints. * Demonstrates: array items, minItems, maxItems, uniqueItems. + * + * @param string[] $items + * + * @return array */ #[McpTool( name: 'manage_list', @@ -230,7 +248,7 @@ enum: ['sort', 'reverse', 'shuffle', 'deduplicate', 'filter_short', 'filter_long )] string $action = 'sort', ): array { - fwrite(\STDERR, 'Manage list tool called with '.\count($items)." items, action: $action\n"); + $this->logger->info(\sprintf('Tool manage_list called with %d items, action: %s', \count($items), $action)); $original = $items; $processed = $items; @@ -273,6 +291,8 @@ enum: ['sort', 'reverse', 'shuffle', 'deduplicate', 'filter_short', 'filter_long /** * Generates configuration with format validation. * Demonstrates: format constraints (date-time, uri, etc). + * + * @return array */ #[McpTool( name: 'generate_config', @@ -316,7 +336,7 @@ enum: ['development', 'staging', 'production'] )] int $port = 8080, ): array { - fwrite(\STDERR, "Generate config tool called for app: $appName\n"); + $this->logger->info(\sprintf('Tool generate_config called for app: %s at %s', $appName, $baseUrl)); $config = [ 'app' => [ @@ -350,6 +370,10 @@ enum: ['development', 'staging', 'production'] /** * Processes time-based data with date-time format validation. * Demonstrates: date-time format, exclusiveMinimum, exclusiveMaximum. + * + * @param string[] $attendees + * + * @return array */ #[McpTool( name: 'schedule_event', @@ -398,7 +422,7 @@ enum: ['low', 'medium', 'high', 'urgent'] )] array $attendees = [], ): array { - fwrite(\STDERR, "Schedule event tool called: $title at $startTime\n"); + $this->logger->info(\sprintf('Tool schedule_event called: %s at %s for %.1f hours', $title, $startTime, $durationHours)); $start = \DateTime::createFromFormat(\DateTime::ISO8601, $startTime); if (!$start) { diff --git a/examples/08-schema-showcase-streamable/server.php b/examples/08-schema-showcase-streamable/server.php index c7a323ea..777b5dda 100644 --- a/examples/08-schema-showcase-streamable/server.php +++ b/examples/08-schema-showcase-streamable/server.php @@ -27,6 +27,7 @@ $server = Server::make() ->setServerInfo('Schema Showcase', '1.0.0') + ->setContainer(container()) ->setLogger(logger()) ->setSession(new FileSessionStore(__DIR__.'/sessions')) ->setDiscovery(__DIR__, ['.']) diff --git a/examples/README.md b/examples/README.md index eb7a63fb..b621c229 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,7 +7,11 @@ README in the `examples/09-standalone-cli` directory. For running an example, you execute the `server.php` like this: ```bash +# For examples using STDIO transport php examples/01-discovery-stdio-calculator/server.php + +# For examples using Streamable HTTP transport +php -S localhost:8000 examples/02-discovery-http-userprofile/server.php ``` You will see debug outputs to help you understand what is happening. diff --git a/examples/bootstrap.php b/examples/bootstrap.php index fef29802..e4a4b98d 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -16,10 +16,7 @@ require_once dirname(__DIR__).'/vendor/autoload.php'; set_exception_handler(function (Throwable $t): never { - fwrite(\STDERR, "[MCP SERVER CRITICAL ERROR]\n"); - fwrite(\STDERR, 'Error: '.$t->getMessage()."\n"); - fwrite(\STDERR, 'File: '.$t->getFile().':'.$t->getLine()."\n"); - fwrite(\STDERR, $t->getTraceAsString()."\n"); + logger()->critical('Uncaught exception: '.$t->getMessage(), ['exception' => $t]); exit(1); }); @@ -42,9 +39,9 @@ public function log($level, Stringable|string $message, array $context = []): vo ([] === $context || !$debug) ? '' : json_encode($context), ); - if ($_SERVER['FILE_LOG'] ?? false) { + if (($_SERVER['FILE_LOG'] ?? false) || !defined('STDERR')) { file_put_contents('dev.log', $logMessage, \FILE_APPEND); - } elseif (defined('STDERR')) { + } else { fwrite(\STDERR, $logMessage); } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 23418901..4dca0b25 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6,318 +6,12 @@ parameters: count: 2 path: examples/02-discovery-http-userprofile/McpElements.php - - - message: '#^Method Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:generateBio\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - - - message: '#^Method Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:getUserProfile\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - - - message: '#^Method Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:listUserIds\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - - - message: '#^Method Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:sendWelcomeMessage\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - - - message: '#^Method Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:testToolWithoutParams\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - message: '#^PHPDoc tag @throws with type Mcp\\Example\\HttpUserProfileExample\\McpServerException is not subtype of Throwable$#' identifier: throws.notThrowable count: 2 path: examples/02-discovery-http-userprofile/McpElements.php - - - message: '#^Property Mcp\\Example\\HttpUserProfileExample\\McpElements\:\:\$users type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/02-discovery-http-userprofile/McpElements.php - - - - message: '#^Expression on left side of \?\? is not nullable\.$#' - identifier: nullCoalesce.expr - count: 1 - path: examples/02-discovery-http-userprofile/server.php - - - - message: '#^Method Mcp\\Example\\ManualStdioExample\\SimpleHandlers\:\:getItemDetails\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/03-manual-registration-stdio/SimpleHandlers.php - - - - message: '#^Method Mcp\\Example\\ManualStdioExample\\SimpleHandlers\:\:greetingPrompt\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/03-manual-registration-stdio/SimpleHandlers.php - - - - message: '#^Class Mcp\\CombinedHttpExample\\Manual\\ManualHandlers not found\.$#' - identifier: class.notFound - count: 2 - path: examples/04-combined-registration-http/server.php - - - - message: '#^Method Mcp\\Example\\StdioEnvVariables\\EnvToolHandler\:\:processData\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/05-stdio-env-variables/EnvToolHandler.php - - - - message: '#^Call to method addTask\(\) on an unknown class Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Call to method completeTask\(\) on an unknown class Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Call to method getSystemStats\(\) on an unknown class Mcp\\DependenciesStdioExample\\Services\\StatsServiceInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Call to method getTasksForUser\(\) on an unknown class Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:addTask\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:completeTask\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:getSystemStatistics\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:listUserTasks\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Parameter \$statsService of method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:__construct\(\) has invalid type Mcp\\DependenciesStdioExample\\Services\\StatsServiceInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Parameter \$taskRepo of method Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:__construct\(\) has invalid type Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Property Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:\$statsService has unknown class Mcp\\DependenciesStdioExample\\Services\\StatsServiceInterface as its type\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Property Mcp\\Example\\DependenciesStdioExample\\McpTaskHandlers\:\:\$taskRepo has unknown class Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface as its type\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/McpTaskHandlers.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\InMemoryTaskRepository\:\:addTask\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\InMemoryTaskRepository\:\:getAllTasks\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\InMemoryTaskRepository\:\:getTasksForUser\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php - - - - message: '#^Property Mcp\\Example\\DependenciesStdioExample\\Service\\InMemoryTaskRepository\:\:\$tasks type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/InMemoryTaskRepository.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\StatsServiceInterface\:\:getSystemStats\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/StatsServiceInterface.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\SystemStatsService\:\:getSystemStats\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/SystemStatsService.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\TaskRepositoryInterface\:\:addTask\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\TaskRepositoryInterface\:\:getAllTasks\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php - - - - message: '#^Method Mcp\\Example\\DependenciesStdioExample\\Service\\TaskRepositoryInterface\:\:getTasksForUser\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/06-custom-dependencies-stdio/Service/TaskRepositoryInterface.php - - - - message: '#^Class Mcp\\DependenciesStdioExample\\Services\\StatsServiceInterface not found\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/server.php - - - - message: '#^Class Mcp\\DependenciesStdioExample\\Services\\TaskRepositoryInterface not found\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/server.php - - - - message: '#^Instantiated class Mcp\\DependenciesStdioExample\\Services\\InMemoryTaskRepository not found\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/server.php - - - - message: '#^Instantiated class Mcp\\DependenciesStdioExample\\Services\\SystemStatsService not found\.$#' - identifier: class.notFound - count: 1 - path: examples/06-custom-dependencies-stdio/server.php - - - - message: '#^Access to constant Normal on an unknown class Mcp\\ComplexSchemaHttpExample\\Model\\EventPriority\.$#' - identifier: class.notFound - count: 1 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Access to property \$name on an unknown class Mcp\\ComplexSchemaHttpExample\\Model\\EventPriority\.$#' - identifier: class.notFound - count: 1 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Access to property \$value on an unknown class Mcp\\ComplexSchemaHttpExample\\Model\\EventType\.$#' - identifier: class.notFound - count: 1 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Method Mcp\\Example\\ComplexSchemaHttpExample\\McpEventScheduler\:\:scheduleEvent\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Parameter \$priority of method Mcp\\Example\\ComplexSchemaHttpExample\\McpEventScheduler\:\:scheduleEvent\(\) has invalid type Mcp\\ComplexSchemaHttpExample\\Model\\EventPriority\.$#' - identifier: class.notFound - count: 2 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Parameter \$type of method Mcp\\Example\\ComplexSchemaHttpExample\\McpEventScheduler\:\:scheduleEvent\(\) has invalid type Mcp\\ComplexSchemaHttpExample\\Model\\EventType\.$#' - identifier: class.notFound - count: 2 - path: examples/07-complex-tool-schema-http/McpEventScheduler.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:calculateRange\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:formatText\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:generateConfig\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:manageList\(\) has parameter \$items with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:manageList\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:scheduleEvent\(\) has parameter \$attendees with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:scheduleEvent\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:validateProfile\(\) has parameter \$profile with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - - - message: '#^Method Mcp\\Example\\SchemaShowcaseExample\\SchemaShowcaseElements\:\:validateProfile\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php - - message: '#^Call to an undefined method Mcp\\Capability\\Registry\\ResourceTemplateReference\:\:handle\(\)\.$#' identifier: method.notFound