Skip to content

Commit f35604b

Browse files
committed
add basic resource support
1 parent ae93538 commit f35604b

File tree

8 files changed

+232
-0
lines changed

8 files changed

+232
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Best practices for Laravel development
2+
3+
Always keep these instructions in mind when working on a Laravel project:
4+
5+
- Always use Laravel's `artisan make` command to create new files, whenever possible. Use `artisan make:class` for generic classes.

src/Mcp/AiAssistant.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
namespace Laravel\AiAssistant\Mcp;
44

5+
use Laravel\AiAssistant\Mcp\Resources\LaravelBestPractices;
6+
use Laravel\AiAssistant\Mcp\Resources\ListResources;
7+
use Laravel\AiAssistant\Mcp\Resources\ReadResource;
8+
use Laravel\AiAssistant\Mcp\Tools\LaravelBestPractices as LaravelBestPracticesTool;
59
use Laravel\AiAssistant\Mcp\Tools\LogReader;
610
use Laravel\Mcp\Server;
711

@@ -26,6 +30,25 @@ class AiAssistant extends Server
2630
* The available tools.
2731
*/
2832
public array $tools = [
33+
LaravelBestPracticesTool::class,
2934
LogReader::class,
3035
];
36+
37+
/**
38+
* The available resources.
39+
*/
40+
public array $resources = [
41+
LaravelBestPractices::class,
42+
];
43+
44+
public function boot()
45+
{
46+
$this->addMethod('resources/list', ListResources::class);
47+
$this->resolveMethodUsing(ListResources::class, fn () => new ListResources($this->resources));
48+
49+
$this->addMethod('resources/read', ReadResource::class);
50+
$this->resolveMethodUsing(ReadResource::class, fn () => new ReadResource($this->resources));
51+
52+
$this->addCapability('resources');
53+
}
3154
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Resources;
4+
5+
class LaravelBestPractices extends Resource
6+
{
7+
public function name(): string
8+
{
9+
return 'Laravel Best Practices';
10+
}
11+
12+
public function description(): string
13+
{
14+
return 'Always include these instructions when writing Laravel code.';
15+
}
16+
17+
public function uri(): string
18+
{
19+
return 'file://instructions/laravel-best-practices.md';
20+
}
21+
22+
public function mimeType(): string
23+
{
24+
return 'text/markdown';
25+
}
26+
27+
public function read(): string
28+
{
29+
return file_get_contents(__DIR__ . '/../../../resources/mcp_resources/laravel-best-practices.md');
30+
}
31+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Resources;
4+
5+
use Laravel\Mcp\Contracts\Methods\Method;
6+
use Laravel\Mcp\Pagination\CursorPaginator;
7+
use Laravel\Mcp\ServerContext;
8+
use Laravel\Mcp\Transport\JsonRpcRequest;
9+
use Laravel\Mcp\Transport\JsonRpcResponse;
10+
11+
class ListResources implements Method
12+
{
13+
public function __construct(
14+
protected array $resources,
15+
) {}
16+
17+
/**
18+
* Handle the JSON-RPC resource/list request.
19+
*/
20+
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
21+
{
22+
$encodedCursor = $request->params['cursor'] ?? null;
23+
$requestedPerPage = $request->params['per_page'] ?? $context->defaultPaginationLength;
24+
$maxPerPage = $context->maxPaginationLength;
25+
26+
$perPage = min($requestedPerPage, $maxPerPage);
27+
28+
$resources = collect($this->resources)->values()
29+
->map(fn ($resourceClass) => is_string($resourceClass) ? new $resourceClass : $resourceClass)
30+
->map(function ($resource, $index) {
31+
return [
32+
'id' => $index + 1,
33+
'uri' => $resource->uri(),
34+
'name' => $resource->name(),
35+
'description' => $resource->description(),
36+
'mimeType' => $resource->mimeType(),
37+
];
38+
})
39+
->sortBy('id')
40+
->values();
41+
42+
$paginator = new CursorPaginator($resources, $perPage, $encodedCursor);
43+
$paginationResult = $paginator->paginate();
44+
45+
$response = [
46+
'resources' => $paginationResult['items']->toArray(),
47+
];
48+
49+
if (! is_null($paginationResult['nextCursor'])) {
50+
$response['nextCursor'] = $paginationResult['nextCursor'];
51+
}
52+
53+
return JsonRpcResponse::create($request->id, $response);
54+
}
55+
}

src/Mcp/Resources/ReadResource.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Resources;
4+
5+
use Laravel\Mcp\Contracts\Methods\Method;
6+
use Laravel\Mcp\ServerContext;
7+
use Laravel\Mcp\Transport\JsonRpcRequest;
8+
use Laravel\Mcp\Transport\JsonRpcResponse;
9+
10+
class ReadResource implements Method
11+
{
12+
public function __construct(
13+
protected array $resources,
14+
) {}
15+
16+
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
17+
{
18+
$resource = collect($this->resources)->map(fn ($resource) => is_string($resource) ? new $resource : $resource)->firstOrFail(fn ($resource) => $resource->uri() === $request->params['uri']);
19+
20+
return new JsonRpcResponse(
21+
$request->id,
22+
[
23+
'contents' => [[
24+
'uri' => $resource->uri(),
25+
'name' => $resource->name(),
26+
'description' => $resource->description(),
27+
'mimeType' => $resource->mimeType(),
28+
'text' => $resource->read(),
29+
]],
30+
],
31+
);
32+
}
33+
}

src/Mcp/Resources/Resource.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Resources;
4+
5+
abstract class Resource
6+
{
7+
abstract public function name(): string;
8+
9+
abstract public function description(): string;
10+
11+
abstract public function uri(): string;
12+
13+
abstract public function mimeType(): string;
14+
15+
abstract public function read(): string;
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Tools;
4+
5+
use Laravel\AiAssistant\Mcp\Resources\LaravelBestPractices as LaravelBestPracticesResource;
6+
use Laravel\AiAssistant\Mcp\Tools\ToolResourceResponse;
7+
use Laravel\Mcp\Tools\ToolInputSchema;
8+
use Laravel\Mcp\Tools\Tool;
9+
10+
class LaravelBestPractices extends Tool
11+
{
12+
public function name(): string
13+
{
14+
return 'Laravel Best Practices';
15+
}
16+
17+
public function description(): string
18+
{
19+
return 'Always include these instructions when writing Laravel code.';
20+
}
21+
22+
public function schema(ToolInputSchema $schema): ToolInputSchema
23+
{
24+
return $schema;
25+
}
26+
27+
public function handle(array $arguments): ToolResourceResponse
28+
{
29+
return new ToolResourceResponse(new LaravelBestPracticesResource());
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Laravel\AiAssistant\Mcp\Tools;
4+
5+
use Laravel\AiAssistant\Mcp\Resources\Resource;
6+
use Laravel\Mcp\Tools\ToolResponse;
7+
8+
class ToolResourceResponse extends ToolResponse
9+
{
10+
/**
11+
* Create a new tool resource response.
12+
*/
13+
public function __construct(private Resource $resource)
14+
{
15+
}
16+
17+
/**
18+
* Convert the response to an array.
19+
*/
20+
public function toArray(): array
21+
{
22+
return [
23+
'contents' => [
24+
[
25+
'type' => 'resource',
26+
'resource' => [
27+
'uri' => $this->resource->uri(),
28+
'name' => $this->resource->name(),
29+
'description' => $this->resource->description(),
30+
'mimeType' => $this->resource->mimeType(),
31+
'text' => $this->resource->read(),
32+
],
33+
],
34+
],
35+
'isError' => false,
36+
];
37+
}
38+
}

0 commit comments

Comments
 (0)