|
4 | 4 |
|
5 | 5 | use Binaryk\LaravelRestify\Fields\Concerns\HasAction; |
6 | 6 | use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; |
| 7 | +use Binaryk\LaravelRestify\MCP\Concerns\FieldMcpSchemaDetection; |
7 | 8 | use Binaryk\LaravelRestify\Repositories\Repository; |
8 | 9 | use Binaryk\LaravelRestify\Traits\Make; |
9 | 10 | use Closure; |
|
12 | 13 | use Illuminate\Support\Str; |
13 | 14 | use Illuminate\Validation\Rules\Unique; |
14 | 15 | use JsonSerializable; |
15 | | -use Laravel\Mcp\Server\Tools\ToolInputSchema; |
16 | 16 | use ReturnTypeWillChange; |
17 | 17 |
|
18 | 18 | class Field extends OrganicField implements JsonSerializable |
19 | 19 | { |
20 | | - use HasAction; |
21 | 20 | use Make; |
| 21 | + use HasAction; |
| 22 | + use FieldMcpSchemaDetection; |
22 | 23 |
|
23 | 24 | /** |
24 | 25 | * The resource associated with the field. |
@@ -925,202 +926,4 @@ public function toolSchema(callable|Closure $callback): self |
925 | 926 |
|
926 | 927 | return $this; |
927 | 928 | } |
928 | | - |
929 | | - /** |
930 | | - * Resolve the tool schema for this field to be used in MCP tools. |
931 | | - */ |
932 | | - public function resolveToolSchema(ToolInputSchema $schema, Repository $repository): self |
933 | | - { |
934 | | - // Check if there's a custom callback defined |
935 | | - if (is_callable($this->toolInputSchemaCallback)) { |
936 | | - call_user_func($this->toolInputSchemaCallback, $schema, $repository, $this); |
937 | | - |
938 | | - return $this; |
939 | | - } |
940 | | - |
941 | | - // Skip computed fields for default implementation |
942 | | - if ($this->computed()) { |
943 | | - return $this; |
944 | | - } |
945 | | - |
946 | | - $attribute = $this->label ?? $this->attribute; |
947 | | - $fieldType = $this->guessFieldType(); |
948 | | - |
949 | | - // Add the field to schema based on its type |
950 | | - $schemaField = match ($fieldType) { |
951 | | - 'boolean' => $schema->boolean($attribute), |
952 | | - 'number' => $schema->number($attribute), |
953 | | - 'array' => $schema->string($attribute), // Arrays are typically sent as JSON strings |
954 | | - default => $schema->string($attribute) |
955 | | - }; |
956 | | - |
957 | | - // Add description |
958 | | - $description = $this->generateFieldDescription($repository); |
959 | | - $schemaField->description($description); |
960 | | - |
961 | | - // Mark as required if field has required validation |
962 | | - if ($this->isRequired()) { |
963 | | - $schemaField->required(); |
964 | | - } |
965 | | - |
966 | | - return $this; |
967 | | - } |
968 | | - |
969 | | - /** |
970 | | - * Generate a comprehensive description for the field. |
971 | | - */ |
972 | | - protected function generateFieldDescription(Repository $repository): string |
973 | | - { |
974 | | - $attribute = $this->label ?? $this->attribute; |
975 | | - $fieldType = $this->guessFieldType(); |
976 | | - |
977 | | - $description = "Field: {$attribute} (type: {$fieldType})"; |
978 | | - |
979 | | - // Add validation rules information |
980 | | - $rules = $this->getStoringRules(); |
981 | | - if (! empty($rules)) { |
982 | | - $ruleDescriptions = $this->formatValidationRules($rules); |
983 | | - if (! empty($ruleDescriptions)) { |
984 | | - $description .= '. Validation: '.implode(', ', $ruleDescriptions); |
985 | | - } |
986 | | - } |
987 | | - |
988 | | - // Add relationship information for relationship fields |
989 | | - if ($this->isRelationshipField()) { |
990 | | - $description .= '. This is a relationship field'; |
991 | | - } |
992 | | - |
993 | | - // Add file information for file fields |
994 | | - if ($this instanceof File) { |
995 | | - $description .= '. Upload a file'; |
996 | | - } |
997 | | - |
998 | | - // Add examples based on field type and name |
999 | | - $examples = $this->generateFieldExamples(); |
1000 | | - if (! empty($examples)) { |
1001 | | - $description .= '. Examples: '.implode(', ', $examples); |
1002 | | - } |
1003 | | - |
1004 | | - return $description; |
1005 | | - } |
1006 | | - |
1007 | | - /** |
1008 | | - * Check if field is required based on validation rules. |
1009 | | - */ |
1010 | | - protected function isRequired(): bool |
1011 | | - { |
1012 | | - $rules = $this->getStoringRules(); |
1013 | | - |
1014 | | - return in_array('required', $rules) || |
1015 | | - collect($rules)->contains(function ($rule) { |
1016 | | - return is_string($rule) && str_starts_with($rule, 'required'); |
1017 | | - }); |
1018 | | - } |
1019 | | - |
1020 | | - /** |
1021 | | - * Check if field is a relationship field. |
1022 | | - */ |
1023 | | - protected function isRelationshipField(): bool |
1024 | | - { |
1025 | | - return $this instanceof \Binaryk\LaravelRestify\Fields\BelongsTo || |
1026 | | - $this instanceof \Binaryk\LaravelRestify\Fields\HasOne || |
1027 | | - $this instanceof \Binaryk\LaravelRestify\Fields\HasMany || |
1028 | | - $this instanceof \Binaryk\LaravelRestify\Fields\BelongsToMany; |
1029 | | - } |
1030 | | - |
1031 | | - /** |
1032 | | - * Format validation rules for display. |
1033 | | - */ |
1034 | | - protected function formatValidationRules(array $rules): array |
1035 | | - { |
1036 | | - $formatted = []; |
1037 | | - |
1038 | | - foreach ($rules as $rule) { |
1039 | | - if (is_string($rule)) { |
1040 | | - $formatted[] = match (true) { |
1041 | | - $rule === 'required' => 'required', |
1042 | | - str_starts_with($rule, 'min:') => 'minimum '.substr($rule, 4).' characters', |
1043 | | - str_starts_with($rule, 'max:') => 'maximum '.substr($rule, 4).' characters', |
1044 | | - str_starts_with($rule, 'between:') => 'between '.str_replace(',', ' and ', substr($rule, 8)), |
1045 | | - $rule === 'email' => 'valid email format', |
1046 | | - $rule === 'url' => 'valid URL format', |
1047 | | - $rule === 'numeric' => 'numeric value', |
1048 | | - $rule === 'integer' => 'integer value', |
1049 | | - $rule === 'boolean' => 'boolean value (true/false)', |
1050 | | - $rule === 'array' => 'array format', |
1051 | | - str_starts_with($rule, 'in:') => 'allowed values: '.str_replace(',', ', ', substr($rule, 3)), |
1052 | | - default => $rule |
1053 | | - }; |
1054 | | - } |
1055 | | - } |
1056 | | - |
1057 | | - return array_filter($formatted); |
1058 | | - } |
1059 | | - |
1060 | | - /** |
1061 | | - * Generate examples for the field. |
1062 | | - */ |
1063 | | - protected function generateFieldExamples(): array |
1064 | | - { |
1065 | | - $attribute = strtolower($this->attribute); |
1066 | | - $fieldType = $this->guessFieldType(); |
1067 | | - |
1068 | | - return match ($fieldType) { |
1069 | | - 'boolean' => ['true', 'false'], |
1070 | | - 'number' => $this->getNumberExamples($attribute), |
1071 | | - 'array' => ['["item1", "item2"]', '{"key": "value"}'], |
1072 | | - default => $this->getStringExamples($attribute) |
1073 | | - }; |
1074 | | - } |
1075 | | - |
1076 | | - /** |
1077 | | - * Get number field examples. |
1078 | | - */ |
1079 | | - protected function getNumberExamples(string $attribute): array |
1080 | | - { |
1081 | | - if (str_contains($attribute, 'price') || str_contains($attribute, 'cost') || str_contains($attribute, 'amount')) { |
1082 | | - return ['99.99', '29.95']; |
1083 | | - } |
1084 | | - if (str_contains($attribute, 'age')) { |
1085 | | - return ['25', '30']; |
1086 | | - } |
1087 | | - if (str_contains($attribute, 'year')) { |
1088 | | - return ['2024', '2023']; |
1089 | | - } |
1090 | | - if (str_ends_with($attribute, '_id')) { |
1091 | | - return ['1', '42']; |
1092 | | - } |
1093 | | - |
1094 | | - return ['1', '100']; |
1095 | | - } |
1096 | | - |
1097 | | - /** |
1098 | | - * Get string field examples. |
1099 | | - */ |
1100 | | - protected function getStringExamples(string $attribute): array |
1101 | | - { |
1102 | | - if (str_contains($attribute, 'email')) { |
1103 | | - |
1104 | | - } |
1105 | | - if (str_contains($attribute, 'name')) { |
1106 | | - return ['John Doe', 'Sample Name']; |
1107 | | - } |
1108 | | - if (str_contains($attribute, 'title')) { |
1109 | | - return ['Sample Title', 'My Title']; |
1110 | | - } |
1111 | | - if (str_contains($attribute, 'description')) { |
1112 | | - return ['A detailed description...', 'Brief summary']; |
1113 | | - } |
1114 | | - if (str_contains($attribute, 'url') || str_contains($attribute, 'link')) { |
1115 | | - return ['https://example.com', 'https://website.org/path']; |
1116 | | - } |
1117 | | - if (str_contains($attribute, 'phone')) { |
1118 | | - return ['+1234567890', '(555) 123-4567']; |
1119 | | - } |
1120 | | - if (str_contains($attribute, 'password')) { |
1121 | | - return ['SecurePassword123!', 'MyPassword456']; |
1122 | | - } |
1123 | | - |
1124 | | - return ['sample text', 'example value']; |
1125 | | - } |
1126 | 929 | } |
0 commit comments