Jeremiah Lowin commited on
Commit
ac8bd40
·
unverified ·
2 Parent(s): 8e52c1969e9489

Merge pull request #917 from jlowin/http

Browse files
CLAUDE.md CHANGED
@@ -32,4 +32,5 @@ async with Client(transport=StreamableHttpTransport(server_url)) as client:
32
  ## Development Workflow
33
 
34
  - You must always run pre-commit if you open a PR, because it is run as part of a required check.
35
- - When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.
 
 
32
  ## Development Workflow
33
 
34
  - You must always run pre-commit if you open a PR, because it is run as part of a required check.
35
+ - When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.
36
+ - NEVER modify files in docs/python-sdk/**, as they are auto-generated.
README.md CHANGED
@@ -349,7 +349,7 @@ mcp.run(transport="stdio") # Default, so transport argument is optional
349
  **Streamable HTTP**: Recommended for web deployments.
350
 
351
  ```python
352
- mcp.run(transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp")
353
  ```
354
 
355
  **SSE**: For compatibility with existing SSE clients.
 
349
  **Streamable HTTP**: Recommended for web deployments.
350
 
351
  ```python
352
+ mcp.run(transport="http", host="127.0.0.1", port=8000, path="/mcp")
353
  ```
354
 
355
  **SSE**: For compatibility with existing SSE clients.
docs/clients/client.mdx CHANGED
@@ -102,7 +102,7 @@ config = {
102
  "mcpServers": {
103
  "server_name": {
104
  # Remote HTTP/SSE server
105
- "transport": "streamable-http", # or "sse"
106
  "url": "https://api.example.com/mcp",
107
  "headers": {"Authorization": "Bearer token"},
108
  "auth": "oauth" # or bearer token string
 
102
  "mcpServers": {
103
  "server_name": {
104
  # Remote HTTP/SSE server
105
+ "transport": "http", # or "sse"
106
  "url": "https://api.example.com/mcp",
107
  "headers": {"Authorization": "Bearer token"},
108
  "auth": "oauth" # or bearer token string
docs/clients/transports.mdx CHANGED
@@ -41,7 +41,7 @@ Streamable HTTP is the recommended transport for web-based deployments, providin
41
 
42
  - **Class:** `fastmcp.client.transports.StreamableHttpTransport`
43
  - **Inferred From:** URLs starting with `http://` or `https://` (default for HTTP URLs since v2.3.0) that do not contain `/sse/` in the path
44
- - **Server Compatibility:** Works with FastMCP servers running in `streamable-http` mode
45
 
46
  #### Basic Usage
47
 
@@ -150,7 +150,7 @@ client = Client(transport)
150
  - **Use Streamable HTTP when:**
151
  - Setting up new deployments (recommended default)
152
  - You need bidirectional streaming
153
- - You're connecting to FastMCP servers running in `streamable-http` mode
154
 
155
  - **Use SSE when:**
156
  - Connecting to legacy FastMCP servers running in `sse` mode
@@ -397,7 +397,7 @@ config = {
397
  # Remote HTTP server
398
  "weather": {
399
  "url": "https://weather-api.example.com/mcp",
400
- "transport": "streamable-http"
401
  },
402
  # Local stdio server
403
  "assistant": {
@@ -408,7 +408,7 @@ config = {
408
  # Another remote server
409
  "calendar": {
410
  "url": "https://calendar-api.example.com/mcp",
411
- "transport": "streamable-http"
412
  }
413
  }
414
  }
 
41
 
42
  - **Class:** `fastmcp.client.transports.StreamableHttpTransport`
43
  - **Inferred From:** URLs starting with `http://` or `https://` (default for HTTP URLs since v2.3.0) that do not contain `/sse/` in the path
44
+ - **Server Compatibility:** Works with FastMCP servers running in `http` mode
45
 
46
  #### Basic Usage
47
 
 
150
  - **Use Streamable HTTP when:**
151
  - Setting up new deployments (recommended default)
152
  - You need bidirectional streaming
153
+ - You're connecting to FastMCP servers running in `http` mode
154
 
155
  - **Use SSE when:**
156
  - Connecting to legacy FastMCP servers running in `sse` mode
 
397
  # Remote HTTP server
398
  "weather": {
399
  "url": "https://weather-api.example.com/mcp",
400
+ "transport": "http"
401
  },
402
  # Local stdio server
403
  "assistant": {
 
408
  # Another remote server
409
  "calendar": {
410
  "url": "https://calendar-api.example.com/mcp",
411
+ "transport": "http"
412
  }
413
  }
414
  }
docs/deployment/running-server.mdx CHANGED
@@ -105,7 +105,7 @@ When using Stdio transport, you will typically *not* run the server yourself as
105
 
106
  Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments.
107
 
108
- To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
109
  <CodeGroup>
110
  ```python {6} server.py
111
  from fastmcp import FastMCP
@@ -113,7 +113,7 @@ from fastmcp import FastMCP
113
  mcp = FastMCP()
114
 
115
  if __name__ == "__main__":
116
- mcp.run(transport="streamable-http")
117
  ```
118
  ```python {5} client.py
119
  import asyncio
@@ -128,6 +128,10 @@ if __name__ == "__main__":
128
  ```
129
  </CodeGroup>
130
 
 
 
 
 
131
  To customize the host, port, path, or log level, provide appropriate keyword arguments to the `run()` method.
132
 
133
  <CodeGroup>
@@ -138,7 +142,7 @@ mcp = FastMCP()
138
 
139
  if __name__ == "__main__":
140
  mcp.run(
141
- transport="streamable-http",
142
  host="127.0.0.1",
143
  port=4200,
144
  path="/my-custom-path",
@@ -158,7 +162,6 @@ if __name__ == "__main__":
158
  ```
159
  </CodeGroup>
160
 
161
-
162
  ### SSE
163
 
164
  <Warning>
@@ -250,7 +253,7 @@ def hello(name: str) -> str:
250
 
251
  async def main():
252
  # Use run_async() in async contexts
253
- await mcp.run_async(transport="streamable-http")
254
 
255
  if __name__ == "__main__":
256
  asyncio.run(main())
 
105
 
106
  Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments.
107
 
108
+ To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
109
  <CodeGroup>
110
  ```python {6} server.py
111
  from fastmcp import FastMCP
 
113
  mcp = FastMCP()
114
 
115
  if __name__ == "__main__":
116
+ mcp.run(transport="http")
117
  ```
118
  ```python {5} client.py
119
  import asyncio
 
128
  ```
129
  </CodeGroup>
130
 
131
+ <Tip>
132
+ For backward compatibility, wherever `"http"` is accepted as a transport name, you can also pass `"streamable-http"` as a fully supported alias. This is particularly useful when upgrading from FastMCP 1.x in the official Python SDK and FastMCP \<= 2.9, where `"streamable-http"` was the standard name.
133
+ </Tip>
134
+
135
  To customize the host, port, path, or log level, provide appropriate keyword arguments to the `run()` method.
136
 
137
  <CodeGroup>
 
142
 
143
  if __name__ == "__main__":
144
  mcp.run(
145
+ transport="http",
146
  host="127.0.0.1",
147
  port=4200,
148
  path="/my-custom-path",
 
162
  ```
163
  </CodeGroup>
164
 
 
165
  ### SSE
166
 
167
  <Warning>
 
253
 
254
  async def main():
255
  # Use run_async() in async contexts
256
+ await mcp.run_async(transport="http")
257
 
258
  if __name__ == "__main__":
259
  asyncio.run(main())
docs/getting-started/installation.mdx CHANGED
@@ -47,7 +47,7 @@ FastMCP root path: ~/Developer/fastmcp
47
  Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
48
 
49
 
50
- ```python {1-5}
51
  # Before
52
  # from mcp.server.fastmcp import FastMCP
53
 
@@ -56,8 +56,9 @@ from fastmcp import FastMCP
56
 
57
  mcp = FastMCP("My MCP Server")
58
  ```
 
59
  <Warning>
60
- Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
61
  </Warning>
62
 
63
  ## Versioning and Breaking Changes
 
47
  Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
48
 
49
 
50
+ ```python {5}
51
  # Before
52
  # from mcp.server.fastmcp import FastMCP
53
 
 
56
 
57
  mcp = FastMCP("My MCP Server")
58
  ```
59
+
60
  <Warning>
61
+ Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
62
  </Warning>
63
 
64
  ## Versioning and Breaking Changes
docs/integrations/anthropic.mdx CHANGED
@@ -31,7 +31,7 @@ def roll_dice(n_dice: int) -> list[int]:
31
  return [random.randint(1, 6) for _ in range(n_dice)]
32
 
33
  if __name__ == "__main__":
34
- mcp.run(transport="streamable-http", port=8000)
35
  ```
36
 
37
  ## Deploy the Server
@@ -175,7 +175,7 @@ def roll_dice(n_dice: int) -> list[int]:
175
 
176
  if __name__ == "__main__":
177
  print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
178
- mcp.run(transport="streamable-http", port=8000)
179
  ```
180
 
181
  ### Client Authentication
 
31
  return [random.randint(1, 6) for _ in range(n_dice)]
32
 
33
  if __name__ == "__main__":
34
+ mcp.run(transport="http", port=8000)
35
  ```
36
 
37
  ## Deploy the Server
 
175
 
176
  if __name__ == "__main__":
177
  print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
178
+ mcp.run(transport="http", port=8000)
179
  ```
180
 
181
  ### Client Authentication
docs/integrations/chatgpt.mdx CHANGED
@@ -102,7 +102,7 @@ def create_server(
102
 
103
  if __name__ == "__main__":
104
  mcp = create_server("path/to/records.json")
105
- mcp.run(transport="streamable-http", port=8000)
106
  ```
107
 
108
  ### Deploy the Server
 
102
 
103
  if __name__ == "__main__":
104
  mcp = create_server("path/to/records.json")
105
+ mcp.run(transport="http", port=8000)
106
  ```
107
 
108
  ### Deploy the Server
docs/integrations/claude-code.mdx CHANGED
@@ -32,7 +32,7 @@ def roll_dice(n_dice: int) -> list[int]:
32
  return [random.randint(1, 6) for _ in range(n_dice)]
33
 
34
  if __name__ == "__main__":
35
- mcp.run(transport="streamable-http", port=8000)
36
  ```
37
 
38
  ## Connect to Claude Code
 
32
  return [random.randint(1, 6) for _ in range(n_dice)]
33
 
34
  if __name__ == "__main__":
35
+ mcp.run(transport="http", port=8000)
36
  ```
37
 
38
  ## Connect to Claude Code
docs/integrations/openai.mdx CHANGED
@@ -38,7 +38,7 @@ def roll_dice(n_dice: int) -> list[int]:
38
  return [random.randint(1, 6) for _ in range(n_dice)]
39
 
40
  if __name__ == "__main__":
41
- mcp.run(transport="streamable-http", port=8000)
42
  ```
43
 
44
  ### Deploy the Server
@@ -172,7 +172,7 @@ def roll_dice(n_dice: int) -> list[int]:
172
 
173
  if __name__ == "__main__":
174
  print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
175
- mcp.run(transport="streamable-http", port=8000)
176
  ```
177
 
178
  #### Client Authentication
 
38
  return [random.randint(1, 6) for _ in range(n_dice)]
39
 
40
  if __name__ == "__main__":
41
+ mcp.run(transport="http", port=8000)
42
  ```
43
 
44
  ### Deploy the Server
 
172
 
173
  if __name__ == "__main__":
174
  print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
175
+ mcp.run(transport="http", port=8000)
176
  ```
177
 
178
  #### Client Authentication
docs/patterns/cli.mdx CHANGED
@@ -42,11 +42,12 @@ This command runs the server directly in your current Python environment. You ar
42
 
43
  | Option | Flag | Description |
44
  | ------ | ---- | ----------- |
45
- | Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `streamable-http`, or `sse`) |
46
  | Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) |
47
  | Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) |
48
  | Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
49
 
 
50
  #### Server Specification
51
  <VersionBadge version="2.3.5" />
52
 
@@ -79,14 +80,14 @@ if __name__ == "__main__":
79
  You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:
80
 
81
  ```bash
82
- fastmcp run server.py --transport streamable-http --port 8000
83
  ```
84
 
85
  **Examples**
86
 
87
  ```bash
88
  # Run a local server with Streamable HTTP transport on a custom port
89
- fastmcp run server.py --transport streamable-http --port 8000
90
 
91
  # Connect to a remote server and proxy as a stdio server
92
  fastmcp run https://example.com/mcp-server
@@ -112,14 +113,14 @@ The `dev` command is a shortcut for testing a server over STDIO only. When the I
112
  1. Select "STDIO" from the transport dropdown
113
  2. Connect manually
114
 
115
- This command does not support HTTP testing. To test a server over HTTP:
116
- 1. Start your server manually with HTTP transport using either:
117
  ```bash
118
- fastmcp run server.py --transport streamable-http
119
  ```
120
- or
121
  ```bash
122
- python server.py # Assuming your __main__ block sets HTTP transport
123
  ```
124
  2. Open the MCP Inspector separately and connect to your running server
125
  </Warning>
 
42
 
43
  | Option | Flag | Description |
44
  | ------ | ---- | ----------- |
45
+ | Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `http`, or `sse`) |
46
  | Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) |
47
  | Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) |
48
  | Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
49
 
50
+
51
  #### Server Specification
52
  <VersionBadge version="2.3.5" />
53
 
 
80
  You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:
81
 
82
  ```bash
83
+ fastmcp run server.py --transport http --port 8000
84
  ```
85
 
86
  **Examples**
87
 
88
  ```bash
89
  # Run a local server with Streamable HTTP transport on a custom port
90
+ fastmcp run server.py --transport http --port 8000
91
 
92
  # Connect to a remote server and proxy as a stdio server
93
  fastmcp run https://example.com/mcp-server
 
113
  1. Select "STDIO" from the transport dropdown
114
  2. Connect manually
115
 
116
+ This command does not support HTTP testing. To test a server over Streamable HTTP or SSE:
117
+ 1. Start your server manually with the appropriate transport using either the command line:
118
  ```bash
119
+ fastmcp run server.py --transport http
120
  ```
121
+ or by setting the transport in your code:
122
  ```bash
123
+ python server.py # Assuming your __main__ block sets Streamable HTTP transport
124
  ```
125
  2. Open the MCP Inspector separately and connect to your running server
126
  </Warning>
docs/python-sdk/fastmcp-client-transports.mdx CHANGED
@@ -168,11 +168,11 @@ Transport for connecting to one or more MCP servers defined in an MCPConfig.
168
  "mcpServers": {
169
  "weather": {
170
  "url": "https://weather-api.example.com/mcp",
171
- "transport": "streamable-http"
172
  },
173
  "calendar": {
174
  "url": "https://calendar-api.example.com/mcp",
175
- "transport": "streamable-http"
176
  }
177
  }
178
  }
 
168
  "mcpServers": {
169
  "weather": {
170
  "url": "https://weather-api.example.com/mcp",
171
+ "transport": "http"
172
  },
173
  "calendar": {
174
  "url": "https://calendar-api.example.com/mcp",
175
+ "transport": "http"
176
  }
177
  }
178
  }
docs/servers/auth/bearer.mdx CHANGED
@@ -19,7 +19,7 @@ The [MCP specification](https://modelcontextprotocol.io/specification/2025-03-26
19
 
20
  Bearer Token authentication is a common way to secure HTTP-based APIs. In this model, the client sends a token (usually a JSON Web Token or JWT) in the `Authorization` header with the "Bearer" scheme. The server then validates this token to grant or deny access.
21
 
22
- FastMCP supports Bearer Token authentication for its HTTP-based transports (`streamable-http` and `sse`), allowing you to protect your server from unauthorized access.
23
 
24
  ## Authentication Strategy
25
 
 
19
 
20
  Bearer Token authentication is a common way to secure HTTP-based APIs. In this model, the client sends a token (usually a JSON Web Token or JWT) in the `Authorization` header with the "Bearer" scheme. The server then validates this token to grant or deny access.
21
 
22
+ FastMCP supports Bearer Token authentication for its HTTP-based transports (`http` and `sse`), allowing you to protect your server from unauthorized access.
23
 
24
  ## Authentication Strategy
25
 
docs/servers/proxy.mdx CHANGED
@@ -118,7 +118,7 @@ config = {
118
  "mcpServers": {
119
  "default": { # For single server configs, 'default' is commonly used
120
  "url": "https://example.com/mcp",
121
- "transport": "streamable-http"
122
  }
123
  }
124
  }
@@ -145,11 +145,11 @@ config = {
145
  "mcpServers": {
146
  "weather": {
147
  "url": "https://weather-api.example.com/mcp",
148
- "transport": "streamable-http"
149
  },
150
  "calendar": {
151
  "url": "https://calendar-api.example.com/mcp",
152
- "transport": "streamable-http"
153
  }
154
  }
155
  }
 
118
  "mcpServers": {
119
  "default": { # For single server configs, 'default' is commonly used
120
  "url": "https://example.com/mcp",
121
+ "transport": "http"
122
  }
123
  }
124
  }
 
145
  "mcpServers": {
146
  "weather": {
147
  "url": "https://weather-api.example.com/mcp",
148
+ "transport": "http"
149
  },
150
  "calendar": {
151
  "url": "https://calendar-api.example.com/mcp",
152
+ "transport": "http"
153
  }
154
  }
155
  }
docs/servers/server.mdx CHANGED
@@ -158,8 +158,8 @@ if __name__ == "__main__":
158
  # This runs the server, defaulting to STDIO transport
159
  mcp.run()
160
 
161
- # To use a different transport, e.g., HTTP:
162
- # mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
163
  ```
164
 
165
  FastMCP supports several transport options:
@@ -260,7 +260,7 @@ Transport settings are provided when running the server and control network beha
260
  ```python
261
  # Configure transport when running
262
  mcp.run(
263
- transport="streamable-http",
264
  host="0.0.0.0", # Bind to all interfaces
265
  port=9000, # Custom port
266
  log_level="DEBUG", # Override global log level
@@ -268,7 +268,7 @@ mcp.run(
268
 
269
  # Or for async usage
270
  await mcp.run_async(
271
- transport="streamable-http",
272
  host="127.0.0.1",
273
  port=8080,
274
  )
 
158
  # This runs the server, defaulting to STDIO transport
159
  mcp.run()
160
 
161
+ # To use a different transport, e.g., Streamable HTTP:
162
+ # mcp.run(transport="http", host="127.0.0.1", port=9000)
163
  ```
164
 
165
  FastMCP supports several transport options:
 
260
  ```python
261
  # Configure transport when running
262
  mcp.run(
263
+ transport="http",
264
  host="0.0.0.0", # Bind to all interfaces
265
  port=9000, # Custom port
266
  log_level="DEBUG", # Override global log level
 
268
 
269
  # Or for async usage
270
  await mcp.run_async(
271
+ transport="http",
272
  host="127.0.0.1",
273
  port=8080,
274
  )
docs/tutorials/rest-api.mdx CHANGED
@@ -82,7 +82,7 @@ mcp = FastMCP.from_openapi(
82
  )
83
 
84
  if __name__ == "__main__":
85
- mcp.run(transport="streamable-http", port=8000)
86
  ```
87
 
88
  And that's it! With just a few lines of code, you've created an MCP server that exposes the entire JSONPlaceholder API as a collection of tools.
@@ -195,7 +195,7 @@ mcp = FastMCP.from_openapi(
195
  )
196
 
197
  if __name__ == "__main__":
198
- mcp.run(transport="streamable-http", port=8000)
199
  ```
200
  With this configuration:
201
  - `GET /users/{id}` becomes a `ResourceTemplate`.
 
82
  )
83
 
84
  if __name__ == "__main__":
85
+ mcp.run(transport="http", port=8000)
86
  ```
87
 
88
  And that's it! With just a few lines of code, you've created an MCP server that exposes the entire JSONPlaceholder API as a collection of tools.
 
195
  )
196
 
197
  if __name__ == "__main__":
198
+ mcp.run(transport="http", port=8000)
199
  ```
200
  With this configuration:
201
  - `GET /users/{id}` becomes a `ResourceTemplate`.
docs/updates.mdx CHANGED
@@ -107,7 +107,7 @@ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.M_hv6gEB.png&w=1000&h=5
107
  cta="Read more"
108
  >
109
 
110
- FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="streamable-http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever.
111
 
112
  </Card>
113
  </Update>
 
107
  cta="Read more"
108
  >
109
 
110
+ FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever.
111
 
112
  </Card>
113
  </Update>
src/fastmcp/cli/cli.py CHANGED
@@ -235,7 +235,7 @@ def run(
235
  typer.Option(
236
  "--transport",
237
  "-t",
238
- help="Transport protocol to use (stdio, streamable-http, or sse)",
239
  ),
240
  ] = None,
241
  host: Annotated[
 
235
  typer.Option(
236
  "--transport",
237
  "-t",
238
+ help="Transport protocol to use (stdio, http, or sse)",
239
  ),
240
  ] = None,
241
  host: Annotated[
src/fastmcp/cli/run.py CHANGED
@@ -4,14 +4,12 @@ import importlib.util
4
  import re
5
  import sys
6
  from pathlib import Path
7
- from typing import Any, Literal
8
 
9
  from fastmcp.utilities.logging import get_logger
10
 
11
  logger = get_logger("cli.run")
12
 
13
- TransportType = Literal["stdio", "streamable-http", "sse"]
14
-
15
 
16
  def is_url(path: str) -> bool:
17
  """Check if a string is a URL."""
 
4
  import re
5
  import sys
6
  from pathlib import Path
7
+ from typing import Any
8
 
9
  from fastmcp.utilities.logging import get_logger
10
 
11
  logger = get_logger("cli.run")
12
 
 
 
13
 
14
  def is_url(path: str) -> bool:
15
  """Check if a string is a URL."""
src/fastmcp/client/transports.py CHANGED
@@ -736,11 +736,11 @@ class MCPConfigTransport(ClientTransport):
736
  "mcpServers": {
737
  "weather": {
738
  "url": "https://weather-api.example.com/mcp",
739
- "transport": "streamable-http"
740
  },
741
  "calendar": {
742
  "url": "https://calendar-api.example.com/mcp",
743
- "transport": "streamable-http"
744
  }
745
  }
746
  }
 
736
  "mcpServers": {
737
  "weather": {
738
  "url": "https://weather-api.example.com/mcp",
739
+ "transport": "http"
740
  },
741
  "calendar": {
742
  "url": "https://calendar-api.example.com/mcp",
743
+ "transport": "http"
744
  }
745
  }
746
  }
src/fastmcp/server/server.py CHANGED
@@ -74,6 +74,7 @@ if TYPE_CHECKING:
74
  logger = get_logger(__name__)
75
 
76
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
 
77
 
78
  # Compiled URI parsing regex to split a URI into protocol and path components
79
  URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
@@ -280,7 +281,7 @@ class FastMCP(Generic[LifespanResultT]):
280
 
281
  async def run_async(
282
  self,
283
- transport: Literal["stdio", "streamable-http", "sse"] | None = None,
284
  **transport_kwargs: Any,
285
  ) -> None:
286
  """Run the FastMCP server asynchronously.
@@ -290,19 +291,19 @@ class FastMCP(Generic[LifespanResultT]):
290
  """
291
  if transport is None:
292
  transport = "stdio"
293
- if transport not in {"stdio", "streamable-http", "sse"}:
294
  raise ValueError(f"Unknown transport: {transport}")
295
 
296
  if transport == "stdio":
297
  await self.run_stdio_async(**transport_kwargs)
298
- elif transport in {"streamable-http", "sse"}:
299
  await self.run_http_async(transport=transport, **transport_kwargs)
300
  else:
301
  raise ValueError(f"Unknown transport: {transport}")
302
 
303
  def run(
304
  self,
305
- transport: Literal["stdio", "streamable-http", "sse"] | None = None,
306
  **transport_kwargs: Any,
307
  ) -> None:
308
  """Run the FastMCP server. Note this is a synchronous function.
@@ -1253,7 +1254,7 @@ class FastMCP(Generic[LifespanResultT]):
1253
 
1254
  async def run_http_async(
1255
  self,
1256
- transport: Literal["streamable-http", "sse"] = "streamable-http",
1257
  host: str | None = None,
1258
  port: int | None = None,
1259
  log_level: str | None = None,
@@ -1384,7 +1385,7 @@ class FastMCP(Generic[LifespanResultT]):
1384
  middleware: list[ASGIMiddleware] | None = None,
1385
  json_response: bool | None = None,
1386
  stateless_http: bool | None = None,
1387
- transport: Literal["streamable-http", "sse"] = "streamable-http",
1388
  ) -> StarletteWithLifespan:
1389
  """Create a Starlette app using the specified HTTP transport.
1390
 
@@ -1397,7 +1398,7 @@ class FastMCP(Generic[LifespanResultT]):
1397
  A Starlette application configured with the specified transport
1398
  """
1399
 
1400
- if transport == "streamable-http":
1401
  return create_streamable_http_app(
1402
  server=self,
1403
  streamable_http_path=path
@@ -1444,7 +1445,7 @@ class FastMCP(Generic[LifespanResultT]):
1444
  stacklevel=2,
1445
  )
1446
  await self.run_http_async(
1447
- transport="streamable-http",
1448
  host=host,
1449
  port=port,
1450
  log_level=log_level,
 
74
  logger = get_logger(__name__)
75
 
76
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
77
+ Transport = Literal["stdio", "http", "sse", "streamable-http"]
78
 
79
  # Compiled URI parsing regex to split a URI into protocol and path components
80
  URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
 
281
 
282
  async def run_async(
283
  self,
284
+ transport: Transport | None = None,
285
  **transport_kwargs: Any,
286
  ) -> None:
287
  """Run the FastMCP server asynchronously.
 
291
  """
292
  if transport is None:
293
  transport = "stdio"
294
+ if transport not in {"stdio", "http", "sse", "streamable-http"}:
295
  raise ValueError(f"Unknown transport: {transport}")
296
 
297
  if transport == "stdio":
298
  await self.run_stdio_async(**transport_kwargs)
299
+ elif transport in {"http", "sse", "streamable-http"}:
300
  await self.run_http_async(transport=transport, **transport_kwargs)
301
  else:
302
  raise ValueError(f"Unknown transport: {transport}")
303
 
304
  def run(
305
  self,
306
+ transport: Transport | None = None,
307
  **transport_kwargs: Any,
308
  ) -> None:
309
  """Run the FastMCP server. Note this is a synchronous function.
 
1254
 
1255
  async def run_http_async(
1256
  self,
1257
+ transport: Literal["http", "streamable-http", "sse"] = "http",
1258
  host: str | None = None,
1259
  port: int | None = None,
1260
  log_level: str | None = None,
 
1385
  middleware: list[ASGIMiddleware] | None = None,
1386
  json_response: bool | None = None,
1387
  stateless_http: bool | None = None,
1388
+ transport: Literal["http", "streamable-http", "sse"] = "http",
1389
  ) -> StarletteWithLifespan:
1390
  """Create a Starlette app using the specified HTTP transport.
1391
 
 
1398
  A Starlette application configured with the specified transport
1399
  """
1400
 
1401
+ if transport in ("streamable-http", "http"):
1402
  return create_streamable_http_app(
1403
  server=self,
1404
  streamable_http_path=path
 
1445
  stacklevel=2,
1446
  )
1447
  await self.run_http_async(
1448
+ transport="http",
1449
  host=host,
1450
  port=port,
1451
  log_level=log_level,
src/fastmcp/utilities/mcp_config.py CHANGED
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
19
 
20
  def infer_transport_type_from_url(
21
  url: str | AnyUrl,
22
- ) -> Literal["streamable-http", "sse"]:
23
  """
24
  Infer the appropriate transport type from the given URL.
25
  """
@@ -34,7 +34,7 @@ def infer_transport_type_from_url(
34
  if re.search(r"/sse(/|\?|&|$)", path):
35
  return "sse"
36
  else:
37
- return "streamable-http"
38
 
39
 
40
  class StdioMCPServer(FastMCPBaseModel):
@@ -58,7 +58,7 @@ class StdioMCPServer(FastMCPBaseModel):
58
  class RemoteMCPServer(FastMCPBaseModel):
59
  url: str
60
  headers: dict[str, str] = Field(default_factory=dict)
61
- transport: Literal["streamable-http", "sse"] | None = None
62
  auth: Annotated[
63
  str | Literal["oauth"] | httpx.Auth | None,
64
  Field(
@@ -79,6 +79,7 @@ class RemoteMCPServer(FastMCPBaseModel):
79
  if transport == "sse":
80
  return SSETransport(self.url, headers=self.headers, auth=self.auth)
81
  else:
 
82
  return StreamableHttpTransport(
83
  self.url, headers=self.headers, auth=self.auth
84
  )
 
19
 
20
  def infer_transport_type_from_url(
21
  url: str | AnyUrl,
22
+ ) -> Literal["http", "sse"]:
23
  """
24
  Infer the appropriate transport type from the given URL.
25
  """
 
34
  if re.search(r"/sse(/|\?|&|$)", path):
35
  return "sse"
36
  else:
37
+ return "http"
38
 
39
 
40
  class StdioMCPServer(FastMCPBaseModel):
 
58
  class RemoteMCPServer(FastMCPBaseModel):
59
  url: str
60
  headers: dict[str, str] = Field(default_factory=dict)
61
+ transport: Literal["http", "streamable-http", "sse"] | None = None
62
  auth: Annotated[
63
  str | Literal["oauth"] | httpx.Auth | None,
64
  Field(
 
79
  if transport == "sse":
80
  return SSETransport(self.url, headers=self.headers, auth=self.auth)
81
  else:
82
+ # Both "http" and "streamable-http" map to StreamableHttpTransport
83
  return StreamableHttpTransport(
84
  self.url, headers=self.headers, auth=self.auth
85
  )
tests/auth/providers/test_bearer.py CHANGED
@@ -65,7 +65,7 @@ def mcp_server_url(rsa_key_pair: RSAKeyPair) -> Generator[str]:
65
  with run_server_in_process(
66
  run_mcp_server,
67
  public_key=rsa_key_pair.public_key,
68
- run_kwargs=dict(transport="streamable-http"),
69
  ) as url:
70
  yield f"{url}/mcp/"
71
 
@@ -696,7 +696,7 @@ class TestFastMCPBearerAuth:
696
  run_mcp_server,
697
  public_key=rsa_key_pair.public_key,
698
  auth_kwargs=dict(required_scopes=["read", "write"]),
699
- run_kwargs=dict(transport="streamable-http"),
700
  ) as url:
701
  mcp_server_url = f"{url}/mcp/"
702
  with pytest.raises(httpx.HTTPStatusError) as exc_info:
@@ -719,7 +719,7 @@ class TestFastMCPBearerAuth:
719
  run_mcp_server,
720
  public_key=rsa_key_pair.public_key,
721
  auth_kwargs=dict(required_scopes=["read", "write"]),
722
- run_kwargs=dict(transport="streamable-http"),
723
  ) as url:
724
  mcp_server_url = f"{url}/mcp/"
725
  async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
 
65
  with run_server_in_process(
66
  run_mcp_server,
67
  public_key=rsa_key_pair.public_key,
68
+ run_kwargs=dict(transport="http"),
69
  ) as url:
70
  yield f"{url}/mcp/"
71
 
 
696
  run_mcp_server,
697
  public_key=rsa_key_pair.public_key,
698
  auth_kwargs=dict(required_scopes=["read", "write"]),
699
+ run_kwargs=dict(transport="http"),
700
  ) as url:
701
  mcp_server_url = f"{url}/mcp/"
702
  with pytest.raises(httpx.HTTPStatusError) as exc_info:
 
719
  run_mcp_server,
720
  public_key=rsa_key_pair.public_key,
721
  auth_kwargs=dict(required_scopes=["read", "write"]),
722
+ run_kwargs=dict(transport="http"),
723
  ) as url:
724
  mcp_server_url = f"{url}/mcp/"
725
  async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
tests/auth/test_oauth_client.py CHANGED
@@ -43,7 +43,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
43
 
44
  @pytest.fixture(scope="module")
45
  def streamable_http_server() -> Generator[str, None, None]:
46
- with run_server_in_process(run_server, transport="streamable-http") as url:
47
  yield f"{url}/mcp/"
48
 
49
 
 
43
 
44
  @pytest.fixture(scope="module")
45
  def streamable_http_server() -> Generator[str, None, None]:
46
+ with run_server_in_process(run_server, transport="http") as url:
47
  yield f"{url}/mcp/"
48
 
49
 
tests/cli/test_cli.py CHANGED
@@ -328,6 +328,41 @@ class TestRunCommand:
328
  assert result.exit_code == 0
329
  mock_server.run.assert_called_once_with(transport="sse")
330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  def test_run_command_with_host(self, temp_python_file):
332
  """Test run command with host option."""
333
  with (
 
328
  assert result.exit_code == 0
329
  mock_server.run.assert_called_once_with(transport="sse")
330
 
331
+ def test_run_command_with_http_transports(self, temp_python_file):
332
+ """Test run command with both http and streamable-http transport options."""
333
+ # Test "http" transport
334
+ with (
335
+ patch("fastmcp.cli.run.parse_file_path") as mock_parse,
336
+ patch("fastmcp.cli.run.import_server") as mock_import,
337
+ ):
338
+ mock_parse.return_value = (temp_python_file, None)
339
+ mock_server = MagicMock()
340
+ mock_server.name = "test_server"
341
+ mock_import.return_value = mock_server
342
+
343
+ result = runner.invoke(
344
+ cli.app, ["run", str(temp_python_file), "--transport", "http"]
345
+ )
346
+ assert result.exit_code == 0
347
+ mock_server.run.assert_called_once_with(transport="http")
348
+
349
+ # Test "streamable-http" transport (alias for http)
350
+ with (
351
+ patch("fastmcp.cli.run.parse_file_path") as mock_parse,
352
+ patch("fastmcp.cli.run.import_server") as mock_import,
353
+ ):
354
+ mock_parse.return_value = (temp_python_file, None)
355
+ mock_server = MagicMock()
356
+ mock_server.name = "test_server"
357
+ mock_import.return_value = mock_server
358
+
359
+ result = runner.invoke(
360
+ cli.app,
361
+ ["run", str(temp_python_file), "--transport", "streamable-http"],
362
+ )
363
+ assert result.exit_code == 0
364
+ mock_server.run.assert_called_once_with(transport="streamable-http")
365
+
366
  def test_run_command_with_host(self, temp_python_file):
367
  """Test run command with host option."""
368
  with (
tests/client/test_openapi.py CHANGED
@@ -56,7 +56,7 @@ def run_proxy_server(host: str, port: int, shttp_url: str, **kwargs) -> None:
56
  class TestClientHeaders:
57
  @pytest.fixture(scope="class")
58
  def shttp_server(self) -> Generator[str, None, None]:
59
- with run_server_in_process(run_server, transport="streamable-http") as url:
60
  yield f"{url}/mcp/"
61
 
62
  @pytest.fixture(scope="class")
@@ -69,7 +69,7 @@ class TestClientHeaders:
69
  with run_server_in_process(
70
  run_proxy_server,
71
  shttp_url=shttp_server,
72
- transport="streamable-http",
73
  ) as url:
74
  yield f"{url}/mcp/"
75
 
 
56
  class TestClientHeaders:
57
  @pytest.fixture(scope="class")
58
  def shttp_server(self) -> Generator[str, None, None]:
59
+ with run_server_in_process(run_server, transport="http") as url:
60
  yield f"{url}/mcp/"
61
 
62
  @pytest.fixture(scope="class")
 
69
  with run_server_in_process(
70
  run_proxy_server,
71
  shttp_url=shttp_server,
72
+ transport="http",
73
  ) as url:
74
  yield f"{url}/mcp/"
75
 
tests/client/test_streamable_http.py CHANGED
@@ -103,13 +103,24 @@ async def streamable_http_server(
103
  stateless_http: bool = False,
104
  ) -> AsyncGenerator[str, None]:
105
  with run_server_in_process(
106
- run_server, stateless_http=stateless_http, transport="streamable-http"
107
  ) as url:
108
  async with Client(transport=StreamableHttpTransport(f"{url}/mcp/")) as client:
109
  assert await client.ping()
110
  yield f"{url}/mcp/"
111
 
112
 
 
 
 
 
 
 
 
 
 
 
 
113
  async def test_ping(streamable_http_server: str):
114
  """Test pinging the server."""
115
  async with Client(
@@ -119,6 +130,19 @@ async def test_ping(streamable_http_server: str):
119
  assert result is True
120
 
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  async def test_http_headers(streamable_http_server: str):
123
  """Test getting HTTP headers from the server."""
124
  async with Client(
 
103
  stateless_http: bool = False,
104
  ) -> AsyncGenerator[str, None]:
105
  with run_server_in_process(
106
+ run_server, stateless_http=stateless_http, transport="http"
107
  ) as url:
108
  async with Client(transport=StreamableHttpTransport(f"{url}/mcp/")) as client:
109
  assert await client.ping()
110
  yield f"{url}/mcp/"
111
 
112
 
113
+ @pytest.fixture()
114
+ async def streamable_http_server_with_streamable_http_alias() -> AsyncGenerator[
115
+ str, None
116
+ ]:
117
+ """Test that the "streamable-http" transport alias works."""
118
+ with run_server_in_process(run_server, transport="streamable-http") as url:
119
+ async with Client(transport=StreamableHttpTransport(f"{url}/mcp/")) as client:
120
+ assert await client.ping()
121
+ yield f"{url}/mcp/"
122
+
123
+
124
  async def test_ping(streamable_http_server: str):
125
  """Test pinging the server."""
126
  async with Client(
 
130
  assert result is True
131
 
132
 
133
+ async def test_ping_with_streamable_http_alias(
134
+ streamable_http_server_with_streamable_http_alias: str,
135
+ ):
136
+ """Test pinging the server."""
137
+ async with Client(
138
+ transport=StreamableHttpTransport(
139
+ streamable_http_server_with_streamable_http_alias
140
+ )
141
+ ) as client:
142
+ result = await client.ping()
143
+ assert result is True
144
+
145
+
146
  async def test_http_headers(streamable_http_server: str):
147
  """Test getting HTTP headers from the server."""
148
  async with Client(
tests/deprecated/test_deprecated.py CHANGED
@@ -85,7 +85,7 @@ async def test_run_streamable_http_async_deprecation_warning():
85
  # Verify the mock was called with the right transport
86
  mock_run.assert_called_once()
87
  call_kwargs = mock_run.call_args.kwargs
88
- assert call_kwargs.get("transport") == "streamable-http"
89
 
90
 
91
  def test_http_app_with_sse_transport():
 
85
  # Verify the mock was called with the right transport
86
  mock_run.assert_called_once()
87
  call_kwargs = mock_run.call_args.kwargs
88
+ assert call_kwargs.get("transport") == "http"
89
 
90
 
91
  def test_http_app_with_sse_transport():
tests/server/http/test_http_dependencies.py CHANGED
@@ -44,7 +44,7 @@ def run_server(host: str, port: int, **kwargs) -> None:
44
 
45
  @pytest.fixture(autouse=True, scope="module")
46
  def shttp_server() -> Generator[str, None, None]:
47
- with run_server_in_process(run_server, transport="streamable-http") as url:
48
  yield f"{url}/mcp/"
49
 
50
 
 
44
 
45
  @pytest.fixture(autouse=True, scope="module")
46
  def shttp_server() -> Generator[str, None, None]:
47
+ with run_server_in_process(run_server, transport="http") as url:
48
  yield f"{url}/mcp/"
49
 
50
 
tests/server/http/test_http_middleware.py CHANGED
@@ -96,7 +96,7 @@ async def test_streamable_http_app_with_custom_middleware():
96
  server._additional_http_routes = routes
97
 
98
  # Create the app with custom middleware
99
- app = server.http_app(transport="streamable-http", middleware=custom_middleware)
100
 
101
  # Create a test client
102
  transport = ASGITransport(app=app)
 
96
  server._additional_http_routes = routes
97
 
98
  # Create the app with custom middleware
99
+ app = server.http_app(transport="http", middleware=custom_middleware)
100
 
101
  # Create a test client
102
  transport = ASGITransport(app=app)