feat(api): add MCP resource listing endpoint

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 13:42:29 +00:00
parent 2d8bb12311
commit 4efa435a47
3 changed files with 59 additions and 0 deletions

View file

@ -110,6 +110,50 @@ class McpApiController extends Controller
]);
}
/**
* List resources for a specific server.
*
* GET /api/v1/mcp/servers/{id}/resources
*
* Query params:
* - include_content: bool - include resource content when the definition already contains it
*/
public function resources(Request $request, string $id): JsonResponse
{
$server = $this->loadServerFull($id);
if (! $server) {
return $this->notFoundResponse('Server');
}
$includeContent = $request->boolean('include_content', false);
$resources = collect($server['resources'] ?? [])
->filter(fn ($resource) => is_array($resource))
->map(function (array $resource) use ($includeContent) {
$payload = array_filter([
'uri' => $resource['uri'] ?? null,
'path' => $resource['path'] ?? null,
'name' => $resource['name'] ?? null,
'description' => $resource['description'] ?? null,
'mime_type' => $resource['mime_type'] ?? ($resource['mimeType'] ?? null),
], static fn ($value) => $value !== null);
if ($includeContent && $this->resourceDefinitionHasContent($resource)) {
$payload['content'] = $this->normaliseResourceContent($resource);
}
return $payload;
})
->values();
return response()->json([
'server' => $id,
'resources' => $resources,
'count' => $resources->count(),
]);
}
/**
* Execute a tool on an MCP server.
*

View file

@ -73,6 +73,8 @@ Route::middleware(['throttle:120,1', McpApiKeyAuth::class, 'api.scope.enforce'])
->name('servers.show');
Route::get('/servers/{id}/tools', [McpApiController::class, 'tools'])
->name('servers.tools');
Route::get('/servers/{id}/resources', [McpApiController::class, 'resources'])
->name('servers.resources');
// Tool version history (read)
Route::get('/servers/{server}/tools/{tool}/versions', [McpApiController::class, 'toolVersions'])

View file

@ -87,3 +87,16 @@ it('reads a resource from the server definition', function () {
]);
});
it('lists resources for a server', function () {
$response = $this->getJson('/api/mcp/servers/test-resource-server/resources', [
'Authorization' => "Bearer {$this->plainKey}",
]);
$response->assertOk();
$response->assertJsonPath('server', 'test-resource-server');
$response->assertJsonPath('count', 1);
$response->assertJsonPath('resources.0.uri', 'test-resource-server://documents/welcome');
$response->assertJsonPath('resources.0.path', 'documents/welcome');
$response->assertJsonPath('resources.0.name', 'welcome');
$response->assertJsonMissingPath('resources.0.content');
});