Jeremiah Lowin commited on
Commit
35c4f24
·
1 Parent(s): fc79ef8

func → fn everywhere

Browse files
src/fastmcp/resources/resource_manager.py CHANGED
@@ -19,9 +19,35 @@ class ResourceManager:
19
  self._templates: Dict[str, ResourceTemplate] = {}
20
  self.warn_on_duplicate_resources = warn_on_duplicate_resources
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  def add_template(
23
  self,
24
- func: Callable,
25
  uri_template: str,
26
  name: Optional[str] = None,
27
  description: Optional[str] = None,
@@ -29,7 +55,7 @@ class ResourceManager:
29
  ) -> ResourceTemplate:
30
  """Add a template from a function."""
31
  template = ResourceTemplate.from_function(
32
- func,
33
  uri_template=uri_template,
34
  name=name,
35
  description=description,
@@ -66,29 +92,3 @@ class ResourceManager:
66
  """List all registered templates."""
67
  logger.debug("Listing templates", extra={"count": len(self._templates)})
68
  return list(self._templates.values())
69
-
70
- def add_resource(self, resource: Resource) -> Resource:
71
- """Add a resource to the manager.
72
-
73
- Args:
74
- resource: A Resource instance to add
75
-
76
- Returns:
77
- The added resource. If a resource with the same URI already exists,
78
- returns the existing resource.
79
- """
80
- logger.debug(
81
- "Adding resource",
82
- extra={
83
- "uri": resource.uri,
84
- "type": type(resource).__name__,
85
- "name": resource.name,
86
- },
87
- )
88
- existing = self._resources.get(str(resource.uri))
89
- if existing:
90
- if self.warn_on_duplicate_resources:
91
- logger.warning(f"Resource already exists: {resource.uri}")
92
- return existing
93
- self._resources[str(resource.uri)] = resource
94
- return resource
 
19
  self._templates: Dict[str, ResourceTemplate] = {}
20
  self.warn_on_duplicate_resources = warn_on_duplicate_resources
21
 
22
+ def add_resource(self, resource: Resource) -> Resource:
23
+ """Add a resource to the manager.
24
+
25
+ Args:
26
+ resource: A Resource instance to add
27
+
28
+ Returns:
29
+ The added resource. If a resource with the same URI already exists,
30
+ returns the existing resource.
31
+ """
32
+ logger.debug(
33
+ "Adding resource",
34
+ extra={
35
+ "uri": resource.uri,
36
+ "type": type(resource).__name__,
37
+ "name": resource.name,
38
+ },
39
+ )
40
+ existing = self._resources.get(str(resource.uri))
41
+ if existing:
42
+ if self.warn_on_duplicate_resources:
43
+ logger.warning(f"Resource already exists: {resource.uri}")
44
+ return existing
45
+ self._resources[str(resource.uri)] = resource
46
+ return resource
47
+
48
  def add_template(
49
  self,
50
+ fn: Callable,
51
  uri_template: str,
52
  name: Optional[str] = None,
53
  description: Optional[str] = None,
 
55
  ) -> ResourceTemplate:
56
  """Add a template from a function."""
57
  template = ResourceTemplate.from_function(
58
+ fn,
59
  uri_template=uri_template,
60
  name=name,
61
  description=description,
 
92
  """List all registered templates."""
93
  logger.debug("Listing templates", extra={"count": len(self._templates)})
94
  return list(self._templates.values())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/fastmcp/resources/templates.py CHANGED
@@ -20,35 +20,35 @@ class ResourceTemplate(BaseModel):
20
  mime_type: str = Field(
21
  default="text/plain", description="MIME type of the resource content"
22
  )
23
- func: Callable = Field(exclude=True)
24
  parameters: dict = Field(description="JSON schema for function parameters")
25
 
26
  @classmethod
27
  def from_function(
28
  cls,
29
- func: Callable,
30
  uri_template: str,
31
  name: Optional[str] = None,
32
  description: Optional[str] = None,
33
  mime_type: Optional[str] = None,
34
  ) -> "ResourceTemplate":
35
  """Create a template from a function."""
36
- func_name = name or func.__name__
37
  if func_name == "<lambda>":
38
  raise ValueError("You must provide a name for lambda functions")
39
 
40
  # Get schema from TypeAdapter - will fail if function isn't properly typed
41
- parameters = TypeAdapter(func).json_schema()
42
 
43
  # ensure the arguments are properly cast
44
- func = validate_call(func)
45
 
46
  return cls(
47
  uri_template=uri_template,
48
  name=func_name,
49
- description=description or func.__doc__ or "",
50
  mime_type=mime_type or "text/plain",
51
- func=func,
52
  parameters=parameters,
53
  )
54
 
@@ -65,7 +65,7 @@ class ResourceTemplate(BaseModel):
65
  """Create a resource from the template with the given parameters."""
66
  try:
67
  # Call function and check if result is a coroutine
68
- result = self.func(**params)
69
  if inspect.iscoroutine(result):
70
  result = await result
71
 
@@ -74,7 +74,7 @@ class ResourceTemplate(BaseModel):
74
  name=self.name,
75
  description=self.description,
76
  mime_type=self.mime_type,
77
- func=lambda: result, # Capture result in closure
78
  )
79
  except Exception as e:
80
  raise ValueError(f"Error creating resource from template: {e}")
 
20
  mime_type: str = Field(
21
  default="text/plain", description="MIME type of the resource content"
22
  )
23
+ fn: Callable = Field(exclude=True)
24
  parameters: dict = Field(description="JSON schema for function parameters")
25
 
26
  @classmethod
27
  def from_function(
28
  cls,
29
+ fn: Callable,
30
  uri_template: str,
31
  name: Optional[str] = None,
32
  description: Optional[str] = None,
33
  mime_type: Optional[str] = None,
34
  ) -> "ResourceTemplate":
35
  """Create a template from a function."""
36
+ func_name = name or fn.__name__
37
  if func_name == "<lambda>":
38
  raise ValueError("You must provide a name for lambda functions")
39
 
40
  # Get schema from TypeAdapter - will fail if function isn't properly typed
41
+ parameters = TypeAdapter(fn).json_schema()
42
 
43
  # ensure the arguments are properly cast
44
+ fn = validate_call(fn)
45
 
46
  return cls(
47
  uri_template=uri_template,
48
  name=func_name,
49
+ description=description or fn.__doc__ or "",
50
  mime_type=mime_type or "text/plain",
51
+ fn=fn,
52
  parameters=parameters,
53
  )
54
 
 
65
  """Create a resource from the template with the given parameters."""
66
  try:
67
  # Call function and check if result is a coroutine
68
+ result = self.fn(**params)
69
  if inspect.iscoroutine(result):
70
  result = await result
71
 
 
74
  name=self.name,
75
  description=self.description,
76
  mime_type=self.mime_type,
77
+ fn=lambda: result, # Capture result in closure
78
  )
79
  except Exception as e:
80
  raise ValueError(f"Error creating resource from template: {e}")
src/fastmcp/resources/types.py CHANGED
@@ -46,12 +46,12 @@ class FunctionResource(Resource):
46
  - other types will be converted to JSON
47
  """
48
 
49
- func: Callable[[], Any] = Field(exclude=True)
50
 
51
  async def read(self) -> Union[str, bytes]:
52
  """Read the resource by calling the wrapped function."""
53
  try:
54
- result = self.func()
55
  if isinstance(result, Resource):
56
  return await result.read()
57
  if isinstance(result, bytes):
 
46
  - other types will be converted to JSON
47
  """
48
 
49
+ fn: Callable[[], Any] = Field(exclude=True)
50
 
51
  async def read(self) -> Union[str, bytes]:
52
  """Read the resource by calling the wrapped function."""
53
  try:
54
+ result = self.fn()
55
  if isinstance(result, Resource):
56
  return await result.read()
57
  if isinstance(result, bytes):
src/fastmcp/server.py CHANGED
@@ -188,7 +188,7 @@ class FastMCP:
188
 
189
  def add_tool(
190
  self,
191
- func: Callable,
192
  name: Optional[str] = None,
193
  description: Optional[str] = None,
194
  ) -> None:
@@ -198,11 +198,11 @@ class FastMCP:
198
  with the Context type annotation. See the @tool decorator for examples.
199
 
200
  Args:
201
- func: The function to register as a tool
202
  name: Optional name for the tool (defaults to function name)
203
  description: Optional description of what the tool does
204
  """
205
- self._tool_manager.add_tool(func, name=name, description=description)
206
 
207
  def tool(
208
  self, name: Optional[str] = None, description: Optional[str] = None
@@ -238,9 +238,9 @@ class FastMCP:
238
  "Did you forget to call it? Use @tool() instead of @tool"
239
  )
240
 
241
- def decorator(func: Callable) -> Callable:
242
- self.add_tool(func, name=name, description=description)
243
- return func
244
 
245
  return decorator
246
 
@@ -293,19 +293,19 @@ class FastMCP:
293
  "Did you forget to call it? Use @resource('uri') instead of @resource"
294
  )
295
 
296
- def decorator(func: Callable) -> Callable:
297
- @functools.wraps(func)
298
  def wrapper(*args: Any, **kwargs: Any) -> Any:
299
- return func(*args, **kwargs)
300
 
301
  # Check if this should be a template
302
  has_uri_params = "{" in uri and "}" in uri
303
- has_func_params = bool(inspect.signature(func).parameters)
304
 
305
  if has_uri_params or has_func_params:
306
  # Validate that URI params match function params
307
  uri_params = set(re.findall(r"{(\w+)}", uri))
308
- func_params = set(inspect.signature(func).parameters.keys())
309
 
310
  if uri_params != func_params:
311
  raise ValueError(
@@ -328,7 +328,7 @@ class FastMCP:
328
  name=name,
329
  description=description,
330
  mime_type=mime_type or "text/plain",
331
- func=wrapper,
332
  )
333
  self.add_resource(resource)
334
  return wrapper
 
188
 
189
  def add_tool(
190
  self,
191
+ fn: Callable,
192
  name: Optional[str] = None,
193
  description: Optional[str] = None,
194
  ) -> None:
 
198
  with the Context type annotation. See the @tool decorator for examples.
199
 
200
  Args:
201
+ fn: The function to register as a tool
202
  name: Optional name for the tool (defaults to function name)
203
  description: Optional description of what the tool does
204
  """
205
+ self._tool_manager.add_tool(fn, name=name, description=description)
206
 
207
  def tool(
208
  self, name: Optional[str] = None, description: Optional[str] = None
 
238
  "Did you forget to call it? Use @tool() instead of @tool"
239
  )
240
 
241
+ def decorator(fn: Callable) -> Callable:
242
+ self.add_tool(fn, name=name, description=description)
243
+ return fn
244
 
245
  return decorator
246
 
 
293
  "Did you forget to call it? Use @resource('uri') instead of @resource"
294
  )
295
 
296
+ def decorator(fn: Callable) -> Callable:
297
+ @functools.wraps(fn)
298
  def wrapper(*args: Any, **kwargs: Any) -> Any:
299
+ return fn(*args, **kwargs)
300
 
301
  # Check if this should be a template
302
  has_uri_params = "{" in uri and "}" in uri
303
+ has_func_params = bool(inspect.signature(fn).parameters)
304
 
305
  if has_uri_params or has_func_params:
306
  # Validate that URI params match function params
307
  uri_params = set(re.findall(r"{(\w+)}", uri))
308
+ func_params = set(inspect.signature(fn).parameters.keys())
309
 
310
  if uri_params != func_params:
311
  raise ValueError(
 
328
  name=name,
329
  description=description,
330
  mime_type=mime_type or "text/plain",
331
+ fn=wrapper,
332
  )
333
  self.add_resource(resource)
334
  return wrapper
src/fastmcp/tools/base.py CHANGED
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
  class Tool(BaseModel):
16
  """Internal tool registration info."""
17
 
18
- func: Callable = Field(exclude=True)
19
  name: str = Field(description="Name of the tool")
20
  description: str = Field(description="Description of what the tool does")
21
  parameters: dict = Field(description="JSON schema for tool parameters")
@@ -27,36 +27,36 @@ class Tool(BaseModel):
27
  @classmethod
28
  def from_function(
29
  cls,
30
- func: Callable,
31
  name: Optional[str] = None,
32
  description: Optional[str] = None,
33
  context_kwarg: Optional[str] = None,
34
  ) -> "Tool":
35
  """Create a Tool from a function."""
36
- func_name = name or func.__name__
37
 
38
  if func_name == "<lambda>":
39
  raise ValueError("You must provide a name for lambda functions")
40
 
41
- func_doc = description or func.__doc__ or ""
42
- is_async = inspect.iscoroutinefunction(func)
43
 
44
  # Get schema from TypeAdapter - will fail if function isn't properly typed
45
- parameters = TypeAdapter(func).json_schema()
46
 
47
  # Find context parameter if it exists
48
  if context_kwarg is None:
49
- sig = inspect.signature(func)
50
  for param_name, param in sig.parameters.items():
51
  if param.annotation is fastmcp.Context:
52
  context_kwarg = param_name
53
  break
54
 
55
  # ensure the arguments are properly cast
56
- func = validate_call(func)
57
 
58
  return cls(
59
- func=func,
60
  name=func_name,
61
  description=func_doc,
62
  parameters=parameters,
@@ -73,7 +73,7 @@ class Tool(BaseModel):
73
 
74
  # Call function with proper async handling
75
  if self.is_async:
76
- return await self.func(**arguments)
77
- return self.func(**arguments)
78
  except Exception as e:
79
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
 
15
  class Tool(BaseModel):
16
  """Internal tool registration info."""
17
 
18
+ fn: Callable = Field(exclude=True)
19
  name: str = Field(description="Name of the tool")
20
  description: str = Field(description="Description of what the tool does")
21
  parameters: dict = Field(description="JSON schema for tool parameters")
 
27
  @classmethod
28
  def from_function(
29
  cls,
30
+ fn: Callable,
31
  name: Optional[str] = None,
32
  description: Optional[str] = None,
33
  context_kwarg: Optional[str] = None,
34
  ) -> "Tool":
35
  """Create a Tool from a function."""
36
+ func_name = name or fn.__name__
37
 
38
  if func_name == "<lambda>":
39
  raise ValueError("You must provide a name for lambda functions")
40
 
41
+ func_doc = description or fn.__doc__ or ""
42
+ is_async = inspect.iscoroutinefunction(fn)
43
 
44
  # Get schema from TypeAdapter - will fail if function isn't properly typed
45
+ parameters = TypeAdapter(fn).json_schema()
46
 
47
  # Find context parameter if it exists
48
  if context_kwarg is None:
49
+ sig = inspect.signature(fn)
50
  for param_name, param in sig.parameters.items():
51
  if param.annotation is fastmcp.Context:
52
  context_kwarg = param_name
53
  break
54
 
55
  # ensure the arguments are properly cast
56
+ fn = validate_call(fn)
57
 
58
  return cls(
59
+ fn=fn,
60
  name=func_name,
61
  description=func_doc,
62
  parameters=parameters,
 
73
 
74
  # Call function with proper async handling
75
  if self.is_async:
76
+ return await self.fn(**arguments)
77
+ return self.fn(**arguments)
78
  except Exception as e:
79
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
src/fastmcp/tools/tool_manager.py CHANGED
@@ -30,12 +30,12 @@ class ToolManager:
30
 
31
  def add_tool(
32
  self,
33
- func: Callable,
34
  name: Optional[str] = None,
35
  description: Optional[str] = None,
36
  ) -> Tool:
37
  """Add a tool to the server."""
38
- tool = Tool.from_function(func, name=name, description=description)
39
  existing = self._tools.get(tool.name)
40
  if existing:
41
  if self.warn_on_duplicate_tools:
 
30
 
31
  def add_tool(
32
  self,
33
+ fn: Callable,
34
  name: Optional[str] = None,
35
  description: Optional[str] = None,
36
  ) -> Tool:
37
  """Add a tool to the server."""
38
+ tool = Tool.from_function(fn, name=name, description=description)
39
  existing = self._tools.get(tool.name)
40
  if existing:
41
  if self.warn_on_duplicate_tools:
tests/prompts/__init__.py ADDED
File without changes
tests/resources/test_function_resources.py CHANGED
@@ -16,13 +16,13 @@ class TestFunctionResource:
16
  uri="fn://test",
17
  name="test",
18
  description="test function",
19
- func=my_func,
20
  )
21
  assert str(resource.uri) == "fn://test"
22
  assert resource.name == "test"
23
  assert resource.description == "test function"
24
  assert resource.mime_type == "text/plain" # default
25
- assert resource.func == my_func
26
 
27
  async def test_read_text(self):
28
  """Test reading text from a FunctionResource."""
@@ -33,7 +33,7 @@ class TestFunctionResource:
33
  resource = FunctionResource(
34
  uri="function://test",
35
  name="test",
36
- func=get_data,
37
  )
38
  content = await resource.read()
39
  assert content == "Hello, world!"
@@ -48,7 +48,7 @@ class TestFunctionResource:
48
  resource = FunctionResource(
49
  uri="function://test",
50
  name="test",
51
- func=get_data,
52
  )
53
  content = await resource.read()
54
  assert content == b"Hello, world!"
@@ -62,7 +62,7 @@ class TestFunctionResource:
62
  resource = FunctionResource(
63
  uri="function://test",
64
  name="test",
65
- func=get_data,
66
  )
67
  content = await resource.read()
68
  assert '"key": "value"' in content
@@ -76,7 +76,7 @@ class TestFunctionResource:
76
  resource = FunctionResource(
77
  uri="function://test",
78
  name="test",
79
- func=failing_func,
80
  )
81
  with pytest.raises(ValueError, match="Error reading resource function://test"):
82
  await resource.read()
@@ -90,7 +90,7 @@ class TestFunctionResource:
90
  resource = FunctionResource(
91
  uri="function://test",
92
  name="test",
93
- func=lambda: MyModel(name="test"),
94
  )
95
  content = await resource.read()
96
  assert content == '{"name": "test"}'
@@ -108,7 +108,7 @@ class TestFunctionResource:
108
  resource = FunctionResource(
109
  uri="function://test",
110
  name="test",
111
- func=get_data,
112
  )
113
  content = await resource.read()
114
  assert isinstance(content, str)
 
16
  uri="fn://test",
17
  name="test",
18
  description="test function",
19
+ fn=my_func,
20
  )
21
  assert str(resource.uri) == "fn://test"
22
  assert resource.name == "test"
23
  assert resource.description == "test function"
24
  assert resource.mime_type == "text/plain" # default
25
+ assert resource.fn == my_func
26
 
27
  async def test_read_text(self):
28
  """Test reading text from a FunctionResource."""
 
33
  resource = FunctionResource(
34
  uri="function://test",
35
  name="test",
36
+ fn=get_data,
37
  )
38
  content = await resource.read()
39
  assert content == "Hello, world!"
 
48
  resource = FunctionResource(
49
  uri="function://test",
50
  name="test",
51
+ fn=get_data,
52
  )
53
  content = await resource.read()
54
  assert content == b"Hello, world!"
 
62
  resource = FunctionResource(
63
  uri="function://test",
64
  name="test",
65
+ fn=get_data,
66
  )
67
  content = await resource.read()
68
  assert '"key": "value"' in content
 
76
  resource = FunctionResource(
77
  uri="function://test",
78
  name="test",
79
+ fn=failing_func,
80
  )
81
  with pytest.raises(ValueError, match="Error reading resource function://test"):
82
  await resource.read()
 
90
  resource = FunctionResource(
91
  uri="function://test",
92
  name="test",
93
+ fn=lambda: MyModel(name="test"),
94
  )
95
  content = await resource.read()
96
  assert content == '{"name": "test"}'
 
108
  resource = FunctionResource(
109
  uri="function://test",
110
  name="test",
111
+ fn=get_data,
112
  )
113
  content = await resource.read()
114
  assert isinstance(content, str)
tests/resources/test_resource_manager.py CHANGED
@@ -99,7 +99,7 @@ class TestResourceManager:
99
  return f"Hello, {name}!"
100
 
101
  template = ResourceTemplate.from_function(
102
- func=greet,
103
  uri_template="greet://{name}",
104
  name="greeter",
105
  )
 
99
  return f"Hello, {name}!"
100
 
101
  template = ResourceTemplate.from_function(
102
+ fn=greet,
103
  uri_template="greet://{name}",
104
  name="greeter",
105
  )
tests/resources/test_resource_template.py CHANGED
@@ -12,7 +12,7 @@ class TestResourceTemplate:
12
  return f"Weather in {city} ({units})"
13
 
14
  template = ResourceTemplate.from_function(
15
- func=weather,
16
  uri_template="weather://{city}/current",
17
  name="weather",
18
  description="Get current weather",
@@ -29,7 +29,7 @@ class TestResourceTemplate:
29
  ValueError, match="You must provide a name for lambda functions"
30
  ):
31
  ResourceTemplate.from_function(
32
- func=lambda x: x,
33
  uri_template="test://{x}",
34
  )
35
 
@@ -40,7 +40,7 @@ class TestResourceTemplate:
40
  return x
41
 
42
  template = ResourceTemplate.from_function(
43
- func=dummy,
44
  uri_template="test://{x}/value",
45
  name="test",
46
  )
@@ -60,7 +60,7 @@ class TestResourceTemplate:
60
  return f"Hello, {name}!"
61
 
62
  template = ResourceTemplate.from_function(
63
- func=greet,
64
  uri_template="greet://{name}",
65
  name="greeter",
66
  )
@@ -81,7 +81,7 @@ class TestResourceTemplate:
81
  return value.encode()
82
 
83
  template = ResourceTemplate.from_function(
84
- func=get_bytes,
85
  uri_template="bytes://{value}",
86
  name="bytes",
87
  )
@@ -102,7 +102,7 @@ class TestResourceTemplate:
102
  return {"key": key, "value": 123}
103
 
104
  template = ResourceTemplate.from_function(
105
- func=get_data,
106
  uri_template="data://{key}",
107
  name="data",
108
  )
@@ -124,7 +124,7 @@ class TestResourceTemplate:
124
  raise ValueError("Test error")
125
 
126
  template = ResourceTemplate.from_function(
127
- func=failing_func,
128
  uri_template="fail://{x}",
129
  name="fail",
130
  )
@@ -139,7 +139,7 @@ class TestResourceTemplate:
139
  return f"Hello, {name}!"
140
 
141
  template = ResourceTemplate.from_function(
142
- func=greet,
143
  uri_template="greet://{name}",
144
  name="greeter",
145
  )
@@ -160,7 +160,7 @@ class TestResourceTemplate:
160
  return value.encode()
161
 
162
  template = ResourceTemplate.from_function(
163
- func=get_bytes,
164
  uri_template="bytes://{value}",
165
  name="bytes",
166
  )
@@ -181,7 +181,7 @@ class TestResourceTemplate:
181
  return {"key": key, "value": 123}
182
 
183
  template = ResourceTemplate.from_function(
184
- func=get_data,
185
  uri_template="data://{key}",
186
  name="data",
187
  )
@@ -203,7 +203,7 @@ class TestResourceTemplate:
203
  raise ValueError("Test error")
204
 
205
  template = ResourceTemplate.from_function(
206
- func=failing_func,
207
  uri_template="fail://{x}",
208
  name="fail",
209
  )
@@ -223,7 +223,7 @@ class TestResourceTemplate:
223
  return async_helper(name) # Returns coroutine
224
 
225
  template = ResourceTemplate.from_function(
226
- func=get_greeting,
227
  uri_template="greet://{name}",
228
  name="greeter",
229
  )
 
12
  return f"Weather in {city} ({units})"
13
 
14
  template = ResourceTemplate.from_function(
15
+ fn=weather,
16
  uri_template="weather://{city}/current",
17
  name="weather",
18
  description="Get current weather",
 
29
  ValueError, match="You must provide a name for lambda functions"
30
  ):
31
  ResourceTemplate.from_function(
32
+ fn=lambda x: x,
33
  uri_template="test://{x}",
34
  )
35
 
 
40
  return x
41
 
42
  template = ResourceTemplate.from_function(
43
+ fn=dummy,
44
  uri_template="test://{x}/value",
45
  name="test",
46
  )
 
60
  return f"Hello, {name}!"
61
 
62
  template = ResourceTemplate.from_function(
63
+ fn=greet,
64
  uri_template="greet://{name}",
65
  name="greeter",
66
  )
 
81
  return value.encode()
82
 
83
  template = ResourceTemplate.from_function(
84
+ fn=get_bytes,
85
  uri_template="bytes://{value}",
86
  name="bytes",
87
  )
 
102
  return {"key": key, "value": 123}
103
 
104
  template = ResourceTemplate.from_function(
105
+ fn=get_data,
106
  uri_template="data://{key}",
107
  name="data",
108
  )
 
124
  raise ValueError("Test error")
125
 
126
  template = ResourceTemplate.from_function(
127
+ fn=failing_func,
128
  uri_template="fail://{x}",
129
  name="fail",
130
  )
 
139
  return f"Hello, {name}!"
140
 
141
  template = ResourceTemplate.from_function(
142
+ fn=greet,
143
  uri_template="greet://{name}",
144
  name="greeter",
145
  )
 
160
  return value.encode()
161
 
162
  template = ResourceTemplate.from_function(
163
+ fn=get_bytes,
164
  uri_template="bytes://{value}",
165
  name="bytes",
166
  )
 
181
  return {"key": key, "value": 123}
182
 
183
  template = ResourceTemplate.from_function(
184
+ fn=get_data,
185
  uri_template="data://{key}",
186
  name="data",
187
  )
 
203
  raise ValueError("Test error")
204
 
205
  template = ResourceTemplate.from_function(
206
+ fn=failing_func,
207
  uri_template="fail://{x}",
208
  name="fail",
209
  )
 
223
  return async_helper(name) # Returns coroutine
224
 
225
  template = ResourceTemplate.from_function(
226
+ fn=get_greeting,
227
  uri_template="greet://{name}",
228
  name="greeter",
229
  )
tests/resources/test_resources.py CHANGED
@@ -15,7 +15,7 @@ class TestResourceValidation:
15
  resource = FunctionResource(
16
  uri="http://example.com/data",
17
  name="test",
18
- func=dummy_func,
19
  )
20
  assert str(resource.uri) == "http://example.com/data"
21
 
@@ -24,7 +24,7 @@ class TestResourceValidation:
24
  FunctionResource(
25
  uri="invalid",
26
  name="test",
27
- func=dummy_func,
28
  )
29
 
30
  # Missing host
@@ -32,7 +32,7 @@ class TestResourceValidation:
32
  FunctionResource(
33
  uri="http://",
34
  name="test",
35
- func=dummy_func,
36
  )
37
 
38
  def test_resource_name_from_uri(self):
@@ -43,7 +43,7 @@ class TestResourceValidation:
43
 
44
  resource = FunctionResource(
45
  uri="resource://my-resource",
46
- func=dummy_func,
47
  )
48
  assert resource.name == "my-resource"
49
 
@@ -56,14 +56,14 @@ class TestResourceValidation:
56
  # Must provide either name or URI
57
  with pytest.raises(ValueError, match="Either name or uri must be provided"):
58
  FunctionResource(
59
- func=dummy_func,
60
  )
61
 
62
  # Explicit name takes precedence over URI
63
  resource = FunctionResource(
64
  uri="resource://uri-name",
65
  name="explicit-name",
66
- func=dummy_func,
67
  )
68
  assert resource.name == "explicit-name"
69
 
@@ -76,14 +76,14 @@ class TestResourceValidation:
76
  # Default mime type
77
  resource = FunctionResource(
78
  uri="resource://test",
79
- func=dummy_func,
80
  )
81
  assert resource.mime_type == "text/plain"
82
 
83
  # Custom mime type
84
  resource = FunctionResource(
85
  uri="resource://test",
86
- func=dummy_func,
87
  mime_type="application/json",
88
  )
89
  assert resource.mime_type == "application/json"
 
15
  resource = FunctionResource(
16
  uri="http://example.com/data",
17
  name="test",
18
+ fn=dummy_func,
19
  )
20
  assert str(resource.uri) == "http://example.com/data"
21
 
 
24
  FunctionResource(
25
  uri="invalid",
26
  name="test",
27
+ fn=dummy_func,
28
  )
29
 
30
  # Missing host
 
32
  FunctionResource(
33
  uri="http://",
34
  name="test",
35
+ fn=dummy_func,
36
  )
37
 
38
  def test_resource_name_from_uri(self):
 
43
 
44
  resource = FunctionResource(
45
  uri="resource://my-resource",
46
+ fn=dummy_func,
47
  )
48
  assert resource.name == "my-resource"
49
 
 
56
  # Must provide either name or URI
57
  with pytest.raises(ValueError, match="Either name or uri must be provided"):
58
  FunctionResource(
59
+ fn=dummy_func,
60
  )
61
 
62
  # Explicit name takes precedence over URI
63
  resource = FunctionResource(
64
  uri="resource://uri-name",
65
  name="explicit-name",
66
+ fn=dummy_func,
67
  )
68
  assert resource.name == "explicit-name"
69
 
 
76
  # Default mime type
77
  resource = FunctionResource(
78
  uri="resource://test",
79
+ fn=dummy_func,
80
  )
81
  assert resource.mime_type == "text/plain"
82
 
83
  # Custom mime type
84
  resource = FunctionResource(
85
  uri="resource://test",
86
+ fn=dummy_func,
87
  mime_type="application/json",
88
  )
89
  assert resource.mime_type == "application/json"
tests/test_server.py CHANGED
@@ -205,7 +205,7 @@ class TestServerResources:
205
  def get_text():
206
  return "Hello, world!"
207
 
208
- resource = FunctionResource(uri="resource://test", name="test", func=get_text)
209
  mcp.add_resource(resource)
210
 
211
  async with client_session(mcp._mcp_server) as client:
@@ -221,7 +221,7 @@ class TestServerResources:
221
  resource = FunctionResource(
222
  uri="resource://binary",
223
  name="binary",
224
- func=get_binary,
225
  is_binary=True,
226
  mime_type="application/octet-stream",
227
  )
 
205
  def get_text():
206
  return "Hello, world!"
207
 
208
+ resource = FunctionResource(uri="resource://test", name="test", fn=get_text)
209
  mcp.add_resource(resource)
210
 
211
  async with client_session(mcp._mcp_server) as client:
 
221
  resource = FunctionResource(
222
  uri="resource://binary",
223
  name="binary",
224
+ fn=get_binary,
225
  is_binary=True,
226
  mime_type="application/octet-stream",
227
  )