Jeremiah Lowin commited on
Commit
0907e77
·
unverified ·
1 Parent(s): cbb550a

Add meta parameter support to tools, resources, templates, and prompts decorators (#1294)

Browse files
docs/servers/prompts.mdx CHANGED
@@ -65,7 +65,8 @@ While FastMCP infers the name and description from your function, you can overri
65
  @mcp.prompt(
66
  name="analyze_data_request", # Custom prompt name
67
  description="Creates a request to analyze data with specific parameters", # Custom description
68
- tags={"analysis", "data"} # Optional categorization tags
 
69
  )
70
  def data_analysis_prompt(
71
  data_uri: str = Field(description="The URI of the resource containing the data."),
@@ -91,6 +92,12 @@ def data_analysis_prompt(
91
  <ParamField body="enabled" type="bool" default="True">
92
  A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
93
  </ParamField>
 
 
 
 
 
 
94
  </Card>
95
 
96
  ### Argument Types
 
65
  @mcp.prompt(
66
  name="analyze_data_request", # Custom prompt name
67
  description="Creates a request to analyze data with specific parameters", # Custom description
68
+ tags={"analysis", "data"}, # Optional categorization tags
69
+ meta={"version": "1.1", "author": "data-team"} # Custom metadata
70
  )
71
  def data_analysis_prompt(
72
  data_uri: str = Field(description="The URI of the resource containing the data."),
 
92
  <ParamField body="enabled" type="bool" default="True">
93
  A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
94
  </ParamField>
95
+
96
+ <ParamField body="meta" type="dict[str, Any] | None">
97
+ <VersionBadge version="2.11.0" />
98
+
99
+ Optional meta information about the prompt. This data is passed through to the MCP client as the `_meta` field of the client-side prompt object and can be used for custom metadata, versioning, or other application-specific purposes.
100
+ </ParamField>
101
  </Card>
102
 
103
  ### Argument Types
docs/servers/resources.mdx CHANGED
@@ -73,7 +73,8 @@ mcp = FastMCP(name="DataServer")
73
  name="ApplicationStatus", # Custom name
74
  description="Provides the current status of the application.", # Custom description
75
  mime_type="application/json", # Explicit MIME type
76
- tags={"monitoring", "status"} # Categorization tags
 
77
  )
78
  def get_application_status() -> dict:
79
  """Internal function description (ignored if description is provided above)."""
@@ -116,6 +117,12 @@ def get_application_status() -> dict:
116
  </ParamField>
117
  </Expandable>
118
  </ParamField>
 
 
 
 
 
 
119
  </Card>
120
 
121
  ### Return Values
 
73
  name="ApplicationStatus", # Custom name
74
  description="Provides the current status of the application.", # Custom description
75
  mime_type="application/json", # Explicit MIME type
76
+ tags={"monitoring", "status"}, # Categorization tags
77
+ meta={"version": "2.1", "team": "infrastructure"} # Custom metadata
78
  )
79
  def get_application_status() -> dict:
80
  """Internal function description (ignored if description is provided above)."""
 
117
  </ParamField>
118
  </Expandable>
119
  </ParamField>
120
+
121
+ <ParamField body="meta" type="dict[str, Any] | None">
122
+ <VersionBadge version="2.11.0" />
123
+
124
+ Optional meta information about the resource. This data is passed through to the MCP client as the `_meta` field of the client-side resource object and can be used for custom metadata, versioning, or other application-specific purposes.
125
+ </ParamField>
126
  </Card>
127
 
128
  ### Return Values
docs/servers/tools.mdx CHANGED
@@ -58,6 +58,7 @@ While FastMCP infers the name and description from your function, you can overri
58
  name="find_products", # Custom tool name for the LLM
59
  description="Search the product catalog with optional category filtering.", # Custom description
60
  tags={"catalog", "search"}, # Optional tags for organization/filtering
 
61
  )
62
  def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
63
  """Internal function description (ignored if description is provided above)."""
@@ -107,6 +108,12 @@ def search_products_implementation(query: str, category: str | None = None) -> l
107
  </ParamField>
108
  </Expandable>
109
  </ParamField>
 
 
 
 
 
 
110
  </Card>
111
 
112
 
 
58
  name="find_products", # Custom tool name for the LLM
59
  description="Search the product catalog with optional category filtering.", # Custom description
60
  tags={"catalog", "search"}, # Optional tags for organization/filtering
61
+ meta={"version": "1.2", "author": "product-team"} # Custom metadata
62
  )
63
  def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
64
  """Internal function description (ignored if description is provided above)."""
 
108
  </ParamField>
109
  </Expandable>
110
  </ParamField>
111
+
112
+ <ParamField body="meta" type="dict[str, Any] | None">
113
+ <VersionBadge version="2.11.0" />
114
+
115
+ Optional meta information about the tool. This data is passed through to the MCP client as the `_meta` field of the client-side tool object and can be used for custom metadata, versioning, or other application-specific purposes.
116
+ </ParamField>
117
  </Card>
118
 
119
 
src/fastmcp/prompts/prompt.py CHANGED
@@ -117,6 +117,7 @@ class Prompt(FastMCPComponent, ABC):
117
  description: str | None = None,
118
  tags: set[str] | None = None,
119
  enabled: bool | None = None,
 
120
  ) -> FunctionPrompt:
121
  """Create a Prompt from a function.
122
 
@@ -133,6 +134,7 @@ class Prompt(FastMCPComponent, ABC):
133
  description=description,
134
  tags=tags,
135
  enabled=enabled,
 
136
  )
137
 
138
  @abstractmethod
@@ -158,6 +160,7 @@ class FunctionPrompt(Prompt):
158
  description: str | None = None,
159
  tags: set[str] | None = None,
160
  enabled: bool | None = None,
 
161
  ) -> FunctionPrompt:
162
  """Create a Prompt from a function.
163
 
@@ -252,6 +255,7 @@ class FunctionPrompt(Prompt):
252
  tags=tags or set(),
253
  enabled=enabled if enabled is not None else True,
254
  fn=fn,
 
255
  )
256
 
257
  def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
 
117
  description: str | None = None,
118
  tags: set[str] | None = None,
119
  enabled: bool | None = None,
120
+ meta: dict[str, Any] | None = None,
121
  ) -> FunctionPrompt:
122
  """Create a Prompt from a function.
123
 
 
134
  description=description,
135
  tags=tags,
136
  enabled=enabled,
137
+ meta=meta,
138
  )
139
 
140
  @abstractmethod
 
160
  description: str | None = None,
161
  tags: set[str] | None = None,
162
  enabled: bool | None = None,
163
+ meta: dict[str, Any] | None = None,
164
  ) -> FunctionPrompt:
165
  """Create a Prompt from a function.
166
 
 
255
  tags=tags or set(),
256
  enabled=enabled if enabled is not None else True,
257
  fn=fn,
258
+ meta=meta,
259
  )
260
 
261
  def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
src/fastmcp/resources/resource.py CHANGED
@@ -76,6 +76,7 @@ class Resource(FastMCPComponent, abc.ABC):
76
  tags: set[str] | None = None,
77
  enabled: bool | None = None,
78
  annotations: Annotations | None = None,
 
79
  ) -> FunctionResource:
80
  return FunctionResource.from_function(
81
  fn=fn,
@@ -87,6 +88,7 @@ class Resource(FastMCPComponent, abc.ABC):
87
  tags=tags,
88
  enabled=enabled,
89
  annotations=annotations,
 
90
  )
91
 
92
  @field_validator("mime_type", mode="before")
@@ -172,6 +174,7 @@ class FunctionResource(Resource):
172
  tags: set[str] | None = None,
173
  enabled: bool | None = None,
174
  annotations: Annotations | None = None,
 
175
  ) -> FunctionResource:
176
  """Create a FunctionResource from a function."""
177
  if isinstance(uri, str):
@@ -186,6 +189,7 @@ class FunctionResource(Resource):
186
  tags=tags or set(),
187
  enabled=enabled if enabled is not None else True,
188
  annotations=annotations,
 
189
  )
190
 
191
  async def read(self) -> str | bytes:
 
76
  tags: set[str] | None = None,
77
  enabled: bool | None = None,
78
  annotations: Annotations | None = None,
79
+ meta: dict[str, Any] | None = None,
80
  ) -> FunctionResource:
81
  return FunctionResource.from_function(
82
  fn=fn,
 
88
  tags=tags,
89
  enabled=enabled,
90
  annotations=annotations,
91
+ meta=meta,
92
  )
93
 
94
  @field_validator("mime_type", mode="before")
 
174
  tags: set[str] | None = None,
175
  enabled: bool | None = None,
176
  annotations: Annotations | None = None,
177
+ meta: dict[str, Any] | None = None,
178
  ) -> FunctionResource:
179
  """Create a FunctionResource from a function."""
180
  if isinstance(uri, str):
 
189
  tags=tags or set(),
190
  enabled=enabled if enabled is not None else True,
191
  annotations=annotations,
192
+ meta=meta,
193
  )
194
 
195
  async def read(self) -> str | bytes:
src/fastmcp/resources/template.py CHANGED
@@ -96,6 +96,7 @@ class ResourceTemplate(FastMCPComponent):
96
  tags: set[str] | None = None,
97
  enabled: bool | None = None,
98
  annotations: Annotations | None = None,
 
99
  ) -> FunctionResourceTemplate:
100
  return FunctionResourceTemplate.from_function(
101
  fn=fn,
@@ -107,6 +108,7 @@ class ResourceTemplate(FastMCPComponent):
107
  tags=tags,
108
  enabled=enabled,
109
  annotations=annotations,
 
110
  )
111
 
112
  @field_validator("mime_type", mode="before")
@@ -219,6 +221,7 @@ class FunctionResourceTemplate(ResourceTemplate):
219
  tags: set[str] | None = None,
220
  enabled: bool | None = None,
221
  annotations: Annotations | None = None,
 
222
  ) -> FunctionResourceTemplate:
223
  """Create a template from a function."""
224
  from fastmcp.server.context import Context
@@ -304,4 +307,5 @@ class FunctionResourceTemplate(ResourceTemplate):
304
  tags=tags or set(),
305
  enabled=enabled if enabled is not None else True,
306
  annotations=annotations,
 
307
  )
 
96
  tags: set[str] | None = None,
97
  enabled: bool | None = None,
98
  annotations: Annotations | None = None,
99
+ meta: dict[str, Any] | None = None,
100
  ) -> FunctionResourceTemplate:
101
  return FunctionResourceTemplate.from_function(
102
  fn=fn,
 
108
  tags=tags,
109
  enabled=enabled,
110
  annotations=annotations,
111
+ meta=meta,
112
  )
113
 
114
  @field_validator("mime_type", mode="before")
 
221
  tags: set[str] | None = None,
222
  enabled: bool | None = None,
223
  annotations: Annotations | None = None,
224
+ meta: dict[str, Any] | None = None,
225
  ) -> FunctionResourceTemplate:
226
  """Create a template from a function."""
227
  from fastmcp.server.context import Context
 
307
  tags=tags or set(),
308
  enabled=enabled if enabled is not None else True,
309
  annotations=annotations,
310
+ meta=meta,
311
  )
src/fastmcp/server/server.py CHANGED
@@ -871,6 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
871
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
872
  annotations: ToolAnnotations | dict[str, Any] | None = None,
873
  exclude_args: list[str] | None = None,
 
874
  enabled: bool | None = None,
875
  ) -> FunctionTool: ...
876
 
@@ -886,6 +887,7 @@ class FastMCP(Generic[LifespanResultT]):
886
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
887
  annotations: ToolAnnotations | dict[str, Any] | None = None,
888
  exclude_args: list[str] | None = None,
 
889
  enabled: bool | None = None,
890
  ) -> Callable[[AnyFunction], FunctionTool]: ...
891
 
@@ -900,6 +902,7 @@ class FastMCP(Generic[LifespanResultT]):
900
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
901
  annotations: ToolAnnotations | dict[str, Any] | None = None,
902
  exclude_args: list[str] | None = None,
 
903
  enabled: bool | None = None,
904
  ) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
905
  """Decorator to register a tool.
@@ -923,6 +926,7 @@ class FastMCP(Generic[LifespanResultT]):
923
  output_schema: Optional JSON schema for the tool's output
924
  annotations: Optional annotations about the tool's behavior
925
  exclude_args: Optional list of argument names to exclude from the tool schema
 
926
  enabled: Optional boolean to enable or disable the tool
927
 
928
  Examples:
@@ -981,6 +985,7 @@ class FastMCP(Generic[LifespanResultT]):
981
  output_schema=output_schema,
982
  annotations=annotations,
983
  exclude_args=exclude_args,
 
984
  serializer=self._tool_serializer,
985
  enabled=enabled,
986
  )
@@ -1013,6 +1018,7 @@ class FastMCP(Generic[LifespanResultT]):
1013
  output_schema=output_schema,
1014
  annotations=annotations,
1015
  exclude_args=exclude_args,
 
1016
  enabled=enabled,
1017
  )
1018
 
@@ -1111,6 +1117,7 @@ class FastMCP(Generic[LifespanResultT]):
1111
  tags: set[str] | None = None,
1112
  enabled: bool | None = None,
1113
  annotations: Annotations | dict[str, Any] | None = None,
 
1114
  ) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
1115
  """Decorator to register a function as a resource.
1116
 
@@ -1135,6 +1142,7 @@ class FastMCP(Generic[LifespanResultT]):
1135
  tags: Optional set of tags for categorizing the resource
1136
  enabled: Optional boolean to enable or disable the resource
1137
  annotations: Optional annotations about the resource's behavior
 
1138
 
1139
  Examples:
1140
  Register a resource with a custom name:
@@ -1208,6 +1216,7 @@ class FastMCP(Generic[LifespanResultT]):
1208
  tags=tags,
1209
  enabled=enabled,
1210
  annotations=annotations,
 
1211
  )
1212
  self.add_template(template)
1213
  return template
@@ -1222,6 +1231,7 @@ class FastMCP(Generic[LifespanResultT]):
1222
  tags=tags,
1223
  enabled=enabled,
1224
  annotations=annotations,
 
1225
  )
1226
  self.add_resource(resource)
1227
  return resource
@@ -1266,6 +1276,7 @@ class FastMCP(Generic[LifespanResultT]):
1266
  description: str | None = None,
1267
  tags: set[str] | None = None,
1268
  enabled: bool | None = None,
 
1269
  ) -> FunctionPrompt: ...
1270
 
1271
  @overload
@@ -1278,6 +1289,7 @@ class FastMCP(Generic[LifespanResultT]):
1278
  description: str | None = None,
1279
  tags: set[str] | None = None,
1280
  enabled: bool | None = None,
 
1281
  ) -> Callable[[AnyFunction], FunctionPrompt]: ...
1282
 
1283
  def prompt(
@@ -1289,6 +1301,7 @@ class FastMCP(Generic[LifespanResultT]):
1289
  description: str | None = None,
1290
  tags: set[str] | None = None,
1291
  enabled: bool | None = None,
 
1292
  ) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
1293
  """Decorator to register a prompt.
1294
 
@@ -1309,6 +1322,7 @@ class FastMCP(Generic[LifespanResultT]):
1309
  description: Optional description of what the prompt does
1310
  tags: Optional set of tags for categorizing the prompt
1311
  enabled: Optional boolean to enable or disable the prompt
 
1312
 
1313
  Examples:
1314
 
@@ -1386,6 +1400,7 @@ class FastMCP(Generic[LifespanResultT]):
1386
  description=description,
1387
  tags=tags,
1388
  enabled=enabled,
 
1389
  )
1390
  self.add_prompt(prompt)
1391
 
@@ -1415,6 +1430,7 @@ class FastMCP(Generic[LifespanResultT]):
1415
  description=description,
1416
  tags=tags,
1417
  enabled=enabled,
 
1418
  )
1419
 
1420
  async def run_stdio_async(self, show_banner: bool = True) -> None:
 
871
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
872
  annotations: ToolAnnotations | dict[str, Any] | None = None,
873
  exclude_args: list[str] | None = None,
874
+ meta: dict[str, Any] | None = None,
875
  enabled: bool | None = None,
876
  ) -> FunctionTool: ...
877
 
 
887
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
888
  annotations: ToolAnnotations | dict[str, Any] | None = None,
889
  exclude_args: list[str] | None = None,
890
+ meta: dict[str, Any] | None = None,
891
  enabled: bool | None = None,
892
  ) -> Callable[[AnyFunction], FunctionTool]: ...
893
 
 
902
  output_schema: dict[str, Any] | None | NotSetT = NotSet,
903
  annotations: ToolAnnotations | dict[str, Any] | None = None,
904
  exclude_args: list[str] | None = None,
905
+ meta: dict[str, Any] | None = None,
906
  enabled: bool | None = None,
907
  ) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
908
  """Decorator to register a tool.
 
926
  output_schema: Optional JSON schema for the tool's output
927
  annotations: Optional annotations about the tool's behavior
928
  exclude_args: Optional list of argument names to exclude from the tool schema
929
+ meta: Optional meta information about the tool
930
  enabled: Optional boolean to enable or disable the tool
931
 
932
  Examples:
 
985
  output_schema=output_schema,
986
  annotations=annotations,
987
  exclude_args=exclude_args,
988
+ meta=meta,
989
  serializer=self._tool_serializer,
990
  enabled=enabled,
991
  )
 
1018
  output_schema=output_schema,
1019
  annotations=annotations,
1020
  exclude_args=exclude_args,
1021
+ meta=meta,
1022
  enabled=enabled,
1023
  )
1024
 
 
1117
  tags: set[str] | None = None,
1118
  enabled: bool | None = None,
1119
  annotations: Annotations | dict[str, Any] | None = None,
1120
+ meta: dict[str, Any] | None = None,
1121
  ) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
1122
  """Decorator to register a function as a resource.
1123
 
 
1142
  tags: Optional set of tags for categorizing the resource
1143
  enabled: Optional boolean to enable or disable the resource
1144
  annotations: Optional annotations about the resource's behavior
1145
+ meta: Optional meta information about the resource
1146
 
1147
  Examples:
1148
  Register a resource with a custom name:
 
1216
  tags=tags,
1217
  enabled=enabled,
1218
  annotations=annotations,
1219
+ meta=meta,
1220
  )
1221
  self.add_template(template)
1222
  return template
 
1231
  tags=tags,
1232
  enabled=enabled,
1233
  annotations=annotations,
1234
+ meta=meta,
1235
  )
1236
  self.add_resource(resource)
1237
  return resource
 
1276
  description: str | None = None,
1277
  tags: set[str] | None = None,
1278
  enabled: bool | None = None,
1279
+ meta: dict[str, Any] | None = None,
1280
  ) -> FunctionPrompt: ...
1281
 
1282
  @overload
 
1289
  description: str | None = None,
1290
  tags: set[str] | None = None,
1291
  enabled: bool | None = None,
1292
+ meta: dict[str, Any] | None = None,
1293
  ) -> Callable[[AnyFunction], FunctionPrompt]: ...
1294
 
1295
  def prompt(
 
1301
  description: str | None = None,
1302
  tags: set[str] | None = None,
1303
  enabled: bool | None = None,
1304
+ meta: dict[str, Any] | None = None,
1305
  ) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
1306
  """Decorator to register a prompt.
1307
 
 
1322
  description: Optional description of what the prompt does
1323
  tags: Optional set of tags for categorizing the prompt
1324
  enabled: Optional boolean to enable or disable the prompt
1325
+ meta: Optional meta information about the prompt
1326
 
1327
  Examples:
1328
 
 
1400
  description=description,
1401
  tags=tags,
1402
  enabled=enabled,
1403
+ meta=meta,
1404
  )
1405
  self.add_prompt(prompt)
1406
 
 
1430
  description=description,
1431
  tags=tags,
1432
  enabled=enabled,
1433
+ meta=meta,
1434
  )
1435
 
1436
  async def run_stdio_async(self, show_banner: bool = True) -> None:
src/fastmcp/tools/tool.py CHANGED
@@ -165,6 +165,7 @@ class Tool(FastMCPComponent):
165
  exclude_args: list[str] | None = None,
166
  output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
167
  serializer: Callable[[Any], str] | None = None,
 
168
  enabled: bool | None = None,
169
  ) -> FunctionTool:
170
  """Create a Tool from a function."""
@@ -178,6 +179,7 @@ class Tool(FastMCPComponent):
178
  exclude_args=exclude_args,
179
  output_schema=output_schema,
180
  serializer=serializer,
 
181
  enabled=enabled,
182
  )
183
 
@@ -240,6 +242,7 @@ class FunctionTool(Tool):
240
  exclude_args: list[str] | None = None,
241
  output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
242
  serializer: Callable[[Any], str] | None = None,
 
243
  enabled: bool | None = None,
244
  ) -> FunctionTool:
245
  """Create a Tool from a function."""
@@ -272,6 +275,7 @@ class FunctionTool(Tool):
272
  annotations=annotations,
273
  tags=tags or set(),
274
  serializer=serializer,
 
275
  enabled=enabled if enabled is not None else True,
276
  )
277
 
 
165
  exclude_args: list[str] | None = None,
166
  output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
167
  serializer: Callable[[Any], str] | None = None,
168
+ meta: dict[str, Any] | None = None,
169
  enabled: bool | None = None,
170
  ) -> FunctionTool:
171
  """Create a Tool from a function."""
 
179
  exclude_args=exclude_args,
180
  output_schema=output_schema,
181
  serializer=serializer,
182
+ meta=meta,
183
  enabled=enabled,
184
  )
185
 
 
242
  exclude_args: list[str] | None = None,
243
  output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
244
  serializer: Callable[[Any], str] | None = None,
245
+ meta: dict[str, Any] | None = None,
246
  enabled: bool | None = None,
247
  ) -> FunctionTool:
248
  """Create a Tool from a function."""
 
275
  annotations=annotations,
276
  tags=tags or set(),
277
  serializer=serializer,
278
+ meta=meta,
279
  enabled=enabled if enabled is not None else True,
280
  )
281
 
tests/prompts/test_prompt.py CHANGED
@@ -482,3 +482,18 @@ class TestPromptArgumentDescriptions:
482
  "Provide as a JSON string matching the following schema:"
483
  not in arg.description
484
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  "Provide as a JSON string matching the following schema:"
483
  not in arg.description
484
  )
485
+
486
+ def test_prompt_meta_parameter(self):
487
+ """Test that meta parameter is properly handled."""
488
+
489
+ def test_prompt(message: str) -> str:
490
+ return f"Response: {message}"
491
+
492
+ meta_data = {"version": "3.0", "type": "prompt"}
493
+ prompt = Prompt.from_function(test_prompt, meta=meta_data)
494
+
495
+ assert prompt.meta == meta_data
496
+ mcp_prompt = prompt.to_mcp_prompt()
497
+ # MCP prompt includes fastmcp meta, so check that our meta is included
498
+ assert mcp_prompt.meta is not None
499
+ assert meta_data.items() <= mcp_prompt.meta.items()
tests/resources/test_resource_template_meta.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastmcp.resources import ResourceTemplate
2
+
3
+
4
+ class TestResourceTemplateMeta:
5
+ """Test ResourceTemplate meta functionality."""
6
+
7
+ def test_template_meta_parameter(self):
8
+ """Test that meta parameter is properly handled."""
9
+
10
+ def template_func(param: str) -> str:
11
+ return f"Result: {param}"
12
+
13
+ meta_data = {"version": "2.0", "template": "test"}
14
+ template = ResourceTemplate.from_function(
15
+ fn=template_func,
16
+ uri_template="test://{param}",
17
+ name="test_template",
18
+ meta=meta_data,
19
+ )
20
+
21
+ assert template.meta == meta_data
22
+ mcp_template = template.to_mcp_template()
23
+ # MCP template includes fastmcp meta, so check that our meta is included
24
+ assert mcp_template.meta is not None
25
+ assert meta_data.items() <= mcp_template.meta.items()
tests/resources/test_resources.py CHANGED
@@ -93,3 +93,23 @@ class TestResourceValidation:
93
 
94
  with pytest.raises(TypeError, match="abstract method"):
95
  ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
  with pytest.raises(TypeError, match="abstract method"):
95
  ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore
96
+
97
+ def test_resource_meta_parameter(self):
98
+ """Test that meta parameter is properly handled."""
99
+
100
+ def resource_func() -> str:
101
+ return "test content"
102
+
103
+ meta_data = {"version": "1.0", "category": "test"}
104
+ resource = Resource.from_function(
105
+ fn=resource_func,
106
+ uri="resource://test",
107
+ name="test_resource",
108
+ meta=meta_data,
109
+ )
110
+
111
+ assert resource.meta == meta_data
112
+ mcp_resource = resource.to_mcp_resource()
113
+ # MCP resource includes fastmcp meta, so check that our meta is included
114
+ assert mcp_resource.meta is not None
115
+ assert meta_data.items() <= mcp_resource.meta.items()
tests/server/test_server.py CHANGED
@@ -421,6 +421,22 @@ class TestToolDecorator:
421
  def my_function(x: int) -> str:
422
  return f"Result: {x}"
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
  class TestResourceDecorator:
426
  async def test_no_resources_before_decorator(self):
@@ -584,6 +600,21 @@ class TestResourceDecorator:
584
  result = await client.read_resource("resource://data")
585
  assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
 
588
  class TestTemplateDecorator:
589
  async def test_template_decorator(self):
@@ -733,6 +764,21 @@ class TestTemplateDecorator:
733
  assert template.uri_template == "resource://{param*}"
734
  assert template.name == "template_resource"
735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
 
737
  class TestPromptDecorator:
738
  async def test_prompt_decorator(self):
@@ -988,6 +1034,21 @@ class TestPromptDecorator:
988
  message = result.messages[0]
989
  assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
991
 
992
  class TestResourcePrefixHelpers:
993
  @pytest.mark.parametrize(
 
421
  def my_function(x: int) -> str:
422
  return f"Result: {x}"
423
 
424
+ async def test_tool_decorator_with_meta(self):
425
+ """Test that meta parameter is passed through the tool decorator."""
426
+ mcp = FastMCP()
427
+
428
+ meta_data = {"version": "1.0", "author": "test"}
429
+
430
+ @mcp.tool(meta=meta_data)
431
+ def multiply(a: int, b: int) -> int:
432
+ """Multiply two numbers."""
433
+ return a * b
434
+
435
+ tools_dict = await mcp.get_tools()
436
+ tool = tools_dict["multiply"]
437
+
438
+ assert tool.meta == meta_data
439
+
440
 
441
  class TestResourceDecorator:
442
  async def test_no_resources_before_decorator(self):
 
600
  result = await client.read_resource("resource://data")
601
  assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
602
 
603
+ async def test_resource_decorator_with_meta(self):
604
+ """Test that meta parameter is passed through the resource decorator."""
605
+ mcp = FastMCP()
606
+
607
+ meta_data = {"version": "1.0", "author": "test"}
608
+
609
+ @mcp.resource("resource://data", meta=meta_data)
610
+ def get_data() -> str:
611
+ return "Hello, world!"
612
+
613
+ resources_dict = await mcp.get_resources()
614
+ resource = resources_dict["resource://data"]
615
+
616
+ assert resource.meta == meta_data
617
+
618
 
619
  class TestTemplateDecorator:
620
  async def test_template_decorator(self):
 
764
  assert template.uri_template == "resource://{param*}"
765
  assert template.name == "template_resource"
766
 
767
+ async def test_template_decorator_with_meta(self):
768
+ """Test that meta parameter is passed through the template decorator."""
769
+ mcp = FastMCP()
770
+
771
+ meta_data = {"version": "2.0", "template": "test"}
772
+
773
+ @mcp.resource("resource://{param}/data", meta=meta_data)
774
+ def get_template_data(param: str) -> str:
775
+ return f"Data for {param}"
776
+
777
+ templates_dict = await mcp.get_resource_templates()
778
+ template = templates_dict["resource://{param}/data"]
779
+
780
+ assert template.meta == meta_data
781
+
782
 
783
  class TestPromptDecorator:
784
  async def test_prompt_decorator(self):
 
1034
  message = result.messages[0]
1035
  assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
1036
 
1037
+ async def test_prompt_decorator_with_meta(self):
1038
+ """Test that meta parameter is passed through the prompt decorator."""
1039
+ mcp = FastMCP()
1040
+
1041
+ meta_data = {"version": "3.0", "type": "prompt"}
1042
+
1043
+ @mcp.prompt(meta=meta_data)
1044
+ def test_prompt(message: str) -> str:
1045
+ return f"Response: {message}"
1046
+
1047
+ prompts_dict = await mcp.get_prompts()
1048
+ prompt = prompts_dict["test_prompt"]
1049
+
1050
+ assert prompt.meta == meta_data
1051
+
1052
 
1053
  class TestResourcePrefixHelpers:
1054
  @pytest.mark.parametrize(
tests/tools/test_tool.py CHANGED
@@ -44,6 +44,22 @@ class TestToolFromFunction:
44
  }
45
  assert tool.output_schema == expected_schema
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  async def test_async_function(self):
48
  """Test registering and running an async function."""
49
 
 
44
  }
45
  assert tool.output_schema == expected_schema
46
 
47
+ def test_meta_parameter(self):
48
+ """Test that meta parameter is properly handled."""
49
+
50
+ def multiply(a: int, b: int) -> int:
51
+ """Multiply two numbers."""
52
+ return a * b
53
+
54
+ meta_data = {"version": "1.0", "author": "test"}
55
+ tool = Tool.from_function(multiply, meta=meta_data)
56
+
57
+ assert tool.meta == meta_data
58
+ mcp_tool = tool.to_mcp_tool()
59
+ # MCP tool includes fastmcp meta, so check that our meta is included
60
+ assert mcp_tool.meta is not None
61
+ assert meta_data.items() <= mcp_tool.meta.items()
62
+
63
  async def test_async_function(self):
64
  """Test registering and running an async function."""
65