ConfigurableHttpServerTransport.php•4.7 kB
<?php
declare(strict_types=1);
namespace OpenFGA\MCP;
use Override;
use PhpMcp\Server\Contracts\EventStoreInterface;
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
use Psr\Log\{LoggerInterface, NullLogger};
/**
* HTTP transport that supports configuration via JSON query parameters.
*
* This transport extends the base StreamableHttpServerTransport to intercept
* the 'config' query parameter and apply configuration before processing requests.
*/
final class ConfigurableHttpServerTransport extends StreamableHttpServerTransport
{
private readonly LoggerInterface $configLogger;
private readonly ConfigurationParser $configParser;
private readonly bool $isStateless;
/**
* @param array<string, mixed>|null $sslContext
* @param string $host
* @param int $port
* @param string $mcpPath
* @param bool $enableJsonResponse
* @param bool $stateless
* @param ?EventStoreInterface $eventStore
* @param ?LoggerInterface $logger
*/
public function __construct(
string $host = '127.0.0.1',
int $port = 9090,
string $mcpPath = '/mcp',
?array $sslContext = null,
bool $enableJsonResponse = true,
bool $stateless = false,
?EventStoreInterface $eventStore = null,
?LoggerInterface $logger = null,
) {
parent::__construct(
host: $host,
port: $port,
mcpPath: $mcpPath,
sslContext: $sslContext,
enableJsonResponse: $enableJsonResponse,
stateless: $stateless,
eventStore: $eventStore,
);
$this->isStateless = $stateless;
$this->configLogger = $logger ?? new NullLogger;
$this->configParser = new ConfigurationParser($this->configLogger);
}
/**
* Parse and apply configuration from a JSON string.
*
* This method is public to allow external code to trigger configuration updates
* when query parameters are detected through other means (e.g., middleware).
*
* @param string $jsonConfig JSON-encoded configuration
* @return ConfigurationResult Result of the configuration parsing
*/
public function applyConfiguration(string $jsonConfig): ConfigurationResult
{
$result = $this->configParser->parseAndApply($jsonConfig);
if (! $result->isSuccessful()) {
$this->configLogger->error('Failed to parse configuration', [
'errors' => $result->getErrors(),
]);
} else {
$this->configLogger->info('Configuration applied', [
'source' => 'query_param',
'values_set' => $result->getAppliedKeys(),
]);
// Log debug information if debug mode is enabled
if (getConfiguredBool('OPENFGA_MCP_DEBUG', true)) {
$this->configLogger->debug('Applied configuration values', [
'config' => $result->getAppliedValues(),
]);
}
}
return $result;
}
/**
* Override the listen method to intercept configuration early in the request lifecycle.
*/
#[Override]
public function listen(): void
{
// Apply any configuration from environment first
$this->applyConfigurationFromEnvironment();
// Start the parent listener
parent::listen();
}
/**
* Check for configuration in query parameters via a request inspection hook
* Note: Since we can't override createRequestHandler (it's private), we rely on
* the environment being set before the server processes the request.
*/
private function applyConfigurationFromEnvironment(): void
{
// This method is called once at startup.
// For per-request configuration in stateless mode, we would need a different
// approach, potentially using middleware or a wrapper around the transport.
// Log that the configurable transport is active
$this->configLogger->info('ConfigurableHttpServerTransport initialized', [
'note' => 'Query parameter configuration support is enabled',
'stateless' => $this->isStateless,
]);
// Important limitation: Since we can't intercept individual requests due to
// the private createRequestHandler method, we need to document that configuration
// via query parameters requires a custom middleware approach or modification
// to the base StreamableHttpServerTransport class.
}
}