From cd86d8061981b362d2feb0a063b8d294efbb5396 Mon Sep 17 00:00:00 2001 From: aton-of-data Date: Sat, 10 Oct 2026 18:35:02 +0000 Subject: [PATCH] [Server] Log the tool name at info for tools/call Since #525 the info-level "Handling request." record in Protocol carries only the method and id, and CallToolHandler logs the called tool at debug level only, so a server logging at INFO no longer shows which tool ran. CallToolHandler now logs the tool name at info level. The arguments stay in the existing debug record, and Protocol stays method-agnostic. --- CHANGELOG.md | 1 + .../Handler/Request/CallToolHandler.php | 2 + .../Handler/Request/CallToolHandlerTest.php | 39 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f80ef9..a5ee3670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/Server/Handler/Request/CallToolHandler.php b/src/Server/Handler/Request/CallToolHandler.php index dc81a672..2a4a7dd9 100644 --- a/src/Server/Handler/Request/CallToolHandler.php +++ b/src/Server/Handler/Request/CallToolHandler.php @@ -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 { diff --git a/tests/Unit/Server/Handler/Request/CallToolHandlerTest.php b/tests/Unit/Server/Handler/Request/CallToolHandlerTest.php index 572c1b0f..80de8f09 100644 --- a/tests/Unit/Server/Handler/Request/CallToolHandlerTest.php +++ b/tests/Unit/Server/Handler/Request/CallToolHandlerTest.php @@ -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', []);