diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d5adf9e..e18fb6d 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -12,8 +12,8 @@
tests/Unit
-
- tests/Inspector
+
+ tests/Application
diff --git a/tests/Application/ApplicationTestCase.php b/tests/Application/ApplicationTestCase.php
new file mode 100644
index 0000000..5f2da9f
--- /dev/null
+++ b/tests/Application/ApplicationTestCase.php
@@ -0,0 +1,172 @@
+ $messages
+ * @param array $env
+ *
+ * @return array>>
+ */
+ protected function runServer(array $messages, float $timeout = 5.0, array $env = []): array
+ {
+ if (0 === \count($messages)) {
+ return [];
+ }
+
+ $process = new Process([
+ 'php',
+ $this->getServerScript(),
+ ], \dirname(__DIR__, 2), [] === $env ? null : $env, null, $timeout);
+
+ $process->setInput($this->formatInput($messages));
+ $process->mustRun();
+
+ return $this->decodeJsonLines($process->getOutput());
+ }
+
+ abstract protected function getServerScript(): string;
+
+ /**
+ * @param mixed[] $params
+ *
+ * @throws \JsonException
+ */
+ protected function jsonRequest(string $method, ?array $params = null, ?string $id = null): string
+ {
+ $payload = [
+ 'jsonrpc' => MessageInterface::JSONRPC_VERSION,
+ 'id' => $id,
+ 'method' => $method,
+ ];
+
+ if (null !== $params) {
+ $payload['params'] = $params;
+ }
+
+ return (string) json_encode($payload, \JSON_THROW_ON_ERROR);
+ }
+
+ /**
+ * @param list $messages
+ */
+ private function formatInput(array $messages): string
+ {
+ return implode("\n", $messages)."\n";
+ }
+
+ /**
+ * @return array>>
+ */
+ private function decodeJsonLines(string $output): array
+ {
+ $output = trim($output);
+ $responses = [];
+
+ if ('' === $output) {
+ return $responses;
+ }
+
+ foreach (preg_split('/\R+/', $output) as $line) {
+ if ('' === $line) {
+ continue;
+ }
+
+ try {
+ $decoded = json_decode($line, true, 512, \JSON_THROW_ON_ERROR);
+ } catch (\JsonException) {
+ continue;
+ }
+
+ if (!\is_array($decoded)) {
+ continue;
+ }
+
+ $id = $decoded['id'] ?? null;
+
+ if (\is_string($id) || \is_int($id)) {
+ $responses[(string) $id] = $decoded;
+ }
+ }
+
+ return $responses;
+ }
+
+ protected function getSnapshotFilePath(string $method): string
+ {
+ $className = substr(static::class, strrpos(static::class, '\\') + 1);
+
+ return __DIR__.'/snapshots/'.$className.'-'.str_replace('/', '_', $method).'.json';
+ }
+
+ /**
+ * @return array
+ *
+ * @throws \JsonException
+ */
+ protected function loadSnapshot(string $method): array
+ {
+ $path = $this->getSnapshotFilePath($method);
+
+ $contents = file_get_contents($path);
+ $this->assertNotFalse($contents, 'Failed to read snapshot: '.$path);
+
+ return json_decode($contents, true, 512, \JSON_THROW_ON_ERROR);
+ }
+
+ /**
+ * @param array> $response
+ *
+ * @throws \JsonException
+ */
+ protected function assertResponseMatchesSnapshot(array $response, string $method): void
+ {
+ $this->assertArrayHasKey('result', $response);
+ $actual = $response['result'];
+
+ $expected = $this->loadSnapshot($method);
+
+ $this->assertEquals($expected, $actual, 'Response payload does not match snapshot '.$this->getSnapshotFilePath($method));
+ }
+
+ /**
+ * @param array> $capabilities
+ * @param string[] $clientInfo
+ *
+ * @throws \JsonException
+ */
+ protected function initializeMessage(
+ ?string $id = null,
+ string $protocolVersion = MessageInterface::PROTOCOL_VERSION,
+ array $capabilities = [],
+ array $clientInfo = [
+ 'name' => 'test-suite',
+ 'version' => '1.0.0',
+ ],
+ ): string {
+ $id ??= uniqid();
+
+ return $this->jsonRequest('initialize', [
+ 'protocolVersion' => $protocolVersion,
+ 'capabilities' => $capabilities,
+ 'clientInfo' => $clientInfo,
+ ], $id);
+ }
+}
diff --git a/tests/Application/ManualStdioExampleTest.php b/tests/Application/ManualStdioExampleTest.php
new file mode 100644
index 0000000..72c6408
--- /dev/null
+++ b/tests/Application/ManualStdioExampleTest.php
@@ -0,0 +1,107 @@
+runServer([
+ $this->initializeMessage($initializeId),
+ ]);
+
+ $this->assertArrayHasKey($initializeId, $responses);
+
+ $initialize = $responses[$initializeId];
+ $this->assertResponseMatchesSnapshot($initialize, 'initialize');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testToolsListMatchesSnapshot(): void
+ {
+ $toolsListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('tools/list', id: $toolsListId),
+ ]);
+
+ $this->assertArrayHasKey($toolsListId, $responses);
+ $toolsList = $responses[$toolsListId];
+ $this->assertResponseMatchesSnapshot($toolsList, 'tools/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testPromptsListMatchesSnapshot(): void
+ {
+ $promptsListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('prompts/list', id: $promptsListId),
+ ]);
+
+ $this->assertArrayHasKey($promptsListId, $responses);
+ $promptsList = $responses[$promptsListId];
+ $this->assertResponseMatchesSnapshot($promptsList, 'prompts/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testResourcesListMatchesSnapshot(): void
+ {
+ $resourcesListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('resources/list', id: $resourcesListId),
+ ]);
+
+ $this->assertArrayHasKey($resourcesListId, $responses);
+ $resourcesList = $responses[$resourcesListId];
+ $this->assertResponseMatchesSnapshot($resourcesList, 'resources/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testResourceTemplatesListMatchesSnapshot(): void
+ {
+ $templatesListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('resources/templates/list', id: $templatesListId),
+ ]);
+
+ $this->assertArrayHasKey($templatesListId, $responses);
+ $templatesList = $responses[$templatesListId];
+ $this->assertResponseMatchesSnapshot($templatesList, 'resources/templates/list');
+ }
+
+ protected function getServerScript(): string
+ {
+ return \dirname(__DIR__, 2).'/examples/stdio-explicit-registration/server.php';
+ }
+}
diff --git a/tests/Application/StdioCalculatorExampleTest.php b/tests/Application/StdioCalculatorExampleTest.php
new file mode 100644
index 0000000..36973b0
--- /dev/null
+++ b/tests/Application/StdioCalculatorExampleTest.php
@@ -0,0 +1,92 @@
+runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('tools/list', id: $toolsListId),
+ ]);
+
+ $this->assertArrayHasKey($toolsListId, $responses);
+ $toolsList = $responses[$toolsListId];
+ $this->assertResponseMatchesSnapshot($toolsList, 'tools/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testPromptsListMatchesSnapshot(): void
+ {
+ $promptsListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('prompts/list', id: $promptsListId),
+ ]);
+
+ $this->assertArrayHasKey($promptsListId, $responses);
+ $promptsList = $responses[$promptsListId];
+ $this->assertResponseMatchesSnapshot($promptsList, 'prompts/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testResourcesListMatchesSnapshot(): void
+ {
+ $resourcesListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('resources/list', id: $resourcesListId),
+ ]);
+
+ $this->assertArrayHasKey($resourcesListId, $responses);
+ $resourcesList = $responses[$resourcesListId];
+ $this->assertResponseMatchesSnapshot($resourcesList, 'resources/list');
+ }
+
+ /**
+ * @throws \JsonException
+ */
+ public function testResourceTemplatesListMatchesSnapshot(): void
+ {
+ $templatesListId = uniqid('t_');
+
+ $responses = $this->runServer([
+ $this->initializeMessage(),
+ $this->jsonRequest('resources/templates/list', id: $templatesListId),
+ ]);
+
+ $this->assertArrayHasKey($templatesListId, $responses);
+ $templatesList = $responses[$templatesListId];
+ $this->assertResponseMatchesSnapshot($templatesList, 'resources/templates/list');
+ }
+
+ protected function getServerScript(): string
+ {
+ return \dirname(__DIR__, 2).'/examples/stdio-discovery-calculator/server.php';
+ }
+}
diff --git a/tests/Application/snapshots/ManualStdioExampleTest-initialize.json b/tests/Application/snapshots/ManualStdioExampleTest-initialize.json
new file mode 100644
index 0000000..30da842
--- /dev/null
+++ b/tests/Application/snapshots/ManualStdioExampleTest-initialize.json
@@ -0,0 +1,13 @@
+{
+ "capabilities": {
+ "prompts": {},
+ "resources": {},
+ "tools": {},
+ "completions": {}
+ },
+ "serverInfo": {
+ "name": "Manual Reg Server",
+ "version": "1.0.0"
+ },
+ "protocolVersion": "2025-06-18"
+}
diff --git a/tests/Inspector/snapshots/ManualStdioExampleTest-prompts_list.json b/tests/Application/snapshots/ManualStdioExampleTest-prompts_list.json
similarity index 100%
rename from tests/Inspector/snapshots/ManualStdioExampleTest-prompts_list.json
rename to tests/Application/snapshots/ManualStdioExampleTest-prompts_list.json
diff --git a/tests/Inspector/snapshots/ManualStdioExampleTest-resources_list.json b/tests/Application/snapshots/ManualStdioExampleTest-resources_list.json
similarity index 100%
rename from tests/Inspector/snapshots/ManualStdioExampleTest-resources_list.json
rename to tests/Application/snapshots/ManualStdioExampleTest-resources_list.json
diff --git a/tests/Inspector/snapshots/ManualStdioExampleTest-resources_templates_list.json b/tests/Application/snapshots/ManualStdioExampleTest-resources_templates_list.json
similarity index 100%
rename from tests/Inspector/snapshots/ManualStdioExampleTest-resources_templates_list.json
rename to tests/Application/snapshots/ManualStdioExampleTest-resources_templates_list.json
diff --git a/tests/Inspector/snapshots/ManualStdioExampleTest-tools_list.json b/tests/Application/snapshots/ManualStdioExampleTest-tools_list.json
similarity index 100%
rename from tests/Inspector/snapshots/ManualStdioExampleTest-tools_list.json
rename to tests/Application/snapshots/ManualStdioExampleTest-tools_list.json
diff --git a/tests/Inspector/snapshots/StdioCalculatorExampleTest-prompts_list.json b/tests/Application/snapshots/StdioCalculatorExampleTest-prompts_list.json
similarity index 100%
rename from tests/Inspector/snapshots/StdioCalculatorExampleTest-prompts_list.json
rename to tests/Application/snapshots/StdioCalculatorExampleTest-prompts_list.json
diff --git a/tests/Inspector/snapshots/StdioCalculatorExampleTest-resources_list.json b/tests/Application/snapshots/StdioCalculatorExampleTest-resources_list.json
similarity index 100%
rename from tests/Inspector/snapshots/StdioCalculatorExampleTest-resources_list.json
rename to tests/Application/snapshots/StdioCalculatorExampleTest-resources_list.json
diff --git a/tests/Inspector/snapshots/StdioCalculatorExampleTest-resources_templates_list.json b/tests/Application/snapshots/StdioCalculatorExampleTest-resources_templates_list.json
similarity index 100%
rename from tests/Inspector/snapshots/StdioCalculatorExampleTest-resources_templates_list.json
rename to tests/Application/snapshots/StdioCalculatorExampleTest-resources_templates_list.json
diff --git a/tests/Inspector/snapshots/StdioCalculatorExampleTest-tools_list.json b/tests/Application/snapshots/StdioCalculatorExampleTest-tools_list.json
similarity index 100%
rename from tests/Inspector/snapshots/StdioCalculatorExampleTest-tools_list.json
rename to tests/Application/snapshots/StdioCalculatorExampleTest-tools_list.json
diff --git a/tests/Inspector/InspectorSnapshotTestCase.php b/tests/Inspector/InspectorSnapshotTestCase.php
deleted file mode 100644
index e576795..0000000
--- a/tests/Inspector/InspectorSnapshotTestCase.php
+++ /dev/null
@@ -1,68 +0,0 @@
-getServerScript(), $method)
- )->mustRun();
-
- $output = $process->getOutput();
- $snapshotFile = $this->getSnapshotFilePath($method);
-
- if (!file_exists($snapshotFile)) {
- file_put_contents($snapshotFile, $output.\PHP_EOL);
- $this->markTestIncomplete("Snapshot created at $snapshotFile, please re-run tests.");
- }
-
- $expected = file_get_contents($snapshotFile);
-
- $this->assertJsonStringEqualsJsonString($expected, $output);
- }
-
- /**
- * List of methods to test.
- *
- * @return array
- */
- abstract public static function provideMethods(): array;
-
- abstract protected function getServerScript(): string;
-
- /**
- * @return array
- */
- protected static function provideListMethods(): array
- {
- return [
- 'Prompt Listing' => ['method' => 'prompts/list'],
- 'Resource Listing' => ['method' => 'resources/list'],
- 'Resource Template Listing' => ['method' => 'resources/templates/list'],
- 'Tool Listing' => ['method' => 'tools/list'],
- ];
- }
-
- private function getSnapshotFilePath(string $method): string
- {
- $className = substr(static::class, strrpos(static::class, '\\') + 1);
-
- return __DIR__.'/snapshots/'.$className.'-'.str_replace('/', '_', $method).'.json';
- }
-}
diff --git a/tests/Inspector/ManualStdioExampleTest.php b/tests/Inspector/ManualStdioExampleTest.php
deleted file mode 100644
index 582de0d..0000000
--- a/tests/Inspector/ManualStdioExampleTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-