[Client] add client conformance test suite (#279)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d5b00cd8ae
commit
abae36e888
3 changed files with 189 additions and 9 deletions
108
tests/Conformance/client.php
Normal file
108
tests/Conformance/client.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
|
||||
|
||||
use Mcp\Client;
|
||||
use Mcp\Client\Handler\Request\RequestHandlerInterface;
|
||||
use Mcp\Client\Transport\HttpTransport;
|
||||
use Mcp\Schema\ClientCapabilities;
|
||||
use Mcp\Schema\Enum\ElicitAction;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ElicitRequest;
|
||||
use Mcp\Schema\Result\ElicitResult;
|
||||
use Mcp\Tests\Conformance\FileLogger;
|
||||
|
||||
$url = $argv[1] ?? null;
|
||||
$scenario = getenv('MCP_CONFORMANCE_SCENARIO') ?: null;
|
||||
|
||||
if (!$url || !$scenario) {
|
||||
fwrite(\STDERR, "Usage: MCP_CONFORMANCE_SCENARIO=<scenario> php client.php <server-url>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@mkdir(__DIR__.'/logs', 0777, true);
|
||||
$logger = new FileLogger(__DIR__.'/logs/client-conformance.log', true);
|
||||
$logger->info(sprintf('Starting client conformance test: scenario=%s, url=%s', $scenario, $url));
|
||||
|
||||
$builder = Client::builder()
|
||||
->setClientInfo('mcp-conformance-test-client', '1.0.0')
|
||||
->setInitTimeout(30)
|
||||
->setRequestTimeout(60)
|
||||
->setLogger($logger);
|
||||
|
||||
if ('elicitation-sep1034-client-defaults' === $scenario) {
|
||||
$builder->setCapabilities(new ClientCapabilities(elicitation: true));
|
||||
$builder->addRequestHandler(new class($logger) implements RequestHandlerInterface {
|
||||
public function __construct(private readonly Psr\Log\LoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
public function supports(Request $request): bool
|
||||
{
|
||||
return $request instanceof ElicitRequest;
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$this->logger->info('Received elicitation request, accepting with empty content');
|
||||
|
||||
return new Response($request->getId(), new ElicitResult(ElicitAction::Accept, []));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$client = $builder->build();
|
||||
$transport = new HttpTransport($url, logger: $logger);
|
||||
|
||||
try {
|
||||
$client->connect($transport);
|
||||
$logger->info('Connected to server');
|
||||
|
||||
$toolsResult = $client->listTools();
|
||||
$logger->info(sprintf('Listed %d tools', count($toolsResult->tools)));
|
||||
|
||||
switch ($scenario) {
|
||||
case 'initialize':
|
||||
break;
|
||||
|
||||
case 'tools_call':
|
||||
$toolName = $toolsResult->tools[0]->name ?? 'test-tool';
|
||||
$client->callTool($toolName, []);
|
||||
$logger->info(sprintf('Called tool: %s', $toolName));
|
||||
break;
|
||||
|
||||
case 'elicitation-sep1034-client-defaults':
|
||||
$toolName = $toolsResult->tools[0]->name ?? 'test_client_elicitation_defaults';
|
||||
$client->callTool($toolName, []);
|
||||
$logger->info(sprintf('Called tool: %s', $toolName));
|
||||
break;
|
||||
|
||||
default:
|
||||
$logger->warning(sprintf('Unknown scenario: %s', $scenario));
|
||||
break;
|
||||
}
|
||||
|
||||
$client->disconnect();
|
||||
$logger->info('Disconnected');
|
||||
exit(0);
|
||||
} catch (Throwable $e) {
|
||||
$logger->error(sprintf('Error: %s', $e->getMessage()));
|
||||
fwrite(\STDERR, sprintf("Error: %s\n%s\n", $e->getMessage(), $e->getTraceAsString()));
|
||||
|
||||
try {
|
||||
$client->disconnect();
|
||||
} catch (Throwable $ignored) {
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -1,3 +1,28 @@
|
|||
server:
|
||||
- dns-rebinding-protection
|
||||
|
||||
client:
|
||||
- elicitation-sep1034-client-defaults
|
||||
- sse-retry
|
||||
- auth/metadata-default
|
||||
- auth/metadata-var1
|
||||
- auth/metadata-var2
|
||||
- auth/metadata-var3
|
||||
- auth/basic-cimd
|
||||
- auth/scope-from-www-authenticate
|
||||
- auth/scope-from-scopes-supported
|
||||
- auth/scope-omitted-when-undefined
|
||||
- auth/scope-step-up
|
||||
- auth/scope-retry-limit
|
||||
- auth/token-endpoint-auth-basic
|
||||
- auth/token-endpoint-auth-post
|
||||
- auth/token-endpoint-auth-none
|
||||
- auth/pre-registration
|
||||
- auth/2025-03-26-oauth-metadata-backcompat
|
||||
- auth/2025-03-26-oauth-endpoint-fallback
|
||||
- auth/offline-access-scope
|
||||
- auth/offline-access-not-supported
|
||||
- auth/client-credentials-jwt
|
||||
- auth/client-credentials-basic
|
||||
- auth/cross-app-access-complete-flow
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ class InitializeHandlerTest extends TestCase
|
|||
$session = $this->createMock(SessionInterface::class);
|
||||
$session->expects($this->exactly(2))
|
||||
->method('set')
|
||||
->willReturnCallback(function (string $key, array $value): void {
|
||||
->willReturnCallback(function (string $key, mixed $value): void {
|
||||
match ($key) {
|
||||
'client_info' => $this->assertSame(['name' => 'client-app', 'version' => '1.0.0'], $value),
|
||||
'client_capabilities' => $this->assertSame([], $value),
|
||||
'client_capabilities' => $this->assertEquals(new \stdClass(), $value),
|
||||
default => $this->fail("Unexpected session key: {$key}"),
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue