Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions 8 src/mcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,12 @@ def run(
) -> None: # pragma: no cover
"""Run an MCP server.
The server can be specified in two ways:\n
1. Module approach: server.py - runs the module directly, expecting a server.run() call.\n
2. Import approach: server.py:app - imports and runs the specified server object.\n\n
The server can be specified in two ways:
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.
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.
""" # noqa: E501
file, server_object = _parse_file_path(file_spec)
Expand Down
2 changes: 1 addition & 1 deletion 2 src/mcp/client/auth/extensions/client_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ def _add_client_authentication_jwt(self, *, token_data: dict[str, Any]): # prag
# 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_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
token_data["audience"] = self.context.get_resource_url()

Expand Down
3 changes: 2 additions & 1 deletion 3 src/mcp/client/auth/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ def prepare_token_auth(

class OAuthClientProvider(httpx.Auth):
"""OAuth2 authentication for httpx.

Handles OAuth flow with automatic client registration and token storage.
"""

Expand All @@ -241,7 +242,7 @@ def __init__(
callback_handler: Handler for authorization callbacks.
timeout: Timeout for the OAuth flow.
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.
Must be a valid HTTPS URL with a non-root pathname.
validate_resource_url: Optional callback to override resource URL validation.
Expand Down
20 changes: 10 additions & 10 deletions 20 src/mcp/client/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""Extract scope parameter from WWW-Authenticate header as per RFC6750.
"""Extract scope parameter from WWW-Authenticate header as per RFC 6750.

Returns:
Scope string if found in WWW-Authenticate header, None otherwise
Expand All @@ -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:
"""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:
Resource metadata URL if found in WWW-Authenticate header, None otherwise
Expand All @@ -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

Args:
www_auth_url: optional resource_metadata url extracted from the WWW-Authenticate header
server_url: server url
www_auth_url: Optional resource_metadata URL extracted from the WWW-Authenticate header
server_url: Server URL

Returns:
Ordered list of URLs to try for discovery
Expand Down Expand Up @@ -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]:
"""Generate ordered list of (url, type) tuples for discovery attempts.
"""Generate an ordered list of URLs for authorization server metadata discovery.

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
"""

Expand Down Expand Up @@ -170,7 +170,7 @@ async def handle_protected_resource_response(
Per SEP-985, supports fallback when discovery fails at one URL.

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:
try:
Expand Down Expand Up @@ -206,7 +206,7 @@ def create_oauth_metadata_request(url: str) -> Request:
def create_client_registration_request(
auth_server_metadata: OAuthMetadata | None, client_metadata: OAuthClientMetadata, auth_base_url: str
) -> Request:
"""Build registration request or skip if already registered."""
"""Build a client registration request."""

if auth_server_metadata and auth_server_metadata.registration_endpoint:
registration_url = str(auth_server_metadata.registration_endpoint)
Expand Down Expand Up @@ -261,7 +261,7 @@ def should_use_client_metadata_url(
"""Determine if URL-based client ID (CIMD) should be used instead of DCR.

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

Args:
Expand Down Expand Up @@ -306,7 +306,7 @@ def create_client_info_from_metadata_url(
async def handle_token_response_scopes(
response: Response,
) -> 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.

Expand Down
8 changes: 4 additions & 4 deletions 8 src/mcp/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
class Client:
"""A high-level MCP client for connecting to MCP servers.

Currently supports in-memory transport for testing. Pass a Server or
MCPServer instance directly to the constructor.
Supports in-memory transport for testing (pass a Server or MCPServer instance),
Streamable HTTP transport (pass a URL string), or a custom Transport instance.

Example:
```python
Expand Down Expand Up @@ -205,7 +205,7 @@ async def read_resource(self, uri: str, *, meta: RequestParamsMeta | None = None

Args:
uri: The URI of the resource to read.
meta: Additional metadata for the request
meta: Additional metadata for the request.

Returns:
The resource content.
Expand Down Expand Up @@ -239,7 +239,7 @@ async def call_tool(
meta: Additional metadata for the request

Returns:
The tool result
The tool result.
"""
return await self.session.call_tool(
name=name,
Expand Down
4 changes: 2 additions & 2 deletions 4 src/mcp/client/experimental/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async def call_tool_as_task(
status = await session.experimental.get_task(task_id)
if status.status == "completed":
break
await asyncio.sleep(0.5)
await anyio.sleep(0.5)

# Get result
final = await session.experimental.get_task_result(task_id, CallToolResult)
Expand Down Expand Up @@ -177,7 +177,7 @@ async def poll_task(self, task_id: str) -> AsyncIterator[types.GetTaskResult]:
"""Poll a task until it reaches a terminal status.

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).

Respects the pollInterval hint from the server.
Expand Down
10 changes: 5 additions & 5 deletions 10 src/mcp/client/session_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Tools, resources, and prompts are aggregated across servers. Servers may
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
Expand All @@ -30,7 +30,7 @@


class SseServerParameters(BaseModel):
"""Parameters for initializing a sse_client."""
"""Parameters for initializing an sse_client."""

# The endpoint URL.
url: str
Expand Down Expand Up @@ -67,8 +67,8 @@ class StreamableHttpParameters(BaseModel):
ServerParameters: TypeAlias = StdioServerParameters | SseServerParameters | StreamableHttpParameters


# Use dataclass instead of pydantic BaseModel
# because pydantic BaseModel cannot handle Protocol fields.
# Use dataclass instead of Pydantic BaseModel
# because Pydantic BaseModel cannot handle Protocol fields.
@dataclass
class ClientSessionParameters:
"""Parameters for establishing a client session to an MCP server."""
Expand Down Expand Up @@ -119,7 +119,7 @@ class _ComponentNames(BaseModel):
_session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack]

# 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}"
_ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str]
_component_name_hook: _ComponentNameHook | None
Expand Down
1 change: 1 addition & 0 deletions 1 src/mcp/client/sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def sse_client(
headers: Optional headers to include in requests.
timeout: HTTP timeout for regular 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.
on_session_created: Optional callback invoked with the session ID when received.
"""
Expand Down
6 changes: 3 additions & 3 deletions 6 src/mcp/client/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,17 @@ class StdioServerParameters(BaseModel):

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"
"""
The text encoding error handler.

See https://docs.python.org/3/library/codecs.html#codec-base-classes for
explanations of possible values
explanations of possible values.
"""


Expand Down
2 changes: 1 addition & 1 deletion 2 src/mcp/client/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ async def _handle_sse_response(
resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None),
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
if is_complete:
await response.aclose()
Expand Down
4 changes: 2 additions & 2 deletions 4 src/mcp/client/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ async def websocket_client(
(read_stream, write_stream)

- read_stream: As you read from this stream, you'll receive either valid
JSONRPCMessage objects or Exception objects (when validation fails).
- write_stream: Write JSONRPCMessage objects to this stream to send them
SessionMessage objects or Exception objects (when validation fails).
- write_stream: Write SessionMessage objects to this stream to send them
over the WebSocket to the server.
"""

Expand Down
15 changes: 8 additions & 7 deletions 15 src/mcp/os/win32/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ async def create_windows_process(
) -> Process | FallbackProcess:
"""Creates a subprocess in a Windows-compatible way with Job Object support.
Attempt to use anyio's open_process for async subprocess creation.
In some cases this will throw NotImplementedError on Windows, e.g.
when using the SelectorEventLoop which does not support async subprocesses.
Attempts to use anyio's open_process for async subprocess creation.
In some cases this will throw NotImplementedError on Windows, e.g.,
when using the SelectorEventLoop, which does not support async subprocesses.
In that case, we fall back to using subprocess.Popen.
The process is automatically added to a Job Object to ensure all child
Expand Down Expand Up @@ -242,8 +242,9 @@ def _create_job_object() -> int | 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
for any reason, the job handle is closed.
"""Try to assign a process to a job object.
If assignment fails for any reason, the job handle is closed.
"""
if not job:
return
Expand Down Expand Up @@ -312,8 +313,8 @@ async def terminate_windows_process(process: Process | FallbackProcess):
Note: On Windows, terminating a process with process.terminate() doesn't
always guarantee immediate process termination.
So we give it 2s to exit, or we call process.kill()
which sends a SIGKILL equivalent signal.
If the process does not exit within 2 seconds, process.kill() is called
to send a SIGKILL-equivalent signal.
Args:
process: The process to terminate
Expand Down
2 changes: 1 addition & 1 deletion 2 src/mcp/server/auth/handlers/revoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


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_type_hint: Literal["access_token", "refresh_token"] | None = None
Expand Down
8 changes: 5 additions & 3 deletions 8 src/mcp/server/auth/middleware/client_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ def __init__(self, message: str):
class ClientAuthenticator:
"""ClientAuthenticator is a callable which validates requests from a client
application, used to verify /token calls.

If, during registration, the client requested to be issued a secret, the
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
logic is skipped.
"""

def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]):
"""Initialize the dependency.
"""Initialize the authenticator.

Args:
provider: Provider to look up client information
Expand Down Expand Up @@ -83,7 +85,7 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation

elif client.token_endpoint_auth_method == "client_secret_post":
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):
request_client_secret = str(raw_form_data)

Expand Down
17 changes: 9 additions & 8 deletions 17 src/mcp/server/auth/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ async def register_client(self, client_info: OAuthClientInformationFull) -> None
"""

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.

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
has an OAuth connection with the MCP server, and the MCP server has an OAuth
Expand All @@ -151,7 +152,7 @@ async def authorize(self, client: OAuthClientInformationFull, params: Authorizat
| |
+------------+

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
code as part of completing the OAuth authorization step.

Expand Down Expand Up @@ -182,7 +183,7 @@ async def load_authorization_code(
authorization_code: The authorization code to get the challenge for.

Returns:
The AuthorizationCode, or None if not found
The AuthorizationCode, or None if not found.
"""
...

Expand All @@ -199,7 +200,7 @@ async def exchange_authorization_code(
The OAuth token, containing access and refresh tokens.

Raises:
TokenError: If the request is invalid
TokenError: If the request is invalid.
"""
...

Expand Down Expand Up @@ -234,18 +235,18 @@ async def exchange_refresh_token(
The OAuth token, containing access and refresh tokens.

Raises:
TokenError: If the request is invalid
TokenError: If the request is invalid.
"""
...

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:
token: The access token to verify.

Returns:
The AuthInfo, or None if the token is invalid.
The access token, or None if the token is invalid.
"""

async def revoke_token(
Expand All @@ -261,7 +262,7 @@ async def revoke_token(
provided.

Args:
token: the token to revoke
token: The token to revoke.
"""


Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.