From 4d22d9c4d750c5ce6e533389ed8eae78cb0e2930 Mon Sep 17 00:00:00 2001 From: "marvin-context-protocol[bot]" <225465937+marvin-context-protocol[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:51:12 -0500 Subject: [PATCH] chore: Update SDK documentation (#2494) Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> --- docs/docs.json | 1 + .../fastmcp-server-auth-oauth_proxy.mdx | 71 +++++++++++------- .../fastmcp-server-auth-oidc_proxy.mdx | 4 +- .../fastmcp-server-auth-providers-azure.mdx | 6 +- .../fastmcp-server-auth-providers-discord.mdx | 72 +++++++++++++++++++ .../fastmcp-server-dependencies.mdx | 13 ++-- docs/python-sdk/fastmcp-server-proxy.mdx | 40 +++++------ .../fastmcp-tools-tool_transform.mdx | 22 +++--- docs/python-sdk/fastmcp-utilities-ui.mdx | 15 ++-- 9 files changed, 173 insertions(+), 71 deletions(-) create mode 100644 docs/python-sdk/fastmcp-server-auth-providers-discord.mdx diff --git a/docs/docs.json b/docs/docs.json index 76488b529..e8af19b5e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -359,6 +359,7 @@ "python-sdk/fastmcp-server-auth-providers-bearer", "python-sdk/fastmcp-server-auth-providers-debug", "python-sdk/fastmcp-server-auth-providers-descope", + "python-sdk/fastmcp-server-auth-providers-discord", "python-sdk/fastmcp-server-auth-providers-github", "python-sdk/fastmcp-server-auth-providers-google", "python-sdk/fastmcp-server-auth-providers-in_memory", diff --git a/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx b/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx index 96d81714d..3aa245abb 100644 --- a/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx +++ b/docs/python-sdk/fastmcp-server-auth-oauth_proxy.mdx @@ -26,17 +26,23 @@ production use with enterprise identity providers. ## Functions -### `create_consent_html` +### `create_consent_html` ```python -create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Application Access Request', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None, client_website_url: str | None = None) -> str +create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Application Access Request', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None, client_website_url: str | None = None, csp_policy: str | None = None) -> str ``` Create a styled HTML consent page for OAuth authorization requests. +**Args:** +- `csp_policy`: Content Security Policy override. +If None, uses the built-in CSP policy with appropriate directives. +If empty string "", disables CSP entirely (no meta tag is rendered). +If a non-empty string, uses that as the CSP policy value. -### `create_error_html` + +### `create_error_html` ```python create_error_html(error_title: str, error_message: str, error_details: dict[str, str] | None = None, server_name: str | None = None, server_icon_url: str | None = None) -> str @@ -95,7 +101,16 @@ This allows stateless JWT validation while still being able to look up the corresponding upstream token when tools need to access upstream APIs. -### `ProxyDCRClient` +### `RefreshTokenMetadata` + + +Metadata for a refresh token, stored keyed by token hash. + +We store only metadata (not the token itself) for security - if storage +is compromised, attackers get hashes they can't reverse into usable tokens. + + +### `ProxyDCRClient` Client for DCR proxy with configurable redirect URI validation. @@ -125,7 +140,7 @@ arise from accepting arbitrary redirect URIs. **Methods:** -#### `validate_redirect_uri` +#### `validate_redirect_uri` ```python validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl @@ -139,7 +154,7 @@ This is essential for cached token scenarios where the client may reconnect with a different port. -### `TokenHandler` +### `TokenHandler` TokenHandler that returns OAuth 2.1 compliant error responses. @@ -162,7 +177,7 @@ Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response." **Methods:** -#### `response` +#### `response` ```python response(self, obj: TokenSuccessResponse | TokenErrorResponse) @@ -171,7 +186,7 @@ response(self, obj: TokenSuccessResponse | TokenErrorResponse) Override response method to provide OAuth 2.1 compliant error handling. -### `OAuthProxy` +### `OAuthProxy` OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs. @@ -251,14 +266,18 @@ OAuth Flow Implementation State Management --------------- -The proxy maintains minimal but crucial state: +The proxy maintains minimal but crucial state via pluggable storage (client_storage): - _oauth_transactions: Active authorization flows with client context - _client_codes: Authorization codes with PKCE challenges and upstream tokens -- _access_tokens, _refresh_tokens: Token storage for revocation -- Token relationship mappings for cleanup and rotation +- _jti_mapping_store: Maps FastMCP token JTIs to upstream token IDs +- _refresh_token_store: Refresh token metadata (keyed by token hash) + +All state is stored in the configured client_storage backend (Redis, disk, etc.) +enabling horizontal scaling across multiple instances. Security Considerations ---------------------- +- Refresh tokens stored by hash only (defense in depth if storage compromised) - PKCE enforced end-to-end (client to proxy, proxy to upstream) - Authorization codes are single-use with short expiry - Transaction IDs are cryptographically random @@ -281,7 +300,7 @@ Handles provider-specific requirements: **Methods:** -#### `get_client` +#### `get_client` ```python get_client(self, client_id: str) -> OAuthClientInformationFull | None @@ -293,7 +312,7 @@ provided to the DCR client during registration, not the upstream client ID. For unregistered clients, returns None (which will raise an error in the SDK). -#### `register_client` +#### `register_client` ```python register_client(self, client_info: OAuthClientInformationFull) -> None @@ -307,7 +326,7 @@ redirect URI will likely be localhost or unknown to the proxied IDP. The proxied IDP only knows about this server's fixed redirect URI. -#### `authorize` +#### `authorize` ```python authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str @@ -324,7 +343,7 @@ If consent is disabled (require_authorization_consent=False), skip the consent s and redirect directly to the upstream IdP. -#### `load_authorization_code` +#### `load_authorization_code` ```python load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None @@ -336,7 +355,7 @@ Look up our client code and return authorization code object with PKCE challenge for validation. -#### `exchange_authorization_code` +#### `exchange_authorization_code` ```python exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken @@ -354,16 +373,19 @@ Implements the token factory pattern: PKCE validation is handled by the MCP framework before this method is called. -#### `load_refresh_token` +#### `load_refresh_token` ```python load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None ``` -Load refresh token from local storage. +Load refresh token metadata from distributed storage. + +Looks up by token hash and reconstructs the RefreshToken object. +Validates that the token belongs to the requesting client. -#### `exchange_refresh_token` +#### `exchange_refresh_token` ```python exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken @@ -380,7 +402,7 @@ Implements two-tier refresh: 6. Keep same FastMCP refresh token (unless upstream rotates) -#### `load_access_token` +#### `load_access_token` ```python load_access_token(self, token: str) -> AccessToken | None @@ -399,7 +421,7 @@ The FastMCP JWT is a reference token - all authorization data comes from validating the upstream token via the TokenVerifier. -#### `revoke_token` +#### `revoke_token` ```python revoke_token(self, token: AccessToken | RefreshToken) -> None @@ -407,11 +429,12 @@ revoke_token(self, token: AccessToken | RefreshToken) -> None Revoke token locally and with upstream server if supported. -Removes tokens from local storage and attempts to revoke them with -the upstream server if a revocation endpoint is configured. +For refresh tokens, removes from local storage by hash. +For all tokens, attempts upstream revocation if endpoint is configured. +Access token JTI mappings expire via TTL. -#### `get_routes` +#### `get_routes` ```python get_routes(self, mcp_path: str | None = None) -> list[Route] diff --git a/docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx b/docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx index 573aea7bb..184bc82af 100644 --- a/docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx +++ b/docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx @@ -52,7 +52,7 @@ that is OIDC compliant. **Methods:** -#### `get_oidc_configuration` +#### `get_oidc_configuration` ```python get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration @@ -66,7 +66,7 @@ Gets the OIDC configuration for the specified configuration URL. - `timeout_seconds`: HTTP request timeout in seconds -#### `get_token_verifier` +#### `get_token_verifier` ```python get_token_verifier(self) -> TokenVerifier diff --git a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx index b0474eb58..5fb265b32 100644 --- a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx +++ b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx @@ -14,13 +14,13 @@ using the OAuth Proxy pattern for non-DCR OAuth flows. ## Classes -### `AzureProviderSettings` +### `AzureProviderSettings` Settings for Azure OAuth provider. -### `AzureProvider` +### `AzureProvider` Azure (Microsoft Entra) OAuth provider for FastMCP. @@ -55,7 +55,7 @@ Setup: **Methods:** -#### `authorize` +#### `authorize` ```python authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str diff --git a/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx b/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx new file mode 100644 index 000000000..031adaae9 --- /dev/null +++ b/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx @@ -0,0 +1,72 @@ +--- +title: discord +sidebarTitle: discord +--- + +# `fastmcp.server.auth.providers.discord` + + +Discord OAuth provider for FastMCP. + +This module provides a complete Discord OAuth integration that's ready to use +with just a client ID and client secret. It handles all the complexity of +Discord's OAuth flow, token validation, and user management. + +Example: + ```python + from fastmcp import FastMCP + from fastmcp.server.auth.providers.discord import DiscordProvider + + # Simple Discord OAuth protection + auth = DiscordProvider( + client_id="your-discord-client-id", + client_secret="your-discord-client-secret" + ) + + mcp = FastMCP("My Protected Server", auth=auth) + ``` + + +## Classes + +### `DiscordProviderSettings` + + +Settings for Discord OAuth provider. + + +### `DiscordTokenVerifier` + + +Token verifier for Discord OAuth tokens. + +Discord OAuth tokens are opaque (not JWTs), so we verify them +by calling Discord's tokeninfo API to check if they're valid and get user info. + + +**Methods:** + +#### `verify_token` + +```python +verify_token(self, token: str) -> AccessToken | None +``` + +Verify Discord OAuth token by calling Discord's tokeninfo API. + + +### `DiscordProvider` + + +Complete Discord OAuth provider for FastMCP. + +This provider makes it trivial to add Discord OAuth protection to any +FastMCP server. Just provide your Discord OAuth app credentials and +a base URL, and you're ready to go. + +Features: +- Transparent OAuth proxy to Discord +- Automatic token validation via Discord's API +- User information extraction from Discord APIs +- Minimal configuration required + diff --git a/docs/python-sdk/fastmcp-server-dependencies.mdx b/docs/python-sdk/fastmcp-server-dependencies.mdx index 7db6f2f0a..9092633d9 100644 --- a/docs/python-sdk/fastmcp-server-dependencies.mdx +++ b/docs/python-sdk/fastmcp-server-dependencies.mdx @@ -7,19 +7,19 @@ sidebarTitle: dependencies ## Functions -### `get_context` +### `get_context` ```python get_context() -> Context ``` -### `get_http_request` +### `get_http_request` ```python get_http_request() -> Request ``` -### `get_http_headers` +### `get_http_headers` ```python get_http_headers(include_all: bool = False) -> dict[str, str] @@ -35,7 +35,7 @@ By default, strips problematic headers like `content-length` that cause issues i If `include_all` is True, all headers are returned. -### `get_access_token` +### `get_access_token` ```python get_access_token() -> AccessToken | None @@ -44,6 +44,11 @@ get_access_token() -> AccessToken | None Get the FastMCP access token from the current context. +This function first tries to get the token from the current HTTP request's scope, +which is more reliable for long-lived connections where the SDK's auth_context_var +may become stale after token refresh. Falls back to the SDK's context var if no +request is available. + **Returns:** - The access token if an authenticated user is available, None otherwise. diff --git a/docs/python-sdk/fastmcp-server-proxy.mdx b/docs/python-sdk/fastmcp-server-proxy.mdx index a64acca64..445fc1a68 100644 --- a/docs/python-sdk/fastmcp-server-proxy.mdx +++ b/docs/python-sdk/fastmcp-server-proxy.mdx @@ -7,7 +7,7 @@ sidebarTitle: proxy ## Functions -### `default_proxy_roots_handler` +### `default_proxy_roots_handler` ```python default_proxy_roots_handler(context: RequestContext[ClientSession, LifespanContextT]) -> RootsList @@ -165,7 +165,7 @@ from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool Factory method to create a ProxyTool from a raw MCP tool schema. -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResult @@ -174,7 +174,7 @@ run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResu Executes the tool by making a call through the client. -### `ProxyResource` +### `ProxyResource` A Resource that represents and reads a resource from a remote server. @@ -182,7 +182,7 @@ A Resource that represents and reads a resource from a remote server. **Methods:** -#### `from_mcp_resource` +#### `from_mcp_resource` ```python from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource @@ -191,7 +191,7 @@ from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> Prox Factory method to create a ProxyResource from a raw MCP resource schema. -#### `read` +#### `read` ```python read(self) -> str | bytes @@ -200,7 +200,7 @@ read(self) -> str | bytes Read the resource content from the remote server. -### `ProxyTemplate` +### `ProxyTemplate` A ResourceTemplate that represents and creates resources from a remote server template. @@ -208,7 +208,7 @@ A ResourceTemplate that represents and creates resources from a remote server te **Methods:** -#### `from_mcp_template` +#### `from_mcp_template` ```python from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate @@ -217,7 +217,7 @@ from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) Factory method to create a ProxyTemplate from a raw MCP template schema. -#### `create_resource` +#### `create_resource` ```python create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> ProxyResource @@ -226,7 +226,7 @@ create_resource(self, uri: str, params: dict[str, Any], context: Context | None Create a resource from the template by calling the remote server. -### `ProxyPrompt` +### `ProxyPrompt` A Prompt that represents and renders a prompt from a remote server. @@ -234,7 +234,7 @@ A Prompt that represents and renders a prompt from a remote server. **Methods:** -#### `from_mcp_prompt` +#### `from_mcp_prompt` ```python from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt @@ -243,7 +243,7 @@ from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPromp Factory method to create a ProxyPrompt from a raw MCP prompt schema. -#### `render` +#### `render` ```python render(self, arguments: dict[str, Any]) -> list[PromptMessage] @@ -252,14 +252,14 @@ render(self, arguments: dict[str, Any]) -> list[PromptMessage] Render the prompt by making a call through the client. -### `FastMCPProxy` +### `FastMCPProxy` A FastMCP server that acts as a proxy to a remote MCP-compliant server. It uses specialized managers that fulfill requests via a client factory. -### `ProxyClient` +### `ProxyClient` A proxy client that forwards advanced interactions between a remote MCP server and the proxy's connected clients. @@ -268,7 +268,7 @@ Supports forwarding roots, sampling, elicitation, logging, and progress. **Methods:** -#### `default_sampling_handler` +#### `default_sampling_handler` ```python default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params: mcp.types.CreateMessageRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> mcp.types.CreateMessageResult @@ -277,7 +277,7 @@ default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params: A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server. -#### `default_elicitation_handler` +#### `default_elicitation_handler` ```python default_elicitation_handler(cls, message: str, response_type: type, params: mcp.types.ElicitRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> ElicitResult @@ -286,7 +286,7 @@ default_elicitation_handler(cls, message: str, response_type: type, params: mcp. A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server. -#### `default_log_handler` +#### `default_log_handler` ```python default_log_handler(cls, message: LogMessage) -> None @@ -295,7 +295,7 @@ default_log_handler(cls, message: LogMessage) -> None A handler that forwards the log notification from the remote server to the proxy's connected clients. -#### `default_progress_handler` +#### `default_progress_handler` ```python default_progress_handler(cls, progress: float, total: float | None, message: str | None) -> None @@ -304,7 +304,7 @@ default_progress_handler(cls, progress: float, total: float | None, message: str A handler that forwards the progress notification from the remote server to the proxy's connected clients. -### `StatefulProxyClient` +### `StatefulProxyClient` A proxy client that provides a stateful client factory for the proxy server. @@ -318,7 +318,7 @@ Note that it is essential to ensure that the proxy server itself is also statefu **Methods:** -#### `clear` +#### `clear` ```python clear(self) @@ -327,7 +327,7 @@ clear(self) Clear all cached clients and force disconnect them. -#### `new_stateful` +#### `new_stateful` ```python new_stateful(self) -> Client[ClientTransportT] diff --git a/docs/python-sdk/fastmcp-tools-tool_transform.mdx b/docs/python-sdk/fastmcp-tools-tool_transform.mdx index c9c416543..bccf670d4 100644 --- a/docs/python-sdk/fastmcp-tools-tool_transform.mdx +++ b/docs/python-sdk/fastmcp-tools-tool_transform.mdx @@ -7,7 +7,7 @@ sidebarTitle: tool_transform ## Functions -### `forward` +### `forward` ```python forward(**kwargs: Any) -> ToolResult @@ -36,7 +36,7 @@ tool has args `a` and `b`, and an `transform_args` was provided that maps `x` to - `TypeError`: If provided arguments don't match the transformed schema. -### `forward_raw` +### `forward_raw` ```python forward_raw(**kwargs: Any) -> ToolResult @@ -62,7 +62,7 @@ y=2)` will call the parent tool with `x=1` and `y=2`. - `RuntimeError`: If called outside a transformed tool context. -### `apply_transformations_to_tools` +### `apply_transformations_to_tools` ```python apply_transformations_to_tools(tools: dict[str, Tool], transformations: dict[str, ToolTransformConfig]) -> dict[str, Tool] @@ -75,7 +75,7 @@ are left unchanged. ## Classes -### `ArgTransform` +### `ArgTransform` Configuration for transforming a parent tool's argument. @@ -137,7 +137,7 @@ ArgTransform(name="new_name", description="New desc", default=None, type=int) ``` -### `ArgTransformConfig` +### `ArgTransformConfig` A model for requesting a single argument transform. @@ -145,7 +145,7 @@ A model for requesting a single argument transform. **Methods:** -#### `to_arg_transform` +#### `to_arg_transform` ```python to_arg_transform(self) -> ArgTransform @@ -154,7 +154,7 @@ to_arg_transform(self) -> ArgTransform Convert the argument transform to a FastMCP argument transform. -### `TransformedTool` +### `TransformedTool` A tool that is transformed from another tool. @@ -171,7 +171,7 @@ inherited from the parent tool but can be overridden or disabled. **Methods:** -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -190,7 +190,7 @@ functions. - ToolResult object containing content and optional structured output. -#### `from_tool` +#### `from_tool` ```python from_tool(cls, tool: Tool, name: str | None = None, title: str | NotSetT | None = NotSet, description: str | NotSetT | None = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | NotSetT | None = NotSet, output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet, serializer: Callable[[Any], str] | NotSetT | None = NotSet, meta: dict[str, Any] | NotSetT | None = NotSet, enabled: bool | None = None) -> TransformedTool @@ -272,7 +272,7 @@ async def custom_output(**kwargs) -> ToolResult: ``` -### `ToolTransformConfig` +### `ToolTransformConfig` Provides a way to transform a tool. @@ -280,7 +280,7 @@ Provides a way to transform a tool. **Methods:** -#### `apply` +#### `apply` ```python apply(self, tool: Tool) -> TransformedTool diff --git a/docs/python-sdk/fastmcp-utilities-ui.mdx b/docs/python-sdk/fastmcp-utilities-ui.mdx index 09f5a425c..5066eab5e 100644 --- a/docs/python-sdk/fastmcp-utilities-ui.mdx +++ b/docs/python-sdk/fastmcp-utilities-ui.mdx @@ -28,13 +28,14 @@ Create a complete HTML page with FastMCP styling. - `content`: HTML content to place inside the page - `title`: Page title - `additional_styles`: Extra CSS to include -- `csp_policy`: Content Security Policy header value +- `csp_policy`: Content Security Policy header value. +If empty string "", the CSP meta tag is omitted entirely. **Returns:** - Complete HTML page as string -### `create_logo` +### `create_logo` ```python create_logo(icon_url: str | None = None, alt_text: str = 'FastMCP') -> str @@ -51,7 +52,7 @@ Create logo HTML. - HTML for logo image tag. -### `create_status_message` +### `create_status_message` ```python create_status_message(message: str, is_success: bool = True) -> str @@ -68,7 +69,7 @@ Create a status message with icon. - HTML for status message -### `create_info_box` +### `create_info_box` ```python create_info_box(content: str, is_error: bool = False, centered: bool = False, monospace: bool = False) -> str @@ -87,7 +88,7 @@ Create an info box. - HTML for info box -### `create_detail_box` +### `create_detail_box` ```python create_detail_box(rows: list[tuple[str, str]]) -> str @@ -103,7 +104,7 @@ Create a detail box with key-value pairs. - HTML for detail box -### `create_button_group` +### `create_button_group` ```python create_button_group(buttons: list[tuple[str, str, str]]) -> str @@ -119,7 +120,7 @@ Create a group of buttons. - HTML for button group -### `create_secure_html_response` +### `create_secure_html_response` ```python create_secure_html_response(html: str, status_code: int = 200) -> HTMLResponse