feat(mcp): add server resource listing

This commit is contained in:
Virgil 2026-04-02 16:47:03 +00:00
parent 4ab909f391
commit 6b78f0c137
5 changed files with 130 additions and 1 deletions

View file

@ -226,7 +226,7 @@ The `McpApiController` exposes five endpoints behind `mcp.auth` middleware:
| `GET` | `/servers/{id}.json` | Server details with tool definitions |
| `GET` | `/servers/{id}/tools` | List tools for a server |
| `POST` | `/tools/call` | Execute a tool |
| `GET` | `/resources/{uri}` | Read a resource (not yet implemented -- returns 501) |
| `GET` | `/resources/{uri}` | Read a resource |
`POST /tools/call` accepts:

View file

@ -113,6 +113,8 @@ class Boot extends ServiceProvider
->where('id', '[a-z0-9-]+');
Route::get('servers/{id}/tools', [Controllers\McpApiController::class, 'tools'])->name('servers.tools')
->where('id', '[a-z0-9-]+');
Route::get('servers/{id}/resources', [Controllers\McpApiController::class, 'resources'])->name('servers.resources')
->where('id', '[a-z0-9-]+');
})
);
}

View file

@ -82,6 +82,26 @@ class McpApiController extends Controller
]);
}
/**
* List resources for a specific server.
*
* GET /api/v1/mcp/servers/{id}/resources
*/
public function resources(Request $request, string $id): JsonResponse
{
$server = $this->loadServerFull($id);
if (! $server) {
return response()->json(['error' => 'Server not found'], 404);
}
return response()->json([
'server' => $id,
'resources' => array_values($server['resources'] ?? []),
'count' => count($server['resources'] ?? []),
]);
}
/**
* Execute a tool on an MCP server.
*

View file

@ -197,6 +197,35 @@ class OpenApiGenerator
],
];
$paths['/servers/{serverId}/resources'] = [
'get' => [
'tags' => ['Discovery'],
'summary' => 'List resources for a server',
'operationId' => 'listServerResources',
'security' => [['bearerAuth' => []], ['apiKeyAuth' => []]],
'parameters' => [
[
'name' => 'serverId',
'in' => 'path',
'required' => true,
'schema' => ['type' => 'string'],
],
],
'responses' => [
'200' => [
'description' => 'List of resources',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/ResourceList',
],
],
],
],
],
],
];
// Execution endpoint
$paths['/tools/call'] = [
'post' => [
@ -402,6 +431,17 @@ class OpenApiGenerator
],
],
],
'ResourceList' => [
'type' => 'object',
'properties' => [
'server' => ['type' => 'string'],
'resources' => [
'type' => 'array',
'items' => ['$ref' => '#/components/schemas/Resource'],
],
'count' => ['type' => 'integer'],
],
],
];
return $schemas;

View file

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Core\Mcp\Tests\Unit;
use Core\Mcp\Controllers\McpApiController;
use Core\Mcp\Services\OpenApiGenerator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Tests\TestCase;
class McpResourceListTest extends TestCase
{
public function test_resources_endpoint_returns_server_resources(): void
{
$controller = new class extends McpApiController {
protected function loadServerFull(string $id): ?array
{
if ($id !== 'demo-server') {
return null;
}
return [
'id' => 'demo-server',
'resources' => [
[
'uri' => 'content://workspace/article',
'name' => 'Article',
'description' => 'Published article',
'mimeType' => 'text/markdown',
],
[
'uri' => 'plans://all',
'name' => 'Plans',
'description' => 'Work plan index',
'mimeType' => 'text/markdown',
],
],
];
}
};
$response = $controller->resources(Request::create('/api/v1/mcp/servers/demo-server/resources', 'GET'), 'demo-server');
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertSame(200, $response->getStatusCode());
$data = $response->getData(true);
$this->assertSame('demo-server', $data['server']);
$this->assertSame(2, $data['count']);
$this->assertSame('content://workspace/article', $data['resources'][0]['uri']);
$this->assertSame('plans://all', $data['resources'][1]['uri']);
}
public function test_openapi_includes_resource_list_endpoint(): void
{
$schema = (new OpenApiGenerator)->generate();
$this->assertArrayHasKey('/servers/{serverId}/resources', $schema['paths']);
$this->assertArrayHasKey('ResourceList', $schema['components']['schemas']);
$this->assertSame(
'#/components/schemas/ResourceList',
$schema['paths']['/servers/{serverId}/resources']['get']['responses']['200']['content']['application/json']['schema']['$ref']
);
}
}