Jeremiah Lowin commited on
Commit
79436d0
·
unverified ·
2 Parent(s): 89fe825df9889e

Merge branch 'main' into transform-tools-2

Browse files
.github/dependabot.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
7
+ labels:
8
+ - "dependencies"
9
+ - package-ecosystem: "pip"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "daily"
13
+ labels:
14
+ - "dependencies"
15
+ - package-ecosystem: "github-actions"
16
+ directory: "/"
17
+ schedule:
18
+ interval: "weekly"
19
+ labels:
20
+ - "dependencies"
.github/release.yml CHANGED
@@ -27,6 +27,10 @@ changelog:
27
  labels:
28
  - documentation
29
 
 
 
 
 
30
  - title: Other Changes 🦾
31
  labels:
32
  - "*"
 
27
  labels:
28
  - documentation
29
 
30
+ - title: Dependencies 📦
31
+ labels:
32
+ - dependencies
33
+
34
  - title: Other Changes 🦾
35
  labels:
36
  - "*"
.github/workflows/publish.yml CHANGED
@@ -17,7 +17,7 @@ jobs:
17
  fetch-depth: 0
18
 
19
  - name: "Install uv"
20
- uses: astral-sh/setup-uv@v3
21
 
22
  - name: Build
23
  run: uv build
 
17
  fetch-depth: 0
18
 
19
  - name: "Install uv"
20
+ uses: astral-sh/setup-uv@v6
21
 
22
  - name: Build
23
  run: uv build
.github/workflows/run-static.yml CHANGED
@@ -32,7 +32,7 @@ jobs:
32
  steps:
33
  - uses: actions/checkout@v4
34
  - name: Install uv
35
- uses: astral-sh/setup-uv@v5
36
  with:
37
  enable-cache: true
38
  cache-dependency-glob: "uv.lock"
 
32
  steps:
33
  - uses: actions/checkout@v4
34
  - name: Install uv
35
+ uses: astral-sh/setup-uv@v6
36
  with:
37
  enable-cache: true
38
  cache-dependency-glob: "uv.lock"
.github/workflows/run-tests.yml CHANGED
@@ -37,7 +37,7 @@ jobs:
37
  - uses: actions/checkout@v4
38
 
39
  - name: Install uv
40
- uses: astral-sh/setup-uv@v5
41
  with:
42
  enable-cache: true
43
  cache-dependency-glob: "uv.lock"
 
37
  - uses: actions/checkout@v4
38
 
39
  - name: Install uv
40
+ uses: astral-sh/setup-uv@v6
41
  with:
42
  enable-cache: true
43
  cache-dependency-glob: "uv.lock"
docs/assets/updates/release-2-7.png ADDED

Git LFS Details

  • SHA256: 0e49b3d8194dfc8cc0a20ef62c34e665594efa31449b2a3c3c3080a13b10c1d6
  • Pointer size: 131 Bytes
  • Size of remote file: 422 kB
docs/integrations/anthropic.mdx CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Anthropic
3
- sidebarTitle: Anthropic
4
  description: Call FastMCP servers from the Anthropic API
5
  icon: message-smile
6
  tag: "New!"
@@ -8,9 +8,6 @@ tag: "New!"
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
11
- Anthropic supports MCP servers through the [MCP connector](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector) feature in the Messages API, allowing you to extend AI capabilities with custom tools from remote MCP servers.
12
-
13
- ## Messages API
14
 
15
  Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
16
 
@@ -18,7 +15,7 @@ Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports
18
  Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
19
  </Tip>
20
 
21
- ### Create a Server
22
 
23
  First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
24
 
@@ -37,7 +34,7 @@ if __name__ == "__main__":
37
  mcp.run(transport="sse", port=8000)
38
  ```
39
 
40
- ### Deploy the Server
41
 
42
  Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
43
 
@@ -59,7 +56,7 @@ ngrok http 8000
59
  This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
60
  </Warning>
61
 
62
- ### Call the Server
63
 
64
  To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
65
 
@@ -114,13 +111,13 @@ The results were 4, 2, and 6. Would you like me to roll again or roll a differen
114
  ```
115
 
116
 
117
- ### Authentication
118
 
119
  <VersionBadge version="2.6.0" />
120
 
121
  The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
122
 
123
- #### Server Authentication
124
 
125
  The simplest way to add authentication to the server is to use a bearer token scheme.
126
 
@@ -181,7 +178,7 @@ if __name__ == "__main__":
181
  mcp.run(transport="sse", port=8000)
182
  ```
183
 
184
- #### Client Authentication
185
 
186
  If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
187
 
 
1
  ---
2
+ title: Anthropic API + FastMCP
3
+ sidebarTitle: Anthropic API
4
  description: Call FastMCP servers from the Anthropic API
5
  icon: message-smile
6
  tag: "New!"
 
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
 
 
 
11
 
12
  Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
13
 
 
15
  Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
16
  </Tip>
17
 
18
+ ## Create a Server
19
 
20
  First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
21
 
 
34
  mcp.run(transport="sse", port=8000)
35
  ```
36
 
37
+ ## Deploy the Server
38
 
39
  Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
40
 
 
56
  This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
57
  </Warning>
58
 
59
+ ## Call the Server
60
 
61
  To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
62
 
 
111
  ```
112
 
113
 
114
+ ## Authentication
115
 
116
  <VersionBadge version="2.6.0" />
117
 
118
  The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
119
 
120
+ ### Server Authentication
121
 
122
  The simplest way to add authentication to the server is to use a bearer token scheme.
123
 
 
178
  mcp.run(transport="sse", port=8000)
179
  ```
180
 
181
+ ### Client Authentication
182
 
183
  If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
184
 
docs/integrations/claude-desktop.mdx CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Claude Desktop
3
  sidebarTitle: Claude Desktop
4
  description: Call FastMCP servers from Claude Desktop
5
  icon: desktop
 
1
  ---
2
+ title: Claude Desktop + FastMCP
3
  sidebarTitle: Claude Desktop
4
  description: Call FastMCP servers from Claude Desktop
5
  icon: desktop
docs/integrations/contrib.mdx CHANGED
@@ -12,7 +12,7 @@ FastMCP includes a `contrib` package that holds community-contributed modules. T
12
 
13
  Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable.
14
 
15
- The available modules can be viewed in the [contrib directory](https://github.com/jlowin/fastmcp/tree/main/src/contrib).
16
 
17
  ## Usage
18
 
 
12
 
13
  Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable.
14
 
15
+ The available modules can be viewed in the [contrib directory](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/contrib).
16
 
17
  ## Usage
18
 
docs/integrations/gemini.mdx CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Gemini SDK
3
  sidebarTitle: Gemini SDK
4
  description: Call FastMCP servers from the Google Gemini SDK
5
  icon: message-smile
 
1
  ---
2
+ title: Gemini SDK + FastMCP
3
  sidebarTitle: Gemini SDK
4
  description: Call FastMCP servers from the Google Gemini SDK
5
  icon: message-smile
docs/integrations/openai.mdx CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: OpenAI
3
- sidebarTitle: OpenAI
4
  description: Call FastMCP servers from the OpenAI API
5
  icon: message-smile
6
  tag: "New!"
@@ -8,14 +8,13 @@ tag: "New!"
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
11
- OpenAI recently announced support for MCP servers in the Responses API. Note that at this time, MCP is not supported in ChatGPT.
12
 
13
  ## Responses API
14
 
15
  OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions.
16
 
17
  <Note>
18
- The Responses API is a distinct API from OpenAI's Completions API, Assistants API, or ChatGPT. At this time, only the Responses API supports MCP.
19
  </Note>
20
 
21
  <Tip>
 
1
  ---
2
+ title: OpenAI API + FastMCP
3
+ sidebarTitle: OpenAI API
4
  description: Call FastMCP servers from the OpenAI API
5
  icon: message-smile
6
  tag: "New!"
 
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
 
11
 
12
  ## Responses API
13
 
14
  OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions.
15
 
16
  <Note>
17
+ The Responses API is a distinct API from OpenAI's Completions API or Assistants API. At this time, only the Responses API supports MCP.
18
  </Note>
19
 
20
  <Tip>
docs/updates.mdx CHANGED
@@ -3,11 +3,36 @@ title: "FastMCP Updates"
3
  sidebarTitle: "Updates"
4
  icon: "sparkles"
5
  tag: "New!"
 
6
  ---
7
- <Update label="FastMCP 2.6" description="June 6, 2025">
 
 
 
 
 
 
 
8
 
 
9
 
 
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  <Card
12
  title="Blast Auth with FastMCP 2.6" href="https://www.jlowin.dev/blog/fastmcp-2-6"
13
  img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Bsu8afiw.png&w=1000&h=500&f=webp"
 
3
  sidebarTitle: "Updates"
4
  icon: "sparkles"
5
  tag: "New!"
6
+ mode: "wide"
7
  ---
8
+ <Update label="FastMCP 2.7" description="June 6, 2025">
9
+ <Card
10
+ title="FastMCP 2.7: Pare Programming" href="https://github.com/jlowin/fastmcp/releases/tag/v2.7.0"
11
+ img="assets/updates/release-2-7.png"
12
+ cta="Read the release notes"
13
+ arrow="false"
14
+ >
15
+ FastMCP 2.7 has been released!
16
 
17
+ Most notably, it introduces the highly requested (and Pythonic) "naked" decorator usage:
18
 
19
+ ```python {3}
20
+ mcp = FastMCP()
21
 
22
+ @mcp.tool
23
+ def add(a: int, b: int) -> int:
24
+ return a + b
25
+ ```
26
+
27
+ In addition, decorators now return the objects they create, instead of the decorated function. This is an important usability enhancement.
28
+
29
+ The bulk of the update is focused on improving the FastMCP internals, including a few breaking internal changes to private APIs. A number of functions that have clung on since 1.0 are now deprecated.
30
+ </Card>
31
+ </Update>
32
+
33
+
34
+
35
+ <Update label="FastMCP 2.6" description="June 6, 2025">
36
  <Card
37
  title="Blast Auth with FastMCP 2.6" href="https://www.jlowin.dev/blog/fastmcp-2-6"
38
  img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Bsu8afiw.png&w=1000&h=500&f=webp"
src/fastmcp/cli/cli.py CHANGED
@@ -1,4 +1,4 @@
1
- """FastmMCP CLI tools."""
2
 
3
  import importlib.metadata
4
  import importlib.util
 
1
+ """FastMCP CLI tools."""
2
 
3
  import importlib.metadata
4
  import importlib.util
src/fastmcp/client/client.py CHANGED
@@ -145,6 +145,7 @@ class Client(Generic[ClientTransportT]):
145
  progress_handler: ProgressHandler | None = None,
146
  timeout: datetime.timedelta | float | int | None = None,
147
  init_timeout: datetime.timedelta | float | int | None = None,
 
148
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
149
  ):
150
  self.transport = cast(ClientTransportT, infer_transport(transport))
@@ -180,6 +181,7 @@ class Client(Generic[ClientTransportT]):
180
  "logging_callback": create_log_callback(log_handler),
181
  "message_handler": message_handler,
182
  "read_timeout_seconds": timeout,
 
183
  }
184
 
185
  if roots is not None:
 
145
  progress_handler: ProgressHandler | None = None,
146
  timeout: datetime.timedelta | float | int | None = None,
147
  init_timeout: datetime.timedelta | float | int | None = None,
148
+ client_info: mcp.types.Implementation | None = None,
149
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
150
  ):
151
  self.transport = cast(ClientTransportT, infer_transport(transport))
 
181
  "logging_callback": create_log_callback(log_handler),
182
  "message_handler": message_handler,
183
  "read_timeout_seconds": timeout,
184
+ "client_info": client_info,
185
  }
186
 
187
  if roots is not None:
src/fastmcp/client/transports.py CHANGED
@@ -8,39 +8,25 @@ import sys
8
  import warnings
9
  from collections.abc import AsyncIterator, Callable
10
  from pathlib import Path
11
- from typing import (
12
- TYPE_CHECKING,
13
- Any,
14
- Literal,
15
- TypedDict,
16
- TypeVar,
17
- cast,
18
- overload,
19
- )
20
 
21
  import anyio
22
  import httpx
 
23
  from mcp import ClientSession, StdioServerParameters
24
- from mcp.client.session import (
25
- ListRootsFnT,
26
- LoggingFnT,
27
- MessageHandlerFnT,
28
- SamplingFnT,
29
- )
30
  from mcp.server.fastmcp import FastMCP as FastMCP1Server
31
- from mcp.shared.memory import create_connected_server_and_client_session
32
  from pydantic import AnyUrl
33
  from typing_extensions import Unpack
34
 
 
35
  from fastmcp.client.auth.oauth import OAuth
36
  from fastmcp.server.dependencies import get_http_headers
37
  from fastmcp.server.server import FastMCP
38
  from fastmcp.utilities.logging import get_logger
39
  from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
40
 
41
- if TYPE_CHECKING:
42
- from fastmcp.utilities.mcp_config import MCPConfig
43
-
44
  logger = get_logger(__name__)
45
 
46
  # TypeVar for preserving specific ClientTransport subclass types
@@ -64,11 +50,12 @@ __all__ = [
64
  class SessionKwargs(TypedDict, total=False):
65
  """Keyword arguments for the MCP ClientSession constructor."""
66
 
 
67
  sampling_callback: SamplingFnT | None
68
  list_roots_callback: ListRootsFnT | None
69
  logging_callback: LoggingFnT | None
70
  message_handler: MessageHandlerFnT | None
71
- read_timeout_seconds: datetime.timedelta | None
72
 
73
 
74
  class ClientTransport(abc.ABC):
@@ -152,7 +139,7 @@ class WSTransport(ClientTransport):
152
  yield session
153
 
154
  def __repr__(self) -> str:
155
- return f"<WebSocket(url='{self.url}')>"
156
 
157
 
158
  class SSETransport(ClientTransport):
@@ -183,8 +170,7 @@ class SSETransport(ClientTransport):
183
  if auth == "oauth":
184
  auth = OAuth(self.url)
185
  elif isinstance(auth, str):
186
- self.headers["Authorization"] = auth
187
- auth = None
188
  self.auth = auth
189
 
190
  @contextlib.asynccontextmanager
@@ -221,7 +207,7 @@ class SSETransport(ClientTransport):
221
  yield session
222
 
223
  def __repr__(self) -> str:
224
- return f"<SSE(url='{self.url}')>"
225
 
226
 
227
  class StreamableHttpTransport(ClientTransport):
@@ -252,8 +238,7 @@ class StreamableHttpTransport(ClientTransport):
252
  if auth == "oauth":
253
  auth = OAuth(self.url)
254
  elif isinstance(auth, str):
255
- self.headers["Authorization"] = auth
256
- auth = None
257
  self.auth = auth
258
 
259
  @contextlib.asynccontextmanager
@@ -291,7 +276,7 @@ class StreamableHttpTransport(ClientTransport):
291
  yield session
292
 
293
  def __repr__(self) -> str:
294
- return f"<StreamableHttp(url='{self.url}')>"
295
 
296
 
297
  class StdioTransport(ClientTransport):
@@ -663,27 +648,49 @@ class FastMCPTransport(ClientTransport):
663
  tests or scenarios where client and server run in the same runtime.
664
  """
665
 
666
- def __init__(self, mcp: FastMCP | FastMCP1Server):
667
  """Initialize a FastMCPTransport from a FastMCP server instance."""
668
 
669
  # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
670
  # ``_mcp_server`` attribute pointing to the underlying MCP server
671
  # implementation, so we can treat them identically.
672
  self.server = mcp
 
673
 
674
  @contextlib.asynccontextmanager
675
  async def connect_session(
676
  self, **session_kwargs: Unpack[SessionKwargs]
677
  ) -> AsyncIterator[ClientSession]:
678
- # create_connected_server_and_client_session manages the session lifecycle itself
679
- async with create_connected_server_and_client_session(
680
- server=self.server._mcp_server,
681
- **session_kwargs,
682
- ) as session:
683
- yield session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
  def __repr__(self) -> str:
686
- return f"<FastMCP(server='{self.server.name}')>"
687
 
688
 
689
  class MCPConfigTransport(ClientTransport):
@@ -769,7 +776,7 @@ class MCPConfigTransport(ClientTransport):
769
  yield session
770
 
771
  def __repr__(self) -> str:
772
- return f"<MCPConfig(config='{self.config}')>"
773
 
774
 
775
  @overload
@@ -860,7 +867,6 @@ def infer_transport(
860
  transport = infer_transport(config)
861
  ```
862
  """
863
- from fastmcp.utilities.mcp_config import MCPConfig
864
 
865
  # the transport is already a ClientTransport
866
  if isinstance(transport, ClientTransport):
 
8
  import warnings
9
  from collections.abc import AsyncIterator, Callable
10
  from pathlib import Path
11
+ from typing import Any, Literal, TypedDict, TypeVar, cast, overload
 
 
 
 
 
 
 
 
12
 
13
  import anyio
14
  import httpx
15
+ import mcp.types
16
  from mcp import ClientSession, StdioServerParameters
17
+ from mcp.client.session import ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
 
 
 
 
 
18
  from mcp.server.fastmcp import FastMCP as FastMCP1Server
19
+ from mcp.shared.memory import create_client_server_memory_streams
20
  from pydantic import AnyUrl
21
  from typing_extensions import Unpack
22
 
23
+ from fastmcp.client.auth.bearer import BearerAuth
24
  from fastmcp.client.auth.oauth import OAuth
25
  from fastmcp.server.dependencies import get_http_headers
26
  from fastmcp.server.server import FastMCP
27
  from fastmcp.utilities.logging import get_logger
28
  from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
29
 
 
 
 
30
  logger = get_logger(__name__)
31
 
32
  # TypeVar for preserving specific ClientTransport subclass types
 
50
  class SessionKwargs(TypedDict, total=False):
51
  """Keyword arguments for the MCP ClientSession constructor."""
52
 
53
+ read_timeout_seconds: datetime.timedelta | None
54
  sampling_callback: SamplingFnT | None
55
  list_roots_callback: ListRootsFnT | None
56
  logging_callback: LoggingFnT | None
57
  message_handler: MessageHandlerFnT | None
58
+ client_info: mcp.types.Implementation | None
59
 
60
 
61
  class ClientTransport(abc.ABC):
 
139
  yield session
140
 
141
  def __repr__(self) -> str:
142
+ return f"<WebSocketTransport(url='{self.url}')>"
143
 
144
 
145
  class SSETransport(ClientTransport):
 
170
  if auth == "oauth":
171
  auth = OAuth(self.url)
172
  elif isinstance(auth, str):
173
+ auth = BearerAuth(auth)
 
174
  self.auth = auth
175
 
176
  @contextlib.asynccontextmanager
 
207
  yield session
208
 
209
  def __repr__(self) -> str:
210
+ return f"<SSETransport(url='{self.url}')>"
211
 
212
 
213
  class StreamableHttpTransport(ClientTransport):
 
238
  if auth == "oauth":
239
  auth = OAuth(self.url)
240
  elif isinstance(auth, str):
241
+ auth = BearerAuth(auth)
 
242
  self.auth = auth
243
 
244
  @contextlib.asynccontextmanager
 
276
  yield session
277
 
278
  def __repr__(self) -> str:
279
+ return f"<StreamableHttpTransport(url='{self.url}')>"
280
 
281
 
282
  class StdioTransport(ClientTransport):
 
648
  tests or scenarios where client and server run in the same runtime.
649
  """
650
 
651
+ def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False):
652
  """Initialize a FastMCPTransport from a FastMCP server instance."""
653
 
654
  # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
655
  # ``_mcp_server`` attribute pointing to the underlying MCP server
656
  # implementation, so we can treat them identically.
657
  self.server = mcp
658
+ self.raise_exceptions = raise_exceptions
659
 
660
  @contextlib.asynccontextmanager
661
  async def connect_session(
662
  self, **session_kwargs: Unpack[SessionKwargs]
663
  ) -> AsyncIterator[ClientSession]:
664
+ async with create_client_server_memory_streams() as (
665
+ client_streams,
666
+ server_streams,
667
+ ):
668
+ client_read, client_write = client_streams
669
+ server_read, server_write = server_streams
670
+
671
+ # Create a cancel scope for the server task
672
+ async with anyio.create_task_group() as tg:
673
+ tg.start_soon(
674
+ lambda: self.server._mcp_server.run(
675
+ server_read,
676
+ server_write,
677
+ self.server._mcp_server.create_initialization_options(),
678
+ raise_exceptions=self.raise_exceptions,
679
+ )
680
+ )
681
+
682
+ try:
683
+ async with ClientSession(
684
+ read_stream=client_read,
685
+ write_stream=client_write,
686
+ **session_kwargs,
687
+ ) as client_session:
688
+ yield client_session
689
+ finally:
690
+ tg.cancel_scope.cancel()
691
 
692
  def __repr__(self) -> str:
693
+ return f"<FastMCPTransport(server='{self.server.name}')>"
694
 
695
 
696
  class MCPConfigTransport(ClientTransport):
 
776
  yield session
777
 
778
  def __repr__(self) -> str:
779
+ return f"<MCPConfigTransport(config='{self.config}')>"
780
 
781
 
782
  @overload
 
867
  transport = infer_transport(config)
868
  ```
869
  """
 
870
 
871
  # the transport is already a ClientTransport
872
  if isinstance(transport, ClientTransport):
src/fastmcp/server/dependencies.py CHANGED
@@ -67,6 +67,7 @@ def get_http_headers(include_all: bool = False) -> dict[str, str]:
67
  "te",
68
  "keep-alive",
69
  "expect",
 
70
  # Proxy-related headers
71
  "proxy-authenticate",
72
  "proxy-authorization",
 
67
  "te",
68
  "keep-alive",
69
  "expect",
70
+ "accept",
71
  # Proxy-related headers
72
  "proxy-authenticate",
73
  "proxy-authorization",
src/fastmcp/server/http.py CHANGED
@@ -13,6 +13,7 @@ from mcp.server.auth.middleware.bearer_auth import (
13
  from mcp.server.auth.routes import create_auth_routes
14
  from mcp.server.lowlevel.server import LifespanResultT
15
  from mcp.server.sse import SseServerTransport
 
16
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
17
  from starlette.applications import Starlette
18
  from starlette.middleware import Middleware
@@ -241,7 +242,7 @@ def create_sse_app(
241
  def create_streamable_http_app(
242
  server: FastMCP[LifespanResultT],
243
  streamable_http_path: str,
244
- event_store: None = None,
245
  auth: OAuthProvider | None = None,
246
  json_response: bool = False,
247
  stateless_http: bool = False,
 
13
  from mcp.server.auth.routes import create_auth_routes
14
  from mcp.server.lowlevel.server import LifespanResultT
15
  from mcp.server.sse import SseServerTransport
16
+ from mcp.server.streamable_http import EventStore
17
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
18
  from starlette.applications import Starlette
19
  from starlette.middleware import Middleware
 
242
  def create_streamable_http_app(
243
  server: FastMCP[LifespanResultT],
244
  streamable_http_path: str,
245
+ event_store: EventStore | None = None,
246
  auth: OAuthProvider | None = None,
247
  json_response: bool = False,
248
  stateless_http: bool = False,
src/fastmcp/server/server.py CHANGED
@@ -41,6 +41,7 @@ from starlette.requests import Request
41
  from starlette.responses import Response
42
  from starlette.routing import BaseRoute, Route
43
 
 
44
  import fastmcp.server
45
  import fastmcp.settings
46
  from fastmcp.exceptions import NotFoundError
@@ -131,6 +132,8 @@ class FastMCP(Generic[LifespanResultT]):
131
  tools: list[Tool | Callable[..., Any]] | None = None,
132
  **settings: Any,
133
  ):
 
 
134
  self.settings = fastmcp.settings.ServerSettings(**settings)
135
 
136
  # If mask_error_details is provided, override the settings value
@@ -148,7 +151,9 @@ class FastMCP(Generic[LifespanResultT]):
148
  self.tags: set[str] = tags or set()
149
  self.dependencies = dependencies
150
  self._cache = TimedCache(
151
- expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
 
 
152
  )
153
  self._mounted_servers: dict[str, MountedServer] = {}
154
  self._additional_http_routes: list[BaseRoute] = []
@@ -496,11 +501,7 @@ class FastMCP(Generic[LifespanResultT]):
496
  with the Context type annotation. See the @tool decorator for examples.
497
 
498
  Args:
499
- fn: The function to register as a tool
500
- name: Optional name for the tool (defaults to function name)
501
- description: Optional description of what the tool does
502
- tags: Optional set of tags for categorizing the tool
503
- annotations: Optional annotations about the tool's behavior
504
  """
505
  self._tool_manager.add_tool(tool)
506
  self._cache.clear()
@@ -870,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
870
 
871
  This decorator supports multiple calling patterns:
872
  - @server.prompt (without parentheses)
873
- - @server.prompt (with empty parentheses)
874
  - @server.prompt("custom_name") (with name as first argument)
875
  - @server.prompt(name="custom_name") (with name as keyword argument)
876
  - server.prompt(function, name="custom_name") (direct function call)
@@ -892,7 +893,7 @@ class FastMCP(Generic[LifespanResultT]):
892
  }
893
  ]
894
 
895
- @server.prompt
896
  def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
897
  ctx.info(f"Analyzing table {table_name}")
898
  schema = read_table_schema(table_name)
 
41
  from starlette.responses import Response
42
  from starlette.routing import BaseRoute, Route
43
 
44
+ import fastmcp
45
  import fastmcp.server
46
  import fastmcp.settings
47
  from fastmcp.exceptions import NotFoundError
 
132
  tools: list[Tool | Callable[..., Any]] | None = None,
133
  **settings: Any,
134
  ):
135
+ if cache_expiration_seconds is not None:
136
+ settings["cache_expiration_seconds"] = cache_expiration_seconds
137
  self.settings = fastmcp.settings.ServerSettings(**settings)
138
 
139
  # If mask_error_details is provided, override the settings value
 
151
  self.tags: set[str] = tags or set()
152
  self.dependencies = dependencies
153
  self._cache = TimedCache(
154
+ expiration=datetime.timedelta(
155
+ seconds=self.settings.cache_expiration_seconds
156
+ )
157
  )
158
  self._mounted_servers: dict[str, MountedServer] = {}
159
  self._additional_http_routes: list[BaseRoute] = []
 
501
  with the Context type annotation. See the @tool decorator for examples.
502
 
503
  Args:
504
+ tool: The Tool instance to register
 
 
 
 
505
  """
506
  self._tool_manager.add_tool(tool)
507
  self._cache.clear()
 
871
 
872
  This decorator supports multiple calling patterns:
873
  - @server.prompt (without parentheses)
874
+ - @server.prompt() (with empty parentheses)
875
  - @server.prompt("custom_name") (with name as first argument)
876
  - @server.prompt(name="custom_name") (with name as keyword argument)
877
  - server.prompt(function, name="custom_name") (direct function call)
 
893
  }
894
  ]
895
 
896
+ @server.prompt()
897
  def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
898
  ctx.info(f"Analyzing table {table_name}")
899
  schema = read_table_schema(table_name)
src/fastmcp/settings.py CHANGED
@@ -170,7 +170,7 @@ class ServerSettings(BaseSettings):
170
  ),
171
  ] = []
172
 
173
- # cache settings (for checking mounted servers)
174
  cache_expiration_seconds: float = 0
175
 
176
  # StreamableHTTP settings
 
170
  ),
171
  ] = []
172
 
173
+ # cache settings (for getting attributes from servers, used to avoid repeated calls)
174
  cache_expiration_seconds: float = 0
175
 
176
  # StreamableHTTP settings
src/fastmcp/utilities/mcp_config.py CHANGED
@@ -1,6 +1,6 @@
1
  from __future__ import annotations
2
 
3
- from typing import TYPE_CHECKING, Any, Literal
4
  from urllib.parse import urlparse
5
 
6
  from pydantic import AnyUrl, Field
@@ -55,7 +55,13 @@ class StdioMCPServer(FastMCPBaseModel):
55
  class RemoteMCPServer(FastMCPBaseModel):
56
  url: str
57
  headers: dict[str, str] = Field(default_factory=dict)
58
- transport: Literal["streamable-http", "sse", "http"] | None = None
 
 
 
 
 
 
59
 
60
  def to_transport(self) -> StreamableHttpTransport | SSETransport:
61
  from fastmcp.client.transports import SSETransport, StreamableHttpTransport
@@ -66,9 +72,11 @@ class RemoteMCPServer(FastMCPBaseModel):
66
  transport = self.transport
67
 
68
  if transport == "sse":
69
- return SSETransport(self.url, headers=self.headers)
70
  else:
71
- return StreamableHttpTransport(self.url, headers=self.headers)
 
 
72
 
73
 
74
  class MCPConfig(FastMCPBaseModel):
 
1
  from __future__ import annotations
2
 
3
+ from typing import TYPE_CHECKING, Annotated, Any, Literal
4
  from urllib.parse import urlparse
5
 
6
  from pydantic import AnyUrl, Field
 
55
  class RemoteMCPServer(FastMCPBaseModel):
56
  url: str
57
  headers: dict[str, str] = Field(default_factory=dict)
58
+ transport: Literal["streamable-http", "sse"] | None = None
59
+ auth: Annotated[
60
+ str | Literal["oauth"] | None,
61
+ Field(
62
+ description='Either a string representing a Bearer token or the literal "oauth" to use OAuth authentication.'
63
+ ),
64
+ ] = None
65
 
66
  def to_transport(self) -> StreamableHttpTransport | SSETransport:
67
  from fastmcp.client.transports import SSETransport, StreamableHttpTransport
 
72
  transport = self.transport
73
 
74
  if transport == "sse":
75
+ return SSETransport(self.url, headers=self.headers, auth=self.auth)
76
  else:
77
+ return StreamableHttpTransport(
78
+ self.url, headers=self.headers, auth=self.auth
79
+ )
80
 
81
 
82
  class MCPConfig(FastMCPBaseModel):
tests/client/test_client.py CHANGED
@@ -1,12 +1,16 @@
1
  import asyncio
2
  import sys
3
  from typing import cast
 
4
 
 
5
  import pytest
6
  from mcp import McpError
 
7
  from pydantic import AnyUrl
8
 
9
  from fastmcp.client import Client
 
10
  from fastmcp.client.transports import (
11
  FastMCPTransport,
12
  MCPConfigTransport,
@@ -273,6 +277,14 @@ async def test_client_connection(fastmcp_server):
273
  assert not client.is_connected()
274
 
275
 
 
 
 
 
 
 
 
 
276
  async def test_initialize_result_connected(fastmcp_server):
277
  """Test that initialize_result returns the correct result when connected."""
278
  client = Client(transport=FastMCPTransport(fastmcp_server))
@@ -810,3 +822,73 @@ class TestInferTransport:
810
  server = FastMCP1()
811
  transport = infer_transport(server)
812
  assert isinstance(transport, FastMCPTransport)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import asyncio
2
  import sys
3
  from typing import cast
4
+ from unittest.mock import AsyncMock
5
 
6
+ import mcp
7
  import pytest
8
  from mcp import McpError
9
+ from mcp.client.auth import OAuthClientProvider
10
  from pydantic import AnyUrl
11
 
12
  from fastmcp.client import Client
13
+ from fastmcp.client.auth.bearer import BearerAuth
14
  from fastmcp.client.transports import (
15
  FastMCPTransport,
16
  MCPConfigTransport,
 
277
  assert not client.is_connected()
278
 
279
 
280
+ async def test_initialize_called_once(fastmcp_server, monkeypatch):
281
+ mock_initialize = AsyncMock()
282
+ monkeypatch.setattr(mcp.ClientSession, "initialize", mock_initialize)
283
+ client = Client(transport=FastMCPTransport(fastmcp_server))
284
+ async with client:
285
+ assert mock_initialize.call_count == 1
286
+
287
+
288
  async def test_initialize_result_connected(fastmcp_server):
289
  """Test that initialize_result returns the correct result when connected."""
290
  client = Client(transport=FastMCPTransport(fastmcp_server))
 
822
  server = FastMCP1()
823
  transport = infer_transport(server)
824
  assert isinstance(transport, FastMCPTransport)
825
+
826
+
827
+ class TestAuth:
828
+ def test_default_auth_is_none(self):
829
+ client = Client(transport=StreamableHttpTransport("http://localhost:8000"))
830
+ assert client.transport.auth is None
831
+
832
+ def test_stdio_doesnt_support_auth(self):
833
+ with pytest.raises(ValueError, match="This transport does not support auth"):
834
+ Client(transport=StdioTransport("echo", ["hello"]), auth="oauth")
835
+
836
+ def test_oauth_literal_sets_up_oauth_shttp(self):
837
+ client = Client(
838
+ transport=StreamableHttpTransport("http://localhost:8000"), auth="oauth"
839
+ )
840
+ assert isinstance(client.transport, StreamableHttpTransport)
841
+ assert isinstance(client.transport.auth, OAuthClientProvider)
842
+
843
+ def test_oauth_literal_pass_direct_to_transport(self):
844
+ client = Client(
845
+ transport=StreamableHttpTransport("http://localhost:8000", auth="oauth"),
846
+ )
847
+ assert isinstance(client.transport, StreamableHttpTransport)
848
+ assert isinstance(client.transport.auth, OAuthClientProvider)
849
+
850
+ def test_oauth_literal_sets_up_oauth_sse(self):
851
+ client = Client(transport=SSETransport("http://localhost:8000"), auth="oauth")
852
+ assert isinstance(client.transport, SSETransport)
853
+ assert isinstance(client.transport.auth, OAuthClientProvider)
854
+
855
+ def test_oauth_literal_pass_direct_to_transport_sse(self):
856
+ client = Client(transport=SSETransport("http://localhost:8000", auth="oauth"))
857
+ assert isinstance(client.transport, SSETransport)
858
+ assert isinstance(client.transport.auth, OAuthClientProvider)
859
+
860
+ def test_auth_string_sets_up_bearer_auth_shttp(self):
861
+ client = Client(
862
+ transport=StreamableHttpTransport("http://localhost:8000"),
863
+ auth="test_token",
864
+ )
865
+ assert isinstance(client.transport, StreamableHttpTransport)
866
+ assert isinstance(client.transport.auth, BearerAuth)
867
+ assert client.transport.auth.token.get_secret_value() == "test_token"
868
+
869
+ def test_auth_string_pass_direct_to_transport_shttp(self):
870
+ client = Client(
871
+ transport=StreamableHttpTransport(
872
+ "http://localhost:8000", auth="test_token"
873
+ ),
874
+ )
875
+ assert isinstance(client.transport, StreamableHttpTransport)
876
+ assert isinstance(client.transport.auth, BearerAuth)
877
+ assert client.transport.auth.token.get_secret_value() == "test_token"
878
+
879
+ def test_auth_string_sets_up_bearer_auth_sse(self):
880
+ client = Client(
881
+ transport=SSETransport("http://localhost:8000"),
882
+ auth="test_token",
883
+ )
884
+ assert isinstance(client.transport, SSETransport)
885
+ assert isinstance(client.transport.auth, BearerAuth)
886
+ assert client.transport.auth.token.get_secret_value() == "test_token"
887
+
888
+ def test_auth_string_pass_direct_to_transport_sse(self):
889
+ client = Client(
890
+ transport=SSETransport("http://localhost:8000", auth="test_token"),
891
+ )
892
+ assert isinstance(client.transport, SSETransport)
893
+ assert isinstance(client.transport.auth, BearerAuth)
894
+ assert client.transport.auth.token.get_secret_value() == "test_token"
tests/server/test_mount.py CHANGED
@@ -304,6 +304,19 @@ class TestDynamicChanges:
304
  tools = await main_app.get_tools()
305
  assert "sub_temp_tool" not in tools
306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
  class TestResourcesAndTemplates:
309
  """Test mounting with resources and resource templates."""
 
304
  tools = await main_app.get_tools()
305
  assert "sub_temp_tool" not in tools
306
 
307
+ async def test_cache_expiration(self):
308
+ main_app = FastMCP("MainApp", cache_expiration_seconds=2)
309
+ sub_app = FastMCP("SubApp")
310
+ tools = await main_app.get_tools()
311
+ assert len(tools) == 0
312
+
313
+ @sub_app.tool
314
+ def sub_tool():
315
+ return "sub_tool"
316
+
317
+ tools = await main_app.get_tools()
318
+ assert len(tools) == 0
319
+
320
 
321
  class TestResourcesAndTemplates:
322
  """Test mounting with resources and resource templates."""
tests/server/test_proxy.py CHANGED
@@ -9,7 +9,7 @@ from pydantic import AnyUrl
9
 
10
  from fastmcp import FastMCP
11
  from fastmcp.client import Client
12
- from fastmcp.client.transports import FastMCPTransport
13
  from fastmcp.exceptions import ToolError
14
  from fastmcp.server.proxy import FastMCPProxy
15
 
@@ -104,7 +104,8 @@ def test_as_proxy_with_url():
104
  """FastMCP.as_proxy should accept a URL without connecting."""
105
  proxy = FastMCP.as_proxy("http://example.com/mcp")
106
  assert isinstance(proxy, FastMCPProxy)
107
- assert repr(proxy.client.transport).startswith("<StreamableHttp(")
 
108
 
109
 
110
  class TestTools:
 
9
 
10
  from fastmcp import FastMCP
11
  from fastmcp.client import Client
12
+ from fastmcp.client.transports import FastMCPTransport, StreamableHttpTransport
13
  from fastmcp.exceptions import ToolError
14
  from fastmcp.server.proxy import FastMCPProxy
15
 
 
104
  """FastMCP.as_proxy should accept a URL without connecting."""
105
  proxy = FastMCP.as_proxy("http://example.com/mcp")
106
  assert isinstance(proxy, FastMCPProxy)
107
+ assert isinstance(proxy.client.transport, StreamableHttpTransport)
108
+ assert proxy.client.transport.url == "http://example.com/mcp"
109
 
110
 
111
  class TestTools:
tests/server/test_server_interactions.py CHANGED
@@ -617,7 +617,7 @@ class TestToolContextInjection:
617
  result = await client.call_tool("tool_with_context", {"x": 42})
618
  assert len(result) == 1
619
  content = result[0]
620
- assert content.text == "2" # type: ignore[attr-defined]
621
 
622
  async def test_async_context(self):
623
  """Test that context works in async functions."""
@@ -632,7 +632,7 @@ class TestToolContextInjection:
632
  result = await client.call_tool("async_tool", {"x": 42})
633
  assert len(result) == 1
634
  content = result[0]
635
- assert content.text == "Async request 2: 42" # type: ignore[attr-defined]
636
 
637
  async def test_optional_context(self):
638
  """Test that context is optional."""
@@ -696,7 +696,7 @@ class TestToolContextInjection:
696
 
697
  async with Client(mcp) as client:
698
  result = await client.call_tool("MyTool", {"x": 2})
699
- assert result[0].text == "4" # type: ignore[attr-defined]
700
 
701
 
702
  class TestResource:
@@ -780,7 +780,7 @@ class TestResourceContext:
780
 
781
  async with Client(mcp) as client:
782
  result = await client.read_resource(AnyUrl("resource://test"))
783
- assert result[0].text == "2" # type: ignore[attr-defined]
784
 
785
 
786
  class TestResourceTemplates:
@@ -1015,7 +1015,7 @@ class TestResourceTemplateContext:
1015
 
1016
  async with Client(mcp) as client:
1017
  result = await client.read_resource(AnyUrl("resource://test"))
1018
- assert result[0].text.startswith("Resource template: test 2") # type: ignore[attr-defined]
1019
 
1020
  async def test_resource_template_context_with_callable_object(self):
1021
  mcp = FastMCP()
@@ -1031,7 +1031,7 @@ class TestResourceTemplateContext:
1031
 
1032
  async with Client(mcp) as client:
1033
  result = await client.read_resource(AnyUrl("resource://test"))
1034
- assert result[0].text.startswith("Resource template: test 2") # type: ignore[attr-defined]
1035
 
1036
 
1037
  class TestPrompts:
@@ -1249,4 +1249,4 @@ class TestPromptContext:
1249
  assert len(result.messages) == 1
1250
  message = result.messages[0]
1251
  assert message.role == "user"
1252
- assert message.content.text == "Hello, World! 2" # type: ignore[attr-defined]
 
617
  result = await client.call_tool("tool_with_context", {"x": 42})
618
  assert len(result) == 1
619
  content = result[0]
620
+ assert content.text == "1" # type: ignore[attr-defined]
621
 
622
  async def test_async_context(self):
623
  """Test that context works in async functions."""
 
632
  result = await client.call_tool("async_tool", {"x": 42})
633
  assert len(result) == 1
634
  content = result[0]
635
+ assert content.text == "Async request 1: 42" # type: ignore[attr-defined]
636
 
637
  async def test_optional_context(self):
638
  """Test that context is optional."""
 
696
 
697
  async with Client(mcp) as client:
698
  result = await client.call_tool("MyTool", {"x": 2})
699
+ assert result[0].text == "3" # type: ignore[attr-defined]
700
 
701
 
702
  class TestResource:
 
780
 
781
  async with Client(mcp) as client:
782
  result = await client.read_resource(AnyUrl("resource://test"))
783
+ assert result[0].text == "1" # type: ignore[attr-defined]
784
 
785
 
786
  class TestResourceTemplates:
 
1015
 
1016
  async with Client(mcp) as client:
1017
  result = await client.read_resource(AnyUrl("resource://test"))
1018
+ assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
1019
 
1020
  async def test_resource_template_context_with_callable_object(self):
1021
  mcp = FastMCP()
 
1031
 
1032
  async with Client(mcp) as client:
1033
  result = await client.read_resource(AnyUrl("resource://test"))
1034
+ assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
1035
 
1036
 
1037
  class TestPrompts:
 
1249
  assert len(result.messages) == 1
1250
  message = result.messages[0]
1251
  assert message.role == "user"
1252
+ assert message.content.text == "Hello, World! 1" # type: ignore[attr-defined]
tests/utilities/test_mcp_config.py CHANGED
@@ -1,6 +1,8 @@
1
  import inspect
2
  from pathlib import Path
3
 
 
 
4
  from fastmcp.client.client import Client
5
  from fastmcp.client.transports import (
6
  SSETransport,
@@ -136,3 +138,60 @@ async def test_multi_client(tmp_path: Path):
136
  result_2 = await client.call_tool("test_2_add", {"a": 1, "b": 2})
137
  assert result_1[0].text == "3" # type: ignore[attr-dict]
138
  assert result_2[0].text == "3" # type: ignore[attr-dict]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import inspect
2
  from pathlib import Path
3
 
4
+ from fastmcp.client.auth.bearer import BearerAuth
5
+ from fastmcp.client.auth.oauth import OAuthClientProvider
6
  from fastmcp.client.client import Client
7
  from fastmcp.client.transports import (
8
  SSETransport,
 
138
  result_2 = await client.call_tool("test_2_add", {"a": 1, "b": 2})
139
  assert result_1[0].text == "3" # type: ignore[attr-dict]
140
  assert result_2[0].text == "3" # type: ignore[attr-dict]
141
+
142
+
143
+ async def test_remote_config_default_no_auth():
144
+ config = {
145
+ "mcpServers": {
146
+ "test_server": {
147
+ "url": "http://localhost:8000",
148
+ }
149
+ }
150
+ }
151
+ client = Client(config)
152
+ assert isinstance(client.transport.transport, StreamableHttpTransport)
153
+ assert client.transport.transport.auth is None
154
+
155
+
156
+ async def test_remote_config_with_auth_token():
157
+ config = {
158
+ "mcpServers": {
159
+ "test_server": {
160
+ "url": "http://localhost:8000",
161
+ "auth": "test_token",
162
+ }
163
+ }
164
+ }
165
+ client = Client(config)
166
+ assert isinstance(client.transport.transport, StreamableHttpTransport)
167
+ assert isinstance(client.transport.transport.auth, BearerAuth)
168
+ assert client.transport.transport.auth.token.get_secret_value() == "test_token"
169
+
170
+
171
+ async def test_remote_config_sse_with_auth_token():
172
+ config = {
173
+ "mcpServers": {
174
+ "test_server": {
175
+ "url": "http://localhost:8000/sse",
176
+ "auth": "test_token",
177
+ }
178
+ }
179
+ }
180
+ client = Client(config)
181
+ assert isinstance(client.transport.transport, SSETransport)
182
+ assert isinstance(client.transport.transport.auth, BearerAuth)
183
+ assert client.transport.transport.auth.token.get_secret_value() == "test_token"
184
+
185
+
186
+ async def test_remote_config_with_oauth_literal():
187
+ config = {
188
+ "mcpServers": {
189
+ "test_server": {
190
+ "url": "http://localhost:8000",
191
+ "auth": "oauth",
192
+ }
193
+ }
194
+ }
195
+ client = Client(config)
196
+ assert isinstance(client.transport.transport, StreamableHttpTransport)
197
+ assert isinstance(client.transport.transport.auth, OAuthClientProvider)