|
15 | 15 | use Mcp\Event\RequestEvent; |
16 | 16 | use Mcp\Event\ResponseEvent; |
17 | 17 | use Mcp\Exception\MissingRequiredClientCapabilityException; |
| 18 | +use Mcp\JsonRpc\MessageFactory; |
18 | 19 | use Mcp\Schema\ClientCapabilities; |
19 | 20 | use Mcp\Schema\Content\TextResourceContents; |
20 | 21 | use Mcp\Schema\Elicitation\ElicitationSchema; |
21 | 22 | use Mcp\Schema\Elicitation\StringSchemaDefinition; |
22 | 23 | use Mcp\Schema\Enum\CacheScope; |
23 | 24 | use Mcp\Schema\Enum\LoggingLevel; |
24 | 25 | use Mcp\Schema\Enum\ProtocolVersion; |
| 26 | +use Mcp\Schema\Implementation; |
25 | 27 | use Mcp\Schema\JsonRpc\Error; |
26 | 28 | use Mcp\Schema\JsonRpc\Request; |
27 | 29 | use Mcp\Schema\JsonRpc\Response; |
|
38 | 40 | use Mcp\Schema\ServerCapabilities; |
39 | 41 | use Mcp\Server; |
40 | 42 | use Mcp\Server\Authorization\AccessToken; |
| 43 | +use Mcp\Server\Configuration; |
41 | 44 | use Mcp\Server\Handler\Request\RequestHandlerInterface; |
42 | 45 | use Mcp\Server\RequestContext; |
43 | 46 | use Mcp\Server\Session\SessionInterface; |
|
49 | 52 | use Mcp\Server\Subscription\InMemoryNotificationBus; |
50 | 53 | use Mcp\Server\Suspension\NotificationSuspension; |
51 | 54 | use Mcp\Server\Wire\CachePolicy; |
| 55 | +use Mcp\Tests\Unit\Fixtures\ThrowingRequest; |
52 | 56 | use Mcp\Tests\Unit\Server\Extension\ThingExtension; |
53 | 57 | use Mcp\Tests\Unit\Server\Extension\UnservedThingExtension; |
54 | 58 | use PHPUnit\Framework\Attributes\DataProvider; |
@@ -319,6 +323,31 @@ public function testRemovedMethodsAreUnknown(string $method, array $params): voi |
319 | 323 | $this->assertSame(Error::METHOD_NOT_FOUND, $answer['body']['error']['code']); |
320 | 324 | } |
321 | 325 |
|
| 326 | + #[TestDox('a known method with malformed params is an invalid request, not an unknown method')] |
| 327 | + public function testMalformedRequestIsNotAnUnknownMethod(): void |
| 328 | + { |
| 329 | + $answer = self::call(self::protocol(), 'tools/list', ['cursor' => [1]]); |
| 330 | + |
| 331 | + $this->assertSame(400, $answer['status']); |
| 332 | + $this->assertSame(Error::INVALID_REQUEST, $answer['body']['error']['code']); |
| 333 | + } |
| 334 | + |
| 335 | + #[TestDox('a request class that crashes while hydrating is answered without leaking the failure')] |
| 336 | + public function testCrashWhileHydratingIsAnswered(): void |
| 337 | + { |
| 338 | + $protocol = new StatelessProtocol( |
| 339 | + [], |
| 340 | + new MessageFactory([ThrowingRequest::class]), |
| 341 | + new Configuration(new Implementation('test-server', '1.0.0'), new ServerCapabilities()), |
| 342 | + ); |
| 343 | + |
| 344 | + $answer = self::call($protocol, 'test/throwing'); |
| 345 | + |
| 346 | + $this->assertSame(404, $answer['status']); |
| 347 | + $this->assertSame(Error::METHOD_NOT_FOUND, $answer['body']['error']['code']); |
| 348 | + $this->assertStringNotContainsString('must not leak', json_encode($answer['body'], \JSON_THROW_ON_ERROR)); |
| 349 | + } |
| 350 | + |
322 | 351 | #[TestDox('a handshake-era client opening with "initialize" is told which revisions are served')] |
323 | 352 | public function testInitializeNamesTheServedRevisions(): void |
324 | 353 | { |
|
0 commit comments