docs: fix docstrings across public API surface (#2095)
This commit is contained in:
parent
688c6e3ade
commit
c0328540c9
33 changed files with 177 additions and 163 deletions
|
|
@ -317,12 +317,12 @@ def run(
|
||||||
) -> None: # pragma: no cover
|
) -> None: # pragma: no cover
|
||||||
"""Run an MCP server.
|
"""Run an MCP server.
|
||||||
|
|
||||||
The server can be specified in two ways:\n
|
The server can be specified in two ways:
|
||||||
1. Module approach: server.py - runs the module directly, expecting a server.run() call.\n
|
1. Module approach: server.py - runs the module directly, expecting a server.run() call.
|
||||||
2. Import approach: server.py:app - imports and runs the specified server object.\n\n
|
2. Import approach: server.py:app - imports and runs the specified server object.
|
||||||
|
|
||||||
Note: This command runs the server directly. You are responsible for ensuring
|
Note: This command runs the server directly. You are responsible for ensuring
|
||||||
all dependencies are available.\n
|
all dependencies are available.
|
||||||
For dependency management, use `mcp install` or `mcp dev` instead.
|
For dependency management, use `mcp install` or `mcp dev` instead.
|
||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
file, server_object = _parse_file_path(file_spec)
|
file, server_object = _parse_file_path(file_spec)
|
||||||
|
|
|
||||||
|
|
@ -450,7 +450,7 @@ class RFC7523OAuthClientProvider(OAuthClientProvider):
|
||||||
# When using private_key_jwt, in a client_credentials flow, we use RFC 7523 Section 2.2
|
# When using private_key_jwt, in a client_credentials flow, we use RFC 7523 Section 2.2
|
||||||
token_data["client_assertion"] = assertion
|
token_data["client_assertion"] = assertion
|
||||||
token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||||
# We need to set the audience to the resource server, the audience is difference from the one in claims
|
# We need to set the audience to the resource server, the audience is different from the one in claims
|
||||||
# it represents the resource server that will validate the token
|
# it represents the resource server that will validate the token
|
||||||
token_data["audience"] = self.context.get_resource_url()
|
token_data["audience"] = self.context.get_resource_url()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ class OAuthContext:
|
||||||
|
|
||||||
class OAuthClientProvider(httpx.Auth):
|
class OAuthClientProvider(httpx.Auth):
|
||||||
"""OAuth2 authentication for httpx.
|
"""OAuth2 authentication for httpx.
|
||||||
|
|
||||||
Handles OAuth flow with automatic client registration and token storage.
|
Handles OAuth flow with automatic client registration and token storage.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -241,7 +242,7 @@ class OAuthClientProvider(httpx.Auth):
|
||||||
callback_handler: Handler for authorization callbacks.
|
callback_handler: Handler for authorization callbacks.
|
||||||
timeout: Timeout for the OAuth flow.
|
timeout: Timeout for the OAuth flow.
|
||||||
client_metadata_url: URL-based client ID. When provided and the server
|
client_metadata_url: URL-based client ID. When provided and the server
|
||||||
advertises client_id_metadata_document_supported=true, this URL will be
|
advertises client_id_metadata_document_supported=True, this URL will be
|
||||||
used as the client_id instead of performing dynamic client registration.
|
used as the client_id instead of performing dynamic client registration.
|
||||||
Must be a valid HTTPS URL with a non-root pathname.
|
Must be a valid HTTPS URL with a non-root pathname.
|
||||||
validate_resource_url: Optional callback to override resource URL validation.
|
validate_resource_url: Optional callback to override resource URL validation.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ def extract_field_from_www_auth(response: Response, field_name: str) -> str | No
|
||||||
|
|
||||||
|
|
||||||
def extract_scope_from_www_auth(response: Response) -> str | None:
|
def extract_scope_from_www_auth(response: Response) -> str | None:
|
||||||
"""Extract scope parameter from WWW-Authenticate header as per RFC6750.
|
"""Extract scope parameter from WWW-Authenticate header as per RFC 6750.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Scope string if found in WWW-Authenticate header, None otherwise
|
Scope string if found in WWW-Authenticate header, None otherwise
|
||||||
|
|
@ -47,7 +47,7 @@ def extract_scope_from_www_auth(response: Response) -> str | None:
|
||||||
|
|
||||||
|
|
||||||
def extract_resource_metadata_from_www_auth(response: Response) -> str | None:
|
def extract_resource_metadata_from_www_auth(response: Response) -> str | None:
|
||||||
"""Extract protected resource metadata URL from WWW-Authenticate header as per RFC9728.
|
"""Extract protected resource metadata URL from WWW-Authenticate header as per RFC 9728.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Resource metadata URL if found in WWW-Authenticate header, None otherwise
|
Resource metadata URL if found in WWW-Authenticate header, None otherwise
|
||||||
|
|
@ -67,8 +67,8 @@ def build_protected_resource_metadata_discovery_urls(www_auth_url: str | None, s
|
||||||
3. Fall back to root-based well-known URI: /.well-known/oauth-protected-resource
|
3. Fall back to root-based well-known URI: /.well-known/oauth-protected-resource
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
www_auth_url: optional resource_metadata url extracted from the WWW-Authenticate header
|
www_auth_url: Optional resource_metadata URL extracted from the WWW-Authenticate header
|
||||||
server_url: server url
|
server_url: Server URL
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Ordered list of URLs to try for discovery
|
Ordered list of URLs to try for discovery
|
||||||
|
|
@ -120,10 +120,10 @@ def get_client_metadata_scopes(
|
||||||
|
|
||||||
|
|
||||||
def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]:
|
def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]:
|
||||||
"""Generate ordered list of (url, type) tuples for discovery attempts.
|
"""Generate an ordered list of URLs for authorization server metadata discovery.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
auth_server_url: URL for the OAuth Authorization Metadata URL if found, otherwise None
|
auth_server_url: OAuth Authorization Server Metadata URL if found, otherwise None
|
||||||
server_url: URL for the MCP server, used as a fallback if auth_server_url is None
|
server_url: URL for the MCP server, used as a fallback if auth_server_url is None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -170,7 +170,7 @@ async def handle_protected_resource_response(
|
||||||
Per SEP-985, supports fallback when discovery fails at one URL.
|
Per SEP-985, supports fallback when discovery fails at one URL.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if metadata was successfully discovered, False if we should try next URL
|
ProtectedResourceMetadata if successfully discovered, None if we should try next URL
|
||||||
"""
|
"""
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
try:
|
try:
|
||||||
|
|
@ -206,7 +206,7 @@ def create_oauth_metadata_request(url: str) -> Request:
|
||||||
def create_client_registration_request(
|
def create_client_registration_request(
|
||||||
auth_server_metadata: OAuthMetadata | None, client_metadata: OAuthClientMetadata, auth_base_url: str
|
auth_server_metadata: OAuthMetadata | None, client_metadata: OAuthClientMetadata, auth_base_url: str
|
||||||
) -> Request:
|
) -> Request:
|
||||||
"""Build registration request or skip if already registered."""
|
"""Build a client registration request."""
|
||||||
|
|
||||||
if auth_server_metadata and auth_server_metadata.registration_endpoint:
|
if auth_server_metadata and auth_server_metadata.registration_endpoint:
|
||||||
registration_url = str(auth_server_metadata.registration_endpoint)
|
registration_url = str(auth_server_metadata.registration_endpoint)
|
||||||
|
|
@ -261,7 +261,7 @@ def should_use_client_metadata_url(
|
||||||
"""Determine if URL-based client ID (CIMD) should be used instead of DCR.
|
"""Determine if URL-based client ID (CIMD) should be used instead of DCR.
|
||||||
|
|
||||||
URL-based client IDs should be used when:
|
URL-based client IDs should be used when:
|
||||||
1. The server advertises client_id_metadata_document_supported=true
|
1. The server advertises client_id_metadata_document_supported=True
|
||||||
2. The client has a valid client_metadata_url configured
|
2. The client has a valid client_metadata_url configured
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -306,7 +306,7 @@ def create_client_info_from_metadata_url(
|
||||||
async def handle_token_response_scopes(
|
async def handle_token_response_scopes(
|
||||||
response: Response,
|
response: Response,
|
||||||
) -> OAuthToken:
|
) -> OAuthToken:
|
||||||
"""Parse and validate token response with optional scope validation.
|
"""Parse and validate a token response.
|
||||||
|
|
||||||
Parses token response JSON. Callers should check response.status_code before calling.
|
Parses token response JSON. Callers should check response.status_code before calling.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ from mcp.types import (
|
||||||
class Client:
|
class Client:
|
||||||
"""A high-level MCP client for connecting to MCP servers.
|
"""A high-level MCP client for connecting to MCP servers.
|
||||||
|
|
||||||
Currently supports in-memory transport for testing. Pass a Server or
|
Supports in-memory transport for testing (pass a Server or MCPServer instance),
|
||||||
MCPServer instance directly to the constructor.
|
Streamable HTTP transport (pass a URL string), or a custom Transport instance.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
|
|
@ -205,7 +205,7 @@ class Client:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uri: The URI of the resource to read.
|
uri: The URI of the resource to read.
|
||||||
meta: Additional metadata for the request
|
meta: Additional metadata for the request.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The resource content.
|
The resource content.
|
||||||
|
|
@ -239,7 +239,7 @@ class Client:
|
||||||
meta: Additional metadata for the request
|
meta: Additional metadata for the request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The tool result
|
The tool result.
|
||||||
"""
|
"""
|
||||||
return await self.session.call_tool(
|
return await self.session.call_tool(
|
||||||
name=name,
|
name=name,
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class ExperimentalClientFeatures:
|
||||||
status = await session.experimental.get_task(task_id)
|
status = await session.experimental.get_task(task_id)
|
||||||
if status.status == "completed":
|
if status.status == "completed":
|
||||||
break
|
break
|
||||||
await asyncio.sleep(0.5)
|
await anyio.sleep(0.5)
|
||||||
|
|
||||||
# Get result
|
# Get result
|
||||||
final = await session.experimental.get_task_result(task_id, CallToolResult)
|
final = await session.experimental.get_task_result(task_id, CallToolResult)
|
||||||
|
|
@ -177,7 +177,7 @@ class ExperimentalClientFeatures:
|
||||||
"""Poll a task until it reaches a terminal status.
|
"""Poll a task until it reaches a terminal status.
|
||||||
|
|
||||||
Yields GetTaskResult for each poll, allowing the caller to react to
|
Yields GetTaskResult for each poll, allowing the caller to react to
|
||||||
status changes (e.g., handle input_required). Exits when task reaches
|
status changes (e.g., handle input_required). Exits when the task reaches
|
||||||
a terminal status (completed, failed, cancelled).
|
a terminal status (completed, failed, cancelled).
|
||||||
|
|
||||||
Respects the pollInterval hint from the server.
|
Respects the pollInterval hint from the server.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Tools, resources, and prompts are aggregated across servers. Servers may
|
Tools, resources, and prompts are aggregated across servers. Servers may
|
||||||
be connected to or disconnected from at any point after initialization.
|
be connected to or disconnected from at any point after initialization.
|
||||||
|
|
||||||
This abstractions can handle naming collisions using a custom user-provided hook.
|
This abstraction can handle naming collisions using a custom user-provided hook.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
@ -30,7 +30,7 @@ from mcp.shared.session import ProgressFnT
|
||||||
|
|
||||||
|
|
||||||
class SseServerParameters(BaseModel):
|
class SseServerParameters(BaseModel):
|
||||||
"""Parameters for initializing a sse_client."""
|
"""Parameters for initializing an sse_client."""
|
||||||
|
|
||||||
# The endpoint URL.
|
# The endpoint URL.
|
||||||
url: str
|
url: str
|
||||||
|
|
@ -67,8 +67,8 @@ class StreamableHttpParameters(BaseModel):
|
||||||
ServerParameters: TypeAlias = StdioServerParameters | SseServerParameters | StreamableHttpParameters
|
ServerParameters: TypeAlias = StdioServerParameters | SseServerParameters | StreamableHttpParameters
|
||||||
|
|
||||||
|
|
||||||
# Use dataclass instead of pydantic BaseModel
|
# Use dataclass instead of Pydantic BaseModel
|
||||||
# because pydantic BaseModel cannot handle Protocol fields.
|
# because Pydantic BaseModel cannot handle Protocol fields.
|
||||||
@dataclass
|
@dataclass
|
||||||
class ClientSessionParameters:
|
class ClientSessionParameters:
|
||||||
"""Parameters for establishing a client session to an MCP server."""
|
"""Parameters for establishing a client session to an MCP server."""
|
||||||
|
|
@ -119,7 +119,7 @@ class ClientSessionGroup:
|
||||||
_session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack]
|
_session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack]
|
||||||
|
|
||||||
# Optional fn consuming (component_name, server_info) for custom names.
|
# Optional fn consuming (component_name, server_info) for custom names.
|
||||||
# This is provide a means to mitigate naming conflicts across servers.
|
# This is to provide a means to mitigate naming conflicts across servers.
|
||||||
# Example: (tool_name, server_info) => "{result.server_info.name}.{tool_name}"
|
# Example: (tool_name, server_info) => "{result.server_info.name}.{tool_name}"
|
||||||
_ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str]
|
_ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str]
|
||||||
_component_name_hook: _ComponentNameHook | None
|
_component_name_hook: _ComponentNameHook | None
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ async def sse_client(
|
||||||
headers: Optional headers to include in requests.
|
headers: Optional headers to include in requests.
|
||||||
timeout: HTTP timeout for regular operations (in seconds).
|
timeout: HTTP timeout for regular operations (in seconds).
|
||||||
sse_read_timeout: Timeout for SSE read operations (in seconds).
|
sse_read_timeout: Timeout for SSE read operations (in seconds).
|
||||||
|
httpx_client_factory: Factory function for creating the HTTPX client.
|
||||||
auth: Optional HTTPX authentication handler.
|
auth: Optional HTTPX authentication handler.
|
||||||
on_session_created: Optional callback invoked with the session ID when received.
|
on_session_created: Optional callback invoked with the session ID when received.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,9 @@ class StdioServerParameters(BaseModel):
|
||||||
|
|
||||||
encoding: str = "utf-8"
|
encoding: str = "utf-8"
|
||||||
"""
|
"""
|
||||||
The text encoding used when sending/receiving messages to the server
|
The text encoding used when sending/receiving messages to the server.
|
||||||
|
|
||||||
defaults to utf-8
|
Defaults to utf-8.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict"
|
encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict"
|
||||||
|
|
@ -97,7 +97,7 @@ class StdioServerParameters(BaseModel):
|
||||||
The text encoding error handler.
|
The text encoding error handler.
|
||||||
|
|
||||||
See https://docs.python.org/3/library/codecs.html#codec-base-classes for
|
See https://docs.python.org/3/library/codecs.html#codec-base-classes for
|
||||||
explanations of possible values
|
explanations of possible values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,7 @@ class StreamableHTTPTransport:
|
||||||
resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None),
|
resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None),
|
||||||
is_initialization=is_initialization,
|
is_initialization=is_initialization,
|
||||||
)
|
)
|
||||||
# If the SSE event indicates completion, like returning respose/error
|
# If the SSE event indicates completion, like returning response/error
|
||||||
# break the loop
|
# break the loop
|
||||||
if is_complete:
|
if is_complete:
|
||||||
await response.aclose()
|
await response.aclose()
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ async def websocket_client(
|
||||||
(read_stream, write_stream)
|
(read_stream, write_stream)
|
||||||
|
|
||||||
- read_stream: As you read from this stream, you'll receive either valid
|
- read_stream: As you read from this stream, you'll receive either valid
|
||||||
JSONRPCMessage objects or Exception objects (when validation fails).
|
SessionMessage objects or Exception objects (when validation fails).
|
||||||
- write_stream: Write JSONRPCMessage objects to this stream to send them
|
- write_stream: Write SessionMessage objects to this stream to send them
|
||||||
over the WebSocket to the server.
|
over the WebSocket to the server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,9 +138,9 @@ async def create_windows_process(
|
||||||
) -> Process | FallbackProcess:
|
) -> Process | FallbackProcess:
|
||||||
"""Creates a subprocess in a Windows-compatible way with Job Object support.
|
"""Creates a subprocess in a Windows-compatible way with Job Object support.
|
||||||
|
|
||||||
Attempt to use anyio's open_process for async subprocess creation.
|
Attempts to use anyio's open_process for async subprocess creation.
|
||||||
In some cases this will throw NotImplementedError on Windows, e.g.
|
In some cases this will throw NotImplementedError on Windows, e.g.,
|
||||||
when using the SelectorEventLoop which does not support async subprocesses.
|
when using the SelectorEventLoop, which does not support async subprocesses.
|
||||||
In that case, we fall back to using subprocess.Popen.
|
In that case, we fall back to using subprocess.Popen.
|
||||||
|
|
||||||
The process is automatically added to a Job Object to ensure all child
|
The process is automatically added to a Job Object to ensure all child
|
||||||
|
|
@ -242,8 +242,9 @@ def _create_job_object() -> int | None:
|
||||||
|
|
||||||
|
|
||||||
def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: JobHandle | None) -> None:
|
def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: JobHandle | None) -> None:
|
||||||
"""Try to assign a process to a job object. If assignment fails
|
"""Try to assign a process to a job object.
|
||||||
for any reason, the job handle is closed.
|
|
||||||
|
If assignment fails for any reason, the job handle is closed.
|
||||||
"""
|
"""
|
||||||
if not job:
|
if not job:
|
||||||
return
|
return
|
||||||
|
|
@ -312,8 +313,8 @@ async def terminate_windows_process(process: Process | FallbackProcess):
|
||||||
|
|
||||||
Note: On Windows, terminating a process with process.terminate() doesn't
|
Note: On Windows, terminating a process with process.terminate() doesn't
|
||||||
always guarantee immediate process termination.
|
always guarantee immediate process termination.
|
||||||
So we give it 2s to exit, or we call process.kill()
|
If the process does not exit within 2 seconds, process.kill() is called
|
||||||
which sends a SIGKILL equivalent signal.
|
to send a SIGKILL-equivalent signal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
process: The process to terminate
|
process: The process to terminate
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from mcp.server.auth.provider import AccessToken, OAuthAuthorizationServerProvid
|
||||||
|
|
||||||
|
|
||||||
class RevocationRequest(BaseModel):
|
class RevocationRequest(BaseModel):
|
||||||
"""# See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1"""
|
"""See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1"""
|
||||||
|
|
||||||
token: str
|
token: str
|
||||||
token_type_hint: Literal["access_token", "refresh_token"] | None = None
|
token_type_hint: Literal["access_token", "refresh_token"] | None = None
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,17 @@ class AuthenticationError(Exception):
|
||||||
class ClientAuthenticator:
|
class ClientAuthenticator:
|
||||||
"""ClientAuthenticator is a callable which validates requests from a client
|
"""ClientAuthenticator is a callable which validates requests from a client
|
||||||
application, used to verify /token calls.
|
application, used to verify /token calls.
|
||||||
|
|
||||||
If, during registration, the client requested to be issued a secret, the
|
If, during registration, the client requested to be issued a secret, the
|
||||||
authenticator asserts that /token calls must be authenticated with
|
authenticator asserts that /token calls must be authenticated with
|
||||||
that same token.
|
that same secret.
|
||||||
|
|
||||||
NOTE: clients can opt for no authentication during registration, in which case this
|
NOTE: clients can opt for no authentication during registration, in which case this
|
||||||
logic is skipped.
|
logic is skipped.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]):
|
def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]):
|
||||||
"""Initialize the dependency.
|
"""Initialize the authenticator.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
provider: Provider to look up client information
|
provider: Provider to look up client information
|
||||||
|
|
@ -83,7 +85,7 @@ class ClientAuthenticator:
|
||||||
|
|
||||||
elif client.token_endpoint_auth_method == "client_secret_post":
|
elif client.token_endpoint_auth_method == "client_secret_post":
|
||||||
raw_form_data = form_data.get("client_secret")
|
raw_form_data = form_data.get("client_secret")
|
||||||
# form_data.get() can return a UploadFile or None, so we need to check if it's a string
|
# form_data.get() can return an UploadFile or None, so we need to check if it's a string
|
||||||
if isinstance(raw_form_data, str):
|
if isinstance(raw_form_data, str):
|
||||||
request_client_secret = str(raw_form_data)
|
request_client_secret = str(raw_form_data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,9 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str:
|
async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str:
|
||||||
"""Called as part of the /authorize endpoint, and returns a URL that the client
|
"""Handle the /authorize endpoint and return a URL that the client
|
||||||
will be redirected to.
|
will be redirected to.
|
||||||
|
|
||||||
Many MCP implementations will redirect to a third-party provider to perform
|
Many MCP implementations will redirect to a third-party provider to perform
|
||||||
a second OAuth exchange with that provider. In this sort of setup, the client
|
a second OAuth exchange with that provider. In this sort of setup, the client
|
||||||
has an OAuth connection with the MCP server, and the MCP server has an OAuth
|
has an OAuth connection with the MCP server, and the MCP server has an OAuth
|
||||||
|
|
@ -151,7 +152,7 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
| |
|
| |
|
||||||
+------------+
|
+------------+
|
||||||
|
|
||||||
Implementations will need to define another handler on the MCP server return
|
Implementations will need to define another handler on the MCP server's return
|
||||||
flow to perform the second redirect, and generate and store an authorization
|
flow to perform the second redirect, and generate and store an authorization
|
||||||
code as part of completing the OAuth authorization step.
|
code as part of completing the OAuth authorization step.
|
||||||
|
|
||||||
|
|
@ -182,7 +183,7 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
authorization_code: The authorization code to get the challenge for.
|
authorization_code: The authorization code to get the challenge for.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The AuthorizationCode, or None if not found
|
The AuthorizationCode, or None if not found.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
@ -199,7 +200,7 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
The OAuth token, containing access and refresh tokens.
|
The OAuth token, containing access and refresh tokens.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TokenError: If the request is invalid
|
TokenError: If the request is invalid.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
@ -234,18 +235,18 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
The OAuth token, containing access and refresh tokens.
|
The OAuth token, containing access and refresh tokens.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TokenError: If the request is invalid
|
TokenError: If the request is invalid.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
async def load_access_token(self, token: str) -> AccessTokenT | None:
|
async def load_access_token(self, token: str) -> AccessTokenT | None:
|
||||||
"""Loads an access token by its token.
|
"""Loads an access token by its token string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
token: The access token to verify.
|
token: The access token to verify.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The AuthInfo, or None if the token is invalid.
|
The access token, or None if the token is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def revoke_token(
|
async def revoke_token(
|
||||||
|
|
@ -261,7 +262,7 @@ class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, Ref
|
||||||
provided.
|
provided.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
token: the token to revoke
|
token: The token to revoke.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ def validate_issuer_url(url: AnyHttpUrl):
|
||||||
"""Validate that the issuer URL meets OAuth 2.0 requirements.
|
"""Validate that the issuer URL meets OAuth 2.0 requirements.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The issuer URL to validate
|
url: The issuer URL to validate.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the issuer URL is invalid
|
ValueError: If the issuer URL is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# RFC 8414 requires HTTPS, but we allow loopback/localhost HTTP for testing
|
# RFC 8414 requires HTTPS, but we allow loopback/localhost HTTP for testing
|
||||||
|
|
@ -213,6 +213,8 @@ def create_protected_resource_routes(
|
||||||
resource_url: The URL of this resource server
|
resource_url: The URL of this resource server
|
||||||
authorization_servers: List of authorization servers that can issue tokens
|
authorization_servers: List of authorization servers that can issue tokens
|
||||||
scopes_supported: Optional list of scopes supported by this resource
|
scopes_supported: Optional list of scopes supported by this resource
|
||||||
|
resource_name: Optional human-readable name for this resource
|
||||||
|
resource_documentation: Optional URL to documentation for this resource
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of Starlette routes for protected resource metadata
|
List of Starlette routes for protected resource metadata
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,8 @@ async def elicit_with_validation(
|
||||||
|
|
||||||
This method can be used to interactively ask for additional information from the
|
This method can be used to interactively ask for additional information from the
|
||||||
client within a tool's execution. The client might display the message to the
|
client within a tool's execution. The client might display the message to the
|
||||||
user and collect a response according to the provided schema. Or in case a
|
user and collect a response according to the provided schema. If the client
|
||||||
client is an agent, it might decide how to handle the elicitation -- either by asking
|
is an agent, it might decide how to handle the elicitation -- either by asking
|
||||||
the user or automatically generating a response.
|
the user or automatically generating a response.
|
||||||
|
|
||||||
For sensitive data like credentials or OAuth flows, use elicit_url() instead.
|
For sensitive data like credentials or OAuth flows, use elicit_url() instead.
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,8 @@ class Experimental:
|
||||||
"""Validate that the request is compatible with the tool's task execution mode.
|
"""Validate that the request is compatible with the tool's task execution mode.
|
||||||
|
|
||||||
Per MCP spec:
|
Per MCP spec:
|
||||||
- "required": Clients MUST invoke as task. Server returns -32601 if not.
|
- "required": Clients MUST invoke as a task. Server returns -32601 if not.
|
||||||
- "forbidden" (or None): Clients MUST NOT invoke as task. Server returns -32601 if they do.
|
- "forbidden" (or None): Clients MUST NOT invoke as a task. Server returns -32601 if they do.
|
||||||
- "optional": Either is acceptable.
|
- "optional": Either is acceptable.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -111,7 +111,7 @@ class Experimental:
|
||||||
"""Check if this client can use a tool with the given task mode.
|
"""Check if this client can use a tool with the given task mode.
|
||||||
|
|
||||||
Useful for filtering tool lists or providing warnings.
|
Useful for filtering tool lists or providing warnings.
|
||||||
Returns False if tool requires "required" but client doesn't support tasks.
|
Returns False if the tool's task mode is "required" but the client doesn't support tasks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tool_task_mode: The tool's execution.taskSupport value
|
tool_task_mode: The tool's execution.taskSupport value
|
||||||
|
|
@ -164,7 +164,7 @@ class Experimental:
|
||||||
async def work(task: ServerTaskContext) -> CallToolResult:
|
async def work(task: ServerTaskContext) -> CallToolResult:
|
||||||
result = await task.elicit(
|
result = await task.elicit(
|
||||||
message="Are you sure?",
|
message="Are you sure?",
|
||||||
requestedSchema={"type": "object", ...}
|
requested_schema={"type": "object", ...}
|
||||||
)
|
)
|
||||||
confirmed = result.content.get("confirm", False)
|
confirmed = result.content.get("confirm", False)
|
||||||
return CallToolResult(content=[TextContent(text="Done" if confirmed else "Cancelled")])
|
return CallToolResult(content=[TextContent(text="Done" if confirmed else "Cancelled")])
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,6 @@ class NotificationOptions:
|
||||||
async def lifespan(_: Server[LifespanResultT]) -> AsyncIterator[dict[str, Any]]:
|
async def lifespan(_: Server[LifespanResultT]) -> AsyncIterator[dict[str, Any]]:
|
||||||
"""Default lifespan context manager that does nothing.
|
"""Default lifespan context manager that does nothing.
|
||||||
|
|
||||||
Args:
|
|
||||||
server: The server instance this lifespan is managing
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An empty context object
|
An empty context object
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class FunctionResource(Resource):
|
||||||
class FileResource(Resource):
|
class FileResource(Resource):
|
||||||
"""A resource that reads from a file.
|
"""A resource that reads from a file.
|
||||||
|
|
||||||
Set is_binary=True to read file as binary data instead of text.
|
Set is_binary=True to read the file as binary data instead of text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path: Path = Field(description="Path to the file")
|
path: Path = Field(description="Path to the file")
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue