feat: give every MCP client its own token, rate limit and log name

This commit is contained in:
fromme committed 2026-10-08 12:37:49 +02:00
1 parent 3af41c06ab
commit bfdedefae3
8 files changed
+134 -33

No files matched your search

+9 -6
View File
@@ -12,12 +12,13 @@ use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\RateLimiter\RateLimiterFactory;
/**
* What every chatbot tool call goes through: the per-token rate limit, the call log, and
* What every chatbot tool call goes through: the per-client rate limit, the call log, and
* the answer as compact JSON.
*
* The SDK would pretty-print an array result and repeat it as structured content; both
* cost the model tokens and neither helps n8n. The log records tool, arguments, result
* count and duration, never anything about the person chatting, which n8n does not send.
* count, duration and the calling client (n8n, claude-desktop, …), never anything about
* the person chatting, which n8n does not send.
*/
class ToolCallRunner
{
@@ -37,10 +38,11 @@ class ToolCallRunner
public function run(string $tool, array $arguments, callable $call): CallToolResult
{
$arguments = array_filter($arguments, static fn (mixed $value): bool => null !== $value);
$limit = $this->chatbotMcpLimiter->create($this->security->getUser()?->getUserIdentifier() ?? 'anonymous')->consume();
$client = $this->security->getUser()?->getUserIdentifier() ?? 'anonymous';
$limit = $this->chatbotMcpLimiter->create($client)->consume();
if (!$limit->isAccepted()) {
$this->chatbotLogger->warning('MCP tool call rate-limited', ['tool' => $tool, 'arguments' => $arguments]);
$this->chatbotLogger->warning('MCP tool call rate-limited', ['client' => $client, 'tool' => $tool, 'arguments' => $arguments]);
throw new ToolCallException(sprintf('Too many tool calls. Retry after %s.', $limit->getRetryAfter()->format('H:i:s')));
}
@@ -50,16 +52,17 @@ class ToolCallRunner
try {
$result = $call();
} catch (ToolCallException $e) {
$this->chatbotLogger->info('MCP tool call rejected', ['tool' => $tool, 'arguments' => $arguments, 'error' => $e->getMessage()]);
$this->chatbotLogger->info('MCP tool call rejected', ['client' => $client, 'tool' => $tool, 'arguments' => $arguments, 'error' => $e->getMessage()]);
throw $e;
} catch (\Throwable $e) {
$this->chatbotLogger->error('MCP tool call failed', ['tool' => $tool, 'arguments' => $arguments, 'exception' => $e]);
$this->chatbotLogger->error('MCP tool call failed', ['client' => $client, 'tool' => $tool, 'arguments' => $arguments, 'exception' => $e]);
throw $e;
}
$this->chatbotLogger->info('MCP tool call', [
'client' => $client,
'tool' => $tool,
'arguments' => $arguments,
'resultCount' => is_array($result['results'] ?? null) ? count($result['results']) : 1,