Skip to content

Commit 12fbae7

Browse files
committed
adds work in progress
1 parent 6e61fe2 commit 12fbae7

35 files changed

+690
-569
lines changed

src/BoostServiceProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Illuminate\View\Compilers\BladeCompiler;
1515
use Laravel\Boost\Mcp\Boost;
1616
use Laravel\Boost\Middleware\InjectBoost;
17-
use Laravel\Mcp\Server\Facades\Mcp;
17+
use Laravel\Mcp\Facades\Mcp;
1818
use Laravel\Roster\Roster;
1919

2020
class BoostServiceProvider extends ServiceProvider

src/Console/ExecuteToolCommand.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
use Illuminate\Console\Command;
88
use Laravel\Boost\Mcp\ToolRegistry;
9-
use Laravel\Mcp\Server\Tools\ToolResult;
9+
use Laravel\Mcp\Request;
10+
use Laravel\Mcp\Response;
1011

1112
class ExecuteToolCommand extends Command
1213
{
@@ -39,17 +40,25 @@ public function handle(): int
3940
try {
4041
// Execute the tool
4142
$tool = app($toolClass);
42-
$result = $tool->handle($arguments ?? []);
43+
44+
$request = new Request($arguments ?? []);
45+
$response = $tool->handle($request);
4346

4447
// Output the result as JSON for the parent process
45-
echo json_encode($result->toArray());
48+
echo json_encode([
49+
'isError' => $response->isError(),
50+
'content' => (string) $response->content(),
51+
]);
4652

4753
return 0;
4854

4955
} catch (\Throwable $e) {
5056
// Output error result
51-
$errorResult = ToolResult::error("Tool execution failed (E_THROWABLE): {$e->getMessage()}");
52-
$this->error(json_encode($errorResult->toArray()));
57+
$errorResult = Response::error("Tool execution failed (E_THROWABLE): {$e->getMessage()}");
58+
$this->error(json_encode([
59+
'isError' => true,
60+
'content' => (string) $errorResult->content(),
61+
]));
5362

5463
return 1;
5564
}

src/Mcp/Boost.php

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,106 +10,138 @@
1010

1111
class Boost extends Server
1212
{
13-
public string $serverName = 'Laravel Boost';
13+
/**
14+
* The MCP server's name.
15+
*/
16+
protected string $name = 'Laravel Boost';
1417

15-
public string $serverVersion = '0.0.1';
18+
/**
19+
* The MCP server's version.
20+
*/
21+
protected string $version = '0.0.1';
1622

17-
public string $instructions = 'Laravel ecosystem MCP server offering database schema access, Artisan commands, error logs, Tinker execution, semantic documentation search and more. Boost helps with code generation.';
23+
/**
24+
* The MCP server's instructions for the LLM.
25+
*/
26+
protected string $instructions = 'Laravel ecosystem MCP server offering database schema access, Artisan commands, error logs, Tinker execution, semantic documentation search and more. Boost helps with code generation.';
1827

19-
public int $defaultPaginationLength = 50;
28+
/**
29+
* The tools registered with this MCP server.
30+
*
31+
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
32+
*/
33+
protected array $tools = [];
2034

2135
/**
22-
* @var string[]
36+
* The resources registered with this MCP server.
37+
*
38+
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
2339
*/
24-
public array $resources = [
40+
protected array $resources = [
2541
ApplicationInfo::class,
2642
];
2743

44+
/**
45+
* The prompts registered with this MCP server.
46+
*
47+
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
48+
*/
49+
protected array $prompts = [];
50+
2851
public function boot(): void
2952
{
30-
$this->discoverTools();
31-
$this->discoverResources();
32-
$this->discoverPrompts();
53+
collect($this->discoverTools())->each(fn (string $tool) => $this->tools[] = $tool);
54+
collect($this->discoverResources())->each(fn (string $resource) => $this->resources[] = $resource);
55+
collect($this->discoverPrompts())->each(fn (string $prompt) => $this->prompts[] = $prompt);
3356

3457
// Override the tools/call method to use our ToolExecutor
3558
$this->methods['tools/call'] = CallToolWithExecutor::class;
3659
}
3760

3861
/**
39-
* @return array<string>
62+
* @return array<int, class-string<\Laravel\Mcp\Server\Tool>>
4063
*/
4164
protected function discoverTools(): array
4265
{
66+
$tools = [];
67+
4368
$excludedTools = config('boost.mcp.tools.exclude', []);
4469
$toolDir = new \DirectoryIterator(__DIR__.DIRECTORY_SEPARATOR.'Tools');
70+
4571
foreach ($toolDir as $toolFile) {
4672
if ($toolFile->isFile() && $toolFile->getExtension() === 'php') {
4773
$fqdn = 'Laravel\\Boost\\Mcp\\Tools\\'.$toolFile->getBasename('.php');
4874
if (class_exists($fqdn) && ! in_array($fqdn, $excludedTools, true)) {
49-
$this->addTool($fqdn);
75+
$tools[] = $fqdn;
5076
}
5177
}
5278
}
5379

5480
$extraTools = config('boost.mcp.tools.include', []);
5581
foreach ($extraTools as $toolClass) {
5682
if (class_exists($toolClass)) {
57-
$this->addTool($toolClass);
83+
$tools[] = $toolClass;
5884
}
5985
}
6086

61-
return $this->registeredTools;
87+
return $tools;
6288
}
6389

6490
/**
65-
* @return array<string>
91+
* @return array<int, class-string<\Laravel\Mcp\Server\Resource>>
6692
*/
6793
protected function discoverResources(): array
6894
{
95+
$resources = [];
96+
6997
$excludedResources = config('boost.mcp.resources.exclude', []);
7098
$resourceDir = new \DirectoryIterator(__DIR__.DIRECTORY_SEPARATOR.'Resources');
99+
71100
foreach ($resourceDir as $resourceFile) {
72101
if ($resourceFile->isFile() && $resourceFile->getExtension() === 'php') {
73-
$fqdn = 'Laravel\\Boost\\Mcp\\Resources\\'.$resourceFile;
74-
if (class_exists($fqdn) && ! in_array($fqdn, $excludedResources, true)) {
75-
$this->addResource($fqdn);
102+
$fqdn = 'Laravel\\Boost\\Mcp\\Resources\\'.$resourceFile->getBasename('.php');
103+
if (class_exists($fqdn) && ! in_array($fqdn, $excludedResources, true) && $fqdn !== ApplicationInfo::class) {
104+
$resources[] = $fqdn;
76105
}
77106
}
78107
}
79108

80109
$extraResources = config('boost.mcp.resources.include', []);
81110
foreach ($extraResources as $resourceClass) {
82111
if (class_exists($resourceClass)) {
83-
$this->addResource($resourceClass);
112+
$resources[] = $resourceClass;
84113
}
85114
}
86115

87-
return $this->registeredResources;
116+
return $resources;
88117
}
89118

90119
/**
91-
* @return array<string>
120+
* @return array<int, class-string<\Laravel\Mcp\Server\Prompt>>
92121
*/
93122
protected function discoverPrompts(): array
94123
{
124+
$prompts = [];
125+
95126
$excludedPrompts = config('boost.mcp.prompts.exclude', []);
96127
$promptDir = new \DirectoryIterator(__DIR__.DIRECTORY_SEPARATOR.'Prompts');
128+
97129
foreach ($promptDir as $promptFile) {
98130
if ($promptFile->isFile() && $promptFile->getExtension() === 'php') {
99-
$fqdn = 'Laravel\\Boost\\Mcp\\Prompts\\'.$promptFile;
131+
$fqdn = 'Laravel\\Boost\\Mcp\\Prompts\\'.$promptFile->getBasename('.php');
100132
if (class_exists($fqdn) && ! in_array($fqdn, $excludedPrompts, true)) {
101-
$this->addPrompt($fqdn);
133+
$prompts[] = $fqdn;
102134
}
103135
}
104136
}
105137

106138
$extraPrompts = config('boost.mcp.prompts.include', []);
107139
foreach ($extraPrompts as $promptClass) {
108140
if (class_exists($promptClass)) {
109-
$this->addResource($promptClass);
141+
$prompts[] = $promptClass;
110142
}
111143
}
112144

113-
return $this->registeredPrompts;
145+
return $prompts;
114146
}
115147
}

src/Mcp/Methods/CallToolWithExecutor.php

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,54 @@
44

55
namespace Laravel\Boost\Mcp\Methods;
66

7-
use Illuminate\Support\ItemNotFoundException;
87
use Laravel\Boost\Mcp\ToolExecutor;
9-
use Laravel\Mcp\Server\Contracts\Methods\Method;
8+
use Laravel\Mcp\Server\Contracts\Errable;
9+
use Laravel\Mcp\Server\Contracts\Method;
10+
use Laravel\Mcp\Server\Exceptions\JsonRpcException;
11+
use Laravel\Mcp\Server\Methods\Concerns\InteractsWithResponses;
1012
use Laravel\Mcp\Server\ServerContext;
11-
use Laravel\Mcp\Server\Tools\ToolResult;
1213
use Laravel\Mcp\Server\Transport\JsonRpcRequest;
1314
use Laravel\Mcp\Server\Transport\JsonRpcResponse;
14-
use Throwable;
1515

16-
class CallToolWithExecutor implements Method
16+
class CallToolWithExecutor implements Method, Errable
1717
{
18+
use InteractsWithResponses;
19+
1820
/**
1921
* Handle the JSON-RPC tool/call request with process isolation.
20-
*
21-
* @param JsonRpcRequest $request
22-
* @param ServerContext $context
23-
* @return JsonRpcResponse
2422
*/
2523
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
2624
{
27-
try {
28-
$tool = $context->tools()->firstOrFail(fn ($tool) => $tool->name() === $request->params['name']);
29-
} catch (ItemNotFoundException) {
30-
return JsonRpcResponse::create(
31-
$request->id,
32-
ToolResult::error('Tool not found')
33-
);
34-
} catch (Throwable $e) {
35-
return JsonRpcResponse::create(
25+
if (is_null($request->get('name'))) {
26+
throw new JsonRpcException(
27+
'Missing [name] parameter.',
28+
-32602,
3629
$request->id,
37-
ToolResult::error('Error finding tool: '.$e->getMessage())
3830
);
3931
}
4032

41-
try {
42-
$executor = app(ToolExecutor::class);
43-
44-
$arguments = [];
45-
if (isset($request->params['arguments']) && is_array($request->params['arguments'])) {
46-
$arguments = $request->params['arguments'];
47-
}
48-
49-
$result = $executor->execute(get_class($tool), $arguments);
33+
$tool = $context
34+
->tools($request->toRequest())
35+
->first(
36+
fn ($tool): bool => $tool->name() === $request->params['name'],
37+
fn () => throw new JsonRpcException(
38+
"Tool [{$request->params['name']}] not found.",
39+
-32602,
40+
$request->id,
41+
));
42+
43+
$executor = app(ToolExecutor::class);
44+
45+
$arguments = [];
46+
if (isset($request->params['arguments']) && is_array($request->params['arguments'])) {
47+
$arguments = $request->params['arguments'];
48+
}
5049

51-
return JsonRpcResponse::create($request->id, $result);
50+
$response = $executor->execute(get_class($tool), $arguments);
5251

53-
} catch (Throwable $e) {
54-
return JsonRpcResponse::create(
55-
$request->id,
56-
ToolResult::error('Tool execution error: '.$e->getMessage())
57-
);
58-
}
52+
return $this->toJsonRpcResponse($request, $response, fn ($responses) => [
53+
'content' => $responses->map(fn ($response) => $response->content()->toTool($tool))->all(),
54+
'isError' => $responses->contains(fn ($response) => $response->isError()),
55+
]);
5956
}
6057
}

src/Mcp/Resources/ApplicationInfo.php

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use Laravel\Boost\Mcp\ToolExecutor;
88
use Laravel\Boost\Mcp\Tools\ApplicationInfo as ApplicationInfoTool;
9+
use Laravel\Mcp\Request;
10+
use Laravel\Mcp\Response;
911
use Laravel\Mcp\Server\Resource;
1012

1113
class ApplicationInfo extends Resource
@@ -14,35 +16,38 @@ public function __construct(protected ToolExecutor $toolExecutor)
1416
{
1517
}
1618

17-
public function description(): string
19+
/**
20+
* The resource's description.
21+
*/
22+
protected string $description = 'Comprehensive application information including PHP version, Laravel version, database engine, all installed packages with their versions, and all Eloquent models in the application.';
23+
24+
/**
25+
* The resource's URI.
26+
*/
27+
protected string $uri = 'file://instructions/application-info.md';
28+
29+
/**
30+
* The resource's MIME type.
31+
*/
32+
protected string $mimeType = 'text/markdown';
33+
34+
/**
35+
* Handle the resource request.
36+
*/
37+
public function handle(Request $request): Response
1838
{
19-
return 'Comprehensive application information including PHP version, Laravel version, database engine, all installed packages with their versions, and all Eloquent models in the application.';
20-
}
21-
22-
public function uri(): string
23-
{
24-
return 'file://instructions/application-info.md';
25-
}
26-
27-
public function mimeType(): string
28-
{
29-
return 'text/markdown';
30-
}
31-
32-
public function read(): string
33-
{
34-
$result = $this->toolExecutor->execute(ApplicationInfoTool::class);
39+
$response = $this->toolExecutor->execute(ApplicationInfoTool::class);
3540

36-
if ($result->isError) {
37-
return 'Error fetching application information: '.$result->toArray()['content'][0]['text'];
41+
if ($response->isError()) {
42+
return $response; // Return the error response directly
3843
}
3944

40-
$data = json_decode($result->toArray()['content'][0]['text'], true);
45+
$data = json_decode((string) $response->content(), true);
4146

4247
if (! $data) {
43-
return 'Error parsing application information';
48+
return Response::error('Error parsing application information');
4449
}
4550

46-
return json_encode($data, JSON_PRETTY_PRINT);
51+
return Response::json($data);
4752
}
4853
}

0 commit comments

Comments
 (0)