From 17e661726dd7ab98bd291af5ce80bab879f4f310 Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Fri, 26 Sep 2025 14:13:34 +0300 Subject: [PATCH 1/2] feat: laravel mcp 0.2 --- composer.json | 2 +- src/Mcp/Prompts/RestifyHowTo.php | 76 +++++++----- src/Mcp/Prompts/RestifyTroubleshooting.php | 87 ++++++++------ src/Mcp/Resources/RestifyApiReference.php | 32 ++++-- src/Mcp/Resources/RestifyDocumentation.php | 32 ++++-- src/Mcp/RestifyDocs.php | 84 ++++++++++---- src/Mcp/Tools/DebugApplicationTool.php | 54 ++++----- src/Mcp/Tools/GenerateActionTool.php | 77 ++++++------- src/Mcp/Tools/GenerateGetterTool.php | 76 ++++++------ src/Mcp/Tools/GenerateMatchFilterTool.php | 99 ++++++++-------- src/Mcp/Tools/GenerateRepositoryTool.php | 88 +++++++------- src/Mcp/Tools/GetCodeExamples.php | 81 +++++++------ src/Mcp/Tools/InstallRestifyTool.php | 128 ++++++++++----------- src/Mcp/Tools/NavigateDocs.php | 65 +++++------ src/Mcp/Tools/SearchRestifyDocs.php | 98 ++++++++-------- src/Mcp/Tools/UpgradeRestifyTool.php | 80 ++++++------- src/RestifyBoostServiceProvider.php | 4 +- 17 files changed, 620 insertions(+), 543 deletions(-) diff --git a/composer.json b/composer.json index 3e17241..40b2c13 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "illuminate/console": "^10.0|^11.0|^12.0", "illuminate/contracts": "^10.0|^11.0|^12.0", "illuminate/support": "^10.0|^11.0|^12.0", - "laravel/mcp": "^0.1.1", + "laravel/mcp": "^0.2.0", "laravel/prompts": "^0.3.6", "spatie/laravel-package-tools": "^1.16", "symfony/yaml": "^6.0|^7.0" diff --git a/src/Mcp/Prompts/RestifyHowTo.php b/src/Mcp/Prompts/RestifyHowTo.php index f2d96cd..534b9ab 100644 --- a/src/Mcp/Prompts/RestifyHowTo.php +++ b/src/Mcp/Prompts/RestifyHowTo.php @@ -5,50 +5,68 @@ namespace BinarCode\RestifyBoost\Mcp\Prompts; use BinarCode\RestifyBoost\Services\DocIndexer; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Prompt; -use Laravel\Mcp\Server\Prompts\PromptInputSchema; -use Laravel\Mcp\Server\Prompts\PromptResult; class RestifyHowTo extends Prompt { public function __construct(protected DocIndexer $indexer) {} - public function name(): string - { - return 'restify-how-to'; - } + /** + * The prompt's name. + */ + protected string $name = 'restify-how-to'; - public function description(): string - { - return 'Get step-by-step guidance on how to accomplish specific tasks with Laravel Restify. This prompt provides structured tutorials and explanations for common development scenarios like creating repositories, defining fields, implementing authentication, adding custom actions, and more.'; - } + /** + * The prompt's title. + */ + protected string $title = 'Laravel Restify How-To Guide'; - public function schema(PromptInputSchema $schema): PromptInputSchema + /** + * Get the prompt's arguments. + * + * @return array + */ + public function arguments(): array { - return $schema - ->string('task') - ->description('What you want to accomplish (e.g., "create a repository", "add custom validation", "implement authentication", "create a custom field")') - ->required() - ->string('context') - ->description('Additional context about your specific use case or requirements') - ->optional() - ->string('difficulty') - ->description('Preferred explanation level: "beginner", "intermediate", "advanced"') - ->optional(); + return [ + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'task', + description: 'What you want to accomplish (e.g., "create a repository", "add custom validation", "implement authentication", "create a custom field")', + required: true + ), + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'context', + description: 'Additional context about your specific use case or requirements', + required: false + ), + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'difficulty', + description: 'Preferred explanation level: "beginner", "intermediate", "advanced"', + required: false + ), + ]; } /** - * @param array $arguments + * Handle the prompt request. */ - public function handle(array $arguments): PromptResult + public function handle(Request $request): Response { try { - $task = trim($arguments['task']); - $context = $arguments['context'] ?? ''; - $difficulty = strtolower($arguments['difficulty'] ?? 'intermediate'); + $validated = $request->validate([ + 'task' => 'required|string|max:200', + 'context' => 'nullable|string|max:500', + 'difficulty' => 'nullable|string|in:beginner,intermediate,advanced', + ]); + + $task = trim($validated['task']); + $context = $validated['context'] ?? ''; + $difficulty = strtolower($validated['difficulty'] ?? 'intermediate'); if (empty($task)) { - return PromptResult::text('Please specify what task you want to accomplish with Laravel Restify.'); + return Response::text('Please specify what task you want to accomplish with Laravel Restify.'); } // Initialize indexer @@ -60,9 +78,9 @@ public function handle(array $arguments): PromptResult // Generate structured how-to guide $howToGuide = $this->generateHowToGuide($task, $context, $difficulty, $searchResults); - return PromptResult::text($howToGuide); + return Response::text($howToGuide); } catch (\Throwable $e) { - return PromptResult::text("I encountered an error while generating the how-to guide: {$e->getMessage()}"); + return Response::text("I encountered an error while generating the how-to guide: {$e->getMessage()}"); } } diff --git a/src/Mcp/Prompts/RestifyTroubleshooting.php b/src/Mcp/Prompts/RestifyTroubleshooting.php index 0230159..a850f93 100644 --- a/src/Mcp/Prompts/RestifyTroubleshooting.php +++ b/src/Mcp/Prompts/RestifyTroubleshooting.php @@ -5,54 +5,75 @@ namespace BinarCode\RestifyBoost\Mcp\Prompts; use BinarCode\RestifyBoost\Services\DocIndexer; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Prompt; -use Laravel\Mcp\Server\Prompts\PromptInputSchema; -use Laravel\Mcp\Server\Prompts\PromptResult; class RestifyTroubleshooting extends Prompt { public function __construct(protected DocIndexer $indexer) {} - public function name(): string - { - return 'restify-troubleshooting'; - } + /** + * The prompt's name. + */ + protected string $name = 'restify-troubleshooting'; - public function description(): string - { - return 'Get help troubleshooting Laravel Restify issues. Provide error messages, describe problems, or ask about common issues to receive targeted solutions and debugging guidance. This prompt helps diagnose and resolve configuration, runtime, and implementation problems.'; - } + /** + * The prompt's title. + */ + protected string $title = 'Laravel Restify Troubleshooting Guide'; - public function schema(PromptInputSchema $schema): PromptInputSchema + /** + * Get the prompt's arguments. + * + * @return array + */ + public function arguments(): array { - return $schema - ->string('issue') - ->description('Describe the problem you\'re experiencing or paste the error message') - ->required() - ->string('context') - ->description('Additional context: what you were trying to do, recent changes, environment details, etc.') - ->optional() - ->string('error_type') - ->description('Type of issue: "error", "performance", "configuration", "unexpected_behavior", or "other"') - ->optional() - ->string('code_snippet') - ->description('Relevant code snippet where the issue occurs') - ->optional(); + return [ + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'issue', + description: 'Describe the problem you\'re experiencing or paste the error message', + required: true + ), + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'context', + description: 'Additional context: what you were trying to do, recent changes, environment details, etc.', + required: false + ), + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'error_type', + description: 'Type of issue: "error", "performance", "configuration", "unexpected_behavior", or "other"', + required: false + ), + new \Laravel\Mcp\Server\Prompts\Argument( + name: 'code_snippet', + description: 'Relevant code snippet where the issue occurs', + required: false + ), + ]; } /** - * @param array $arguments + * Handle the prompt request. */ - public function handle(array $arguments): PromptResult + public function handle(Request $request): Response { try { - $issue = trim($arguments['issue']); - $context = $arguments['context'] ?? ''; - $errorType = strtolower($arguments['error_type'] ?? 'other'); - $codeSnippet = $arguments['code_snippet'] ?? ''; + $validated = $request->validate([ + 'issue' => 'required|string|max:1000', + 'context' => 'nullable|string|max:1000', + 'error_type' => 'nullable|string|in:error,performance,configuration,unexpected_behavior,other', + 'code_snippet' => 'nullable|string|max:2000', + ]); + + $issue = trim($validated['issue']); + $context = $validated['context'] ?? ''; + $errorType = strtolower($validated['error_type'] ?? 'other'); + $codeSnippet = $validated['code_snippet'] ?? ''; if (empty($issue)) { - return PromptResult::text('Please describe the issue you\'re experiencing with Laravel Restify.'); + return Response::text('Please describe the issue you\'re experiencing with Laravel Restify.'); } // Initialize indexer @@ -61,9 +82,9 @@ public function handle(array $arguments): PromptResult // Analyze the issue and generate troubleshooting guidance $troubleshootingGuide = $this->generateTroubleshootingGuide($issue, $context, $errorType, $codeSnippet); - return PromptResult::text($troubleshootingGuide); + return Response::text($troubleshootingGuide); } catch (\Throwable $e) { - return PromptResult::text("I encountered an error while generating troubleshooting guidance: {$e->getMessage()}"); + return Response::text("I encountered an error while generating troubleshooting guidance: {$e->getMessage()}"); } } diff --git a/src/Mcp/Resources/RestifyApiReference.php b/src/Mcp/Resources/RestifyApiReference.php index 360ae3c..a86a8bc 100644 --- a/src/Mcp/Resources/RestifyApiReference.php +++ b/src/Mcp/Resources/RestifyApiReference.php @@ -5,19 +5,33 @@ namespace BinarCode\RestifyBoost\Mcp\Resources; use BinarCode\RestifyBoost\Services\DocParser; -use Laravel\Mcp\Server\Contracts\Resources\Content; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Resource; class RestifyApiReference extends Resource { public function __construct(protected DocParser $parser) {} - public function description(): string - { - return 'Complete Laravel Restify API reference with detailed method signatures, field types, relationship patterns, and implementation examples. Includes repositories, fields, relations, actions, filters, authentication, and MCP-specific features.'; - } - - public function read(): string|Content + /** + * The resource's description. + */ + protected string $description = 'Complete Laravel Restify API reference with detailed method signatures, field types, relationship patterns, and implementation examples. Includes repositories, fields, relations, actions, filters, authentication, and MCP-specific features.'; + + /** + * The resource's URI. + */ + protected string $uri = 'file://restify-api-reference.json'; + + /** + * The resource's MIME type. + */ + protected string $mimeType = 'application/json'; + + /** + * Handle the resource request. + */ + public function handle(): Response { try { $apiReference = $this->buildApiReference(); @@ -31,9 +45,9 @@ public function read(): string|Content 'generated_at' => now()->toIso8601String(), ]; - return json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + return Response::json($response); } catch (\Throwable $e) { - return "Error generating Laravel Restify API reference: {$e->getMessage()}"; + return Response::error("Error generating Laravel Restify API reference: {$e->getMessage()}"); } } diff --git a/src/Mcp/Resources/RestifyDocumentation.php b/src/Mcp/Resources/RestifyDocumentation.php index 6e98c68..bc586bb 100644 --- a/src/Mcp/Resources/RestifyDocumentation.php +++ b/src/Mcp/Resources/RestifyDocumentation.php @@ -5,19 +5,33 @@ namespace BinarCode\RestifyBoost\Mcp\Resources; use BinarCode\RestifyBoost\Services\DocParser; -use Laravel\Mcp\Server\Contracts\Resources\Content; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Resource; class RestifyDocumentation extends Resource { public function __construct(protected DocParser $parser) {} - public function description(): string - { - return 'Complete Laravel Restify documentation including installation guides, repositories, fields, filters, authentication, actions, and performance optimization. This resource provides structured access to all documentation content for comprehensive understanding of the framework.'; - } - - public function read(): string|Content + /** + * The resource's description. + */ + protected string $description = 'Complete Laravel Restify documentation including installation guides, repositories, fields, filters, authentication, actions, and performance optimization. This resource provides structured access to all documentation content for comprehensive understanding of the framework.'; + + /** + * The resource's URI. + */ + protected string $uri = 'file://restify-documentation.json'; + + /** + * The resource's MIME type. + */ + protected string $mimeType = 'application/json'; + + /** + * Handle the resource request. + */ + public function handle(): Response { try { $documentation = $this->loadDocumentation(); @@ -31,9 +45,9 @@ public function read(): string|Content 'last_updated' => now()->toIso8601String(), ]; - return json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + return Response::json($response); } catch (\Throwable $e) { - return "Error loading Laravel Restify documentation: {$e->getMessage()}"; + return Response::error("Error loading Laravel Restify documentation: {$e->getMessage()}"); } } diff --git a/src/Mcp/RestifyDocs.php b/src/Mcp/RestifyDocs.php index 648f27c..b06434d 100644 --- a/src/Mcp/RestifyDocs.php +++ b/src/Mcp/RestifyDocs.php @@ -4,30 +4,66 @@ namespace BinarCode\RestifyBoost\Mcp; +use DirectoryIterator; use Laravel\Mcp\Server; class RestifyDocs extends Server { - public string $serverName = 'Laravel Restify Documentation'; + /** + * The MCP server's name. + */ + protected string $name = 'Laravel Restify Documentation'; - public string $serverVersion = '1.0.0'; + /** + * The MCP server's version. + */ + protected string $version = '1.0.0'; - public string $instructions = 'Laravel Restify MCP server providing comprehensive documentation access, API references, code examples, and troubleshooting guides. Helps AI assistants understand and work with Laravel Restify framework features including repositories, fields, filters, authentication, and performance optimization.'; + /** + * The MCP server's instructions for the LLM. + */ + protected string $instructions = 'Laravel Restify MCP server providing comprehensive documentation access, API references, code examples, and troubleshooting guides. Helps AI assistants understand and work with Laravel Restify framework features including repositories, fields, filters, authentication, and performance optimization.'; + /** + * The default pagination length for resources that support pagination. + */ public int $defaultPaginationLength = 25; - public function boot(): void + /** + * The tools registered with this MCP server. + * + * @var array> + */ + protected array $tools = []; + + /** + * The resources registered with this MCP server. + * + * @var array> + */ + protected array $resources = []; + + /** + * The prompts registered with this MCP server. + * + * @var array> + */ + protected array $prompts = []; + + protected function boot(): void { - $this->discoverTools(); - $this->discoverResources(); - $this->discoverPrompts(); + collect($this->discoverTools())->each(fn (string $tool): string => $this->tools[] = $tool); + collect($this->discoverResources())->each(fn (string $resource): string => $this->resources[] = $resource); + collect($this->discoverPrompts())->each(fn (string $prompt): string => $this->prompts[] = $prompt); } /** - * @return array + * @return array> */ protected function discoverTools(): array { + $tools = []; + $excludedTools = config('restify-boost.mcp.tools.exclude', []); $toolsPath = __DIR__.DIRECTORY_SEPARATOR.'Tools'; @@ -35,12 +71,12 @@ protected function discoverTools(): array return []; } - $toolDir = new \DirectoryIterator($toolsPath); + $toolDir = new DirectoryIterator($toolsPath); foreach ($toolDir as $toolFile) { if ($toolFile->isFile() && $toolFile->getExtension() === 'php') { $fqdn = 'BinarCode\\RestifyBoost\\Mcp\\Tools\\'.$toolFile->getBasename('.php'); if (class_exists($fqdn) && ! in_array($fqdn, $excludedTools, true)) { - $this->addTool($fqdn); + $tools[] = $fqdn; } } } @@ -48,18 +84,20 @@ protected function discoverTools(): array $extraTools = config('restify-boost.mcp.tools.include', []); foreach ($extraTools as $toolClass) { if (class_exists($toolClass)) { - $this->addTool($toolClass); + $tools[] = $toolClass; } } - return $this->registeredTools; + return $tools; } /** - * @return array + * @return array> */ protected function discoverResources(): array { + $resources = []; + $excludedResources = config('restify-boost.mcp.resources.exclude', []); $resourcesPath = __DIR__.DIRECTORY_SEPARATOR.'Resources'; @@ -67,12 +105,12 @@ protected function discoverResources(): array return []; } - $resourceDir = new \DirectoryIterator($resourcesPath); + $resourceDir = new DirectoryIterator($resourcesPath); foreach ($resourceDir as $resourceFile) { if ($resourceFile->isFile() && $resourceFile->getExtension() === 'php') { $fqdn = 'BinarCode\\RestifyBoost\\Mcp\\Resources\\'.$resourceFile->getBasename('.php'); if (class_exists($fqdn) && ! in_array($fqdn, $excludedResources, true)) { - $this->addResource($fqdn); + $resources[] = $fqdn; } } } @@ -80,18 +118,20 @@ protected function discoverResources(): array $extraResources = config('restify-boost.mcp.resources.include', []); foreach ($extraResources as $resourceClass) { if (class_exists($resourceClass)) { - $this->addResource($resourceClass); + $resources[] = $resourceClass; } } - return $this->registeredResources; + return $resources; } /** - * @return array + * @return array> */ protected function discoverPrompts(): array { + $prompts = []; + $excludedPrompts = config('restify-boost.mcp.prompts.exclude', []); $promptsPath = __DIR__.DIRECTORY_SEPARATOR.'Prompts'; @@ -99,12 +139,12 @@ protected function discoverPrompts(): array return []; } - $promptDir = new \DirectoryIterator($promptsPath); + $promptDir = new DirectoryIterator($promptsPath); foreach ($promptDir as $promptFile) { if ($promptFile->isFile() && $promptFile->getExtension() === 'php') { $fqdn = 'BinarCode\\RestifyBoost\\Mcp\\Prompts\\'.$promptFile->getBasename('.php'); if (class_exists($fqdn) && ! in_array($fqdn, $excludedPrompts, true)) { - $this->addPrompt($fqdn); + $prompts[] = $fqdn; } } } @@ -112,10 +152,10 @@ protected function discoverPrompts(): array $extraPrompts = config('restify-boost.mcp.prompts.include', []); foreach ($extraPrompts as $promptClass) { if (class_exists($promptClass)) { - $this->addPrompt($promptClass); + $prompts[] = $promptClass; } } - return $this->registeredPrompts; + return $prompts; } } diff --git a/src/Mcp/Tools/DebugApplicationTool.php b/src/Mcp/Tools/DebugApplicationTool.php index 98a3708..dca796a 100644 --- a/src/Mcp/Tools/DebugApplicationTool.php +++ b/src/Mcp/Tools/DebugApplicationTool.php @@ -4,7 +4,6 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; @@ -12,8 +11,9 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Symfony\Component\Finder\Finder; use Throwable; @@ -24,34 +24,30 @@ public function description(): string return 'Comprehensive debugging tool for Laravel Restify applications. Performs health checks on Laravel installation, database connectivity, Restify configuration, repository validation, performance analysis, and common issue detection. Provides detailed diagnostic reports with actionable suggestions and optional automatic fixes.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema + public function schema(JsonSchema $schema): array { - return $schema - ->string('check_type') - ->description('Specific check to run: "all", "config", "database", "restify", "performance", "health" (default: all)') - ->optional() - ->boolean('detailed_output') - ->description('Include detailed diagnostic information and stack traces (default: true)') - ->optional() - ->boolean('fix_issues') - ->description('Attempt to automatically fix common issues found (default: false)') - ->optional() - ->boolean('export_report') - ->description('Export diagnostic report to storage/logs/debug-report.md (default: false)') - ->optional() - ->boolean('include_suggestions') - ->description('Include detailed fix suggestions in output (default: true)') - ->optional(); + return [ + 'check_type' => $schema->string() + ->description('Specific check to run: "all", "config", "database", "restify", "performance", "health" (default: all)'), + 'detailed_output' => $schema->boolean() + ->description('Include detailed diagnostic information and stack traces (default: true)'), + 'fix_issues' => $schema->boolean() + ->description('Attempt to automatically fix common issues found (default: false)'), + 'export_report' => $schema->boolean() + ->description('Export diagnostic report to storage/logs/debug-report.md (default: false)'), + 'include_suggestions' => $schema->boolean() + ->description('Include detailed fix suggestions in output (default: true)'), + ]; } - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $checkType = $arguments['check_type'] ?? 'all'; - $detailedOutput = $arguments['detailed_output'] ?? true; - $fixIssues = $arguments['fix_issues'] ?? false; - $exportReport = $arguments['export_report'] ?? false; - $includeSuggestions = $arguments['include_suggestions'] ?? true; + $checkType = $request->get('check_type') ?? 'all'; + $detailedOutput = $request->get('detailed_output') ?? true; + $fixIssues = $request->get('fix_issues') ?? false; + $exportReport = $request->get('export_report') ?? false; + $includeSuggestions = $request->get('include_suggestions') ?? true; $report = [ 'summary' => [], @@ -107,7 +103,7 @@ public function handle(array $arguments): ToolResult|Generator return $this->generateDebugReport($report, $detailedOutput); } catch (Throwable $e) { - return ToolResult::error('Debug analysis failed: '.$e->getMessage()); + return Response::error('Debug analysis failed: '.$e->getMessage()); } } @@ -665,7 +661,7 @@ protected function generateMarkdownReport(array $report): string return $content; } - protected function generateDebugReport(array $report, bool $detailed): ToolResult + protected function generateDebugReport(array $report, bool $detailed): Response { $response = "# Laravel Restify Debug Report\n\n"; @@ -788,7 +784,7 @@ protected function generateDebugReport(array $report, bool $detailed): ToolResul $response .= "2. **Run with fix_issues=true** to automatically fix common problems\n"; $response .= "3. **Export detailed report** with export_report=true for documentation\n"; - return ToolResult::text($response); + return Response::text($response); } protected function getStatusEmoji(string $status): string diff --git a/src/Mcp/Tools/GenerateActionTool.php b/src/Mcp/Tools/GenerateActionTool.php index b629355..13f172d 100644 --- a/src/Mcp/Tools/GenerateActionTool.php +++ b/src/Mcp/Tools/GenerateActionTool.php @@ -4,12 +4,12 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Symfony\Component\Finder\Finder; class GenerateActionTool extends Tool @@ -19,54 +19,45 @@ public function description(): string return 'Generate a Laravel Restify action class. Actions allow you to define custom operations for your repositories beyond basic CRUD. This tool can create different types of actions: index actions (for multiple models), show actions (for single models), standalone actions (no models required), invokable actions, or destructive actions. It follows existing project patterns and generates appropriate validation rules.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema + public function schema(JsonSchema $schema): array { - return $schema - ->string('action_name') - ->description('Name of the action class (e.g., "PublishPost", "DisableProfile", "ExportUsers")') - ->required() - ->string('action_type') - ->description('Type of action: "index" (for multiple models), "show" (for single model), "standalone" (no models), "invokable" (simple __invoke method), or "destructive" (extends DestructiveAction)') - ->optional() - ->string('model_name') - ->description('Name of the model this action works with (optional, used for show/index actions)') - ->optional() - ->raw('validation_rules', [ - 'description' => 'Validation rules for the action payload as key-value pairs', - 'type' => 'object', - 'additionalProperties' => true, - ]) - ->optional() - ->string('uri_key') - ->description('Custom URI key for the action (defaults to kebab-case of class name)') - ->optional() - ->string('namespace') - ->description('Override default namespace (auto-detected from existing actions)') - ->optional() - ->boolean('force') - ->description('Overwrite existing action file if it exists') - ->optional(); + return [ + 'action_name' => $schema->string() + ->description('Name of the action class (e.g., "PublishPost", "DisableProfile", "ExportUsers")'), + 'action_type' => $schema->string() + ->description('Type of action: "index" (for multiple models), "show" (for single model), "standalone" (no models), "invokable" (simple __invoke method), or "destructive" (extends DestructiveAction)'), + 'model_name' => $schema->string() + ->description('Name of the model this action works with (optional, used for show/index actions)'), + 'validation_rules' => $schema->object() + ->description('Validation rules for the action payload as key-value pairs'), + 'uri_key' => $schema->string() + ->description('Custom URI key for the action (defaults to kebab-case of class name)'), + 'namespace' => $schema->string() + ->description('Override default namespace (auto-detected from existing actions)'), + 'force' => $schema->boolean() + ->description('Overwrite existing action file if it exists'), + ]; } - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $actionName = trim($arguments['action_name']); - $actionType = $arguments['action_type'] ?? 'index'; - $modelName = $arguments['model_name'] ?? null; - $validationRules = $arguments['validation_rules'] ?? []; - $uriKey = $arguments['uri_key'] ?? null; - $customNamespace = $arguments['namespace'] ?? null; - $force = $arguments['force'] ?? false; + $actionName = trim($request->get('action_name')); + $actionType = $request->get('action_type') ?? 'index'; + $modelName = $request->get('model_name') ?? null; + $validationRules = $request->get('validation_rules') ?? []; + $uriKey = $request->get('uri_key') ?? null; + $customNamespace = $request->get('namespace') ?? null; + $force = $request->get('force') ?? false; if (empty($actionName)) { - return ToolResult::error('Action name is required'); + return Response::error('Action name is required'); } // Validate action type $validActionTypes = ['index', 'show', 'standalone', 'invokable', 'destructive']; if (! in_array($actionType, $validActionTypes)) { - return ToolResult::error('Invalid action type. Must be one of: '.implode(', ', $validActionTypes)); + return Response::error('Invalid action type. Must be one of: '.implode(', ', $validActionTypes)); } // Step 1: Analyze existing action patterns @@ -83,7 +74,7 @@ public function handle(array $arguments): ToolResult|Generator // Step 3: Check if action already exists if (! $force && File::exists($actionDetails['file_path'])) { - return ToolResult::error( + return Response::error( "Action already exists at: {$actionDetails['file_path']}\n". "Use 'force: true' to overwrite." ); @@ -105,7 +96,7 @@ public function handle(array $arguments): ToolResult|Generator return $this->generateSuccessResponse($actionDetails, $actionContent); } catch (\Throwable $e) { - return ToolResult::error('Action generation failed: '.$e->getMessage()); + return Response::error('Action generation failed: '.$e->getMessage()); } } @@ -421,7 +412,7 @@ protected function generateActionFileContent(array $content): string "; } - protected function generateSuccessResponse(array $actionDetails, array $content): ToolResult + protected function generateSuccessResponse(array $actionDetails, array $content): Response { $response = "# Action Generated Successfully!\n\n"; $response .= "**Action:** `{$actionDetails['action_name']}`\n"; @@ -545,7 +536,7 @@ protected function generateSuccessResponse(array $actionDetails, array $content) $response .= "- **Authorization**: Use `->canSee(fn(\$request) => ...)` for access control\n"; $response .= "- **Custom URI Key**: Set `\$uriKey` property for consistent API endpoints\n"; - return ToolResult::text($response); + return Response::text($response); } protected function getRootNamespace(): string diff --git a/src/Mcp/Tools/GenerateGetterTool.php b/src/Mcp/Tools/GenerateGetterTool.php index 8f89650..a2e67c6 100644 --- a/src/Mcp/Tools/GenerateGetterTool.php +++ b/src/Mcp/Tools/GenerateGetterTool.php @@ -4,12 +4,12 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Symfony\Component\Finder\Finder; class GenerateGetterTool extends Tool @@ -19,57 +19,51 @@ public function description(): string return 'Generate a Laravel Restify getter class. Getters allow you to define custom GET-only operations for your repositories to retrieve additional data without modifying the main CRUD operations. This tool can create invokable getters (simple __invoke method) or extended getters (with handle method), and can be scoped to index (multiple items), show (single model), or both contexts.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema + public function schema(JsonSchema $schema): array { - return $schema - ->string('getter_name') - ->description('Name of the getter class (e.g., "StripeInformation", "UserStats", "ExportData")') - ->required() - ->string('getter_type') - ->description('Type of getter: "invokable" (simple __invoke method) or "extended" (extends Getter class with handle method)') - ->optional() - ->string('scope') - ->description('Getter scope: "index" (multiple items), "show" (single model), or "both" (default - can be used in both contexts)') - ->optional() - ->string('model_name') - ->description('Name of the model this getter works with (optional, used for show getters to generate proper method signature)') - ->optional() - ->string('uri_key') - ->description('Custom URI key for the getter (defaults to kebab-case of class name)') - ->optional() - ->string('namespace') - ->description('Override default namespace (auto-detected from existing getters)') - ->optional() - ->boolean('force') - ->description('Overwrite existing getter file if it exists') - ->optional(); + return [ + 'getter_name' => $schema->string() + ->description('Name of the getter class (e.g., "StripeInformation", "UserStats", "ExportData")'), + 'getter_type' => $schema->string() + ->description('Type of getter: "invokable" (simple __invoke method) or "extended" (extends Getter class with handle method)'), + 'scope' => $schema->string() + ->description('Getter scope: "index" (multiple items), "show" (single model), or "both" (default - can be used in both contexts)'), + 'model_name' => $schema->string() + ->description('Name of the model this getter works with (optional, used for show getters to generate proper method signature)'), + 'uri_key' => $schema->string() + ->description('Custom URI key for the getter (defaults to kebab-case of class name)'), + 'namespace' => $schema->string() + ->description('Override default namespace (auto-detected from existing getters)'), + 'force' => $schema->boolean() + ->description('Overwrite existing getter file if it exists'), + ]; } - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $getterName = trim($arguments['getter_name']); - $getterType = $arguments['getter_type'] ?? 'extended'; - $scope = $arguments['scope'] ?? 'both'; - $modelName = $arguments['model_name'] ?? null; - $uriKey = $arguments['uri_key'] ?? null; - $customNamespace = $arguments['namespace'] ?? null; - $force = $arguments['force'] ?? false; + $getterName = trim($request->get('getter_name')); + $getterType = $request->get('getter_type') ?? 'extended'; + $scope = $request->get('scope') ?? 'both'; + $modelName = $request->get('model_name') ?? null; + $uriKey = $request->get('uri_key') ?? null; + $customNamespace = $request->get('namespace') ?? null; + $force = $request->get('force') ?? false; if (empty($getterName)) { - return ToolResult::error('Getter name is required'); + return Response::error('Getter name is required'); } // Validate getter type $validGetterTypes = ['invokable', 'extended']; if (! in_array($getterType, $validGetterTypes)) { - return ToolResult::error('Invalid getter type. Must be one of: '.implode(', ', $validGetterTypes)); + return Response::error('Invalid getter type. Must be one of: '.implode(', ', $validGetterTypes)); } // Validate scope $validScopes = ['index', 'show', 'both']; if (! in_array($scope, $validScopes)) { - return ToolResult::error('Invalid scope. Must be one of: '.implode(', ', $validScopes)); + return Response::error('Invalid scope. Must be one of: '.implode(', ', $validScopes)); } // Step 1: Analyze existing getter patterns @@ -84,7 +78,7 @@ public function handle(array $arguments): ToolResult|Generator // Step 3: Check if getter already exists if (! $force && File::exists($getterDetails['file_path'])) { - return ToolResult::error( + return Response::error( "Getter already exists at: {$getterDetails['file_path']}\n". "Use 'force: true' to overwrite." ); @@ -106,7 +100,7 @@ public function handle(array $arguments): ToolResult|Generator return $this->generateSuccessResponse($getterDetails, $getterContent); } catch (\Throwable $e) { - return ToolResult::error('Getter generation failed: '.$e->getMessage()); + return Response::error('Getter generation failed: '.$e->getMessage()); } } @@ -371,7 +365,7 @@ protected function generateGetterFileContent(array $content): string "; } - protected function generateSuccessResponse(array $getterDetails, array $content): ToolResult + protected function generateSuccessResponse(array $getterDetails, array $content): Response { $response = "# Getter Generated Successfully!\n\n"; $response .= "**Getter:** `{$getterDetails['getter_name']}`\n"; @@ -476,7 +470,7 @@ protected function generateSuccessResponse(array $getterDetails, array $content) $response .= "- **Custom URI Key**: Set `\$uriKey` property for consistent API endpoints\n"; $response .= "- **GET-only**: Remember getters should only perform read operations\n"; - return ToolResult::text($response); + return Response::text($response); } protected function getRootNamespace(): string diff --git a/src/Mcp/Tools/GenerateMatchFilterTool.php b/src/Mcp/Tools/GenerateMatchFilterTool.php index 3297894..37fed6b 100644 --- a/src/Mcp/Tools/GenerateMatchFilterTool.php +++ b/src/Mcp/Tools/GenerateMatchFilterTool.php @@ -4,70 +4,61 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolResult; class GenerateMatchFilterTool extends Tool { - public function name(): string - { - return 'generate-match-filter'; - } - - public function description(): string - { - return 'Generate Laravel Restify match filter classes for exact matching and custom filtering logic. Supports all match types: string, int, bool, datetime, between, array, and custom filters with complex logic.'; - } + /** + * The tool's description. + */ + protected string $description = 'Generate Laravel Restify match filter classes for exact matching and custom filtering logic. Supports all match types: string, int, bool, datetime, between, array, and custom filters with complex logic.'; - public function parameters(): array + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array { return [ - 'name' => [ - 'type' => 'string', - 'description' => 'The name of the match filter class (e.g., ActivePostMatchFilter)', - 'required' => true, - ], - 'attribute' => [ - 'type' => 'string', - 'description' => 'The database attribute/column to filter on (e.g., status, active, category)', - 'required' => true, - ], - 'type' => [ - 'type' => 'string', - 'description' => 'The match filter type', - 'enum' => ['string', 'int', 'integer', 'bool', 'boolean', 'datetime', 'between', 'array', 'custom'], - 'default' => 'string', - ], - 'partial' => [ - 'type' => 'boolean', - 'description' => 'Whether to use partial matching (LIKE queries) for text fields', - 'default' => false, - ], - 'custom_logic' => [ - 'type' => 'string', - 'description' => 'Custom filtering logic description for complex filters (only when type is custom)', - ], - 'repository' => [ - 'type' => 'string', - 'description' => 'The repository class name to add the filter to (optional)', - ], - 'namespace' => [ - 'type' => 'string', - 'description' => 'Custom namespace for the filter class', - 'default' => 'App\\Restify\\Filters', - ], + 'name' => $schema->string() + ->description('The name of the match filter class (e.g., ActivePostMatchFilter)'), + 'attribute' => $schema->string() + ->description('The database attribute/column to filter on (e.g., status, active, category)'), + 'type' => $schema->string() + ->description('The match filter type') + ->enum(['string', 'int', 'integer', 'bool', 'boolean', 'datetime', 'between', 'array', 'custom']) + ->optional(), + 'partial' => $schema->boolean() + ->description('Whether to use partial matching (LIKE queries) for text fields') + ->optional(), + 'custom_logic' => $schema->string() + ->description('Custom filtering logic description for complex filters (only when type is custom)') + ->optional(), + 'repository' => $schema->string() + ->description('The repository class name to add the filter to (optional)') + ->optional(), + 'namespace' => $schema->string() + ->description('Custom namespace for the filter class') + ->optional(), ]; } - public function handle(array $arguments): \Laravel\Mcp\Server\Tools\ToolResult + /** + * Handle the tool request. + */ + public function handle(Request $request): Response { - $name = $arguments['name']; - $attribute = $arguments['attribute']; - $type = $arguments['type'] ?? 'string'; - $partial = $arguments['partial'] ?? false; - $customLogic = $arguments['custom_logic'] ?? null; - $repository = $arguments['repository'] ?? null; - $namespace = $arguments['namespace'] ?? 'App\\Restify\\Filters'; + $name = $request->get('name'); + $attribute = $request->get('attribute'); + $type = $request->get('type', 'string'); + $partial = $request->get('partial', false); + $customLogic = $request->get('custom_logic'); + $repository = $request->get('repository'); + $namespace = $request->get('namespace', 'App\\Restify\\Filters'); // Generate the filter class $filterClass = $this->generateFilterClass($name, $attribute, $type, $partial, $customLogic, $namespace); @@ -78,7 +69,7 @@ public function handle(array $arguments): \Laravel\Mcp\Server\Tools\ToolResult // Generate usage examples $usageExamples = $this->generateUsageExamples($attribute, $type); - return ToolResult::content([ + return Response::json([ 'filter_class' => $filterClass, 'repository_integration' => $repositoryExample, 'usage_examples' => $usageExamples, diff --git a/src/Mcp/Tools/GenerateRepositoryTool.php b/src/Mcp/Tools/GenerateRepositoryTool.php index f0b9b93..161f299 100644 --- a/src/Mcp/Tools/GenerateRepositoryTool.php +++ b/src/Mcp/Tools/GenerateRepositoryTool.php @@ -4,13 +4,13 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; +use Illuminate\JsonSchema\JsonSchema; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; use ReflectionClass; use Symfony\Component\Finder\Finder; @@ -18,52 +18,60 @@ class GenerateRepositoryTool extends Tool { protected ?string $confirmedModelClass = null; - public function description(): string + /** + * The tool's description. + */ + protected string $description = 'Generate a Laravel Restify repository class based on an existing Eloquent model. This tool analyzes the model\'s database schema, detects relationships, generates appropriate fields, and follows existing repository organization patterns in your project. It can auto-detect foreign keys, generate proper field types, and create relationship methods.'; + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array { - return 'Generate a Laravel Restify repository class based on an existing Eloquent model. This tool analyzes the model\'s database schema, detects relationships, generates appropriate fields, and follows existing repository organization patterns in your project. It can auto-detect foreign keys, generate proper field types, and create relationship methods.'; - } - - public function schema(ToolInputSchema $schema): ToolInputSchema - { - return $schema - ->string('model_name') - ->description('Name of the Eloquent model to generate repository for (e.g., "User", "BlogPost")') - ->required() - ->boolean('include_fields') - ->description('Generate fields from model database schema') - ->optional() - ->boolean('include_relationships') - ->description('Generate relationships (BelongsTo/HasMany) from schema analysis') - ->optional() - ->string('repository_name') - ->description('Override default repository name (default: {Model}Repository)') - ->optional() - ->string('namespace') - ->description('Override default namespace (auto-detected from existing repositories)') - ->optional() - ->boolean('force') - ->description('Overwrite existing repository file if it exists') - ->optional(); + return [ + 'model_name' => $schema->string() + ->description('Name of the Eloquent model to generate repository for (e.g., "User", "BlogPost")'), + 'include_fields' => $schema->boolean() + ->description('Generate fields from model database schema') + ->optional(), + 'include_relationships' => $schema->boolean() + ->description('Generate relationships (BelongsTo/HasMany) from schema analysis') + ->optional(), + 'repository_name' => $schema->string() + ->description('Override default repository name (default: {Model}Repository)') + ->optional(), + 'namespace' => $schema->string() + ->description('Override default namespace (auto-detected from existing repositories)') + ->optional(), + 'force' => $schema->boolean() + ->description('Overwrite existing repository file if it exists') + ->optional(), + ]; } - public function handle(array $arguments): ToolResult|Generator + /** + * Handle the tool request. + */ + public function handle(Request $request): Response { try { - $modelName = trim($arguments['model_name']); - $includeFields = $arguments['include_fields'] ?? true; - $includeRelationships = $arguments['include_relationships'] ?? true; - $customRepositoryName = $arguments['repository_name'] ?? null; - $customNamespace = $arguments['namespace'] ?? null; - $force = $arguments['force'] ?? false; + $modelName = trim($request->get('model_name')); + $includeFields = $request->get('include_fields', true); + $includeRelationships = $request->get('include_relationships', true); + $customRepositoryName = $request->get('repository_name'); + $customNamespace = $request->get('namespace'); + $force = $request->get('force', false); if (empty($modelName)) { - return ToolResult::error('Model name is required'); + return Response::error('Model name is required'); } // Step 1: Find and resolve the model $modelClass = $this->resolveModelClass($modelName); if (! $modelClass) { - return ToolResult::error("Could not find model: {$modelName}"); + return Response::error("Could not find model: {$modelName}"); } // Step 2: Analyze existing repository patterns @@ -79,7 +87,7 @@ public function handle(array $arguments): ToolResult|Generator // Step 4: Check if repository already exists if (! $force && File::exists($repositoryDetails['file_path'])) { - return ToolResult::error( + return Response::error( "Repository already exists at: {$repositoryDetails['file_path']}\n". "Use 'force: true' to overwrite." ); @@ -100,7 +108,7 @@ public function handle(array $arguments): ToolResult|Generator return $this->generateSuccessResponse($modelClass, $repositoryDetails, $repositoryContent); } catch (\Throwable $e) { - return ToolResult::error('Repository generation failed: '.$e->getMessage()); + return Response::error('Repository generation failed: '.$e->getMessage()); } } @@ -658,7 +666,7 @@ protected function generateSuccessResponse(string $modelClass, array $repository $response .= "- **Factory:** `php artisan make:factory {$repositoryDetails['model_base_name']}Factory`\n"; $response .= '- **Migration:** `php artisan make:migration create_'.Str::snake(Str::plural($repositoryDetails['model_base_name']))."_table`\n"; - return ToolResult::text($response); + return Response::text($response); } protected function getRootNamespace(): string diff --git a/src/Mcp/Tools/GetCodeExamples.php b/src/Mcp/Tools/GetCodeExamples.php index 658e9f5..ed68b06 100644 --- a/src/Mcp/Tools/GetCodeExamples.php +++ b/src/Mcp/Tools/GetCodeExamples.php @@ -5,55 +5,60 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; use BinarCode\RestifyBoost\Services\DocIndexer; -use Generator; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; class GetCodeExamples extends Tool { public function __construct(protected DocIndexer $indexer) {} - public function description(): string - { - return 'Get specific code examples from Laravel Restify documentation. This tool extracts and formats code examples for specific features like repositories, fields, filters, actions, and authentication. Perfect for understanding implementation patterns and getting copy-paste ready code snippets.'; - } + /** + * The tool's description. + */ + protected string $description = 'Get specific code examples from Laravel Restify documentation. This tool extracts and formats code examples for specific features like repositories, fields, filters, actions, and authentication. Perfect for understanding implementation patterns and getting copy-paste ready code snippets.'; - public function schema(ToolInputSchema $schema): ToolInputSchema + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array { - return $schema - ->string('topic') - ->description('The topic or feature you need code examples for (e.g., "repository", "field validation", "custom filter", "authentication")') - ->required() - ->string('language') - ->description('Filter by programming language (php, javascript, json, yaml, etc.)') - ->optional() - ->string('category') - ->description('Limit to specific documentation category: installation, repositories, fields, filters, auth, actions, performance, testing') - ->optional() - ->integer('limit') - ->description('Maximum number of examples to return (default: 10, max: 25)') - ->optional() - ->boolean('include_context') - ->description('Include surrounding documentation context for each example (default: true)') - ->optional(); + return [ + 'topic' => $schema->string() + ->description('The topic or feature you need code examples for (e.g., "repository", "field validation", "custom filter", "authentication")'), + 'language' => $schema->string() + ->description('Filter by programming language (php, javascript, json, yaml, etc.)') + ->optional(), + 'category' => $schema->string() + ->description('Limit to specific documentation category: installation, repositories, fields, filters, auth, actions, performance, testing') + ->optional(), + 'limit' => $schema->integer() + ->description('Maximum number of examples to return (default: 10, max: 25)') + ->optional(), + 'include_context' => $schema->boolean() + ->description('Include surrounding documentation context for each example (default: true)') + ->optional(), + ]; } /** - * @param array $arguments + * Handle the tool request. */ - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $topic = trim($arguments['topic']); + $topic = trim($request->get('topic')); if (empty($topic)) { - return ToolResult::error('Topic is required'); + return Response::error('Topic is required'); } - $language = $arguments['language'] ?? null; - $category = $arguments['category'] ?? null; - $limit = min($arguments['limit'] ?? 10, 25); - $includeContext = $arguments['include_context'] ?? true; + $language = $request->get('language'); + $category = $request->get('category'); + $limit = min($request->get('limit', 10), 25); + $includeContext = $request->get('include_context', true); // Initialize indexer $this->initializeIndexer(); @@ -76,9 +81,9 @@ public function handle(array $arguments): ToolResult|Generator $limitedExamples = array_slice($codeExamples, 0, $limit); $response = $this->formatCodeExamples($limitedExamples, $topic, $includeContext); - return ToolResult::text($response); + return Response::text($response); } catch (\Throwable $e) { - return ToolResult::error('Failed to get code examples: '.$e->getMessage()); + return Response::error('Failed to get code examples: '.$e->getMessage()); } } @@ -243,7 +248,7 @@ protected function formatCodeExamples(array $examples, string $topic, bool $incl return $output; } - protected function handleNoExamples(string $topic, ?string $category): ToolResult + protected function handleNoExamples(string $topic, ?string $category): Response { $message = "No documentation found for topic: **{$topic}**"; if ($category) { @@ -255,10 +260,10 @@ protected function handleNoExamples(string $topic, ?string $category): ToolResul $message .= "- Check available categories with the search tool\n"; $message .= "- Use `search-restify-docs` to explore available documentation\n"; - return ToolResult::text($message); + return Response::text($message); } - protected function handleNoCodeExamples(string $topic, ?string $language): ToolResult + protected function handleNoCodeExamples(string $topic, ?string $language): Response { $message = "No code examples found for topic: **{$topic}**"; if ($language) { @@ -270,6 +275,6 @@ protected function handleNoCodeExamples(string $topic, ?string $language): ToolR $message .= "- Check if documentation uses different terminology\n"; $message .= "- Use `search-restify-docs` to find conceptual information\n"; - return ToolResult::text($message); + return Response::text($message); } } diff --git a/src/Mcp/Tools/InstallRestifyTool.php b/src/Mcp/Tools/InstallRestifyTool.php index a7ff572..9f2ded7 100644 --- a/src/Mcp/Tools/InstallRestifyTool.php +++ b/src/Mcp/Tools/InstallRestifyTool.php @@ -4,13 +4,13 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Process; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; class InstallRestifyTool extends Tool { @@ -21,60 +21,52 @@ public function description(): string return 'Install and setup Laravel Restify package with all necessary configurations. This tool handles composer installation, downloads the latest config file from Laravel Restify 10.x, runs setup commands, creates the Restify service provider, scaffolds repositories, optionally configures authentication middleware, generates mock data, and can setup MCP (Model Context Protocol) server routes for AI integration.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema + public function schema(JsonSchema $schema): array { - return $schema - ->boolean('run_migrations') - ->description('Run migrations after setup (default: true)') - ->optional() - ->boolean('enable_sanctum_auth') - ->description('Enable Sanctum authentication middleware in restify config') - ->optional() - ->string('api_prefix') - ->description('Custom API prefix (default: /api/restify)') - ->optional() - ->boolean('install_doctrine_dbal') - ->description('Install doctrine/dbal for mock data generation') - ->optional() - ->integer('generate_users_count') - ->description('Number of mock users to generate (requires doctrine/dbal)') - ->optional() - ->boolean('generate_repositories') - ->description('Auto-generate repositories for all existing models') - ->optional() - ->boolean('force') - ->description('Force installation even if already installed') - ->optional() - ->boolean('update_config') - ->description('Download and use the latest config file from Laravel Restify 10.x (default: true)') - ->optional() - ->boolean('setup_mcp') - ->description('Setup MCP (Model Context Protocol) server routes for AI integration (default: false)') - ->optional(); + return [ + 'run_migrations' => $schema->boolean() + ->description('Run migrations after setup (default: true)'), + 'enable_sanctum_auth' => $schema->boolean() + ->description('Enable Sanctum authentication middleware in restify config'), + 'api_prefix' => $schema->string() + ->description('Custom API prefix (default: /api/restify)'), + 'install_doctrine_dbal' => $schema->boolean() + ->description('Install doctrine/dbal for mock data generation'), + 'generate_users_count' => $schema->integer() + ->description('Number of mock users to generate (requires doctrine/dbal)'), + 'generate_repositories' => $schema->boolean() + ->description('Auto-generate repositories for all existing models'), + 'force' => $schema->boolean() + ->description('Force installation even if already installed'), + 'update_config' => $schema->boolean() + ->description('Download and use the latest config file from Laravel Restify 10.x (default: true)'), + 'setup_mcp' => $schema->boolean() + ->description('Setup MCP (Model Context Protocol) server routes for AI integration (default: false)'), + ]; } - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $runMigrations = $arguments['run_migrations'] ?? true; - $enableSanctumAuth = $arguments['enable_sanctum_auth'] ?? false; - $apiPrefix = $arguments['api_prefix'] ?? null; - $installDoctrineDbal = $arguments['install_doctrine_dbal'] ?? false; - $generateUsersCount = $arguments['generate_users_count'] ?? 0; - $generateRepositories = $arguments['generate_repositories'] ?? false; - $force = $arguments['force'] ?? false; - $updateConfig = $arguments['update_config'] ?? true; - $setupMcp = $arguments['setup_mcp'] ?? false; + $runMigrations = $request->get('run_migrations') ?? true; + $enableSanctumAuth = $request->get('enable_sanctum_auth') ?? false; + $apiPrefix = $request->get('api_prefix') ?? null; + $installDoctrineDbal = $request->get('install_doctrine_dbal') ?? false; + $generateUsersCount = $request->get('generate_users_count') ?? 0; + $generateRepositories = $request->get('generate_repositories') ?? false; + $force = $request->get('force') ?? false; + $updateConfig = $request->get('update_config') ?? true; + $setupMcp = $request->get('setup_mcp') ?? false; // Step 1: Validate environment $validationResult = $this->validateEnvironment(); if (! $validationResult['success']) { - return ToolResult::error($validationResult['message']); + return Response::error($validationResult['message']); } // Step 2: Check if already installed if (! $force && $this->isRestifyAlreadyInstalled()) { - return ToolResult::error( + return Response::error( 'Laravel Restify is already installed. Use "force: true" to reinstall.' ); } @@ -85,14 +77,14 @@ public function handle(array $arguments): ToolResult|Generator $installResult = $this->installComposerPackage(); $results[] = $installResult; if (! $installResult['success']) { - return ToolResult::error($installResult['message']); + return Response::error($installResult['message']); } // Step 4: Run restify setup $setupResult = $this->runRestifySetup(); $results[] = $setupResult; if (! $setupResult['success']) { - return ToolResult::error($setupResult['message']); + return Response::error($setupResult['message']); } // Step 4.5: Update config file with latest version @@ -137,10 +129,10 @@ public function handle(array $arguments): ToolResult|Generator $results[] = $this->setupMcpRoutes(); } - return $this->generateSuccessResponse($results, $arguments); + return $this->generateSuccessResponse($results, $request); } catch (\Throwable $e) { - return ToolResult::error('Restify installation failed: '.$e->getMessage()); + return Response::error('Restify installation failed: '.$e->getMessage()); } } @@ -660,7 +652,7 @@ protected function getFullMcpRoutesFile(): string '; } - protected function generateSuccessResponse(array $results, array $arguments): ToolResult + protected function generateSuccessResponse(array $results, Request $request): Response { $response = "# Laravel Restify Installation Complete! 🎉\n\n"; @@ -677,33 +669,33 @@ protected function generateSuccessResponse(array $results, array $arguments): To // Configuration summary $response .= "\n## Configuration Applied\n\n"; - if ($arguments['enable_sanctum_auth'] ?? false) { + if ($request->get('enable_sanctum_auth') ?? false) { $response .= "✅ **Sanctum Authentication:** Enabled\n"; } - if (! empty($arguments['api_prefix'])) { - $response .= "✅ **API Prefix:** `{$arguments['api_prefix']}`\n"; + if (! empty($request->get('api_prefix'))) { + $response .= "✅ **API Prefix:** `{$request->get('api_prefix')}`\n"; } else { $response .= "â„šī¸ **API Prefix:** `/api/restify` (default)\n"; } - if ($arguments['install_doctrine_dbal'] ?? false) { + if ($request->get('install_doctrine_dbal') ?? false) { $response .= "✅ **Doctrine DBAL:** Installed for mock data generation\n"; } - if (($arguments['generate_users_count'] ?? 0) > 0) { - $response .= "✅ **Mock Users:** Generated {$arguments['generate_users_count']} users\n"; + if (($request->get('generate_users_count') ?? 0) > 0) { + $response .= "✅ **Mock Users:** Generated {$request->get('generate_users_count')} users\n"; } - if ($arguments['generate_repositories'] ?? false) { + if ($request->get('generate_repositories') ?? false) { $response .= "✅ **Repositories:** Auto-generated for existing models\n"; } - if ($arguments['update_config'] ?? true) { + if ($request->get('update_config') ?? true) { $response .= "✅ **Config File:** Updated with latest Laravel Restify 10.x configuration\n"; } - if ($arguments['setup_mcp'] ?? false) { + if ($request->get('setup_mcp') ?? false) { $response .= "✅ **MCP Server:** AI integration routes configured in routes/ai.php\n"; } @@ -716,16 +708,16 @@ protected function generateSuccessResponse(array $results, array $arguments): To $response .= "- `app/Restify/UserRepository.php` - User repository example\n"; $response .= "- Database migration for action logs\n"; - if ($arguments['update_config'] ?? true) { + if ($request->get('update_config') ?? true) { $response .= "- `config/restify.php.backup-*` - Backup of previous config (if existed)\n"; } - if ($arguments['setup_mcp'] ?? false) { + if ($request->get('setup_mcp') ?? false) { $response .= "- `routes/ai.php` - MCP server routes for AI integration\n"; } // API endpoints - $apiPrefix = $arguments['api_prefix'] ?? '/api/restify'; + $apiPrefix = $request->get('api_prefix') ?? '/api/restify'; $response .= "\n## Available API Endpoints\n\n"; $response .= "Your Laravel Restify API is now available at:\n\n"; $response .= "```\n"; @@ -737,7 +729,7 @@ protected function generateSuccessResponse(array $results, array $arguments): To $response .= "```\n"; // MCP endpoints if enabled - if ($arguments['setup_mcp'] ?? false) { + if ($request->get('setup_mcp') ?? false) { $response .= "\n## MCP Server Endpoints\n\n"; $response .= "Your MCP (Model Context Protocol) server is available at:\n\n"; $response .= "```\n"; @@ -754,17 +746,17 @@ protected function generateSuccessResponse(array $results, array $arguments): To $response .= "4. **Generate policies:** `php artisan restify:policy UserPolicy`\n"; $response .= "5. **Review documentation:** https://restify.binarcode.com\n"; - if ($arguments['enable_sanctum_auth'] ?? false) { + if ($request->get('enable_sanctum_auth') ?? false) { $response .= "6. **Configure Sanctum:** Ensure Laravel Sanctum is properly set up\n"; } - if ($arguments['setup_mcp'] ?? false) { - $nextStepNumber = ($arguments['enable_sanctum_auth'] ?? false) ? "7" : "6"; + if ($request->get('setup_mcp') ?? false) { + $nextStepNumber = ($request->get('enable_sanctum_auth') ?? false) ? "7" : "6"; $response .= "{$nextStepNumber}. **Test MCP Server:** Connect your AI client to `/mcp/restify` endpoint\n"; } // Authentication note - if ($arguments['enable_sanctum_auth'] ?? false) { + if ($request->get('enable_sanctum_auth') ?? false) { $response .= "\n## Authentication Note\n\n"; $response .= "âš ī¸ **Sanctum authentication is enabled.** Make sure:\n"; $response .= "- Laravel Sanctum is installed: `composer require laravel/sanctum`\n"; @@ -773,7 +765,7 @@ protected function generateSuccessResponse(array $results, array $arguments): To } // MCP note - if ($arguments['setup_mcp'] ?? false) { + if ($request->get('setup_mcp') ?? false) { $response .= "\n## MCP Server Note\n\n"; $response .= "🤖 **MCP server is configured.** To use it:\n"; $response .= "- The MCP server is protected by Sanctum authentication\n"; @@ -797,6 +789,6 @@ protected function generateSuccessResponse(array $results, array $arguments): To $response .= "php artisan vendor:publish --provider=\"Binaryk\\LaravelRestify\\LaravelRestifyServiceProvider\" --tag=config --force\n"; $response .= "```\n"; - return ToolResult::text($response); + return Response::text($response); } } diff --git a/src/Mcp/Tools/NavigateDocs.php b/src/Mcp/Tools/NavigateDocs.php index 8dfde64..c76c499 100644 --- a/src/Mcp/Tools/NavigateDocs.php +++ b/src/Mcp/Tools/NavigateDocs.php @@ -5,10 +5,10 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; use BinarCode\RestifyBoost\Services\DocIndexer; -use Generator; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; class NavigateDocs extends Tool { @@ -19,33 +19,30 @@ public function description(): string return 'Navigate and browse Laravel Restify documentation by category or get an overview of available documentation structure. This tool helps you understand what documentation is available and provides organized access to different sections like installation, repositories, fields, authentication, etc.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema + public function schema(JsonSchema $schema): array { - return $schema - ->string('action') - ->description('Navigation action: "overview" for documentation structure, "category" to browse a specific category, "list-categories" to see all available categories') - ->required() - ->string('category') - ->description('Specific category to browse (required when action is "category"): installation, repositories, fields, filters, auth, actions, performance, testing') - ->optional() - ->boolean('include_content') - ->description('Include document summaries and content previews (default: true)') - ->optional() - ->integer('limit') - ->description('Maximum number of documents to show per category (default: 20)') - ->optional(); + return [ + 'action' => $schema->string() + ->description('Navigation action: "overview" for documentation structure, "category" to browse a specific category, "list-categories" to see all available categories'), + 'category' => $schema->string() + ->description('Specific category to browse (required when action is "category"): installation, repositories, fields, filters, auth, actions, performance, testing'), + 'include_content' => $schema->boolean() + ->description('Include document summaries and content previews (default: true)'), + 'limit' => $schema->integer() + ->description('Maximum number of documents to show per category (default: 20)'), + ]; } /** * @param array $arguments */ - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { - $action = strtolower(trim($arguments['action'])); - $category = $arguments['category'] ?? null; - $includeContent = $arguments['include_content'] ?? true; - $limit = min($arguments['limit'] ?? 20, 50); + $action = strtolower(trim($request->get('action'))); + $category = $request->get('category') ?? null; + $includeContent = $request->get('include_content') ?? true; + $limit = min($request->get('limit') ?? 20, 50); // Initialize indexer $this->initializeIndexer(); @@ -54,10 +51,10 @@ public function handle(array $arguments): ToolResult|Generator 'overview' => $this->generateOverview($includeContent), 'list-categories', 'categories' => $this->listCategories(), 'category' => $this->browseCategory($category, $includeContent, $limit), - default => ToolResult::error('Invalid action. Use "overview", "list-categories", or "category"'), + default => Response::error('Invalid action. Use "overview", "list-categories", or "category"'), }; } catch (\Throwable $e) { - return ToolResult::error('Navigation failed: '.$e->getMessage()); + return Response::error('Navigation failed: '.$e->getMessage()); } } @@ -97,12 +94,12 @@ protected function scanDirectoryForMarkdown(string $directory): array return $files; } - protected function generateOverview(bool $includeContent): ToolResult + protected function generateOverview(bool $includeContent): Response { $categories = $this->indexer->getCategories(); if (empty($categories)) { - return ToolResult::text("No Laravel Restify documentation found.\n\nPlease ensure the Laravel Restify package is installed and documentation is available."); + return Response::text("No Laravel Restify documentation found.\n\nPlease ensure the Laravel Restify package is installed and documentation is available."); } $output = "# Laravel Restify Documentation Overview\n\n"; @@ -140,15 +137,15 @@ protected function generateOverview(bool $includeContent): ToolResult $output .= "- Use `search-restify-docs` to find specific topics\n"; $output .= "- Use `get-code-examples` for implementation examples\n"; - return ToolResult::text($output); + return Response::text($output); } - protected function listCategories(): ToolResult + protected function listCategories(): Response { $categories = $this->indexer->getCategories(); if (empty($categories)) { - return ToolResult::text('No documentation categories found.'); + return Response::text('No documentation categories found.'); } $output = "# Laravel Restify Documentation Categories\n\n"; @@ -171,13 +168,13 @@ protected function listCategories(): ToolResult $output .= "**Usage:** Use `navigate-docs` with action \"category\" and specify one of the category keys above.\n"; - return ToolResult::text($output); + return Response::text($output); } - protected function browseCategory(?string $category, bool $includeContent, int $limit): ToolResult + protected function browseCategory(?string $category, bool $includeContent, int $limit): Response { if (! $category) { - return ToolResult::error('Category is required when action is "category". Use "list-categories" to see available categories.'); + return Response::error('Category is required when action is "category". Use "list-categories" to see available categories.'); } $documents = $this->indexer->getDocumentsByCategory($category); @@ -185,7 +182,7 @@ protected function browseCategory(?string $category, bool $includeContent, int $ if (empty($documents)) { $availableCategories = array_keys($this->indexer->getCategories()); - return ToolResult::text( + return Response::text( "No documents found in category: **{$category}**\n\n". '**Available categories:** '.implode(', ', $availableCategories) ); @@ -252,7 +249,7 @@ protected function browseCategory(?string $category, bool $includeContent, int $ $output .= "- `search-restify-docs` with category=\"{$category}\" for specific topics\n"; $output .= "- `get-code-examples` with category=\"{$category}\" for implementation examples\n"; - return ToolResult::text($output); + return Response::text($output); } protected function getCategoryName(string $category): string diff --git a/src/Mcp/Tools/SearchRestifyDocs.php b/src/Mcp/Tools/SearchRestifyDocs.php index 07190de..e08c5ba 100644 --- a/src/Mcp/Tools/SearchRestifyDocs.php +++ b/src/Mcp/Tools/SearchRestifyDocs.php @@ -5,75 +5,77 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; use BinarCode\RestifyBoost\Services\DocIndexer; -use Generator; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; class SearchRestifyDocs extends Tool { public function __construct(protected DocIndexer $indexer) {} - public function description(): string - { - return 'Search Laravel Restify documentation for specific topics, methods, concepts, or questions. This tool automatically handles questions like "how many types of filters", "what are the available field types", "how to create repositories", etc. It searches through comprehensive documentation including installation, repositories, fields, filters, authentication, actions, and performance guides.'.PHP_EOL. - 'IMPORTANT: Always use this tool first when users ask any questions about Laravel Restify concepts, features, usage, or implementation. This includes questions about:'.PHP_EOL. - '- Types of filters, fields, actions, or other components ("how many types of X")'.PHP_EOL. - '- Available options or methods ("what are the available Y")'.PHP_EOL. - '- How to implement features ("how to create Z")'.PHP_EOL. - '- Best practices and examples'.PHP_EOL. - 'Use multiple queries if unsure about terminology (e.g., ["validation", "validate"], ["filter types", "filtering options"]).'; - } + /** + * The tool's description. + */ + protected string $description = 'Search Laravel Restify documentation for specific topics, methods, concepts, or questions. This tool automatically handles questions like "how many types of filters", "what are the available field types", "how to create repositories", etc. It searches through comprehensive documentation including installation, repositories, fields, filters, authentication, actions, and performance guides. - public function schema(ToolInputSchema $schema): ToolInputSchema +IMPORTANT: Always use this tool first when users ask any questions about Laravel Restify concepts, features, usage, or implementation. This includes questions about: +- Types of filters, fields, actions, or other components ("how many types of X") +- Available options or methods ("what are the available Y") +- How to implement features ("how to create Z") +- Best practices and examples +Use multiple queries if unsure about terminology (e.g., ["validation", "validate"], ["filter types", "filtering options"]).'; + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array { - return $schema - ->raw('queries', [ - 'description' => 'List of search queries to perform. For questions like "how many types of filters", use ["filter types", "filtering", "match filters"]. For "available field types", use ["field types", "fields", "field methods"]. Pass multiple queries if you aren\'t sure about exact terminology.', - 'type' => 'array', - 'items' => [ - 'type' => 'string', - 'description' => 'Search query string - extract key terms from user questions', - ], - 'minItems' => 1, - ])->required() - ->string('question_type') - ->description('Type of question being asked: "count" (how many types), "list" (what are available), "howto" (how to do something), "concept" (explain concept), "example" (show examples)') - ->optional() - ->string('category') - ->description('Limit search to specific category: installation, repositories, fields, filters, auth, actions, performance, testing') - ->optional() - ->integer('limit') - ->description('Maximum number of results to return per query (default: 10, max: 50)') - ->optional() - ->integer('token_limit') - ->description('Maximum number of tokens to return in the response. Defaults to 10,000 tokens, maximum 100,000 tokens.') - ->optional(); + return [ + 'queries' => $schema->array() + ->description('List of search queries to perform. For questions like "how many types of filters", use ["filter types", "filtering", "match filters"]. For "available field types", use ["field types", "fields", "field methods"]. Pass multiple queries if you aren\'t sure about exact terminology.') + ->items($schema->string()->description('Search query string - extract key terms from user questions')) + ->minItems(1), + 'question_type' => $schema->string() + ->description('Type of question being asked: "count" (how many types), "list" (what are available), "howto" (how to do something), "concept" (explain concept), "example" (show examples)') + ->optional(), + 'category' => $schema->string() + ->description('Limit search to specific category: installation, repositories, fields, filters, auth, actions, performance, testing') + ->optional(), + 'limit' => $schema->integer() + ->description('Maximum number of results to return per query (default: 10, max: 50)') + ->optional(), + 'token_limit' => $schema->integer() + ->description('Maximum number of tokens to return in the response. Defaults to 10,000 tokens, maximum 100,000 tokens.') + ->optional(), + ]; } /** - * @param array $arguments + * Handle the tool request. */ - public function handle(array $arguments): ToolResult|Generator + public function handle(Request $request): Response { try { $queries = array_filter( - array_map('trim', $arguments['queries']), + array_map('trim', $request->get('queries', [])), fn ($query) => $query !== '' && strlen($query) >= config('restify-boost.search.min_query_length', 2) ); if (empty($queries)) { - return ToolResult::error('At least one valid query is required (minimum 2 characters)'); + return Response::error('At least one valid query is required (minimum 2 characters)'); } - $questionType = $arguments['question_type'] ?? null; - $category = $arguments['category'] ?? null; + $questionType = $request->get('question_type'); + $category = $request->get('category'); $limit = min( - $arguments['limit'] ?? config('restify-boost.search.default_limit', 10), + $request->get('limit', config('restify-boost.search.default_limit', 10)), config('restify-boost.search.max_limit', 50) ); $tokenLimit = min( - $arguments['token_limit'] ?? config('restify-boost.optimization.default_token_limit', 10000), + $request->get('token_limit', config('restify-boost.optimization.default_token_limit', 10000)), config('restify-boost.optimization.max_token_limit', 100000) ); @@ -94,9 +96,9 @@ public function handle(array $arguments): ToolResult|Generator // Format and optimize results $response = $this->formatResults($allResults, $tokenLimit, $questionType, $queries); - return ToolResult::text($response); + return Response::text($response); } catch (\Throwable $e) { - return ToolResult::error('Search failed: '.$e->getMessage()); + return Response::error('Search failed: '.$e->getMessage()); } } @@ -138,7 +140,7 @@ protected function scanDirectoryForMarkdown(string $directory): array return $files; } - protected function handleNoResults(array $queries, ?string $category): ToolResult + protected function handleNoResults(array $queries, ?string $category): Response { $suggestions = []; @@ -170,7 +172,7 @@ protected function handleNoResults(array $queries, ?string $category): ToolResul $message .= "\n\n".implode("\n", $suggestions); - return ToolResult::text($message); + return Response::text($message); } protected function formatResults(array $allResults, int $tokenLimit, ?string $questionType = null, array $originalQueries = []): string diff --git a/src/Mcp/Tools/UpgradeRestifyTool.php b/src/Mcp/Tools/UpgradeRestifyTool.php index 92c4226..dfab595 100644 --- a/src/Mcp/Tools/UpgradeRestifyTool.php +++ b/src/Mcp/Tools/UpgradeRestifyTool.php @@ -4,11 +4,11 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Generator; use Illuminate\Support\Facades\File; use Laravel\Mcp\Server\Tool; -use Laravel\Mcp\Server\Tools\ToolInputSchema; -use Laravel\Mcp\Server\Tools\ToolResult; +use Illuminate\JsonSchema\JsonSchema; +use Laravel\Mcp\Request; +use Laravel\Mcp\Response; use Symfony\Component\Finder\Finder; class UpgradeRestifyTool extends Tool @@ -36,43 +36,37 @@ public function description(): string return 'Upgrade Laravel Restify from version 9.x to 10.x. This tool migrates repositories to use modern PHP attributes for model definitions, converts static search/sort arrays to field-level methods, checks config file compatibility, and provides a comprehensive upgrade report with recommendations.'; } - public function schema(ToolInputSchema $schema): ToolInputSchema - { - return $schema - ->boolean('dry_run') - ->description('Preview changes without applying them (default: true)') - ->optional() - ->boolean('migrate_attributes') - ->description('Convert static $model properties to PHP attributes (default: true)') - ->optional() - ->boolean('migrate_fields') - ->description('Convert static $search/$sort arrays to field-level methods (default: true)') - ->optional() - ->boolean('check_config') - ->description('Check and report config file compatibility (default: true)') - ->optional() - ->boolean('backup_files') - ->description('Create backups of modified files (default: true)') - ->optional() - ->boolean('interactive') - ->description('Prompt for confirmation before each change (default: true)') - ->optional() - ->string('path') - ->description('Specific path to scan for repositories (defaults to app/Restify)') - ->optional(); - } - - public function handle(array $arguments): ToolResult|Generator + public function schema(JsonSchema $schema): array + { + return [ + 'dry_run' => $schema->boolean() + ->description('Preview changes without applying them (default: true)'), + 'migrate_attributes' => $schema->boolean() + ->description('Convert static $model properties to PHP attributes (default: true)'), + 'migrate_fields' => $schema->boolean() + ->description('Convert static $search/$sort arrays to field-level methods (default: true)'), + 'check_config' => $schema->boolean() + ->description('Check and report config file compatibility (default: true)'), + 'backup_files' => $schema->boolean() + ->description('Create backups of modified files (default: true)'), + 'interactive' => $schema->boolean() + ->description('Prompt for confirmation before each change (default: true)'), + 'path' => $schema->string() + ->description('Specific path to scan for repositories (defaults to app/Restify)'), + ]; + } + + public function handle(Request $request): Response { try { - $options = $this->parseArguments($arguments); + $options = $this->parseArguments($request); $report = $this->initializeReport(); $repositories = $this->scanRepositories($options['customPath']); $report['summary']['repositories_found'] = count($repositories); if (empty($repositories)) { - return ToolResult::text('No Restify repositories found. Ensure you have repositories in app/Restify or specify a custom path.'); + return Response::text('No Restify repositories found. Ensure you have repositories in app/Restify or specify a custom path.'); } $report = $this->analyzeRepositories($repositories, $report); @@ -90,20 +84,20 @@ public function handle(array $arguments): ToolResult|Generator return $this->generateUpgradeReport($report, $options['dryRun']); } catch (\Throwable $e) { - return ToolResult::error('Restify upgrade failed: '.$e->getMessage()); + return Response::error('Restify upgrade failed: '.$e->getMessage()); } } - private function parseArguments(array $arguments): array + private function parseArguments(Request $request): array { return [ - 'dryRun' => $arguments['dry_run'] ?? true, - 'migrateAttributes' => $arguments['migrate_attributes'] ?? true, - 'migrateFields' => $arguments['migrate_fields'] ?? true, - 'checkConfig' => $arguments['check_config'] ?? true, - 'backupFiles' => $arguments['backup_files'] ?? true, - 'interactive' => $arguments['interactive'] ?? true, - 'customPath' => $arguments['path'] ?? null, + 'dryRun' => $request->get('dry_run') ?? true, + 'migrateAttributes' => $request->get('migrate_attributes') ?? true, + 'migrateFields' => $request->get('migrate_fields') ?? true, + 'checkConfig' => $request->get('check_config') ?? true, + 'backupFiles' => $request->get('backup_files') ?? true, + 'interactive' => $request->get('interactive') ?? true, + 'customPath' => $request->get('path') ?? null, ]; } @@ -568,7 +562,7 @@ protected function createBackup(string $filePath): ?string } } - protected function generateUpgradeReport(array $report, bool $dryRun): ToolResult + protected function generateUpgradeReport(array $report, bool $dryRun): Response { $response = $this->buildReportHeader($dryRun); $response .= $this->buildSummarySection($report); @@ -579,7 +573,7 @@ protected function generateUpgradeReport(array $report, bool $dryRun): ToolResul $response .= $this->buildBackupInformationSection($report); $response .= $this->buildAdditionalResourcesSection(); - return ToolResult::text($response); + return Response::text($response); } private function buildReportHeader(bool $dryRun): string diff --git a/src/RestifyBoostServiceProvider.php b/src/RestifyBoostServiceProvider.php index e4467ec..d7e6b44 100644 --- a/src/RestifyBoostServiceProvider.php +++ b/src/RestifyBoostServiceProvider.php @@ -12,7 +12,6 @@ use BinarCode\RestifyBoost\Services\DocIndexer; use BinarCode\RestifyBoost\Services\DocParser; use Illuminate\Support\ServiceProvider; -use Laravel\Mcp\Server\Facades\Mcp; class RestifyBoostServiceProvider extends ServiceProvider { @@ -40,7 +39,8 @@ public function boot(): void return; } - Mcp::local('laravel-restify', RestifyDocs::class); + // TODO: Update MCP server registration for Laravel MCP 0.2 + // Mcp::local('laravel-restify', RestifyDocs::class); $this->registerPublishing(); $this->registerCommands(); From 3d06d3886db2c4c14170e85df10ff50d202317f6 Mon Sep 17 00:00:00 2001 From: binaryk <6833714+binaryk@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:13:52 +0000 Subject: [PATCH 2/2] Fix styling --- src/Mcp/Tools/DebugApplicationTool.php | 4 ++-- src/Mcp/Tools/GenerateActionTool.php | 4 ++-- src/Mcp/Tools/GenerateGetterTool.php | 4 ++-- src/Mcp/Tools/InstallRestifyTool.php | 26 +++++++++++++------------- src/Mcp/Tools/NavigateDocs.php | 2 +- src/Mcp/Tools/UpgradeRestifyTool.php | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Mcp/Tools/DebugApplicationTool.php b/src/Mcp/Tools/DebugApplicationTool.php index dca796a..3cbbb4a 100644 --- a/src/Mcp/Tools/DebugApplicationTool.php +++ b/src/Mcp/Tools/DebugApplicationTool.php @@ -4,16 +4,16 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; +use Illuminate\JsonSchema\JsonSchema; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; -use Laravel\Mcp\Server\Tool; -use Illuminate\JsonSchema\JsonSchema; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; use Symfony\Component\Finder\Finder; use Throwable; diff --git a/src/Mcp/Tools/GenerateActionTool.php b/src/Mcp/Tools/GenerateActionTool.php index 13f172d..99569e0 100644 --- a/src/Mcp/Tools/GenerateActionTool.php +++ b/src/Mcp/Tools/GenerateActionTool.php @@ -4,12 +4,12 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; +use Illuminate\JsonSchema\JsonSchema; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; -use Laravel\Mcp\Server\Tool; -use Illuminate\JsonSchema\JsonSchema; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; use Symfony\Component\Finder\Finder; class GenerateActionTool extends Tool diff --git a/src/Mcp/Tools/GenerateGetterTool.php b/src/Mcp/Tools/GenerateGetterTool.php index a2e67c6..fe8f44d 100644 --- a/src/Mcp/Tools/GenerateGetterTool.php +++ b/src/Mcp/Tools/GenerateGetterTool.php @@ -4,12 +4,12 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; +use Illuminate\JsonSchema\JsonSchema; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; -use Laravel\Mcp\Server\Tool; -use Illuminate\JsonSchema\JsonSchema; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; use Symfony\Component\Finder\Finder; class GenerateGetterTool extends Tool diff --git a/src/Mcp/Tools/InstallRestifyTool.php b/src/Mcp/Tools/InstallRestifyTool.php index 9f2ded7..89a47d1 100644 --- a/src/Mcp/Tools/InstallRestifyTool.php +++ b/src/Mcp/Tools/InstallRestifyTool.php @@ -4,13 +4,13 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; +use Illuminate\JsonSchema\JsonSchema; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Process; -use Laravel\Mcp\Server\Tool; -use Illuminate\JsonSchema\JsonSchema; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; class InstallRestifyTool extends Tool { @@ -572,11 +572,11 @@ protected function setupMcpRoutes(): array { try { $routesPath = base_path('routes/ai.php'); - + // Check if routes/ai.php already exists if (File::exists($routesPath)) { $existingContent = File::get($routesPath); - + // Check if MCP routes already exist if (str_contains($existingContent, 'RestifyServer') || str_contains($existingContent, 'mcp.restify')) { return [ @@ -585,33 +585,33 @@ protected function setupMcpRoutes(): array 'message' => 'MCP routes already configured in routes/ai.php', ]; } - + // Append to existing file $mcpRoutes = $this->getMcpRoutesContent(); - File::append($routesPath, "\n\n" . $mcpRoutes); - + File::append($routesPath, "\n\n".$mcpRoutes); + return [ 'success' => true, 'step' => 'MCP Setup', 'message' => 'MCP routes added to existing routes/ai.php file', ]; } - + // Create new routes/ai.php file $fullRoutesContent = $this->getFullMcpRoutesFile(); File::put($routesPath, $fullRoutesContent); - + return [ 'success' => true, 'step' => 'MCP Setup', 'message' => 'Created routes/ai.php with MCP server configuration', ]; - + } catch (\Exception $e) { return [ 'success' => false, 'step' => 'MCP Setup', - 'message' => 'Failed to setup MCP routes: ' . $e->getMessage(), + 'message' => 'Failed to setup MCP routes: '.$e->getMessage(), ]; } } @@ -711,7 +711,7 @@ protected function generateSuccessResponse(array $results, Request $request): Re if ($request->get('update_config') ?? true) { $response .= "- `config/restify.php.backup-*` - Backup of previous config (if existed)\n"; } - + if ($request->get('setup_mcp') ?? false) { $response .= "- `routes/ai.php` - MCP server routes for AI integration\n"; } @@ -751,7 +751,7 @@ protected function generateSuccessResponse(array $results, Request $request): Re } if ($request->get('setup_mcp') ?? false) { - $nextStepNumber = ($request->get('enable_sanctum_auth') ?? false) ? "7" : "6"; + $nextStepNumber = ($request->get('enable_sanctum_auth') ?? false) ? '7' : '6'; $response .= "{$nextStepNumber}. **Test MCP Server:** Connect your AI client to `/mcp/restify` endpoint\n"; } diff --git a/src/Mcp/Tools/NavigateDocs.php b/src/Mcp/Tools/NavigateDocs.php index c76c499..c290d0e 100644 --- a/src/Mcp/Tools/NavigateDocs.php +++ b/src/Mcp/Tools/NavigateDocs.php @@ -5,10 +5,10 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; use BinarCode\RestifyBoost\Services\DocIndexer; -use Laravel\Mcp\Server\Tool; use Illuminate\JsonSchema\JsonSchema; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; class NavigateDocs extends Tool { diff --git a/src/Mcp/Tools/UpgradeRestifyTool.php b/src/Mcp/Tools/UpgradeRestifyTool.php index dfab595..570e40e 100644 --- a/src/Mcp/Tools/UpgradeRestifyTool.php +++ b/src/Mcp/Tools/UpgradeRestifyTool.php @@ -4,11 +4,11 @@ namespace BinarCode\RestifyBoost\Mcp\Tools; -use Illuminate\Support\Facades\File; -use Laravel\Mcp\Server\Tool; use Illuminate\JsonSchema\JsonSchema; +use Illuminate\Support\Facades\File; use Laravel\Mcp\Request; use Laravel\Mcp\Response; +use Laravel\Mcp\Server\Tool; use Symfony\Component\Finder\Finder; class UpgradeRestifyTool extends Tool