Skip to content

Commit b303661

Browse files
authored
10.1.0 (#654)
* feat: adding field description for the mcp * Fix styling --------- Co-authored-by: binaryk <[email protected]>
1 parent 88e9f7d commit b303661

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

.github/workflows/release.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Create Release
2+
3+
on:
4+
push:
5+
branches:
6+
- 10.x
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
if: github.event_name == 'push' && github.ref == 'refs/heads/10.x'
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Setup PHP
20+
uses: shivammathur/setup-php@v2
21+
with:
22+
php-version: 8.3
23+
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
24+
coverage: none
25+
26+
- name: Install dependencies
27+
run: composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
28+
29+
- name: Run tests
30+
run: |
31+
composer require "laravel/framework:^11.0" "orchestra/testbench:^9.0" --no-interaction --no-update
32+
composer update --prefer-stable --prefer-dist --no-interaction
33+
vendor/bin/pest --ci
34+
35+
- name: Get next version
36+
id: get_version
37+
run: |
38+
# Get the latest tag (handle both v-prefixed and non-prefixed)
39+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
40+
echo "Latest tag: $LATEST_TAG"
41+
42+
# Extract version numbers (remove 'v' prefix if present)
43+
VERSION_NUM=${LATEST_TAG#v}
44+
45+
# Split version into parts
46+
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_NUM"
47+
48+
# Default to 0 if parts are empty
49+
MAJOR=${MAJOR:-0}
50+
MINOR=${MINOR:-0}
51+
PATCH=${PATCH:-0}
52+
53+
# Get the previous tag for commit analysis
54+
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
55+
56+
# Analyze commits since last tag to determine version bump
57+
if [ -z "$PREVIOUS_TAG" ]; then
58+
# If no previous tags, analyze all commits
59+
COMMITS=$(git log --pretty=format:"%s" --no-merges)
60+
else
61+
# Get commits since last tag
62+
COMMITS=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"%s" --no-merges)
63+
fi
64+
65+
echo "Analyzing commits:"
66+
echo "$COMMITS"
67+
68+
# Check for breaking changes (MAJOR version bump)
69+
if echo "$COMMITS" | grep -E "^(BREAKING|BREAKING CHANGE|feat!|fix!):" > /dev/null; then
70+
echo "Found breaking changes, incrementing MAJOR version"
71+
MAJOR=$((MAJOR + 1))
72+
MINOR=0
73+
PATCH=0
74+
# Check for features (MINOR version bump)
75+
elif echo "$COMMITS" | grep -E "^feat(\(.+\))?:" > /dev/null; then
76+
echo "Found features, incrementing MINOR version"
77+
MINOR=$((MINOR + 1))
78+
PATCH=0
79+
# Default to patch version bump
80+
else
81+
echo "No features or breaking changes found, incrementing PATCH version"
82+
PATCH=$((PATCH + 1))
83+
fi
84+
85+
# Create new version (no v prefix)
86+
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
87+
88+
echo "New version: $NEW_VERSION"
89+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
90+
echo "tag_name=$NEW_VERSION" >> $GITHUB_OUTPUT
91+
92+
- name: Generate changelog
93+
id: changelog
94+
run: |
95+
# Get the previous tag for changelog
96+
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
97+
98+
if [ -z "$PREVIOUS_TAG" ]; then
99+
# If no previous tags, get all commits
100+
COMMITS=$(git log --pretty=format:"* %s (%an)" --no-merges)
101+
else
102+
# Get commits since last tag
103+
COMMITS=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"* %s (%an)" --no-merges)
104+
fi
105+
106+
# Create changelog
107+
CHANGELOG="## What's Changed\n\n$COMMITS"
108+
109+
# Handle multiline output for GitHub Actions
110+
echo "changelog<<EOF" >> $GITHUB_OUTPUT
111+
echo -e "$CHANGELOG" >> $GITHUB_OUTPUT
112+
echo "EOF" >> $GITHUB_OUTPUT
113+
114+
- name: Create Release
115+
uses: softprops/action-gh-release@v1
116+
env:
117+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118+
with:
119+
tag_name: ${{ steps.get_version.outputs.tag_name }}
120+
name: Release ${{ steps.get_version.outputs.tag_name }}
121+
body: ${{ steps.changelog.outputs.changelog }}
122+
draft: false
123+
prerelease: false

docs-v2/content/en/api/fields.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,44 @@ The MCP visibility system automatically detects when a request is coming from an
10651065

10661066
This allows you to have different field visibility for your regular API consumers versus AI agents accessing your data through MCP tools.
10671067

1068+
### Field Descriptions
1069+
1070+
Fields can have custom descriptions that are used when generating schema documentation, particularly useful for MCP tools and API documentation:
1071+
1072+
```php
1073+
public function fields(RestifyRequest $request)
1074+
{
1075+
return [
1076+
field('status')
1077+
->description('The current status of the item')
1078+
->rules(['required', 'string']),
1079+
1080+
field('feedbackable_id')
1081+
->description('This is the id of the employee.')
1082+
->rules(['required', 'string', 'max:26']),
1083+
1084+
field('priority')
1085+
->description(function($generatedDescription, $field, $repository) {
1086+
return $generatedDescription . ' - Values range from 1 (low) to 5 (high)';
1087+
}),
1088+
];
1089+
}
1090+
```
1091+
1092+
The `description()` method accepts either:
1093+
- **String**: A static description text
1094+
- **Closure**: A callback that receives the auto-generated description, field instance, and repository for dynamic modifications
1095+
1096+
When using a closure, you can:
1097+
- Modify the automatically generated description
1098+
- Add context-specific information
1099+
- Access field and repository data for dynamic descriptions
1100+
1101+
The description callback receives three parameters:
1102+
- `$generatedDescription` - The automatically generated description based on field type and validation rules
1103+
- `$field` - The field instance
1104+
- `$repository` - The repository context
1105+
10681106
### Custom Tool Schema
10691107

10701108
When using MCP, you can define custom schema definitions for individual fields using the `toolSchema()` method:

src/Fields/Field.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ class Field extends OrganicField implements JsonSerializable, Matchable, Sortabl
145145

146146
public $toolInputSchemaCallback = null;
147147

148+
/**
149+
* Closure to modify the generated field description.
150+
*/
151+
public $descriptionCallback = null;
152+
148153
/**
149154
* Create a new field.
150155
*
@@ -937,4 +942,22 @@ public function toolSchema(callable|Closure $callback): self
937942

938943
return $this;
939944
}
945+
946+
/**
947+
* Set a callback to modify the generated field description.
948+
*
949+
* @return $this
950+
*/
951+
public function description(string|callable|Closure $callback): self
952+
{
953+
if (is_string($callback)) {
954+
$this->descriptionCallback = fn () => $callback;
955+
956+
return $this;
957+
}
958+
959+
$this->descriptionCallback = $callback;
960+
961+
return $this;
962+
}
940963
}

src/MCP/Concerns/FieldMcpSchemaDetection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ protected function generateFieldDescription(Repository $repository): string
8383
$description .= '. Examples: '.implode(', ', $examples);
8484
}
8585

86+
// Apply custom description callback if provided
87+
if (is_callable($this->descriptionCallback)) {
88+
$description = call_user_func($this->descriptionCallback, $description, $this, $repository);
89+
}
90+
8691
return $description;
8792
}
8893

tests/Fields/FieldMcpSchemaDetectionTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,40 @@ public function test_resolve_tool_schema_with_custom_callback(): void
6565
$this->assertSame($field, $result);
6666
}
6767

68+
public function test_resolve_tool_schema_with_string_description(): void
69+
{
70+
$schema = Mockery::mock(ToolInputSchema::class);
71+
$repository = new PostRepository;
72+
73+
$schema->shouldReceive('string')->with('title')->once()->andReturnSelf();
74+
$schema->shouldReceive('description')->with('Custom description for the title field')->once()->andReturnSelf();
75+
76+
$field = $this->createTestField('title');
77+
$field->description('Custom description for the title field');
78+
79+
$result = $field->resolveToolSchema($schema, $repository);
80+
81+
$this->assertSame($field, $result);
82+
}
83+
84+
public function test_resolve_tool_schema_with_closure_description(): void
85+
{
86+
$schema = Mockery::mock(ToolInputSchema::class);
87+
$repository = new PostRepository;
88+
89+
$schema->shouldReceive('string')->with('title')->once()->andReturnSelf();
90+
$schema->shouldReceive('description')->with('Field: title (type: string). Examples: Sample Title, My Title - Custom addition')->once()->andReturnSelf();
91+
92+
$field = $this->createTestField('title');
93+
$field->description(function ($generatedDescription, $field, $repository) {
94+
return $generatedDescription.' - Custom addition';
95+
});
96+
97+
$result = $field->resolveToolSchema($schema, $repository);
98+
99+
$this->assertSame($field, $result);
100+
}
101+
68102
public function test_get_string_examples_for_different_contexts(): void
69103
{
70104
$field = $this->createTestField('email');
@@ -107,6 +141,10 @@ protected function createTestField(string $attribute, array $rules = []): Field
107141
$field->shouldReceive('generateFieldExamples')->passthru();
108142
$field->shouldReceive('getNumberExamples')->passthru();
109143
$field->shouldReceive('getStringExamples')->passthru();
144+
$field->shouldReceive('description')->passthru();
145+
146+
// Initialize the descriptionCallback property
147+
$field->descriptionCallback = null;
110148

111149
return $field;
112150
}

0 commit comments

Comments
 (0)