container = Mockery::mock(Container::class); $this->toolRegistry = Mockery::mock(McpToolRegistry::class); $this->resourceRegistry = Mockery::mock(McpResourceRegistry::class); $this->mcpServer = new McpServer( $this->container, $this->toolRegistry, $this->resourceRegistry ); }); afterEach(function () { Mockery::close(); }); test('initialize returns correct protocol response', function () { $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'initialize', 'params' => [], 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('jsonrpc', '2.0') ->and($decoded)->toHaveKey('id', 1) ->and($decoded['result'])->toHaveKey('protocolVersion') ->and($decoded['result'])->toHaveKey('capabilities') ->and($decoded['result'])->toHaveKey('serverInfo') ->and($decoded['result']['serverInfo']['name'])->toBe('Custom PHP Framework MCP Server'); }); test('tools list returns registered tools', function () { $mockTools = [ [ 'name' => 'test_tool', 'description' => 'A test tool', 'parameters' => [], 'inputSchema' => null, ], ]; $this->toolRegistry ->shouldReceive('getAllTools') ->andReturn($mockTools); $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'tools/list', 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('result') ->and($decoded['result'])->toHaveKey('tools') ->and($decoded['result']['tools'])->toHaveCount(1) ->and($decoded['result']['tools'][0]['name'])->toBe('test_tool'); }); test('resources list returns registered resources', function () { $mockResources = [ [ 'uri' => 'framework://test', 'name' => 'Test Resource', 'description' => 'A test resource', 'mimeType' => 'application/json', ], ]; $this->resourceRegistry ->shouldReceive('getAllResources') ->andReturn($mockResources); $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'resources/list', 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('result') ->and($decoded['result'])->toHaveKey('resources') ->and($decoded['result']['resources'])->toHaveCount(1) ->and($decoded['result']['resources'][0]['uri'])->toBe('framework://test'); }); test('invalid json request returns error', function () { $invalidJson = '{"invalid": json}'; $response = $this->mcpServer->handleRequest($invalidJson); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('error') ->and($decoded['error']['message'])->toContain('Invalid JSON'); }); test('missing method returns error', function () { $request = json_encode([ 'jsonrpc' => '2.0', 'id' => 1, // Missing 'method' field ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('error') ->and($decoded['error']['message'])->toContain('method missing'); }); test('unknown method returns error', function () { $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'unknown/method', 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('error') ->and($decoded['error']['message'])->toContain('Unknown method'); }); test('tool call executes registered tool', function () { $mockTool = [ 'name' => 'test_tool', 'class' => 'TestToolClass', 'method' => 'execute', 'parameters' => [], ]; $mockToolInstance = new class () { public function execute(array $params): array { return ['result' => 'success', 'data' => $params]; } }; $this->toolRegistry ->shouldReceive('getTool') ->with('test_tool') ->andReturn($mockTool); $this->container ->shouldReceive('get') ->with('TestToolClass') ->andReturn($mockToolInstance); $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'tools/call', 'params' => [ 'name' => 'test_tool', 'arguments' => ['param1' => 'value1'], ], 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('result') ->and($decoded['result']['content'])->toHaveCount(1) ->and($decoded['result']['content'][0]['type'])->toBe('text'); }); test('resource read returns resource content', function () { $mockResource = [ 'uri' => 'framework://test', 'class' => 'TestResourceClass', 'method' => 'read', ]; $mockResourceInstance = new class () { public function read(string $uri): array { return ['content' => 'test content', 'mimeType' => 'text/plain']; } }; $this->resourceRegistry ->shouldReceive('getResource') ->with('framework://test') ->andReturn($mockResource); $this->container ->shouldReceive('get') ->with('TestResourceClass') ->andReturn($mockResourceInstance); $request = json_encode([ 'jsonrpc' => '2.0', 'method' => 'resources/read', 'params' => [ 'uri' => 'framework://test', ], 'id' => 1, ]); $response = $this->mcpServer->handleRequest($request); $decoded = json_decode($response, true); expect($decoded)->toHaveKey('result') ->and($decoded['result']['contents'])->toHaveCount(1) ->and($decoded['result']['contents'][0]['uri'])->toBe('framework://test'); });