[Server] Feat: Add comprehensive HTTP inspector tests with improved framework (#125)

* feat: add comprehensive HTTP inspector tests and improve test framework

* chore: remove debug flag from server configuration
This commit is contained in:
Kyrian Obikwelu 2025-10-30 17:47:53 +01:00 • committed by GitHub
commit 757b959bfd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
100 changed files with 1877 additions and 189 deletions

View file

@ -0,0 +1,64 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
final class HttpClientCommunicationTest extends HttpInspectorSnapshotTestCase
{
protected function setUp(): void
{
$this->markTestSkipped('Test skipped: SDK cannot handle logging/setLevel requests required by logging capability, and built-in PHP server does not support sampling.');
}
public static function provideMethods(): array
{
return [
...parent::provideMethods(),
'Prepare Project Briefing (Simple)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'prepare_project_briefing',
'toolArgs' => [
'projectName' => 'Website Redesign',
'milestones' => ['Discovery', 'Design', 'Development', 'Testing'],
],
],
'testName' => 'prepare_project_briefing_simple',
],
'Prepare Project Briefing (Complex)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'prepare_project_briefing',
'toolArgs' => [
'projectName' => 'Mobile App Launch',
'milestones' => ['Market Research', 'UI/UX Design', 'MVP Development', 'Beta Testing', 'Marketing Campaign', 'Public Launch'],
],
],
'testName' => 'prepare_project_briefing_complex',
],
'Run Service Maintenance' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'run_service_maintenance',
'toolArgs' => [
'serviceName' => 'Payment Gateway API',
],
],
'testName' => 'run_service_maintenance',
],
];
}
protected function getServerScript(): string
{
return \dirname(__DIR__, 3).'/examples/http-client-communication/server.php';
}
}

View file

@ -0,0 +1,50 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
final class HttpCombinedRegistrationTest extends HttpInspectorSnapshotTestCase
{
public static function provideMethods(): array
{
return [
...parent::provideMethods(),
'Manual Greeter Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'manualGreeter',
'toolArgs' => ['user' => 'HTTP Test User'],
],
'testName' => 'manual_greeter',
],
'Discovered Status Check Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'discovered_status_check',
'toolArgs' => [],
],
'testName' => 'discovered_status_check',
],
'Read Priority Config (Manual Override)' => [
'method' => 'resources/read',
'options' => [
'uri' => 'config://priority',
],
'testName' => 'config_priority',
],
];
}
protected function getServerScript(): string
{
return \dirname(__DIR__, 3).'/examples/http-combined-registration/server.php';
}
}

View file

@ -0,0 +1,87 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
final class HttpComplexToolSchemaTest extends HttpInspectorSnapshotTestCase
{
public static function provideMethods(): array
{
return [
...parent::provideMethods(),
'Schedule Event (Meeting with Time)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'schedule_event',
'toolArgs' => [
'title' => 'Team Standup',
'date' => '2024-12-01',
'type' => 'meeting',
'time' => '09:00',
'priority' => 'normal',
'attendees' => ['alice@example.com', 'bob@example.com'],
'sendInvites' => true,
],
],
'testName' => 'schedule_event_meeting_with_time',
],
'Schedule Event (All Day Reminder)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'schedule_event',
'toolArgs' => [
'title' => 'Project Deadline',
'date' => '2024-12-15',
'type' => 'reminder',
'priority' => 'high',
],
],
'testName' => 'schedule_event_all_day_reminder',
],
'Schedule Event (Call with High Priority)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'schedule_event',
'toolArgs' => [
'title' => 'Client Call',
'date' => '2024-12-02',
'type' => 'call',
'time' => '14:30',
'priority' => 'high',
'attendees' => ['client@example.com'],
'sendInvites' => false,
],
],
'testName' => 'schedule_event_high_priority',
],
'Schedule Event (Other Event with Low Priority)' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'schedule_event',
'toolArgs' => [
'title' => 'Office Party',
'date' => '2024-12-20',
'type' => 'other',
'time' => '18:00',
'priority' => 'low',
'attendees' => ['team@company.com'],
],
],
'testName' => 'schedule_event_low_priority',
],
];
}
protected function getServerScript(): string
{
return \dirname(__DIR__, 3).'/examples/http-complex-tool-schema/server.php';
}
}

View file

@ -0,0 +1,72 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
final class HttpDiscoveryUserProfileTest extends HttpInspectorSnapshotTestCase
{
public static function provideMethods(): array
{
return [
...parent::provideMethods(),
'Send Welcome Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'send_welcome',
'toolArgs' => ['userId' => '101', 'customMessage' => 'Welcome to our platform!'],
],
'testName' => 'send_welcome',
],
'Test Tool Without Params' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'test_tool_without_params',
'toolArgs' => [],
],
'testName' => 'test_tool_without_params',
],
'Read User Profile 101' => [
'method' => 'resources/read',
'options' => [
'uri' => 'user://101/profile',
],
'testName' => 'read_user_profile_101',
],
'Read User Profile 102' => [
'method' => 'resources/read',
'options' => [
'uri' => 'user://102/profile',
],
'testName' => 'read_user_profile_102',
],
'Read User ID List' => [
'method' => 'resources/read',
'options' => [
'uri' => 'user://list/ids',
],
'testName' => 'read_user_id_list',
],
'Generate Bio Prompt (Formal)' => [
'method' => 'prompts/get',
'options' => [
'promptName' => 'generate_bio_prompt',
'promptArgs' => ['userId' => '101', 'tone' => 'formal'],
],
'testName' => 'generate_bio_prompt',
],
];
}
protected function getServerScript(): string
{
return \dirname(__DIR__, 3).'/examples/http-discovery-userprofile/server.php';
}
}

View file

@ -0,0 +1,102 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
use Mcp\Tests\Inspector\InspectorSnapshotTestCase;
use Symfony\Component\Process\Process;
abstract class HttpInspectorSnapshotTestCase extends InspectorSnapshotTestCase
{
private Process $serverProcess;
private int $serverPort;
protected function setUp(): void
{
parent::setUp();
$this->startServer();
}
protected function tearDown(): void
{
$this->stopServer();
parent::tearDown();
}
abstract protected function getServerScript(): string;
protected function getServerConnectionArgs(): array
{
return [\sprintf('http://127.0.0.1:%d', $this->serverPort)];
}
protected function getTransport(): string
{
return 'http';
}
private function startServer(): void
{
$this->serverPort = 8000 + (getmypid() % 1000);
$this->serverProcess = new Process([
'php',
'-S',
\sprintf('127.0.0.1:%d', $this->serverPort),
$this->getServerScript(),
]);
$this->serverProcess->start();
$timeout = 5; // seconds
$startTime = time();
while (time() - $startTime < $timeout) {
if ($this->serverProcess->isRunning() && $this->isServerReady()) {
return;
}
usleep(100000); // 100ms
}
$this->fail(\sprintf('Server failed to start on port %d within %d seconds', $this->serverPort, $timeout));
}
private function stopServer(): void
{
if (isset($this->serverProcess)) {
$this->serverProcess->stop(1, \SIGTERM);
}
}
private function isServerReady(): bool
{
$context = stream_context_create([
'http' => [
'timeout' => 1,
'method' => 'GET',
],
]);
// Try a simple health check - this will likely fail with MCP but should respond
$response = @file_get_contents(\sprintf('http://127.0.0.1:%d', $this->serverPort), false, $context);
// We don't care about the response content, just that the server is accepting connections
return false !== $response || false === str_contains(error_get_last()['message'] ?? '', 'Connection refused');
}
protected function getSnapshotFilePath(string $method, ?string $testName = null): string
{
$className = substr(static::class, strrpos(static::class, '\\') + 1);
$suffix = $testName ? '-'.preg_replace('/[^a-zA-Z0-9_]/', '_', $testName) : '';
return __DIR__.'/snapshots/'.$className.'-'.str_replace('/', '_', $method).$suffix.'.json';
}
}

View file

@ -0,0 +1,116 @@
<?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.
*/
namespace Mcp\Tests\Inspector\Http;
final class HttpSchemaShowcaseTest extends HttpInspectorSnapshotTestCase
{
public static function provideMethods(): array
{
return [
...parent::provideMethods(),
'Format Text Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'format_text',
'toolArgs' => ['text' => 'Hello World Test', 'format' => 'uppercase'],
],
'testName' => 'format_text',
],
'Calculate Range Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'calculate_range',
'toolArgs' => ['first' => 10, 'second' => 5, 'operation' => 'multiply', 'precision' => 2],
],
'testName' => 'calculate_range',
],
'Validate Profile Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'validate_profile',
'toolArgs' => [
'profile' => ['name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, 'role' => 'user'],
],
],
'testName' => 'validate_profile',
],
'Manage List Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'manage_list',
'toolArgs' => [
'items' => ['apple', 'banana', 'cherry', 'date'],
'action' => 'sort',
],
],
'testName' => 'manage_list',
],
'Generate Config Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'generate_config',
'toolArgs' => [
'appName' => 'TestApp',
'baseUrl' => 'https://example.com',
'environment' => 'development',
'debug' => true,
'port' => 8080,
],
],
'testName' => 'generate_config',
],
'Schedule Event Tool' => [
'method' => 'tools/call',
'options' => [
'toolName' => 'schedule_event',
'toolArgs' => [
'title' => 'Team Meeting',
'startTime' => '2024-12-01T14:30:00Z',
'durationHours' => 1.5,
'priority' => 'high',
'attendees' => ['alice@example.com', 'bob@example.com'],
],
],
'testName' => 'schedule_event',
],
];
}
protected function getServerScript(): string
{
return \dirname(__DIR__, 3).'/examples/http-schema-showcase/server.php';
}
protected function normalizeTestOutput(string $output, ?string $testName = null): string
{
return match ($testName) {
'validate_profile' => preg_replace(
'/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/',
'2025-01-01 00:00:00',
$output
),
'generate_config' => preg_replace(
'/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}/',
'2025-01-01T00:00:00+00:00',
$output
),
'schedule_event' => preg_replace([
'/event_[a-f0-9]{13,}/',
'/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}/',
], [
'event_test123456789',
'2025-01-01T00:00:00+00:00',
], $output),
default => $output,
};
}
}

View file

@ -0,0 +1,3 @@
{
"prompts": []
}

View file

@ -0,0 +1,9 @@
{
"resources": [
{
"name": "priority_config_discovered",
"uri": "config://priority",
"description": "A resource discovered via attributes."
}
]
}

View file

@ -0,0 +1,9 @@
{
"contents": [
{
"uri": "config://priority",
"mimeType": "text/plain",
"text": "Discovered Priority Config: Low"
}
]
}

View file

@ -0,0 +1,3 @@
{
"resourceTemplates": []
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "System status: OK (discovered)"
}
],
"isError": false
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "Hello HTTP Test User, from manual registration!"
}
],
"isError": false
}

View file

@ -0,0 +1,28 @@
{
"tools": [
{
"name": "manualGreeter",
"description": "A manually registered tool.",
"inputSchema": {
"type": "object",
"properties": {
"user": {
"type": "string",
"description": "the user to greet"
}
},
"required": [
"user"
]
}
},
{
"name": "discovered_status_check",
"description": "A tool discovered via attributes.",
"inputSchema": {
"type": "object",
"properties": {}
}
}
]
}

View file

@ -0,0 +1,3 @@
{
"prompts": []
}

View file

@ -0,0 +1,3 @@
{
"resources": []
}

View file

@ -0,0 +1,3 @@
{
"resourceTemplates": []
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"message\": \"Event \\\"Project Deadline\\\" scheduled successfully for \\\"2024-12-15\\\".\",\n \"event_details\": {\n \"title\": \"Project Deadline\",\n \"date\": \"2024-12-15\",\n \"type\": \"reminder\",\n \"time\": \"All day\",\n \"priority\": \"Normal\",\n \"attendees\": [],\n \"invites_will_be_sent\": false\n }\n}"
}
],
"isError": false
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"message\": \"Event \\\"Client Call\\\" scheduled successfully for \\\"2024-12-02\\\".\",\n \"event_details\": {\n \"title\": \"Client Call\",\n \"date\": \"2024-12-02\",\n \"type\": \"call\",\n \"time\": \"14:30\",\n \"priority\": \"Normal\",\n \"attendees\": [\n \"client@example.com\"\n ],\n \"invites_will_be_sent\": false\n }\n}"
}
],
"isError": false
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"message\": \"Event \\\"Office Party\\\" scheduled successfully for \\\"2024-12-20\\\".\",\n \"event_details\": {\n \"title\": \"Office Party\",\n \"date\": \"2024-12-20\",\n \"type\": \"other\",\n \"time\": \"18:00\",\n \"priority\": \"Normal\",\n \"attendees\": [\n \"team@company.com\"\n ],\n \"invites_will_be_sent\": true\n }\n}"
}
],
"isError": false
}

View file

@ -0,0 +1,9 @@
{
"content": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"message\": \"Event \\\"Team Standup\\\" scheduled successfully for \\\"2024-12-01\\\".\",\n \"event_details\": {\n \"title\": \"Team Standup\",\n \"date\": \"2024-12-01\",\n \"type\": \"meeting\",\n \"time\": \"09:00\",\n \"priority\": \"Normal\",\n \"attendees\": [\n \"alice@example.com\",\n \"bob@example.com\"\n ],\n \"invites_will_be_sent\": true\n }\n}"
}
],
"isError": false
}

Some files were not shown because too many files have changed in this diff Show more