fastmcp 2.9.2__py3-none-any.whl → 2.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. fastmcp/client/auth/oauth.py +5 -82
  2. fastmcp/client/client.py +114 -24
  3. fastmcp/client/elicitation.py +63 -0
  4. fastmcp/client/transports.py +50 -36
  5. fastmcp/contrib/component_manager/README.md +170 -0
  6. fastmcp/contrib/component_manager/__init__.py +4 -0
  7. fastmcp/contrib/component_manager/component_manager.py +186 -0
  8. fastmcp/contrib/component_manager/component_service.py +225 -0
  9. fastmcp/contrib/component_manager/example.py +59 -0
  10. fastmcp/prompts/prompt.py +12 -4
  11. fastmcp/resources/resource.py +8 -3
  12. fastmcp/resources/template.py +5 -0
  13. fastmcp/server/auth/auth.py +15 -0
  14. fastmcp/server/auth/providers/bearer.py +41 -3
  15. fastmcp/server/auth/providers/bearer_env.py +4 -0
  16. fastmcp/server/auth/providers/in_memory.py +15 -0
  17. fastmcp/server/context.py +144 -4
  18. fastmcp/server/elicitation.py +160 -0
  19. fastmcp/server/http.py +1 -9
  20. fastmcp/server/low_level.py +4 -2
  21. fastmcp/server/middleware/__init__.py +14 -1
  22. fastmcp/server/middleware/logging.py +11 -0
  23. fastmcp/server/middleware/middleware.py +10 -6
  24. fastmcp/server/openapi.py +19 -77
  25. fastmcp/server/proxy.py +13 -6
  26. fastmcp/server/server.py +27 -7
  27. fastmcp/settings.py +0 -17
  28. fastmcp/tools/tool.py +209 -57
  29. fastmcp/tools/tool_manager.py +2 -3
  30. fastmcp/tools/tool_transform.py +125 -26
  31. fastmcp/utilities/components.py +5 -1
  32. fastmcp/utilities/json_schema_type.py +648 -0
  33. fastmcp/utilities/openapi.py +69 -0
  34. fastmcp/utilities/types.py +50 -19
  35. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/METADATA +3 -2
  36. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/RECORD +39 -31
  37. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/WHEEL +0 -0
  38. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/entry_points.txt +0 -0
  39. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/prompts/prompt.py CHANGED
@@ -9,9 +9,9 @@ from collections.abc import Awaitable, Callable, Sequence
9
9
  from typing import Any
10
10
 
11
11
  import pydantic_core
12
+ from mcp.types import ContentBlock, PromptMessage, Role, TextContent
12
13
  from mcp.types import Prompt as MCPPrompt
13
14
  from mcp.types import PromptArgument as MCPPromptArgument
14
- from mcp.types import PromptMessage, Role, TextContent
15
15
  from pydantic import Field, TypeAdapter
16
16
 
17
17
  from fastmcp.exceptions import PromptError
@@ -21,7 +21,6 @@ from fastmcp.utilities.json_schema import compress_schema
21
21
  from fastmcp.utilities.logging import get_logger
22
22
  from fastmcp.utilities.types import (
23
23
  FastMCPBaseModel,
24
- MCPContent,
25
24
  find_kwarg_by_type,
26
25
  get_cached_typeadapter,
27
26
  )
@@ -30,7 +29,7 @@ logger = get_logger(__name__)
30
29
 
31
30
 
32
31
  def Message(
33
- content: str | MCPContent, role: Role | None = None, **kwargs: Any
32
+ content: str | ContentBlock, role: Role | None = None, **kwargs: Any
34
33
  ) -> PromptMessage:
35
34
  """A user-friendly constructor for PromptMessage."""
36
35
  if isinstance(content, str):
@@ -100,6 +99,7 @@ class Prompt(FastMCPComponent, ABC):
100
99
  "name": self.name,
101
100
  "description": self.description,
102
101
  "arguments": arguments,
102
+ "title": self.title,
103
103
  }
104
104
  return MCPPrompt(**kwargs | overrides)
105
105
 
@@ -107,6 +107,7 @@ class Prompt(FastMCPComponent, ABC):
107
107
  def from_function(
108
108
  fn: Callable[..., PromptResult | Awaitable[PromptResult]],
109
109
  name: str | None = None,
110
+ title: str | None = None,
110
111
  description: str | None = None,
111
112
  tags: set[str] | None = None,
112
113
  enabled: bool | None = None,
@@ -120,7 +121,12 @@ class Prompt(FastMCPComponent, ABC):
120
121
  - A sequence of any of the above
121
122
  """
122
123
  return FunctionPrompt.from_function(
123
- fn=fn, name=name, description=description, tags=tags, enabled=enabled
124
+ fn=fn,
125
+ name=name,
126
+ title=title,
127
+ description=description,
128
+ tags=tags,
129
+ enabled=enabled,
124
130
  )
125
131
 
126
132
  @abstractmethod
@@ -142,6 +148,7 @@ class FunctionPrompt(Prompt):
142
148
  cls,
143
149
  fn: Callable[..., PromptResult | Awaitable[PromptResult]],
144
150
  name: str | None = None,
151
+ title: str | None = None,
145
152
  description: str | None = None,
146
153
  tags: set[str] | None = None,
147
154
  enabled: bool | None = None,
@@ -233,6 +240,7 @@ class FunctionPrompt(Prompt):
233
240
 
234
241
  return cls(
235
242
  name=func_name,
243
+ title=title,
236
244
  description=description,
237
245
  arguments=arguments,
238
246
  tags=tags or set(),
@@ -62,9 +62,10 @@ class Resource(FastMCPComponent, abc.ABC):
62
62
 
63
63
  @staticmethod
64
64
  def from_function(
65
- fn: Callable[[], Any],
65
+ fn: Callable[..., Any],
66
66
  uri: str | AnyUrl,
67
67
  name: str | None = None,
68
+ title: str | None = None,
68
69
  description: str | None = None,
69
70
  mime_type: str | None = None,
70
71
  tags: set[str] | None = None,
@@ -74,6 +75,7 @@ class Resource(FastMCPComponent, abc.ABC):
74
75
  fn=fn,
75
76
  uri=uri,
76
77
  name=name,
78
+ title=title,
77
79
  description=description,
78
80
  mime_type=mime_type,
79
81
  tags=tags,
@@ -111,6 +113,7 @@ class Resource(FastMCPComponent, abc.ABC):
111
113
  "name": self.name,
112
114
  "description": self.description,
113
115
  "mimeType": self.mime_type,
116
+ "title": self.title,
114
117
  }
115
118
  return MCPResource(**kwargs | overrides)
116
119
 
@@ -141,14 +144,15 @@ class FunctionResource(Resource):
141
144
  - other types will be converted to JSON
142
145
  """
143
146
 
144
- fn: Callable[[], Any]
147
+ fn: Callable[..., Any]
145
148
 
146
149
  @classmethod
147
150
  def from_function(
148
151
  cls,
149
- fn: Callable[[], Any],
152
+ fn: Callable[..., Any],
150
153
  uri: str | AnyUrl,
151
154
  name: str | None = None,
155
+ title: str | None = None,
152
156
  description: str | None = None,
153
157
  mime_type: str | None = None,
154
158
  tags: set[str] | None = None,
@@ -161,6 +165,7 @@ class FunctionResource(Resource):
161
165
  fn=fn,
162
166
  uri=uri,
163
167
  name=name or fn.__name__,
168
+ title=title,
164
169
  description=description or inspect.getdoc(fn),
165
170
  mime_type=mime_type or "text/plain",
166
171
  tags=tags or set(),
@@ -86,6 +86,7 @@ class ResourceTemplate(FastMCPComponent):
86
86
  fn: Callable[..., Any],
87
87
  uri_template: str,
88
88
  name: str | None = None,
89
+ title: str | None = None,
89
90
  description: str | None = None,
90
91
  mime_type: str | None = None,
91
92
  tags: set[str] | None = None,
@@ -95,6 +96,7 @@ class ResourceTemplate(FastMCPComponent):
95
96
  fn=fn,
96
97
  uri_template=uri_template,
97
98
  name=name,
99
+ title=title,
98
100
  description=description,
99
101
  mime_type=mime_type,
100
102
  tags=tags,
@@ -144,6 +146,7 @@ class ResourceTemplate(FastMCPComponent):
144
146
  "name": self.name,
145
147
  "description": self.description,
146
148
  "mimeType": self.mime_type,
149
+ "title": self.title,
147
150
  }
148
151
  return MCPResourceTemplate(**kwargs | overrides)
149
152
 
@@ -197,6 +200,7 @@ class FunctionResourceTemplate(ResourceTemplate):
197
200
  fn: Callable[..., Any],
198
201
  uri_template: str,
199
202
  name: str | None = None,
203
+ title: str | None = None,
200
204
  description: str | None = None,
201
205
  mime_type: str | None = None,
202
206
  tags: set[str] | None = None,
@@ -278,6 +282,7 @@ class FunctionResourceTemplate(ResourceTemplate):
278
282
  return cls(
279
283
  uri_template=uri_template,
280
284
  name=func_name,
285
+ title=title,
281
286
  description=description,
282
287
  mime_type=mime_type or "text/plain",
283
288
  fn=fn,
@@ -43,3 +43,18 @@ class OAuthProvider(
43
43
  self.client_registration_options = client_registration_options
44
44
  self.revocation_options = revocation_options
45
45
  self.required_scopes = required_scopes
46
+
47
+ async def verify_token(self, token: str) -> AccessToken | None:
48
+ """
49
+ Verify a bearer token and return access info if valid.
50
+
51
+ This method implements the TokenVerifier protocol by delegating
52
+ to our existing load_access_token method.
53
+
54
+ Args:
55
+ token: The token string to validate
56
+
57
+ Returns:
58
+ AccessToken object if valid, None if invalid or expired
59
+ """
60
+ return await self.load_access_token(token)
@@ -1,6 +1,6 @@
1
1
  import time
2
2
  from dataclasses import dataclass
3
- from typing import Any, TypedDict
3
+ from typing import Any
4
4
 
5
5
  import httpx
6
6
  from authlib.jose import JsonWebKey, JsonWebToken
@@ -18,6 +18,7 @@ from mcp.shared.auth import (
18
18
  OAuthToken,
19
19
  )
20
20
  from pydantic import AnyHttpUrl, SecretStr, ValidationError
21
+ from typing_extensions import TypedDict
21
22
 
22
23
  from fastmcp.server.auth.auth import (
23
24
  ClientRegistrationOptions,
@@ -112,6 +113,7 @@ class RSAKeyPair:
112
113
  Returns:
113
114
  Signed JWT token string
114
115
  """
116
+ # TODO : Add support for configurable algorithms
115
117
  jwt = JsonWebToken(["RS256"])
116
118
 
117
119
  now = int(time.time())
@@ -150,7 +152,7 @@ class RSAKeyPair:
150
152
  class BearerAuthProvider(OAuthProvider):
151
153
  """
152
154
  Simple JWT Bearer Token validator for hosted MCP servers.
153
- Uses RS256 asymmetric encryption. Supports either static public key
155
+ Uses RS256 asymmetric encryption by default but supports all JWA algorithms. Supports either static public key
154
156
  or JWKS URI for key rotation.
155
157
 
156
158
  Note that this provider DOES NOT permit client registration or revocation, or any OAuth flows.
@@ -162,6 +164,7 @@ class BearerAuthProvider(OAuthProvider):
162
164
  public_key: str | None = None,
163
165
  jwks_uri: str | None = None,
164
166
  issuer: str | None = None,
167
+ algorithm: str | None = None,
165
168
  audience: str | list[str] | None = None,
166
169
  required_scopes: list[str] | None = None,
167
170
  ):
@@ -172,6 +175,7 @@ class BearerAuthProvider(OAuthProvider):
172
175
  public_key: RSA public key in PEM format (for static key)
173
176
  jwks_uri: URI to fetch keys from (for key rotation)
174
177
  issuer: Expected issuer claim (optional)
178
+ algorithm: Algorithm to use for verification (optional, defaults to RS256)
175
179
  audience: Expected audience claim - can be a string or list of strings (optional)
176
180
  required_scopes: List of required scopes for access (optional)
177
181
  """
@@ -180,6 +184,24 @@ class BearerAuthProvider(OAuthProvider):
180
184
  if public_key and jwks_uri:
181
185
  raise ValueError("Provide either public_key or jwks_uri, not both")
182
186
 
187
+ if not algorithm:
188
+ algorithm = "RS256"
189
+ if algorithm not in {
190
+ "HS256",
191
+ "HS384",
192
+ "HS512",
193
+ "RS256",
194
+ "RS384",
195
+ "RS512",
196
+ "ES256",
197
+ "ES384",
198
+ "ES512",
199
+ "PS256",
200
+ "PS384",
201
+ "PS512",
202
+ }:
203
+ raise ValueError(f"Unsupported algorithm: {algorithm}.")
204
+
183
205
  # Only pass issuer to parent if it's a valid URL, otherwise use default
184
206
  # This allows the issuer claim validation to work with string issuers per RFC 7519
185
207
  try:
@@ -195,11 +217,12 @@ class BearerAuthProvider(OAuthProvider):
195
217
  required_scopes=required_scopes,
196
218
  )
197
219
 
220
+ self.algorithm = algorithm
198
221
  self.issuer = issuer
199
222
  self.audience = audience
200
223
  self.public_key = public_key
201
224
  self.jwks_uri = jwks_uri
202
- self.jwt = JsonWebToken(["RS256"])
225
+ self.jwt = JsonWebToken([self.algorithm]) # Use RS256 by default
203
226
  self.logger = get_logger(__name__)
204
227
 
205
228
  # Simple JWKS cache
@@ -384,6 +407,21 @@ class BearerAuthProvider(OAuthProvider):
384
407
  return scope_claim
385
408
  return []
386
409
 
410
+ async def verify_token(self, token: str) -> AccessToken | None:
411
+ """
412
+ Verify a bearer token and return access info if valid.
413
+
414
+ This method implements the TokenVerifier protocol by delegating
415
+ to our existing load_access_token method.
416
+
417
+ Args:
418
+ token: The JWT token string to validate
419
+
420
+ Returns:
421
+ AccessToken object if valid, None if invalid or expired
422
+ """
423
+ return await self.load_access_token(token)
424
+
387
425
  # --- Unused OAuth server methods ---
388
426
  async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
389
427
  raise NotImplementedError("Client management not supported")
@@ -17,6 +17,7 @@ class EnvBearerAuthProviderSettings(BaseSettings):
17
17
  public_key: str | None = None
18
18
  jwks_uri: str | None = None
19
19
  issuer: str | None = None
20
+ algorithm: str | None = None
20
21
  audience: str | None = None
21
22
  required_scopes: list[str] | None = None
22
23
 
@@ -33,6 +34,7 @@ class EnvBearerAuthProvider(BearerAuthProvider):
33
34
  public_key: str | None | EllipsisType = ...,
34
35
  jwks_uri: str | None | EllipsisType = ...,
35
36
  issuer: str | None | EllipsisType = ...,
37
+ algorithm: str | None | EllipsisType = ...,
36
38
  audience: str | None | EllipsisType = ...,
37
39
  required_scopes: list[str] | None | EllipsisType = ...,
38
40
  ):
@@ -43,6 +45,7 @@ class EnvBearerAuthProvider(BearerAuthProvider):
43
45
  public_key: RSA public key in PEM format (for static key)
44
46
  jwks_uri: URI to fetch keys from (for key rotation)
45
47
  issuer: Expected issuer claim (optional)
48
+ algorithm: Algorithm to use for verification (optional)
46
49
  audience: Expected audience claim (optional)
47
50
  required_scopes: List of required scopes for access (optional)
48
51
  """
@@ -50,6 +53,7 @@ class EnvBearerAuthProvider(BearerAuthProvider):
50
53
  "public_key": public_key,
51
54
  "jwks_uri": jwks_uri,
52
55
  "issuer": issuer,
56
+ "algorithm": algorithm,
53
57
  "audience": audience,
54
58
  "required_scopes": required_scopes,
55
59
  }
@@ -271,6 +271,21 @@ class InMemoryOAuthProvider(OAuthProvider):
271
271
  return token_obj
272
272
  return None
273
273
 
274
+ async def verify_token(self, token: str) -> AccessToken | None:
275
+ """
276
+ Verify a bearer token and return access info if valid.
277
+
278
+ This method implements the TokenVerifier protocol by delegating
279
+ to our existing load_access_token method.
280
+
281
+ Args:
282
+ token: The token string to validate
283
+
284
+ Returns:
285
+ AccessToken object if valid, None if invalid or expired
286
+ """
287
+ return await self.load_access_token(token)
288
+
274
289
  def _revoke_internal(
275
290
  self, access_token_str: str | None = None, refresh_token_str: str | None = None
276
291
  ):
fastmcp/server/context.py CHANGED
@@ -1,4 +1,4 @@
1
- from __future__ import annotations as _annotations
1
+ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import warnings
@@ -6,12 +6,15 @@ from collections.abc import Generator
6
6
  from contextlib import contextmanager
7
7
  from contextvars import ContextVar, Token
8
8
  from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Any, Literal, TypeVar, cast, get_origin, overload
9
11
 
10
12
  from mcp import LoggingLevel, ServerSession
11
13
  from mcp.server.lowlevel.helper_types import ReadResourceContents
12
14
  from mcp.server.lowlevel.server import request_ctx
13
15
  from mcp.shared.context import RequestContext
14
16
  from mcp.types import (
17
+ ContentBlock,
15
18
  CreateMessageResult,
16
19
  ModelHint,
17
20
  ModelPreferences,
@@ -24,12 +27,20 @@ from starlette.requests import Request
24
27
 
25
28
  import fastmcp.server.dependencies
26
29
  from fastmcp import settings
30
+ from fastmcp.server.elicitation import (
31
+ AcceptedElicitation,
32
+ CancelledElicitation,
33
+ DeclinedElicitation,
34
+ ScalarElicitationType,
35
+ get_elicitation_schema,
36
+ )
27
37
  from fastmcp.server.server import FastMCP
28
38
  from fastmcp.utilities.logging import get_logger
29
- from fastmcp.utilities.types import MCPContent
39
+ from fastmcp.utilities.types import get_cached_typeadapter
30
40
 
31
41
  logger = get_logger(__name__)
32
42
 
43
+ T = TypeVar("T")
33
44
  _current_context: ContextVar[Context | None] = ContextVar("context", default=None)
34
45
  _flush_lock = asyncio.Lock()
35
46
 
@@ -167,7 +178,10 @@ class Context:
167
178
  if level is None:
168
179
  level = "info"
169
180
  await self.session.send_log_message(
170
- level=level, data=message, logger=logger_name
181
+ level=level,
182
+ data=message,
183
+ logger=logger_name,
184
+ related_request_id=self.request_id,
171
185
  )
172
186
 
173
187
  @property
@@ -261,7 +275,7 @@ class Context:
261
275
  temperature: float | None = None,
262
276
  max_tokens: int | None = None,
263
277
  model_preferences: ModelPreferences | str | list[str] | None = None,
264
- ) -> MCPContent:
278
+ ) -> ContentBlock:
265
279
  """
266
280
  Send a sampling request to the client and await the response.
267
281
 
@@ -293,10 +307,136 @@ class Context:
293
307
  temperature=temperature,
294
308
  max_tokens=max_tokens,
295
309
  model_preferences=self._parse_model_preferences(model_preferences),
310
+ related_request_id=self.request_id,
296
311
  )
297
312
 
298
313
  return result.content
299
314
 
315
+ @overload
316
+ async def elicit(
317
+ self,
318
+ message: str,
319
+ response_type: None,
320
+ ) -> (
321
+ AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
322
+ ): ...
323
+
324
+ """When response_type is None, the accepted elicitaiton will contain an
325
+ empty dict"""
326
+
327
+ @overload
328
+ async def elicit(
329
+ self,
330
+ message: str,
331
+ response_type: type[T],
332
+ ) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation: ...
333
+
334
+ """When response_type is not None, the accepted elicitaiton will contain the
335
+ response data"""
336
+
337
+ @overload
338
+ async def elicit(
339
+ self,
340
+ message: str,
341
+ response_type: list[str],
342
+ ) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation: ...
343
+
344
+ """When response_type is a list of strings, the accepted elicitaiton will
345
+ contain the selected string response"""
346
+
347
+ async def elicit(
348
+ self,
349
+ message: str,
350
+ response_type: type[T] | list[str] | None = None,
351
+ ) -> (
352
+ AcceptedElicitation[T]
353
+ | AcceptedElicitation[dict[str, Any]]
354
+ | AcceptedElicitation[str]
355
+ | DeclinedElicitation
356
+ | CancelledElicitation
357
+ ):
358
+ """
359
+ Send an elicitation request to the client and await the response.
360
+
361
+ Call this method at any time to request additional information from
362
+ the user through the client. The client must support elicitation,
363
+ or the request will error.
364
+
365
+ Note that the MCP protocol only supports simple object schemas with
366
+ primitive types. You can provide a dataclass, TypedDict, or BaseModel to
367
+ comply. If you provide a primitive type, an object schema with a single
368
+ "value" field will be generated for the MCP interaction and
369
+ automatically deconstructed into the primitive type upon response.
370
+
371
+ If the response_type is None, the generated schema will be that of an
372
+ empty object in order to comply with the MCP protocol requirements.
373
+ Clients must send an empty object ("{}")in response.
374
+
375
+ Args:
376
+ message: A human-readable message explaining what information is needed
377
+ response_type: The type of the response, which should be a primitive
378
+ type or dataclass or BaseModel. If it is a primitive type, an
379
+ object schema with a single "value" field will be generated.
380
+ """
381
+ if response_type is None:
382
+ schema = {"type": "object", "properties": {}}
383
+ else:
384
+ # if the user provided a list of strings, treat it as a Literal
385
+ if isinstance(response_type, list):
386
+ if not all(isinstance(item, str) for item in response_type):
387
+ raise ValueError(
388
+ "List of options must be a list of strings. Received: "
389
+ f"{response_type}"
390
+ )
391
+ # Convert list of options to Literal type and wrap
392
+ choice_literal = Literal[tuple(response_type)] # type: ignore
393
+ response_type = ScalarElicitationType[choice_literal] # type: ignore
394
+ # if the user provided a primitive scalar, wrap it in an object schema
395
+ elif response_type in {bool, int, float, str}:
396
+ response_type = ScalarElicitationType[response_type] # type: ignore
397
+ # if the user provided a Literal type, wrap it in an object schema
398
+ elif get_origin(response_type) is Literal:
399
+ response_type = ScalarElicitationType[response_type] # type: ignore
400
+ # if the user provided an Enum type, wrap it in an object schema
401
+ elif isinstance(response_type, type) and issubclass(response_type, Enum):
402
+ response_type = ScalarElicitationType[response_type] # type: ignore
403
+
404
+ response_type = cast(type[T], response_type)
405
+
406
+ schema = get_elicitation_schema(response_type)
407
+
408
+ result = await self.session.elicit(
409
+ message=message,
410
+ requestedSchema=schema,
411
+ related_request_id=self.request_id,
412
+ )
413
+
414
+ if result.action == "accept":
415
+ if response_type is not None:
416
+ type_adapter = get_cached_typeadapter(response_type)
417
+ validated_data = cast(
418
+ T | ScalarElicitationType[T],
419
+ type_adapter.validate_python(result.content),
420
+ )
421
+ if isinstance(validated_data, ScalarElicitationType):
422
+ return AcceptedElicitation[T](data=validated_data.value)
423
+ else:
424
+ return AcceptedElicitation[T](data=validated_data)
425
+ elif result.content:
426
+ raise ValueError(
427
+ "Elicitation expected an empty response, but received: "
428
+ f"{result.content}"
429
+ )
430
+ else:
431
+ return AcceptedElicitation[dict[str, Any]](data={})
432
+ elif result.action == "decline":
433
+ return DeclinedElicitation()
434
+ elif result.action == "cancel":
435
+ return CancelledElicitation()
436
+ else:
437
+ # This should never happen, but handle it just in case
438
+ raise ValueError(f"Unexpected elicitation action: {result.action}")
439
+
300
440
  def get_http_request(self) -> Request:
301
441
  """Get the active starlette request."""
302
442