$toolArgs */ #[DataProvider('provideMethods')] public function testMethodOutputMatchesSnapshot( string $method, ?string $toolName = null, array $toolArgs = [], ?string $uri = null, ): void { $inspector = \sprintf('@modelcontextprotocol/inspector@%s', self::INSPECTOR_VERSION); $args = [ 'npx', $inspector, '--cli', 'php', $this->getServerScript(), '--method', $method, ]; // Options for tools/call if (null !== $toolName) { $args[] = '--tool-name'; $args[] = $toolName; foreach ($toolArgs as $key => $value) { $args[] = '--tool-arg'; $args[] = \sprintf('%s=%s', $key, $value); } } // Options for resources/read if (null !== $uri) { $args[] = '--uri'; $args[] = $uri; } $output = (new Process($args)) ->mustRun() ->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'; } }