Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to `mcp/sdk` will be documented in this file.
* Add `HttpTransport::getSessionId()` to read the server-minted `Mcp-Session-Id`: a request-scoped caller can persist it and pass it back through the constructor's `$headers` on a later transport. Always `null` on `2026-07-28`, which removed protocol-level sessions.
* Fix OIDC discovery rejecting issuers with a trailing slash (e.g. Authentik, Auth0).
* Fix stateless SSE streams holding back frames until close when PHP output buffering is enabled.
* Log the tool name of a `tools/call` at info level in `CallToolHandler`, so a server logging at INFO shows which tool is called; its arguments stay at debug level.
* Reject a recognized `Mcp-Param-*` header whose mirrored argument is absent from the body with `-32020`, instead of accepting the request (SEP-2243).
* Fix `JwtTokenValidator` with several issuers always fetching the keys of the first one: keys now come from the issuer the token claims, which must be configured.
* Fix `RequestEvent`, `ResponseEvent` and `ErrorEvent` not being dispatched for `2026-07-28` requests.
Expand Down
2 changes: 2 additions & 0 deletions src/Server/Handler/Request/CallToolHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public function handle(Request $request, SessionInterface $session): Response|Er
$toolName = $request->name;
$arguments = $request->arguments;

// The arguments may carry sensitive input, so only the name is logged at info level.
$this->logger->info('Calling tool', ['name' => $toolName]);
$this->logger->debug('Executing tool', ['name' => $toolName, 'arguments' => $arguments]);

try {
Expand Down
39 changes: 39 additions & 0 deletions tests/Unit/Server/Handler/Request/CallToolHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,45 @@ public function log($level, $message, array $context = []): void
)));
}

public function testToolNameIsLoggedAtInfoLevelAndArgumentsOnlyAtDebugLevel(): void
{
$request = $this->createCallToolRequest('login', ['password' => 's3cr3t-argument']);
$logger = new class extends AbstractLogger {
public array $records = [];

// @phpstan-ignore missingType.parameter (compatible with psr/log 1.x)
public function log($level, $message, array $context = []): void
{
$this->records[] = ['level' => $level, 'message' => (string) $message, 'context' => $context];
}
};
$handler = new CallToolHandler($this->registry, $this->referenceHandler, $logger);
$toolReference = $this->createToolReference('login', static fn () => 'ok');

$this->registry->method('getTool')->willReturn($toolReference);
$this->referenceHandler->method('handle')->willReturn('ok');
$toolReference->method('formatResult')->willReturn([new TextContent('ok')]);

$handler->handle($request, $this->session);

$infoRecords = array_values(array_filter(
$logger->records,
static fn (array $record): bool => 'info' === $record['level'],
));
$this->assertSame([
['level' => 'info', 'message' => 'Calling tool', 'context' => ['name' => 'login']],
], $infoRecords);

$recordsWithArguments = array_values(array_filter(
$logger->records,
static fn (array $record): bool => \array_key_exists('arguments', $record['context']),
));
$this->assertNotSame([], $recordsWithArguments);
foreach ($recordsWithArguments as $record) {
$this->assertSame('debug', $record['level']);
}
}

public function testHandleWithNullResult(): void
{
$request = $this->createCallToolRequest('null_tool', []);
Expand Down
Loading