diff --git a/.docs/README.md b/.docs/README.md index e304553..dc1226f 100644 --- a/.docs/README.md +++ b/.docs/README.md @@ -60,6 +60,7 @@ mcp: # Server information name: # Server name (default: 'MCP') version: # Server version (default: '1.0.0') + paginationLimit: # Max items returned per page for list methods (default: 50) # Discovery configuration discovery: diff --git a/composer.json b/composer.json index fc902ec..1f4f209 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,13 @@ { - "name": "contributte/mcp", - "description": "Doctrine DBAL for Nette Framework", + "name": "demmmmios/mcp", + "description": "MCP server for Nette Framework", "keywords": [ "nette", - "doctrine", - "dbal", - "database", - "mysql", - "postgres", - "sqlite" + "mcp" ], "type": "library", "license": "MIT", - "homepage": "https://github.com/contributte/doctrine-dbal", + "homepage": "https://github.com/contributte/mcp", "authors": [ { "name": "Milan Felix Ć ulc", @@ -20,16 +15,16 @@ } ], "require": { - "php": ">=8.4", + "php": ">=8.2", "guzzlehttp/guzzle": "^7.10.0", - "psr/container": "^2.0.2", - "psr/cache": "^3.0.0", - "psr/simple-cache": "^3.0.0", - "mcp/sdk": "dev-main#369932378f7bcfa29a00efc1530b70c4d501a24b" + "psr/container": "^1.0 || ^2.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "mcp/sdk": "^0.7.0" }, "require-dev": { "ext-pdo": "*", - "contributte/phpstan": "^0.2.0", + "contributte/phpstan": "^0.3.0", "contributte/qa": "^0.5.0", "contributte/tester": "^0.5.0", "mockery/mockery": "^1.6.12", @@ -37,6 +32,7 @@ "nette/di": "^3.2.5", "nette/http": "^3.3.3", "symfony/console": "^7.4.1 || ^8.0.1", + "symfony/finder": "^7.4.1 || ^8.0.1", "tracy/tracy": "^2.11.0" }, "autoload": { @@ -49,9 +45,6 @@ "Tests\\": "tests" } }, - "conflict": { - "nette/neon": "<3.4.5" - }, "minimum-stability": "dev", "prefer-stable": true, "config": { diff --git a/phpstan.neon b/phpstan.neon index 201ecd8..16b3197 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,7 +3,7 @@ includes: parameters: level: 9 - phpVersion: 80400 + phpVersion: 80200 scanDirectories: - src diff --git a/src/DI/Helpers/BuilderMan.php b/src/DI/Helpers/BuilderMan.php index 233b183..24d1a9d 100644 --- a/src/DI/Helpers/BuilderMan.php +++ b/src/DI/Helpers/BuilderMan.php @@ -31,7 +31,11 @@ public function getServiceDefinitionsByTag(string $tag): array $definitions = []; foreach ($builder->findByTag($tag) as $serviceName => $tagValue) { - $definitions[(string) $tagValue] = $builder->getDefinition($serviceName); + if (!is_string($tagValue)) { + continue; + } + + $definitions[$tagValue] = $builder->getDefinition($serviceName); } return $definitions; diff --git a/src/DI/McpExtension.php b/src/DI/McpExtension.php index ff7f127..bbcda07 100644 --- a/src/DI/McpExtension.php +++ b/src/DI/McpExtension.php @@ -17,7 +17,7 @@ use Mcp\Server\Builder; use Mcp\Server\Session\FileSessionStore; use Mcp\Server\Session\InMemorySessionStore; -use Mcp\Server\Session\Psr16StoreSession; +use Mcp\Server\Session\Psr16SessionStore; use Nette\Application\IPresenterFactory; use Nette\DI\CompilerExtension; use Nette\DI\Definitions\ServiceDefinition; @@ -34,9 +34,9 @@ class McpExtension extends CompilerExtension { - public const string SERVER_TAG = 'contributte.mcp.server'; - public const string SERVER_FACTORY_TAG = 'contributte.mcp.server_factory'; - public const string TRANSPORT_FACTORY_TAG = 'contributte.mcp.transport_factory'; + public const SERVER_TAG = 'contributte.mcp.server'; + public const SERVER_FACTORY_TAG = 'contributte.mcp.server_factory'; + public const TRANSPORT_FACTORY_TAG = 'contributte.mcp.transport_factory'; public function getConfigSchema(): Schema { @@ -55,11 +55,12 @@ public function getConfigSchema(): Schema Expect::structure([ 'name' => Expect::string()->default('MCP'), 'version' => Expect::string()->default('1.0.0'), + 'paginationLimit' => Expect::int(50), 'discovery' => Expect::structure([ 'enabled' => Expect::bool(true), 'basePath' => Expect::string()->default($parameters['appDir'] ?? getcwd()), - 'scanDirs' => Expect::arrayOf(Expect::string())->default(['.']), - 'excludeDirs' => Expect::arrayOf(Expect::string())->default([]), + 'scanDirs' => Expect::arrayOf(Expect::string())->default(['.'])->mergeDefaults(false), + 'excludeDirs' => Expect::arrayOf(Expect::string())->default([])->mergeDefaults(false), 'cache' => Expect::anyOf($expectService, null)->default(null), ])->required(), 'session' => Expect::structure([ @@ -154,6 +155,11 @@ private function loadServerConfiguration(string $serverName, stdClass $serverCon $serverConfig->version, ]); + // Server:PaginationLimit + $builderDef->addSetup('setPaginationLimit', [ + $serverConfig->paginationLimit, + ]); + // Server:Container if ($serverConfig->container === null) { $builderDef->addSetup('setContainer', [new Statement(NetteContainer::class, ['@container'])]); @@ -178,7 +184,8 @@ private function loadServerConfiguration(string $serverName, stdClass $serverCon // Server:Session switch ($serverConfig->session->type) { case 'file': - $path = $serverConfig->session->path ?? (isset($builder->parameters['tempDir']) ? $builder->parameters['tempDir'] . '/mcp' : null); + $tempDir = $builder->parameters['tempDir'] ?? null; + $path = $serverConfig->session->path ?? (is_string($tempDir) ? $tempDir . '/mcp' : null); if ($path === null) { throw new LogicalException( sprintf('Session path must be configured for file sessions (server "%s"). Either set session.path or ensure %%tempDir%% is available.', $serverName) @@ -198,7 +205,7 @@ private function loadServerConfiguration(string $serverName, stdClass $serverCon } $cacheService = BuilderMan::of($this)->resolveService($serverConfig->session->cache); - $sessionStore = new Statement(Psr16StoreSession::class, [$cacheService, $serverConfig->session->prefix, $serverConfig->session->ttl]); + $sessionStore = new Statement(Psr16SessionStore::class, [$cacheService, $serverConfig->session->prefix, $serverConfig->session->ttl]); break; default: $sessionStore = null; diff --git a/src/Http/GuzzleBridge.php b/src/Http/GuzzleBridge.php index d2bd371..686b14b 100644 --- a/src/Http/GuzzleBridge.php +++ b/src/Http/GuzzleBridge.php @@ -22,7 +22,7 @@ public static function fromNette(IRequest $netteRequest): ServerRequestInterface $headers = []; foreach ($netteRequest->getHeaders() as $name => $value) { - $headers[$name] = is_array($value) ? $value : [$value]; + $headers[$name] = [$value]; } $body = $netteRequest->getRawBody(); diff --git a/src/Http/StreamableTransportFactory.php b/src/Http/StreamableTransportFactory.php index e64cbe1..cdd8527 100644 --- a/src/Http/StreamableTransportFactory.php +++ b/src/Http/StreamableTransportFactory.php @@ -3,11 +3,13 @@ namespace Contributte\Mcp\Http; use Contributte\Mcp\Exception\LogicalException; +use Mcp\Server\Transport\Http\Middleware\DnsRebindingProtectionMiddleware; use Mcp\Server\Transport\StreamableHttpTransport; use Mcp\Server\Transport\TransportInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Server\MiddlewareInterface; use Psr\Log\LoggerInterface; final class StreamableTransportFactory implements TransportFactoryInterface @@ -36,9 +38,19 @@ public function create(mixed ...$args): TransportInterface $serverRequest, $this->responseFactory, $this->streamFactory, - [], $this->logger, + $this->createMiddleware(), ); } + /** + * @return list + */ + private function createMiddleware(): array + { + $middleware = StreamableHttpTransport::defaultMiddleware(); + + return array_values(array_filter($middleware, static fn ($item) => !$item instanceof DnsRebindingProtectionMiddleware)); + } + } diff --git a/src/Registry/TraceableRegistry.php b/src/Registry/TraceableRegistry.php index e8d0dbe..e625c0f 100644 --- a/src/Registry/TraceableRegistry.php +++ b/src/Registry/TraceableRegistry.php @@ -2,7 +2,6 @@ namespace Contributte\Mcp\Registry; -use Mcp\Capability\Discovery\DiscoveryState; use Mcp\Capability\Registry\PromptReference; use Mcp\Capability\Registry\ResourceReference; use Mcp\Capability\Registry\ResourceTemplateReference; @@ -10,7 +9,7 @@ use Mcp\Capability\RegistryInterface; use Mcp\Schema\Page; use Mcp\Schema\Prompt; -use Mcp\Schema\Resource; +use Mcp\Schema\ResourceDefinition; use Mcp\Schema\ResourceTemplate; use Mcp\Schema\Tool; @@ -39,14 +38,14 @@ public function getCalls(): array return $this->calls; } - public function registerTool(Tool $tool, callable|array|string $handler, bool $isManual = false): void + public function registerTool(Tool $tool, callable|array|string $handler): ToolReference { - $this->registry->registerTool($tool, $handler, $isManual); + return $this->registry->registerTool($tool, $handler); } - public function registerResource(Resource $resource, callable|array|string $handler, bool $isManual = false): void + public function registerResource(ResourceDefinition $resource, callable|array|string $handler): ResourceReference { - $this->registry->registerResource($resource, $handler, $isManual); + return $this->registry->registerResource($resource, $handler); } /** @@ -56,10 +55,9 @@ public function registerResourceTemplate( ResourceTemplate $template, callable|array|string $handler, array $completionProviders = [], - bool $isManual = false, - ): void + ): ResourceTemplateReference { - $this->registry->registerResourceTemplate($template, $handler, $completionProviders, $isManual); + return $this->registry->registerResourceTemplate($template, $handler, $completionProviders); } /** @@ -69,25 +67,49 @@ public function registerPrompt( Prompt $prompt, callable|array|string $handler, array $completionProviders = [], - bool $isManual = false, - ): void + ): PromptReference { - $this->registry->registerPrompt($prompt, $handler, $completionProviders, $isManual); + return $this->registry->registerPrompt($prompt, $handler, $completionProviders); } - public function clear(): void + public function unregisterTool(string $name): void { - $this->registry->clear(); + $this->registry->unregisterTool($name); } - public function getDiscoveryState(): DiscoveryState + public function unregisterResource(string $uri): void { - return $this->registry->getDiscoveryState(); + $this->registry->unregisterResource($uri); } - public function setDiscoveryState(DiscoveryState $state): void + public function unregisterResourceTemplate(string $uriTemplate): void { - $this->registry->setDiscoveryState($state); + $this->registry->unregisterResourceTemplate($uriTemplate); + } + + public function unregisterPrompt(string $name): void + { + $this->registry->unregisterPrompt($name); + } + + public function hasTool(string $name): bool + { + return $this->registry->hasTool($name); + } + + public function hasResource(string $uri): bool + { + return $this->registry->hasResource($uri); + } + + public function hasResourceTemplate(string $uriTemplate): bool + { + return $this->registry->hasResourceTemplate($uriTemplate); + } + + public function hasPrompt(string $name): bool + { + return $this->registry->hasPrompt($name); } public function hasTools(): bool diff --git a/src/Tracy/McpPanel.php b/src/Tracy/McpPanel.php index 498db26..9375a1c 100644 --- a/src/Tracy/McpPanel.php +++ b/src/Tracy/McpPanel.php @@ -34,7 +34,7 @@ public function getTab(): string ob_start(); require __DIR__ . '/templates/tab.phtml'; - return (string) ob_get_clean(); + return ob_get_clean(); } public function getPanel(): string @@ -52,7 +52,7 @@ public function getPanel(): string ob_start(); require __DIR__ . '/templates/panel.phtml'; - return (string) ob_get_clean(); + return ob_get_clean(); } } diff --git a/tests/Cases/Tracy/TraceableRegistry.phpt b/tests/Cases/Tracy/TraceableRegistry.phpt index ae948f8..d7e6c7c 100644 --- a/tests/Cases/Tracy/TraceableRegistry.phpt +++ b/tests/Cases/Tracy/TraceableRegistry.phpt @@ -71,6 +71,7 @@ Toolkit::test(function (): void { $tool = new Tool( name: 'test-tool', + title: null, inputSchema: ['type' => 'object', 'properties' => [], 'required' => []], description: 'A test tool', annotations: null, diff --git a/tests/Toolkit/ServerInspector.php b/tests/Toolkit/ServerInspector.php index 067315c..400aa48 100644 --- a/tests/Toolkit/ServerInspector.php +++ b/tests/Toolkit/ServerInspector.php @@ -4,7 +4,7 @@ use Mcp\Capability\RegistryInterface; use Mcp\Schema\Prompt; -use Mcp\Schema\Resource; +use Mcp\Schema\ResourceDefinition; use Mcp\Schema\Tool; use Mcp\Server; use Mcp\Server\Configuration; @@ -43,7 +43,7 @@ public static function hasTools(Server $server): bool } /** - * @return array + * @return array */ public static function getResources(Server $server): array {