diff --git a/docs/architecture.md b/docs/architecture.md index f784c80..aeccd3f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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: diff --git a/src/php/src/Mcp/Boot.php b/src/php/src/Mcp/Boot.php index 68631a5..1cf546c 100644 --- a/src/php/src/Mcp/Boot.php +++ b/src/php/src/Mcp/Boot.php @@ -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-]+'); }) ); } diff --git a/src/php/src/Mcp/Controllers/McpApiController.php b/src/php/src/Mcp/Controllers/McpApiController.php index 1df214b..e05fed9 100644 --- a/src/php/src/Mcp/Controllers/McpApiController.php +++ b/src/php/src/Mcp/Controllers/McpApiController.php @@ -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. * diff --git a/src/php/src/Mcp/Services/OpenApiGenerator.php b/src/php/src/Mcp/Services/OpenApiGenerator.php index e2b1379..cd6da93 100644 --- a/src/php/src/Mcp/Services/OpenApiGenerator.php +++ b/src/php/src/Mcp/Services/OpenApiGenerator.php @@ -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; diff --git a/src/php/tests/Unit/McpResourceListTest.php b/src/php/tests/Unit/McpResourceListTest.php new file mode 100644 index 0000000..834848d --- /dev/null +++ b/src/php/tests/Unit/McpResourceListTest.php @@ -0,0 +1,67 @@ + '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'] + ); + } +}