Add fastmcp-slim for client-only installs (#4122)

* Add fastmcp-client workspace package

* Fix client package static checks

* Document client-only package

* Harden fastmcp-client package split

* Preserve forwarded headers in full package

* Switch to fastmcp-slim package

* Fix fastmcp-slim release edges

* Match pydantic-style slim layout

* Polish fastmcp-slim packaging
This commit is contained in:
Jeremiah Lowin 2026-05-11 17:13:21 -04:00 committed by GitHub
commit bb4894d215
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
298 changed files with 1259 additions and 438 deletions

View file

@ -125,7 +125,7 @@ jobs:
<evidence_standards> <evidence_standards>
Every claim in your response must be grounded in evidence you can cite: Every claim in your response must be grounded in evidence you can cite:
- **Code references**: Always include file path and line number (e.g., `src/fastmcp/client/client.py:142`). Never say "the client code does X" without pointing to where. - **Code references**: Always include file path and line number (e.g., `fastmcp_slim/fastmcp/client/client.py:142`). Never say "the client code does X" without pointing to where.
- **Bug confirmation**: If you say a bug is real, show the specific code path that produces it. If you ran a test, include the command and output. - **Bug confirmation**: If you say a bug is real, show the specific code path that produces it. If you ran a test, include the command and output.
- **Related items**: When citing a related issue or PR, explain specifically why it's related — not just that it exists. - **Related items**: When citing a related issue or PR, explain specifically why it's related — not just that it exists.
- **Confidence**: If you're uncertain about a finding, say so. "I don't know" or "I couldn't confirm this" is better than a speculative diagnosis. Only report findings you would confidently defend. - **Confidence**: If you're uncertain about a finding, say so. "I don't know" or "I couldn't confirm this" is better than a speculative diagnosis. Only report findings you would confidently defend.

View file

@ -116,7 +116,7 @@ jobs:
- auth: Authentication is the main concern (Bearer, JWT, OAuth, WorkOS) - auth: Authentication is the main concern (Bearer, JWT, OAuth, WorkOS)
- openapi: OpenAPI integration/parsing is the primary topic - openapi: OpenAPI integration/parsing is the primary topic
- http: HTTP transport or networking is the main issue - http: HTTP transport or networking is the main issue
- contrib: Specifically about community contributions in src/contrib/ - contrib: Specifically about community contributions in fastmcp_slim/fastmcp/contrib/
- tests: Issues primarily about testing infrastructure, CI/CD workflows, or test coverage - tests: Issues primarily about testing infrastructure, CI/CD workflows, or test coverage
- security: Apply ONLY when the issue/PR addresses an exploitable vulnerability or hardens against one. Examples: SSRF, LFI, path traversal, injection, auth bypass allowing unauthorized access, scope escalation, open redirects. Do NOT apply for ordinary auth bugs (wrong scopes returned, token refresh logic, OAuth flow correctness) unless an attacker could exploit the bug to bypass access controls or escalate privileges. The key question: "Could a malicious actor exploit this?" If the answer is just "it breaks for legitimate users," that's a bug, not a security issue. - security: Apply ONLY when the issue/PR addresses an exploitable vulnerability or hardens against one. Examples: SSRF, LFI, path traversal, injection, auth bypass allowing unauthorized access, scope escalation, open redirects. Do NOT apply for ordinary auth bugs (wrong scopes returned, token refresh logic, OAuth flow correctness) unless an attacker could exploit the bug to bypass access controls or escalate privileges. The key question: "Could a malicious actor exploit this?" If the answer is just "it breaks for legitimate users," that's a bug, not a security issue.

View file

@ -0,0 +1,30 @@
name: Publish fastmcp-slim to PyPI
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
pypi-publish:
name: Upload fastmcp-slim to PyPI
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Build fastmcp-slim
run: uv build --package fastmcp-slim
- name: Publish fastmcp-slim to PyPI
run: uv publish -v dist/fastmcp_slim-*.tar.gz dist/fastmcp_slim-*.whl

87
.github/workflows/publish-fastmcp.yml vendored Normal file
View file

@ -0,0 +1,87 @@
name: Publish fastmcp to PyPI
on:
workflow_run:
workflows: ["Publish fastmcp-slim to PyPI"]
types: [completed]
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
pypi-publish:
name: Upload fastmcp to PyPI
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'release')
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Build fastmcp
run: uv build --package fastmcp
- name: Verify matching fastmcp-slim is published
run: |
SLIM_VERSION=$(python - <<'PY'
import email.parser
import re
import zipfile
from pathlib import Path
wheel = next(Path("dist").glob("fastmcp-*.whl"))
metadata_name = next(
name for name in zipfile.ZipFile(wheel).namelist()
if name.endswith(".dist-info/METADATA")
)
metadata = email.parser.Parser().parsestr(
zipfile.ZipFile(wheel).read(metadata_name).decode()
)
for value in metadata.get_all("Requires-Dist", []):
requirement, _, marker = value.partition(";")
if marker.strip():
continue
match = re.fullmatch(
r"fastmcp-slim(?:\[[^\]]+\])?==([^;\s]+)",
requirement.strip(),
)
if match:
print(match.group(1))
break
else:
raise RuntimeError("Could not find the base fastmcp-slim dependency")
PY
)
for attempt in {1..12}; do
if python - "$SLIM_VERSION" <<'PY'
import json
import sys
import urllib.request
version = sys.argv[1]
url = f"https://pypi.org/pypi/fastmcp-slim/{version}/json"
with urllib.request.urlopen(url, timeout=30) as response:
json.load(response)
PY
then
exit 0
fi
echo "fastmcp-slim ${SLIM_VERSION} is not available on PyPI yet; retrying (${attempt}/12)."
sleep 10
done
echo "fastmcp-slim ${SLIM_VERSION} is not available on PyPI; refusing to publish fastmcp." >&2
exit 1
- name: Publish fastmcp to PyPI
run: uv publish -v dist/fastmcp-*.tar.gz dist/fastmcp-*.whl

View file

@ -1,26 +0,0 @@
name: Publish FastMCP to PyPI
on:
release:
types: [published]
workflow_dispatch:
jobs:
pypi-publish:
name: Upload to PyPI
runs-on: ubuntu-latest
permissions:
id-token: write # For PyPI's trusted publishing
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Install uv"
uses: astral-sh/setup-uv@v7
- name: Build
run: uv build
- name: Publish to PyPi
run: uv publish -v dist/*

View file

@ -4,21 +4,21 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/fastmcp/utilities/json_schema_type.py" - "fastmcp_slim/fastmcp/utilities/json_schema_type.py"
- "src/fastmcp/utilities/json_schema.py" - "fastmcp_slim/fastmcp/utilities/json_schema.py"
- "src/fastmcp/utilities/openapi/**" - "fastmcp_slim/fastmcp/utilities/openapi/**"
- "src/fastmcp/server/providers/openapi/**" - "fastmcp_slim/fastmcp/server/providers/openapi/**"
- "src/fastmcp/client/mixins/tools.py" - "fastmcp_slim/fastmcp/client/mixins/tools.py"
- "tests/utilities/json_schema_type/test_real_world_schemas.py" - "tests/utilities/json_schema_type/test_real_world_schemas.py"
- ".github/workflows/run-schema-crash-test.yml" - ".github/workflows/run-schema-crash-test.yml"
pull_request: pull_request:
paths: paths:
- "src/fastmcp/utilities/json_schema_type.py" - "fastmcp_slim/fastmcp/utilities/json_schema_type.py"
- "src/fastmcp/utilities/json_schema.py" - "fastmcp_slim/fastmcp/utilities/json_schema.py"
- "src/fastmcp/utilities/openapi/**" - "fastmcp_slim/fastmcp/utilities/openapi/**"
- "src/fastmcp/server/providers/openapi/**" - "fastmcp_slim/fastmcp/server/providers/openapi/**"
- "src/fastmcp/client/mixins/tools.py" - "fastmcp_slim/fastmcp/client/mixins/tools.py"
- "tests/utilities/json_schema_type/test_real_world_schemas.py" - "tests/utilities/json_schema_type/test_real_world_schemas.py"
- ".github/workflows/run-schema-crash-test.yml" - ".github/workflows/run-schema-crash-test.yml"

View file

@ -7,10 +7,10 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/**" - "fastmcp_slim/**"
- "tests/**" - "tests/**"
- "uv.lock"
- "pyproject.toml" - "pyproject.toml"
- "uv.lock"
- ".github/workflows/**" - ".github/workflows/**"
# run on all pull requests because these checks are required and will block merges otherwise # run on all pull requests because these checks are required and will block merges otherwise

View file

@ -7,10 +7,10 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/**" - "fastmcp_slim/**"
- "tests/**" - "tests/**"
- "uv.lock"
- "pyproject.toml" - "pyproject.toml"
- "uv.lock"
- ".github/workflows/**" - ".github/workflows/**"
# run on all pull requests because these checks are required and will block merges otherwise # run on all pull requests because these checks are required and will block merges otherwise
@ -117,3 +117,102 @@ jobs:
FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }} FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }}
FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }} FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }}
FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET }} FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_SECRET }}
package_install_smoke:
name: "Package install smoke"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Setup uv
uses: ./.github/actions/setup-uv
with:
resolution: locked
- name: Build package wheels
run: uv build --all-packages --wheel --out-dir /tmp/fastmcp-dist
- name: Install bare slim wheel
run: |
uv venv /tmp/fastmcp-slim-bare-smoke
SLIM_WHEEL=$(ls /tmp/fastmcp-dist/fastmcp_slim-*.whl)
uv pip install --python /tmp/fastmcp-slim-bare-smoke/bin/python "$SLIM_WHEEL"
/tmp/fastmcp-slim-bare-smoke/bin/python - <<'PY'
from importlib.metadata import entry_points
import fastmcp
import fastmcp.settings
assert not any(ep.name == "fastmcp" for ep in entry_points(group="console_scripts"))
try:
fastmcp.FastMCP
except ImportError as exc:
assert "fastmcp-slim[server]" in str(exc)
else:
raise AssertionError("bare fastmcp-slim unexpectedly imported FastMCP")
PY
- name: Install client slim wheel
run: |
uv venv /tmp/fastmcp-slim-client-smoke
SLIM_WHEEL=$(ls /tmp/fastmcp-dist/fastmcp_slim-*.whl)
uv pip install --python /tmp/fastmcp-slim-client-smoke/bin/python "${SLIM_WHEEL}[client]"
/tmp/fastmcp-slim-client-smoke/bin/python - <<'PY'
from importlib.metadata import entry_points
from fastmcp import Client
from fastmcp.client.transports import StdioTransport, StreamableHttpTransport
from fastmcp.mcp_config import MCPConfig
assert not any(ep.name == "fastmcp" for ep in entry_points(group="console_scripts"))
assert Client("https://example.com/mcp")
assert StreamableHttpTransport("https://example.com/mcp")
assert StdioTransport(command="uvx", args=["demo"])
assert MCPConfig.from_dict({"mcpServers": {"demo": {"url": "https://example.com/mcp"}}})
try:
from fastmcp import FastMCP
except ImportError as exc:
assert "fastmcp-slim[server]" in str(exc)
else:
raise AssertionError(f"client-only slim unexpectedly imported {FastMCP!r}")
PY
- name: Install server slim wheel
run: |
uv venv /tmp/fastmcp-slim-server-smoke
SLIM_WHEEL=$(ls /tmp/fastmcp-dist/fastmcp_slim-*.whl)
uv pip install --python /tmp/fastmcp-slim-server-smoke/bin/python "${SLIM_WHEEL}[server]"
/tmp/fastmcp-slim-server-smoke/bin/python - <<'PY'
from fastmcp import FastMCP
mcp = FastMCP("smoke")
assert mcp.name == "smoke"
PY
- name: Install full package from matching local wheels
run: |
uv venv /tmp/fastmcp-full-smoke
FULL_WHEEL=$(ls /tmp/fastmcp-dist/fastmcp-*.whl)
uv pip install --python /tmp/fastmcp-full-smoke/bin/python --find-links /tmp/fastmcp-dist "$FULL_WHEEL"
/tmp/fastmcp-full-smoke/bin/python - <<'PY'
from importlib.metadata import entry_points
from fastmcp import Client, FastMCP
from fastmcp.client.client import CallToolResult
from fastmcp.exceptions import ToolError
assert any(
ep.name == "fastmcp" and ep.value == "fastmcp.cli:app"
for ep in entry_points(group="console_scripts")
)
assert Client("https://example.com/mcp")
assert FastMCP("smoke").name == "smoke"
assert CallToolResult is not None
assert ToolError is not None
PY

View file

@ -7,10 +7,10 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/**" - "fastmcp_slim/**"
- "tests/**" - "tests/**"
- "uv.lock"
- "pyproject.toml" - "pyproject.toml"
- "uv.lock"
- ".github/workflows/**" - ".github/workflows/**"
schedule: schedule:

View file

@ -7,8 +7,8 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/fastmcp/utilities/mcp_server_config/**" - "fastmcp_slim/fastmcp/utilities/mcp_server_config/**"
- "!src/fastmcp/utilities/mcp_server_config/v1/schema.json" - "!fastmcp_slim/fastmcp/utilities/mcp_server_config/v1/schema.json"
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -47,7 +47,7 @@ jobs:
from fastmcp.utilities.mcp_server_config import generate_schema from fastmcp.utilities.mcp_server_config import generate_schema
generate_schema('docs/public/schemas/fastmcp.json/latest.json') generate_schema('docs/public/schemas/fastmcp.json/latest.json')
generate_schema('docs/public/schemas/fastmcp.json/v1.json') generate_schema('docs/public/schemas/fastmcp.json/v1.json')
generate_schema('src/fastmcp/utilities/mcp_server_config/v1/schema.json') generate_schema('fastmcp_slim/fastmcp/utilities/mcp_server_config/v1/schema.json')
" "
- name: Create Pull Request - name: Create Pull Request
@ -59,7 +59,7 @@ jobs:
body: | body: |
This PR updates the fastmcp.json schema files to match the current source code. This PR updates the fastmcp.json schema files to match the current source code.
The schema is automatically generated from `src/fastmcp/utilities/mcp_server_config/` to ensure consistency. The schema is automatically generated from `fastmcp_slim/fastmcp/utilities/mcp_server_config/` to ensure consistency.
**Note:** This PR is fully automated and will update itself with any subsequent changes to the schema, or close automatically if the schema becomes up-to-date through other means. **Note:** This PR is fully automated and will update itself with any subsequent changes to the schema, or close automatically if the schema becomes up-to-date through other means.

View file

@ -7,7 +7,7 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "src/**" - "fastmcp_slim/**"
- "pyproject.toml" - "pyproject.toml"
workflow_dispatch: workflow_dispatch:

View file

@ -29,7 +29,7 @@ repos:
entry: uv run --isolated ty check entry: uv run --isolated ty check
language: system language: system
types: [python] types: [python]
files: ^src/|^tests/ files: ^fastmcp_slim/|^tests/
pass_filenames: false pass_filenames: false
require_serial: true require_serial: true

View file

@ -27,7 +27,7 @@ uv run prek run --all-files # Ruff + Prettier + ty
| Path | Purpose | | Path | Purpose |
| ----------------- | -------------------------------------- | | ----------------- | -------------------------------------- |
| `src/fastmcp/` | Library source code | | `fastmcp_slim/fastmcp/` | Library source code |
| `├─server/` | Server implementation | | `├─server/` | Server implementation |
| `│ ├─auth/` | Authentication providers | | `│ ├─auth/` | Authentication providers |
| `│ └─middleware/` | Error handling, logging, rate limiting | | `│ └─middleware/` | Error handling, logging, rate limiting |
@ -50,7 +50,7 @@ When modifying MCP functionality, changes typically need to be applied across al
- **Resource Templates** (`src/resources/`) - **Resource Templates** (`src/resources/`)
- **Prompts** (`src/prompts/`) - **Prompts** (`src/prompts/`)
**Before writing cross-component logic (dedupe, grouping, lookups, identity checks), read `FastMCPComponent` in `src/fastmcp/utilities/components.py`.** The base class defines the shared surface — `name`, `version`, `tags`, `meta`, and critically the `key` property which is the canonical MCP identity (encodes type, identifier, and version). Prefer `item.key` over ad-hoc `name or uri or uri_template` fallbacks; overrides in `Resource` and `ResourceTemplate` already handle URI-based identity, and `.key` includes the version suffix so variants of the same component don't falsely collide. **Before writing cross-component logic (dedupe, grouping, lookups, identity checks), read `FastMCPComponent` in `fastmcp_slim/fastmcp/utilities/components.py`.** The base class defines the shared surface — `name`, `version`, `tags`, `meta`, and critically the `key` property which is the canonical MCP identity (encodes type, identifier, and version). Prefer `item.key` over ad-hoc `name or uri or uri_template` fallbacks; overrides in `Resource` and `ResourceTemplate` already handle URI-based identity, and `.key` includes the version suffix so variants of the same component don't falsely collide.
## Development Rules ## Development Rules
@ -147,9 +147,9 @@ gh api -X POST repos/PrefectHQ/fastmcp/releases/generate-notes \
- Uses Mintlify framework - Uses Mintlify framework
- Files must be in docs.json to be included - Files must be in docs.json to be included
- Do not manually modify `docs/python-sdk/**` — these files are auto-generated from source code by a bot and maintained via a long-lived PR. Do not include changes to these files in contributor PRs. - Do not manually modify `docs/python-sdk/**` — these files are auto-generated from source code by a bot and maintained via a long-lived PR. Do not include changes to these files in contributor PRs.
- Do not manually modify `docs/public/schemas/**` or `src/fastmcp/utilities/mcp_server_config/v1/schema.json` — these are auto-generated and maintained via a long-lived PR. - Do not manually modify `docs/public/schemas/**` or `fastmcp_slim/fastmcp/utilities/mcp_server_config/v1/schema.json` — these are auto-generated and maintained via a long-lived PR.
- **Core Principle:** A feature doesn't exist unless it is documented! - **Core Principle:** A feature doesn't exist unless it is documented!
- When adding or modifying settings in `src/fastmcp/settings.py`, update `docs/more/settings.mdx` to match. - When adding or modifying settings in `fastmcp_slim/fastmcp/settings.py`, update `docs/more/settings.mdx` to match.
### Documentation Guidelines ### Documentation Guidelines

View file

@ -0,0 +1,89 @@
---
title: Client-Only Package
description: Use FastMCP's client without installing the full server framework.
icon: package
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.3.0" />
FastMCP's full `fastmcp` package includes everything needed to build and run MCP servers, apps, proxies, and clients. If you are only embedding an MCP client in another framework, building your own LLM host, or testing MCP servers, you can install the smaller client-only package instead.
```bash
pip install "fastmcp-slim[client]"
```
The client-only package uses the `fastmcp` import namespace:
```python
from fastmcp import Client
client = Client("https://example.com/mcp")
```
Use `fastmcp-slim[client]` when your code connects to MCP servers but does not define or run FastMCP servers itself. For example, framework authors can depend on `fastmcp-slim[client]` to provide MCP connectivity without requiring users to install the full FastMCP server stack.
## Supported Usage
Client-only installs support remote and subprocess transports:
```python
from fastmcp import Client
# Remote MCP server
http_client = Client("https://example.com/mcp")
# Local MCP server over stdio
stdio_client = Client("my_server.py")
```
Single-server MCP configuration works as well:
```python
from fastmcp import Client
config = {
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp"
}
}
}
client = Client(config)
```
Optional sampling handlers are available through the same extras as the full package:
```bash
pip install "fastmcp-slim[client,openai]"
pip install "fastmcp-slim[client,anthropic]"
pip install "fastmcp-slim[client,gemini]"
```
## When to Use the Full Package
Install `fastmcp` when you need server-side FastMCP features:
```bash
pip install fastmcp
```
The full package remains the default for most users and continues to support the existing import style:
```python
from fastmcp import Client, FastMCP
server = FastMCP("Example")
client = Client(server)
```
Use the full package for:
- defining or running FastMCP servers
- in-memory clients connected directly to `FastMCP` server objects
- multi-server MCP configurations
- FastMCP apps, proxies, server auth, middleware, and other server-side features
The `fastmcp-slim` package is intentionally narrower: it is for client-only consumers who want FastMCP's MCP client behavior without depending on the full framework.

View file

@ -186,5 +186,5 @@ client = Client(
Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results. Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results.
<Tip> <Tip>
To implement a custom sampling handler, see the [handler source code](https://github.com/PrefectHQ/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference. To implement a custom sampling handler, see the [handler source code](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/client/sampling/handlers) as a reference.
</Tip> </Tip>

View file

@ -33,7 +33,7 @@ Tests should complete in under 1 second unless marked as integration tests. This
### Test Organization ### Test Organization
Our test organization mirrors the `src/` directory structure, creating a predictable mapping between code and tests. When you're working on `src/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files. Our test organization mirrors the source package structure, creating a predictable mapping between code and tests. When you're working on `fastmcp_slim/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files.
### Test Markers ### Test Markers
@ -393,4 +393,4 @@ just docs
mintlify dev mintlify dev
``` ```
The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it. The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it.

View file

@ -444,7 +444,7 @@ async def dashboard(ctx: Context) -> dict:
**Future phases** will add a component DSL for building UIs declaratively, an in-repo renderer, and a `FastMCPApp` class. **Future phases** will add a component DSL for building UIs declaratively, an in-repo renderer, and a `FastMCPApp` class.
Implementation: `src/fastmcp/server/apps.py` (models and constants), with integration points in `server.py` (decorator parameters), `low_level.py` (extension advertisement), and `context.py` (`client_supports_extension` method). Implementation: `fastmcp_slim/fastmcp/server/apps.py` (models and constants), with integration points in `server.py` (decorator parameters), `low_level.py` (extension advertisement), and `context.py` (`client_supports_extension` method).
--- ---
@ -454,7 +454,7 @@ Implementation: `src/fastmcp/server/apps.py` (models and constants), with integr
v3.0 introduces a provider-based component system that replaces v2's static-only registration ([#2622](https://github.com/PrefectHQ/fastmcp/pull/2622)). Providers dynamically source tools, resources, templates, and prompts at runtime. v3.0 introduces a provider-based component system that replaces v2's static-only registration ([#2622](https://github.com/PrefectHQ/fastmcp/pull/2622)). Providers dynamically source tools, resources, templates, and prompts at runtime.
**Core abstraction** (`src/fastmcp/server/providers/base.py`): **Core abstraction** (`fastmcp_slim/fastmcp/server/providers/base.py`):
```python ```python
class Provider: class Provider:
async def list_tools(self) -> Sequence[Tool]: ... async def list_tools(self) -> Sequence[Tool]: ...
@ -474,7 +474,7 @@ Providers support:
### LocalProvider ### LocalProvider
`LocalProvider` (`src/fastmcp/server/providers/local_provider.py`) manages components registered via decorators. Can be used standalone and attached to multiple servers: `LocalProvider` (`fastmcp_slim/fastmcp/server/providers/local_provider.py`) manages components registered via decorators. Can be used standalone and attached to multiple servers:
```python ```python
from fastmcp.server.providers import LocalProvider from fastmcp.server.providers import LocalProvider
@ -492,7 +492,7 @@ server2 = FastMCP("Server2", providers=[provider])
### ProxyProvider ### ProxyProvider
`ProxyProvider` (`src/fastmcp/server/providers/proxy.py`) proxies components from remote MCP servers via a client factory. Used by `create_proxy()` and `FastMCP.mount()` for remote server integration. `ProxyProvider` (`fastmcp_slim/fastmcp/server/providers/proxy.py`) proxies components from remote MCP servers via a client factory. Used by `create_proxy()` and `FastMCP.mount()` for remote server integration.
```python ```python
from fastmcp.server import create_proxy from fastmcp.server import create_proxy
@ -503,7 +503,7 @@ server = create_proxy("http://remote-server/mcp")
### OpenAPIProvider ### OpenAPIProvider
`OpenAPIProvider` (`src/fastmcp/server/providers/openapi/provider.py`) creates MCP components from OpenAPI specifications. Routes map HTTP operations to tools, resources, or templates based on configurable rules. `OpenAPIProvider` (`fastmcp_slim/fastmcp/server/providers/openapi/provider.py`) creates MCP components from OpenAPI specifications. Routes map HTTP operations to tools, resources, or templates based on configurable rules.
```python ```python
from fastmcp.server.providers.openapi import OpenAPIProvider from fastmcp.server.providers.openapi import OpenAPIProvider
@ -523,7 +523,7 @@ Features:
### FastMCPProvider ### FastMCPProvider
`FastMCPProvider` (`src/fastmcp/server/providers/fastmcp_provider.py`) wraps a FastMCP server to enable mounting one server onto another. Components delegate execution through the wrapped server's middleware chain. `FastMCPProvider` (`fastmcp_slim/fastmcp/server/providers/fastmcp_provider.py`) wraps a FastMCP server to enable mounting one server onto another. Components delegate execution through the wrapped server's middleware chain.
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
@ -548,7 +548,7 @@ main.add_provider(provider)
Transforms modify components (tools, resources, prompts) as they flow from providers to clients ([#2836](https://github.com/PrefectHQ/fastmcp/pull/2836)). They use a middleware pattern where each transform receives a `call_next` callable to continue the chain. Transforms modify components (tools, resources, prompts) as they flow from providers to clients ([#2836](https://github.com/PrefectHQ/fastmcp/pull/2836)). They use a middleware pattern where each transform receives a `call_next` callable to continue the chain.
**Built-in transforms** (`src/fastmcp/server/transforms/`): **Built-in transforms** (`fastmcp_slim/fastmcp/server/transforms/`):
- `Namespace` - adds prefixes to names (`tool` → `api_tool`) and path segments to URIs (`data://x` → `data://api/x`) - `Namespace` - adds prefixes to names (`tool` → `api_tool`) and path segments to URIs (`data://x` → `data://api/x`)
- `ToolTransform` - modifies tool schemas (rename, description, tags, argument transforms) - `ToolTransform` - modifies tool schemas (rename, description, tags, argument transforms)
@ -873,7 +873,7 @@ v3.0 introduces type-safe result classes that provide explicit control over comp
#### ToolResult #### ToolResult
`ToolResult` (`src/fastmcp/tools/tool.py:79`) provides structured tool responses: `ToolResult` (`fastmcp_slim/fastmcp/tools/tool.py:79`) provides structured tool responses:
```python ```python
from fastmcp.tools import ToolResult from fastmcp.tools import ToolResult
@ -894,7 +894,7 @@ Fields:
#### ResourceResult #### ResourceResult
`ResourceResult` (`src/fastmcp/resources/resource.py:117`) provides structured resource responses: `ResourceResult` (`fastmcp_slim/fastmcp/resources/resource.py:117`) provides structured resource responses:
```python ```python
from fastmcp.resources import ResourceResult, ResourceContent from fastmcp.resources import ResourceResult, ResourceContent
@ -914,7 +914,7 @@ Accepts strings, bytes, or `list[ResourceContent]` for flexible content handling
#### PromptResult #### PromptResult
`PromptResult` (`src/fastmcp/prompts/prompt.py:109`) provides structured prompt responses: `PromptResult` (`fastmcp_slim/fastmcp/prompts/prompt.py:109`) provides structured prompt responses:
```python ```python
from fastmcp.prompts import PromptResult, Message from fastmcp.prompts import PromptResult, Message
@ -936,7 +936,7 @@ def conversation() -> PromptResult:
v3.0 implements MCP SEP-1686 for background task execution via Docket integration. v3.0 implements MCP SEP-1686 for background task execution via Docket integration.
**Configuration** (`src/fastmcp/server/tasks/config.py`): **Configuration** (`fastmcp_slim/fastmcp/server/tasks/config.py`):
```python ```python
from fastmcp.server.tasks import TaskConfig from fastmcp.server.tasks import TaskConfig
@ -1012,7 +1012,7 @@ fastmcp run server.py --reload --reload-dir ./src --reload-dir ./lib
fastmcp run server.py --reload --transport http --port 8080 fastmcp run server.py --reload --transport http --port 8080
``` ```
Implementation (`src/fastmcp/cli/run.py`): Implementation (`fastmcp_slim/fastmcp/cli/run.py`):
- Uses `watchfiles` for efficient file monitoring - Uses `watchfiles` for efficient file monitoring
- Runs server as subprocess for clean restarts - Runs server as subprocess for clean restarts
- Stateless mode for seamless reconnection after restart - Stateless mode for seamless reconnection after restart

View file

@ -235,6 +235,7 @@
"group": "Clients", "group": "Clients",
"pages": [ "pages": [
"clients/client", "clients/client",
"clients/client-only-package",
"clients/transports", "clients/transports",
{ {
"collapsed": true, "collapsed": true,

View file

@ -12,7 +12,7 @@ FastMCP includes a `contrib` package that holds community-contributed modules. T
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. 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.
The available modules can be viewed in the [contrib directory](https://github.com/PrefectHQ/fastmcp/tree/main/src/fastmcp/contrib). The available modules can be viewed in the [contrib directory](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/contrib).
## Usage ## Usage
@ -32,7 +32,7 @@ from fastmcp.contrib import my_module
We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it: We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it:
1. Create a new directory in `src/fastmcp/contrib/` for your module 1. Create a new directory in `fastmcp_slim/fastmcp/contrib/` for your module
3. Add proper tests for your module in `tests/contrib/` 3. Add proper tests for your module in `tests/contrib/`
2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions 2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions
5. Submit a pull request 5. Submit a pull request

View file

@ -254,5 +254,5 @@ Install the Anthropic handler with `pip install fastmcp[anthropic]`.
Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results. Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results.
<Tip> <Tip>
To implement a custom sampling handler, see the [handler source code](https://github.com/PrefectHQ/fastmcp/tree/main/src/fastmcp/client/sampling/handlers) as a reference. To implement a custom sampling handler, see the [handler source code](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/client/sampling/handlers) as a reference.
</Tip> </Tip>

View file

@ -33,7 +33,7 @@ Tests should complete in under 1 second unless marked as integration tests. This
### Test Organization ### Test Organization
Our test organization mirrors the `src/` directory structure, creating a predictable mapping between code and tests. When you're working on `src/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files. Our test organization mirrors the source package structure, creating a predictable mapping between code and tests. When you're working on `fastmcp_slim/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files.
### Test Markers ### Test Markers
@ -393,4 +393,4 @@ just docs
mintlify dev mintlify dev
``` ```
The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it. The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it.

View file

@ -12,7 +12,7 @@ FastMCP includes a `contrib` package that holds community-contributed modules. T
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. 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.
The available modules can be viewed in the [contrib directory](https://github.com/PrefectHQ/fastmcp/tree/main/src/fastmcp/contrib). The available modules can be viewed in the [contrib directory](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/contrib).
## Usage ## Usage
@ -32,7 +32,7 @@ from fastmcp.contrib import my_module
We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it: We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it:
1. Create a new directory in `src/fastmcp/contrib/` for your module 1. Create a new directory in `fastmcp_slim/fastmcp/contrib/` for your module
3. Add proper tests for your module in `tests/contrib/` 3. Add proper tests for your module in `tests/contrib/`
2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions 2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions
5. Submit a pull request 5. Submit a pull request

120
fastmcp_slim/README.md Normal file
View file

@ -0,0 +1,120 @@
<div align="center">
<!-- omit in toc -->
<picture>
<source width="550" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/brand/f-watercolor-waves-4-dark.png">
<source width="550" media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/brand/f-watercolor-waves-4.png">
<img width="550" alt="FastMCP Logo" src="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/brand/f-watercolor-waves-2.png">
</picture>
# FastMCP 🚀
<strong>Move fast and make things.</strong>
*Made with 💙 by [Prefect](https://www.prefect.io/)*
[![Docs](https://img.shields.io/badge/docs-gofastmcp.com-blue)](https://gofastmcp.com)
[![Discord](https://img.shields.io/badge/community-discord-5865F2?logo=discord&logoColor=white)](https://discord.gg/uu8dJCgttd)
[![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
[![Tests](https://github.com/PrefectHQ/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/PrefectHQ/fastmcp/actions/workflows/run-tests.yml)
[![License](https://img.shields.io/github/license/PrefectHQ/fastmcp.svg)](https://github.com/PrefectHQ/fastmcp/blob/main/LICENSE)
<a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="prefecthq%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
---
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) connects LLMs to tools and data. FastMCP gives you everything you need to go from prototype to production:
```python
from fastmcp import FastMCP
mcp = FastMCP("Demo 🚀")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
if __name__ == "__main__":
mcp.run()
```
## Why FastMCP
Building an effective MCP application is harder than it looks. FastMCP handles all of it. Declare a tool with a Python function, and the schema, validation, and documentation are generated automatically. Connect to a server with a URL, and transport negotiation, authentication, and protocol lifecycle are managed for you. You focus on your logic, and the MCP part just works: **with FastMCP, best practices are built in.**
**That's why FastMCP is the standard framework for working with MCP.** FastMCP 1.0 was incorporated into the official MCP Python SDK in 2024. Today, the actively maintained standalone project is downloaded a million times a day, and some version of FastMCP powers 70% of MCP servers across all languages.
FastMCP has three pillars:
<table>
<tr>
<td align="center" valign="top" width="33%">
<a href="https://gofastmcp.com/servers/server">
<img src="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/images/servers-card.png" alt="Servers" />
<br /><strong>Servers</strong>
</a>
<br />Expose tools, resources, and prompts to LLMs.
</td>
<td align="center" valign="top" width="33%">
<a href="https://gofastmcp.com/apps/overview">
<img src="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/images/apps-card.png" alt="Apps" />
<br /><strong>Apps</strong>
</a>
<br />Give your tools interactive UIs rendered directly in the conversation.
</td>
<td align="center" valign="top" width="33%">
<a href="https://gofastmcp.com/clients/client">
<img src="https://raw.githubusercontent.com/PrefectHQ/fastmcp/main/docs/assets/images/clients-card.png" alt="Clients" />
<br /><strong>Clients</strong>
</a>
<br />Connect to any MCP server — local or remote, programmatic or CLI.
</td>
</tr>
</table>
**[Servers](https://gofastmcp.com/servers/server)** wrap your Python functions into MCP-compliant tools, resources, and prompts. **[Clients](https://gofastmcp.com/clients/client)** connect to any server with full protocol support. And **[Apps](https://gofastmcp.com/apps/overview)** give your tools interactive UIs rendered directly in the conversation.
Ready to build? Start with the [installation guide](https://gofastmcp.com/getting-started/installation) or jump straight to the [quickstart](https://gofastmcp.com/getting-started/quickstart).
## Run FastMCP in production with Horizon
FastMCP is the standard way to build MCP servers. **[Prefect Horizon](https://www.prefect.io/horizon?utm_source=github&utm_medium=readme&utm_campaign=readme_horizon&utm_content=readme_body)** is the enterprise MCP gateway for running them safely.
Built by the FastMCP team, Horizon packages the best practices we've learned shipping the world's most popular MCP framework.
Deploy FastMCP servers from GitHub with branch previews and instant rollback. Create a private registry of every MCP your company uses. Secure access with SSO and tool-level RBAC. Get audit logs, observability, and governance across your MCP stack. Remix approved tools into purpose-built endpoints for teams and agents.
Start with FastMCP. [Scale with Horizon →](https://www.prefect.io/horizon?utm_source=github&utm_medium=readme&utm_campaign=readme_horizon&utm_content=readme_cta)
## Installation
We recommend installing FastMCP with [uv](https://docs.astral.sh/uv/):
```bash
uv pip install fastmcp
```
For full installation instructions, including verification and upgrading, see the [**Installation Guide**](https://gofastmcp.com/getting-started/installation).
**Upgrading?** We have guides for:
- [Upgrading from FastMCP v2](https://gofastmcp.com/getting-started/upgrading/from-fastmcp-2)
- [Upgrading from the MCP Python SDK](https://gofastmcp.com/getting-started/upgrading/from-mcp-sdk)
- [Upgrading from the low-level SDK](https://gofastmcp.com/getting-started/upgrading/from-low-level-sdk)
## 📚 Documentation
FastMCP's complete documentation is available at **[gofastmcp.com](https://gofastmcp.com)**, including detailed guides, API references, and advanced patterns.
Documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily:
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
**Community:** Join our [Discord server](https://discord.gg/uu8dJCgttd) to connect with other FastMCP developers and share what you're building.
## Contributing
We welcome contributions! See the [Contributing Guide](https://gofastmcp.com/development/contributing) for setup instructions, testing requirements, and PR guidelines.

View file

@ -0,0 +1,117 @@
"""FastMCP - An ergonomic MCP interface."""
import importlib
import warnings
from importlib.metadata import PackageNotFoundError, version as _version
from typing import TYPE_CHECKING
from fastmcp.settings import Settings
from fastmcp.utilities.logging import configure_logging as _configure_logging
if TYPE_CHECKING:
from fastmcp.client import Client as Client
from fastmcp.apps.app import FastMCPApp as FastMCPApp
from fastmcp.exceptions import (
FastMCPDeprecationWarning as FastMCPDeprecationWarning,
)
from fastmcp.server.context import Context as Context
from fastmcp.server.server import FastMCP as FastMCP
settings = Settings()
if settings.log_enabled:
_configure_logging(
level=settings.log_level,
enable_rich_tracebacks=settings.enable_rich_tracebacks,
)
try:
__version__ = _version("fastmcp-slim")
except PackageNotFoundError:
__version__ = _version("fastmcp")
if settings.deprecation_warnings:
try:
from fastmcp.exceptions import FastMCPDeprecationWarning
except ImportError:
pass
else:
warnings.simplefilter("default", FastMCPDeprecationWarning)
# --- Lazy imports for performance (see #3292) ---
# Client and the client submodule are deferred so that server-only users
# don't pay for the client import chain. Do not convert back to top-level.
def __getattr__(name: str) -> object:
if name == "Client":
try:
from fastmcp.client import Client
except ImportError as exc:
raise ImportError(
"FastMCP client support is not installed. Install "
"`fastmcp-slim[client]` or `fastmcp`."
) from exc
return Client
if name == "Context":
try:
from fastmcp.server.context import Context
except ImportError as exc:
raise ImportError(
"FastMCP server support is not installed. Install "
"`fastmcp-slim[server]` or `fastmcp`."
) from exc
return Context
if name == "FastMCP":
try:
from fastmcp.server.server import FastMCP
except ImportError as exc:
raise ImportError(
"FastMCP server support is not installed. Install "
"`fastmcp-slim[server]` or `fastmcp`."
) from exc
return FastMCP
if name == "FastMCPApp":
try:
from fastmcp.apps.app import FastMCPApp
except ImportError as exc:
raise ImportError(
"FastMCP app support is not installed. Install "
"`fastmcp-slim[server,apps]` or `fastmcp[apps]`."
) from exc
return FastMCPApp
if name == "FastMCPDeprecationWarning":
from fastmcp.exceptions import FastMCPDeprecationWarning
return FastMCPDeprecationWarning
if name == "client":
try:
return importlib.import_module("fastmcp.client")
except ImportError as exc:
raise ImportError(
"FastMCP client support is not installed. Install "
"`fastmcp-slim[client]` or `fastmcp`."
) from exc
if name == "server":
try:
return importlib.import_module("fastmcp.server")
except ImportError as exc:
raise ImportError(
"FastMCP server support is not installed. Install "
"`fastmcp-slim[server]` or `fastmcp`."
) from exc
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"Client",
"Context",
"FastMCP",
"FastMCPApp",
"FastMCPDeprecationWarning",
"settings",
]

View file

@ -0,0 +1,36 @@
try:
from .auth import OAuth, BearerAuth
from .client import Client
from .transports import (
ClientTransport,
FastMCPTransport,
NodeStdioTransport,
NpxStdioTransport,
PythonStdioTransport,
SSETransport,
StdioTransport,
StreamableHttpTransport,
UvStdioTransport,
UvxStdioTransport,
)
except ImportError as exc:
raise ImportError(
"FastMCP client support is not installed. Install "
"`fastmcp-slim[client]` or `fastmcp`."
) from exc
__all__ = [
"BearerAuth",
"Client",
"ClientTransport",
"FastMCPTransport",
"NodeStdioTransport",
"NpxStdioTransport",
"OAuth",
"PythonStdioTransport",
"SSETransport",
"StdioTransport",
"StreamableHttpTransport",
"UvStdioTransport",
"UvxStdioTransport",
]

View file

@ -10,7 +10,7 @@ from collections.abc import Coroutine
from contextlib import AsyncExitStack, asynccontextmanager, suppress from contextlib import AsyncExitStack, asynccontextmanager, suppress
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any, Generic, Literal, TypeVar, cast, overload from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, overload
import anyio import anyio
import httpx import httpx
@ -20,9 +20,12 @@ from mcp import ClientSession, McpError
from mcp.types import GetTaskResult, TaskStatusNotification from mcp.types import GetTaskResult, TaskStatusNotification
from pydantic import AnyUrl from pydantic import AnyUrl
import fastmcp import fastmcp as fastmcp
from fastmcp.client.auth.oauth import OAuth from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.elicitation import ElicitationHandler, create_elicitation_callback from fastmcp.client.elicitation import (
ElicitationHandler,
create_elicitation_callback,
)
from fastmcp.client.logging import ( from fastmcp.client.logging import (
LogHandler, LogHandler,
create_log_callback, create_log_callback,
@ -52,7 +55,6 @@ from fastmcp.client.tasks import (
ToolTask, ToolTask,
) )
from fastmcp.mcp_config import MCPConfig from fastmcp.mcp_config import MCPConfig
from fastmcp.server import FastMCP
from fastmcp.utilities.exceptions import get_catch_handlers from fastmcp.utilities.exceptions import get_catch_handlers
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.timeout import ( from fastmcp.utilities.timeout import (
@ -60,6 +62,11 @@ from fastmcp.utilities.timeout import (
normalize_timeout_to_timedelta, normalize_timeout_to_timedelta,
) )
if TYPE_CHECKING:
from fastmcp.server import FastMCP
else:
FastMCP = Any
from .transports import ( from .transports import (
ClientTransport, ClientTransport,
ClientTransportT, ClientTransportT,

View file

@ -0,0 +1,20 @@
"""Client-side dependency helpers."""
def get_http_headers(
include_all: bool = False,
include: set[str] | None = None,
) -> dict[str, str]:
"""Return HTTP headers from an ambient server request, when available.
The standalone client package has no server request context. When the full
FastMCP package is installed, delegate to its request-aware implementation.
"""
try:
from fastmcp.server.dependencies import (
get_http_headers as get_server_http_headers,
)
except ImportError:
return {}
return get_server_http_headers(include_all=include_all, include=include)

View file

@ -58,7 +58,7 @@ def create_sampling_callback(
if isinstance(result, str): if isinstance(result, str):
result = CreateMessageResult( result = CreateMessageResult(
role="assistant", role="assistant",
model="fastmcp-client", model="fastmcp-slim",
content=mcp.types.TextContent(type="text", text=result), content=mcp.types.TextContent(type="text", text=result),
) )
return result return result

View file

@ -41,7 +41,7 @@ try:
except ImportError as e: except ImportError as e:
raise ImportError( raise ImportError(
"The `anthropic` package is not installed. " "The `anthropic` package is not installed. "
"Install it with `pip install fastmcp[anthropic]` or add `anthropic` to your dependencies." "Install it with `pip install fastmcp-slim[anthropic]` or add `anthropic` to your dependencies."
) from e ) from e
__all__ = ["AnthropicSamplingHandler"] __all__ = ["AnthropicSamplingHandler"]

View file

@ -27,7 +27,7 @@ try:
except ImportError as e: except ImportError as e:
raise ImportError( raise ImportError(
"The `google-genai` package is not installed. " "The `google-genai` package is not installed. "
"Install it with `pip install fastmcp[gemini]` or add `google-genai` " "Install it with `pip install fastmcp-slim[gemini]` or add `google-genai` "
"to your dependencies." "to your dependencies."
) from e ) from e

View file

@ -44,7 +44,7 @@ try:
except ImportError as e: except ImportError as e:
raise ImportError( raise ImportError(
"The `openai` package is not installed. " "The `openai` package is not installed. "
"Please install `fastmcp[openai]` or add `openai` to your dependencies manually." "Please install `fastmcp-slim[openai]` or add `openai` to your dependencies manually."
) from e ) from e
# OpenAI only supports wav and mp3 for input audio # OpenAI only supports wav and mp3 for input audio

View file

@ -1,4 +1,3 @@
# Re-export all public APIs for backward compatibility
from mcp.server.fastmcp import FastMCP as FastMCP1Server from mcp.server.fastmcp import FastMCP as FastMCP1Server
from fastmcp.client.transports.base import ( from fastmcp.client.transports.base import (
@ -20,7 +19,6 @@ from fastmcp.client.transports.stdio import (
UvStdioTransport, UvStdioTransport,
UvxStdioTransport, UvxStdioTransport,
) )
from fastmcp.server.server import FastMCP
__all__ = [ __all__ = [
"ClientTransport", "ClientTransport",

View file

@ -1,7 +1,7 @@
import contextlib import contextlib
import datetime import datetime
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from typing import Any from typing import TYPE_CHECKING, Any
from mcp import ClientSession from mcp import ClientSession
from typing_extensions import Unpack from typing_extensions import Unpack
@ -15,10 +15,13 @@ from fastmcp.mcp_config import (
StdioMCPServer, StdioMCPServer,
TransformingRemoteMCPServer, TransformingRemoteMCPServer,
TransformingStdioMCPServer, TransformingStdioMCPServer,
_coerce_tool_transform_configs,
) )
from fastmcp.server.server import FastMCP, create_proxy
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
if TYPE_CHECKING:
from fastmcp.server.server import FastMCP
logger = get_logger(__name__) logger = get_logger(__name__)
@ -98,6 +101,14 @@ class MCPConfigTransport(ClientTransport):
# each ProxyClient so its underlying transport session stays alive for # each ProxyClient so its underlying transport session stays alive for
# the duration of this context (fixes session persistence for # the duration of this context (fixes session persistence for
# streamable-http backends — see #2790). # streamable-http backends — see #2790).
try:
from fastmcp.server.server import FastMCP
except ImportError as exc:
raise ImportError(
"MCP configs with multiple servers require the full `fastmcp` "
"package for now. Install it with `pip install fastmcp`."
) from exc
timeout = session_kwargs.get("read_timeout_seconds") timeout = session_kwargs.get("read_timeout_seconds")
composite = FastMCP[Any](name="MCPRouter") composite = FastMCP[Any](name="MCPRouter")
@ -138,7 +149,7 @@ class MCPConfigTransport(ClientTransport):
config: MCPServerTypes, config: MCPServerTypes,
timeout: datetime.timedelta | None, timeout: datetime.timedelta | None,
stack: contextlib.AsyncExitStack, stack: contextlib.AsyncExitStack,
) -> tuple[ClientTransport, Any, FastMCP[Any]]: ) -> tuple[ClientTransport, Any, "FastMCP[Any]"]:
"""Create underlying transport, proxy client, and proxy server for a single backend. """Create underlying transport, proxy client, and proxy server for a single backend.
The ProxyClient is connected via the AsyncExitStack *before* being The ProxyClient is connected via the AsyncExitStack *before* being
@ -149,6 +160,7 @@ class MCPConfigTransport(ClientTransport):
""" """
# Import here to avoid circular dependency # Import here to avoid circular dependency
from fastmcp.server.providers.proxy import StatefulProxyClient from fastmcp.server.providers.proxy import StatefulProxyClient
from fastmcp.server.server import create_proxy
tool_transforms = None tool_transforms = None
include_tags = None include_tags = None
@ -194,7 +206,9 @@ class MCPConfigTransport(ClientTransport):
if tool_transforms: if tool_transforms:
from fastmcp.server.transforms import ToolTransform from fastmcp.server.transforms import ToolTransform
proxy.add_transform(ToolTransform(tool_transforms)) proxy.add_transform(
ToolTransform(_coerce_tool_transform_configs(tool_transforms))
)
# Then add enabled filters - they filter based on tags # Then add enabled filters - they filter based on tags
if include_tags: if include_tags:
proxy.enable(tags=set(include_tags), only=True) proxy.enable(tags=set(include_tags), only=True)

View file

@ -15,12 +15,12 @@ from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client
from pydantic import AnyUrl from pydantic import AnyUrl
from typing_extensions import Unpack from typing_extensions import Unpack
import fastmcp import fastmcp as fastmcp
from fastmcp.client.auth.bearer import BearerAuth from fastmcp.client.auth.bearer import BearerAuth
from fastmcp.client.auth.oauth import OAuth from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.dependencies import get_http_headers
from fastmcp.client.transports.base import ClientTransport, SessionKwargs from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.exceptions import FastMCPDeprecationWarning from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.server.dependencies import get_http_headers
from fastmcp.utilities.timeout import normalize_timeout_to_timedelta from fastmcp.utilities.timeout import normalize_timeout_to_timedelta

View file

@ -9,13 +9,17 @@ from fastmcp.client.transports.config import MCPConfigTransport
from fastmcp.client.transports.http import StreamableHttpTransport from fastmcp.client.transports.http import StreamableHttpTransport
from fastmcp.client.transports.memory import FastMCPTransport from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.client.transports.sse import SSETransport from fastmcp.client.transports.sse import SSETransport
from fastmcp.client.transports.stdio import NodeStdioTransport, PythonStdioTransport from fastmcp.client.transports.stdio import (
NodeStdioTransport,
PythonStdioTransport,
)
from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url
from fastmcp.server.server import FastMCP
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
if TYPE_CHECKING: if TYPE_CHECKING:
pass from fastmcp.server.server import FastMCP
else:
FastMCP = Any
logger = get_logger(__name__) logger = get_logger(__name__)
@ -114,9 +118,9 @@ def infer_transport(
return transport return transport
# the transport is a FastMCP server (2.x or 1.0) # the transport is a FastMCP server (2.x or 1.0)
elif isinstance(transport, FastMCP | FastMCP1Server): elif _is_fastmcp_server(transport):
inferred_transport = FastMCPTransport( inferred_transport = FastMCPTransport(
mcp=cast(FastMCP[Any] | FastMCP1Server, transport) mcp=cast("FastMCP[Any] | FastMCP1Server", transport)
) )
# the transport is a path to a script # the transport is a path to a script
@ -152,3 +156,15 @@ def infer_transport(
logger.debug(f"Inferred transport: {inferred_transport}") logger.debug(f"Inferred transport: {inferred_transport}")
return inferred_transport return inferred_transport
def _is_fastmcp_server(transport: object) -> bool:
if isinstance(transport, FastMCP1Server):
return True
try:
from fastmcp.server.server import FastMCP as FastMCP2Server
except ImportError:
return False
return isinstance(transport, FastMCP2Server)

View file

@ -1,5 +1,7 @@
import contextlib import contextlib
import importlib
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from typing import TYPE_CHECKING, Any
import anyio import anyio
from mcp import ClientSession from mcp import ClientSession
@ -8,7 +10,9 @@ from mcp.shared.memory import create_client_server_memory_streams
from typing_extensions import Unpack from typing_extensions import Unpack
from fastmcp.client.transports.base import ClientTransport, SessionKwargs from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.server.server import FastMCP
if TYPE_CHECKING:
from fastmcp.server.server import FastMCP
class FastMCPTransport(ClientTransport): class FastMCPTransport(ClientTransport):
@ -20,7 +24,9 @@ class FastMCPTransport(ClientTransport):
tests or scenarios where client and server run in the same runtime. tests or scenarios where client and server run in the same runtime.
""" """
def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False): def __init__(
self, mcp: "FastMCP[Any] | FastMCP1Server", raise_exceptions: bool = False
):
"""Initialize a FastMCPTransport from a FastMCP server instance.""" """Initialize a FastMCPTransport from a FastMCP server instance."""
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
@ -87,10 +93,22 @@ class FastMCPTransport(ClientTransport):
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def _enter_server_lifespan( async def _enter_server_lifespan(
server: FastMCP | FastMCP1Server, server: "FastMCP[Any] | FastMCP1Server",
) -> AsyncIterator[None]: ) -> AsyncIterator[None]:
"""Enters the server's lifespan context for FastMCP servers and does nothing for FastMCP 1 servers.""" """Enters the server's lifespan context for FastMCP servers and does nothing for FastMCP 1 servers."""
if isinstance(server, FastMCP): FastMCP2: type[Any] | None
try:
FastMCP2 = importlib.import_module("fastmcp.server.server").FastMCP
except ImportError:
FastMCP2 = None
if FastMCP2 is None and not isinstance(server, FastMCP1Server):
raise ImportError(
"In-memory FastMCP transports require the full `fastmcp` package. "
"Install it with `pip install fastmcp`."
)
if FastMCP2 is not None and isinstance(server, FastMCP2):
async with server._lifespan_manager(): async with server._lifespan_manager():
yield yield
else: else:

View file

@ -17,8 +17,8 @@ from typing_extensions import Unpack
from fastmcp.client.auth.bearer import BearerAuth from fastmcp.client.auth.bearer import BearerAuth
from fastmcp.client.auth.oauth import OAuth from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.dependencies import get_http_headers
from fastmcp.client.transports.base import ClientTransport, SessionKwargs from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.server.dependencies import get_http_headers
from fastmcp.utilities.timeout import normalize_timeout_to_timedelta from fastmcp.utilities.timeout import normalize_timeout_to_timedelta

View file

@ -14,7 +14,6 @@ from typing_extensions import Unpack
from fastmcp.client.transports.base import ClientTransport, SessionKwargs from fastmcp.client.transports.base import ClientTransport, SessionKwargs
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
logger = get_logger(__name__) logger = get_logger(__name__)
@ -385,20 +384,18 @@ class UvStdioTransport(StdioTransport):
f"Project directory not found: {project_directory}" f"Project directory not found: {project_directory}"
) )
# Create Environment from provided parameters (internal use)
env_config = UVEnvironment(
python=python_version,
dependencies=with_packages,
requirements=with_requirements,
project=project_directory,
editable=None, # Not exposed in this transport
)
# Build uv arguments using the config # Build uv arguments using the config
uv_args: list[str] = [] uv_args: list[str] = []
# Check if we need any environment setup # Check if we need any environment setup
if env_config._must_run_with_uv(): if any(
[
python_version,
with_packages,
with_requirements,
project_directory,
]
):
# Use the config to build args, but we need to handle the command differently # Use the config to build args, but we need to handle the command differently
# since transport has specific needs # since transport has specific needs
uv_args = ["run"] uv_args = ["run"]

View file

@ -2,7 +2,12 @@
import logging import logging
from mcp import McpError # noqa: F401 try:
from mcp import McpError
except ImportError:
class McpError(Exception): # type: ignore[no-redef]
"""Fallback used when MCP dependencies are not installed."""
class FastMCPDeprecationWarning(DeprecationWarning): class FastMCPDeprecationWarning(DeprecationWarning):

Some files were not shown because too many files have changed in this diff Show more