Revert "Add missing client & transport files"

This reverts commit d07fedd3f1.
This commit is contained in:
Pascal Birchler 2025-04-03 15:42:54 +02:00
parent d07fedd3f1
commit e1bdce366c
No known key found for this signature in database
GPG key ID: 0DECE73DD74E8B2F
4 changed files with 0 additions and 493 deletions

View file

@ -1,98 +0,0 @@
<?php

namespace McpWp\MCP;

use Mcp\Client\Client as McpCLient;
use Mcp\Client\ClientSession;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class Client extends McpCLient {

private LoggerInterface $logger;

/**
* Client constructor.
*
* @param LoggerInterface|null $logger PSR-3 compliant logger.
*/
public function __construct( ?LoggerInterface $logger = null ) {
$this->logger = $logger ?? new NullLogger();

parent::__construct( $this->logger );
}

/**
* @param string|class-string<Server> $command_or_url Class name, command, or URL.
* @param array $args Unused.
* @param array|null $env Unused.
* @param float|null $read_timeout Unused.
* @return ClientSession
*/
public function connect(
string $command_or_url,
array $args = [],
?array $env = null,
?float $read_timeout = null
): ClientSession {
$session = null;
if ( class_exists( $command_or_url ) ) {
/**
* @var Server $server
*/
$server = new $command_or_url( $this->logger );

$transport = new InMemoryTransport(
$server,
$this->logger
);

[$read_stream, $write_stream] = $transport->connect();

$session = new InMemorySession(
$read_stream,
$write_stream,
$this->logger
);

$session->initialize();

return $session;
}

// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
$url_parts = parse_url( $command_or_url );

if ( isset( $url_parts['scheme'] ) && in_array( strtolower( $url_parts['scheme'] ), [ 'http', 'https' ], true ) ) {
$options = [
// Just for local debugging.
'verify' => false,
];
if ( ! empty( $url_parts['user'] ) && ! empty( $url_parts['pass'] ) ) {
$options['auth'] = [ $url_parts['user'], $url_parts['pass'] ];
}

$url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'];

$transport = new HttpTransport( $url, $options, $this->logger );
$transport->connect();

[$read_stream, $write_stream] = $transport->connect();

// Initialize the client session with the obtained streams
$session = new InMemorySession(
$read_stream,
$write_stream,
$this->logger
);

// Initialize the session (e.g., perform handshake if necessary)
$session->initialize();
$this->logger->info( 'Session initialized successfully' );

return $session;
}

return parent::connect( $command_or_url, $args, $env, $read_timeout );
}
}

View file

@ -1,180 +0,0 @@
<?php

namespace McpWp\MCP;

use Exception;
use InvalidArgumentException;
use Mcp\Shared\MemoryStream;
use Mcp\Types\JsonRpcMessage;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Psr\Log\NullLogger;
use Mcp\Types\JSONRPCRequest;
use Mcp\Types\JSONRPCNotification;
use Mcp\Types\JSONRPCResponse;
use Mcp\Types\JSONRPCError;
use Mcp\Types\RequestId;
use Mcp\Types\JsonRpcErrorObject;
use WpOrg\Requests\Response;

/**
* Class HttpTransport
*
* Handles streamable-HTTP based communication with an MCP server.
*/
class HttpTransport {
/** @var LoggerInterface */
private LoggerInterface $logger;

/**
* SseTransport constructor.
*
* @param string $url The HTTP endpoint URL.
* @param array $options Requests options.
* @param LoggerInterface|null $logger PSR-3 compliant logger.
*
* @throws InvalidArgumentException If the URL is empty.
*/
public function __construct(
private readonly string $url,
private readonly array $options = [],
?LoggerInterface $logger = null,
) {
if ( empty( $url ) ) {
throw new InvalidArgumentException( 'URL cannot be empty' );
}
$this->logger = $logger ?? new NullLogger();
}

/**
* @return array{0: MemoryStream, 1: MemoryStream}
*/
public function connect(): array {
$shared_stream = new class($this->url,$this->options, $this->logger) extends MemoryStream {
private LoggerInterface $logger;

private ?string $session_id = null;

public function __construct( private readonly string $url, private readonly array $options, LoggerInterface $logger ) {
$this->logger = $logger;
}

/**
* Send a JsonRpcMessage or Exception to the server via SSE.
*
* @param JsonRpcMessage|Exception $item The JSON-RPC message or exception to send.
*
* @return void
*
* @throws InvalidArgumentException If the message is not a JsonRpcMessage.
* @throws RuntimeException If sending the message fails.
*/
public function send( mixed $item ): void {
if ( ! $item instanceof JsonRpcMessage ) {
throw new InvalidArgumentException( 'Only JsonRpcMessage instances can be sent.' );
}

/**
* @var Response $response
*/
$response = \WP_CLI\Utils\http_request(
'POST',
$this->url,
json_encode( $item, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES ),
[
'Content-Type' => 'application/json',
'Mcp-Session-Id' => $this->session_id,
],
$this->options
);

if ( isset( $response->headers['mcp-session-id'] ) && ! isset( $this->session_id ) ) {
$this->session_id = (string) $response->headers['mcp-session-id'];
}

if ( empty( $response->body ) ) {
return;
}

$data = json_decode( $response->body, true, 512, JSON_THROW_ON_ERROR );

$this->logger->debug( 'Received response for sent message: ' . json_encode( $data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) );

$json_rpc_response = $this->instantiateJsonRpcMessage( $data );

parent::send( $json_rpc_response );
}

/**
* Instantiate a JsonRpcMessage from decoded data.
*
* @param array $data The decoded JSON data.
*
* @return JsonRpcMessage The instantiated JsonRpcMessage object.
*
* @throws InvalidArgumentException If the message structure is invalid.
*/
private function instantiateJsonRpcMessage( array $data ): JsonRpcMessage {
if ( ! isset( $data['jsonrpc'] ) || '2.0' !== $data['jsonrpc'] ) {
throw new InvalidArgumentException( 'Invalid JSON-RPC version.' );
}

if ( isset( $data['method'] ) ) {
// It's a Request or Notification
if ( isset( $data['id'] ) ) {
// It's a Request
return new JsonRpcMessage(
new JSONRPCRequest(
'2.0',
new RequestId( $data['id'] ),
$data['params'] ?? null,
$data['method']
)
);
}

// It's a Notification
return new JsonRpcMessage(
new JSONRPCNotification(
'2.0',
$data['params'] ?? null,
$data['method']
)
);
}

if ( isset( $data['result'] ) || isset( $data['error'] ) ) {
// It's a Response or Error
if ( isset( $data['error'] ) ) {
// It's an Error
$error_data = $data['error'];
return new JsonRpcMessage(
new JSONRPCError(
'2.0',
isset( $data['id'] ) ? new RequestId( $data['id'] ) : null,
new JsonRpcErrorObject(
$error_data['code'],
$error_data['message'],
$error_data['data'] ?? null
)
)
);
}

// It's a Response
return new JsonRpcMessage(
new JSONRPCResponse(
'2.0',
isset( $data['id'] ) ? new RequestId( $data['id'] ) : null,
$data['result']
)
);
}

throw new InvalidArgumentException( 'Invalid JSON-RPC message structure.' );
}
};

return [ $shared_stream, $shared_stream ];
}
}

View file

@ -1,160 +0,0 @@
<?php

namespace McpWp\MCP;

use InvalidArgumentException;
use Mcp\Client\ClientSession;
use Mcp\Shared\ErrorData;
use Mcp\Shared\McpError;
use Mcp\Shared\MemoryStream;
use Mcp\Types\JSONRPCError;
use Mcp\Types\JsonRpcMessage;
use Mcp\Types\JSONRPCRequest;
use Mcp\Types\JSONRPCResponse;
use Mcp\Types\McpModel;
use Mcp\Types\RequestId;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class InMemorySession extends ClientSession {
private ?MemoryStream $read_stream;

private ?MemoryStream $write_stream;

private LoggerInterface|NullLogger $logger;

private int $request_id = 0;

/**
* ClientSession constructor.
*
* @param MemoryStream $read_stream Stream to read incoming messages from.
* @param MemoryStream $write_stream Stream to write outgoing messages to.
* @param LoggerInterface|null $logger PSR-3 compliant logger.
*
* @throws InvalidArgumentException If the provided streams are invalid.
*/
public function __construct(
MemoryStream $read_stream,
MemoryStream $write_stream,
?LoggerInterface $logger = null
) {
$this->logger = $logger ?? new NullLogger();
$this->read_stream = $read_stream;
$this->write_stream = $write_stream;

parent::__construct(
$read_stream,
$write_stream,
null,
$this->logger
);
}

/**
* Sends a request and waits for a typed result. If an error response is received, throws an exception.
*
* @param McpModel $request A typed request object (e.g., InitializeRequest, PingRequest).
* @param string $result_type The fully-qualified class name of the expected result type (must implement McpModel). TODO: Implement.
* @return McpModel The validated result object.
* @throws McpError If an error response is received.
*/
public function sendRequest( McpModel $request, string $result_type ): McpModel {
$this->validate_request_object( $request );

$request_id_value = $this->request_id++;
$request_id = new RequestId( $request_id_value );

// Convert the typed request into a JSON-RPC request message
// Assuming $request has public properties: method, params
$json_rpc_request = new JsonRpcMessage(
new JSONRPCRequest(
'2.0',
$request_id,
$request->params ?? null,
$request->method
)
);

// Send the request message
$this->writeMessage( $json_rpc_request );

$message = $this->readNextMessage();

$inner_message = $message->message;

if ( $inner_message instanceof JSONRPCError ) {
// It's an error response
// Convert JsonRpcErrorObject into ErrorData
$error_data = new ErrorData(
$inner_message->error->code,
$inner_message->error->message,
$inner_message->error->data
);
throw new McpError( $error_data );
}

if ( $inner_message instanceof JSONRPCResponse ) {
// Coming from HttpTransport.
if ( is_array( $inner_message->result ) ) {
return $result_type::fromResponseData( $inner_message->result );
}

// InMemoryTransport already returns the correct instances.
return $inner_message->result;
}

// Invalid response
throw new InvalidArgumentException( 'Invalid JSON-RPC response received' );
}

private function validate_request_object( McpModel $request ): void {
// Check if request has a method property
if ( ! property_exists( $request, 'method' ) || empty( $request->method ) ) {
throw new InvalidArgumentException( 'Request must have a method' );
}
}

/**
* Write a JsonRpcMessage to the write stream.
*
* @param JsonRpcMessage $message The JSON-RPC message to send.
*
* @throws RuntimeException If writing to the stream fails.
*
* @return void
*/
protected function writeMessage( JsonRpcMessage $message ): void {
$this->logger->debug( 'Sending message to server: ' . json_encode( $message, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) );
$this->write_stream->send( $message );
}

/**
* Read the next message from the read stream.
*
* @throws RuntimeException If an invalid message type is received.
*
* @return JsonRpcMessage The received JSON-RPC message.
*/
protected function readNextMessage(): JsonRpcMessage {
return $this->read_stream->receive();
}

/**
* Start any additional message processing mechanisms if necessary.
*
* @return void
*/
protected function startMessageProcessing(): void {
// Not used.
}

/**
* Stop any additional message processing mechanisms if necessary.
*
* @return void
*/
protected function stopMessageProcessing(): void {
// Not used.
}
}

View file

@ -1,55 +0,0 @@
<?php

namespace McpWp\MCP;

use Exception;
use InvalidArgumentException;
use Mcp\Shared\MemoryStream;
use Mcp\Types\JsonRpcMessage;
use Psr\Log\LoggerInterface;
use RuntimeException;

readonly class InMemoryTransport {

public function __construct( private Server $server, private LoggerInterface $logger ) {
}

/**
* @return array{0: MemoryStream, 1: MemoryStream}
*/
public function connect(): array {
$shared_stream = new class($this->server,$this->logger) extends MemoryStream {
private LoggerInterface $logger;

public function __construct( private readonly Server $server, LoggerInterface $logger ) {
$this->logger = $logger;
}

/**
* Send a JsonRpcMessage or Exception to the server via SSE.
*
* @param JsonRpcMessage|Exception $message The JSON-RPC message or exception to send.
*
* @return void
*
* @throws InvalidArgumentException If the message is not a JsonRpcMessage.
* @throws RuntimeException If sending the message fails.
*/
public function send( mixed $message ): void {
if ( ! $message instanceof JsonRpcMessage ) {
throw new InvalidArgumentException( 'Only JsonRpcMessage instances can be sent.' );
}

$response = $this->server->handle_message( $message );

$this->logger->debug( 'Received response for sent message: ' . json_encode( $response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) );

if ( null !== $response ) {
parent::send( $response );
}
}
};

return [ $shared_stream, $shared_stream ];
}
}