Skip to content

Commit a7476d8

Browse files
committed
chore: rename and clarify function inspection code
1 parent 9ac0186 commit a7476d8

File tree

3 files changed

+25
-21
lines changed

3 files changed

+25
-21
lines changed

src/mcp/server/lowlevel/func_inspection.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
from typing import Any
44

55

6-
def accepts_request(func: Callable[..., Any]) -> bool:
6+
def accepts_single_positional_arg(func: Callable[..., Any]) -> bool:
77
"""
8-
True if the function accepts a cursor parameter call, otherwise false.
8+
True if the function accepts at least one positional argument, otherwise false.
99
10-
`accepts_cursor` does not validate that the function will work. For
11-
example, if `func` contains keyword-only arguments with no defaults,
12-
then it will not work when used in the `lowlevel/server.py` code, but
13-
this function will not raise an exception.
10+
This function intentionally does not define behavior for `func`s that
11+
contain more than one positional argument, or any required keyword
12+
arguments without defaults.
1413
"""
1514
try:
1615
sig = inspect.signature(func)
@@ -20,26 +19,28 @@ def accepts_request(func: Callable[..., Any]) -> bool:
2019
params = dict(sig.parameters.items())
2120

2221
if len(params) == 0:
23-
# No parameters at all - can't accept cursor
22+
# No parameters at all - can't accept single argument
2423
return False
2524

2625
# Check if ALL remaining parameters are keyword-only
2726
all_keyword_only = all(param.kind == inspect.Parameter.KEYWORD_ONLY for param in params.values())
2827

2928
if all_keyword_only:
3029
# If all params are keyword-only, check if they ALL have defaults
31-
# If they do, the function can be called with no arguments -> no cursor
30+
# If they do, the function can be called with no arguments -> no argument
3231
all_have_defaults = all(param.default is not inspect.Parameter.empty for param in params.values())
33-
return not all_have_defaults # False if all have defaults (no cursor), True otherwise
32+
if all_have_defaults:
33+
return False
34+
# otherwise, undefined (doesn't accept a positional argument, and requires at least one keyword only)
3435

3536
# Check if the ONLY parameter is **kwargs (VAR_KEYWORD)
36-
# A function with only **kwargs can't accept a positional cursor argument
37+
# A function with only **kwargs can't accept a positional argument
3738
if len(params) == 1:
3839
only_param = next(iter(params.values()))
3940
if only_param.kind == inspect.Parameter.VAR_KEYWORD:
40-
return False # Can't pass positional cursor to **kwargs
41+
return False # Can't pass positional argument to **kwargs
4142

42-
# Has at least one positional or variadic parameter - can accept cursor
43+
# Has at least one positional or variadic parameter - can accept argument
4344
# Important note: this is designed to _not_ handle the situation where
4445
# there are multiple keyword only arguments with no defaults. In those
4546
# situations it's an invalid handler function, and will error. But it's

src/mcp/server/lowlevel/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async def main():
8282
from typing_extensions import TypeVar
8383

8484
import mcp.types as types
85-
from mcp.server.lowlevel.func_inspection import accepts_request
85+
from mcp.server.lowlevel.func_inspection import accepts_single_positional_arg
8686
from mcp.server.lowlevel.helper_types import ReadResourceContents
8787
from mcp.server.models import InitializationOptions
8888
from mcp.server.session import ServerSession
@@ -235,7 +235,7 @@ def decorator(
235235
| Callable[[types.ListPromptsRequest], Awaitable[types.ListPromptsResult]],
236236
):
237237
logger.debug("Registering handler for PromptListRequest")
238-
pass_request = accepts_request(func)
238+
pass_request = accepts_single_positional_arg(func)
239239

240240
if pass_request:
241241
request_func = cast(Callable[[types.ListPromptsRequest], Awaitable[types.ListPromptsResult]], func)
@@ -280,7 +280,7 @@ def decorator(
280280
| Callable[[types.ListResourcesRequest], Awaitable[types.ListResourcesResult]],
281281
):
282282
logger.debug("Registering handler for ListResourcesRequest")
283-
pass_request = accepts_request(func)
283+
pass_request = accepts_single_positional_arg(func)
284284

285285
if pass_request:
286286
request_func = cast(Callable[[types.ListResourcesRequest], Awaitable[types.ListResourcesResult]], func)
@@ -420,7 +420,7 @@ def decorator(
420420
| Callable[[types.ListToolsRequest], Awaitable[types.ListToolsResult]],
421421
):
422422
logger.debug("Registering handler for ListToolsRequest")
423-
pass_request = accepts_request(func)
423+
pass_request = accepts_single_positional_arg(func)
424424

425425
if pass_request:
426426
request_func = cast(Callable[[types.ListToolsRequest], Awaitable[types.ListToolsResult]], func)

tests/server/lowlevel/test_func_inspection.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import pytest
55

66
from mcp import types
7-
from mcp.server.lowlevel.func_inspection import accepts_request
7+
from mcp.server.lowlevel.func_inspection import accepts_single_positional_arg
88

99

10-
# Test fixtures - functions and methods with various signatures
1110
class MyClass:
1211
async def no_request_method(self):
1312
"""Instance method without request parameter"""
@@ -161,13 +160,17 @@ async def mixed_positional_and_keyword(request: types.ListPromptsRequest, *, ext
161160
],
162161
ids=lambda x: x if isinstance(x, str) else "",
163162
)
164-
def test_accepts_request(callable_obj: Callable[..., Any], expected: bool, description: str):
165-
"""Test that accepts_request correctly identifies functions that accept a request parameter.
163+
def test_accepts_single_positional_arg(callable_obj: Callable[..., Any], expected: bool, description: str):
164+
"""Test that `accepts_single_positional_arg` correctly identifies functions that accept a single argument.
165+
166+
`accepts_single_positional_arg` is currently only used in the case of
167+
the lowlevel server code checking whether a handler accepts a request
168+
argument, so the test cases reference a "request" param/arg.
166169
167170
The function should return True if the callable can potentially accept a positional
168171
request argument. Returns False if:
169172
- No parameters at all
170173
- Only keyword-only parameters that ALL have defaults (can call with no args)
171174
- Only **kwargs parameter (can't accept positional arguments)
172175
"""
173-
assert accepts_request(callable_obj) == expected, f"Failed for {description}"
176+
assert accepts_single_positional_arg(callable_obj) == expected, f"Failed for {description}"

0 commit comments

Comments
 (0)