Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ For details on how to use this package, check out our [documentation](.docs).

## Versions

| State | Version | Branch | Nette | PHP |
|-------------|---------|----------|--------|---------|
| dev | `^0.1` | `master` | `3.2+` | `>=8.4` |
| State | Version | Branch | mcp/sdk | Nette | PHP |
|-------------|---------|----------|------------|--------|---------|
| dev | `^0.3` | `master` | `^0.8` | `3.2+` | `>=8.4` |
| stable | `^0.2` | `master` | `dev-main` | `3.2+` | `>=8.4` |

## Development

Expand Down
30 changes: 18 additions & 12 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
{
"name": "contributte/mcp",
"description": "Doctrine DBAL for Nette Framework",
"description": "Model Context Protocol (MCP) server integration for Nette Framework",
"keywords": [
"nette",
"doctrine",
"dbal",
"database",
"mysql",
"postgres",
"sqlite"
"mcp",
"model-context-protocol",
"ai",
"llm",
"server"
],
"type": "library",
"license": "MIT",
"homepage": "https://github.com/contributte/doctrine-dbal",
"homepage": "https://github.com/contributte/mcp",
"authors": [
{
"name": "Milan Felix Šulc",
Expand All @@ -22,10 +21,12 @@
"require": {
"php": ">=8.4",
"guzzlehttp/guzzle": "^7.10.0",
"psr/container": "^2.0.2",
"mcp/sdk": "^0.8.1",
"nette/di": "^3.2.5",
"psr/cache": "^3.0.0",
"psr/container": "^2.0.2",
"psr/simple-cache": "^3.0.0",
"mcp/sdk": "dev-main#369932378f7bcfa29a00efc1530b70c4d501a24b"
"symfony/finder": "^7.4 || ^8.0"
},
"require-dev": {
"ext-pdo": "*",
Expand All @@ -34,11 +35,16 @@
"contributte/tester": "^0.5.0",
"mockery/mockery": "^1.6.12",
"nette/application": "^3.2.8",
"nette/di": "^3.2.5",
"nette/http": "^3.3.3",
"symfony/console": "^7.4.1 || ^8.0.1",
"tracy/tracy": "^2.11.0"
},
"suggest": {
"nette/application": "Required for McpPresenter and McpRouter (HTTP transport)",
"nette/http": "Required for McpPresenter and GuzzleBridge (HTTP transport)",
"symfony/console": "Required for McpCommand (stdio transport)",
"tracy/tracy": "Required for the Tracy debug panel"
},
"autoload": {
"psr-4": {
"Contributte\\Mcp\\": "src/"
Expand All @@ -63,7 +69,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
"dev-master": "0.3.x-dev"
}
}
}
5 changes: 5 additions & 0 deletions src/DI/Helpers/BuilderMan.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Contributte\Mcp\DI\Helpers;

use Contributte\Mcp\Exception\LogicalException;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\Definition;
use Nette\DI\Definitions\Reference;
Expand Down Expand Up @@ -31,6 +32,10 @@ public function getServiceDefinitionsByTag(string $tag): array
$definitions = [];

foreach ($builder->findByTag($tag) as $serviceName => $tagValue) {
if (!is_string($tagValue) && !is_int($tagValue)) {
throw new LogicalException(sprintf('Tag "%s" of service "%s" must have a string value', $tag, $serviceName));
}

$definitions[(string) $tagValue] = $builder->getDefinition($serviceName);
}

Expand Down
7 changes: 4 additions & 3 deletions src/DI/McpExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -178,7 +178,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)
Expand All @@ -198,7 +199,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;
Expand Down
2 changes: 1 addition & 1 deletion src/Http/GuzzleBridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 0 additions & 1 deletion src/Http/StreamableTransportFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public function create(mixed ...$args): TransportInterface
$serverRequest,
$this->responseFactory,
$this->streamFactory,
[],
$this->logger,
);
}
Expand Down
58 changes: 40 additions & 18 deletions src/Registry/TraceableRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

namespace Contributte\Mcp\Registry;

use Mcp\Capability\Discovery\DiscoveryState;
use Mcp\Capability\Registry\PromptReference;
use Mcp\Capability\Registry\ResourceReference;
use Mcp\Capability\Registry\ResourceTemplateReference;
use Mcp\Capability\Registry\ToolReference;
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;

Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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
Expand Down
13 changes: 12 additions & 1 deletion tests/Cases/Tracy/TraceableRegistry.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Tests\Cases\Tracy;
use Contributte\Mcp\Registry\TraceableRegistry;
use Contributte\Tester\Toolkit;
use Mcp\Capability\Registry;
use Mcp\Capability\Registry\ToolReference;
use Mcp\Capability\RegistryInterface;
use Mcp\Schema\Page;
use Mcp\Schema\Tool;
Expand Down Expand Up @@ -71,14 +72,24 @@ Toolkit::test(function (): void {

$tool = new Tool(
name: 'test-tool',
title: null,
inputSchema: ['type' => 'object', 'properties' => [], 'required' => []],
description: 'A test tool',
annotations: null,
);
$traceableRegistry->registerTool($tool, fn () => 'result');
$reference = $traceableRegistry->registerTool($tool, fn () => 'result');

Assert::type(ToolReference::class, $reference);
Assert::same($tool, $reference->tool);
Assert::true($traceableRegistry->hasTools());
Assert::true($innerRegistry->hasTools());
Assert::true($traceableRegistry->hasTool('test-tool'));
Assert::false($traceableRegistry->hasTool('missing-tool'));

$traceableRegistry->unregisterTool('test-tool');

Assert::false($traceableRegistry->hasTool('test-tool'));
Assert::false($innerRegistry->hasTool('test-tool'));
});

// Test: TraceableRegistry accumulates multiple calls
Expand Down
4 changes: 2 additions & 2 deletions tests/Toolkit/ServerInspector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,7 +43,7 @@ public static function hasTools(Server $server): bool
}

/**
* @return array<string, Resource>
* @return array<string, ResourceDefinition>
*/
public static function getResources(Server $server): array
{
Expand Down
Loading