Skip to content

Commit a6a04f9

Browse files
feat: add closure handler support and custom input schema for tools
- Bump php-mcp/server dependency to ^3.2 - Add support for callable/closure handlers in all MCP element types (tools, resources, resource templates, prompts) - Introduce inputSchema() method for tools to define custom JSON validation schemas Breaking: None - all existing v3.0 code remains compatible
1 parent 6f9ce34 commit a6a04f9

File tree

9 files changed

+262
-19
lines changed

9 files changed

+262
-19
lines changed

README.md

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,69 @@ Mcp::tool([CalculatorService::class, 'add'])
9595
Mcp::tool(EmailService::class)
9696
->description('Send emails to users');
9797

98+
// Register a closure as a tool with custom input schema
99+
Mcp::tool(function(float $x, float $y): float {
100+
return $x * $y;
101+
})
102+
->name('multiply')
103+
->description('Multiply two numbers')
104+
->inputSchema([
105+
'type' => 'object',
106+
'properties' => [
107+
'x' => ['type' => 'number', 'description' => 'First number'],
108+
'y' => ['type' => 'number', 'description' => 'Second number'],
109+
],
110+
'required' => ['x', 'y'],
111+
]);
112+
98113
// Register a resource with metadata
99114
Mcp::resource('config://app/settings', [UserService::class, 'getAppSettings'])
100115
->name('app_settings')
101116
->description('Application configuration settings')
102117
->mimeType('application/json')
103118
->size(1024);
104119

120+
// Register a closure as a resource
121+
Mcp::resource('system://time', function(): string {
122+
return now()->toISOString();
123+
})
124+
->name('current_time')
125+
->description('Get current server time')
126+
->mimeType('text/plain');
127+
105128
// Register a resource template for dynamic content
106129
Mcp::resourceTemplate('user://{userId}/profile', [UserService::class, 'getUserProfile'])
107130
->name('user_profile')
108131
->description('Get user profile by ID')
109132
->mimeType('application/json');
110133

134+
// Register a closure as a resource template
135+
Mcp::resourceTemplate('file://{path}', function(string $path): string {
136+
if (!file_exists($path) || !is_readable($path)) {
137+
throw new \InvalidArgumentException("File not found or not readable: {$path}");
138+
}
139+
return file_get_contents($path);
140+
})
141+
->name('file_reader')
142+
->description('Read file contents by path')
143+
->mimeType('text/plain');
144+
111145
// Register a prompt generator
112146
Mcp::prompt([PromptService::class, 'generateWelcome'])
113147
->name('welcome_user')
114148
->description('Generate a personalized welcome message');
149+
150+
// Register a closure as a prompt
151+
Mcp::prompt(function(string $topic, string $tone = 'professional'): array {
152+
return [
153+
[
154+
'role' => 'user',
155+
'content' => "Write a {$tone} summary about {$topic}. Make it informative and engaging."
156+
]
157+
];
158+
})
159+
->name('topic_summary')
160+
->description('Generate topic summary prompts');
115161
```
116162

117163
**Available Fluent Methods:**
@@ -120,14 +166,23 @@ Mcp::prompt([PromptService::class, 'generateWelcome'])
120166
- `name(string $name)`: Override the inferred name
121167
- `description(string $description)`: Set a custom description
122168

169+
**For Tools:**
170+
- `annotations(ToolAnnotations $annotations)`: Add MCP tool annotations
171+
- `inputSchema(array $schema)`: Define custom JSON schema for parameters
172+
123173
**For Resources:**
124174
- `mimeType(string $mimeType)`: Specify content type
125175
- `size(int $size)`: Set content size in bytes
126-
- `annotations(array|Annotations $annotations)`: Add MCP annotations
176+
- `annotations(Annotations $annotations)`: Add MCP annotations
177+
178+
**For Resource Templates:**
179+
- `mimeType(string $mimeType)`: Specify content type
180+
- `annotations(Annotations $annotations)`: Add MCP annotations
127181

128182
**Handler Formats:**
129183
- `[ClassName::class, 'methodName']` - Class method
130184
- `InvokableClass::class` - Invokable class with `__invoke()` method
185+
- `function(...) { ... }` - Callables (v3.2+)
131186

132187
### 2. Attribute-Based Discovery
133188

@@ -717,6 +772,69 @@ Create a dedicated log channel in `config/logging.php`:
717772

718773
## Migration Guide
719774

775+
### From v3.0 to v3.1
776+
777+
**New Handler Types:**
778+
779+
Laravel MCP v3.1 introduces support for closure handlers, expanding beyond just class methods and invokable classes:
780+
781+
```php
782+
// v3.0 and earlier - Class-based handlers only
783+
Mcp::tool([CalculatorService::class, 'add'])
784+
->name('add_numbers');
785+
786+
Mcp::tool(EmailService::class) // Invokable class
787+
->name('send_email');
788+
789+
// v3.1+ - Now supports closures
790+
Mcp::tool(function(float $x, float $y): float {
791+
return $x * $y;
792+
})
793+
->name('multiply')
794+
->description('Multiply two numbers');
795+
796+
Mcp::resource('system://time', function(): string {
797+
return now()->toISOString();
798+
})
799+
->name('current_time');
800+
```
801+
802+
**Input Schema Support:**
803+
804+
Tools can now define custom JSON schemas for parameter validation:
805+
806+
```php
807+
// v3.1+ - Custom input schema
808+
Mcp::tool([CalculatorService::class, 'calculate'])
809+
->inputSchema([
810+
'type' => 'object',
811+
'properties' => [
812+
'operation' => [
813+
'type' => 'string',
814+
'enum' => ['add', 'subtract', 'multiply', 'divide']
815+
],
816+
'numbers' => [
817+
'type' => 'array',
818+
'items' => ['type' => 'number'],
819+
'minItems' => 2
820+
]
821+
],
822+
'required' => ['operation', 'numbers']
823+
]);
824+
```
825+
826+
**Enhanced Blueprint Methods:**
827+
828+
New fluent methods available on blueprints:
829+
830+
```php
831+
->inputSchema(array $schema) // Define custom parameter schema
832+
```
833+
834+
**No Breaking Changes:**
835+
836+
All existing v3.0 code continues to work without modification. The new features are additive enhancements.
837+
720838
### From v2.x to v3.x
721839

722840
**Configuration Changes:**

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"require": {
2929
"php": "^8.1",
3030
"laravel/framework": "^9.46 || ^10.34 || ^11.29 || ^12.0",
31-
"php-mcp/server": "^3.1"
31+
"php-mcp/server": "^3.2"
3232
},
3333
"require-dev": {
3434
"laravel/pint": "^1.13",

src/Blueprints/PromptBlueprint.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ class PromptBlueprint
88
{
99
public ?string $description = null;
1010

11+
/**
12+
* @param string|array|callable $handler
13+
*/
1114
public function __construct(
12-
public array|string $handler,
15+
public mixed $handler,
1316
public ?string $name = null
1417
) {}
1518

src/Blueprints/ResourceBlueprint.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpMcp\Laravel\Blueprints;
66

7+
use Closure;
78
use PhpMcp\Schema\Annotations;
89

910
class ResourceBlueprint
@@ -18,9 +19,12 @@ class ResourceBlueprint
1819

1920
public ?Annotations $annotations = null;
2021

22+
/**
23+
* @param string|array|callable $handler
24+
*/
2125
public function __construct(
2226
public string $uri,
23-
public array|string $handler,
27+
public mixed $handler,
2428
) {}
2529

2630
public function name(string $name): static

src/Blueprints/ResourceTemplateBlueprint.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpMcp\Laravel\Blueprints;
66

7+
use Closure;
78
use PhpMcp\Schema\Annotations;
89

910
class ResourceTemplateBlueprint
@@ -16,9 +17,12 @@ class ResourceTemplateBlueprint
1617

1718
public ?Annotations $annotations = null;
1819

20+
/**
21+
* @param string|array|callable $handler
22+
*/
1923
public function __construct(
2024
public string $uriTemplate,
21-
public array|string $handler,
25+
public mixed $handler,
2226
) {}
2327

2428
public function name(string $name): static

src/Blueprints/ToolBlueprint.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44

55
namespace PhpMcp\Laravel\Blueprints;
66

7+
use Closure;
78
use PhpMcp\Schema\ToolAnnotations;
89

910
class ToolBlueprint
1011
{
1112
public ?string $description = null;
1213
public ?ToolAnnotations $annotations = null;
14+
public ?array $inputSchema = null;
1315

16+
/**
17+
* @param string|array|callable $handler
18+
*/
1419
public function __construct(
15-
public array|string $handler,
20+
public mixed $handler,
1621
public ?string $name = null
1722
) {}
1823

@@ -36,4 +41,11 @@ public function annotations(ToolAnnotations $annotations): static
3641

3742
return $this;
3843
}
44+
45+
public function inputSchema(array $inputSchema): static
46+
{
47+
$this->inputSchema = $inputSchema;
48+
49+
return $this;
50+
}
3951
}

src/Facades/Mcp.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
use PhpMcp\Laravel\Blueprints\ToolBlueprint;
1212

1313
/**
14-
* @method static ToolBlueprint tool(string|array $handlerOrName, array|string|null $handler = null)
15-
* @method static ResourceBlueprint resource(string $uri, array|string $handler)
16-
* @method static ResourceTemplateBlueprint resourceTemplate(string $uriTemplate, array|string $handler)
17-
* @method static PromptBlueprint prompt(string|array $handlerOrName, array|string|null $handler = null)
14+
* @method static ToolBlueprint tool(string|callable|array $handlerOrName, callable|array|string|null $handler = null)
15+
* @method static ResourceBlueprint resource(string $uri, callable|array|string $handler)
16+
* @method static ResourceTemplateBlueprint resourceTemplate(string $uriTemplate, callable|array|string $handler)
17+
* @method static PromptBlueprint prompt(string|callable|array $handlerOrName, callable|array|string|null $handler = null)
1818
*
1919
* @see \PhpMcp\Laravel\McpRegistrar
2020
*/

src/McpRegistrar.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ public function __construct() {}
3434
* Mcp::tool('tool_name', $handler)
3535
* Mcp::tool($handler) // Name will be inferred
3636
*/
37-
public function tool(string|array ...$args): ToolBlueprint
37+
public function tool(string|callable|array ...$args): ToolBlueprint
3838
{
3939
$name = null;
4040
$handler = null;
4141

42-
if (count($args) === 1 && (is_array($args[0]) || (is_string($args[0]) && (class_exists($args[0]) || is_callable($args[0]))))) {
42+
if (count($args) === 1 && (is_callable($args[0]) || is_array($args[0]) || (is_string($args[0]) && (class_exists($args[0]) || is_callable($args[0]))))) {
4343
$handler = $args[0];
44-
} elseif (count($args) === 2 && is_string($args[0]) && (is_array($args[1]) || (is_string($args[1]) && (class_exists($args[1]) || is_callable($args[1]))))) {
44+
} elseif (count($args) === 2 && is_string($args[0]) && (is_callable($args[1]) || is_array($args[1]) || (is_string($args[1]) && (class_exists($args[1]) || is_callable($args[1]))))) {
4545
$name = $args[0];
4646
$handler = $args[1];
4747
} else {
@@ -57,7 +57,7 @@ public function tool(string|array ...$args): ToolBlueprint
5757
/**
5858
* Register a new resource.
5959
*/
60-
public function resource(string $uri, array|string $handler): ResourceBlueprint
60+
public function resource(string $uri, callable|array|string $handler): ResourceBlueprint
6161
{
6262
$pendingResource = new ResourceBlueprint($uri, $handler);
6363
$this->pendingResources[] = $pendingResource;
@@ -68,7 +68,7 @@ public function resource(string $uri, array|string $handler): ResourceBlueprint
6868
/**
6969
* Register a new resource template.
7070
*/
71-
public function resourceTemplate(string $uriTemplate, array|string $handler): ResourceTemplateBlueprint
71+
public function resourceTemplate(string $uriTemplate, callable|array|string $handler): ResourceTemplateBlueprint
7272
{
7373
$pendingResourceTemplate = new ResourceTemplateBlueprint($uriTemplate, $handler);
7474
$this->pendingResourceTemplates[] = $pendingResourceTemplate;
@@ -83,14 +83,14 @@ public function resourceTemplate(string $uriTemplate, array|string $handler): Re
8383
* Mcp::prompt('prompt_name', $handler)
8484
* Mcp::prompt($handler) // Name will be inferred
8585
*/
86-
public function prompt(string|array ...$args): PromptBlueprint
86+
public function prompt(string|callable|array ...$args): PromptBlueprint
8787
{
8888
$name = null;
8989
$handler = null;
9090

91-
if (count($args) === 1 && (is_array($args[0]) || (is_string($args[0]) && (class_exists($args[0]) || is_callable($args[0]))))) {
91+
if (count($args) === 1 && (is_callable($args[0]) || is_array($args[0]) || (is_string($args[0]) && (class_exists($args[0]) || is_callable($args[0]))))) {
9292
$handler = $args[0];
93-
} elseif (count($args) === 2 && is_string($args[0]) && (is_array($args[1]) || (is_string($args[1]) && (class_exists($args[1]) || is_callable($args[1]))))) {
93+
} elseif (count($args) === 2 && is_string($args[0]) && (is_callable($args[1]) || is_array($args[1]) || (is_string($args[1]) && (class_exists($args[1]) || is_callable($args[1]))))) {
9494
$name = $args[0];
9595
$handler = $args[1];
9696
} else {
@@ -106,7 +106,7 @@ public function prompt(string|array ...$args): PromptBlueprint
106106
public function applyBlueprints(ServerBuilder $builder): void
107107
{
108108
foreach ($this->pendingTools as $pendingTool) {
109-
$builder->withTool($pendingTool->handler, $pendingTool->name, $pendingTool->description, $pendingTool->annotations);
109+
$builder->withTool($pendingTool->handler, $pendingTool->name, $pendingTool->description, $pendingTool->annotations, $pendingTool->inputSchema);
110110
}
111111

112112
foreach ($this->pendingResources as $pendingResource) {

0 commit comments

Comments
 (0)