Skip to content
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,31 @@ $server = Server::builder()
->build();
```

### Logging

Automatically inject PSR-3 compatible logger into your registered handlers:

```php
// Enable logging in server
$server = Server::builder()
->enableClientLogging()
->build();

// Use in any handler - logger is auto-injected
#[McpTool]
public function processData(string $input, ClientLogger $logger): array {
$logger->info('Processing data', ['input' => $input]);
return ['result' => 'processed'];
}

// Also works with PSR-3 LoggerInterface
#[McpResource(uri: 'data://config')]
public function getConfig(LoggerInterface $logger): array {
$logger->info('Config accessed');
return ['setting' => 'value'];
}
```

## Documentation

**Core Concepts:**
Expand All @@ -239,6 +264,7 @@ $server = Server::builder()
**Learning:**
- [Examples](docs/examples.md) - Comprehensive example walkthroughs


**External Resources:**
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"Mcp\\Example\\StdioDiscoveryCalculator\\": "examples/stdio-discovery-calculator/",
"Mcp\\Example\\StdioEnvVariables\\": "examples/stdio-env-variables/",
"Mcp\\Example\\StdioExplicitRegistration\\": "examples/stdio-explicit-registration/",
"Mcp\\Example\\StdioLoggingShowcase\\": "examples/stdio-logging-showcase/",
"Mcp\\Tests\\": "tests/"
}
},
Expand Down
72 changes: 62 additions & 10 deletions examples/stdio-discovery-calculator/McpElements.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Logger\ClientLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

Expand Down Expand Up @@ -40,14 +41,15 @@ public function __construct(
* Supports 'add', 'subtract', 'multiply', 'divide'.
* Obeys the 'precision' and 'allow_negative' settings from the config resource.
*
* @param float $a the first operand
* @param float $b the second operand
* @param string $operation the operation ('add', 'subtract', 'multiply', 'divide')
* @param float $a the first operand
* @param float $b the second operand
* @param string $operation the operation ('add', 'subtract', 'multiply', 'divide')
* @param ClientLogger $logger Auto-injected MCP logger
*
* @return float|string the result of the calculation, or an error message string
*/
#[McpTool(name: 'calculate')]
public function calculate(float $a, float $b, string $operation): float|string
public function calculate(float $a, float $b, string $operation, ClientLogger $logger): float|string
{
$this->logger->info(\sprintf('Calculating: %f %s %f', $a, $operation, $b));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could remain to be a server-side log message


Expand All @@ -65,25 +67,48 @@ public function calculate(float $a, float $b, string $operation): float|string
break;
case 'divide':
if (0 == $b) {
$logger->warning('Division by zero attempted', [
'operand_a' => $a,
'operand_b' => $b,
]);

return 'Error: Division by zero.';
}
$result = $a / $b;
break;
default:
$logger->error('Unknown operation requested', [
'operation' => $operation,
'supported_operations' => ['add', 'subtract', 'multiply', 'divide'],
]);

return "Error: Unknown operation '{$operation}'. Supported: add, subtract, multiply, divide.";
}

if (!$this->config['allow_negative'] && $result < 0) {
$logger->warning('Negative result blocked by configuration', [
'result' => $result,
'allow_negative_setting' => false,
]);

return 'Error: Negative results are disabled.';
}

return round($result, $this->config['precision']);
$finalResult = round($result, $this->config['precision']);
$logger->info('✅ Calculation completed successfully', [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$logger->info('Calculation completed successfully', [
$logger->info('Calculation completed successfully', [

'result' => $finalResult,
'precision' => $this->config['precision'],
]);

return $finalResult;
}

/**
* Provides the current calculator configuration.
* Can be read by clients to understand precision etc.
*
* @param ClientLogger $logger Auto-injected MCP logger for demonstration
*
* @return Config the configuration array
*/
#[McpResource(
Expand All @@ -92,9 +117,12 @@ public function calculate(float $a, float $b, string $operation): float|string
description: 'Current settings for the calculator tool (precision, allow_negative).',
mimeType: 'application/json',
)]
public function getConfiguration(): array
public function getConfiguration(ClientLogger $logger): array
{
$this->logger->info('Resource config://calculator/settings read.');
$logger->info('📊 Resource config://calculator/settings accessed via auto-injection!', [
'current_config' => $this->config,
'auto_injection_demo' => 'ClientLogger was automatically injected into this resource handler',
]);

return $this->config;
}
Expand All @@ -103,8 +131,9 @@ public function getConfiguration(): array
* Updates a specific configuration setting.
* Note: This requires more robust validation in a real app.
*
* @param string $setting the setting key ('precision' or 'allow_negative')
* @param mixed $value the new value (int for precision, bool for allow_negative)
* @param string $setting the setting key ('precision' or 'allow_negative')
* @param mixed $value the new value (int for precision, bool for allow_negative)
* @param ClientLogger $logger Auto-injected MCP logger
*
* @return array{
* success: bool,
Expand All @@ -113,18 +142,32 @@ public function getConfiguration(): array
* } success message or error
*/
#[McpTool(name: 'update_setting')]
public function updateSetting(string $setting, mixed $value): array
public function updateSetting(string $setting, mixed $value, ClientLogger $logger): array
{
$this->logger->info(\sprintf('Setting tool called: setting=%s, value=%s', $setting, var_export($value, true)));
if (!\array_key_exists($setting, $this->config)) {
$logger->error('Unknown setting requested', [
'setting' => $setting,
'available_settings' => array_keys($this->config),
]);

return ['success' => false, 'error' => "Unknown setting '{$setting}'."];
}

if ('precision' === $setting) {
if (!\is_int($value) || $value < 0 || $value > 10) {
$logger->warning('Invalid precision value provided', [
'value' => $value,
'valid_range' => '0-10',
]);

return ['success' => false, 'error' => 'Invalid precision value. Must be integer between 0 and 10.'];
}
$this->config['precision'] = $value;
$logger->info('✅ Precision setting updated', [
'new_precision' => $value,
'previous_config' => $this->config,
]);

// In real app, notify subscribers of config://calculator/settings change
// $registry->notifyResourceChanged('config://calculator/settings');
Expand All @@ -138,10 +181,19 @@ public function updateSetting(string $setting, mixed $value): array
} elseif (\in_array(strtolower((string) $value), ['false', '0', 'no', 'off'])) {
$value = false;
} else {
$logger->warning('Invalid allow_negative value provided', [
'value' => $value,
'expected_type' => 'boolean',
]);

return ['success' => false, 'error' => 'Invalid allow_negative value. Must be boolean (true/false).'];
}
}
$this->config['allow_negative'] = $value;
$logger->info('✅ Allow negative setting updated', [
'new_allow_negative' => $value,
'updated_config' => $this->config,
]);

// $registry->notifyResourceChanged('config://calculator/settings');
return ['success' => true, 'message' => 'Allow negative results set to '.($value ? 'true' : 'false').'.'];
Expand Down
1 change: 1 addition & 0 deletions examples/stdio-discovery-calculator/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
->setInstructions('This server supports basic arithmetic operations: add, subtract, multiply, and divide. Send JSON-RPC requests to perform calculations.')
->setContainer(container())
->setLogger(logger())
->enableClientLogging() // Enable Client logging capability and auto-injection!
->setDiscovery(__DIR__, ['.'])
->build();

Expand Down
80 changes: 80 additions & 0 deletions examples/stdio-logging-showcase/LoggingShowcaseHandlers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Mcp\Example\StdioLoggingShowcase;

use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Logger\ClientLogger;

/**
* Example handlers showcasing auto-injected MCP logging capabilities.
*
* This demonstrates how handlers can receive ClientLogger automatically
* without any manual configuration - just declare it as a parameter!
*/
final class LoggingShowcaseHandlers
{
/**
* Tool that demonstrates different logging levels with auto-injected ClientLogger.
*
* @param string $message The message to log
* @param string $level The logging level (debug, info, warning, error)
* @param ClientLogger $logger Auto-injected MCP logger
*
* @return array<string, mixed>
*/
#[McpTool(name: 'log_message', description: 'Demonstrates MCP logging with different levels')]
public function logMessage(string $message, string $level, ClientLogger $logger): array
{
$logger->info('🚀 Starting log_message tool', [
'requested_level' => $level,
'message_length' => \strlen($message),
]);

switch (strtolower($level)) {
case 'debug':
$logger->debug("Debug: $message", ['tool' => 'log_message']);
break;
case 'info':
$logger->info("Info: $message", ['tool' => 'log_message']);
break;
case 'notice':
$logger->notice("Notice: $message", ['tool' => 'log_message']);
break;
case 'warning':
$logger->warning("Warning: $message", ['tool' => 'log_message']);
break;
case 'error':
$logger->error("Error: $message", ['tool' => 'log_message']);
break;
case 'critical':
$logger->critical("Critical: $message", ['tool' => 'log_message']);
break;
case 'alert':
$logger->alert("Alert: $message", ['tool' => 'log_message']);
break;
case 'emergency':
$logger->emergency("Emergency: $message", ['tool' => 'log_message']);
break;
default:
$logger->warning("Unknown level '$level', defaulting to info");
$logger->info("Info: $message", ['tool' => 'log_message']);
}

$logger->debug('log_message tool completed successfully');

return [
'message' => "Logged message with level: $level",
'logged_at' => date('Y-m-d H:i:s'),
'level_used' => $level,
];
}
}
35 changes: 35 additions & 0 deletions examples/stdio-logging-showcase/server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env php
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

require_once dirname(__DIR__).'/bootstrap.php';
chdir(__DIR__);

use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;

logger()->info('Starting MCP Stdio Logging Showcase Server...');

// Create server with auto-discovery of MCP capabilities and ENABLE MCP LOGGING
$server = Server::builder()
->setServerInfo('Stdio Logging Showcase', '1.0.0', 'Demonstration of auto-injected MCP logging in capability handlers.')
->setContainer(container())
->setLogger(logger())
->enableClientLogging() // Enable MCP logging capability and auto-injection!
->setDiscovery(__DIR__, ['.'])
->build();

$transport = new StdioTransport(logger: logger());

$server->run($transport);

logger()->info('Logging Showcase Server is ready!');
logger()->info('This example demonstrates auto-injection of ClientLogger into capability handlers.');
25 changes: 25 additions & 0 deletions src/Capability/Discovery/SchemaGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ private function parseParametersInfo(\ReflectionMethod|\ReflectionFunction $refl
$parametersInfo = [];

foreach ($reflection->getParameters() as $rp) {
if ($this->isAutoInjectedParameter($rp)) {
continue;
}

$paramName = $rp->getName();
$paramTag = $paramTags['$'.$paramName] ?? null;

Expand Down Expand Up @@ -784,4 +788,25 @@ private function mapSimpleTypeToJsonSchema(string $type): string
default => \in_array(strtolower($type), ['datetime', 'datetimeinterface']) ? 'string' : 'object',
};
}

/**
* Determines if a parameter was auto-injected and should be excluded from schema generation.
*
* Parameters that are auto-injected by the framework (like ClientLogger) should not appear
* in the tool schema since they're not provided by the client.
*/
private function isAutoInjectedParameter(\ReflectionParameter $parameter): bool
{
$type = $parameter->getType();

if (!$type instanceof \ReflectionNamedType) {
return false;
}

$typeName = $type->getName();

// Auto-inject for ClientLogger or LoggerInterface types
return 'Mcp\\Capability\\Logger\\ClientLogger' === $typeName
|| 'Psr\\Log\\LoggerInterface' === $typeName;
}
}
Loading