Skip to content

Commit 1f54069

Browse files
authored
Add mcp proxy command (#65)
1 parent 87fc7b6 commit 1f54069

File tree

4 files changed

+215
-1
lines changed

4 files changed

+215
-1
lines changed

ai-command.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414

1515
WP_CLI::add_command( 'ai', AiCommand::class );
1616
WP_CLI::add_command( 'mcp prompt', AiCommand::class );
17+
WP_CLI::add_command( 'mcp', McpCommand::class );
1718
WP_CLI::add_command( 'mcp server', McpServerCommand::class );

src/AiCommand.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use McpWp\AiCommand\MCP\Client;
88
use McpWp\AiCommand\Utils\CliLogger;
99
use McpWp\AiCommand\Utils\McpConfig;
10-
use McpWp\MCP\Server;
1110
use McpWp\MCP\Servers\WordPress\WordPress;
1211
use WP_CLI;
1312
use WP_CLI\Utils;

src/MCP/ProxySession.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace McpWp\AiCommand\MCP;
4+
5+
use InvalidArgumentException;
6+
use Mcp\Client\ClientSession;
7+
use Mcp\Server\InitializationOptions;
8+
use Mcp\Server\ServerSession;
9+
use Mcp\Server\Transport\Transport;
10+
use Mcp\Shared\RequestResponder;
11+
use Mcp\Types\CallToolRequest;
12+
use Mcp\Types\CallToolResult;
13+
use Mcp\Types\ClientRequest;
14+
use Mcp\Types\CompleteRequest;
15+
use Mcp\Types\CompleteResult;
16+
use Mcp\Types\EmptyResult;
17+
use Mcp\Types\GetPromptRequest;
18+
use Mcp\Types\GetPromptResult;
19+
use Mcp\Types\InitializeRequest;
20+
use Mcp\Types\InitializeResult;
21+
use Mcp\Types\ListPromptsRequest;
22+
use Mcp\Types\ListPromptsResult;
23+
use Mcp\Types\ListResourcesRequest;
24+
use Mcp\Types\ListResourcesResult;
25+
use Mcp\Types\ListToolsRequest;
26+
use Mcp\Types\ListToolsResult;
27+
use Mcp\Types\PingRequest;
28+
use Mcp\Types\ReadResourceRequest;
29+
use Mcp\Types\ReadResourceResult;
30+
use Mcp\Types\SubscribeRequest;
31+
use Mcp\Types\UnsubscribeRequest;
32+
use Psr\Log\LoggerInterface;
33+
34+
35+
/**
36+
* ProxySession to pass messages from STDIO to an HTTP MCP server.
37+
*/
38+
class ProxySession extends ServerSession {
39+
protected ?ClientSession $client_session;
40+
41+
public function __construct(
42+
protected readonly string $url,
43+
Transport $transport,
44+
InitializationOptions $init_options,
45+
?LoggerInterface $logger = null
46+
) {
47+
parent::__construct(
48+
$transport,
49+
$init_options,
50+
$logger
51+
);
52+
}
53+
54+
/**
55+
* Handle incoming requests. If it's the initialize request, handle it specially.
56+
* Otherwise, ensure initialization is complete before handling other requests.
57+
*
58+
* @param ClientRequest $request The incoming client request.
59+
* @param callable $respond The responder callable.
60+
*/
61+
public function handleRequest( RequestResponder $responder ): void {
62+
$request = $responder->getRequest();
63+
$actual_request = $request->getRequest();
64+
$method = $actual_request->method;
65+
66+
$this->logger->info( 'Proxying request: ' . json_encode( $actual_request ) );
67+
68+
$result = null;
69+
70+
if ( ! isset( $this->client_session ) ) {
71+
$client = new Client( $this->logger );
72+
$this->client_session = $client->connect(
73+
$this->url
74+
);
75+
}
76+
77+
switch ( get_class( $actual_request ) ) {
78+
case InitializeRequest::class:
79+
$result = $this->client_session->sendRequest( $actual_request, InitializeResult::class );
80+
break;
81+
case SubscribeRequest::class:
82+
case UnsubscribeRequest::class:
83+
case PingRequest::class:
84+
$result = $this->client_session->sendRequest( $actual_request, EmptyResult::class );
85+
break;
86+
case ListResourcesRequest::class:
87+
$result = $this->client_session->sendRequest( $actual_request, ListResourcesResult::class );
88+
break;
89+
case ListToolsRequest::class:
90+
$result = $this->client_session->sendRequest( $actual_request, ListToolsResult::class );
91+
break;
92+
case CallToolRequest::class:
93+
$result = $this->client_session->sendRequest( $actual_request, CallToolResult::class );
94+
break;
95+
case ReadResourceRequest::class:
96+
$result = $this->client_session->sendRequest( $actual_request, ReadResourceResult::class );
97+
break;
98+
case ListPromptsRequest::class:
99+
$result = $this->client_session->sendRequest( $actual_request, ListPromptsResult::class );
100+
break;
101+
case GetPromptRequest::class:
102+
$result = $this->client_session->sendRequest( $actual_request, GetPromptResult::class );
103+
break;
104+
case CompleteRequest::class:
105+
$result = $this->client_session->sendRequest( $actual_request, CompleteResult::class );
106+
break;
107+
}
108+
109+
if ( null === $result ) {
110+
throw new InvalidArgumentException( "Unhandled proxied request for method: $method / " . get_class( $actual_request ) );
111+
}
112+
113+
$responder->sendResponse( $result );
114+
}
115+
}

src/McpCommand.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace McpWp\AiCommand;
4+
5+
use Exception;
6+
use Mcp\Server\Server;
7+
use Mcp\Server\Transport\StdioServerTransport;
8+
use McpWp\AiCommand\MCP\ProxySession;
9+
use McpWp\AiCommand\Utils\CliLogger;
10+
use McpWp\AiCommand\Utils\McpConfig;
11+
use WP_CLI;
12+
use WP_CLI_Command;
13+
14+
/**
15+
* MCP command.
16+
*
17+
* Manage MCP servers for use with WP-CLI and proxy requests to servers using the HTTP transport.
18+
*/
19+
class McpCommand extends WP_CLI_Command {
20+
/**
21+
* Proxy MCP requests to a given server.
22+
*
23+
* ## OPTIONS
24+
*
25+
* <server>
26+
* : Name of an existing server to proxy requests to.
27+
*
28+
* ## EXAMPLES
29+
*
30+
* # Add server from URL.
31+
* $ wp mcp server add "mywpserver" "https://example.com/wp-json/mcp/v1/mcp"
32+
* Success: Server added.
33+
*
34+
* # Proxy requests to server
35+
* $ wp mcp proxy "mywpserver"
36+
*
37+
* @when before_wp_load
38+
*
39+
* @param string[] $args Indexed array of positional arguments.
40+
*/
41+
public function proxy( $args ): void {
42+
$server = $this->get_config()->get_server( $args[0] );
43+
44+
if ( null === $server ) {
45+
WP_CLI::error( 'Server does not exist.' );
46+
return;
47+
}
48+
49+
$url = $server['server'];
50+
51+
if ( ! str_starts_with( $url, 'http://' ) && ! str_starts_with( $url, 'https://' ) ) {
52+
WP_CLI::error( 'Server is not using HTTP transport.' );
53+
return;
54+
}
55+
56+
$logger = new CliLogger();
57+
58+
$server = new Server( $args[0], $logger );
59+
60+
try {
61+
$transport = StdioServerTransport::create();
62+
63+
$proxy_session = new ProxySession(
64+
$url,
65+
$transport,
66+
$server->createInitializationOptions(),
67+
$logger
68+
);
69+
70+
$server->setSession( $proxy_session );
71+
72+
$proxy_session->registerHandlers( $server->getHandlers() );
73+
$proxy_session->registerNotificationHandlers( $server->getNotificationHandlers() );
74+
75+
$proxy_session->start();
76+
77+
$logger->info( 'Server started' );
78+
79+
} catch ( Exception $e ) {
80+
$logger->error( 'Proxy error: ' . $e->getMessage() );
81+
} finally {
82+
if ( isset( $proxy_session ) ) {
83+
$proxy_session->stop();
84+
}
85+
if ( isset( $transport ) ) {
86+
$transport->stop();
87+
}
88+
}
89+
}
90+
91+
/**
92+
* Returns an McpConfig instance.
93+
*
94+
* @return McpConfig Config instance.
95+
*/
96+
protected function get_config(): McpConfig {
97+
return new McpConfig();
98+
}
99+
}

0 commit comments

Comments
 (0)