Skip to content

Commit 025d25b

Browse files
committed
[minimcp] Add Limiter for concurrency and timeout control
- Implement Limiter class for enforcing concurrency and idle timeout limits - Add TimeLimiter for resettable idle timeout management - Support configurable max_concurrency and idle_timeout settings - Add comprehensive unit test suite
1 parent fa9546e commit 025d25b

File tree

2 files changed

+609
-0
lines changed

2 files changed

+609
-0
lines changed

src/mcp/server/minimcp/limiter.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import logging
2+
from collections.abc import AsyncGenerator
3+
from contextlib import asynccontextmanager
4+
from typing import Any
5+
6+
from anyio import CancelScope, CapacityLimiter, current_time
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class TimeLimiter:
12+
"""
13+
TimeLimiter enforces an idle timeout for message handlers.
14+
15+
The timer can be reset during handler execution to extend the deadline,
16+
preventing timeout when the handler is actively processing.
17+
"""
18+
19+
_timeout: float
20+
_scope: CancelScope
21+
22+
def __init__(self, timeout: float):
23+
"""
24+
Args:
25+
timeout: The idle timeout in seconds.
26+
"""
27+
self._timeout = float(timeout)
28+
self._scope = CancelScope()
29+
self.reset()
30+
31+
def reset(self) -> None:
32+
"""Reset the idle timeout, extending the deadline from the current time."""
33+
self._scope.deadline = current_time() + self._timeout
34+
35+
def __enter__(self):
36+
self._scope.__enter__()
37+
return self
38+
39+
def __exit__(self, *args: Any):
40+
return self._scope.__exit__(*args)
41+
42+
43+
class Limiter:
44+
"""
45+
Limiter enforces concurrency and idle timeout limits for MiniMCP message handlers.
46+
47+
MiniMCP controls how many handlers can run at the same time (max_concurrency)
48+
and how long each handler can remain idle (idle_timeout) before being cancelled.
49+
By default, idle_timeout is set to 30 seconds and max_concurrency to 100 in MiniMCP.
50+
51+
The TimeLimiter returned by this limiter is available in the handler context
52+
and can be reset using time_limiter.reset() to extend the deadline during
53+
active processing.
54+
55+
Yields:
56+
A TimeLimiter that can be used to reset the idle timeout during handler execution.
57+
"""
58+
59+
_idle_timeout: int
60+
61+
def __init__(self, idle_timeout: int, max_concurrency: int) -> None:
62+
"""
63+
Args:
64+
idle_timeout: The idle timeout in seconds. Handlers exceeding this timeout
65+
will be cancelled if they don't reset the timer.
66+
max_concurrency: The maximum number of concurrent message handlers allowed.
67+
Additional requests will wait until a slot becomes available.
68+
"""
69+
self._idle_timeout = idle_timeout
70+
self._capacity_limiter = CapacityLimiter(max_concurrency)
71+
72+
@asynccontextmanager
73+
async def __call__(self) -> AsyncGenerator[TimeLimiter, None]:
74+
async with self._capacity_limiter:
75+
with TimeLimiter(self._idle_timeout) as time_limiter:
76+
yield time_limiter

0 commit comments

Comments
 (0)