Skip to content

Commit 90bd497

Browse files
committed
Add RequestContext
1 parent 06aec0e commit 90bd497

37 files changed

+255
-1519
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"Mcp\\Example\\EnvVariables\\": "examples/env-variables/",
6464
"Mcp\\Example\\ExplicitRegistration\\": "examples/explicit-registration/",
6565
"Mcp\\Example\\SchemaShowcase\\": "examples/schema-showcase/",
66-
"Mcp\\Example\\StdioLoggingShowcase\\": "examples/stdio-logging-showcase/",a
66+
"Mcp\\Example\\ClientLogging\\": "examples/client-logging/",
6767
"Mcp\\Tests\\": "tests/"
6868
}
6969
},

docs/mcp-elements.md

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -507,32 +507,21 @@ public function generatePrompt(string $topic, string $style): array
507507

508508
## Logging
509509

510-
The SDK provides automatic logging support, handlers can receive logger instances automatically to send structured log messages to clients.
510+
The SDK provides support to send structured log messages to clients. All standard PSR-3 log levels are supported.
511+
Level **warning** as the default level.
511512

512-
### Configuration
513+
### Usage
513514

514-
Logging is **enabled by default**. Use `disableClientLogging()` to turn it off:
515-
516-
```php
517-
// Logging enabled (default)
518-
$server = Server::builder()->build();
519-
520-
// Disable logging
521-
$server = Server::builder()
522-
->disableClientLogging()
523-
->build();
524-
```
525-
526-
### Auto-injection
527-
528-
The SDK automatically injects logger instances into handlers:
515+
The SDK automatically injects a `RequestContext` instance into handlers. This can be used to create a `ClientLogger`.
529516

530517
```php
531518
use Mcp\Capability\Logger\ClientLogger;
532519
use Psr\Log\LoggerInterface;
533520

534521
#[McpTool]
535-
public function processData(string $input, ClientLogger $logger): array {
522+
public function processData(string $input, RequestContext $context): array {
523+
$logger = $context->getClientLogger();
524+
536525
$logger->info('Processing started', ['input' => $input]);
537526
$logger->warning('Deprecated API used');
538527

@@ -541,19 +530,8 @@ public function processData(string $input, ClientLogger $logger): array {
541530
$logger->info('Processing completed');
542531
return ['result' => 'processed'];
543532
}
544-
545-
// Also works with PSR-3 LoggerInterface
546-
#[McpResource(uri: 'data://config')]
547-
public function getConfig(LoggerInterface $logger): array {
548-
$logger->info('Configuration accessed');
549-
return ['setting' => 'value'];
550-
}
551533
```
552534

553-
### Log Levels
554-
555-
The SDK supports all standard PSR-3 log levels with **warning** as the default level:
556-
557535
## Completion Providers
558536

559537
Completion providers help MCP clients offer auto-completion suggestions for Resource Templates and Prompts. Unlike Tools and static Resources (which can be listed via `tools/list` and `resources/list`), Resource Templates and Prompts have dynamic parameters that benefit from completion hints.

docs/server-builder.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,6 @@ $server = Server::builder()
577577
| `addRequestHandlers()` | handlers | Prepend multiple custom request handlers |
578578
| `addNotificationHandler()` | handler | Prepend a single custom notification handler |
579579
| `addNotificationHandlers()` | handlers | Prepend multiple custom notification handlers |
580-
| `disableClientLogging()` | - | Disable MCP client logging (enabled by default) |
581580
| `addTool()` | handler, name?, description?, annotations?, inputSchema? | Register tool |
582581
| `addResource()` | handler, uri, name?, description?, mimeType?, size?, annotations? | Register resource |
583582
| `addResourceTemplate()` | handler, uriTemplate, name?, description?, mimeType?, annotations? | Register resource template |

examples/stdio-logging-showcase/LoggingShowcaseHandlers.php renamed to examples/client-logging/LoggingShowcaseHandlers.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Mcp\Example\StdioLoggingShowcase;
12+
namespace Mcp\Example\ClientLogging;
1313

1414
use Mcp\Capability\Attribute\McpTool;
1515
use Mcp\Capability\Logger\ClientLogger;
16+
use Mcp\Server\RequestContext;
1617

1718
/**
1819
* Example handlers showcasing auto-injected MCP logging capabilities.
@@ -23,17 +24,17 @@
2324
final class LoggingShowcaseHandlers
2425
{
2526
/**
26-
* Tool that demonstrates different logging levels with auto-injected ClientLogger.
27+
* Tool that demonstrates different logging levels.
2728
*
28-
* @param string $message The message to log
29-
* @param string $level The logging level (debug, info, warning, error)
30-
* @param ClientLogger $logger Auto-injected MCP logger
29+
* @param string $message The message to log
30+
* @param string $level The logging level (debug, info, warning, error)
3131
*
3232
* @return array<string, mixed>
3333
*/
3434
#[McpTool(name: 'log_message', description: 'Demonstrates MCP logging with different levels')]
35-
public function logMessage(string $message, string $level, ClientLogger $logger): array
35+
public function logMessage(RequestContext $context, string $message, string $level): array
3636
{
37+
$logger = $context->getClientLogger();
3738
$logger->info('🚀 Starting log_message tool', [
3839
'requested_level' => $level,
3940
'message_length' => \strlen($message),

examples/client-logging/server.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/*
5+
* This file is part of the official PHP MCP SDK.
6+
*
7+
* A collaboration between Symfony and the PHP Foundation.
8+
*
9+
* For the full copyright and license information, please view the LICENSE
10+
* file that was distributed with this source code.
11+
*/
12+
13+
require_once dirname(__DIR__).'/bootstrap.php';
14+
chdir(__DIR__);
15+
16+
use Mcp\Server;
17+
18+
$server = Server::builder()
19+
->setServerInfo('Client Logging', '1.0.0', 'Demonstration of MCP logging in capability handlers.')
20+
->setContainer(container())
21+
->setLogger(logger())
22+
->setDiscovery(__DIR__)
23+
->build();
24+
25+
$result = $server->run(transport());
26+
27+
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
28+
29+
shutdown($result);

examples/stdio-logging-showcase/server.php

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/Capability/Discovery/SchemaGenerator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Mcp\Capability\Attribute\Schema;
1515
use Mcp\Server\ClientGateway;
16+
use Mcp\Server\RequestContext;
1617
use phpDocumentor\Reflection\DocBlock\Tags\Param;
1718

1819
/**
@@ -415,7 +416,7 @@ private function parseParametersInfo(\ReflectionMethod|\ReflectionFunction $refl
415416
if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) {
416417
$typeName = $reflectionType->getName();
417418

418-
if (is_a($typeName, ClientGateway::class, true)) {
419+
if (is_a($typeName, ClientGateway::class, true) || is_a($typeName, RequestContext::class, true)) {
419420
continue;
420421
}
421422
}

src/Capability/Logger/ClientLogger.php

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,22 @@
1212
namespace Mcp\Capability\Logger;
1313

1414
use Mcp\Schema\Enum\LoggingLevel;
15-
use Mcp\Server\NotificationSender;
15+
use Mcp\Server\ClientGateway;
16+
use Mcp\Server\Protocol;
17+
use Mcp\Server\Session\SessionInterface;
1618
use Psr\Log\AbstractLogger;
17-
use Psr\Log\LoggerInterface;
1819

1920
/**
2021
* MCP-aware PSR-3 logger that sends log messages as MCP notifications.
2122
*
22-
* This logger implements the standard PSR-3 LoggerInterface and forwards
23-
* log messages to the NotificationSender. The NotificationHandler will
24-
* decide whether to actually send the notification based on capabilities.
25-
*
2623
* @author Adam Jamiu <jamiuadam120@gmail.com>
24+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
2725
*/
2826
final class ClientLogger extends AbstractLogger
2927
{
3028
public function __construct(
31-
private readonly NotificationSender $notificationSender,
32-
private readonly ?LoggerInterface $fallbackLogger = null,
29+
private ClientGateway $client,
30+
private SessionInterface $session,
3331
) {
3432
}
3533

@@ -41,30 +39,20 @@ public function __construct(
4139
*/
4240
public function log($level, $message, array $context = []): void
4341
{
44-
// Always log to fallback logger if provided (for local debugging)
45-
$this->fallbackLogger?->log($level, $message, $context);
46-
4742
// Convert PSR-3 level to MCP LoggingLevel
4843
$mcpLevel = $this->convertToMcpLevel($level);
4944
if (null === $mcpLevel) {
5045
return; // Unknown level, skip MCP notification
5146
}
5247

53-
// Send MCP logging notification - let NotificationHandler decide if it should be sent
54-
try {
55-
$this->notificationSender->send('notifications/message', [
56-
'level' => $mcpLevel->value,
57-
'data' => (string) $message,
58-
'logger' => $context['logger'] ?? null,
59-
]);
60-
} catch (\Throwable $e) {
61-
// If MCP notification fails, at least log to fallback
62-
$this->fallbackLogger?->error('Failed to send MCP log notification', [
63-
'original_level' => $level,
64-
'original_message' => $message,
65-
'error' => $e->getMessage(),
66-
]);
48+
$minimumLevel = $this->session->get(Protocol::SESSION_LOGGING_LEVEL, '');
49+
$minimumLevel = LoggingLevel::tryFrom($minimumLevel) ?? LoggingLevel::Warning;
50+
51+
if ($this->getSeverityIndex($minimumLevel) > $this->getSeverityIndex($mcpLevel)) {
52+
return;
6753
}
54+
55+
$this->client->log($mcpLevel, $message);
6856
}
6957

7058
/**
@@ -88,4 +76,24 @@ private function convertToMcpLevel($level): ?LoggingLevel
8876
default => null,
8977
};
9078
}
79+
80+
/**
81+
* Gets the severity index for this log level.
82+
* Higher values indicate more severe log levels.
83+
*
84+
* @return int Severity index (0-7, where 7 is most severe)
85+
*/
86+
private function getSeverityIndex(LoggingLevel $level): int
87+
{
88+
return match ($level) {
89+
LoggingLevel::Debug => 0,
90+
LoggingLevel::Info => 1,
91+
LoggingLevel::Notice => 2,
92+
LoggingLevel::Warning => 3,
93+
LoggingLevel::Error => 4,
94+
LoggingLevel::Critical => 5,
95+
LoggingLevel::Alert => 6,
96+
LoggingLevel::Emergency => 7,
97+
};
98+
}
9199
}

src/Capability/Registry.php

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use Mcp\Exception\PromptNotFoundException;
2626
use Mcp\Exception\ResourceNotFoundException;
2727
use Mcp\Exception\ToolNotFoundException;
28-
use Mcp\Schema\Enum\LoggingLevel;
2928
use Mcp\Schema\Page;
3029
use Mcp\Schema\Prompt;
3130
use Mcp\Schema\Resource;
@@ -62,10 +61,6 @@ final class Registry implements RegistryInterface
6261
*/
6362
private array $resourceTemplates = [];
6463

65-
private bool $logging = true;
66-
67-
private LoggingLevel $loggingLevel = LoggingLevel::Warning;
68-
6964
public function __construct(
7065
private readonly ?EventDispatcherInterface $eventDispatcher = null,
7166
private readonly LoggerInterface $logger = new NullLogger(),
@@ -396,46 +391,6 @@ public function setDiscoveryState(DiscoveryState $state): void
396391
}
397392
}
398393

399-
400-
/**
401-
* Disable logging message notifications for this registry.
402-
*/
403-
public function disableLogging(): void
404-
{
405-
$this->logging = false;
406-
}
407-
408-
/**
409-
* Checks if logging message notification capability is enabled.
410-
*
411-
* @return bool True if logging capability is enabled, false otherwise
412-
*/
413-
public function isLoggingEnabled(): bool
414-
{
415-
return $this->logging;
416-
}
417-
418-
/**
419-
* Sets the current logging message notification level for the client.
420-
*
421-
* This determines which log messages should be sent to the client.
422-
* Only messages at this level and higher (more severe) will be sent.
423-
*/
424-
public function setLoggingLevel(LoggingLevel $level): void
425-
{
426-
$this->loggingLevel = $level;
427-
}
428-
429-
/**
430-
* Gets the current logging message notification level set by the client.
431-
*
432-
* @return LoggingLevel The current log level
433-
*/
434-
public function getLoggingLevel(): LoggingLevel
435-
{
436-
return $this->loggingLevel;
437-
}
438-
439394
/**
440395
* Calculate next cursor for pagination.
441396
*

src/Capability/Registry/ReferenceHandler.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Mcp\Exception\RegistryException;
1616
use Mcp\Server\ClientAwareInterface;
1717
use Mcp\Server\ClientGateway;
18+
use Mcp\Server\RequestContext;
1819
use Mcp\Server\Session\SessionInterface;
1920
use Psr\Container\ContainerInterface;
2021

@@ -109,9 +110,15 @@ private function prepareArguments(\ReflectionFunctionAbstract $reflection, array
109110
$typeName = $type->getName();
110111

111112
if (ClientGateway::class === $typeName && isset($arguments['_session'])) {
113+
// Deprecated, use RequestContext instead
112114
$finalArgs[$paramPosition] = new ClientGateway($arguments['_session']);
113115
continue;
114116
}
117+
118+
if (RequestContext::class === $typeName && isset($arguments['_session'], $arguments['_request'])) {
119+
$finalArgs[$paramPosition] = new RequestContext($arguments['_session'], $arguments['_request']);
120+
continue;
121+
}
115122
}
116123

117124
if (isset($arguments[$paramName])) {

0 commit comments

Comments
 (0)