mirror of
https://hk.gh-proxy.com/https://github.com/mcp-wp/mcp-server.git
synced 2025-10-03 21:21:17 +08:00
Revert "Add missing client & transport files"
This reverts commit d07fedd3f1
.
This commit is contained in:
parent
d07fedd3f1
commit
e1bdce366c
4 changed files with 0 additions and 493 deletions
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 ];
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
|
@ -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 ];
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue